CLAUDE.md gains the writing/ layout, the voice-sheet pipeline and its direction of truth, the sync commands, and a pointer that Ground Truth must never be copied into this repo. The writer-owned/code-owned table now covers writing/. The restructure plan records Phase 2 as executed, and closes its first open question: the writing handbook does exist, in the Obsidian vault, so STYLE.md became a digest with pointers rather than the reconstruction the plan assumed. Also noted there: the vault's opening-scene spec records SC-101's tone check as BLOCKED on a one-page Voice Sheet that was never written. STYLE.md now holds that sheet with four fields explicitly unset. Co-Authored-By: Claude Opus 5 <[email protected]>
570 lines
32 KiB
Markdown
570 lines
32 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 and 2 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.
|
||
|
||
Phase 2 (the writing pipeline) is also done — see §4. Phases 1, 3, 4 and 5 remain proposal.
|
||
|
||
### 0.1 The binary scene — diagnosis corrected
|
||
|
||
The original reading of this was **wrong**, and the correction matters for Phase 3.
|
||
|
||
The theory was that the scene had been saved during a temporary Force Binary session and merely
|
||
needed re-saving. It isn't that. Unity **re-writes this scene as binary even now**, with the
|
||
project set to Force Text and `EditorSettings.serializationMode` reporting `ForceText` from the
|
||
API. Force Text governs conversion **at the moment the setting changes**, not on every
|
||
subsequent save — so a file that entered the project as binary stays binary indefinitely.
|
||
|
||
Four approaches were tried headlessly, all failing silently with success return codes:
|
||
|
||
| Approach | Result |
|
||
|---|---|
|
||
| `AssetDatabase.ForceReserializeAssets(scenePath)` | Opens and imports the scene; does not rewrite scene files at all |
|
||
| `OpenScene` + `MarkSceneDirty` + `SaveScene(samePath)` | Returns `true`, writes nothing — `MarkSceneDirty` does not take in batch mode |
|
||
| `OpenScene` + `SaveScene(newPath)` | Writes a fresh file that is **also binary**, byte-identical in size |
|
||
| `serializationMode = Mixed` → `ForceText` | Mode changes as logged; no reserialize pass occurs |
|
||
|
||
**The remaining fix is Editor-UI only** and takes about thirty seconds:
|
||
|
||
> Project Settings ▸ Editor ▸ Asset Serialization → set **Mixed**, let it apply, then set
|
||
> **Force Text** again. Changing the value in the dialog is what triggers the reserialize pass.
|
||
|
||
Then verify from the terminal — you want `%YAML 1.1`:
|
||
|
||
```bash
|
||
head -c 20 NightclubArcadia/Assets/Scenes/DialogueTest.unity
|
||
```
|
||
|
||
Worth knowing: this will reserialize **every** asset the setting touches, so expect a wide diff.
|
||
That is now safe to inspect and revert per-file, because everything is committed.
|
||
|
||
The scene itself is healthy — it opens with 14 roots (`Room_101`, `Dialogue System`,
|
||
`Skill System`, the three UI canvases, `CinemachineBrain`, `CinemachineCamera`, `Navigation`,
|
||
`CameraAnchor`, `CM_Reveal_Room101`, `RevealVolume_Room101`, `Directional Light`, `UI System`).
|
||
Its md5 was verified unchanged across every failed attempt. The cost of it staying binary is
|
||
that it is **not diffable and not mergeable** — which is an argument for bringing Phase 3's
|
||
scene split forward, since the split rebuilds these files anyway.
|
||
|
||
### 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 (no Unity changes)
|
||
|
||
| # | Step | Detail | Effort |
|
||
|---|---|---|---|
|
||
| 1.1 | Add `.gitattributes` | Unity YAML merge driver for `*.unity` / `*.prefab` / `*.asset`; LFS for `Assets/Art/**` binaries; `* text=auto eol=lf` | S |
|
||
| 1.2 | Untrack `tools/YarnCheck/{bin,obj}` | `git rm -r --cached`, add ignore rules | S |
|
||
| 1.3 | Remove committed `.DS_Store` files, confirm ignore rule | S |
|
||
| 1.4 | Add a `Makefile` or `scripts/` wrapping the `CLAUDE.md` commands | `make test`, `make yarncheck`, `make build` | S |
|
||
|
||
`.gitattributes` before any restructuring is deliberate — the moment work happens on a branch,
|
||
scene merges without a merge driver will corrupt files.
|
||
|
||
### 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 (the risky part)
|
||
|
||
| # | Step | Detail | Effort |
|
||
|---|---|---|---|
|
||
| 3.1 | Delete template leftovers | `SampleScene.unity`, `Readme.asset`, `TutorialInfo/`; fix `EditorBuildSettings` | S |
|
||
| 3.2 | Create `Bootstrap.unity` + `Systems/Systems.unity` | Systems empty at first | M |
|
||
| 3.3 | **Copy** `DialogueTest.unity` → `Levels/SC101_ConferenceHall.unity` | Copy, do not move. Keep the original until 3.6. | S |
|
||
| 3.4 | Move systems objects out of the level scene into `Systems.unity` | dialogue runner, UI layer, skills, commentary, camera director | L |
|
||
| 3.5 | Wire additive loading in `Scripts/Core/` | Bootstrap → Systems → level | M |
|
||
| 3.6 | Verify parity, then delete `DialogueTest.unity` | full playthrough of SC-101 vs. pre-migration behaviour | M |
|
||
| 3.7 | Retarget `YarnDemoSetup.cs` or retire it | it hardcodes `Assets/Scenes/DialogueTest.unity` | S |
|
||
|
||
Step 3.4 is where things break. Do it in small commits — one system per commit, playtest between
|
||
each. Do **not** batch it.
|
||
|
||
### 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.
|