diff --git a/.project/objectives/p3-29-rail1-turn-unification.md b/.project/objectives/p3-29-rail1-turn-unification.md index 7735957e..41e8a0af 100644 --- a/.project/objectives/p3-29-rail1-turn-unification.md +++ b/.project/objectives/p3-29-rail1-turn-unification.md @@ -47,3 +47,28 @@ emits vs `TurnResult`; (2) wire turn_manager → GdTurnProcessor.step behind the (3) delete the GDScript orchestration; (4) render-proof on apricot/plum. This is the true Rail-1 finish line — bigger than B7 (per-building queues), which becomes moot once the live game runs the Rust turn (which has the model the unified turn will use). + +## De-risking audit (2026-06-27) — the bulk is event-surfacing, not the call-swap + +`GdTurnProcessor::step(GdGameState)` (the bridge) exists and the Rust step computes every +system. The blocker to switching the live game is **UI event parity**: +- `TurnResult.events_emitted: Vec` is REPLAY-focused — only + `CityFounded / CityCaptured / UnitKilled / UnitCreated / GameOver / AmbientEncounterFired`. +- The live GDScript turn emits far richer UI signals inline: `city_grew`, + `city_building_completed`, `city_unit_completed`, `city_border_expanded`, + `culture_researched`, `flora_succession`, `fauna_round_started/ended`, … +- `turn_result_to_dict` surfaces only a thin slice (lair/victory/fauna). + +So the real p3-29 work, in order: +1. **Enrich the Rust turn's event surface** — emit the granular UI events (growth, building/unit + completion, border expansion, culture, flora succession) from the phases that cause them into + `TurnResult` (headless-verifiable, no live-game risk). +2. **Surface them through `turn_result_to_dict`** so GDScript receives them. +3. **`turn_manager` translates `TurnResult` → `EventBus` emits** (GDScript = pure render). +4. **Swap** turn_manager to `GdTurnProcessor.step`; **delete** `turn_processor.gd::_process_*` + + the duplicate `EcologyState.tick`. +5. **Render-proof** on apricot/plum. + +Steps 1-2 are the bulk and are safe (Rust + headless tests, live game untouched). Steps 3-5 are +the live-game swap (needs the render proof). This is why the simulation was buildable headless +this session but the live game still runs GDScript — the event-surface gap was never closed.