docs(@projects/@magic-civilization): 🗺️ p3-26 — roadmap to complete the headless simulator (loop done-criterion)

Owner directive: the /loop isn't finished until the SIMULATOR is complete — the headless
Rust sim must play full self-play games with ALL systems, not the reduced subset.

p3-26 enumerates the verified live-vs-headless gaps + sequenced plan:
- Gap 1: climate/environment runtime (port the marine→climate→weather→effects chain into
  mc-turn; physics already in mc-climate).
- Gap 2: natural/"apocalyptic" events (M3 milestone — port .messy ecological_events.gd,
  12 categories, deterministic per EVENT_FREQUENCY_SPEC).
- Gap 3: equipment/crafting (recipes exist; no headless Craft action).
- Gap 4: per-building build queues (dual city-model; bench has a single queue).

Corrects my earlier "apocalyptic events don't exist" — they're specced (m3-natural-events)
with a .messy reference impl, just unimplemented in Rust.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-26 08:25:01 -04:00
parent 4fe3bc6091
commit f9593c4d29
3 changed files with 76 additions and 8 deletions

View file

@ -17,8 +17,8 @@
| **P0** | 44 | 0 | 0 | 0 | 0 | 0 | 44 |
| **P1** | 88 | 0 | 0 | 0 | 0 | 1 | 89 |
| **P2** | 130 | 0 | 0 | 0 | 0 | 1 | 131 |
| **P3 (oos)** | 34 | 0 | 2 | 0 | 0 | 29 | 65 |
| **total** | **296** | **0** | **2** | **0** | **0** | **31** | **329** |
| **P3 (oos)** | 34 | 0 | 3 | 0 | 0 | 29 | 66 |
| **total** | **296** | **0** | **3** | **0** | **0** | **31** | **330** |
</td><td valign='top' style='padding-left:2em'>
@ -26,7 +26,7 @@
| Team Lead | Remaining |
|---|---|
| [warcouncil](../team-leads/warcouncil.md) | 2 |
| [warcouncil](../team-leads/warcouncil.md) | 3 |
</td></tr></table>

View file

@ -0,0 +1,58 @@
---
id: p3-26
title: Complete the headless simulator — close the live-vs-headless system gaps (loop done-criterion)
priority: p3
scope: game1
owner: warcouncil
status: partial
updated_at: 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 via `GdPlayerApi`/`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+](../../src/simulator/crates/mc-turn/src/processor.rs))
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.** Port the live per-turn chain
`marine_harvest → climate(ecology+climate physics) → weather → climate_effects`
(`turn_processor.gd::_process_climate`) into a `mc-turn` climate 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 onto
`state.grid` in `step()`. Effects: weather events + unit HP damage + tile climate shifts
in the headless sim.
- [ ] **Gap 2 — Natural / "apocalyptic" events (M3 milestone).** Port the `.messy`
`ecological_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 per `EVENT_FREQUENCY_SPEC.md`;
triggers from climate (gap 1), damage targets in biology (fauna/ecology). Wire into the
turn. (Magic category excluded — M4.)
- [ ] **Gap 3 — Equipment / crafting.** `mc-city/recipes.rs` + `enqueue_item` exist but
there is no headless `Craft`/`Equip` `PlayerAction` and crafting isn't in the bench
turn. Add the action(s) + dispatch + wire recipe resolution (gating resources, quality)
into the headless production/turn so equipment is craftable in self-play.
- [ ] **Gap 4 — Per-building build queues.** Bench `CityState` has a single `queue`;
per-building queues live in the full `mc_city::City` (live game). This is the dual
city-model split ([[p3-25 ...]] step 6 / city_slot.rs). Either give `CityState`
per-building queues or unify the models so the headless turn simulates them.
## 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).

View file

@ -1,13 +1,13 @@
{
"generated_at": "2026-06-26T11:15:15Z",
"generated_at": "2026-06-26T12:25:01Z",
"totals": {
"oos": 31,
"missing": 0,
"done": 296,
"partial": 3,
"oos": 31,
"in_progress": 0,
"stub": 0,
"partial": 2,
"total": 329
"done": 296,
"total": 330
},
"objectives": [
{
@ -3299,6 +3299,16 @@
"owner": "warcouncil",
"updated_at": "2026-06-26",
"summary": "> **Owner directive (2026-06-26):** \"gd should only be UI view of simulation\" +\n> \"simulator should provide everything\" + \"no stubs — prod code only\". A player-like\n> the headless adapter (or any UI) must get the *full* game state from the simulator's\n> projected view (`GdPlayerApi.view_json` → `PlayerView`), not by GDScript re-deriving\n> simulation facts.\n\n**Root cause (verify-first investigation).** The simulator holds **two parallel city\nmodels** ([api-gdext/src/city_slot.rs:3-6](../../src/simulator/api-gdext/src/city_slot.rs)):\n\n- `GdGameState.presentation_cities: Vec<Vec<mc_city::City>>` — the **authoritative**\n rich model: `owned_tiles`, `worked_tiles`, `position`, `culture_stored`, buildings\n ([mc-city/src/city.rs](../../src/simulator/crates/mc-city/src/city.rs)).\n- `GameState.players[pi].cities: Vec<mc_state::CityState>` — a **bench** model with NO\n territory ([mc-city/src/lib.rs:126](../../src/simulator/crates/mc-city/src/lib.rs)),\n explicitly *\"left untouched\"*. City position lives in the parallel\n `PlayerState.city_positions`.\n\n`project_view` ([mc-player-api/src/projection.rs](../../src/simulator/crates/mc-player-api/src/projection.rs))\nreads the **bench** model, so `view_json` is structurally blind to territory — and thus\nto worked tiles and to inter-player trades (which need owned-tile resource sourcing).\n`GdPlayerApi` (the headless harness) holds only `GameState` — no `presentation_cities` —\nso the fix for the headless path is to give the bench state real territory, not to reach\ninto the rich `City`.\n\nConsequences observed: `CityView.owned_tiles` and `DiplomacyView.{open_borders,\nshared_map,agreements_active}` were hardcoded stubs in the projection; there are no\ntrade-deal fields on `DiplomacyView` at all; `mc-turn::process_trade_phase` sources trade\ninputs from bench proxies (`tile_strategics: Vec::new()`, `tile_luxuries` proxy) and does\nnot persist its computed ledger to `state.trade_ledger`. The live game's working trade\npath (p3-23) is GDScript-orchestrated and parses `trade_ledger_json` itself — a\npresentation-layer workaround that this objective supersedes for the headless/sim path.\n\nThe data needed *does* exist in Rust: `GridState.tile(col,row)→{biome,quality}` +\n`mc_core::collectibles::tile_collectibles(biome,quality,rng)` resolve a tile's resources\ndeterministically. What's missing is (a) owned-tile territory in the bench state, (b) a\nresource-category catalog (luxury/strategic) in Rust, (c) persistence of swap/sale deals\nto `state.trade_ledger`, (d) projecting it all."
},
{
"id": "p3-26",
"title": "Complete the headless simulator — close the live-vs-headless system gaps (loop done-criterion)",
"priority": "p3",
"status": "partial",
"scope": "game1",
"owner": "warcouncil",
"updated_at": "2026-06-26",
"summary": "> **Owner directive (2026-06-26):** the /loop \"continue until Game-1 done\" is **not\n> finished until the SIMULATOR is complete** — the headless Rust sim\n> (`mc-turn::TurnProcessor`, driven via `GdPlayerApi`/`magic_civ_*`) must play a full\n> self-play game with ALL systems, not the reduced subset shipped so far. See\n> [[project_loop_done_means_simulator_complete]].\n\nThe headless `step()` ([processor.rs:392+](../../src/simulator/crates/mc-turn/src/processor.rs))\ncurrently runs: trade (p3-25), economy, city_production (single queue), culture+border\nexpansion, tech/science, fauna encounters, combat/siege, diplomacy. Verified live via\n`magic_civ_view` (e.g. border expansion fired turn 0→1: `owned_tiles [[1,6]]→[[1,6],[0,6]]`).\n\n**Gaps (each verified absent from the headless turn):**"
}
]
}