97 lines
3.6 KiB
Markdown
97 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.
|