| .. | ||
| harness.py | ||
| README.md | ||
| run_game.py | ||
| smoke.py | ||
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 anAutomationHarnessnode 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.jsonis written on startup.
Without the flag, the harness is not instantiated — zero runtime overhead and zero behavior change for normal play.
Quick start
# 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 withHarness(godot_exe=...)or edit the default inharness.py). - A compiled build: run
dotnet build Chessistics.csprojfirst.
Python API
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) -> Pathstate() -> dict— full snapshot +animatingflagselect(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 intervalload_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
--patharg is wrong. Rundotnet build Chessistics.csprojand check stderr from the harness. - Screenshot is all black —
--headlesswas passed; the harness needs a real rendering context. Don't use--headlesswith 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.