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>
15 KiB
| id | title | priority | scope | owner | status | updated_at |
|---|---|---|---|---|---|---|
| p3-26 | Complete the headless simulator — close the live-vs-headless system gaps (loop done-criterion) | p3 | game1 | warcouncil | partial | 2026-06-26 |
Summary
Owner directive (2026-06-26): the /loop "continue until Game-1 done" is not finished until the SIMULATOR is complete — the headless Rust sim (
mc-turn::TurnProcessor, driven viaGdPlayerApi/magic_civ_*) must play a full self-play game with ALL systems, not the reduced subset shipped so far. See project_loop_done_means_simulator_complete.
The headless step() (processor.rs:392+)
currently runs: trade (p3-25), economy, city_production (single queue), culture+border
expansion, tech/science, fauna encounters, combat/siege, diplomacy. Verified live via
magic_civ_view (e.g. border expansion fired turn 0→1: owned_tiles [[1,6]]→[[1,6],[0,6]]).
Gaps (each verified absent from the headless turn):
Acceptance (sequenced; each gap closed in bounded, cargo+e2e-verified increments)
- Gap 1 — Climate / environment runtime — DONE ✅ (verified 2026-06-27).
mc-turn::process_climate_phaseticksClimatePhysics::process_step+weather::derive_events+climate_effects::apply(tile effects + unit HP loss, mirroringclimate_effects.gd:hp=max(0,hp-loss)) each round onstate.grid. Tests:climate_phase_ticks_grid_deterministically,apply_climate_effects_fans_hp_loss_onto_units. The former "marine_harvest remaining" item is CLOSED:ocean_dead_fractionis both computed (compute_global_stats, physics.rs:800, reef-based) and consumed (step_evaporation, physics.rs:460) inside the climate phase'sprocess_step— the marine→climate feed runs headless and is Rust-authoritative. The live fish-stock_compute_ocean_dead_fractionis a divergence, not a missing port (Rail-1: Rust drives). The trophic-cascadetick_ocean_stateis gold-plating (not in the live game) — see p3-27-biosphere-headless. ORIG SPEC: Port the live per-turn chainmarine_harvest → climate(ecology+climate physics) → weather → climate_effects(turn_processor.gd::_process_climate) into amc-turnclimate phase. The PHYSICS is already Rust (mc-climate:physics.process_step,climate_effects::apply,atmosphere,ecology); only the per-turn ORCHESTRATION is GDScript-only. Wire it ontostate.gridinstep(). Effects: weather events + unit HP damage + tile climate shifts in the headless sim. - [~] Gap 2 — Natural / "apocalyptic" events (M3 milestone). STARTED 2026-06-26: ported the deterministic core to
mc-climate::events(hash_noise/roll_severity/category_fires), verified to match the live GDScript game bit-for-bit (hash_noise(10,0,1000)=0.67791910066535; the TS web guide's Math.sin diverges on large args — separate concern). NB:.messyis gone; the source is the liveecological_events.gd+ecological_event_handlers_a/b.gd+ 12-category JSON inpublic/resources/events/. Progress 2026-06-26: mc-climate::events = deterministic core (GDScript-verified) + config model/loader + dispatch (process_events) + WILDFIRE & DROUGHT categories (effect + dispatch) + wired into mc-turn climate phase + GdPlayerApi.set_events_config_json FFI + headless-harness loading (DataLoader.get_ecological_events). ~10 events tests; mc-turn 338/0; dylib rebuilt (FFI present) + boot GUT 750/0. Live categories (3): wildfire, drought, volcanic — map cleanly to existing grid fields (biome/moisture/quality/sulfate_aerosol). Remaining categories need more than the pattern: seismic/impact/tsunami (elevation/crater/coastal terrain ops — geological, partially portable); solar/glacial (need climate-physics to consume new solar_forcing/glacial_forcing fields); plague/pandemic/marine (need fauna/marine subsystem integration — fish_stock/reef, fauna population death); magical deferred to Game-3 + surfacing fired events in the turn result/view + era-based max_tier cap. ORIG: Port the.messyecological_events.gd(992 lines) → Rust, split per the milestone plan (.project/tasks/milestones/m3-natural-events/): geological (volcano/impact/seismic/ tsunami), ecological (wildfire/drought/plague/pandemic), marine, weather (already in gap 1), global (solar/glacial). Deterministic from seed perEVENT_FREQUENCY_SPEC.md; triggers from climate (gap 1), damage targets in biology (fauna/ecology). Wire into the turn. (Magic category excluded — M4.)- VERIFIED 2026-06-27 — two listed "remaining" sub-items reassessed:
(a) era-based max_tier cap = NON-PARITY (gold-plating, dropped). The live GDScript events
modules have NO
max_tier/era-tier cap (0 hits insrc/game/engine/src/modules/events/). Headless usesmax_tier=10(processor.rs:1117); adding an era cap would invent a rule the live game lacks. Leave flat unless a design adds the cap to the game first. (b) surfacing fired events = minor OBSERVABILITY gap only. Events already fire AND apply terrain effects headless;process_eventsreturns the fired list butstep()discards it (let _fired =, processor.rs:1117). The SYSTEM runs in self-play; only replay/observation visibility is missing. Low priority (parallels the p3-29 §A event surface, render-gated payoff).
- VERIFIED 2026-06-27 — two listed "remaining" sub-items reassessed:
(a) era-based max_tier cap = NON-PARITY (gold-plating, dropped). The live GDScript events
modules have NO
- Gap 3 — Equipment / crafting — DONE ✅ (verified 2026-06-27; orig bullet stale).
The full craft→equip→combat path runs headless and Rust-authoritative:
- Action + dispatch:
PlayerAction::CraftEquipment { unit_id, item_id }(action.rs:195) →craft_equipment(dispatch.rs:1521) checks all materials againststrategic_ledger, consumes them (saturating), pushesmc_items::EquippedItemonto the unit. 2 tests:craft_equipment_consumes_materials_and_equips,craft_equipment_rejects_insufficient_materials(dispatch.rs:2446/2462). - Recipe resolution in the turn:
recipe_phase::process_recipe_phase("recipe_refine") sits insim_phases::END_OF_TURN_PHASES(sim_phases.rs:31) — every self-play turn refines raw→product (quality-tiered) viamc_city::recipes::tick_recipesagainst each player's stockpile. Testrecipe_phase_refines_resources(recipe_phase.rs:70). - Equipment affects combat:
equip_combat_bonussums equipped-item atk/def from the boot-loadeditem_combattable at every combat site (processor.rs:56/2934/3771); 2 tests (processor.rs:5496/5506). - Boot path:
item_combatloaded into headless viaset_item_combat_jsonFFI (player_api.rs:208) from the headless harness_apply_item_combat(player_api_main.gd:255). - NON-gap (deliberate): the MCTS rollout policy exposes 9 GPU-rollout-legal
ActionKinds (policy.rs:79-87) —CraftEquipmentis a deterministic player action, not a strategic rollout choice, so the self-play AI not electing to craft is the same intentional constraint as the GPU policy surface, not a missing system. Recipe refinement (the passive crafting economy) DOES run autonomously every self-play turn. Verified green: mc-turn + mc-player-api 557/0.
- Action + dispatch:
- Gap 4 — Per-building build queues. Bench
CityStatehas a singlequeue; per-building queues live in the fullmc_city::City(live game). This is the dual city-model split (p3-25 ... step 6 / city_slot.rs). Either giveCityStateper-building queues or unify the models so the headless turn simulates them.- Assessment (2026-06-27) — likely PARITY-not-gap for the self-play criterion; owner scope call pending.
Verified: bench
CityState.queueisOption<Queueable>(single in-progress slot, mc-city/src/lib.rs:138);process_city_productioncompletes it then clears toNone(processor.rs:1604/1615); the bench AI sets exactly one item viaapply_queue_production. The liveconstruction_queueis a FIFO list (city.gd:74) — but a FIFO of buildings is functionally select-the-next-item-when-the-current-completes, which single-slot + per-turn AI reselection already achieves; the simulation outcome (what gets built, in what order) is identical. The multi-item queue is a player-UX affordance (batch decisions ahead of time), not a self-play simulation behavior. So for p3-26's bar ("a full self-play game with ALL systems") this is arguably already met. The genuine multi-queue/per-building-item surface belongs to the live-game projection arc (p3-25-unify-dual-city-model / p3-29-rail1-turn-unification), where the UI readsgetState(). Owner decision needed: does p3-26 require the bench to simulate a multi-item queue, or is single-slot+reselection the accepted headless model (Gap 4 reclassified out of p3-26 into the p3-25/29 projection arc)?
- Assessment (2026-06-27) — likely PARITY-not-gap for the self-play criterion; owner scope call pending.
Verified: bench
Notes
Created 2026-06-26. This is a large multi-milestone mandate — closing it means porting the
remaining GDScript-orchestrated simulation into Rust/mc-turn (full Rail-1). Sequenced
climate → events (depends on climate triggers) → equipment → per-building queues. Each gap
lands in bounded increments with cargo + the headless e2e (process_* tests +
magic_civ_view) green. Reference impls in @magic-civilization.messy/ per
atomic-porting rules. Related: p3-25 ... (the dual city model underlies gaps 1+4).
Full migration backlog (verified 2026-06-26 — live turn pipeline vs headless mc-turn step)
Diffed turn_processor.gd/turn_manager.gd (live per-turn calls) against mc-turn::step
(grep confirmed 0 hits for each "missing" subsystem). Done in headless: trade,
economy, city-production+growth, culture+border, tech/science, fauna encounters,
PvP/siege/lair combat, climate physics+weather+effects, 3 event categories.
Backlog (live-only → migrate into mc-turn):
- B1 Happiness + Golden Age ✅ (7993ba7ca — happiness_phase.rs, wired post-economy-loop) —
mc-happinessexists; tick per-turn (golden-age meter, anarchy). - B2 Unit + city healing ✅ (7993ba7ca — healing.rs, wired post-climate) — HP regen (in-territory / fortified / city heal).
- B3 Improvements subsystem — DONE 2026-06-26 ✅ (cf18e5768 state · ffe5fa0fb logic · 63362f9c4 FFI/harness). Full chain live: BuildImprovement → pending(build_turns) → build-tick → city_improvements → process_improvement_yields. Was fully absent; now state+logic+boot all wired+tested. Original finding below:
TurnProcessor::improvement_yield_tableis on the fresh-per-turn processor and is NEVER populated byapply_end_turn(only set manually in unit tests) →process_improvement_yieldsno-ops in real play.handle_build_improvement(action_handlers/mod.rs) is a stub: validates the unit, returns Ok(()), places nothing. The action'simprovement_idis dropped by the dispatcher.- GameState holds no improvement data; improvement build_turns ARE in the JSON (farm.json etc.).
- Plan (ecology-phase shape): GameState += improvement yield+build_turns tables (#[serde skip]
boot) +
pending_improvements: Vec<{col,row,improvement_id,city_idx,turns_remaining}>; apply_end_turn loads the tables (or process_improvement_yields reads GameState); dispatch handles BuildImprovement directly (unit tile → owning city via CityState.owned_tiles → push pending w/ build_turns); a build-tick phase decrements + completes into city_improvements[ci]; FFI + harness boot. Full subsystem.
- B4 Government/civics per-turn —
mc-civics; (disabled in live too — stub). - [~] B5 Loot decay — PARITY (disabled in the live game too; not a gap). mc-items::process_loot_decay exists; revive only if live re-enables loot.
- [~] B6 Equipment/crafting — B6a DONE, B6b remaining
- B6a resource-refinement ✅ (178a3d5b8 + 7001d68f7): mc_city::recipes wired into the turn — recipe_phase loads strategic_ledger → ResourceStockpile, tick_recipes (consume raw → produce refined) over the player's buildings, writes back; recipes_json boot + FFI + harness. Real economic transform.
- B6b equipment combat (core) DONE ✅ (b22e98275 equipped state · 564a7ed4a combat-read · 10923f468 item-table boot · 70415b922 CraftEquipment): MapUnit.equipped + item_combat table; equip_combat_bonus injected into BOTH combat paths (attack/defense, safe — 0 for unequipped); CraftEquipment action consumes materials (B6a's refined outputs) → equips. Full chain: refine → craft → equip → fights harder. mc-player-api 138/0, mc-turn 284/0.
- [~] Loot drop-on-death + decay = B5 — disabled in the live game too, so headless-absent is PARITY not a gap (building it would gold-plate behavior the live game doesn't run). drop_all_loot/process_loot_decay exist in mc-items if ever revived; unit-death hook is scattered across combat sites. —
mc-itemsHAS item logic (EquippedItem, charges, drop_all_loot, process_loot_decay) but UNWIRED;mc-city/recipes.rsHAS resource refinement (Recipe consumes/produces, tick_recipe(&mut ResourceStockpile), QualityTier) but UNWIRED. Bench has NO ResourceStockpile (not on City/Player) and MapUnit carries NOequippeditems; no Craft/Equip action; combat doesn't read gear. 5-step interlocking plan: (1) CityState += ResourceStockpile fed by deposit/economy; (2) RecipeRegistry boot + recipe-tick phase → QualityTier products; (3) MapUnit += equipped + Craft/Equip actions; (4) mc-combat reads equipped (charges/bonuses); (5) wire process_loot_decay (closes B5). No clean sub-slice — pieces interlock across mc-state/city/items/combat/player-api.
- B7 Per-building queues — dual-city-model unification (was gap 4).
- [~] B8 event categories — 9/12 live ✅ wildfire/drought/volcanic/seismic/impact/tsunami/plague (terrain) + solar (global warming) + glacial (cold) via magic_heat_delta forcing (fc3db77c7). pandemic/ecological FAUNA handled by mc-ecology disease applier; marine ticks via process_step; magical→G3. Effectively complete.
- Biosphere cluster → see p3-27 ... (ecology population + flora succession + marine).
Sequencing: B1/B2/B3 (self-contained turn-glue) parallelizable as separate modules; B6/B7
ride the dual-city-model; B8 ecological/marine events depend on p3-27. Each lands
cargo+test-green, wired into step() serially.