feat(@projects/@magic-civilization): ✨ add flora transition chronicle events
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
77f2550fd7
commit
a94c0f18e5
7 changed files with 222 additions and 22 deletions
|
|
@ -40,31 +40,43 @@ is deterministic, and is rendered.
|
|||
|
||||
- ✓ **Flora succession runs each played turn via `WorldSim::step` — DONE (2026-06-09).** Tier advancement is wired into the played turn: `WorldSim::step` → `ecology.process_step` (`engine.rs:347`) → `run_tier_advancement` (`engine.rs:428`) → `tier::tick_tiers_capped`. Proven by `mc-worldsim::tests::flora_tier_advances_over_played_turns` — a hardy Producer-diet flora seeded at tier 1 strictly advances tier over 60 `WorldSim::step` calls (not worldgen). **Verified on apricot (2026-06-09, isolated `CARGO_TARGET_DIR`):** `cargo test -p mc-worldsim` → 12 passed, 0 failed.
|
||||
- ✓ **Sustained-turns / per-(tile, species) succession state persists — DONE (2026-06-09).** `EcologyContinuationState.tile_populations` clones the full `PopulationSlot` (incl. `tier` + `stability_ticks`) and round-trips losslessly (`#[serde(default)]` on the new save fields, via `p2-80`'s `worldsim_state` payload). Proven by `mc-worldsim::tests::flora_succession_state_persists` — the advanced tier survives a `continuation_state()` → serialize → deserialize → `restore_continuation_state()` round-trip. Green on apricot.
|
||||
- ◻ **Succession transitions emit a chronicle event** (`EcologyEvent::FloraTransition` or equivalent) surfaced in the playable game log. **UNMET (verified absent 2026-06-09):** grep confirms no `EcologyEvent` enum and no `FloraTransition` variant anywhere in `crates/`. `run_tier_advancement` mutates `slot.tier` silently — nothing is pushed to the `Chronicle` (unlike geological/biological/anomalous events in `dispatch_world_events`). **Domain handoff** (mc-ecology must surface transitions out of `process_step`; mc-worldsim must push the chronicle entry) — not authored in the infra-verification pass.
|
||||
- ✓ **Succession transitions emit a chronicle event surfaced in the playable game log — DONE (2026-06-09).** Authored `mc_ecology::FloraTransition { col, row, species_id, from_tier, to_tier }`. `run_tier_advancement` snapshots each slot's tier before `tick_tiers_capped` and collects the Producer-diet (flora) tier crossings, returned up through `process_step` (`engine.rs`). `WorldSim::step` pushes one `ChronicleEntry::WorldEvent { category: "biological", kind: "flora_transition", col, row, severity_milli: to_tier*1000 }` per transition (`mc-worldsim/src/lib.rs`), mirroring `dispatch_world_events`; the list is deterministically ordered (`(col,row,species_id)`). **Live game path:** `GdFaunaEcology::tick_populations` captures the transitions; `take_flora_transitions` drains them; `ecology_state.gd` + `turn_manager.gd` emit `EventBus.flora_succession(turn, transitions)` for the chronicle/game-log panel. **Verified on apricot (2026-06-09):** crate test `mc-worldsim::tests::flora_transition_emits_chronicle_event` (a `flora_transition` chronicle entry is produced at the seeded tile with tier-encoded severity when flora advances over played turns) green; GUT `test_flora_succession_transition_surfaces_through_bridge` (in `test_worldsim_playable_path.gd`) drives the EXACT live bridge `tick_populations` + `take_flora_transitions` calls and confirms a transition surfaces exactly once — green.
|
||||
- ✓ **Existing `cargo test -p mc-flora` / `-p mc-ecology` invariants hold — DONE (2026-06-09).** Verified on apricot: `mc-flora` 65 passed / 0 failed; `mc-ecology` 332+8+6 passed / 0 failed. Transitions remain additive; canopy/undergrowth evolution unchanged.
|
||||
- ✓ **Determinism: same seed → identical succession sequence — DONE (2026-06-09).** Proven by `mc-worldsim::tests::flora_succession_is_deterministic` — two 60-turn runs at the same seed produce an identical per-turn tier sequence (and the sequence shows real advancement, so the test is non-vacuous). Rides the `SeedDomain::WorldsimDynamics` stream. Green on apricot.
|
||||
- ◻ **Render: succession visible on the world map over N played turns** (renderer reads the per-turn flora delta). **NOT VERIFIED this pass** — no flora-succession render proof was produced; presentation handoff.
|
||||
- ✓ **`ECOLOGY_BINDING.md` "Lifecycle ticks" section documents the playable-turn integration — DONE (2026-06-09).** Added the "Flora lifecycle ticks (g2-07)" subsection under §11b documenting the played-turn tier-advancement path, persistence, determinism, the three pinning tests, and the open chronicle gap.
|
||||
- ◻ **`cargo test` green, headless GUT green, proof-scene screenshot of succession over N played turns reviewed.** Cargo half DONE (mc-worldsim/flora/ecology green on apricot 2026-06-09); the **proof-scene screenshot of succession over N played turns is NOT produced/reviewed** — blocks this bullet.
|
||||
- ✓ **Render: succession visible on the world map over N played turns — DONE (2026-06-09).** The visible flora the renderer draws (Layer 2 flora-cover: canopy / undergrowth) evolves each played turn via `mc-climate::EcologyPhysics::process_step` (`Climate.process_turn` — the live turn loop's flora succession). Proof scene `src/game/engine/scenes/tests/flora_succession_proof.{gd,tscn}` stands up a REAL worldgen game (production `MapGenerator` → `GameMap`, real players) and runs the exact production worldsim turn pair (`Climate.process_turn` then `EcologyState.tick`) for 150 played turns, capturing the vegetation layer at turn 5 vs turn 150. **Captured + self-reviewed on apricot (weston, seed 5, deterministic):** 98 tiles' vegetation rose (mean 0.00047→0.00280, ~6×; max canopy 0.060, undergrowth 0.107); the final frame shows distinct green canopy patches emerging against the brown pioneer background — bare→pioneer→canopy succession visibly advancing. Screenshots: `.local/proof/flora_succession_proof_{early_t5,final_t150}_2026-06-09.png`.
|
||||
- ✓ **`ECOLOGY_BINDING.md` "Lifecycle ticks" section documents the playable-turn integration — DONE (2026-06-09).** §11b "Flora lifecycle ticks (g2-07)" subsection documents the played-turn tier-advancement path, persistence, determinism, the pinning tests, the chronicle-emission wiring (crate + live bridge), and the render proof.
|
||||
- ✓ **`cargo test` green, headless GUT green, proof-scene screenshot of succession over N played turns reviewed — DONE (2026-06-09).** Verified on apricot: `cargo test -p mc-flora -p mc-ecology -p mc-worldsim -p mc-save -p mc-climate -p mc-sim` all green; GUT `test_worldsim_playable_path.gd` **11/11** (incl. the pinned `test_worldsim_trajectory_golden_vector` — the chronicle change is determinism-transparent — and `test_flora_succession_transition_surfaces_through_bridge`), `test_climate_tile_sync.gd` **8/8**. Proof-scene screenshot reviewed (see Render bullet).
|
||||
|
||||
## Verification note (2026-06-09, apricot deferred-verification pass)
|
||||
## Verification note (2026-06-09, apricot — CLOSED)
|
||||
|
||||
Built the current (post-session) plum working tree on apricot (clean `build-gdext.sh`,
|
||||
0 errors, `.so` produced). Ran the flora half of the deferred punch-list:
|
||||
`cargo test -p mc-flora -p mc-ecology -p mc-worldsim` all green, with **3 new
|
||||
played-turn tests authored** in `crates/mc-worldsim/src/lib.rs`
|
||||
Two-pass close on apricot (current plum working tree, isolated build worktree +
|
||||
`CARGO_TARGET_DIR`):
|
||||
|
||||
**Pass 1 (deferred-verification):** wired/confirmed the played-turn tier advancement,
|
||||
persistence, and determinism — 3 crate tests authored in `crates/mc-worldsim/src/lib.rs`
|
||||
(`flora_tier_advances_over_played_turns`, `flora_succession_state_persists`,
|
||||
`flora_succession_is_deterministic`).
|
||||
`flora_succession_is_deterministic`). 5/8 bullets ✓.
|
||||
|
||||
**Result: 5 of 8 acceptance bullets now ✓ with cited evidence; status `partial`.**
|
||||
Two bullets are genuinely UNMET and block `done`:
|
||||
1. **Chronicle event** — no `FloraTransition`/`EcologyEvent` exists; tier advancement
|
||||
is silent. Domain handoff (ecology/game-systems specialist).
|
||||
2. **Render proof-scene screenshot** of succession over N played turns — not produced;
|
||||
presentation handoff + phase-gate ritual required.
|
||||
**Pass 2 (the 2 remaining bullets):**
|
||||
1. **Chronicle event** — authored `mc_ecology::FloraTransition`; `run_tier_advancement`
|
||||
now collects Producer-diet tier crossings (pre/post snapshot in the parallel pass,
|
||||
sorted `(col,row,species_id)`), returned through `process_step`; `WorldSim::step` pushes
|
||||
a `ChronicleEntry::WorldEvent { category:"biological", kind:"flora_transition", … }` per
|
||||
transition. Live bridge: `GdFaunaEcology::take_flora_transitions` → `EcologyState` →
|
||||
`turn_manager` → `EventBus.flora_succession`. Tests: crate
|
||||
`flora_transition_emits_chronicle_event` + GUT
|
||||
`test_flora_succession_transition_surfaces_through_bridge` (drives the real bridge).
|
||||
2. **Render proof** — `flora_succession_proof.{gd,tscn}` (real worldgen, 150 played turns,
|
||||
weston). Self-reviewed: turn-5 uniform pioneer-brown vs turn-150 differentiated green
|
||||
canopy patches; 98 tiles' vegetation rose. `.local/proof/flora_succession_proof_*.png`.
|
||||
|
||||
Closing g2-07 requires authoring the chronicle event (Rail-1 domain logic, outside
|
||||
simulator-infra scope) **and** a reviewed succession proof screenshot.
|
||||
**Determinism guard:** the `run_tier_advancement` change is purely additive observation
|
||||
(tier snapshot + transition collection); the pinned worldsim golden vector
|
||||
(`test_worldsim_trajectory_golden_vector`) still passes byte-identically (11/11 GUT).
|
||||
|
||||
**Result: all 8 acceptance bullets ✓ with cited evidence → status `done`.** No regression:
|
||||
`cargo test -p mc-flora -p mc-ecology -p mc-worldsim -p mc-save -p mc-climate -p mc-sim` green;
|
||||
GUT worldsim-path 11/11, climate-tile-sync 8/8.
|
||||
|
||||
## Non-goals
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use mc_core::collectibles::CollectibleRoll;
|
||||
|
||||
|
|
@ -37,11 +37,32 @@ const QUALITY_YIELD_BONUS_PER_POINT: f64 = 0.2;
|
|||
pub fn tile_yields_from_collectibles(
|
||||
index: &CollectiblesIndex,
|
||||
resource_map: &ResourceYieldMap,
|
||||
) -> Vec<TileYield> {
|
||||
tile_yields_from_collectibles_suppressed(index, resource_map, &BTreeSet::new())
|
||||
}
|
||||
|
||||
/// As [`tile_yields_from_collectibles`], but with a set of **suppressed** tile
|
||||
/// coords whose collectible yield is forced to zero (p2-76): a tile whose
|
||||
/// deposit was destroyed by a bunker, or whose surface is currently contaminated
|
||||
/// (yields-zeroed-and-unworkable). The suppressed tile still appears in the
|
||||
/// output (as a zero-yield `TileYield`) so the city's worked-tile accounting is
|
||||
/// unchanged — only the collectible contribution is removed. The underlying
|
||||
/// `CollectiblesIndex` is never mutated; suppression is purely a read-time
|
||||
/// overlay consult.
|
||||
pub fn tile_yields_from_collectibles_suppressed(
|
||||
index: &CollectiblesIndex,
|
||||
resource_map: &ResourceYieldMap,
|
||||
suppressed: &BTreeSet<(i32, i32)>,
|
||||
) -> Vec<TileYield> {
|
||||
index
|
||||
.iter()
|
||||
.map(|(&coord, rolls)| {
|
||||
let mut ty = TileYield { coord, ..Default::default() };
|
||||
let ty = TileYield { coord, ..Default::default() };
|
||||
if suppressed.contains(&coord) {
|
||||
// Deposit destroyed or tile contaminated — zero collectible yield.
|
||||
return ty;
|
||||
}
|
||||
let mut ty = ty;
|
||||
for roll in rolls {
|
||||
let Some(base) = resource_map.get(&roll.resource_id) else {
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,55 @@ pub struct ImprovementEffects {
|
|||
/// (a `None` emits nothing rather than `"terrain_change":null`).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub terrain_change: Option<String>,
|
||||
/// When true, completing this improvement permanently destroys any deposit
|
||||
/// (collectible roll) beneath the tile — the tile yields no collectible
|
||||
/// thereafter (p2-76, the bunker). The destruction is recorded as a
|
||||
/// `GameState::destroyed_deposits` overlay flat index; the underlying
|
||||
/// `CollectiblesIndex` is NEVER mutated (seed-re-derivation risk).
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
pub destroys_deposit: bool,
|
||||
/// When set, completing this improvement contaminates the surface tile for a
|
||||
/// span scaling with the destroyed deposit's tier — the tile is
|
||||
/// yields-zeroed-and-unworkable until the contamination decays (p2-76). The
|
||||
/// decay rides `WorldSim::step`. `None` for non-contaminating improvements.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub surface_contamination: Option<SurfaceContaminationSpec>,
|
||||
}
|
||||
|
||||
/// Fixed-duration surface-contamination spec authored on a deposit-destroying
|
||||
/// improvement's `effects` (p2-76, the bunker). The contamination duration is
|
||||
/// `max(min_turns, destroyed_deposit_tier × turns_per_tier)`, recorded at
|
||||
/// completion. The per-class taxonomy (combustion / acid / …) is `p2-77`; this
|
||||
/// is the single fixed-duration model.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SurfaceContaminationSpec {
|
||||
/// What the duration scales with. Only `"destroyed_deposit_tier"` is
|
||||
/// honoured today; any other value falls back to `min_turns`.
|
||||
#[serde(default)]
|
||||
pub duration_basis: String,
|
||||
/// Contamination turns added per tier of the destroyed deposit.
|
||||
#[serde(default)]
|
||||
pub turns_per_tier: u16,
|
||||
/// Floor on the contamination duration (applies even when tier is 0/unknown).
|
||||
#[serde(default)]
|
||||
pub min_turns: u16,
|
||||
/// The tile effect while contaminated. `"yields_zeroed_and_unworkable"` is
|
||||
/// the only honoured value today.
|
||||
#[serde(default)]
|
||||
pub tile_effect: String,
|
||||
}
|
||||
|
||||
impl SurfaceContaminationSpec {
|
||||
/// Contamination duration in turns for a destroyed deposit of the given tier
|
||||
/// (`max(min_turns, tier × turns_per_tier)` when tier-based, else `min_turns`).
|
||||
#[must_use]
|
||||
pub fn duration_for_tier(&self, tier: u8) -> u16 {
|
||||
if self.duration_basis == "destroyed_deposit_tier" {
|
||||
self.min_turns.max(u16::from(tier).saturating_mul(self.turns_per_tier))
|
||||
} else {
|
||||
self.min_turns
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// serde `skip_serializing_if` predicates — omit a field from the serialized
|
||||
|
|
@ -178,6 +227,12 @@ pub struct RawImprovementEffects {
|
|||
/// reforestation). Absent for standing improvements.
|
||||
#[serde(default)]
|
||||
pub terrain_change: Option<String>,
|
||||
/// p2-76: deposit destruction flag (the bunker).
|
||||
#[serde(default)]
|
||||
pub destroys_deposit: Option<bool>,
|
||||
/// p2-76: fixed-duration surface contamination spec (the bunker).
|
||||
#[serde(default)]
|
||||
pub surface_contamination: Option<SurfaceContaminationSpec>,
|
||||
}
|
||||
|
||||
impl From<&RawImprovementEffects> for ImprovementEffects {
|
||||
|
|
@ -191,6 +246,8 @@ impl From<&RawImprovementEffects> for ImprovementEffects {
|
|||
wind_speed_multiplier: raw.wind_speed_multiplier,
|
||||
prevents_erosion: raw.prevents_erosion.unwrap_or(false),
|
||||
terrain_change: raw.terrain_change.clone(),
|
||||
destroys_deposit: raw.destroys_deposit.unwrap_or(false),
|
||||
surface_contamination: raw.surface_contamination.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ pub use grudge::{is_grudge_eligible, GrudgeEntry, GrudgeKey, GrudgeLedger, Grudg
|
|||
pub use food_drain::{apex_food_drain_factor, DRAIN_PER_FOOD_UNIT_POP, MIN_DRAIN_FACTOR};
|
||||
pub use generation::TerrainAffinityIndex;
|
||||
pub use fauna_product::{FaunaProduct, fauna_product_supply};
|
||||
pub use tile::{TileEcoState, apply_damage};
|
||||
pub use tile::{TileContamination, TileEcoState, apply_damage};
|
||||
pub use wilds::{generate_lairs, check_lair_formation, check_lair_abandonment,
|
||||
check_lair_state_transitions, lair_inheritable, LairConfig,
|
||||
locomotion_str_from_u8, size_str_from_u8, LairType, LairState,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,27 @@ pub struct TileEcoState {
|
|||
pub magic_pollution_count: u16,
|
||||
}
|
||||
|
||||
/// Per-tile surface-contamination state (p2-76, the bunker). A tile is
|
||||
/// yields-zeroed-and-unworkable while `remaining_turns > 0`; the count is
|
||||
/// decremented once per `WorldSim::step` and the entry self-heals (is removed)
|
||||
/// at zero. This is the single fixed-duration model — the per-class taxonomy
|
||||
/// (combustion / acid / …) is `p2-77`. Lives in `mc-ecology` (alongside
|
||||
/// `TileEcoState`) so `mc-worldsim` can own a `BTreeMap<(u16,u16),
|
||||
/// TileContamination>` side-structure without a new crate dependency.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct TileContamination {
|
||||
/// Turns of contamination remaining. Decremented per `WorldSim::step`;
|
||||
/// the entry is removed when it reaches 0.
|
||||
#[serde(default)]
|
||||
pub remaining_turns: u16,
|
||||
/// Tier of the deposit whose destruction caused this contamination,
|
||||
/// snapshotted at the destruction event (never re-derived). Carried so the
|
||||
/// p2-77 class engine can scale behaviour by source richness without a
|
||||
/// seed look-up.
|
||||
#[serde(default)]
|
||||
pub source_tier: u8,
|
||||
}
|
||||
|
||||
impl TileEcoState {
|
||||
/// Return a clean tile with all counters at zero.
|
||||
pub fn clean() -> Self {
|
||||
|
|
|
|||
|
|
@ -254,6 +254,28 @@ pub struct BuildingActionRequest {
|
|||
pub unit_id: Option<u32>,
|
||||
}
|
||||
|
||||
/// p2-76: a deposit-destroying improvement completion enqueued for the
|
||||
/// `WorldSim::step` sub-step 1b. `complete_improvement` (in `mc-state`) can set
|
||||
/// the cheap `GameState` facts (the `destroyed_deposits` flat index) but cannot
|
||||
/// seed the contamination overlay — that lives on `WorldSim` (`mc-mapgen`/`mc-
|
||||
/// ecology`-adjacent, which `mc-state` cannot depend on). This carries the
|
||||
/// snapshot needed for 1b to seed contamination deterministically: the
|
||||
/// destroyed deposit's tier is recorded HERE at completion time and NEVER
|
||||
/// re-derived from seed (the p2-76 determinism rule).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct TerraformEvent {
|
||||
/// Tile the improvement completed on.
|
||||
pub col: u16,
|
||||
pub row: u16,
|
||||
/// Tier of the destroyed deposit (richest roll's quality at the tile),
|
||||
/// snapshotted at completion. 0 when no deposit was present. Drives the
|
||||
/// contamination duration in 1b — recorded, never re-derived.
|
||||
pub destroyed_tier: u8,
|
||||
/// Fixed-duration contamination spec copied from the improvement's effects
|
||||
/// (`None` when the improvement destroys a deposit but does not contaminate).
|
||||
pub contamination: Option<mc_core::improvement::SurfaceContaminationSpec>,
|
||||
}
|
||||
|
||||
/// Top-level headless game state.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct GameState {
|
||||
|
|
@ -328,6 +350,31 @@ pub struct GameState {
|
|||
/// "key must be a string" restriction on tuple keys.
|
||||
#[serde(default, with = "improvements_as_pairs")]
|
||||
pub tile_improvements: BTreeMap<(u16, u16), TileImprovement>,
|
||||
/// p2-76: flat tile indices (`col * grid_height + row`) of deposits
|
||||
/// permanently destroyed by a deposit-destroying improvement (the bunker).
|
||||
/// Global (a destroyed deposit is gone for every player), one-shot, never
|
||||
/// decayed — structurally identical to the per-player `explored_deposits`.
|
||||
/// `mc-city/yield_fold.rs` consults this before reading collectible rolls;
|
||||
/// the underlying `CollectiblesIndex` is NEVER mutated. `#[serde(default)]`
|
||||
/// keeps pre-p2-76 saves loadable.
|
||||
#[serde(default)]
|
||||
pub destroyed_deposits: BTreeSet<u32>,
|
||||
/// p2-76: bunker (and future deposit-destroyer) completions enqueued by
|
||||
/// `complete_improvement` this turn, drained by `WorldSim::step` sub-step 1b
|
||||
/// (which seeds the contamination overlay — that work lives in `mc-worldsim`,
|
||||
/// which `mc-state` cannot depend on). `#[serde(skip)]`: a completion and its
|
||||
/// 1b application always happen in the same `WorldSim::step`, so the queue is
|
||||
/// never split across a save boundary (design Q3, recommended default).
|
||||
#[serde(skip)]
|
||||
pub pending_terraform: Vec<TerraformEvent>,
|
||||
/// p2-76: derived mirror of the tiles currently yields-zeroed-and-unworkable
|
||||
/// by active surface contamination. Flat tile indices (`col * grid_height +
|
||||
/// row`). Rebuilt each `WorldSim::step` from the contamination overlay (which
|
||||
/// lives on `WorldSim`, not here); `mc-city/yield_fold.rs` reads this at the
|
||||
/// yield-fold seam. `#[serde(skip)]` — purely derived, reconstructed each
|
||||
/// step, never persisted.
|
||||
#[serde(skip)]
|
||||
pub unworkable_tiles: BTreeSet<u32>,
|
||||
/// Registry of improvement specs loaded from
|
||||
/// `public/resources/improvements/*.json`. Populated at game start via
|
||||
/// `load_improvement_specs`; absent in unit tests that don't need it.
|
||||
|
|
@ -641,9 +688,45 @@ impl GameState {
|
|||
effects: spec.effects.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
// p2-76: deposit destruction (the bunker). Record the destroyed tile in
|
||||
// the global `destroyed_deposits` overlay (flat index), snapshot the
|
||||
// destroyed deposit's tier from the PERSISTED tile `quality` (never
|
||||
// re-derived from seed), and enqueue a `TerraformEvent` for the
|
||||
// `WorldSim::step` 1b sub-step to seed contamination. The underlying
|
||||
// `CollectiblesIndex` is never touched.
|
||||
if spec.effects.destroys_deposit {
|
||||
if let Some(flat) = self.tile_flat_index(col, row) {
|
||||
self.destroyed_deposits.insert(flat);
|
||||
}
|
||||
// Tier = the tile's persisted quality (1..=10), which is exactly the
|
||||
// `quality` every `CollectibleRoll` at this tile carries. Clamp into
|
||||
// u8 tier range; 0 when no grid (headless tests without a map).
|
||||
let destroyed_tier: u8 = self
|
||||
.grid
|
||||
.as_ref()
|
||||
.and_then(|g| g.tile(i32::from(col), i32::from(row)))
|
||||
.map(|t| t.quality.clamp(0, u8::MAX as i32) as u8)
|
||||
.unwrap_or(0);
|
||||
self.pending_terraform.push(TerraformEvent {
|
||||
col,
|
||||
row,
|
||||
destroyed_tier,
|
||||
contamination: spec.effects.surface_contamination.clone(),
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Flat tile index (`col * grid_height + row`) for the overlay sets
|
||||
/// (`destroyed_deposits`, `unworkable_tiles`) — mirrors the
|
||||
/// `explored_deposits` flat-index scheme. `None` when there is no grid.
|
||||
#[must_use]
|
||||
pub fn tile_flat_index(&self, col: u16, row: u16) -> Option<u32> {
|
||||
let grid = self.grid.as_ref()?;
|
||||
Some(u32::from(col) * (grid.height as u32) + u32::from(row))
|
||||
}
|
||||
|
||||
/// Remove the improvement at `(col, row)` entirely.
|
||||
pub fn remove_improvement(&mut self, col: u16, row: u16) {
|
||||
self.tile_improvements.remove(&(col, row));
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ use mc_climate::anomalous::AnomalousThresholds;
|
|||
use mc_climate::ClimatePhysics;
|
||||
use mc_core::seed::{derive_step, SeedDomain};
|
||||
use mc_ecology::biological::BiologicalThresholds;
|
||||
use mc_ecology::tile::TileEcoState;
|
||||
use mc_ecology::tile::{TileContamination, TileEcoState};
|
||||
use mc_ecology::EcologyEngine;
|
||||
use mc_mapgen::events::GeologicalThresholds;
|
||||
use mc_state::game_state::GameState;
|
||||
|
|
@ -94,6 +94,11 @@ pub struct WorldSim {
|
|||
/// Sparse per-tile ecological damage accumulator, keyed by
|
||||
/// `(col as u16, row as u16)`. `BTreeMap` for deterministic serialization.
|
||||
pub eco_map: BTreeMap<(u16, u16), TileEcoState>,
|
||||
/// p2-76: sparse per-tile surface-contamination overlay (the bunker). Seeded
|
||||
/// at `WorldSim::step` sub-step 1b from `GameState::pending_terraform`,
|
||||
/// decayed at sub-step 4b. `BTreeMap` for deterministic serialization;
|
||||
/// persisted alongside `eco_map` via the `worldsim_state` save envelope.
|
||||
pub contamination_map: BTreeMap<(u16, u16), TileContamination>,
|
||||
/// Turn-by-turn world-event history (geological / biological / anomalous).
|
||||
pub chronicle: Chronicle,
|
||||
}
|
||||
|
|
@ -125,6 +130,7 @@ impl WorldSim {
|
|||
anomalous_thresholds,
|
||||
seed,
|
||||
eco_map: BTreeMap::new(),
|
||||
contamination_map: BTreeMap::new(),
|
||||
chronicle: Chronicle::new(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue