---
title: "Pitfalls: netcode & persistence"
slug: /workflows/pitfalls-netcode/
markdown: /workflows/pitfalls-netcode.md
section: pitfalls
tier: 2
summary: "Intent-only clients, relevancy, late join, persistence."
sources: ['pitfalls/netcode.md']
canonical: https://unityworkflowlibrary.dev/workflows/pitfalls-netcode/
read_first: /read-first.md
---

> Read first, on every prompt: [non-destructive rules](/read-first.md).

# Pitfalls: netcode & persistence

> Intent-only clients, relevancy, late join, persistence.

Generalized pitfalls from shipped and active projects. Record your own project's traps the same way: [Keeping a project pitfall file](/workflows/pitfall-file/). All entries LOCKED evidence. Status: locked (empirical, seen in shipped/active projects).

- **CLIENTS-SEND-INTENT-ONLY — clients send input or intent, never durable state or outcomes; the server owns movement truth, combat, inventory, rewards and persistence.** Check: every client->server message is an input or a request the server validates. See also OWNERSHIP-KEYED-ON-DURABLE-ID (the [Netcode overview](/workflows/networking-overview/) page). Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (CLIENTS-SEND-INTENT-ONLY): Projects that let clients report results had to be rewritten for authority.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **VISIBLE-IS-NOT-RELEVANT — a client-visible entity is not automatically client-relevant; interest management limits bandwidth and cheat exposure.** Check: relevance is defined per entity type before high-volume entities ship. Status: locked (empirical, seen in shipped/active projects).
- **RELEVANCY-RADIUS-FITS-THE-FASTEST-MOVER — a relevancy radius sized for weapon range lets fast vehicles pop in late and close.** Why: radius ignored closing speed. Check: radius >= (max closing speed x entry latency) plus margin, or pace region entry for fast movers. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (RELEVANCY-RADIUS-FITS-THE-FASTEST-MOVER): Aircraft crossed the radius in under a second and appeared next to players.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **SCALE-SENDS-BY-IMPORTANCE — with many relevant players at high tick rate, send by importance/distance, not round-robin.** Check: ghost/object importance scales with distance; a crowd test reports snapshot size. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (SCALE-SENDS-BY-IMPORTANCE): Crowded scenes overflowed snapshot packets and near players updated as rarely as far ones.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **REWIND-EVERYTHING-YOU-CAN-HIT — lag compensation must rewind every hittable body (NPCs, animals, dummies) and its depth must cover the supported RTT.** Check: a lag-comp test at the max supported RTT hits every target class. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (REWIND-EVERYTHING-YOU-CAN-HIT): Hits on players were rewound but hits on creatures were not, and rewind depth was capped below real latencies.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **ONE-SHARED-FRAME-NOT-PER-PLAYER — do not mint coordinate frames/physics worlds per player position.** Why: frame creation keyed on the requester. Check: frames are keyed by fixed spatial cells shared by all players. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (ONE-SHARED-FRAME-NOT-PER-PLAYER): A server created a new local chart and physics world per wandering player, so nearby players lived in different frames.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **LATE-JOIN-REPLAY-IS-SCOPED-AND-PACED — a late joiner's world replay must be scoped by relevance and streamed, not dumped and throttled.** Check: replay only relevant content; queue, never drop, initial-sync requests. See also LATE-JOIN-SNAPSHOT-STREAMS-PER-PIECE. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (LATE-JOIN-REPLAY-IS-SCOPED-AND-PACED): Every client received every building piece in the world, and a late joiner waited tens of seconds behind a rate limit that also dropped over-budget requests.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **NO-RELIABLE-RPC-FOR-HIGH-FREQUENCY-STATE — high-frequency state goes in replicated snapshots, not reliable RPCs.** Check: RPCs are events; per-tick values are ghost fields/NetworkVariables. Status: locked (empirical, seen in shipped/active projects).
- **PREDICTION-STAYS-PENDING — client predictions stay explicitly pending until server confirmation.** Check: predicted state is visually and logically distinct until confirmed. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (PREDICTION-STAYS-PENDING): UI showed predicted purchases/hits as final, then silently reverted.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **RECOMPILE-COPIED-NETCODE — never copy a system from an older netcode package version without recompiling its lifecycle and snapshot APIs against the current package.** Check: copied netcode is diffed against the current package API, not just compiled. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (RECOMPILE-COPIED-NETCODE): Copied systems compiled but used retired lifecycle hooks and serialized wrong.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **NETWORKED-OBJECTS-OUTLIVE-MENU-SCENES-ONLY-IF-PLACED-RIGHT — an object spawned while a menu scene is active dies when the client unloads that scene.** Why: it was parented to/registered with the menu scene. Check: spawn session objects into a persistent scene or after the gameplay scene is active (`DestroyWithScene` false where appropriate). Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (NETWORKED-OBJECTS-OUTLIVE-MENU-SCENES-ONLY-IF-PLACED-RIGHT): A networked object spawned mid-menu vanished on the client at scene change.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **ONE-PREFAB-ONE-REGISTRATION-LIST — the same prefab in two network prefab lists unregisters it.** Check: one source list per prefab; a validator flags duplicates. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (ONE-PREFAB-ONE-REGISTRATION-LIST): A bootstrap that added a second list containing an already-listed prefab made spawns fail.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **MULTIPLAYER-PLAYMODE-PACKAGE-CAN-BE-A-FACADE — verify the multiplayer play-mode package actually contains its runtime on your editor version.** Check: confirm the package's assemblies/types exist before relying on it. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (MULTIPLAYER-PLAYMODE-PACKAGE-CAN-BE-A-FACADE): A package version installed as an empty facade on a newer editor; virtual players never started.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **STORE-ISSUES-REVISIONS — let the persistence store issue revisions; never keep a counter beside it.** Check: revision comes from the store's transaction. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (STORE-ISSUES-REVISIONS): A side counter diverged from the store after restart and accepted stale writes.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **SMOKE-TEST-ACROSS-A-RESTART — a persistence smoke test must close and reopen the same store file.** Check: test writes, closes, reopens and reads. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (SMOKE-TEST-ACROSS-A-RESTART): Single-session round-trips passed while restart-only faults (unflushed writes, cache-only state) shipped.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **ONE-WRITER-PROCESS-PER-STORE — two processes over one store file lose updates.** Why: coalescing caches and revision counters are per process. Check: one writer per store; others go through it. Status: locked (empirical, seen in shipped/active projects).
- **COMMIT-THEN-MARK-CLEAN — advance dirty hashes/flags only after the database transaction commits.** Check: clean-marking happens after commit success. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (COMMIT-THEN-MARK-CLEAN): A failed write left records marked clean and never retried.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **NO-WHOLE-TABLE-REPLACE-FROM-PARTIAL-DATA — never replace a full table with a partial or client-derived payload.** Check: writes are row-level upserts/deletes with explicit scope. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (NO-WHOLE-TABLE-REPLACE-FROM-PARTIAL-DATA): A partial snapshot wiped rows it did not contain.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **DB-OPEN-FAILURE-IS-FATAL — a database open failure is a server startup failure, not an empty-world fallback.** Check: open failure aborts startup with an error. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (DB-OPEN-FAILURE-IS-FATAL): A locked database started a fresh empty world that then overwrote saves.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **RESTORE-BEFORE-CAPTURE — never capture/save a world before its domains restored successfully; clear restore gates when the world is recreated in-process.** Check: save is gated on restore success; world teardown resets the gate. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (RESTORE-BEFORE-CAPTURE): An autosave right after a failed load persisted the empty world; a recreated server world skipped restore because the gate was still set.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **SNAPSHOT-BEFORE-DESPAWN — snapshot disconnecting players before destroying or releasing their networked objects.** Check: disconnect handler saves, then despawns. Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (SNAPSHOT-BEFORE-DESPAWN): Last-session progress was lost because the player object was destroyed first.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **ECONOMY-NEEDS-ROWS-NOT-BLOBS — snapshot blobs are for regenerable state only; anything an economy depends on needs row-level records and real transactions.** Check: currency, inventory and ownership are rows with transactions. Status: locked (empirical, seen in shipped/active projects).
- **ONE-COMMAND-ID-PER-DECISION — retries of the same logical action reuse one command id and replay the original result; deterministic rejections are recorded, infrastructure faults throw.** Check: idempotency store keyed by command id; only deterministic outcomes are persisted. See also IDEMPOTENCY-RECORD-ORDER-BY-RETRY-COST (the ugs page). Status: locked (empirical, seen in shipped/active projects).

> **Why this rule exists** (ONE-COMMAND-ID-PER-DECISION): Retries double-applied, and transient faults were recorded as permanent rejections.
> *Source: Pitfall log of a shipped or active Unity project, generalized.*

- **LOCAL-SAVES-AUTHORIZE-NOTHING-ONLINE — local saves are not proof of integrity and may not authorize online economy, ownership or participation; client anti-cheat is friction, not authority; admin bypasses are isolated from production builds.** Check: every online action resolves against server records. Status: locked (empirical, seen in shipped/active projects).
