From 04763a3870da2609f13b0d88cc7f1bd874131243 Mon Sep 17 00:00:00 2001 From: Natalie Date: Sat, 27 Jun 2026 15:05:34 -0400 Subject: [PATCH] feat(view): project CityView.culture_stored from the per-city CulturePool (Rail-1 Phase 0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The live city_screen reads a per-city culture meter off the GDScript CityScript; view_json had no equivalent, so the UI-pure-view migration couldn't render it from getState(). project_cities now surfaces culture_stored from PlayerState.culture_pool.city(c_idx) (the same accumulator mc-turn::process_culture ticks for border expansion); 0.0 for a city with no pool entry. Closes the last genuine Phase-0 projection gap (UnitView equipped/experience/movement/ posture + ResourceView golden_age were already projected — design-doc table was stale). Test: projection_surfaces_city_culture_stored. mc-player-api lib 142/0. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../crates/mc-player-api/src/projection.rs | 41 +++++++++++++++++++ .../crates/mc-player-api/src/view.rs | 5 +++ 2 files changed, 46 insertions(+) diff --git a/src/simulator/crates/mc-player-api/src/projection.rs b/src/simulator/crates/mc-player-api/src/projection.rs index e34c4dcc..636d3da3 100644 --- a/src/simulator/crates/mc-player-api/src/projection.rs +++ b/src/simulator/crates/mc-player-api/src/projection.rs @@ -286,6 +286,15 @@ fn project_cities( // and adapters can call view() across turns to observe progress. // TRACKED: surface the canonical threshold via mc-city. food_growth_threshold: 0.0, + // p3-25/Phase-0: real per-city culture accumulator from the + // player's CulturePool (the same value border expansion ticks + // in mc-turn::process_culture). 0.0 when the city has no pool + // entry yet (registered lazily on first culture yield). + culture_stored: player + .culture_pool + .city(c_idx) + .map(|cc| cc.culture_stored as f32) + .unwrap_or(0.0), production_queue, buildings, // p3-25: real territory. CityState.owned_tiles is empty on a fresh @@ -2142,6 +2151,38 @@ mod tests { ); } + /// p3-25 Phase-0: `CityView.culture_stored` surfaces the per-city culture + /// accumulator from the player's `CulturePool` (the same meter the live + /// `city_screen` reads + border expansion ticks). A city with no pool entry + /// projects `0.0`. + #[test] + fn projection_surfaces_city_culture_stored() { + let mut state = GameState::default(); + state.turn = 1; + state.grid = Some(mc_core::grid::GridState::new(20, 20)); + let mut a = PlayerState::default(); + a.player_index = 0; + a.city_positions = vec![(3, 4), (7, 8)]; + a.cities = vec![mc_city::CityState::starter(), mc_city::CityState::starter()]; + // City 0 accrues culture via its pool; city 1 has no pool entry. + a.culture_pool.register_city(0, 5.0); + a.culture_pool.tick_all(); + a.culture_pool.tick_all(); + state.players.push(a); + + let view = project_view(&state, 0, /*omniscient=*/ true); + assert_eq!(view.cities.len(), 2); + assert!( + (view.cities[0].culture_stored - 10.0).abs() < 1e-3, + "registered city's stored culture must surface: {}", + view.cities[0].culture_stored + ); + assert_eq!( + view.cities[1].culture_stored, 0.0, + "city with no pool entry projects 0.0" + ); + } + /// p3-24 rail-1: the diplomacy projection reads real OpenBorders/SharedMap /// agreement state from `state.trade_ledger` (replacing the former hardcoded /// `false`/empty stubs). diff --git a/src/simulator/crates/mc-player-api/src/view.rs b/src/simulator/crates/mc-player-api/src/view.rs index 33cb1da0..b61bad0d 100644 --- a/src/simulator/crates/mc-player-api/src/view.rs +++ b/src/simulator/crates/mc-player-api/src/view.rs @@ -142,6 +142,11 @@ pub struct CityView { pub food_stored: f32, /// Food needed for the next growth event. pub food_growth_threshold: f32, + /// Culture accumulated toward the next border expansion (mirrors the live + /// `CityScript` culture meter the `city_screen` reads). Sourced from the + /// player's `CulturePool` per-city state; `0.0` for a city with no pool entry. + #[serde(default)] + pub culture_stored: f32, /// Current production queue. pub production_queue: Vec, /// Buildings completed in this city.