Unity Workflow LibraryUWLAbout

Topic 2 · Scene flow

Pitfalls: gameplay logic & readiness gates

Silent readiness signals, retry flags, stable keys and shared placement paths.

Generalized pitfalls harvested from shipped and active projects. Each entry: rule, what broke, why, check. Record your own project's traps the same way: Keeping a project pitfall file. All rules here are LOCKED evidence (the customizable rules page LOCKED-VS-CUSTOMIZABLE). Status: locked (empirical, seen in shipped/active projects).

  • SILENT-SIGNAL-COUNTS-AS-SATISFIED — a readiness input that is never produced in this build must count as satisfied, not pending. Why: "not reported" was treated as "not ready". Check: for every gate input, name which builds/worlds write it; inputs with no writer in a configuration are excluded or pre-satisfied there. Status: locked (empirical, seen in shipped/active projects).
  • FIRST-TIME-FLAG-IS-NOT-A-RETRY-FLAG — never key a "first time" test off a flag that also means "retry next tick". Why: two states shared one bit. Check: one-shot init and retry have separate fields; a failure path cannot reset the "initialised" field. Status: locked (empirical, seen in shipped/active projects).
  • CATALOG-ORDER-IS-NOT-PRIORITY-ORDER — fixed-order iteration over a category-sorted list streams the world in category order, not distance order. Why: catalog order encodes authoring categories, not relevance. Check: streaming/LOD work queues sort by distance or importance each pass, never by source-list order. Status: locked (empirical, seen in shipped/active projects).
  • BUDGETING-SPREADS-COST-DOES-NOT-REMOVE-IT — a per-frame cap on expensive boot work only moves the total. Why: frame budgeting changes distribution, not work. Check: measure total cost before budgeting; remove or cache the work first, then budget what remains. See also LOADING-WORK-IS-FRAME-BUDGETED (the scene flow page). Status: locked (empirical, seen in shipped/active projects).
  • BASELINE-IS-CAPTURED-ONCE — a scale/offset applier captures the authored baseline once per target, never from the current (already modified) value. Why: the captured value already contained the last application. Check: baselines are keyed by target and written only when absent; re-apply from the stored baseline. Status: locked (empirical, seen in shipped/active projects).
  • STABLE-KEYS-OUTLIVE-PLACEMENT-ALGORITHMS — ids derived from a procedural placement sequence break every saved reference when the sequence changes. Why: identity was the sequence index, not a stable cell+salt key. Check: persisted world-object keys are derived from a stable spatial cell and a per-cell slot, and placement changes ship with a key migration or a version stamp. Status: locked (empirical, seen in shipped/active projects).
  • SERVER-AND-CLIENT-SHARE-ONE-PLACEMENT-PATH — when server and client both generate the same procedural content, they must run the same code path. Why: two implementations of "the same" math diverge. Check: one shared deterministic function (same compilation mode) or the server replicates the result; a parity test compares both outputs. Status: locked (empirical, seen in shipped/active projects).
  • WORLD-EVENTS-ARE-SEEN-BY-EVERYONE — a state change triggered by one player (felling, destroying, opening) must replicate to all observers, not play only on the actor's client. Why: the effect was client-local and the authoritative update rode a long cadence. Check: every world mutation has a replicated event or state change; test with a second client watching. Status: locked (empirical, seen in shipped/active projects).
  • TIMEOUT-ADVANCE-HIDES-THE-SCREEN — an auto-advancing timeout makes the screen it leaves impossible to inspect or test. Why: the timeout doubled as navigation. Check: timeouts surface a visible state (error/retry) rather than silently navigating; tests can disable the timer. Status: locked (empirical, seen in shipped/active projects).
  • PICKER-CARRIES-THE-ACTION-AND-CLOSES-PER-CHOICE — a target/choice picker result must carry which action it answers, and must close after each choice. Why: the result event relied on ambient "current action" state. Check: picker result payloads include the action id; each open has exactly one close. Status: locked (empirical, seen in shipped/active projects).
  • IDLE-PRESENTATION-KEEPS-THE-TURN-ACTOR — an idle/attract presentation must not drop the current-turn player. Why: presentation code wrote gameplay state. Check: presentation layers read turn state, never write it. Status: locked (empirical, seen in shipped/active projects).