diff --git a/.project/history/20260510_p2-67-phase5-wire-transcript.md b/.project/history/20260510_p2-67-phase5-wire-transcript.md new file mode 100644 index 00000000..02168395 --- /dev/null +++ b/.project/history/20260510_p2-67-phase5-wire-transcript.md @@ -0,0 +1,80 @@ +# p2-67 Phase 5 — End-to-end wire transcript + +**Date:** 2026-05-10 +**Status:** Phase 5 acceptance evidence — JSON-Lines pump verified end-to-end on apricot. + +## Reproducer + +``` +ssh apricot 'cd ~/Code/project-buildspace/magic-civilization && \ + printf "{\"type\":\"view\",\"id\":1}\n{\"type\":\"act\",\"id\":2,\"action\":{\"type\":\"end_turn\"}}\n{\"type\":\"shutdown\",\"id\":3}\n" \ + | flatpak run --user \ + --env=CP_SEED=42 --env=CP_PLAYERS=2 --env=CP_CLAUDE_SLOT=0 \ + org.godotengine.Godot \ + --path src/game --headless --rendering-method gl_compatibility \ + res://engine/scenes/headless/claude_player_main.tscn' +``` + +## Transcript + +### `->` (notifications) +``` +{"player":0,"turn":0,"type":"turn_started"} +{"phase":"player_actions","type":"phase_changed"} +``` + +### `->` response to `view` (id=1) +Full `PlayerView` for player slot 0 — empty cities/units (default +`GameState` until map+unit hydration lands in Phase 3 follow-up), +`legal_actions: [{end_turn, enabled:true}]`, fully-populated nested +view structures (`resources`, `research`, `culture`, `civics`, +`pending_events`, `score`). Confirms `GdPlayerApi::view_json` → +`project_view` round-trips through serde + Godot dictionary +conversion successfully. + +### `->` response to `act` (id=2, EndTurn) +```json +{ + "events": [ + {"player": 0, "turn": 0, "type": "turn_ended"}, + {"phase": "end_turn", "type": "phase_changed"}, + {"player": 0, "turn": 1, "type": "turn_started"} + ], + "id": 2, + "ok": true, + "view": { /* turn: 1, ... */ } +} +``` +Confirms `dispatch::apply_action(EndTurn)` correctly emits the +three-event tuple and the post-action `view` reflects the +incremented `turn` field. + +### `->` response to `shutdown` (id=3) +```json +{"id": 3, "ok": true} +``` + +## What this verifies + +- ✓ `mc-player-api` → `api-gdext` → GDScript harness → stdout pipe + end-to-end JSON-Lines round-trip +- ✓ `GdPlayerApi::view_json(player)` returns a deserialisable + `PlayerView` +- ✓ `GdPlayerApi::apply_action_json(player, action)` dispatches + through `mc_turn::action_handlers` and emits canonical `Event` + variants +- ✓ Request `id` correlation (1, 2, 3) preserved across responses +- ✓ Notifications fire without `id` and are distinguishable from + responses +- ✓ Shutdown is clean (process exits with no protocol error) + +## What's still TRACKED (out-of-scope for Phase 5 close) + +- Map / unit hydration: harness initialises GameState (autoload + state) but the held `GdPlayerApi::state` stays at default until + `GdGameState::serialize_to_json` is wired to `load_state_json`. +- Real Claude vs AI E2E: the Anthropic Agent SDK adapter is shipped + (`tooling/claude-player/`) but requires `ANTHROPIC_API_KEY` and is + not run in this CI-equivalent transcript. Run via `npm run dev` + from `tooling/claude-player/` with the key exported. +- AI driving of other slots in the harness: Phase 3 follow-up. diff --git a/.project/objectives/p2-67-claude-player-api.md b/.project/objectives/p2-67-claude-player-api.md index d34fdf9e..4ca51044 100644 --- a/.project/objectives/p2-67-claude-player-api.md +++ b/.project/objectives/p2-67-claude-player-api.md @@ -2,7 +2,7 @@ id: p2-67 title: "Claude-driven player API — programmatic player + Agent-SDK adapter" priority: p2 -status: stub +status: partial scope: game1 category: tooling owner: simulator-infra @@ -218,6 +218,71 @@ This unlocks: Phase 0–5 = **3–4 days** focused work. Phase 1 (`mc-player-api`) is the bulk; Phases 2–5 are small once the core surface exists. +## 2026-05-10 — Phases 0-5 v1 shipped + +All six phases landed in this session. Status moves to `partial` +(not `done`) because several acceptance bullets are wire-stable but +have TRACKED follow-up subsystem wiring listed under each phase. + +### Phase 0 — Design doc ✓ +- `src/game/engine/docs/CLAUDE_PLAYER_API.md` — wire spec, action + taxonomy, view shape, error codes, env contract, adapter loop + pattern, UI button → action audit per scene. + +### Phase 1 — `mc-player-api` crate ✓ +- 5 modules (action, dispatch, error, projection, view, wire). +- 39/39 tests green: `cargo test -p mc-player-api`. +- Wire types complete; dispatcher routes EndTurn + Attack-hex-resolve + + 11 unit-verb variants through `mc_turn::action_handlers::invoke`; + other variants return typed `NotYetImplemented` with TRACKED + breadcrumbs. +- Projection wires gold / science / tech / culture / cities / units + / diplomacy / score with strict fog redaction + (own player only by default, omniscient via flag). + +### Phase 2 — GDExtension surface ✓ +- `api-gdext::player_api::GdPlayerApi` — `view_json(player)`, + `apply_action_json(player, action_json)`, `load_state_json`, + `dump_state_json`, `set_omniscient`. +- `cargo check -p magic-civ-physics-gdext` clean. +- gdext binary rebuilt + copied into engine/addons. + +### Phase 3 — Headless harness ✓ +- `src/game/engine/scenes/headless/claude_player_main.{gd,tscn}` — + stdin/stdout JSON-Lines pump. +- `scripts/claude-player-server.sh` — flatpak launcher. +- Env-driven: CP_SEED, CP_PLAYERS, CP_CLAUDE_SLOT, CP_MAP_SIZE, + CP_MAP_TYPE, CP_OMNISCIENT, CP_TIMEOUT_SEC, CP_LOG_FILE. + +### Phase 4 — Claude SDK TypeScript adapter ✓ +- `tooling/claude-player/` package — strict TS, Node 20+. +- `HarnessClient` — child-process spawn + JSON-Lines correlation by + monotonic id, timeouts, notification dispatch. +- `runAgent` — Anthropic Messages API tool-use loop with three + tools (view / act / end_turn), system prompt anchored in the + game's priorities. +- Append-only logs: `.local/runs//{log.jsonl,wire.jsonl,result.json}`. + +### Phase 5 — E2E demo ✓ (wire transcript) +- `.project/history/20260510_p2-67-phase5-wire-transcript.md` — full + request/response trace for view → act(end_turn) → shutdown verified + end-to-end on apricot. Real `PlayerView` JSON returned; EndTurn + emitted canonical `TurnEnded/PhaseChanged/TurnStarted` event triple; + shutdown clean. +- **What's still TRACKED for Phase 5 to flip to `done`**: + - Map + unit hydration of `GdPlayerApi::state` (wired once + `GdGameState::serialize_to_json` exists). Harness initialises + autoload `GameState` already; the API's held state stays default + until that bridge lands. + - Live Claude vs AI run with screenshots — requires + `ANTHROPIC_API_KEY` and a fresh hydrated `GameState`. The adapter + + harness pipe is proven; the run is a single `npm run dev` + invocation away once the state bridge is hot. + - Subsystem dispatch follow-ups for the variants currently returning + `NotYetImplemented`: Move (needs `pending_move_requests` queue in + mc-turn), city ops (mc-city dispatch), diplomacy verbs (mc-trade + dispatch), tech / culture / civic selection. + ## References - `src/simulator/crates/mc-core/src/action.rs` — unit action enum