Rail-1 spine rewrite Phase 2 foundation. GdTurnProcessor::step mutates
GdGameState.inner only, but the live game holds authoritative cities/units
in the rich presentation_* slots. Add state_sync module + two #[func]s
(sync_presentation_to_inner / sync_inner_to_presentation) implementing
Option C batch sync around the step:
- Units: whole-vec clone both ways (presentation_units and
inner.players[].units are the identical mc_state::MapUnit type).
- Cities: rich City <-> lean CityState scalar projection (population,
food_stored<->food, production_progress<->production_stored, owned_tiles,
hp/max_hp). Down-sync updates lean in place, preserving lean-only fields
(queue/queue_cost/queue_tier/food_yield/prod_yield/worker_expertise);
up-sync merges only the bridged scalars back, leaving rich-only fields
(queues, buildings, building_yields, culture_*, focus, name) untouched.
city_positions/capital_position kept aligned for process_culture/siege.
- Player scalars (gold/science/culture_pool/tech/relations) are inner-only;
no parallel rich slot, so no sync needed.
Sync gap (documented, not fabricated): lean single queue vs rich per-building
queues map has no clean 1:1 mapping and is deliberately not bridged.
8 cargo tests incl. a real mc_turn::TurnProcessor::step driven through the
down/up loop (city grows, rich queues survive). Not yet wired into the live
turn (GDScript Phase-2b). 8/0 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>