Skip to content

Commit d7cbe52

Browse files
dsarnoclaude
andauthored
Add screenshot-testing recipe doc + local game-capture diagnostic (#414)
* Add screenshot-testing doc + local game-capture diagnostic script docs/screenshot-testing.md captures the agent-facing recipe for editor_screenshot — when to pick viewport vs game vs cinematic, the project_run → poll game_capture_ready → screenshot pattern, the pre-flight checklist for the "autoload never registered its debugger capture within 20s" timeout, and a decision tree for diagnosing it. Also calls out preferring state assertions (node_get_properties, game print() forwarded over mcp:log_batch) over pixel screenshots when the test isn't truly visual. script/local-game-capture-diag is the developer-facing companion to ci-game-capture-smoke: works against whatever scene is currently open (no fixture scene required), dumps the autoload list and project.godot [autoload] section before touching the game, polls game_capture_ready deterministically, and on failure dumps recent plugin + game logs so the failure mode is visible (autoload missing vs game crashed during boot vs F5-launched-not-project_run vs wrong session active). * Address Copilot review on #414: correct tool surface refs, real PNG dims, --session flag - project_stop is not a top-level MCP tool — stop is project_manage(op="stop"). Updated both doc references (recipe step 6 + decision-tree mention) and the script's cleanup call + --keep-running help text. - input_send / call_method aren't registered tools. Genericized recipe step 4 to point at the actual interaction surface (node_set_property, batch_execute, ui_manage, theme_manage) without prescribing a specific tool. - Module docstring promised "PNG dimensions + byte size" but only printed byte length. Added _png_dimensions(): parses width/height from the IHDR chunk (zero-dep), printed alongside byte count on success. - Added --session <hint> flag for the multi-editor routing case the doc explicitly calls out as a timeout cause. Calls session_activate before any other tool, prints the resolved session, fails fast on no-match. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b602cd5 commit d7cbe52

2 files changed

Lines changed: 510 additions & 0 deletions

File tree

docs/screenshot-testing.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Screenshot-driven testing
2+
3+
Notes for AI agents (and humans) on getting `editor_screenshot` to work the
4+
first time, instead of looping through "autoload never registered its debugger
5+
capture within 20s" timeouts.
6+
7+
## Pick the right `source`
8+
9+
| Goal | `source` | Notes |
10+
|------|----------|-------|
11+
| Verify in-editor UI / inspector / dock layout | `viewport` | Captures the editor's 2D view directly — no debugger bridge, always works. |
12+
| Verify a 3D scene framing as the editor camera sees it | `viewport` | Same path, no game subprocess required. |
13+
| Verify a running game's framebuffer (menus that exist at runtime, gameplay state, particle effects, runtime UI animations) | `game` | Requires the game subprocess + `_mcp_game_helper` autoload + a `project_run`-driven play cycle. |
14+
| Verify a specific `Camera3D` view without playing | `cinematic` (where supported) | Doesn't require Play. |
15+
16+
**Default to `viewport` when in doubt.** `source="game"` is only the right
17+
answer when the thing you want to see only exists in the *running* game.
18+
19+
## Recipe: testing a runtime UI option (e.g. main menu)
20+
21+
```
22+
1. editor_state -> confirm session, current_scene, readiness="ready"
23+
2. project_run(mode="current", -> autosave=False so MCP scene mutations stay in memory
24+
autosave=False)
25+
3. poll editor_state every ~500ms -> wait until is_playing=true AND game_capture_ready=true
26+
4. (interact via available write tools as -> e.g. node_set_property, batch_execute,
27+
needed for the scenario being tested) ui_manage, theme_manage
28+
5. editor_screenshot(source="game", -> request the capture
29+
max_resolution=1280)
30+
6. project_manage(op="stop") -> always stop the run when done
31+
```
32+
33+
`game_capture_ready` is the deterministic readiness signal — it flips true
34+
*only* after the game-side autoload's `mcp:hello` beacon arrives, which means
35+
the debugger channel is wired up and `mcp:take_screenshot` will land. Do not
36+
sleep-and-pray; poll this field.
37+
38+
## Pre-flight checklist (when `source="game"` keeps timing out)
39+
40+
1. **Is `_mcp_game_helper` actually in the project's autoload list?**
41+
- Open `Project Settings → Autoload`, or grep `project.godot` for
42+
`_mcp_game_helper="*res://addons/godot_ai/runtime/game_helper.gd"`.
43+
- If missing: disable + re-enable the Godot AI plugin in Project Settings →
44+
Plugins. Re-enabling fires `_ensure_game_helper_autoload()` which writes
45+
the entry and persists it via `ProjectSettings.save()`.
46+
2. **Was the game launched via `project_run`?**
47+
- `editor_screenshot(source="game")` requires `_game_run_active=true` on
48+
the editor side, which is only set by `project_run`. F5-from-keyboard
49+
plays the game but `mcp:hello` from that play cycle is *explicitly
50+
ignored*, and you will time out.
51+
3. **Is the right session active?**
52+
- Multi-editor / multi-worktree setups: call `session_activate` (or pass
53+
`session_id` per call) so the screenshot routes to the editor whose game
54+
is actually running.
55+
4. **Did the game subprocess actually boot?**
56+
- Look at the Godot Output panel for
57+
`[godot_ai game_helper] registered mcp capture (debugger active=true, logger=true)`.
58+
If that line never prints, the autoload didn't run. If
59+
`debugger active=false`, you're in a headless / custom-main-loop /
60+
exported build where the debugger channel is off.
61+
5. **Did the game crash during boot?**
62+
- `logs_read` (or `logs_read source="game"`) surfaces any `print`/error
63+
output the game emitted before dying. A crashed game can never beacon.
64+
65+
## Decision tree for the timeout error
66+
67+
`Game-side autoload never registered its debugger capture within 20s`:
68+
69+
- `is_playing` was **false** when you called `editor_screenshot`?
70+
→ The game wasn't running. Call `project_run` first and poll readiness.
71+
- `is_playing=true` but `game_capture_ready` stayed **false**?
72+
→ Either the autoload isn't in `project.godot` (item 1 above), or the
73+
project was launched outside `project_run` (item 2), or the game's
74+
`_ready` errored before reaching the `mcp:hello` send (check `logs_read
75+
source="game"`).
76+
- Worked once, fails on second attempt within the same play cycle?
77+
→ Did you `project_manage(op="stop")` and forget to `project_run` again?
78+
Each new run rotates a token; the readiness flag is reset on
79+
`begin_game_run()`.
80+
81+
## Things to prefer over screenshots, when possible
82+
83+
Screenshots are the slowest, flakiest assertion surface — they require
84+
rendering, encoding, and a live debugger bridge, and any of those can fail
85+
intermittently. When you can, assert on **state** instead of pixels:
86+
87+
- `node_get_properties` to read the actual visible/visibility/text/etc. of a
88+
Control after the menu opens.
89+
- `print()` from the game's `_pressed()` handler — game prints are forwarded
90+
back over `mcp:log_batch` and surface in `logs_read source="game"`. The AI
91+
can grep for `"menu_opened"` instead of trying to OCR a screenshot.
92+
- `node_find` with a query like "find a Control named MainMenu that's
93+
visible" — gives a yes/no without ever rendering.
94+
95+
Reach for `source="game"` screenshots when the assertion is genuinely
96+
visual (layout, colors, particle bursts, animation poses) and skip them
97+
when state inspection would do.
98+
99+
## Reproducing the timeout deterministically
100+
101+
`script/local-game-capture-diag` (developer-facing, runs against your local
102+
editor) walks through the full bridge end-to-end against the currently-open
103+
scene and prints diagnostics on failure. Use it when you can't tell whether
104+
the bug is in your project, in the plugin, or in the AI's calling pattern.
105+
106+
`script/ci-game-capture-smoke` is the CI equivalent — it requires the
107+
fixture scene `test_project/capture_smoke.tscn` and asserts pixel colors at
108+
known coordinates.

0 commit comments

Comments
 (0)