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]>
600 lines
34 KiB
Markdown
600 lines
34 KiB
Markdown
# Restructure Plan — writer-facing content pipeline + migration off the test scaffold
|
||
|
||
Status: **proposal, not executed.** Nothing in this document has been applied to the
|
||
repository. Two files were written as part of producing it — `CLAUDE.md` and `AGENTS.md`
|
||
at the repo root — because they were requested as deliverables. No asset, script, scene
|
||
or setting was moved, renamed or altered.
|
||
|
||
Companion reading: `docs/skill-system-refactor-plan.md`, `docs/candidate-system-plan.md`,
|
||
`docs/candidate-system-implementation-plan.md`.
|
||
|
||
---
|
||
|
||
## 0. Status — Phases 0, 1, 2 and 3 are complete
|
||
|
||
Executed 2026-08-25. The working tree is clean and tagged `pre-restructure`.
|
||
|
||
All previously uncommitted work is now in git across five commits: camera/locomotion/interaction,
|
||
the UI layer, player control setup, the dialogue-vs-menu interaction fix, and the scene itself.
|
||
A full pre-flight backup was taken first (`.tar.gz` of the tree excluding `Library/`, `Temp/`,
|
||
`Logs/`, and build output) plus a separate copy of the scene file.
|
||
|
||
Phases 1, 2 and 3 are also done — see §4. Only Phase 4 (code structure) and Phase 5 (replacing the NPC scaffold) remain proposal.
|
||
|
||
### 0.1 The binary scene — RESOLVED in Phase 3
|
||
|
||
This went through two wrong diagnoses before the real one. Recorded in full because the wrong
|
||
turns are the instructive part.
|
||
|
||
**First theory (wrong):** the scene had been saved during a temporary Force Binary session and
|
||
merely needed re-saving.
|
||
|
||
**Second theory (wrong, but closer):** Force Text only converts at the moment the setting
|
||
changes, so a file that entered binary stays binary — and the fix is therefore an Editor-UI
|
||
toggle. Four headless approaches were tried and all failed silently with success return codes:
|
||
`ForceReserializeAssets` does not rewrite scenes; `MarkSceneDirty` + `SaveScene` writes nothing
|
||
in batch mode; `SaveScene` to a *new* path reproduces binary; re-assigning `serializationMode`
|
||
triggers no reserialize pass.
|
||
|
||
**The actual cause:** the `NavMeshSurface` on the `Navigation` root held its baked `NavMeshData`
|
||
*embedded in the scene* rather than as an asset. `NavMeshData` prefers binary serialization, and
|
||
a single such object forces the entire scene file to binary no matter what the project setting
|
||
says. That is why every attempt to re-save it failed — the file was being written correctly each
|
||
time, in the only format its contents allowed.
|
||
|
||
Found by bisection: moving the roots one at a time into a fresh scene, thirteen produced text
|
||
files and `Navigation` produced a binary one. Extracting the data to
|
||
`Scenes/Levels/SC101_ConferenceHall/NavMesh-Navigation.asset` — which is what baking from the
|
||
NavMeshSurface inspector produces anyway; the embedded copy was the anomaly — made the scene
|
||
serialize as text. No Editor-UI step was required.
|
||
|
||
The scene is now `Assets/Scenes/Levels/SC101_ConferenceHall.unity`, 43KB of YAML, diffable and
|
||
mergeable, with the UnityYAMLMerge driver configured (Phase 1). The generalised lesson is
|
||
trap 1 in `CLAUDE.md`.
|
||
|
||
### 0.2 The Unity CLI — finding corrected
|
||
|
||
The initial survey reported the standalone `unity` CLI as not installed and lacking build/test
|
||
commands. It **is** installed, at `/Users/lennart/.unity/bin/unity` (v1.0.0-beta.6), and it has
|
||
both `build` and `test`, plus `run`, `open`, and editor management. The original check failed
|
||
because the binary is not on `PATH` for non-interactive shells — `unity doctor` flags this
|
||
itself as `check.binary-on-path warn`.
|
||
|
||
It is the better tool for CI work than raw `-batchmode`: `unity test` handles NUnit **and**
|
||
JUnit reports, `--retries` with flaky detection, `--rerun-failed`, and `--shard N/M`. See
|
||
`CLAUDE.md` §3 for the full command set and the gotchas (`unity run` reserves `-batchmode`,
|
||
`-nographics`, `-quit` and `-logFile`; its output goes to `Logs/Editor.log`, not stdout).
|
||
|
||
---
|
||
|
||
## 1. What the survey actually found
|
||
|
||
The brief described a proof-of-concept where dialogue is authored inside the Editor. That is
|
||
not what is in the repository. The state is considerably more advanced, and that changes what
|
||
the work is.
|
||
|
||
### 1.1 Already correct — do not "fix" these
|
||
|
||
| Thing | State |
|
||
|---|---|
|
||
| **Dialogue is already plain text** | 679 lines across 9 `.yarn` files under `Assets/Dialogue/{Scenes,Characters,Objects}/`. No Editor authoring anywhere. |
|
||
| **The `.yarnproject` glob** | `**/*.yarn`. A new scene or character file is picked up with zero registration. Nothing to build here. |
|
||
| **A JSON → ScriptableObject codegen pipeline exists** | `skill_bible.json` and `candidates.json` are the source of truth; `Assets/Editor/SkillSystemSetup.cs` and `CandidateSystemSetup.cs` regenerate the `.asset` files. This is exactly the right pattern and it is already load-bearing. |
|
||
| **A headless Yarn validator exists** | `tools/YarnCheck` compiles *and plays* Yarn against the game's own Yarn Spinner DLLs, no Unity needed. |
|
||
| **A real EditMode suite exists** | ~870 LOC / 7 files, including `CandidateYarnLintTests`, which lints Yarn source against the candidate registry. |
|
||
| **Variable discipline exists** | `Common.yarn` declares everything project-wide so typos are compile errors. |
|
||
|
||
### 1.2 The real gap for a Cowork writer
|
||
|
||
It is not the `.yarn` files. It is three things:
|
||
|
||
1. **The writing handbook does not exist as a file.** `SC101.yarn` cites a *"cap introductions
|
||
at three voices"* rule and `docs/candidate-system-implementation-plan.md` says outright that
|
||
the handbook is not in this repo. So the rules a writer must follow are partly in someone's
|
||
head, partly scattered across code comments. A Cowork session cannot read a handbook that
|
||
isn't checked in — which is precisely the "re-explaining the world each time" problem.
|
||
|
||
2. **The best character material is trapped inside JSON string fields.** `skill_bible.json`
|
||
holds, per skill, a `notes` field containing an entire embedded prose sheet — domain, *what
|
||
it wants for you*, *what it's wrong about*, register, verbal tics, SUCCESS/FAILURE/PASSIVE
|
||
voice samples, firing frequency, forbidden domains. It is excellent writing. It is stored as
|
||
a single `\n`-escaped string in a machine file. A writer cannot comfortably read or revise it,
|
||
and every edit risks breaking the JSON.
|
||
|
||
3. **NPC characters have no sheets at all.** The Bartender, Bradford Kane and Chevalier Cassian
|
||
Thal exist only as their `.yarn` lines. There is no record of who they are, what they know,
|
||
or how they speak.
|
||
|
||
### 1.3 Scaffold vs. load-bearing
|
||
|
||
| Verdict | Items |
|
||
|---|---|
|
||
| **Load-bearing** | `Scripts/Skills/**` (25 files, asmdef'd), `Scripts/UI/**` (13 files, ~1660 LOC), `Scripts/Camera/**` (7 files, ~727 LOC), `Scripts/Player/**` (5 files, ~654 LOC + `Locomotion.Math` asmdef), `Scripts/Interaction/**`, `Scripts/Dialogue/**`, `Assets/Dialogue/**`, `Assets/Skills/**`, `Assets/Candidates/**`, `Assets/Prefabs/UI/**`, `Assets/Tests/**`, `tools/YarnCheck` |
|
||
| **Scaffold — replace** | `NPCStandIn.cs` (global namespace, sphere-primitive placeholder, hardwired `[F]` prompt, "drop this on a sphere" in its own docstring), `Assets/Editor/YarnDemoSetup.cs` (targets `DialogueTest.unity` by literal path), `Assets/Scenes/DialogueTest.unity` itself |
|
||
| **Scaffold — keep for now** | `Assets/Editor/UILayerSetup.cs` (818 lines), `PlayerControlSetup.cs` (316), `UILayerSetupAutoRun.cs`. These are one-shot scene/prefab builders. They are how the scene got wired and they still work; retiring them is a later step, not this one. |
|
||
| **Template leftovers — delete** | `Assets/Scenes/SampleScene.unity`, `Assets/Readme.asset`, `Assets/TutorialInfo/` |
|
||
| **Should not be committed** | `tools/YarnCheck/bin/`, `tools/YarnCheck/obj/`, `.DS_Store` files |
|
||
|
||
### 1.4 Infrastructure gaps
|
||
|
||
- **No `.gitattributes`.** No Unity YAML merge driver, no LFS rule for `Assets/Art/`. Scene and
|
||
prefab merges will conflict destructively the first time work happens on two branches.
|
||
- **asmdef coverage is partial.** Only `Skills` and `Locomotion.Math`. Camera, Player, UI,
|
||
Dialogue, NPCs and Interaction all land in `Assembly-CSharp`, so every C# edit recompiles
|
||
everything.
|
||
- **No PlayMode tests.** `Assets/Tests/` is EditMode only.
|
||
- **No build entry point.** No `-executeMethod` build script; `EditorBuildSettings` lists
|
||
`SampleScene` and `DialogueTest`.
|
||
- **Single branch, no remote work in flight.** `main` only, `origin/main` in sync.
|
||
|
||
---
|
||
|
||
## 2. Part 1 — externalizing dialogue and character writing
|
||
|
||
### 2.1 The principle
|
||
|
||
**Prose lives in markdown. Machine-readable structure lives in JSON. Playable content lives in
|
||
`.yarn`. Nothing that a writer needs to read is stored as an escaped string inside a data file.**
|
||
|
||
The existing JSON-is-source-of-truth pattern is kept and extended — not replaced. What changes
|
||
is that the *prose* half moves out of the JSON into markdown with frontmatter, and the JSON keeps
|
||
only the fields code actually reads.
|
||
|
||
### 2.2 Proposed structure
|
||
|
||
A new top-level `writing/` directory at the **repo root**, deliberately outside `Assets/`:
|
||
|
||
```
|
||
writing/ ← the Cowork working directory
|
||
├── README.md # "start here" for a writing session
|
||
├── STYLE.md # the missing writing handbook, finally checked in
|
||
├── GLOSSARY.md # world terms, factions, place names, in-fiction jargon
|
||
├── YARN-PRIMER.md # the ~1 page of Yarn syntax a writer needs
|
||
├── characters/
|
||
│ ├── _TEMPLATE.md
|
||
│ ├── bartender.md
|
||
│ ├── bradford-kane.md
|
||
│ └── chevalier-cassian-thal.md
|
||
├── voices/ # the eleven skills, as readable sheets
|
||
│ ├── _TEMPLATE.md
|
||
│ ├── provenance.md
|
||
│ ├── confluence.md
|
||
│ └── … (facework, the-float, room-tone, amnesty, house-pour,
|
||
│ long-shift, placement, standing-order, undisclosed)
|
||
├── scenes/
|
||
│ ├── _TEMPLATE.md
|
||
│ ├── sc101-the-card-at-the-desk.md # brief, beats, tone — NOT the dialogue
|
||
│ └── sc102-….md
|
||
└── lore/
|
||
├── candidates.md # the three readings, in prose
|
||
└── world.md # the club, the conference, the night
|
||
```
|
||
|
||
Playable dialogue stays exactly where it is, in `NightclubArcadia/Assets/Dialogue/`. **Do not
|
||
move the `.yarn` files.** They are already correct, Unity's importer watches that path, and the
|
||
`.meta` files there carry GUIDs that scene references depend on.
|
||
|
||
So the writer's day looks like: read `writing/`, write `.yarn`. Two directories, no Unity.
|
||
|
||
**Why root and not `Assets/writing/`:** anything under `Assets/` gets a `.meta` file, gets
|
||
imported, and shows up in the Unity project window. Markdown notes should not be Unity assets.
|
||
Root-level also means a Cowork session can be pointed at `writing/` alone.
|
||
|
||
### 2.3 File format: markdown + YAML frontmatter
|
||
|
||
Frontmatter carries the few fields a script may want to check; the body is unconstrained prose.
|
||
|
||
**`writing/characters/_TEMPLATE.md`:**
|
||
|
||
```markdown
|
||
---
|
||
id: bartender # matches the Yarn speaker name, lowercased/kebabed
|
||
name: The Bartender # exact string used as the Yarn speaker prefix
|
||
yarn_file: Assets/Dialogue/Characters/Bartender.yarn
|
||
status: authored # planned | drafted | authored
|
||
scenes: [SC101]
|
||
knows: [doss, the-estate-drink]
|
||
---
|
||
|
||
## Who they are
|
||
One paragraph. What they want tonight.
|
||
|
||
## How they speak
|
||
Register, sentence length, contractions, what they never say.
|
||
|
||
## What they know
|
||
Bullet list. For each: does the player learn it, under what condition, and which
|
||
`$flag` records it.
|
||
|
||
## What they must never do
|
||
The negative constraints. Usually more useful than the positive ones.
|
||
|
||
## Sample lines
|
||
Three to five. Not for shipping — for calibration.
|
||
```
|
||
|
||
**`writing/voices/_TEMPLATE.md`** mirrors the structure already present in
|
||
`skill_bible.json`'s `notes` field, promoted to real headings — `domain`, *what it wants for
|
||
you*, *what it's wrong about*, *how it addresses you*, register, verbal tics, *what it never
|
||
does*, opposed/allied, SUCCESS / FAILURE / PASSIVE voice, firing frequency, forbidden domains.
|
||
That structure was already good; it just needs to be readable.
|
||
|
||
**`writing/scenes/_TEMPLATE.md`** carries the brief and the beat list, plus the constraints that
|
||
are currently buried in `.yarn` header comments — the evidence-balance rule, the beat order, the
|
||
passive-pattern rule, any deliberate exception. `SC101.yarn`'s 25-line header comment is the
|
||
model; it should live here and be *referenced* from the `.yarn` file, not duplicated.
|
||
|
||
### 2.4 Migrating the Skill Bible without losing the JSON pipeline
|
||
|
||
`skill_bible.json` currently holds both machine fields (`axis`, `opposedTo`, `alliedWith`,
|
||
`targetFiringsPerHour`, `accentColor`, `startingRank`, `primaryChannel`, `clueYield`) and the
|
||
prose blob (`notes`, plus a duplicated `domain`).
|
||
|
||
Proposed split, once:
|
||
|
||
1. Write a one-shot script that reads `skill_bible.json` and emits `writing/voices/<id>.md` —
|
||
frontmatter from the machine fields, body from the parsed `notes`.
|
||
2. `SkillDefinition.notes` stays in the C# type and keeps rendering wherever it renders. The
|
||
generator that produces it now reads the markdown body instead of the JSON string.
|
||
3. `skill_bible.json` remains the machine source. Either it keeps a `notes` field regenerated
|
||
*from* the markdown, or `SkillSystemSetup.cs` learns to read `writing/voices/*.md` directly.
|
||
|
||
**Recommendation: the first.** `writing/voices/*.md` is the human source; a small script folds
|
||
prose back into `skill_bible.json`; `SkillSystemSetup.cs` is untouched. That keeps the existing,
|
||
tested Editor pipeline unchanged and confines the new machinery to one script — which matters
|
||
because `SkillRosterTests` asserts against the generated assets.
|
||
|
||
The direction of truth must be written down and never ambiguous:
|
||
|
||
```
|
||
writing/voices/*.md ──(sync script)──▶ skill_bible.json ──(SkillSystemSetup)──▶ *.asset
|
||
human writes here machine mirror build output
|
||
```
|
||
|
||
An EditMode test should assert the mirror is in sync, so a JSON hand-edit fails CI instead of
|
||
silently winning.
|
||
|
||
### 2.5 The flow back into Unity
|
||
|
||
| Change | What the writer does | Sync step | Verify |
|
||
|---|---|---|---|
|
||
| New/edited dialogue line, branch, node | edit `.yarn`, save | **none** — importer picks it up | `dotnet run` YarnCheck |
|
||
| New scene or character `.yarn` file | create file under `Assets/Dialogue/` | **none** — `**/*.yarn` glob | YarnCheck |
|
||
| New variable | add `<<declare>>` to `Common.yarn` | **none** | YarnCheck |
|
||
| Voice prose | edit `writing/voices/*.md` | run sync script → JSON | EditMode tests |
|
||
| Character sheet, scene brief, lore, style | edit `writing/*.md` | **none** — docs only | — |
|
||
| Candidate/clue status, new clue id | edit `candidates.json` | Editor menu or script reload | `CandidateYarnLintTests` |
|
||
|
||
The everyday writing loop has **no manual sync step at all**. That is the design goal and it is
|
||
already met for `.yarn`; the work is to reach the same property for character and voice material.
|
||
|
||
### 2.6 Validation a writer can run alone
|
||
|
||
```bash
|
||
cd tools/YarnCheck
|
||
dotnet run -- ../../NightclubArcadia/Assets/Dialogue # does it compile?
|
||
dotnet run -- ../../NightclubArcadia/Assets/Dialogue Bartender_Talk 2 2 0 0 # does it play?
|
||
```
|
||
|
||
Requires only the .NET 9 SDK — no Unity, no license, no Editor lock. This should be the first
|
||
thing `writing/README.md` tells a new session to run, and the last thing it tells them to run
|
||
before handing work back.
|
||
|
||
Caveat that must be stated plainly in the README, because it will otherwise cause confusion:
|
||
YarnCheck stubs `<<check>>`, so `$check_result` is always `false` and **every check-gated branch
|
||
shows its failure side**. A writer seeing only failure prose has not found a bug.
|
||
|
||
### 2.7 Scope boundary
|
||
|
||
Writer-owned: `writing/**`, `Assets/Dialogue/**/*.yarn`, prose fields in the two JSON files.
|
||
Code-owned: all C#, all `.asset` / `.prefab` / `.unity` / `.meta`, all `ProjectSettings/`,
|
||
and the *implementation* of any Yarn command or function.
|
||
|
||
A writer needing a new Yarn command is a request to engineering, not a task they can complete —
|
||
and that is the only such boundary. Everything else about writing a scene must be self-serve.
|
||
|
||
---
|
||
|
||
## 3. Part 2 — target project structure
|
||
|
||
### 3.1 Scenes
|
||
|
||
The current model — one scene holding everything — is what makes `DialogueTest.unity` both
|
||
irreplaceable and unmergeable. The target is additive scene loading, which is also the standard
|
||
answer to Unity scene merge conflicts.
|
||
|
||
```
|
||
Assets/Scenes/
|
||
├── Bootstrap.unity # entry point. Systems only, no geometry. First in build list.
|
||
├── Systems/
|
||
│ └── Systems.unity # DialogueRunner, UIStateController, PlayerSkills, Commentary,
|
||
│ # camera director. Loaded additively by Bootstrap, never unloaded.
|
||
├── Levels/
|
||
│ └── SC101_ConferenceHall.unity # geometry, lights, nav mesh, NPC + interactable placement
|
||
└── Dev/
|
||
└── Sandbox.unity # scratch scene, explicitly disposable
|
||
```
|
||
|
||
`Bootstrap` loads `Systems` then the requested level. The payoff is direct: two people (or a
|
||
person and an agent) can edit level content and systems wiring without touching the same file,
|
||
and a corrupted level scene no longer takes the systems layer with it.
|
||
|
||
`DialogueTest.unity` becomes `Levels/SC101_ConferenceHall.unity` — **renamed and split, not
|
||
rebuilt**. Its contents are the asset; only its scope changes.
|
||
|
||
### 3.2 Folders
|
||
|
||
Mostly already right. The deltas:
|
||
|
||
```
|
||
Assets/
|
||
├── Art/ ← exists (placeholder portrait). Add Environment/, Characters/, VFX/
|
||
├── Audio/ ← new: Music/, SFX/, VO/
|
||
├── Dialogue/ ← unchanged. Do not move.
|
||
├── Candidates/ Skills/ ← unchanged (JSON + generated assets)
|
||
├── Prefabs/
|
||
│ ├── UI/ ← exists
|
||
│ ├── Characters/ ← new: real NPC prefabs, replacing sphere stand-ins
|
||
│ ├── Interactables/ ← new: chair, door, object interactables
|
||
│ └── Systems/ ← new: Bootstrap/Systems prefabs
|
||
├── Scenes/ ← per §3.1
|
||
├── Scripts/
|
||
│ ├── Core/ ← new: bootstrap, scene loading, save/load, service locator
|
||
│ ├── Camera/ Player/ Interaction/ Dialogue/ UI/ Skills/ ← unchanged
|
||
│ └── NPCs/ ← NPCStandIn replaced by a real NPC component (§4, step 6)
|
||
├── Settings/ ← unchanged (URP)
|
||
└── Tests/
|
||
├── EditMode/ ← unchanged
|
||
└── PlayMode/ ← new
|
||
```
|
||
|
||
### 3.3 Conventions
|
||
|
||
- **Scenes:** `PascalCase`. Levels prefixed with their scene id — `SC101_ConferenceHall`.
|
||
- **Prefabs:** `PascalCase`, no `_Prefab` suffix. Variants suffixed `_Variant`.
|
||
- **Scripts:** one public type per file, filename = type name. Namespace `NightclubArcadia.<Area>`,
|
||
matching the folder. **No global-namespace runtime types.**
|
||
- **Yarn nodes:** the existing conventions hold and are already consistent —
|
||
`<Character>_Talk`, `SC###_Hook_<Subject>`, `Commentary_<Context>_<skill_id>`.
|
||
- **Yarn variables:** the families in `CLAUDE.md` §4.2. These are lint-enforced; treat them as API.
|
||
- **asmdefs:** one per top-level `Scripts/` area, named `NightclubArcadia.<Area>`.
|
||
- **`.yarn` filenames** may contain spaces (`Bradford Kane.yarn`) — the glob handles it, but
|
||
every shell command touching them needs quoting. Worth normalising to kebab-case during a
|
||
rename batch; low priority.
|
||
|
||
---
|
||
|
||
## 4. The migration plan
|
||
|
||
Incremental, ordered, each step independently committable and revertible. **Steps 0–2 are the
|
||
safety work and should happen before anything is moved.**
|
||
|
||
Effort marks are rough: **S** ≲1h, **M** a half day, **L** a day or more.
|
||
|
||
### Phase 0 — checkpoint ✅ DONE (2026-08-25)
|
||
|
||
| # | Step | Status |
|
||
|---|---|---|
|
||
| 0.1 | Close the Unity Editor | done |
|
||
| 0.2 | Full backup of the tree + separate copy of the scene | done |
|
||
| 0.3 | Commit all uncommitted work in logical slices (§5) | done — 5 commits |
|
||
| 0.4 | Convert `DialogueTest.unity` to text | **blocked** — Editor-UI only, see §0.1 |
|
||
| 0.5 | Tag `pre-restructure` (on a green tree, 39/39) | done |
|
||
| 0.6 | Push `main` and the tag to `origin` | done |
|
||
|
||
0.4 is the one open item and it does not block Phase 1 or 2.
|
||
|
||
### Phase 1 — repo hygiene ✅ DONE (2026-08-25)
|
||
|
||
| # | Step | Status |
|
||
|---|---|---|
|
||
| 1.1 | `.gitattributes` | done — UnityYAMLMerge driver, LF pinned, binaries marked |
|
||
| 1.2 | Untrack `tools/YarnCheck/{bin,obj}` | **no-op** — already ignored, never tracked |
|
||
| 1.3 | Remove committed `.DS_Store` | **no-op** — already ignored, never tracked |
|
||
| 1.4 | Makefile wrapping the CLAUDE.md commands | done, plus `tools/ci/report_tests.py` |
|
||
|
||
1.2 and 1.3 were wrong in the original plan: both were listed from a filesystem `find`, not from
|
||
`git ls-files`. Nothing was ever tracked, and `.gitignore` already covers both.
|
||
|
||
Line endings were renormalized in one deliberate pass (74 files, all CRLF→LF, `git diff -w`
|
||
empty) rather than left to surface as phantom diffs later. LFS was **not** enabled: the remote is
|
||
self-hosted and its LFS support is unverified, and turning the filter on against a server that
|
||
lacks it breaks pushing. The rules and the migration step are recorded in `.gitattributes`.
|
||
|
||
`make merge-driver` configures UnityYAMLMerge. Note the binary is at `Contents/Helpers/`, not
|
||
`Contents/Tools/` as most guides claim — there is no `Tools` directory in Unity 6 on macOS.
|
||
|
||
### Phase 2 — writing pipeline ✅ DONE (2026-08-25)
|
||
|
||
| # | Step | Status |
|
||
|---|---|---|
|
||
| 2.1 | `writing/` skeleton + templates | done |
|
||
| 2.2 | `writing/STYLE.md` | done — **digested from the vault handbook, not reconstructed** (below) |
|
||
| 2.3 | Extract `skill_bible.json` prose → `writing/voices/*.md` | done — 11 sheets, lossless round-trip |
|
||
| 2.4 | Sync script + EditMode drift test | done — `tools/writing/sync_voices.py`, `VoiceSheetSyncTests` |
|
||
| 2.5 | Character sheets | done — Bartender, Chevalier Cassian Thal, Bradford Kane |
|
||
| 2.6 | `README.md` + `YARN-PRIMER.md` | done, plus `GLOSSARY.md` and `lore/candidates.md` |
|
||
| 2.7 | SC-101 header rules → scene brief | done — header trimmed to the six rules that bind while editing, rationale moved to the brief |
|
||
|
||
Validated: EditMode **42/42** (three new), YarnCheck compiles 9 files / 35 nodes, sheets in sync.
|
||
The drift test was verified to actually fail on drift, not merely to pass.
|
||
|
||
#### Phase 2 — what changed against the plan
|
||
|
||
Two things, both discovered during execution.
|
||
|
||
**The handbook exists.** `docs/skill-system-refactor-plan.md` §2 names it, and it is at
|
||
`~/Obsidian-Vaults/obsidian-vault/Projects/Active/NightclubArcadia/` — a 874-line
|
||
`[[Mystery Writing Handbook]]`, plus `[[Character Sheet Handbook]]`, `[[Skill System Draft]]`,
|
||
`[[Lorebook]]`, `[[Candidate System — Design Explanation]]`, scene specs, and `[[Ground Truth]]`.
|
||
So §2.2 became a **digest with pointers** rather than a reconstruction, which is both more
|
||
accurate and honest about where the authoring home is. The vault remains that home.
|
||
|
||
**`[[Ground Truth]]` must never enter this repo.** It is the solution document. The premise of
|
||
the game is that the protagonist's identity never resolves, and a copy of the answer in a git
|
||
history is exactly how that leaks. This is now stated in `writing/README.md` and `CLAUDE.md` §4.1.
|
||
|
||
One thing surfaced that is worth acting on independently: `[[000_OpeningSceneTemplate]]` records
|
||
SC-101's tone check as **BLOCKED** because the one-page Voice Sheet has never been written — the
|
||
scene spec could not honestly certify its own tone field. `writing/STYLE.md` §9 now holds that
|
||
sheet with four fields explicitly unset. Filling them is about half an hour of decisions and it
|
||
unblocks a check that has been stuck for a while.
|
||
|
||
### Phase 3 — scene restructure ✅ DONE (2026-08-25)
|
||
|
||
| # | Step | Status |
|
||
|---|---|---|
|
||
| 3.1 | Delete template leftovers | done — SampleScene, Readme.asset, TutorialInfo |
|
||
| 3.2 | Create `Bootstrap` + `Systems/Systems` | done, both text |
|
||
| 3.3 | ~~Copy~~ **rebuild** `DialogueTest` → `Levels/SC101_ConferenceHall` | done — see below |
|
||
| 3.4 | Move systems objects into `Systems.unity` | done — 10 roots incl. the player |
|
||
| 3.5 | Additive loading in `Scripts/Core/` | done — `GameBootstrap`, `SceneServices` |
|
||
| 3.6 | Verify parity, delete `DialogueTest.unity` | done — verified by diff, then deleted |
|
||
| 3.7 | Retarget `YarnDemoSetup.cs` | done — all four setup menus share `ScenePaths` |
|
||
|
||
Validated: EditMode **42/42**, PlayMode **5/5**, YarnCheck 9 files / 35 nodes.
|
||
|
||
#### Phase 3 — what changed against the plan
|
||
|
||
**The binary scene is fixed, and the cause was not what §0.1 assumed.** The `NavMeshSurface` held
|
||
its baked `NavMeshData` *embedded in the scene*. That type prefers binary serialization, and one
|
||
such object forces the entire file to binary regardless of Force Text — which is why toggling the
|
||
setting, re-saving, and saving to a new path all did nothing. Bisecting the roots one at a time
|
||
found it: thirteen saved as text alone, `Navigation` did not. Extracting the data to
|
||
`Scenes/Levels/SC101_ConferenceHall/NavMesh-Navigation.asset` — what baking from the inspector
|
||
produces anyway — made the scene text. **No Editor-UI step was needed after all.**
|
||
|
||
So 3.3 became a *rebuild*, not a copy: saving the binary scene to a new path reproduces binary, so
|
||
all 14 roots were moved into a fresh scene instead. Verified roots 14→14, cross-root references
|
||
25→25, zero missing scripts, render settings carried across by hand.
|
||
|
||
**The player moved to Systems.** It had been parented under `Room_101`, i.e. under level geometry.
|
||
It is persistent content, and moving it removes most of the cross-scene breakage by itself.
|
||
|
||
**The split nulled exactly seven references**, measured by auditing the pre-split scene from git
|
||
and diffing: both NPCs' `dialogueRunner`, `dialogueUI` and `player`, plus the reveal camera's
|
||
tracking target. A first, naive audit reported 210 — those turned out to be pre-existing Unity
|
||
defaults (`Image.m_Material` and friends), which is why the before/after baseline mattered.
|
||
|
||
**A real bug surfaced, and only PlayMode could see it.** The player's `NavMeshAgent` is 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`. This is
|
||
exactly the class of failure an EditMode suite cannot reach, and it is the argument for having
|
||
written `BootstrapTests` rather than eyeballing the scene.
|
||
|
||
**Phase 4.2 came early.** `Assets/Tests/PlayMode` now exists. Two of its checks use reflection,
|
||
because an asmdef assembly cannot reference `Assembly-CSharp` where `NPCStandIn` and the
|
||
`Cinematics` namespace live — Phase 4.1 (per-area asmdefs) is what removes that.
|
||
|
||
### Phase 4 — code structure
|
||
|
||
| # | Step | Detail | Effort |
|
||
|---|---|---|---|
|
||
| 4.1 | Add asmdefs per area | Camera, Player, Interaction, Dialogue, UI, NPCs, Core | M |
|
||
| 4.2 | Add `Assets/Tests/PlayMode/` + asmdef | first test: bootstrap loads and reaches playable state | M |
|
||
| 4.3 | Add `Assets/Editor/BuildPipeline.cs` | `-executeMethod` entry, `EditorApplication.Exit(1)` on failure (`CLAUDE.md` §3.4) | M |
|
||
| 4.4 | Rename batch | `DialougueSkillComparison.cs` → `DialogueSkillComparison.cs`; kebab-case `.yarn` filenames. Rename **in the Unity Editor** so `.meta` GUIDs are preserved. | S |
|
||
|
||
### Phase 5 — replace the scaffold
|
||
|
||
| # | Step | Detail | Effort |
|
||
|---|---|---|---|
|
||
| 5.1 | Design the real NPC component | data-driven from a character definition asset, not public fields on a sphere | M |
|
||
| 5.2 | Build `Prefabs/Characters/` prefabs | one per named character | M |
|
||
| 5.3 | Migrate scene NPCs off `NPCStandIn`, delete it | proximity prompt + label logic is worth keeping — extract, don't discard | M |
|
||
| 5.4 | Retire the `Assets/Editor/*Setup.cs` one-shot builders | only once prefabs are authored and committed; `UILayerSetup.cs` alone is 818 lines of accumulated wiring knowledge | L |
|
||
|
||
### 4.6 Keep / relocate / replace, at a glance
|
||
|
||
| Verdict | Items |
|
||
|---|---|
|
||
| **Keep as-is** | `Assets/Dialogue/**`, `Assets/Skills/**`, `Assets/Candidates/**`, `Scripts/Skills/**`, `Scripts/UI/**`, `Scripts/Camera/**`, `Scripts/Player/**`, `Scripts/Interaction/**`, `Scripts/Dialogue/**`, `Assets/Tests/EditMode/**`, `Assets/Prefabs/UI/**`, `tools/YarnCheck`, `Assets/Settings/**` |
|
||
| **Rename / relocate** | `DialogueTest.unity` → `Levels/SC101_ConferenceHall.unity` (+ split); `skill_bible.json` prose → `writing/voices/*.md`; `SC101.yarn` header rules → `writing/scenes/`; `DialougueSkillComparison.cs` spelling; `.yarn` filenames with spaces |
|
||
| **Replace** | `NPCStandIn.cs` → real NPC component + prefabs; `YarnDemoSetup.cs` → retargeted or retired; single-scene model → Bootstrap/Systems/Level |
|
||
| **Delete** | `SampleScene.unity`, `Readme.asset`, `TutorialInfo/`, committed `bin/` `obj/` `.DS_Store` |
|
||
| **Add** | `.gitattributes`, `writing/**`, `Scripts/Core/**`, `Tests/PlayMode/**`, `Editor/BuildPipeline.cs`, per-area asmdefs, `Assets/Audio/` |
|
||
|
||
---
|
||
|
||
## 5. Checkpointing — what was done
|
||
|
||
Roughly **3,100 lines of C#** across untracked directories, 291 changed lines in tracked
|
||
scripts, 5 UI prefabs and the scene were uncommitted. All of it is now in git.
|
||
|
||
**Order used**, which differs from the original draft on one point: the safe text work was
|
||
committed *before* any Unity run, so that a headless Editor invocation could not endanger it.
|
||
That turned out to matter — four Unity runs followed, one ending in a `SIGILL` on teardown.
|
||
|
||
1. Verified the Editor was closed and no lockfile was held.
|
||
2. Took a full backup: a `.tar.gz` of the tree excluding `Library/`, `Temp/`, `Logs/` and build
|
||
output, plus a separate copy of the scene file.
|
||
3. Committed in five slices, in dependency order:
|
||
|
||
| Commit | Contents |
|
||
|---|---|
|
||
| `add camera rig, click-to-move locomotion, and click interaction` | `Scripts/{Camera,Player,Interaction}`, locomotion math tests, Cinemachine package, `Interactable`/`Ground` tags, NavMesh agent dimensions |
|
||
| `add UI layer: dialogue stream, HUD, player menu, and skills panel` | `Scripts/UI`, Skills UI views + asmdef reference, `Prefabs/UI`, placeholder art, `UILayerSetup` |
|
||
| `wire player control setup and tune the third-person controller` | `PlayerControlSetup.cs`, StarterAssets input/armature/controller |
|
||
| `suppress world interaction while a menu panel is open` | `Scripts/Dialogue`, `Scripts/NPCs`, yarnproject graph positions |
|
||
| `check in the DialogueTest scene and add it to the build settings` | the scene (binary, deliberately) + `EditorBuildSettings` |
|
||
|
||
4. Attempted the binary→text conversion four ways; all failed (§0.1). Committed the scene as
|
||
binary rather than leave it untracked — a valid, intact, opening scene in an awkward format
|
||
beats days of wiring living only on disk.
|
||
5. Tagged `pre-restructure`.
|
||
|
||
Slices rather than one lump, because the restructure will want to revert *part* of this later.
|
||
|
||
6. Fixed two wrong expectations in the locomotion math tests (both test bugs, not
|
||
production bugs); EditMode is **39/39**. Moved the tag onto that green tree and pushed.
|
||
|
||
**Still to do:** the Editor-UI serialization fix (§0.1). That is the only open Phase 0 item.
|
||
|
||
### 5.1 Residual risks after checkpointing
|
||
|
||
| Risk | Mitigation |
|
||
|---|---|
|
||
| Scene re-save produces a text file that differs semantically from the binary one | Play SC-101 end to end before committing; compare against the pre-migration run |
|
||
| Renaming assets outside Unity orphans `.meta` GUIDs | Rename inside the Editor, always |
|
||
| Scene merge conflicts once branching starts | `.gitattributes` merge driver in Phase 1.1, before any branch |
|
||
| Deleting `UILayerSetup.cs` too early loses wiring knowledge | Phase 5.4 is last, and only after prefabs are authored and committed |
|
||
| Binary scene state recurs | Verify `EditorSettings` shows Force Text; add a CI check that `*.unity` files start with `%YAML` |
|
||
|
||
---
|
||
|
||
## 6. Sequencing recommendation
|
||
|
||
**Phase 0 today.** It is under an hour and it is the difference between a recoverable project
|
||
and a lost week.
|
||
|
||
**Then Phase 2, before Phase 3.** The writing pipeline touches no Unity asset, cannot break the
|
||
game, and is what actually unblocks the stated goal of writing with Cowork. Phase 3 is the risky
|
||
work and it does not need to come first. `writing/STYLE.md` (2.2) is the single highest-value
|
||
item in this document — it is the artifact whose absence is currently forcing you to re-explain
|
||
the world every session.
|
||
|
||
**Phase 1 alongside Phase 2**, since `.gitattributes` must land before any branching.
|
||
|
||
**Phase 3–5 after**, in order, small commits, playtest between each.
|
||
|
||
---
|
||
|
||
## 7. Open questions
|
||
|
||
1. ~~Does a writing handbook exist outside the repo?~~ **Answered — yes** (Phase 2 notes). It is in the
|
||
Obsidian vault and is now pointed at rather than duplicated. The open part is whether any of
|
||
it should eventually move into the repo wholesale; the current answer is no, because the vault
|
||
is reachable from writing sessions and duplication would drift.
|
||
2. ~~How did the scene become binary?~~ **Answered** (§0.1): Force Text only converts when the
|
||
setting changes, so anything that entered binary stays binary. The open part is *how* it
|
||
first entered binary — most likely created or imported during a Force Binary window. Adding a
|
||
CI check that every `*.unity` starts with `%YAML` would catch a recurrence cheaply.
|
||
3. **Is `SC101_ConferenceHall` the right level granularity,** or should the conference hall split
|
||
further (entrance / hall / bar / back office) given the `$visited_*` flag families already
|
||
declared in `Common.yarn`? Those flags imply at least seven distinct locations.
|
||
4. **Target platform(s)?** `EditorBuildSettings` and the URP settings carry both PC and Mobile
|
||
render pipeline assets. It affects whether Phase 4.3's build script targets one platform or several.
|
||
5. **Should `writing/` be a separate repository** so Cowork sessions never see Unity? Recommendation:
|
||
**no** — the coupling to `.yarn` files and `candidates.json` is tight enough that split repos
|
||
would drift. A single repo with a clear directory boundary is the better trade.
|