magicciv/.project/objectives/p3-31-replay-live-recording.md
Natalie b4c402e766 docs(@projects/@magic-civilization): p3-26 Gap 3 DONE (equipment/crafting verified headless) + Gap 4 scope assessment
Gap 3 — Equipment/crafting: verified the full craft→equip→combat path runs
headless and Rust-authoritative (orig bullet was stale at [ ]):
  - PlayerAction::CraftEquipment → craft_equipment dispatch (materials gate +
    consume strategic_ledger + equip), 2 tests
  - recipe_phase ("recipe_refine") in END_OF_TURN_PHASES — passive crafting
    economy refines raw→quality-tiered product every self-play turn, 1 test
  - equip_combat_bonus reads boot-loaded item_combat at every combat site, 2 tests
  - boot path: set_item_combat_json FFI ← headless harness _apply_item_combat
  - MCTS AI not electing to craft = deliberate 9-kind GPU-rollout constraint,
    not a missing system
  Verified green: mc-turn + mc-player-api 557/0.

Gap 4 — Per-building queues: recorded verified assessment. Bench single-slot +
per-turn AI reselection is functionally equivalent to a FIFO build queue for the
self-play SIMULATION outcome; the multi-item queue is a live-game UI affordance
belonging to the p3-25/p3-29 projection arc. Owner scope call pending: does p3-26
require simulating a multi-item queue, or reclassify Gap 4 out of the headless bar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 09:44:11 -04:00

4.1 KiB
Raw Permalink Blame History

id title priority status scope category owner created updated_at blocked_by follow_ups related
p3-31 Replay recording — live games archive a GameHistory (per-turn TurnSnapshot + events) on game-over p3 missing game1-stretch simulation shipwright 2026-06-27 2026-06-27
p3-32
p2-46
mc-replay-followup-unit-spawn-events

Context

The replay surface (p2-46) and the projected match-chronicle (standings ladder

  • event feed via GameHistory::standings_at, shipped 2026-06-26) both work — but only against synthetic fixtures. No real game is ever archived, so there is nothing real to replay.

Verified state (2026-06-27):

  • write_game is called only from GdReplayArchive::write_fixture (src/simulator/api-gdext/src/replay.rs). No production path archives a game.
  • TurnSnapshot is pushed only in write_fixture (replay.rs:637). The live game never records a per-turn snapshot — mc-replay/src/snapshot.rs still documents itself as "the schema, not the writer."
  • TurnEvents are emitted in mc-turn during headless turn resolution (p2-46 + mc-replay-followup-unit-spawn-events: CityFounded/CityCaptured/ UnitKilled/UnitCreated/WonderBuilt/TechResearched land in TurnResult), but the live Godot turn loop (turn_manager.gdAiTurnBridge) is a separate path; whether it populates a TurnEventCollector and where those events would be archived is unwired.

The goal: a finished game — including an OBSERVER cast or an AI_ARENA match — produces a GameHistory on disk so it can be re-watched and compared.

Source-of-truth rails

  • Rust (mc-replay + the recorder) owns: TurnSnapshot derivation per clan per turn (population / cities / army_strength / gold / tech_count / land_area / score — already computed by the score + economy crates), event collection into GameHistory.events, and write_game on game-over. No stats math or archive I/O in GDScript (Rail-1).
  • GDScript only: fires the game-over trigger, hands the recorder the per-turn hook, and reads the archive back via the existing GdReplayArchive / GdReplayPlayer bridges (Rail-3).
  • JSON: archive-root path stays in EnvConfig; retention policy in Settings (both already exist from p2-46). No new tunables hardcoded (Rail-2).

Acceptance bullets

  • A GdGameRecorder (or equivalent) holds a session GameHistory and, on a per-turn hook, appends one TurnSnapshot per clan with the live stat values. After an N-turn headless OBSERVER/AI_ARENA game, history.snapshots has N × clans rows. Projection (standings_at) is unchanged.
  • The live turn path collects the same TurnEvents the headless mc-turn path already emits (CityFounded/CityCaptured/UnitKilled/UnitCreated/WonderBuilt/ TechResearched/WarDeclared/…) into the session GameHistory.events. (First determine whether turn_manager.gd's path already drains a collector; wire it if not.)
  • write_game is invoked on game-over (victory and turn-limit), writing meta.json + history.bin under <archive_root>/<pack>/<game-id>/. GdReplayArchive.list() returns the just-finished game; GameState's last_archived_game_id is set for the end-game summary.
  • end_game_summary.gd "Watch Replay" and past_games.gd open the freshly-recorded game and the chronicle (standings ladder + event feed) renders the real match, not a fixture. Proof screenshot reviewed in conversation.
  • Rust round-trip test: build a multi-turn GameHistory via the recorder, write_gameread_game, assert snapshots + events survive and standings_at returns the recorded ladder. cargo test -p mc-replay green.
  • Determinism: same seed + same controller ids → byte-identical recorded snapshots + event-count vectors across two runs.

Out of scope

  • Visual map/unit replay rendering — tracked by p3-32 (this objective only makes real games recordable; that one makes them watchable on a map).
  • Cloud sync of archives; replay branching / mid-game forking.
  • Per-turn delta compression beyond bincode (the p2-46 10 MB cap assert still governs; optimise only if it fires).