diff --git a/.project/objectives/p2-81.md b/.project/objectives/p2-81.md index 4cfe5747..b614fae6 100644 --- a/.project/objectives/p2-81.md +++ b/.project/objectives/p2-81.md @@ -29,7 +29,7 @@ Evidence captured 2026-06-08 during the p2-75 terrain_change follow-up audit. Do - ✓ **Decision recorded + typed foundation (2026-06-08): DROP `Eq` (keep `PartialEq`).** Rationale: `TileImprovement` is only a *value* in `BTreeMap<(u16,u16), TileImprovement>` (the key is the coord tuple) — never a map key or set element, and `GameState` already can't derive `Eq` (it is full of `f32`/`f64`), so the `Eq` bound was unused; `f32` is the honest representation of `moisture_delta`/`wind_speed_multiplier`, and integer-encoding would be a workaround (Commandment 8). `ImprovementEffects` + `TileImprovement` now derive `PartialEq` (not `Eq`). All four remaining effects are parsed into typed fields: `movement_cost_modifier: i32`, `moisture_delta: f32`, `wind_speed_multiplier: Option` (None = no-op, since the multiplicative identity is 1.0 not 0.0), `prevents_erosion: bool`; each carries `skip_serializing_if` at its no-op value. **Verified on apricot:** parse test `standing_per_turn_effects_parse_from_canonical_json` ✓; `cargo build --workspace` Finished (Eq-drop broke no consumer); cross-crate goldens (mc-save, `full_turn_golden`, mc-worldsim `determinism_same_seed_byte_identical`) all unmoved — `skip_serializing_if` kept standing-improvement bytes byte-stable. Per-system *consumption* of these typed fields remains (bullets below). - ◑ `movement_cost_modifier` — **authority TRACED (2026-06-08): the live game pathfinds in GDScript** (`pathfinder.gd:268` → `tile.get_movement_cost()`, also consumed by `auto_play.gd` movement + `wild_creature_ai.gd`). Rust `mc_pathfinding::effective_cost` is an UNWIRED port-in-progress — no api-gdext bridge calls it, and its own doc defers improvement cost to a "Phase-10 follow-up." So porting improvement movement into Rust pathfinding is **moot until the live game switches to Rust pathfinding** (a separate p0-26-class GDScript→Rust pathfinding migration). The typed `movement_cost_modifier: i32` field now exists in `ImprovementEffects` ready for that port to consume; the live-effect wiring is BLOCKED-WITH-CAUSE on the pathfinding migration, not deliverable within p2-81. -- ❌ improvement `yields` authority traced (Rust mc-city/mc-economy vs tile.gd) and reconciled so yields are computed once, in Rust +- ◑ improvement `yields` — **authority TRACED (2026-06-08): the tile-yield fold is GDScript.** `tile.gd:get_yields()` folds terrain + resource + improvement yields together in GDScript; `city_buildable_helper.gd:build_tile_yields_json` then serializes the **pre-folded** `food`/`production`/`trade` per tile and hands it to the Rust `GdCity.get_yields()` → `parse_tile_yields` bridge, which only rolls collectibles + applies the p1-38 food modifier (it never sees the improvement). Rust does not parse improvement `yields` at all (no `yields` field on `RawImprovementJson`). So moving the improvement-yield fold to Rust is NOT separable — it requires moving the **whole** tile-yield computation (terrain + resource + improvement, with the registries) into `parse_tile_yields`, a broad economy-path refactor that drives city production/food (high regression) and is part of the broader Rust-authority migration (p2-72a), **not p2-81-sized**. BLOCKED-WITH-CAUSE on that migration; the improvement-yield data path can be Rust-authoritative only once tile-yield computation itself is. - ❌ `moisture_delta` (irrigation/drainage) applied per-turn in Rust climate for tiles carrying the improvement; verified by a unit test - ❌ `wind_speed_multiplier` (windbreak) wired into the Rust wind/weather path; verified by a unit test - ❌ `prevents_erosion` (terrace_farming) guards per-turn terrain-quality degradation in Rust; verified by a unit test