Launching Godot with --automation=<dir> activates an AutomationHarness node that polls <dir>/inbox/ for JSON command files, executes them via a thin facade over existing public surfaces (GameSim, InputMapper, EventAnimator, ControlBar, PieceStockPanel), and writes results plus screenshots back to disk. The black-box simulation boundary is not crossed — every command routes through the same signals/methods a real player would trigger. A stdlib-only Python helper (tools/automation/harness.py) wraps the protocol for test scripts and interactive REPLs. Smoke test passes end-to-end: load mission, place a piece, step 10 turns, capture 14 1280x720 PNGs, handle rejections, quit cleanly. Existing 102 engine unit tests still green.
96 lines
3.6 KiB
Markdown
96 lines
3.6 KiB
Markdown
# Chessistics automation harness
|
|
|
|
Drive a running Chessistics build via file-based IPC, so an AI agent (or a
|
|
scripted test) can take screenshots, inject inputs, and read game state
|
|
without a human in the loop.
|
|
|
|
## How it works
|
|
|
|
- Launch Godot with `--automation=<dir>`. The game activates an
|
|
`AutomationHarness` node that polls `<dir>/inbox/` each frame.
|
|
- Send commands as JSON files; the game writes results to `<dir>/outbox/`
|
|
and screenshots to `<dir>/screens/`.
|
|
- A handshake file `<dir>/ready.json` is written on startup.
|
|
|
|
Without the flag, the harness is not instantiated — zero runtime overhead
|
|
and zero behavior change for normal play.
|
|
|
|
## Quick start
|
|
|
|
```bash
|
|
# From repo root
|
|
python tools/automation/smoke.py # end-to-end smoke test
|
|
python tools/automation/run_game.py # interactive REPL
|
|
```
|
|
|
|
Requirements:
|
|
- Python 3.10+ (stdlib only)
|
|
- Godot 4.6 mono build at `C:\Apps\godot\Godot_v4.6.2-stable_mono_win64_console.exe`
|
|
(override with `Harness(godot_exe=...)` or edit the default in `harness.py`).
|
|
- A compiled build: run `dotnet build Chessistics.csproj` first.
|
|
|
|
## Python API
|
|
|
|
```python
|
|
from tools.automation.harness import Harness
|
|
|
|
with Harness.launch() as h:
|
|
h.load_mission("campaign_01", 0)
|
|
state = h.state() # full snapshot as dict
|
|
h.screenshot("before") # → .automation_runs/<ts>/screens/before.png
|
|
h.place("Rook", (0, 0), (0, 3)) # place a piece
|
|
h.step() # one simulation tick (auto-waits for animation)
|
|
h.screenshot("after")
|
|
h.set_speed(0.1); h.play() # auto-run fast
|
|
```
|
|
|
|
Methods on `Harness`:
|
|
- `screenshot(name) -> Path`
|
|
- `state() -> dict` — full snapshot + `animating` flag
|
|
- `select(kind)` — e.g. `"Rook"`, `"Knight"`
|
|
- `place(kind, start, end, level=1)` — returns `{placed, pieceId, reason}`
|
|
- `click_cell(col, row, button="left"|"right")`
|
|
- `key(name)` — `"Space"` (play/pause), `"Escape"` (cancel)
|
|
- `play()` / `pause()` / `step()` / `wait_idle()`
|
|
- `set_speed(interval_seconds)` — auto-step interval
|
|
- `load_mission(campaign, missionIndex=0)`
|
|
- `back_to_menu()` / `quit()`
|
|
|
|
Every non-query command auto-waits for `EventAnimator.IsAnimating == false`
|
|
before returning, so consecutive calls see a fully-settled state.
|
|
|
|
## JSON protocol
|
|
|
|
Inbox: `{ "id": "<uuid>", "cmd": "...", "args": {...} }`
|
|
Outbox: `{ "id": "<uuid>", "ok": true, "result": {...} }` or
|
|
`{ "id": "<uuid>", "ok": false, "error": "..." }`
|
|
|
|
See the `cmd` table in `Scripts/Automation/CommandDispatcher.cs` for the
|
|
complete list.
|
|
|
|
## Output locations
|
|
|
|
- `.automation_runs/<name>/ready.json` — handshake
|
|
- `.automation_runs/<name>/inbox/`, `outbox/` — command queue
|
|
- `.automation_runs/<name>/screens/*.png` — 1280x720 PNGs
|
|
|
|
`.automation_runs/` is a good candidate for `.gitignore` (not added here
|
|
automatically — add it manually if needed).
|
|
|
|
## Troubleshooting
|
|
|
|
- **Godot exits before ready.json** — build probably didn't compile, or the
|
|
`--path` arg is wrong. Run `dotnet build Chessistics.csproj` and check
|
|
stderr from the harness.
|
|
- **Screenshot is all black** — `--headless` was passed; the harness needs
|
|
a real rendering context. Don't use `--headless` with automation.
|
|
- **Command times out** — an animation may be stuck; check the Godot
|
|
console log for errors.
|
|
|
|
## Architecture notes
|
|
|
|
The harness sits behind a thin facade (`AutomationFacade`) that the
|
|
dispatcher uses. It never reaches into engine internals — only calls
|
|
existing public surfaces on `GameSim`, `InputMapper`, `EventAnimator`,
|
|
`ControlBar`, `PieceStockPanel`. The black-box simulation separation
|
|
stays intact.
|