split the scene into Bootstrap, Systems and Level
The game now starts from Bootstrap.unity, which additively loads Systems (the dialogue runner, skills, UI layer, camera rig and the player) and then a level (geometry, light, navmesh, reveal cameras). The level becomes the active scene so new objects and lighting land there. The player moved into Systems. It had been parented under Room_101 — under level geometry — and it is persistent content, not level content. Moving it also removes most of the cross-scene breakage on its own. Unity nulls any serialized reference that crosses a scene boundary. Measured rather than guessed: the pre-split scene was pulled from git and its null references diffed against the split result. Exactly seven were lost — both NPCs' dialogueRunner, dialogueUI and player, plus the reveal camera's tracking target. A first naive audit reported 210, which turned out to be pre-existing Unity defaults like Image.m_Material; the baseline diff is what separated the two. Each of the seven now resolves at runtime. SceneServices.Resolve fills in a null Inspector reference by searching the loaded scenes, keeping an assigned value if there is one; CinemachineFollowsPlayer binds the reveal camera once the player exists. Both follow the pattern already used by CameraFramingVolume and by NPCStandIn's player lookup, rather than introducing a new one. Assets/Tests/PlayMode is new, and it immediately paid for itself. BootstrapTests loads Bootstrap and asserts the whole game comes up; on its first run it caught a real bug the split had introduced. The player's NavMeshAgent lives in Systems while the NavMesh is baked into the level, so during load the agent exists off-mesh and ResetPath logs an error. ClickToMoveController now guards on agent.isOnNavMesh rather than a bare null check. No EditMode test could have seen that, because none of it has run yet. Two of those checks reach their types by name through reflection: NPCStandIn and the Cinematics namespace live in Assembly-CSharp, and an asmdef test assembly cannot reference the predefined assemblies. Per-area asmdefs remove the need. Build settings list all three scenes with Bootstrap at index 0, which LoadSceneAsync by name requires. EditMode 42/42, PlayMode 5/5, YarnCheck 9 files / 35 nodes. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -47,12 +47,12 @@ AI Navigation 2.0.14, ProBuilder 6.1.2, Test Framework 1.7.0. Yarn Spinner is an
|
||||
overwritten by the Editor setup scripts. Edit the JSON.
|
||||
2. **Never hand-edit scene or prefab YAML unless you have read the surrounding block.**
|
||||
GUID/fileID surgery in `.unity` / `.prefab` files silently detaches components.
|
||||
3. **One Unity instance per project path.** Unity refuses to open a project in batch
|
||||
5. **One Unity instance per project path.** Unity refuses to open a project in batch
|
||||
mode while the Editor has it open — a single instance at a time. Always check before
|
||||
any `-batchmode` invocation (§3.1).
|
||||
4. **Prefer YarnCheck over Unity for dialogue validation.** It is seconds, not minutes,
|
||||
and needs no Editor lock (§5.4).
|
||||
5. **Do not commit on the user's behalf** unless asked. Unity writes to tracked files
|
||||
6. **Prefer YarnCheck over Unity for dialogue validation.** It is seconds, not minutes,
|
||||
and needs no Editor lock (§5.5).
|
||||
7. **Do not commit on the user's behalf** unless asked. Unity writes to tracked files
|
||||
(`ProjectSettings/*`, `.meta`, scenes) as a side effect of simply being open, so
|
||||
`git status` noise is expected and is not always yours.
|
||||
|
||||
@@ -133,10 +133,11 @@ misread the check. `Temp/UnityLockfile` and `unity status` are the other two sig
|
||||
Useful flags: `--filter <pattern>`, `--retries <0-10>` (reports tests that pass on retry as
|
||||
flaky), `--rerun-failed`, `--shard N/M` for parallel CI, `--coverage`, `--timeout <seconds>`.
|
||||
|
||||
`--mode PlayMode` runs the PlayMode suite. **There are none yet** — `Assets/Tests/` is EditMode
|
||||
only, so that command reports zero tests. A PlayMode suite needs a new
|
||||
`Assets/Tests/PlayMode/*.asmdef` referencing `UnityEngine.TestRunner` with `includePlatforms`
|
||||
left empty.
|
||||
`--mode PlayMode` runs `Assets/Tests/PlayMode` — currently `BootstrapTests`, which loads
|
||||
`Bootstrap.unity` and asserts the whole game comes up: both scenes loaded, the player present
|
||||
and tagged, the dialogue runner found, both NPCs having resolved their runner across the scene
|
||||
boundary, and the reveal camera bound to the player. **If that suite fails, the game does not
|
||||
start.**
|
||||
|
||||
Raw-Editor equivalent, if you need a flag the CLI does not expose:
|
||||
|
||||
@@ -208,6 +209,20 @@ NightclubArcadia/Assets/Dialogue/
|
||||
|
||||
The writer-facing context sits at the repo root, outside `Assets/` so it gets no `.meta` files:
|
||||
|
||||
The scenes are split three ways, loaded additively from `Bootstrap`:
|
||||
|
||||
```
|
||||
Assets/Scenes/
|
||||
├── Bootstrap.unity entry point; GameBootstrap loads the rest
|
||||
├── Systems/Systems.unity dialogue, skills, UI, camera rig, and the PLAYER
|
||||
└── Levels/
|
||||
├── SC101_ConferenceHall.unity geometry, light, navmesh, reveal cameras
|
||||
└── SC101_ConferenceHall/ baked data for that scene (NavMesh asset)
|
||||
```
|
||||
|
||||
`Bootstrap` must stay index 0 in the build settings. The level is made the active scene after
|
||||
load, so new objects and lighting land there rather than in Bootstrap.
|
||||
|
||||
```
|
||||
writing/
|
||||
├── README.md session entry point
|
||||
@@ -357,7 +372,17 @@ or a branch.** If a writing task requires either, that is a pipeline bug — fil
|
||||
`$self_lean_*` / `$found_*` in any `.yarn` file resolves to a real id in `CandidateDatabase`.
|
||||
It is a tripwire, not a proof.
|
||||
|
||||
### 5.2 Voice-sheet sync
|
||||
### 5.2 PlayMode suite
|
||||
|
||||
`Assets/Tests/PlayMode/BootstrapTests.cs`. Verifies the scene split at runtime — the thing no
|
||||
EditMode test can see, because none of the runtime resolution has happened yet.
|
||||
|
||||
Two of its checks reach their types by name through reflection. `NPCStandIn` and
|
||||
`NightclubArcadia.Cinematics` live in `Assembly-CSharp`, and an asmdef test assembly cannot
|
||||
reference the predefined assemblies — only the reverse. Giving the runtime code its own asmdefs
|
||||
removes the need; until then the reflection is confined to those two tests.
|
||||
|
||||
### 5.3 Voice-sheet sync
|
||||
|
||||
```bash
|
||||
python3 tools/writing/sync_voices.py --check
|
||||
@@ -365,11 +390,11 @@ python3 tools/writing/sync_voices.py --check
|
||||
|
||||
Seconds, no Unity. Run it after touching `writing/voices/*.md` or `skill_bible.json` (§4.6).
|
||||
|
||||
### 5.3 One-click in-Editor run
|
||||
### 5.4 One-click in-Editor run
|
||||
|
||||
`Tools ▸ Nightclub Arcadia ▸ Run Skill EditMode Tests` (`Assets/Editor/SkillSystemTestRunner.cs`).
|
||||
|
||||
### 5.4 YarnCheck — validate dialogue without Unity ⭐
|
||||
### 5.5 YarnCheck — validate dialogue without Unity ⭐
|
||||
|
||||
`tools/YarnCheck` is a .NET 9 console app that compiles **and plays** the Yarn scripts using
|
||||
the exact Yarn Spinner DLLs shipped in `Packages/dev.yarnspinner.unity/Runtime/DLLs`, so it
|
||||
@@ -420,29 +445,48 @@ should not be — see `docs/restructure-plan.md`.
|
||||
|
||||
## 7. Traps
|
||||
|
||||
1. **`Assets/Scenes/DialogueTest.unity` is serialized as binary**, and Unity will not convert it
|
||||
from a headless run. The project is set to Force Text (`m_SerializationMode: 2`) and the API
|
||||
agrees — `EditorSettings.serializationMode` reads `ForceText` — but Force Text governs
|
||||
conversion **at the moment the setting changes**, not on every save. A file that entered the
|
||||
project as binary stays binary. Four approaches were tried and all failed silently:
|
||||
`AssetDatabase.ForceReserializeAssets` does not rewrite scene files; `OpenScene` +
|
||||
`MarkSceneDirty` + `SaveScene` to the same path returns `true` and writes nothing, because
|
||||
`MarkSceneDirty` does not take in batch mode; `SaveScene` to a *new* path writes a fresh file
|
||||
that is also binary; and re-assigning `serializationMode` (Mixed → ForceText) from script
|
||||
does not trigger a reserialize pass. **The fix is Editor-UI only:** Project Settings ▸ Editor
|
||||
▸ Asset Serialization, switch the mode and switch it back, which reserializes on apply.
|
||||
Until then the scene is not diffable or mergeable. It is committed and intact (14 roots).
|
||||
2. **`unity run` reserves `-batchmode`, `-nographics`, `-quit`, `-logFile`.** Passing any of them
|
||||
1. **A scene silently turns binary if it holds an object that prefers binary serialization.**
|
||||
This one cost real time. `DialogueTest.unity` was binary for weeks despite the project being
|
||||
set to Force Text, because the `NavMeshSurface` held its baked `NavMeshData` *embedded in the
|
||||
scene* rather than as an asset. One such object forces the whole file to binary, and no amount
|
||||
of re-saving or toggling the setting fixes it — Force Text only converts at the moment the
|
||||
setting changes. The fix is to extract the data to its own asset, which is what baking from
|
||||
the NavMeshSurface inspector produces anyway. If a scene goes binary again, bisect: move the
|
||||
roots one at a time into a fresh scene and see which one yields a binary file.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
mesh. `ResetPath`, `isStopped` and `SetDestination` all log errors in that state. Guard on
|
||||
`agent.isOnNavMesh`, not just a null check.
|
||||
|
||||
4. **An asmdef assembly cannot reference `Assembly-CSharp`.** Only the reverse. Most runtime code
|
||||
here has no asmdef, so a test assembly cannot see `NPCStandIn` or `NightclubArcadia.Cinematics`
|
||||
directly — hence the reflection in `BootstrapTests`. Adding per-area asmdefs removes this.
|
||||
|
||||
5. **`unity run` reserves `-batchmode`, `-nographics`, `-quit`, `-logFile`.** Passing any of them
|
||||
after `--` is a hard error, and its output does not reach stdout — read `Logs/Editor.log` (§3.4).
|
||||
3. **`-quit` with `-runTests`** hangs or truncates. Omit it (§3.2).
|
||||
4. **Exit code alone is not a test verdict.** Parse the results XML (§3.2).
|
||||
5. **`which unity` fails** in non-interactive shells even though the CLI is installed. Use the
|
||||
|
||||
6. **`-quit` with `-runTests`** hangs or truncates. Omit it (§3.2).
|
||||
|
||||
7. **Exit code alone is not a test verdict.** Parse the results XML (§3.2), which is what
|
||||
`make test` does.
|
||||
|
||||
8. **`which unity` fails** in non-interactive shells even though the CLI is installed. Use the
|
||||
absolute path `/Users/lennart/.unity/bin/unity` (§3.0).
|
||||
6. **Editor lock** — check before every headless run, filtering out `AssetImportWorker` children (§3.1).
|
||||
7. **Regenerated assets** — hand edits to `Skills/Definitions/*`, `Candidates/Definitions/*`
|
||||
and the two `*Database.asset` files are silently discarded (§2.1).
|
||||
8. **`top_skill` ties** resolve to the first argument. Reordering arguments changes the game.
|
||||
9. **No `.gitattributes`** — no Unity YAML merge driver, no LFS rule for `Assets/Art/`.
|
||||
Scene and prefab merges will conflict destructively under branching.
|
||||
10. **Template leftovers still present**: `Assets/Scenes/SampleScene.unity`, `Assets/Readme.asset`,
|
||||
`Assets/TutorialInfo/`. `SampleScene` is in the build scene list. Not load-bearing.
|
||||
|
||||
9. **Editor lock** — check before every headless run, filtering out `AssetImportWorker` children.
|
||||
`make test` and `make build` do this for you (§2b).
|
||||
|
||||
10. **Regenerated assets** — hand edits to `Skills/Definitions/*`, `Candidates/Definitions/*`
|
||||
and the two `*Database.asset` files are silently discarded (§2.1).
|
||||
|
||||
11. **`top_skill` ties** resolve to the first argument. Reordering arguments changes the game.
|
||||
|
||||
12. **`Bootstrap` must stay index 0** in the build settings, and all three scenes must be listed,
|
||||
or `LoadSceneAsync` by name fails at runtime with nothing but a console error.
|
||||
|
||||
Reference in New Issue
Block a user