mirror of
https://github.com/Donchitos/Claude-Code-Game-Studios.git
synced 2026-10-16 09:58:36 +00:00
Fix release for 1.1.2: the 14 longest skills keep their later phases after a compaction, a question nobody answered is never taken as an answer, settings read the same from a subfolder, the commit check works for a project in a subfolder of a larger repository, every skill opens with a title, and the shipped allow rules no longer approve deleting a branch or writing files without asking. Details in CHANGELOG.md; upgrade steps in UPGRADING.md (v1.1.2 → v1.1.3). Refs #83 Refs #133 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0185en4t76fPn7315q4c3nko
521 lines
29 KiB
Markdown
521 lines
29 KiB
Markdown
# /setup-engine — Section 5.5: Populate project.yaml
|
||
|
||
> Part of `/setup-engine`. `SKILL.md` names when to read this file; the settings it
|
||
> resolved and the rules under its later headings still apply here.
|
||
|
||
## 5.5 Populate `project.yaml` (Primary Config Store)
|
||
|
||
**In this file:**
|
||
|
||
- 5.5.1 Build the four blocks
|
||
- 5.5.2 Write the blocks to `project.yaml`
|
||
- 5.5.3 Verify the dual-write
|
||
|
||
> **v1.1:** `project.yaml` at the repo root is the **primary** store for project
|
||
> configuration. The `.claude/docs/technical-preferences.md` file written in
|
||
> Section 5 is kept as a **legacy mirror** for backward compatibility. This step
|
||
> is a **dual-write** — the same engine facts go into both files, and
|
||
> `project.yaml` wins when a skill reads a key present in both.
|
||
|
||
After `technical-preferences.md` is written and approved, dual-write the
|
||
structured config into `project.yaml`. Four blocks are written: `engine`,
|
||
`specialists`, `naming`, and `commands`.
|
||
|
||
### 5.5.1 Build the four blocks
|
||
|
||
Derive every value from the engine and language already chosen in Sections 3–4.
|
||
Do NOT re-ask for the **engine, version or language** — reuse those decisions.
|
||
|
||
> **That is not a blanket "never ask".** `rendering` and `physics` below depend on
|
||
> two further inputs — target platform and 2D-vs-3D — which **Section 2 collects on
|
||
> only one of the four entry paths**. When they were never collected, ask; the
|
||
> unavailable-branch under the project-shape table says how.
|
||
|
||
**`engine` block:**
|
||
|
||
```yaml
|
||
engine:
|
||
name: "<Godot|Unity|Unreal>"
|
||
version: "<version from Section 3>"
|
||
language: "<GDScript|C#|C++|Blueprint>"
|
||
rendering: "<engine default — see table>"
|
||
physics: "<engine default — see table>"
|
||
```
|
||
|
||
**On Godot these two values depend on the answers Section 2 already collected —
|
||
the target platform and 2D-vs-3D — so do not pick them from the engine name
|
||
alone.** On Unity and Unreal the table has a single `any` row and the engine name
|
||
*is* sufficient; take those rows directly.
|
||
|
||
| Engine | Project shape | `rendering` default | `physics` default |
|
||
|--------|---------------|---------------------|-------------------|
|
||
| Godot 4.x | 2D · web target | Compatibility | Godot Physics 2D |
|
||
| Godot 4.x | 2D · non-web | Forward+ | Godot Physics 2D |
|
||
| Godot 4.x | 3D · web target | Compatibility | Jolt |
|
||
| Godot 4.x | 3D · non-web | Forward+ | Jolt |
|
||
| Unity | any | URP | PhysX |
|
||
| Unreal | any | Lumen + Nanite (deferred) | Chaos |
|
||
|
||
> **Why this table has a shape column and the others do not.** Both Godot
|
||
> defaults were wrong for a 2D browser game, and both were wrong from data this
|
||
> skill had already collected and then discarded.
|
||
> - **Jolt is Godot's default *3D* engine.** `docs/engine-reference/godot/modules/physics.md`:
|
||
> "Jolt Physics is the DEFAULT 3D engine" and "2D physics UNCHANGED (still
|
||
> Godot Physics 2D)". Writing `Jolt` on a 2D project sends the implementer to
|
||
> tune a backend their game does not use.
|
||
> - **Forward+ is desktop-focused.** `docs/engine-reference/godot/modules/rendering.md`:
|
||
> Forward+ is "Full featured, desktop-focused"; Compatibility is
|
||
> "OpenGL 3.3 / WebGL 2, broadest hardware support" — the browser path.
|
||
>
|
||
> If Section 2's platform answer was `Multiple platforms` **including** web, use
|
||
> the web row: the renderer must work everywhere the game ships.
|
||
|
||
> **Godot only — Unity and Unreal have one row each, keyed `any`.** Shape cannot
|
||
> change `rendering` or `physics` for those two, so do **not** run the resolution
|
||
> below for them: asking an operator a question whose answer you will discard is
|
||
> exactly what §5.5.1 opens by forbidding. On Unity write `URP`/`PhysX` and on
|
||
> Unreal write `Lumen + Nanite (deferred)`/`Chaos` straight from the table.
|
||
>
|
||
> **Section 2 did not necessarily run — check before reading its answers.**
|
||
> Target platform and 2D-vs-3D are collected under *Questions 2–6, only if no
|
||
> prior engine experience*. **Three of the four entry paths never reach them:**
|
||
> `/setup-engine <engine> <version>` and `/setup-engine <engine>` skip Section 2
|
||
> outright, and guided mode skips the matrix as soon as the user names an engine
|
||
> they have worked in before. Only guided mode answered `None` or `Multiple`
|
||
> collects both.
|
||
>
|
||
> Resolve each input in this order and stop at the first that answers:
|
||
> 1. **Section 2's answers**, if that path ran.
|
||
> 2. **The design artifact** — `design/gdd/game-concept.md` at `standard`/`full`,
|
||
> `design/game-brief.md` at `minimal`. Section 2 already extracts platform
|
||
> targets from these; extract 2D-vs-3D the same way when it is stated.
|
||
> 3. **Ask.** Do not infer the shape from the engine name, and do not write a row
|
||
> you could not justify:
|
||
> - Prompt: "`rendering` and `physics` depend on the project's shape, which
|
||
> hasn't been established yet. Is this 2D or 3D, and does it ship to web?"
|
||
> - Options: `2D · non-web` / `2D · web` / `3D · non-web` / `3D · web`, plus
|
||
> `Leave as [TO BE CONFIGURED]` — a legitimate answer here, revisited via
|
||
> `/settings` once the concept exists.
|
||
>
|
||
> `[TO BE CONFIGURED]` is a valid value for both keys. A wrong `Jolt` on a 2D
|
||
> project is worse than an unset one, because nothing downstream questions it.
|
||
|
||
- `rendering` and `physics` are engine-typical defaults — tell the user they can
|
||
be changed later via `/settings` if the project uses a different setup.
|
||
- For Godot **Both**, write `language: "GDScript"` (the primary gameplay
|
||
language); the C# usage is recorded in `technical-preferences.md`.
|
||
- For Unreal, write `language: "C++"` — or `"Blueprint"` only if the project is
|
||
Blueprint-primary. Blueprint as a secondary tool does not change this value.
|
||
|
||
**`specialists` block** — copy directly from the routing table chosen in
|
||
Section 5 (**A3** in `references/godot-language-config.md` for Godot):
|
||
|
||
| Engine / language | `code` | `shader` | `ui` | `additional` |
|
||
|-------------------|--------|----------|------|--------------|
|
||
| Godot — GDScript | godot-gdscript-specialist | godot-shader-specialist | godot-specialist | [godot-gdextension-specialist] |
|
||
| Godot — C# | godot-csharp-specialist | godot-shader-specialist | godot-specialist | [godot-gdextension-specialist] |
|
||
| Godot — Both | godot-gdscript-specialist | godot-shader-specialist | godot-specialist | [godot-csharp-specialist, godot-gdextension-specialist] |
|
||
| Unity | unity-specialist | unity-shader-specialist | unity-ui-specialist | [unity-dots-specialist, unity-addressables-specialist] |
|
||
| Unreal | unreal-specialist | unreal-specialist | ue-umg-specialist | [ue-gas-specialist, ue-blueprint-specialist, ue-replication-specialist] |
|
||
|
||
```yaml
|
||
specialists:
|
||
code: "<code specialist>"
|
||
shader: "<shader specialist>"
|
||
ui: "<ui specialist>"
|
||
additional: ["<...>"]
|
||
```
|
||
|
||
**`naming` block** — enum values (NOT the prose form used in
|
||
`technical-preferences.md`). Use the column matching the engine and, for Godot,
|
||
the language:
|
||
|
||
| Field | Godot GDScript | Godot C# | Unity | Unreal |
|
||
|-------|----------------|----------|-------|--------|
|
||
| `classes` | PascalCase | PascalCase | PascalCase | PascalCase |
|
||
| `variables` | snake_case | camelCase | camelCase | **PascalCase** |
|
||
| `constants` | SCREAMING_SNAKE | PascalCase | SCREAMING_SNAKE | SCREAMING_SNAKE |
|
||
| `signals` | past_tense | on_event_name | **PascalCase `On<Event>`** | **PascalCase `On<Event>`** |
|
||
| `files` | snake_case | PascalCase | PascalCase | PascalCase |
|
||
| `scenes` | snake_case | PascalCase | PascalCase | PascalCase |
|
||
|
||
> **The Unreal column is sourced, not inherited from the table's generic shape.**
|
||
> `variables: camelCase` and `signals: on_event_name` are the two rows that shape
|
||
> would produce, and both are wrong for Unreal. Checked against this repo's
|
||
> pinned reference: `docs/engine-reference/unreal/` contains **zero** camelCase
|
||
> variable declarations and uses PascalCase throughout (`int32 Health;`,
|
||
> `FVector ServerPosition;`), and the word "signal" **never appears in it at all** —
|
||
> it is Godot vocabulary. Unreal has delegates and events, and the reference's
|
||
> handlers are PascalCase `On<Event>`.
|
||
>
|
||
> Two caveats kept rather than smoothed over. (1) `classes: PascalCase` is
|
||
> **incomplete** for Unreal, not wrong: UE requires a mandatory type prefix
|
||
> (`U`/`A`/`F`/`E`/`I`/`T`) that this enum cannot express, so an Unreal project's
|
||
> coding standards must state it separately. (2) `scenes` is a **category
|
||
> mismatch** — Unreal has Levels and Maps under World Partition, not scenes — left
|
||
> in place because the key is shared across engines and renaming it is a schema
|
||
> change, not a value fix.
|
||
>
|
||
> The blast radius is limited: `naming.*` is currently a
|
||
> **documented-but-unread** setting, so wrong values misinform a human reading
|
||
> `project.yaml` rather than driving any automated check — today. That is a
|
||
> reason to get them right now, while it is cheap.
|
||
|
||
> **Three further rules for this table.**
|
||
>
|
||
> **The Unity `build` row must not hardcode `-buildTarget StandaloneLinux64`.**
|
||
> A hardcoded target gives every Unity project a **Linux** build command, whatever
|
||
> platform it actually ships on. The row reads `<TARGET>`: **ask the operator for
|
||
> the build target and write the value they give.** The correct `-buildTarget` identifiers
|
||
> are not sourceable from `docs/engine-reference/unity/`, so this skill must not
|
||
> emit one from recall — the same call made for `/security-audit`'s save
|
||
> patterns and for Unity's release dates. A wrong-but-plausible default is worse
|
||
> than an explicit question: it produces a `commands.build` that runs, succeeds,
|
||
> and builds the wrong artifact.
|
||
>
|
||
> **`-buildTarget` alone builds nothing.** It switches the active platform and
|
||
> exits 0 (verified on 6000.3.23f1). The row also needs a player-build flag,
|
||
> `-build<PLATFORM>Player <output path>` — for Windows 64-bit,
|
||
> `-buildWindows64Player Builds/Win64/<Game>.exe` (verified: exit 0 and an
|
||
> `.exe`; exit 1 on a compile error or when no scene is in the build settings).
|
||
> Ask which platform, and confirm any other platform's flag against Unity's
|
||
> command-line documentation rather than recalling one.
|
||
>
|
||
> **The Godot `build` row must not hardcode `'Linux/X11'` either — same defect,
|
||
> same remedy.** A Godot export preset name is **project-defined**: it is whatever
|
||
> string the operator typed into `export_presets.cfg`, and the shipped defaults are
|
||
> per-platform (`Windows Desktop`, `macOS`, `Web`, `Linux/X11`). Writing
|
||
> `'Linux/X11'` gives a Windows-targeting project a build command that fails with
|
||
> `Unknown export preset`, and gives a project that *does* have a Linux preset a
|
||
> command that silently builds the wrong platform. The row reads `<PRESET>`:
|
||
> **ask the operator which export preset to build and write the value they give**,
|
||
> exactly as the Unity row does. If no presets exist yet — common, since
|
||
> `/setup-engine` usually runs before the Godot project has any — write
|
||
> `[TO BE CONFIGURED]` and say so, rather than guessing a name that does not exist.
|
||
>
|
||
> **The `constants` row must not contradict this skill's own prose.** The table says
|
||
> `SCREAMING_SNAKE` for Unity; the Unity section of this same file says
|
||
> "Constants: PascalCase or UPPER_SNAKE_CASE". Both cannot be authoritative, and
|
||
> an agent following one produces config the other rejects. The row is **left
|
||
> unchanged pending sourcing** rather than picked by preference — neither
|
||
> `docs/engine-reference/unity/` nor `unreal/` contains a SCREAMING_SNAKE constant
|
||
> to confirm or refute it, and the Unreal run flagged the same row as wrong for
|
||
> Unreal on the same grounds. Resolve by adding a naming section to the engine
|
||
> references, then correct table and prose together. Until then, treat
|
||
> `naming.constants` as unverified for Unity and Unreal.
|
||
>
|
||
> **Write that caveat into `project.yaml`, do not leave it here.** On Unity and
|
||
> Unreal, emit the `constants` line with a trailing comment:
|
||
> ```yaml
|
||
> naming:
|
||
> constants: SCREAMING_SNAKE # UNVERIFIED — not sourceable from
|
||
> # docs/engine-reference/<engine>/; confirm before relying on it
|
||
> ```
|
||
> Without it, a value the skill *knows* is unsourced is recorded as flatly as
|
||
> `classes: PascalCase`, which is sourced, and the reader of the primary config
|
||
> store cannot tell them apart. The `commands` block nine lines below already does
|
||
> exactly this with its `# TODO: confirm these` line — this is the same pattern,
|
||
> applied to the value that needs it just as much.
|
||
|
||
For Godot **Both**, use the GDScript column — `.gd` files dominate gameplay
|
||
scripting; `.cs` files follow C# conventions per-file (see **A2** in `references/godot-language-config.md`).
|
||
|
||
```yaml
|
||
naming:
|
||
classes: <value>
|
||
variables: <value>
|
||
constants: <value>
|
||
signals: <value>
|
||
files: <value>
|
||
scenes: <value>
|
||
```
|
||
|
||
**`commands` block** — engine-default shell commands, simple-string form:
|
||
|
||
| Engine | `build` | `test` | `run` | `smoke` |
|
||
|--------|---------|--------|-------|---------|
|
||
| Godot | `godot --headless --export-debug '<PRESET>'` **(ASK — do not default)** | `godot --headless -s -d --remote-debug tcp://127.0.0.1:0 res://addons/gdUnit4/bin/GdUnitCmdTool.gd -a res://tests --ignoreHeadlessMode` | `godot --path . --windowed --resolution 1280x720` | `godot --headless --quit-after 5` |
|
||
| Unity | `"<Unity editor>" -batchmode -quit -projectPath . -buildTarget <TARGET> -build<PLATFORM>Player Builds/<Target>/<Game>.exe` **(ASK — do not default)** | `"<Unity editor>" -batchmode -runTests -projectPath . -testPlatform EditMode -testResults test-results/editmode.xml` | `Builds/<Target>/<Game>.exe -screen-width 1280 -screen-height 720 -screen-fullscreen 0` | `"<Unity editor>" -batchmode -quit -projectPath . -logFile -` |
|
||
|
||
> **What each exit code proves — every row verified on the pinned engines.**
|
||
> Godot `test` exits 0 when all pass, 100 on a failure, 101 when all pass but
|
||
> nodes leaked (a warning, not a failure), 105 when a test script does not
|
||
> parse, and 103 / 104 when gdUnit4 cannot run at all (headless refused, Godot
|
||
> older than 4.3). Keep `--remote-debug tcp://127.0.0.1:0`: without it a script
|
||
> error opens Godot's interactive debugger and the run waits at a `debug>`
|
||
> prompt forever instead of exiting. Run `godot --headless --path . --import`
|
||
> before it on a fresh clone: with no `.godot/` class cache,
|
||
> `GdUnitCmdTool.gd` did not load and the run exits 1 having run nothing. Unity `test`
|
||
> exits 0 / 2, and a results file with `testcasecount="0"` means no test was
|
||
> compiled — not a pass. It runs Edit Mode only: Play Mode tests under
|
||
> `Assets/Tests/PlayMode/` need a second run into their own results file
|
||
> (`-testPlatform PlayMode -testResults test-results/playmode.xml`), which
|
||
> `/smoke-check` makes whenever that folder holds tests and CI runs as its own
|
||
> step. Unity `smoke` exits 1 on a compile error, 0 when clean.
|
||
> **Godot `smoke` exits 0 even on a parse error** — it is a boot check: read its
|
||
> output for `SCRIPT ERROR`. It also needs `run/main_scene` set: without one it
|
||
> prints `Can't run project: no main scene defined` and never exits, so run it
|
||
> under a timeout. Unity `smoke` compiles only the editor-side assemblies; an
|
||
> error that exists only in a player build (an `Editor/` script without its own
|
||
> assembly) shows up in `build`, not here. The real Godot parse check is
|
||
> `godot --headless --path . --import`, then
|
||
> `godot --headless --path . -s res://.claude/scripts/godot-parse-check.gd -- res://<file>.gd …`
|
||
> (exit 1 when a script does not load). `--check-only` is not one: it fails
|
||
> valid code that names an autoload. `<Unity editor>` is the **editor**
|
||
> executable's full path, quoted. Never write bare `Unity`: on `PATH` that name
|
||
> may be Unity's separate CLI, which rejects `-batchmode` with exit 2 — the same
|
||
> code as a failed test.
|
||
|
||
> **`commands.run` is the one the run-and-observe step depends on.** It must
|
||
> launch the **game**, windowed, at a fixed resolution — never the editor, and
|
||
> never with a headless / batch / null-RHI flag, which exist to skip rendering.
|
||
> `/dev-story` Phase 6 step 4 appends the per-engine capture flags to it
|
||
> (`.claude/docs/run-and-observe.md`). `smoke` is Unity's parse check, `test`
|
||
> feeds `/smoke-check`; `build` is the one row you must ask for.
|
||
|
||
> **YAML quoting rule — applies to EVERY command value.** `yaml-helper.sh` — the
|
||
> parser every config read in this framework goes through — decodes **no**
|
||
> escape sequences: neither `\"` inside a double-quoted scalar nor `''` inside a
|
||
> single-quoted scalar. It takes the first matching quote character as the end of
|
||
> the value and discards the rest, so `"he said \"hi\" now"` parses as
|
||
> `he said \`. Verified against the parser, not inferred. The rule is
|
||
> load-bearing: `/smoke-check` and `/dev-story` run these values, `/settings`
|
||
> reads and writes them through this parser, and the same holds for every other
|
||
> quoted value in `project.yaml`.
|
||
> Pick the quote style that needs **no escaping**:
|
||
> - value contains a single quote `'` (e.g. the Godot `build` above) → wrap it in
|
||
> a **double-quoted** YAML scalar: `"... 'Linux/X11' ..."`
|
||
> - value contains a double quote `"` (e.g. the Unreal `test`/`smoke` below) →
|
||
> wrap it in a **single-quoted** YAML scalar: `'... "Quit" ...'`
|
||
> - value contains neither → use a double-quoted scalar for consistency.
|
||
> Never use a quote style that forces an escape (`\"` or `''`) — the parser
|
||
> truncates the value at the escape. A value containing BOTH quote types cannot
|
||
> be represented for this parser; rewrite the command to drop one.
|
||
|
||
**Godot / Unity** — write the table values directly, substituting the operator's
|
||
answer for `<PRESET>` / `<TARGET>` / `<PLATFORM>`, and the editor's full path for
|
||
`<Unity editor>`. Godot's `build` contains a single quote, so it
|
||
takes a double-quoted scalar (`Windows Desktop` below is the *example* answer, not
|
||
a default — write what the operator said):
|
||
|
||
```yaml
|
||
commands:
|
||
build: "godot --headless --export-debug 'Windows Desktop'"
|
||
test: "godot --headless -s -d --remote-debug tcp://127.0.0.1:0 res://addons/gdUnit4/bin/GdUnitCmdTool.gd -a res://tests --ignoreHeadlessMode"
|
||
run: "godot --path . --windowed --resolution 1280x720"
|
||
smoke: "godot --headless --quit-after 5"
|
||
```
|
||
|
||
**Write bare `godot` only when Section 3's `godot --version` ran** — that is,
|
||
`godot` is on `PATH`. Otherwise (the usual case on Windows and macOS)
|
||
substitute the executable the probe found or the user gave — a Windows
|
||
`Godot_v<version>-stable_win64.exe`, macOS `/Applications/Godot.app/Contents/MacOS/Godot`
|
||
— as its full path, in double quotes, for the leading `godot` in all four
|
||
values, exactly as `<Unity editor>` is. The value then holds double quotes, so it
|
||
takes a single-quoted scalar, and `build` quotes its preset with double quotes
|
||
too, so no value holds both quote types. Godot not on PATH, installed under
|
||
`C:/Program Files/Godot/`:
|
||
|
||
```yaml
|
||
commands:
|
||
build: '"C:/Program Files/Godot/Godot_v4.6.1-stable_win64.exe" --headless --export-debug "Windows Desktop"'
|
||
test: '"C:/Program Files/Godot/Godot_v4.6.1-stable_win64.exe" --headless -s -d --remote-debug tcp://127.0.0.1:0 res://addons/gdUnit4/bin/GdUnitCmdTool.gd -a res://tests --ignoreHeadlessMode'
|
||
run: '"C:/Program Files/Godot/Godot_v4.6.1-stable_win64.exe" --path . --windowed --resolution 1280x720'
|
||
smoke: '"C:/Program Files/Godot/Godot_v4.6.1-stable_win64.exe" --headless --quit-after 5'
|
||
```
|
||
|
||
If the probe found no executable and the user does not know where it is, keep
|
||
bare `godot`, put `# TODO: godot is not on PATH here — set the editor's full
|
||
path via /settings` above the block, and say so: those commands will not run
|
||
until one of the two is fixed.
|
||
|
||
For Unity, probe the Hub install (`C:/Program Files/Unity/Hub/Editor/<version>/Editor/Unity.exe`
|
||
on Windows, `/Applications/Unity/Hub/Editor/<version>/Unity.app/Contents/MacOS/Unity`
|
||
on macOS, `/home/<user>/Unity/Hub/Editor/<version>/Editor/Unity` on Linux) or ask.
|
||
Write the expanded absolute path — a `~` inside quotes is not expanded, and the
|
||
command then exits 127. The
|
||
quoted path puts double quotes in the command, so the value takes a
|
||
single-quoted scalar:
|
||
|
||
```yaml
|
||
commands:
|
||
test: '"C:/Program Files/Unity/Hub/Editor/6000.3.23f1/Editor/Unity.exe" -batchmode -runTests -projectPath . -testPlatform EditMode -testResults test-results/editmode.xml'
|
||
smoke: '"C:/Program Files/Unity/Hub/Editor/6000.3.23f1/Editor/Unity.exe" -batchmode -quit -projectPath . -logFile -'
|
||
```
|
||
|
||
> **Also write `engine.path` if the editor is not on `PATH`.** Agents and
|
||
> `/smoke-check` read it to find the editor, but a command is run as written —
|
||
> `engine.path` is never spliced into `commands.*` at run time, which is why the
|
||
> full path goes into the commands above — and on Windows and macOS none of
|
||
> the three engines installs onto `PATH` by default (on macOS the editor is inside an app
|
||
> bundle, e.g. `/Applications/Godot.app/Contents/MacOS/Godot`). Ask for or probe the
|
||
> install location and record it — the editor executable for Godot and Unity,
|
||
> the engine folder (`<UE root>`) for Unreal:
|
||
>
|
||
> ```yaml
|
||
> engine:
|
||
> path: "C:/Program Files/Epic Games/UE_5.7" # omit if the editor is on PATH
|
||
> ```
|
||
>
|
||
> **This is not cosmetic.** Without it, an agent reports *"no editor on this
|
||
> machine"* and ships C++ it never compiled — on a machine where that engine WAS
|
||
> installed. Nothing in the project told it where to look, so absence of a path read
|
||
> as absence of an engine.
|
||
|
||
For **Unreal**, UE build/test commands vary by version and project setup. Write
|
||
best-effort values and add a `# TODO` comment so the user knows to confirm them.
|
||
Substitute the engine's install folder for `<UE root>` (the value you record as
|
||
`engine.path`) — none of the editor binaries is on `PATH`. The commands embed
|
||
double quotes, so they take single-quoted scalars. Write the block for the
|
||
machine the project is developed on (`uname -s`: `Linux`, `Darwin` = macOS,
|
||
anything else = Windows). The Windows block was run on UE 5.7; the Linux and
|
||
macOS lines come from Epic's documentation, recorded with their sources in
|
||
`docs/engine-reference/unreal/current-best-practices.md` ("Command Line").
|
||
|
||
Windows:
|
||
|
||
```yaml
|
||
commands:
|
||
# TODO: confirm these for your UE version and project — adjust via /settings
|
||
build: '"<UE root>/Engine/Binaries/DotNET/AutomationTool/AutomationTool.exe" BuildCookRun -project="$(pwd -W 2>/dev/null || pwd)/<project>.uproject" -platform=Win64 -build -cook'
|
||
test: '"<UE root>/Engine/Binaries/Win64/UnrealEditor-Cmd.exe" "$(pwd -W 2>/dev/null || pwd)/<project>.uproject" -ExecCmds="Automation RunTests <project>.; Quit" -unattended -nullrhi -stdout -FullStdOutLogOutput'
|
||
run: '"<UE root>/Engine/Binaries/Win64/UnrealEditor.exe" "$(pwd -W 2>/dev/null || pwd)/<project>.uproject" -game -windowed -ResX=1280 -ResY=720'
|
||
smoke: '"<UE root>/Engine/Binaries/Win64/UnrealEditor-Cmd.exe" "$(pwd -W 2>/dev/null || pwd)/<project>.uproject" -game -nullrhi -unattended -stdout -ExecCmds="Quit"'
|
||
```
|
||
|
||
Linux — the editor binary is `Engine/Binaries/Linux/UnrealEditor`, the build
|
||
scripts are shell scripts:
|
||
|
||
```yaml
|
||
commands:
|
||
# TODO: confirm these for your UE version and project — adjust via /settings
|
||
build: '"<UE root>/Engine/Build/BatchFiles/RunUAT.sh" BuildCookRun -project="$(pwd -W 2>/dev/null || pwd)/<project>.uproject" -platform=Linux -build -cook'
|
||
test: '"<UE root>/Engine/Binaries/Linux/UnrealEditor" "$(pwd -W 2>/dev/null || pwd)/<project>.uproject" -ExecCmds="Automation RunTests <project>.; Quit" -unattended -nullrhi -stdout -FullStdOutLogOutput'
|
||
run: '"<UE root>/Engine/Binaries/Linux/UnrealEditor" "$(pwd -W 2>/dev/null || pwd)/<project>.uproject" -game -windowed -ResX=1280 -ResY=720'
|
||
smoke: '"<UE root>/Engine/Binaries/Linux/UnrealEditor" "$(pwd -W 2>/dev/null || pwd)/<project>.uproject" -game -nullrhi -unattended -stdout -ExecCmds="Quit"'
|
||
```
|
||
|
||
macOS — only the build is sourced. Epic documents the `UnrealEditor.app`
|
||
bundle but no command-line editor inside it, so write `build` and leave the
|
||
other three as TODO lines rather than a guessed path; `/smoke-check` reports
|
||
NOT ASSESSED for tests until the user sets `commands.test`:
|
||
|
||
```yaml
|
||
commands:
|
||
# TODO: confirm this for your UE version and project — adjust via /settings
|
||
build: '"<UE root>/Engine/Build/BatchFiles/RunUAT.sh" BuildCookRun -project="$(pwd -W 2>/dev/null || pwd)/<project>.uproject" -platform=Mac -build -cook'
|
||
# TODO: test, run and smoke — no documented command-line editor on macOS.
|
||
# Set them via /settings to the editor command you run for this project.
|
||
```
|
||
|
||
On Windows, `build` calls AutomationTool directly: from Git Bash, `RunUAT.bat` fails
|
||
(`'C:\Program' is not recognized`) whenever the project path has a space.
|
||
AutomationTool.exe runs on the machine's .NET 8 runtime; `RunUAT.bat` brings
|
||
its own SDK, so with no space in the path and no .NET 8 installed, use it.
|
||
`-build` builds the editor target too (verified on 5.7).
|
||
|
||
**Three details are load-bearing.** The project path is absolute: UE 5.7
|
||
does not find a relative `<project>.uproject` and exits 1 (`Project file not
|
||
found`) before anything runs. `$(pwd -W 2>/dev/null || pwd)` gives the `C:/…`
|
||
form in Git Bash and the plain path elsewhere, run from the project root;
|
||
`$PWD` alone fails when `MSYS_NO_PATHCONV` is set. `smoke` needs `-game`: without it
|
||
`UnrealEditor-Cmd` boots the *editor*, where a bare `Quit` console command only
|
||
ends a play-in-editor session — the process stays resident and the command
|
||
never returns; with `-game` the same line boots headless and exits in seconds.
|
||
`test` needs the `; Quit` inside the `Automation` string: the automation
|
||
runner handles that trailing `Quit` itself and exits the editor once the tests
|
||
finish, and without it the run also never returns. Its filter runs every test
|
||
whose full name *contains* `<project>.` — a substring match, not a prefix — so
|
||
name tests `<project>.[System].[Scenario]` as `qa-tester` does. If the
|
||
project's name is also an engine area — `Audio`, `Core`, `Input`, `Test`,
|
||
`System`, `Engine`, `Editor`, `AI`, `Math` — `<project>.` also runs hundreds
|
||
to thousands of the engine's own tests; give the tests a distinct root (say
|
||
`<project>Game.`) and put the same root in the filter. This form exits 255 on
|
||
a failing test AND on a filter that matches nothing (`No automation tests
|
||
matched`, verified on 5.7), so `/smoke-check` reads the output, not the exit
|
||
code. `smoke` carries `-stdout` for the same reason: without it a failed boot
|
||
prints nothing. Do not swap in
|
||
`-TestExit="Automation Test Queue Empty"`: that form exits **0** on a failing
|
||
test unless its report JSON is parsed.
|
||
|
||
### 5.5.2 Write the blocks to `project.yaml`
|
||
|
||
Show the user the full set of four blocks and ask:
|
||
|
||
> "May I write the `engine`, `specialists`, `naming`, and `commands` blocks to `project.yaml`?"
|
||
|
||
Wait for confirmation, then apply based on the file's current state:
|
||
|
||
- **`project.yaml` does not exist** — create it with the Write tool using this
|
||
v1.1 template (the four blocks appended after `framework`; replace `<...>` and
|
||
the date):
|
||
|
||
```yaml
|
||
# CCGS project configuration — single source of truth for project settings.
|
||
# Schema: grep the `## <key>` section of .claude/docs/effects-map.md —
|
||
# it is ~31k tokens whole, ~900 per section. Do not open it entire.
|
||
|
||
schema_version: 1
|
||
|
||
framework:
|
||
version: 1.1.3
|
||
last_upgraded: <today's date>
|
||
|
||
engine:
|
||
name: "<...>"
|
||
version: "<...>"
|
||
language: "<...>"
|
||
rendering: "<...>"
|
||
physics: "<...>"
|
||
|
||
specialists:
|
||
code: "<...>"
|
||
shader: "<...>"
|
||
ui: "<...>"
|
||
additional: ["<...>"]
|
||
|
||
naming:
|
||
classes: <...>
|
||
variables: <...>
|
||
constants: <...>
|
||
signals: <...>
|
||
files: <...>
|
||
scenes: <...>
|
||
|
||
commands:
|
||
build: "<...>"
|
||
test: "<...>"
|
||
run: "<...>"
|
||
smoke: "<...>"
|
||
```
|
||
|
||
Do not seed `modes.review_mode` here. It is a rigor-fronted knob — `modes.rigor`
|
||
supplies its value, so an explicit value would shadow the rigor expansion and pin
|
||
the review mode regardless of the project's rigor.
|
||
|
||
- **`project.yaml` exists** — Read it first (the Edit tool requires the file to
|
||
have been read this session), then, processing the blocks in the order
|
||
`engine` → `specialists` → `naming` → `commands`, for each block:
|
||
- **Block absent** — insert the whole block, appended after the last existing
|
||
top-level block.
|
||
- **Block already present** (a re-run of `/setup-engine`) — Edit the existing
|
||
keys in place, and add any keys the block is missing (a partial block must
|
||
end up with its full key set). Do NOT add a second copy of the block.
|
||
|
||
**Unreal on macOS is the exception in both cases:** `commands.test`, `run` and
|
||
`smoke` are written as the TODO comment lines from the macOS block — never as
|
||
`<...>` placeholders or a guessed path — and a re-run leaves them unset until the
|
||
user sets them.
|
||
|
||
Preserve all other content in `project.yaml` (`schema_version`, `framework`,
|
||
`modes`, `project`, etc.) — only the four blocks are touched.
|
||
|
||
### 5.5.3 Verify the dual-write
|
||
|
||
Read `project.yaml` back and confirm the four blocks are present and that
|
||
`engine.name`, `engine.language`, and the naming values match what was written
|
||
to `technical-preferences.md`. If they diverge, report the discrepancy to the
|
||
user and stop — a split config corrupts skill and hook reads.
|
||
|
||
---
|