# 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.