add agent reference and restructure plan
CLAUDE.md documents repository layout, the Unity command line, the dialogue content pipeline, validation, conventions, and known traps. AGENTS.md points at it for non-Claude agents. docs/restructure-plan.md covers both halves of the restructure: moving character and voice prose out of JSON string fields into a writer-facing writing/ tree, and migrating off the single-scene DialogueTest scaffold into Bootstrap/Systems/Level scenes. Phase 0 is recorded as executed; everything from Phase 1 on is proposal. Two findings in it were corrected after first being got wrong: the Unity CLI is installed (just not on PATH for non-interactive shells) and does have build and test commands, and the binary scene is not a stale save but a standing Unity behaviour where Force Text converts only when the setting changes. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -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).
|
||||
@@ -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 <pattern>`, `--retries <0-10>` (reports tests that pass on retry as
|
||||
flaky), `--rerun-failed`, `--shard N/M` for parallel CI, `--coverage`, `--timeout <seconds>`.
|
||||
|
||||
`--mode PlayMode` runs the PlayMode suite. **There are none yet** — `Assets/Tests/` is EditMode
|
||||
only, so that command reports zero tests. A PlayMode suite needs a new
|
||||
`Assets/Tests/PlayMode/*.asmdef` referencing `UnityEngine.TestRunner` with `includePlatforms`
|
||||
left empty.
|
||||
|
||||
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 <name>` 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 <<declare>>, 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 `<<declare>>`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 `<<declare>>` 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_<candidate_id>` | identity-lean counter | `CandidateYarnLintTests` — id must exist in `candidates.json` |
|
||||
| `$found_<clue_id>` | clue coverage | `CandidateYarnLintTests` — id must exist in `candidates.json` |
|
||||
| `$visited_<location_id>` | first arrival | convention; `unlockFlag` in `candidates.json` may only name these |
|
||||
| `$errand_<errand_id>` | errand state | same |
|
||||
| `$seen_<scene_id>` | scene witnessed | convention |
|
||||
| `$check_*` | written by `<<check>>` | **never `<<set>>` 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 |
|
||||
|---|---|---|
|
||||
| `<<check <skill> <Band>>>` | `Skills/Yarn/YarnSkillCommands.cs` | writes `$check_result`, `$check_roll`, `$check_total`, `$check_dc`, `$check_modifier`, `$check_degree`, `$check_skill` |
|
||||
| `<<skill_mod <source> <skill> <n>>>` | same | `source` = bucket key, e.g. `scene`, `drunk` |
|
||||
| `<<clear_skill_mods <source>>>` | same | |
|
||||
| `skill_rank(<skill>)` | 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(<name>)` | `Dialogue/AliasNameGenerator.cs` | deterministic; `$player_alias` is a smart variable derived from it |
|
||||
| `<<enter_environment>>` | `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
|
||||
|
||||
`<<detour>>` returns to the caller; `<<jump>>` 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
|
||||
`<<jump>>`, and each destination calls `<<enter_environment>>` itself.
|
||||
- A sub-conversation that must resume its caller → `<<detour>>`, and **never** `<<jump>>` 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 `<<if>>` 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` |
|
||||
| `<<declare>>` 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 `<<check>>` / `<<enter_environment>>` / `<<skill_mod>>` 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
|
||||
`<<declare>>` 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.<Area>` — `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.
|
||||
@@ -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/<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` | 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.
|
||||
Reference in New Issue
Block a user