feat(@projects/@magic-civilization): ✨ update playerstate cities to full city type
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
259695fdbf
commit
f2d78e7aa4
1 changed files with 117 additions and 0 deletions
117
.project/objectives/p2-72b-promote-playerstate-cities-to-city.md
Normal file
117
.project/objectives/p2-72b-promote-playerstate-cities-to-city.md
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
---
|
||||
id: p2-72b-promote-playerstate-cities-to-city
|
||||
title: "Promote PlayerState.cities from Vec<CityState> → Vec<City>"
|
||||
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<mc_city::CityState>` — 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<CityState>` → `Vec<City>`. 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<CityState>` 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<mc_city::City>,
|
||||
```
|
||||
|
||||
### 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<City>` (was `Vec<CityState>`).
|
||||
- ☐ 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.
|
||||
Loading…
Add table
Reference in a new issue