docs(@projects/@magic-civilization): 🌊 p3-26/27 — close marine gap (Rust-authoritative); drop ocean-collapse as gold-plating

Verified file:line that the marine→climate feed is already complete headless:
process_climate_phase → ClimatePhysics::process_step → compute_global_stats
writes grid.ocean_dead_fraction (reef-based, physics.rs:800) and step_evaporation
consumes it (physics.rs:460), every turn. Gap-1's "marine_harvest remaining"
is CLOSED.

Correction: mc_ecology:🌊:tick_ocean_state (4-phase trophic cascade) is
wired in NEITHER the live GDExt bridge NOR the live GDScript — the live game
runs a simple fish-stock ocean_dead_fraction (marine_harvest.gd), not the
cascade. Wiring tick_ocean_state headless would build a system the live game
doesn't run (parity ≠ gap). Marked OUT/gold-plating with citations so a future
session doesn't port it. The Rust reef-based formula vs the live fish-stock
formula is a divergence; Rail-1 → Rust drives, no reconciliation owed.

Also recorded D1 ruling (distinct ItemProduced) in p3-29.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-27 06:27:29 -04:00
parent a0ace92c23
commit ac5efa4bec
3 changed files with 23 additions and 6 deletions

View file

@ -25,7 +25,7 @@ expansion, tech/science, fauna encounters, combat/siege, diplomacy. Verified liv
## Acceptance (sequenced; each gap closed in bounded, cargo+e2e-verified increments)
- [~] **Gap 1 — Climate / environment runtime.** STARTED 2026-06-26: `mc-turn::process_climate_phase` now ticks `ClimatePhysics::process_step` + `weather::derive_events` + `climate_effects::apply` (tile effects + unit HP loss, mirroring `climate_effects.gd`: `hp=max(0,hp-loss)`) each round on `state.grid`. Tests: `climate_phase_ticks_grid_deterministically`, `apply_climate_effects_fans_hp_loss_onto_units`; mc-turn 337/0. **Remaining:** `marine_harvest` (ocean_dead_fraction feeding climate). ORIG SPEC: Port the live per-turn chain
- [x] **Gap 1 — Climate / environment runtime — DONE** ✅ (verified 2026-06-27). `mc-turn::process_climate_phase` ticks `ClimatePhysics::process_step` + `weather::derive_events` + `climate_effects::apply` (tile effects + unit HP loss, mirroring `climate_effects.gd`: `hp=max(0,hp-loss)`) each round on `state.grid`. Tests: `climate_phase_ticks_grid_deterministically`, `apply_climate_effects_fans_hp_loss_onto_units`. **The former "marine_harvest remaining" item is CLOSED:** `ocean_dead_fraction` is both computed (`compute_global_stats`, physics.rs:800, reef-based) and consumed (`step_evaporation`, physics.rs:460) inside the climate phase's `process_step` — the marine→climate feed runs headless and is Rust-authoritative. The live fish-stock `_compute_ocean_dead_fraction` is a divergence, not a missing port (Rail-1: Rust drives). The trophic-cascade `tick_ocean_state` is gold-plating (not in the live game) — see [[p3-27-biosphere-headless]]. ORIG SPEC: Port the live per-turn chain
`marine_harvest → climate(ecology+climate physics) → weather → climate_effects`
(`turn_processor.gd::_process_climate`) into a `mc-turn` climate phase. The PHYSICS is
already Rust (`mc-climate`: `physics.process_step`, `climate_effects::apply`,

View file

@ -23,7 +23,23 @@ interact with.
- [x] **Ecology population tick** ✅ (eb38e8678 + cba0ea8ec) — `process_ecology_phase` drives `EcologyEngine` per turn in `apply_end_turn` (relocated from mc-turn to dodge the mc-turn→mc-ecology→mc-mapgen cycle); seeds genesis on tick 1, persists via continuation-JSON on GameState; FFI `set_ecology_species_json` + harness `_apply_ecology_species`. mc-player-api 138/0.
- [~] **Flora succession**`EcologyEngine::process_step` already returns + applies `FloraTransition`s (subsumed in the ecology tick above). Confirm whether a separate `mc-flora::FloraEngine` pass is still needed or if process_step covers it.
- [x] **Marine ecology (core)** ✅ — per-tile fish-stock + coral-reef + mangrove→fish feedback already tick inside `EcologyEngine::process_step` (engine.rs:416/424), which runs in the headless ecology phase; `ocean_dead_fraction` updates via the climate phase (physics.rs:800). No live `MarineHarvestScript` remains — fully Rust. **Refinement:** the global catastrophic ocean-collapse `mc_ecology::ocean::tick_ocean_state` is unwired. Prereqs to wire it (scoped 2026-06-26): (1) `global_fish_stock` is never aggregated from per-tile `fish_stock` (collapse would be inert) — add a mean-over-water aggregation; (2) `tick_ocean_state`/`apply_coastal_damage` take a `BiomeTagRegistry` that the headless path lacks — either boot one or switch their water/coast checks to the built-in `has_tag` free fn (note: NOT a behaviour-free swap — a coastal-damage test depends on the registry's biome set, so the test must be reconciled). Then call it in the ecology phase with OceanDeadZoneConfig (Default or climate_spec-booted). A real task, not a stub — the code + tests exist in isolation.
- [x] **Marine ecology (core) — DONE; ocean-collapse refinement is GOLD-PLATING, dropped**
(verified 2026-06-27). Per-tile fish-stock + coral-reef + mangrove→fish feedback tick inside
`EcologyEngine::process_step` (engine.rs:416/424) in the headless ecology phase. `ocean_dead_fraction`
is computed AND consumed every turn: `process_climate_phase` (processor.rs:1107) → `ClimatePhysics::
process_step` (physics.rs:153) → `compute_global_stats` writes `grid.ocean_dead_fraction = dead_reef/
coast` (physics.rs:800) and `step_evaporation` scales evaporation by it (physics.rs:460). So the
marine→climate feed is **complete headless and Rust-authoritative**.
- **CORRECTION — the `tick_ocean_state` "refinement" is OUT (gold-plating, do NOT wire).** Verified
file:line: the LIVE game runs `marine_harvest.gd::_compute_ocean_dead_fraction` (dead = is_coast
tiles with `fish_stock==0 && resource_id==""`) feeding climate evaporation — a *simple fraction*,
NOT the 4-phase trophic-cascade state machine in `mc_ecology::ocean::tick_ocean_state`
(healthy→stressed→anoxic→toxic). `tick_ocean_state` is wired in NEITHER the live GDExt bridge
(api-gdext: 0 hits) NOR the live GDScript (0 hits). Building it headless = a system the live game
does not run → parity ≠ gap (rail: don't build disabled systems). The Rust reef-based
`ocean_dead_fraction` (physics.rs:800) and the live fish-stock formula are a *divergence*; Rail-1
says Rust drives, so no reconciliation/port is owed. `tick_ocean_state` stays as isolated,
tested-in-place dead code unless a future design explicitly enables ocean collapse in the game.
- [x] **Bio-targeting events — DONE** ✅ apply_disease_events applies the FULL tier spec: fauna_loss + canopy_loss + tier_loss + o2_delta (global, a95f2d113) + lair_kill_chance (a95f2d113), from the boot-loaded events_config, struck in the ecology phase. Marine fish/reef already tick via process_step (see Marine row).
- [~] Deterministic from seed ✅ (cargo green, determinism test); headless e2e boot pending dylib rebuild + GUT boot proof.

View file

@ -266,10 +266,11 @@ real remaining steps 1-2 (Rust + headless only, live game untouched, no render-p
### BLOCKING decisions (need an owner ruling before coding T3/T-dec)
- **D1 — `item_produced`:** is a crafted item distinct from a building, or fold into
`CityBuildingCompleted`? GDScript fires `item_produced` separately (`_process_production`,
turn_processor.gd:130) AND `city_building_completed`. Recommend a distinct `ItemProduced`
event (mirrors the separate signal); confirm.
- **D1 — `item_produced`:** ✅ **RULED (owner, 2026-06-27): distinct `ItemProduced` event**
{turn, clan, city, item_id, hex}, mirroring the separate live `item_produced` signal
(turn_processor.gd:130), distinct from `CityBuildingCompleted`. (T4 unblocked, deferred —
owner pivoted this session to p3-26/p3-27 headless completion; the live swap that consumes
§A is render-gated.)
- **D2 — `strategic_gate_rejected` + round-lifecycle markers** (`fauna_round_*`,
`worldsim_round_*`, `player_round_*`, `game_phase_changed`): surface through `step()` /
`TurnResult`, or keep the existing worldsim-bridge + GDScript path as the acceptance-#4