resolve cross-scene dependencies at the point of use, not in Awake
Fixes "[SceneServices] Bartender: could not resolve dialogueRunner". Resolving a cross-scene dependency in Awake is a race by construction. The dialogue runner lives in Systems; an NPC lives in the level. Open the level on its own, or let the scenes load in the other order, and the NPC wakes with no Systems scene in sight. Worse, the failed lookup was cached into the serialized field, so it stayed broken for the rest of the session rather than recovering once Systems appeared. NPCStandIn and DialogueInteractable now resolve lazily through a property and cache only a successful result, so any load order works. Awake still does a silent best-effort pass, which keeps the common case free. SceneServices splits in two accordingly. TryResolve is silent and is what speculative callers use; Resolve logs an error and belongs only where the dependency is genuinely required. Both now include inactive objects — a system parked inactive is still the object we mean, and excluding it reported "not found" for something sitting in the hierarchy. The error text now says where the runner lives and how to open the scenes, instead of asking a question. BootstrapTests only ever covered the happy order, which is why it stayed green while this was broken. SceneOrderTests loads the level BEFORE Systems and holds that NPCs still reach the runner. It also asserts it is reproducing the race — it counts the NPCs whose Awake-time resolution failed and fails if that is zero, so the test cannot quietly start passing for the wrong reason. It currently reports 2 of 2, which is the bug this commit fixes. EditMode 42/42, PlayMode 7/7, YarnCheck 9 files / 35 nodes. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -482,9 +482,18 @@ should not be — see `docs/restructure-plan.md`.
|
||||
|
||||
2. **Serialized references cannot cross a scene boundary.** Unity nulls them. The split cost
|
||||
exactly seven: both NPCs' `dialogueRunner`, `dialogueUI` and `player`, plus the reveal
|
||||
camera's tracking target. All are resolved at runtime now — `SceneServices.Resolve` for the
|
||||
first, `CinemachineFollowsPlayer` for the last. Anything new that a level object needs from
|
||||
Systems must follow that pattern, and `BootstrapTests` is where you prove it works.
|
||||
camera's tracking target. All are resolved at runtime now — `SceneServices` for the first,
|
||||
`CinemachineFollowsPlayer` for the last.
|
||||
|
||||
**Resolve at the point of use, never in `Awake`.** A level object can wake up before the
|
||||
scene holding its dependency exists — open the level on its own, or in the wrong order, and
|
||||
`Awake` runs with no Systems scene in sight. That produced
|
||||
`[SceneServices] Bartender: could not resolve dialogueRunner`, and because the result was
|
||||
cached once, it stayed broken for the whole session. `SceneServices.TryResolve` is the silent
|
||||
lookup for speculative calls; `SceneServices.Resolve` logs and belongs only where the
|
||||
dependency is genuinely needed. `NPCStandIn.ResolvedDialogueRunner` is the pattern to copy.
|
||||
`SceneOrderTests` holds the property, and asserts it is actually reproducing the race rather
|
||||
than passing for free.
|
||||
|
||||
3. **The player's NavMeshAgent outlives the NavMesh.** The player is in Systems, the NavMesh is
|
||||
baked into the level, so there is a window during load where the agent exists and is not on a
|
||||
|
||||
Reference in New Issue
Block a user