document the writing pipeline and record Phase 2

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]>
This commit is contained in:
2026-08-25 20:23:42 +02:00
co-authored by Claude Opus 5
parent f1fadf7909
commit ed28763017
2 changed files with 103 additions and 24 deletions
+64 -7
View File
@@ -17,7 +17,9 @@ to fix — it is the checkout path). The Unity project is a **subdirectory**:
/Users/lennart/Dev/nightlcub-arcadia/ # git root /Users/lennart/Dev/nightlcub-arcadia/ # git root
├── CLAUDE.md, AGENTS.md # this reference ├── CLAUDE.md, AGENTS.md # this reference
├── docs/ # design + plan docs (markdown) ├── docs/ # design + plan docs (markdown)
├── writing/ # writer-facing context — see §4
├── tools/YarnCheck/ # .NET 9 Yarn compiler/player, no Unity needed ├── tools/YarnCheck/ # .NET 9 Yarn compiler/player, no Unity needed
├── tools/writing/ # voice-sheet sync (md <-> skill_bible.json)
├── .gitignore # macOS / IDE / toolchain rules ├── .gitignore # macOS / IDE / toolchain rules
└── NightclubArcadia/ # ← the Unity project (-projectPath) └── NightclubArcadia/ # ← the Unity project (-projectPath)
├── .gitignore # Unity rules (anchored, must stay here) ├── .gitignore # Unity rules (anchored, must stay here)
@@ -49,7 +51,7 @@ AI Navigation 2.0.14, ProBuilder 6.1.2, Test Framework 1.7.0. Yarn Spinner is an
mode while the Editor has it open — a single instance at a time. Always check before mode while the Editor has it open — a single instance at a time. Always check before
any `-batchmode` invocation (§3.1). any `-batchmode` invocation (§3.1).
4. **Prefer YarnCheck over Unity for dialogue validation.** It is seconds, not minutes, 4. **Prefer YarnCheck over Unity for dialogue validation.** It is seconds, not minutes,
and needs no Editor lock (§5.3). and needs no Editor lock (§5.4).
5. **Do not commit on the user's behalf** unless asked. Unity writes to tracked files 5. **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 (`ProjectSettings/*`, `.meta`, scenes) as a side effect of simply being open, so
`git status` noise is expected and is not always yours. `git status` noise is expected and is not always yours.
@@ -174,6 +176,25 @@ NightclubArcadia/Assets/Dialogue/
└── Objects/ Chair └── Objects/ Chair
``` ```
The writer-facing context sits at the repo root, outside `Assets/` so it gets no `.meta` files:
```
writing/
├── README.md session entry point
├── STYLE.md register, tone moves, ban list, branching rules ← read first
├── YARN-PRIMER.md the one page of syntax a writer needs
├── GLOSSARY.md in-world and project vocabulary
├── voices/ the eleven skill voices, one sheet each (generated — see §4.7)
├── characters/ named NPCs, dialogue-facing
├── scenes/ per-scene briefs: intent, beats, checks, constraints
└── lore/ candidates.md — the three identity readings and their hard rules
```
Design material deeper than this lives in Obsidian, not the repo:
`~/Obsidian-Vaults/obsidian-vault/Projects/Active/NightclubArcadia/`. **`Ground Truth.md` there
is the solution document — never copy, quote, or summarise it into this repo**, including into
commit messages. The game's premise is that the protagonist's identity never resolves.
**Dialogue is already plain text in the repo.** It is authored in `.yarn` files, not inside **Dialogue is already plain text in the repo.** It is authored in `.yarn` files, not inside
the Unity Editor, and any new `.yarn` file dropped anywhere under `Assets/Dialogue/` is picked the Unity Editor, and any new `.yarn` file dropped anywhere under `Assets/Dialogue/` is picked
up automatically by the `**/*.yarn` glob in the `.yarnproject`. Adding a scene or a character up automatically by the `**/*.yarn` glob in the `.yarnproject`. Adding a scene or a character
@@ -249,13 +270,41 @@ candidates.json → Tools ▸ Nightclub Arcadia ▸ (CandidateSystemSetup)
Those setup scripts also register a `[DidReloadScripts]` callback, so in practice the assets Those setup scripts also register a `[DidReloadScripts]` callback, so in practice the assets
regenerate on script reload too. The JSON is the source of truth either way. regenerate on script reload too. The JSON is the source of truth either way.
### 4.6 Code-owned vs writer-owned ### 4.6 The voice-sheet pipeline
The Skill Bible prose used to live as a `\n`-escaped string inside `skill_bible.json`, which no
writer could comfortably read or revise. It now lives in `writing/voices/*.md`, and truth flows
in opposite directions for the two halves of each sheet:
```
PROSE writing/voices/<id>.md -> skill_bible.json -> Skills/Definitions/*.asset
MACHINE skill_bible.json -> the "Machine fields" table in the .md
```
```bash
python3 tools/writing/sync_voices.py --check # verify; exit 1 on drift
python3 tools/writing/sync_voices.py --to-json # fold edited prose into the bible
python3 tools/writing/sync_voices.py --to-md # refresh the machine table from the bible
```
`tools/writing/voicelib.py` does the parsing and guarantees `notes -> dict -> notes` is identity
for all eleven skills. `--to-json` on unchanged sheets is byte-identical, so it is safe to run
at any time.
`VoiceSheetSyncTests` (EditMode) enforces the same invariant, so drift fails the build instead
of silently shipping a voice that no longer matches the sheet the next scene is written against.
It deliberately does not reimplement the markdown parser in C# — it asserts the shipped prose
appears verbatim in the sheet, which catches drift in either direction with no second parser to
keep in step.
### 4.7 Code-owned vs writer-owned
| Writer-owned (edit freely, no Unity) | Code-owned (needs an engineer) | | Writer-owned (edit freely, no Unity) | Code-owned (needs an engineer) |
|---|---| |---|---|
| `Assets/Dialogue/**/*.yarn` | `Assets/Scripts/**/*.cs` | | `writing/**` | `Assets/Scripts/**/*.cs` |
| `<<declare>>` lines in `Common.yarn` | `Assets/Editor/**/*.cs` | | `Assets/Dialogue/**/*.yarn` | `Assets/Editor/**/*.cs` |
| `Assets/Skills/skill_bible.json` (prose fields) | Yarn command/function *implementations* | | `<<declare>>` lines in `Common.yarn` | Yarn command/function *implementations* |
| `writing/voices/*.md` (prose → the bible, §4.6) | `Assets/Skills/skill_bible.json` (machine fields) |
| `Assets/Candidates/candidates.json` (`status`, `sceneId`, `notes`, `unlockFlag`) | `.asset`, `.prefab`, `.unity`, `.meta` | | `Assets/Candidates/candidates.json` (`status`, `sceneId`, `notes`, `unlockFlag`) | `.asset`, `.prefab`, `.unity`, `.meta` |
| `docs/` writing docs | `ProjectSettings/**` | | `docs/` writing docs | `ProjectSettings/**` |
@@ -278,11 +327,19 @@ 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`. `$self_lean_*` / `$found_*` in any `.yarn` file resolves to a real id in `CandidateDatabase`.
It is a tripwire, not a proof. It is a tripwire, not a proof.
### 5.2 One-click in-Editor run ### 5.2 Voice-sheet sync
```bash
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
`Tools ▸ Nightclub Arcadia ▸ Run Skill EditMode Tests` (`Assets/Editor/SkillSystemTestRunner.cs`). `Tools ▸ Nightclub Arcadia ▸ Run Skill EditMode Tests` (`Assets/Editor/SkillSystemTestRunner.cs`).
### 5.3 YarnCheck — validate dialogue without Unity ⭐ ### 5.4 YarnCheck — validate dialogue without Unity ⭐
`tools/YarnCheck` is a .NET 9 console app that compiles **and plays** the Yarn scripts using `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 the exact Yarn Spinner DLLs shipped in `Packages/dev.yarnspinner.unity/Runtime/DLLs`, so it
+39 -17
View File
@@ -10,7 +10,7 @@ Companion reading: `docs/skill-system-refactor-plan.md`, `docs/candidate-system-
--- ---
## 0. Status — Phase 0 is complete ## 0. Status — Phases 0 and 2 are complete
Executed 2026-08-25. The working tree is clean and tagged `pre-restructure`. Executed 2026-08-25. The working tree is clean and tagged `pre-restructure`.
@@ -19,7 +19,7 @@ the UI layer, player control setup, the dialogue-vs-menu interaction fix, and th
A full pre-flight backup was taken first (`.tar.gz` of the tree excluding `Library/`, `Temp/`, 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. `Logs/`, and build output) plus a separate copy of the scene file.
Everything below from §1 onward is unchanged proposal. Nothing in §2–§5 has been applied. 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 ### 0.1 The binary scene — diagnosis corrected
@@ -405,20 +405,41 @@ Effort marks are rough: **S** ≲1h, **M** a half day, **L** a day or more.
`.gitattributes` before any restructuring is deliberate — the moment work happens on a branch, `.gitattributes` before any restructuring is deliberate — the moment work happens on a branch,
scene merges without a merge driver will corrupt files. scene merges without a merge driver will corrupt files.
### Phase 2 — writing pipeline (no Unity changes, fully parallel to Phase 3+) ### Phase 2 — writing pipeline ✅ DONE (2026-08-25)
| # | Step | Detail | Effort | | # | Step | Status |
|---|---|---|---| |---|---|---|
| 2.1 | Create `writing/` skeleton + templates | §2.2, §2.3 | S | | 2.1 | `writing/` skeleton + templates | done |
| 2.2 | **Write `writing/STYLE.md`** | The missing handbook. Recover the rules from `.yarn` header comments and the three `docs/` plans. Highest-value single item in this plan. | M | | 2.2 | `writing/STYLE.md` | done — **digested from the vault handbook, not reconstructed** (below) |
| 2.3 | Extract `skill_bible.json` `notes` → `writing/voices/*.md` | one-shot script, 11 files | M | | 2.3 | Extract `skill_bible.json` prose → `writing/voices/*.md` | done — 11 sheets, lossless round-trip |
| 2.4 | Write the sync script md → JSON + an EditMode drift test | §2.4 | M | | 2.4 | Sync script + EditMode drift test | done — `tools/writing/sync_voices.py`, `VoiceSheetSyncTests` |
| 2.5 | Author the three character sheets | Bartender, Bradford Kane, Chevalier Cassian Thal | M | | 2.5 | Character sheets | done — Bartender, Chevalier Cassian Thal, Bradford Kane |
| 2.6 | Write `writing/README.md` + `YARN-PRIMER.md` | The Cowork session entry point, incl. the YarnCheck caveat (§2.6) | S | | 2.6 | `README.md` + `YARN-PRIMER.md` | done, plus `GLOSSARY.md` and `lore/candidates.md` |
| 2.7 | Move `SC101.yarn`'s header rules into `writing/scenes/sc101-*.md` | leave a one-line pointer in the `.yarn` | S | | 2.7 | SC-101 header rules → scene brief | done — header trimmed to the six rules that bind while editing, rationale moved to the brief |
Phase 2 touches **no Unity asset at all**. It can proceed while the Editor is open and while Validated: EditMode **42/42** (three new), YarnCheck compiles 9 files / 35 nodes, sheets in sync.
Phase 3 is in flight, and it is what unblocks Cowork. Consider doing it first. 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) ### Phase 3 — scene restructure (the risky part)
@@ -530,9 +551,10 @@ the world every session.
## 7. Open questions ## 7. Open questions
1. **Does a writing handbook exist outside the repo?** `docs/candidate-system-implementation-plan.md` 1. ~~Does a writing handbook exist outside the repo?~~ **Answered — yes** (Phase 2 notes). It is in the
says it exists but is not checked in. If there is a document somewhere, importing it beats Obsidian vault and is now pointed at rather than duplicated. The open part is whether any of
reconstructing it (Phase 2.2 assumes reconstruction). 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 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 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 first entered binary — most likely created or imported during a Force Binary window. Adding a