diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5e06926 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,10 @@ +# AGENTS.md + +This repository's agent instructions live in [`CLAUDE.md`](./CLAUDE.md). It is +tool-agnostic — repository layout, the Unity command line, the dialogue content +pipeline, validation, conventions, and known traps. + +Read `CLAUDE.md` before making changes, regardless of which agent you are. + +Design and migration plans are in [`docs/`](./docs/), most relevantly +[`docs/restructure-plan.md`](./docs/restructure-plan.md). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..58319a1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,361 @@ +# Nightclub Arcadia — agent reference + +Reference for coding agents (Claude Code and others) working in this repository. +Human-facing design docs live in `docs/`. This file is the operational one: where +things are, what is generated, and how to build/test/validate from the terminal. + +Companion: `AGENTS.md` (pointer file, same content applies). + +--- + +## 1. Repository layout + +Repo root is `nightlcub-arcadia` (the typo is in the directory name, not a mistake +to fix — it is the checkout path). The Unity project is a **subdirectory**: + +``` +/Users/lennart/Dev/nightlcub-arcadia/ # git root +├── CLAUDE.md, AGENTS.md # this reference +├── docs/ # design + plan docs (markdown) +├── tools/YarnCheck/ # .NET 9 Yarn compiler/player, no Unity needed +├── .gitignore # macOS / IDE / toolchain rules +└── NightclubArcadia/ # ← the Unity project (-projectPath) + ├── .gitignore # Unity rules (anchored, must stay here) + ├── Assets/ + ├── Packages/ # incl. embedded dev.yarnspinner.unity + └── ProjectSettings/ +``` + +**Two `.gitignore` files, deliberately.** The Unity one anchors every rule with a +leading `/`, so it only works at the root of the Unity project folder. Do not merge them. + +Unity **6000.5.8f1**, URP 17.5.0, Cinemachine 3.1.7, Input System 1.20.0, +AI Navigation 2.0.14, ProBuilder 6.1.2, Test Framework 1.7.0. Yarn Spinner is an +**embedded package** at `NightclubArcadia/Packages/dev.yarnspinner.unity` (plus the +`textanimator` addon) — it is committed, not resolved from a registry. + +--- + +## 2. Ground rules + +1. **Never hand-edit generated `.asset` files.** `Assets/Skills/Definitions/*.asset`, + `Assets/Skills/SkillDatabase.asset`, `Assets/Candidates/Definitions/*.asset` and + `Assets/Candidates/CandidateDatabase.asset` are *build output*. Their sources are + `Assets/Skills/skill_bible.json` and `Assets/Candidates/candidates.json`. Edits are + 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 + 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.3). +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 + `git status` noise is expected and is not always yours. + +--- + +## 3. Unity from the command line + +### 3.0 Two different tools, both present + +| | What it is | Status | +|---|---|---| +| `/Users/lennart/.unity/bin/unity` | The **Unity CLI** (v1.0.0-beta.6). Wraps the Editor for CI-shaped work: `build`, `test`, `run`, `open`, plus editor/license/cloud management. | Installed. **Prefer this.** | +| `/Applications/Unity/Hub/Editor/6000.5.8f1/Unity.app/Contents/MacOS/Unity` | The **Editor executable**. Raw `-batchmode` flags. | Installed. Fallback, for flags the CLI does not expose. | + +The CLI is **not on `PATH` for non-interactive shells** — `which unity` fails even though the +binary exists, and `unity doctor` reports `check.binary-on-path warn`. Always invoke it by +absolute path, or export it: + +```bash +export UNITY_CLI="/Users/lennart/.unity/bin/unity" +export UNITY="/Applications/Unity/Hub/Editor/6000.5.8f1/Unity.app/Contents/MacOS/Unity" +export UNITY_PROJECT="/Users/lennart/Dev/nightlcub-arcadia/NightclubArcadia" +``` + +`unity doctor --no-banner` confirms the environment: signed in via OAuth keyring, editor +`6000.5.8f1` arm64 resolved from `ProjectVersion.txt`. Add `--json` to any command for +machine-readable output and `--no-banner` to keep logs clean. + +### 3.1 Always check the lock first + +Only one Unity instance per project path. Check before any headless run: + +```bash +pgrep -fl "Unity.app/Contents/MacOS/Unity" | grep -v AssetImportWorker +``` + +`AssetImportWorker*` processes are children of the main Editor — filter them out or you will +misread the check. `Temp/UnityLockfile` and `unity status` are the other two signals. + +### 3.2 Run tests + +```bash +"$UNITY_CLI" test "$UNITY_PROJECT" --no-banner --mode EditMode \ + --output /tmp/editmode-results.xml \ + --report-format both --junit-output /tmp/editmode-junit.xml +``` + +Useful flags: `--filter `, `--retries <0-10>` (reports tests that pass on retry as +flaky), `--rerun-failed`, `--shard N/M` for parallel CI, `--coverage`, `--timeout `. + +`--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. + +Raw-Editor equivalent, if you need a flag the CLI does not expose: + +```bash +"$UNITY" -batchmode -nographics -projectPath "$UNITY_PROJECT" \ + -runTests -testPlatform EditMode \ + -testResults /tmp/editmode-results.xml -logFile /tmp/unity-editmode.log +``` + +**Do not add `-quit` to a `-runTests` run** — the runner terminates the Editor itself, and +`-quit` on top of async work can hang it. + +**Parse the results XML; do not trust the exit code.** Unity's own docs state there is no common +exit-code definition across the components under test. Read `total`/`passed`/`failed` off the +root element and list any `test-case` whose `result` is not `Passed`. + +### 3.3 Build a player + +```bash +"$UNITY_CLI" build "$UNITY_PROJECT" --no-banner \ + --target StandaloneOSX -o /tmp/NightclubArcadia.app +``` + +Without `--execute-method` the CLI uses Unity's built-in build — a Build Profile on Unity 6+, or +the legacy desktop player flags — and builds whatever scenes `EditorBuildSettings` enables. +`--profile ` selects a profile from `Assets/Settings/Build Profiles` (the profile defines +the target). `-l/--log-file` overrides the log path; the log streams to console unless `--no-tail`. + +**No build script exists yet.** When one is added, put it at `Assets/Editor/BuildPipeline.cs` and +call `--execute-method NightclubArcadia.Editor.BuildPipeline.BuildMacOS`. The CLI forwards the +output path as `-buildOutput` and **your method is responsible for honouring it**. A build method +must call `EditorApplication.Exit(1)` on failure, or Unity exits 0 and CI reports a green build +that produced nothing. + +### 3.4 Run an arbitrary Editor method + +```bash +"$UNITY_CLI" run "$UNITY_PROJECT" --no-banner --timeout 900 -- \ + -executeMethod SomeClass.SomeStaticMethod +``` + +**`unity run` owns `-batchmode`, `-nographics`, `-quit` and `-logFile`.** Passing any of them +after `--` fails with a reserved-flag error. Pass only your own arguments. + +Output does **not** stream to stdout. The log lands at `$UNITY_PROJECT/Logs/Editor.log` (the +previous run rotates to `Editor-prev.log`), so tag your `Debug.Log` calls and grep for them: + +```bash +grep -n "\[YourTag\]" "$UNITY_PROJECT/Logs/Editor.log" | tail -20 +``` + +This is the mechanism for the JSON → ScriptableObject regeneration in +`Assets/Editor/SkillSystemSetup.cs` and `CandidateSystemSetup.cs`. Read the actual class and +method names out of those files before invoking — do not guess them. + +## 4. The dialogue content pipeline + +### 4.1 Where content lives + +``` +NightclubArcadia/Assets/Dialogue/ +├── NightclubArcadia.yarnproject # globs **/*.yarn — new files need no registration +├── Common.yarn # every <>, project-wide. Never played. +├── Commentary.yarn # passive skill barks, one node per (context, skill) +├── Scenes/ SC101, SC102, Debug +├── Characters/ Bartender, Bradford Kane, Chevalier Cassian Thal +└── Objects/ Chair +``` + +**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 +up automatically by the `**/*.yarn` glob in the `.yarnproject`. Adding a scene or a character +requires **no C#, no Unity, and no `.yarnproject` edit**. + +The one caveat: the `.yarnproject` also stores `editorOptions.yarnScriptEditor.projectGraphPositions` +— per-file x/y coordinates for the Yarn graph editor. These churn whenever anyone opens the +graph view. Treat that block as editor state; do not fight over it in review. + +### 4.2 The variable contract + +Every variable used anywhere must be `<>`d in `Common.yarn`. This is the whole point +of that file: the compiler then catches `$reputaton` instead of silently creating a second +variable at runtime. Adding a scene flag means adding a `<>` line — that is a writer +action, not a code action. + +Naming families that are load-bearing (enforced or lint-checked): + +| Family | Meaning | Checked by | +|---|---|---| +| `$self_lean_` | identity-lean counter | `CandidateYarnLintTests` — id must exist in `candidates.json` | +| `$found_` | clue coverage | `CandidateYarnLintTests` — id must exist in `candidates.json` | +| `$visited_` | first arrival | convention; `unlockFlag` in `candidates.json` may only name these | +| `$errand_` | errand state | same | +| `$seen_` | scene witnessed | convention | +| `$check_*` | written by `<>` | **never `<>` by hand** | + +### 4.3 Writer-facing Yarn vocabulary + +Commands and functions registered by game code, i.e. the full set a writer may use: + +| Syntax | Defined in | Notes | +|---|---|---| +| `< >>` | `Skills/Yarn/YarnSkillCommands.cs` | writes `$check_result`, `$check_roll`, `$check_total`, `$check_dc`, `$check_modifier`, `$check_degree`, `$check_skill` | +| `< >>` | same | `source` = bucket key, e.g. `scene`, `drunk` | +| `<>>` | same | | +| `skill_rank()` | same | function | +| `top_skill(a, b, …)` | `Dialogue/DialougueSkillComparison.cs` | **returns its FIRST argument on a tie** — argument order is a design decision, not incidental | +| `alias_name()` | `Dialogue/AliasNameGenerator.cs` | deterministic; `$player_alias` is a smart variable derived from it | +| `<>` | `Dialogue/DialogueTransitionController.cs` | hands control back to the world | + +(The filename `DialougueSkillComparison.cs` is misspelled in the repo. Left alone deliberately — +renaming it changes a `.meta` GUID. Fold it into a rename batch, not a drive-by fix.) + +### 4.4 Flow control discipline + +`<>` returns to the caller; `<>` does not and clears the return stack. +The rule the existing files follow, and which new content must follow: + +- A node started directly by `DialogueInteractable` has nothing waiting on it → its branches + `<>`, and each destination calls `<>` itself. +- A sub-conversation that must resume its caller → `<>`, and **never** `<>` inside it. + +Nodes sharing a `title:` form a **node group**; Yarn runs the most specific variation whose +`when:` conditions pass. Prefer adding a variation over growing an `<>` chain. + +### 4.5 How content reaches Unity + +There is **no manual sync step** for dialogue. The chain is: + +``` +.yarn file saved → Unity's YarnProjectImporter recompiles the .yarnproject on focus + → DialogueRunner plays the new nodes on next Play +``` + +The only things that need a human/Editor step are the *data* files, not the dialogue: + +``` +skill_bible.json → Tools ▸ Nightclub Arcadia ▸ (SkillSystemSetup) → Skills/Definitions/*.asset + SkillDatabase.asset +candidates.json → Tools ▸ Nightclub Arcadia ▸ (CandidateSystemSetup) → Candidates/Definitions/*.asset + CandidateDatabase.asset +``` + +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. + +### 4.6 Code-owned vs writer-owned + +| Writer-owned (edit freely, no Unity) | Code-owned (needs an engineer) | +|---|---| +| `Assets/Dialogue/**/*.yarn` | `Assets/Scripts/**/*.cs` | +| `<>` lines in `Common.yarn` | `Assets/Editor/**/*.cs` | +| `Assets/Skills/skill_bible.json` (prose fields) | Yarn command/function *implementations* | +| `Assets/Candidates/candidates.json` (`status`, `sceneId`, `notes`, `unlockFlag`) | `.asset`, `.prefab`, `.unity`, `.meta` | +| `docs/` writing docs | `ProjectSettings/**` | + +**A writer must never need to open Unity or touch C# to write or revise a scene, a character, +or a branch.** If a writing task requires either, that is a pipeline bug — file it. + +--- + +## 5. Validation + +### 5.1 EditMode test suite + +`NightclubArcadia/Assets/Tests/EditMode/` — 7 files, ~870 LOC, two assemblies: + +- `NightclubArcadia.Skills.Tests` — `SkillRosterTests`, `SkillCheckSystemTests`, `DcBandTests`, + `CommentaryPickerTests`, `CandidateRegistryTests`, `CandidateYarnLintTests` +- `NightclubArcadia.Locomotion.Math.Tests` — `MotionInputMathTests` + +`CandidateYarnLintTests` is a **regex lint over Yarn source**: it asserts every +`$self_lean_*` / `$found_*` in any `.yarn` file resolves to a real id in `CandidateDatabase`. +It is a tripwire, not a proof. + +### 5.2 One-click in-Editor run + +`Tools ▸ Nightclub Arcadia ▸ Run Skill EditMode Tests` (`Assets/Editor/SkillSystemTestRunner.cs`). + +### 5.3 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 +can never drift from the compiler the game uses. This is the fastest correctness signal in the +repo and it needs no Editor lock. Use it on every dialogue change. + +```bash +cd /Users/lennart/Dev/nightlcub-arcadia/tools/YarnCheck + +# compile everything — exit 1 + diagnostics on failure +dotnet run -- ../../NightclubArcadia/Assets/Dialogue + +# compile, then play a node, choosing option 2, 2, 0, 0 +dotnet run -- ../../NightclubArcadia/Assets/Dialogue Bartender_Talk 2 2 0 0 +``` + +Leftover picks restart the node — that deliberately models the player walking away and coming +back, which is how these scenes are actually played. + +**What it does not model** (read traces with this in mind): Unity commands are printed, not +executed, so `<>` / `<>` / `<>` are stepped over and +`$check_result` stays `false` — every check-gated branch shows its *failure* side. Yarn +functions are stubbed: `top_skill` mirrors the real first-argument-wins tie behaviour, +`alias_name` returns a placeholder, `skill_rank` returns 0. Variables reset to their +`<>` defaults each run; there is no save state. + +Requires the .NET 9 SDK. `tools/YarnCheck/bin/` and `obj/` are currently **committed** and +should not be — see `docs/restructure-plan.md`. + +--- + +## 6. Code conventions + +- Namespaces: `NightclubArcadia.` — `Skills`, `Dialogue`, `UI`, `UI.Dialogue`, + `UI.HUD`, `UI.Menu`, `Cinematics`, `Interaction`, `Player`, `Player.Math`. + **Exception:** `NPCStandIn.cs` sits in the global namespace. That is a scaffold tell, not a + convention to copy — new runtime types always get a namespace. +- Assembly definitions exist for `NightclubArcadia.Skills` and `NightclubArcadia.Locomotion.Math` + only. Camera, Player, UI, Dialogue, NPCs and Interaction all currently compile into the + default `Assembly-CSharp`. Widening asmdef coverage is planned, not done. +- Comments in this codebase carry *decision history* — why a flag is written at the bottom of a + node rather than the top, why four voices in SC-101 breaks the three-voice cap on purpose. + Match that density. Do not strip such comments as "noise"; they are the design record. +- Editor-only code lives under `Assets/Editor/`. Anything there is stripped from builds and may + reference `UnityEditor`. + +--- + +## 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 + 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 + 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. diff --git a/docs/restructure-plan.md b/docs/restructure-plan.md new file mode 100644 index 0000000..e0565d3 --- /dev/null +++ b/docs/restructure-plan.md @@ -0,0 +1,544 @@ +# 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 — Phase 0 is 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. + +Everything below from §1 onward is unchanged proposal. Nothing in §2–§5 has been applied. + +### 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/.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 `<>` 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 `<>`, 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.`, + matching the folder. **No global-namespace runtime types.** +- **Yarn nodes:** the existing conventions hold and are already consistent — + `_Talk`, `SC###_Hook_`, `Commentary__`. +- **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.`. +- **`.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` | done | +| 0.6 | Push `main` and the tag to `origin` | pending | + +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 (no Unity changes, fully parallel to Phase 3+) + +| # | Step | Detail | Effort | +|---|---|---|---| +| 2.1 | Create `writing/` skeleton + templates | §2.2, §2.3 | S | +| 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.3 | Extract `skill_bible.json` `notes` → `writing/voices/*.md` | one-shot script, 11 files | M | +| 2.4 | Write the sync script md → JSON + an EditMode drift test | §2.4 | M | +| 2.5 | Author the three character sheets | Bartender, Bradford Kane, Chevalier Cassian Thal | M | +| 2.6 | Write `writing/README.md` + `YARN-PRIMER.md` | The Cowork session entry point, incl. the YarnCheck caveat (§2.6) | S | +| 2.7 | Move `SC101.yarn`'s header rules into `writing/scenes/sc101-*.md` | leave a one-line pointer in the `.yarn` | S | + +Phase 2 touches **no Unity asset at all**. It can proceed while the Editor is open and while +Phase 3 is in flight, and it is what unblocks Cowork. Consider doing it first. + +### 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. + +**Still to do:** push `main` and the tag to `origin`, and the Editor-UI serialization fix (§0.1). + +### 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?** `docs/candidate-system-implementation-plan.md` + says it exists but is not checked in. If there is a document somewhere, importing it beats + reconstructing it (Phase 2.2 assumes reconstruction). +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.