diff --git a/.project/objectives/README.md b/.project/objectives/README.md
index b6cbbbd6..33c2e39b 100644
--- a/.project/objectives/README.md
+++ b/.project/objectives/README.md
@@ -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** |
@@ -26,7 +26,7 @@
| Team Lead | Remaining |
|---|---|
-| [warcouncil](../team-leads/warcouncil.md) | 2 |
+| [warcouncil](../team-leads/warcouncil.md) | 3 |
|
diff --git a/.project/objectives/p3-26-complete-headless-simulator.md b/.project/objectives/p3-26-complete-headless-simulator.md
new file mode 100644
index 00000000..6d5e26a0
--- /dev/null
+++ b/.project/objectives/p3-26-complete-headless-simulator.md
@@ -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).
diff --git a/public/games/age-of-dwarves/data/objectives.json b/public/games/age-of-dwarves/data/objectives.json
index 6f234176..99613431 100644
--- a/public/games/age-of-dwarves/data/objectives.json
+++ b/public/games/age-of-dwarves/data/objectives.json
@@ -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>` — 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` — 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):**"
}
]
}