`project.yaml` at the repo root is now the single source of truth for a project's configuration, replacing `production/stage.txt`, `production/review-mode.txt` and `.claude/docs/technical-preferences.md` (all three still read as a fallback; nothing is force-deleted). **`modes.rigor` — one question instead of six.** `minimal`, `standard` or `full` fronts six knobs: `modes.workflow`, `docs.density`, `qa.level`, `modes.story_granularity`, `modes.review_mode` and `team.size`. It defaults to `minimal`, which is a change from 1.0's behaviour: the heavier tier was measured costing several times more to reach working code without producing a better result, and returning nothing when a fresh developer inherited the project. Pin `modes.rigor: standard` to keep 1.0's process level. Also in this release: - `/settings` to view or change any setting, and `project.local.yaml` for per-developer preferences that should not be committed. - `system_overrides` to hold one system to a higher standard than the rest of the project, and `testing.strict` to set per test type whether missing evidence blocks a story or warns. - A story that changes anything the player sees is not closed until the game has been launched, observed, and a screenshot retained in `production/qa/evidence/`. A parse check is not a run. This holds at every rigor level, including `minimal`, where automated tests are waived but the look is not. - The code root is engine-specific and enforced as such: Godot `src/`, Unity `Assets/`, Unreal `Source/<Module>/`. Two of the three will not build a project laid out any other way. - Migration tooling for existing projects: `.claude/scripts/migrate-v1-config.sh` converts a 1.0 layout, non-destructively by default, with a report to review before `--finalize` deletes anything. - Ask-before-write holds regardless of personal automation settings. Upgrading from 1.0 or earlier: see UPGRADING.md. Full list of changes: CHANGELOG.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016xvzG3i7pWQg4LjyDwWMXd
9.0 KiB
Run and Observe
Shared procedure for any skill that closes a story which changes something a
player can see. Referenced from the point of use in /dev-story (Phase 6) and
/story-done (Phase 3). Those skills keep the one load-bearing imperative
inline — a parse check is not a run, and a story nobody looked at does not
close — and cite this file for how to look.
.claude/docs/coding-standards.md is the authority for what evidence a story
needs. This file is the procedure for producing the visual half of it.
The rule
Every story that changes something player-observable is launched and observed
before it closes, and the observation is a retained screenshot in
production/qa/evidence/. Compiling proves the code loads. Tests prove the
logic. Neither proves the text fits in the box.
The agent can see images: Claude Code's Read tool renders a PNG. The whole
problem is getting a picture out of the engine unattended, and every supported
engine has a native way.
The procedure
- Launch windowed at a fixed resolution —
1280x720unless the project says otherwise — usingcommands.runfromproject.yaml. Never the headless / batch / null-RHI flag: those exist to skip rendering. - Launch straight into the scene or map the story touched. A feature that
is only reachable through the main menu and three clicks is a feature the
run will not reach. Stories should be written so their surface is reachable
from a launch argument (a scene path, a map URL, a
--sceneflag). - Wait, capture, quit. Let the engine render real frames — 60 is a safe floor — capture one, and exit. Each engine's mechanism is below.
- Look.
Readthe PNG. Compare what is on screen to the acceptance criteria. Clipped text, an overflowing panel, a missing element, the wrong colour — these are defects, and this is the only step that finds them. - Retain. Copy the image to
production/qa/evidence/[story-slug]/[NN]-[what-it-shows].pngand reference it from the evidence doc. A screenshot that was taken and discarded is an assertion, not evidence. - Report one
Run result:line in the implementation summary — vocabulary below.
Per engine
engine.name |
Launch | Capture | Output lands in | Never use | Scaffold needed |
|---|---|---|---|---|---|
| Godot 4 | godot --path . --windowed --resolution 1280x720 <scene>.tscn |
--write-movie <path>.png --quit-after <N> |
beside the given path, <name>00000000.png … |
--headless |
none |
| Unity 6 | built player .exe -screen-width 1280 -screen-height 720 -screen-fullscreen 0 --scene <Name> |
ScreenCapture.CaptureScreenshot(<abs path>) from a bootstrap script |
the absolute path you pass | -batchmode, -nographics |
one MonoBehaviour |
| Unreal 5 | MSYS_NO_PATHCONV=1 UnrealEditor.exe <Game>.uproject <MapURL> -game -windowed -ResX=1280 -ResY=720 |
HighResShot filename=<path> console command |
Saved/Screenshots/Windows/ |
-nullrhi, -unattended alone |
one actor or -ExecCmds |
Godot 4 — zero scaffold
godot --path . --windowed --resolution 1280x720 --write-movie shots/shop.png --quit-after 60 res://scenes/shop.tscn
--write-movie is Movie Maker mode: with a .png target it writes a frame
sequence and --quit-after sets the frame count. Filenames carry 8
zero-padded digits — shop00000000.png through shop00000059.png — so take the
last. --fixed-fps is applied automatically. The scene path is positional; any
.tscn can be launched directly, which is how step 2 is satisfied.
Capture-on-trigger alternative, for a state a scene launch cannot reach: an
autoload that on the wanted condition does
await RenderingServer.frame_post_draw, then
get_viewport().get_texture().get_image().save_png(path), then
get_tree().quit().
Unity 6 — one MonoBehaviour
-batchmode displays nothing, so the run is either the built player or the
editor in play mode. The player is the robust path:
Builds/Win64/Game.exe -screen-width 1280 -screen-height 720 -screen-fullscreen 0 --scene Shop --screenshot C:/abs/path/shop.png --after 60
-screen-* are Unity's own player arguments. --scene, --screenshot and
--after are yours: a ScreenshotOnArg MonoBehaviour in the bootstrap
scene parses System.Environment.GetCommandLineArgs(), loads --scene, waits
--after frames, calls ScreenCapture.CaptureScreenshot(path), waits one more
frame, then Application.Quit(). Pass an absolute path — the relative base
differs between editor and player. CaptureScreenshot captures the last frame
presented, so it must run after at least one rendered frame.
/test-setup scaffolds this script under the code root. Building the player
first costs 1–3 minutes on a small project; that is the price of looking at
the thing you ship rather than the editor.
Unreal 5 — one actor, or -ExecCmds
MSYS_NO_PATHCONV=1 UnrealEditor.exe Game.uproject /Game/Maps/Shop -game -windowed -ResX=1280 -ResY=720
-game runs standalone without the editor UI. The map is a positional URL
immediately after the .uproject — not a console command. The
MSYS_NO_PATHCONV=1 prefix is required on Windows: Claude Code runs commands
in Git Bash, which rewrites any argument beginning with / as a Windows path, so
/Game/Maps/Shop reaches the engine as C:/Program Files/Git/Game/Maps/Shop
and the game stops on a "map not found" dialog nobody can see. Alternatively
pass the map through -ExecCmds="open /Game/Maps/Shop", which is not converted. Capture is the
HighResShot console command, fired either from a dev actor (Execute Console Command on BeginPlay after a short delay, then quit) or from the launch line:
-ExecCmds="HighResShot filename=C:/abs/path/shop.png,Quit"
-ExecCmds is comma-separated. Output defaults to Saved/Screenshots/Windows/.
Do not Quit on the same frame as the request — the file flushes late and
an immediate quit can leave nothing on disk; a one-second delay before quit
is enough. The Blueprint node Take High Res Screenshot is editor-only and
does not work in a -game launch.
Any engine — OS capture fallback
On Windows, PowerShell with System.Drawing can CopyFromScreen into a bitmap
and save it. It needs a visible desktop and the game window on top, and it
captures whatever is there — it cannot be told "the shop panel". Use it when the
engine-native path is unavailable, and say so in the run result. Linux CI with
no display needs xvfb-run in front of the launch; --headless is not a
substitute.
The Run result: line
Exactly one of three shapes, in the implementation summary:
Run result: OBSERVED — <what was on screen, one line>with the retained path. "Shop panel renders, all 6 slots inside the frame, prices legible at 720p —production/qa/evidence/shop-panel/01-shop-open.png."Run result: NOT VERIFIED — <reason>. Engine binary not found,commands.rununset, launch crashed, screenshot empty. This is a blocker at the default gate level for Visual/Feel and UI stories, not a note — a story nobody looked at does not close. Under an explicittesting.strict.visual/.ui: falseit is recorded and the story may close with the gap in## Completion Notes.Run result: N/A — <reason>, only for a story with genuinely nothing observable — a pure data migration, a save-format change with no surface. Say why. "It's a Logic story" is not a reason: a damage formula has a number on screen somewhere.
The run is not waived at qa.level: minimal. Tests are waived there; the
look is not. It is the cheapest verification in the pipeline and the one
whose absence produces a well-tested game that looks wrong.
What a still can and cannot show
Can: layout, clipping, overflow, presence, alignment, colour, legibility, that the right screen appeared at all. This is where shipped visual defects actually live.
Cannot: timing, responsiveness, weight, animation curves, audio sync — the
"feel" half of Visual/Feel. Godot's --write-movie gives a frame sequence; the
other engines need OS video capture. Feel stays a human check in /team-qa.
Say which half the screenshot covered.
Reaching the state
A screenshot of the title screen verifies nothing about depth 2. Either launch
the scene/map directly (Godot scene arg, Unreal map URL, Unity --scene) or
give the bootstrap a debug argument that sets state (--gold=500 --depth=2).
Write stories so their surface is reachable one of those ways; a story that is
not is a story that will be marked NOT VERIFIED every time.
Finding the engine
None of the three engines installs onto PATH on Windows. engine.path in
project.yaml is where the editor lives; resolve it before concluding "no
engine on this machine". Absence of a path is not absence of an engine.
Commands and APIs above verified against the official Godot 4.7, Unity 6.3 LTS
and Unreal 5.8 references on 2026-09-14. docs/engine-reference/<engine>/ is
the project's pinned authority; check it before trusting a version-qualified
claim here.