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]>
362 lines
18 KiB
Markdown
362 lines
18 KiB
Markdown
# 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.
|