From f2d78e7aa436a3138768b14a4703ff950685448a Mon Sep 17 00:00:00 2001 From: Natalie Date: Tue, 12 May 2026 11:26:55 -0700 Subject: [PATCH] =?UTF-8?q?feat(@projects/@magic-civilization):=20?= =?UTF-8?q?=E2=9C=A8=20update=20playerstate=20cities=20to=20full=20city=20?= =?UTF-8?q?type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- ...-72b-promote-playerstate-cities-to-city.md | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 .project/objectives/p2-72b-promote-playerstate-cities-to-city.md diff --git a/.project/objectives/p2-72b-promote-playerstate-cities-to-city.md b/.project/objectives/p2-72b-promote-playerstate-cities-to-city.md new file mode 100644 index 00000000..f77f5d19 --- /dev/null +++ b/.project/objectives/p2-72b-promote-playerstate-cities-to-city.md @@ -0,0 +1,117 @@ +--- +id: p2-72b-promote-playerstate-cities-to-city +title: "Promote PlayerState.cities from Vec → Vec" +priority: p2 +status: open +scope: game1 +category: architecture +owner: simulator-infra +created: 2026-05-12 +updated_at: 2026-05-12 +blocked_by: [] +follow_ups: [p2-72a, p2-72, p2-67] +--- + +## Context + +`p2-72a Stage 4 Wave 2b` (CityScript view conversion) blocked here. `mc_turn::PlayerState.cities` is `Vec` — the minimal bench struct with 9 fields (population, food_stored, production_stored, queue, queue_cost, queue_tier, food_yield, prod_yield, worker_expertise). Every public CityScript field the GDScript renderers read lives on `mc_city::City` — the full type at `mc-city/src/city.rs:234` with `city_name`, `position`, `buildings`, `placed_buildings`, `culture_stored`, `focus`, `owned_tiles`, `worked_tiles`, `hp`/`max_hp`, per-building queues, etc. + +The two types are deliberately decoupled per the comment at `mc-city/src/lib.rs:105-108`. To make CityScript a thin view, the held type must carry every field renderers consume. + +## Locked decision + +**Promote, don't widen.** Change `PlayerState.cities: Vec` → `Vec`. The full type already exists and is exercised by the production game via `GdCity` instances; promoting consolidates around one canonical city representation. Widening `CityState` would create a second drift point. + +Where `CityState` is genuinely the right minimal struct for some pure-sim subsystem (bench MCTS rollouts?), document the call site and keep a separate `Vec` for that path. Don't drop `CityState` entirely. + +## Surface + +### 1. Field shape + +Update `PlayerState::cities` field type. Add `#[serde(default)]` for backward compat. + +```rust +#[serde(default)] +pub cities: Vec, +``` + +### 2. Constructors + accessors + +`PlayerState::new(...)` and all `PlayerState` construction sites in `api-gdext/src/lib.rs` (lines around 3515, 3721-3737) update to construct `City::starter(name, col, row, owner_slot)` or equivalent. + +`set_player_cities_from_array` (`lib.rs:3836-3858`) updates to walk the GDScript array and construct full `City` entries. + +### 3. Audit downstream consumers + +`grep -rn 'PlayerState.cities\|players\[.*\]\.cities' src/simulator/crates/` — every consumer must accept the new type. + +Likely sites: +- `mc-economy::gold_income` — reads `city.production_stored` etc. Fields exist on `City`. +- `mc-score::city_count` / `culture_total` — reads `city.culture_stored`. +- `mc-ai::tactical::evaluator` — city scoring. +- `mc-turn::processor::process_city_growth` — food/production tick. + +Each consumer either: +- (a) Compiles trivially because `City` has a superset of `CityState` fields. +- (b) Needs a small adapter call to extract the bench subset (`city.to_bench_state()` helper). + +### 4. `GdGameState` accessors + +Add per-city Rust accessor: +```rust +#[func] +fn city_dict(&self, player_idx: i64, city_idx: i64) -> Dictionary; +``` + +Returns the same shape `CityScript.to_dict()` currently emits, sourced from the full `City`. + +Plus mutators per Stage 4 Wave 2b's needs: `set_city_population`, `set_city_food_stored`, `set_city_production_stored`, `spawn_city(player_idx, name, col, row)`, `remove_city_by_id`, etc. + +### 5. Save round-trip + +`SaveEnvelope` v1 already serdes through `GameState`. After the field type change, the envelope shape changes (City has more fields than CityState). Bump `SaveEnvelope::CURRENT_VERSION` to 2 if any pre-bump saves exist; if not, leave at 1 with the wider shape. + +### 6. CityScript view conversion (the original Stage 4 Wave 2b sub-task) + +Once promotion lands, replay the BuildingScript pattern: +- `CityScript` becomes a thin view holding `_state_ref: Node + _player_idx + _idx`. +- `_get` proxy onto `_state_ref._gd_state.city_dict(_player_idx, _idx)`. +- Mutators route through Rust + emit `state_changed`. +- Spawn paths route through `_gd_state.spawn_city(...)`. + +## Acceptance + +- ☐ `PlayerState.cities: Vec` (was `Vec`). +- ☐ All `mc-turn`, `mc-economy`, `mc-score`, `mc-ai`, `mc-city` consumers compile against the new type. +- ☐ `api-gdext` PlayerState constructors + setters updated. +- ☐ `GdGameState::city_dict(player_idx, city_idx)` accessor exists. +- ☐ Per-city mutators on `GdGameState` (`set_city_population`, `spawn_city`, `remove_city_by_id`, focus/queue mutators as Stage 4 needs them). +- ☐ Save round-trip byte-identity for the wider shape. +- ☐ `cargo check --workspace && cargo test --workspace` green (modulo pre-existing). +- ☐ Real-apricot 5-EndTurn smoke still passes. +- ☐ CityScript view conversion lands inline (Stage 4 Wave 2b sub-task). +- ☐ p2-72a Stage 4 Wave 2 per-class checkbox for CityScript flips ✓. + +## Why this size + +- PlayerState.cities field type change: ~1 hr. +- Constructor + accessor updates: ~2 hr. +- Consumer audit + fixes: ~1 day (~5 crates). +- New GdGameState accessors: ~2 hr. +- CityScript view conversion (Stage 4 Wave 2b): ~3 hr. +- Tests + smoke: ~2 hr. + +**Total: ~2-3 days.** + +## Unblocks + +- p2-72a Stage 4 Wave 2b (CityScript view conversion). +- Implicitly unblocks `p2-72c-promote-playerstate-units` (same pattern for units). + +## References + +- `src/simulator/crates/mc-city/src/lib.rs:100-170` — CityState (minimal bench). +- `src/simulator/crates/mc-city/src/city.rs:234` — City (full type). +- `src/simulator/api-gdext/src/lib.rs:3515, 3640-3644, 3721-3737, 3836-3858` — current accessors. +- `src/game/engine/src/entities/city.gd:96-105` — CityScript per-instance `GdCity` (current state). +- `.project/objectives/p2-72a-gdgamestate-canonical-render-source.md` — Stage 4 Wave 2b checkbox.