InputMapper tracks a mouse-down over a placed piece and promotes it to
drag mode once the cursor travels past an 8px threshold. Legal drop
cells (those where the piece's start→end vector still fits a legal
placement) are highlighted in green. Releasing on a legal cell emits a
RelocateRequested signal; Main feeds it to MovePieceCommand, which is
already undoable via the existing history stack.
Escape or releasing on an invalid cell cancels. The harness gains a
relocate() helper so UI tests can script drag-and-drop moves without
synthesizing motion events.
GameSim snapshots the state before each undoable command
(PlacePiece / RemovePiece / MovePiece) into a bounded LinkedList stack
(max 32). Undo() pops the last checkpoint and emits StateRestoredEvent,
reusing the presentation rebuild path already wired for QuickLoad.
Ctrl+Z in Main triggers the engine method; the harness exposes undo()
for tests. QuickLoad clears the stack (fresh timeline). Seven unit tests
cover empty stack, place/remove/move undo, reverse-order multiple undos,
rejected commands not checkpointing, and post-simulation rewind.
BoardState.CaptureSave/RestoreFromSave deep-copy every mutable field
(grid, pieces, demands, transformers, buffers, stock, campaign progress)
into a WorldSave slot. GameSim.QuickSave/QuickLoad expose slotted saves
and emit StateSavedEvent / StateRestoredEvent — the latter carries a
fresh BoardSnapshot so the presentation can rebuild board, pieces,
trajectories, objectives, stock, camera, and control bar in one pass.
F5/F9 trigger it in Main; harness gains quick_save/quick_load commands so
UI tests can checkpoint a scenario and resume without replaying from
scratch. Seven xUnit tests cover the roundtrip (including independence
from post-save mutations, campaign state, and multi-slot isolation).
Claude Code running inside the project's dev container can now build the
game, launch a real Godot instance under Xvfb, and drive the automation
harness end-to-end — no Windows dependency.
Dockerfile adds (as root, before USER node):
- X11 / Mesa software GL / audio runtime deps + python3
- .NET SDK 9.0 via upstream dot.net install script -> /usr/local/dotnet
- Godot 4.6.2-stable mono Linux x86_64 -> /opt/godot/godot
- /usr/local/bin/godot-xvfb wrapper: auto-wraps invocations in
xvfb-run -a --server-args="-screen 0 1280x720x24 ..."
harness.py picks GODOT_BIN from env, defaults to /opt/godot/godot on
Linux, and auto-wraps the subprocess in xvfb-run when DISPLAY is unset.
Windows code path unchanged.
init-firewall.sh adds api.nuget.org to the allowlist so dotnet restore
works post-boot. Godot + .NET SDK are fetched at image build time, before
the firewall exists.
New docs:
- autonomous_plan.md: design rationale, alternatives considered
- README.md: launch instructions for Windows terminal / Docker Desktop /
VS Code Dev Containers / WSL2 natif
- CLAUDE.md already documents the harness (done in previous commit)
Validation: docker build succeeds; inside the container, dotnet --version
=9.0.313, godot --version=4.6.2.stable.mono, dotnet test=102/102,
python3 tools/automation/smoke.py passes end-to-end with 14 non-black
1280x720 PNGs. Mission 1 screenshot is visually identical to the Windows
build, and Xvfb determinism is a bonus (det_a.png ≡ det_b.png bytewise).
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.