Files
Claude-Code-Game-Studios/.claude/docs/run-and-observe.md
T
DonchitosandClaude Opus 5 d05699707f Release 1.1.0 — one config file, and a process weight you choose
`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
2026-09-23 12:25:11 +10:00

179 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
1. **Launch windowed** at a fixed resolution — `1280x720` unless the project
says otherwise — using `commands.run` from `project.yaml`. Never the
headless / batch / null-RHI flag: those exist to *skip* rendering.
2. **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 `--scene` flag).
3. **Wait, capture, quit.** Let the engine render real frames — 60 is a safe
floor — capture one, and exit. Each engine's mechanism is below.
4. **Look.** `Read` the 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.
5. **Retain.** Copy the image to
`production/qa/evidence/[story-slug]/[NN]-[what-it-shows].png` and reference
it from the evidence doc. A screenshot that was taken and discarded is an
assertion, not evidence.
6. **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.run` unset, 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 explicit
`testing.strict.visual`/`.ui: false` it 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.*