Chessistics/tools/automation/README.md

97 lines
3.6 KiB
Markdown
Raw Permalink Normal View History

# 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.