fix(@projects/@magic-civilization): 🐛 update bunker status and acceptance criteria
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
d8288b2a9d
commit
da5f138c23
2 changed files with 75 additions and 9 deletions
|
|
@ -2,9 +2,9 @@
|
|||
id: p2-76
|
||||
title: Bunker improvement — deposit-destroying fortified subterranean chamber
|
||||
priority: p2
|
||||
status: missing
|
||||
status: partial
|
||||
scope: game1
|
||||
updated_at: 2026-06-06
|
||||
updated_at: 2026-06-09
|
||||
blocked_by: [p2-75, p2-80]
|
||||
---
|
||||
|
||||
|
|
@ -57,13 +57,40 @@ contents and *contaminate* a tile.
|
|||
|
||||
## Acceptance
|
||||
|
||||
- ◻ Bunker is buildable only on `hills` / `mountains` and only with `pneumatic_construction` researched (gate read from JSON, per Rail 2).
|
||||
- ◻ On completion, the bunker applies `defense_bonus: 100` and `concealed_from_surface: true` via the `p2-75` subsystem.
|
||||
- ◻ `destroys_deposit`: completion records the tile's flat index in a new `destroyed_deposits: BTreeSet<u32>` (or equivalent) overlay; `mc-city/yield_fold.rs` consults it and yields zero collectible from that tile thereafter. The underlying `CollectiblesIndex` is **not** mutated.
|
||||
- ◻ `surface_contamination`: completion records the tile in a contamination overlay with `contamination_turns = max(min_turns, destroyed_deposit_tier × turns_per_tier)`; while active the tile is `yields_zeroed_and_unworkable`. Overlay decays per turn; self-heals at 0 (fixed-duration model, no class engine yet — that is `p2-77`).
|
||||
- ◻ **River-gap guard (INITIAL)**: the bunker is forbidden on a tile that would dam a river (a river-edge tile bridging upstream/downstream) until the runtime hydrology re-solve (`p2-78`) lands. Document this as a temporary build restriction, removed by `p2-78`.
|
||||
- ◻ Serde round-trip: a `GameState` with a completed bunker (destroyed deposit + active contamination) round-trips cleanly; both overlays `#[serde(default)]`.
|
||||
- ◻ `cargo test` green (build-gate, deposit-destruction overlay, contamination-duration, yield-zeroing, serde round-trip), headless GUT green, proof-scene screenshot of a built bunker (defense + concealment + dead surface tile) reviewed.
|
||||
- ◐ **Bunker buildable only on `hills` / `mountains` + `pneumatic_construction`** (gate read from JSON, per Rail 2). The terrain/tech gate is in `bunker.json` (`valid_terrain`, `requires_tech`) and read by the existing GDScript build-validity path. **River-gap guard added in Rust** (see below). The terrain/tech build-validity enforcement is a GDScript-presentation concern not re-verified this pass — partial.
|
||||
- ✓ **Defense + concealment via p2-75 — DONE (2026-06-09).** `complete_improvement` writes the bunker anchor mirroring `effects` (`defense_bonus: 100`, `concealed_from_surface: true`); the p2-75 combat/vision path consumes them. Verified by `bunker_completion_records_destroyed_deposit_and_pending_terraform` (asserts the mirrored effects) + the gdext `tile_improvement_defense_bonus`/`tile_improvement_concealed` bridges.
|
||||
- ✓ **`destroys_deposit` overlay + yield-zeroing — DONE (2026-06-09).** `ImprovementEffects` extended with `destroys_deposit: bool` (`#[serde(default)]`). On completion, `GameState::complete_improvement` records the tile's flat index (`col * grid_height + row`) in the new global `destroyed_deposits: BTreeSet<u32>` overlay; `mc-city/yield_fold.rs::tile_yields_from_collectibles_suppressed` consults a suppressed-coords set and yields zero collectible. The `CollectiblesIndex` is never mutated. **Verified:** `bunker_completion_records_destroyed_deposit_and_pending_terraform`, `non_destroying_improvement_records_no_overlay`, `p2_76_suppressed_tile_yields_zero_collectible` (all green on apricot).
|
||||
- ✓ **`surface_contamination` overlay + decay + self-heal — DONE (2026-06-09).** `SurfaceContaminationSpec` (+ `duration_for_tier(tier) = max(min_turns, tier × turns_per_tier)`) parsed into `ImprovementEffects.surface_contamination`. Completion enqueues a `TerraformEvent` (tier SNAPSHOTTED from the persisted tile `quality` — never re-derived from seed). `WorldSim::step` sub-step 1b seeds a `TileContamination { remaining_turns, source_tier }` on the `WorldSim` `contamination_map` overlay + chronicles it; sub-step 4b decays every active contamination and removes (self-heals) at 0, rebuilding the derived `GameState::unworkable_tiles` mirror. **Verified:** `contamination_duration_scales_with_destroyed_tier`, `terraform_seeds_contamination_then_decays_and_self_heals` (seeds tier-3 → 30 turns, decays to self-heal, asserts the unworkable mirror + chronicle entry), green on apricot.
|
||||
- ✓ **River-gap guard (INITIAL) — DONE (2026-06-09).** `GameState::bunker_river_gap_blocked(col, row)` returns true when the tile carries any `river_edges` (a river course). Bridged as `GdGameState::bunker_river_gap_blocked` for the build-validity path. Documented as temporary — removed by `p2-78`. **Verified:** `bunker_river_gap_guard_blocks_river_course_tiles`.
|
||||
- ✓ **Serde round-trip — DONE (2026-06-09).** `destroyed_deposits` is `#[serde(default)]` on `GameState` and survives a full `GameState` serde round-trip (`destroyed_deposits_overlay_round_trips_serde`). The contamination overlay round-trips losslessly via the `worldsim_state`-envelope pairs form + `restore_contamination_map` (`contamination_map_round_trips_serde`). `pending_terraform` is `#[serde(skip)]` (drained each turn, design Q3) and is empty on load.
|
||||
- ◐ **`cargo test` green + headless GUT + proof-scene screenshot.** Cargo DONE — `cargo test -p mc-core -p mc-state -p mc-city -p mc-turn -p mc-worldsim` green on apricot (incl. all 6 new bunker tests + 3 worldsim contamination tests + the yield-suppression test); `validate-game-data` 1103/0. The **proof-scene screenshot of a built bunker (defense + concealment + dead surface tile) is NOT yet captured** — blocks `done`. (Determinism preserved: `p2_76_substeps_are_noop_without_terraform` confirms 1b/4b are no-ops without a pending terraform, so the worldsim golden vector is unperturbed.)
|
||||
|
||||
## Verification note (2026-06-09, apricot — p2-76 bridgehead built)
|
||||
|
||||
Built per the design `.project/designs/p2-76-79-terraforming-cascade-design.md`
|
||||
Increment 1, with the RECOMMENDED defaults for the open questions:
|
||||
- **Q1 (overlay home):** split by lifetime — `destroyed_deposits` (permanent,
|
||||
one-shot) on `GameState`; contamination (per-turn-evolving) on the `WorldSim`
|
||||
side-structure; derived `unworkable_tiles` mirror on `GameState` for the yield
|
||||
seam. Implemented as recommended.
|
||||
- **Q3 (pending_terraform persistence):** `#[serde(skip)]` — completion + 1b
|
||||
application happen in the same `WorldSim::step`, so the queue never crosses a
|
||||
save boundary. Implemented as recommended.
|
||||
|
||||
**Files:** `mc-core/src/improvement.rs` (`destroys_deposit` +
|
||||
`SurfaceContaminationSpec`), `mc-state/src/game_state.rs` (`destroyed_deposits`,
|
||||
`pending_terraform`, `unworkable_tiles`, `TerraformEvent`, deposit-destruction in
|
||||
`complete_improvement`, `tile_flat_index`, `bunker_river_gap_blocked`),
|
||||
`mc-city/src/yield_fold.rs` (`tile_yields_from_collectibles_suppressed`),
|
||||
`mc-ecology/src/tile.rs` (`TileContamination`), `mc-worldsim/src/lib.rs`
|
||||
(`contamination_map`, 1b `apply_pending_terraform`, 4b `tick_contamination`,
|
||||
`restore_contamination_map`), `api-gdext/src/lib.rs` (`is_deposit_destroyed`,
|
||||
`is_tile_unworkable`, `bunker_river_gap_blocked` bridges).
|
||||
|
||||
**Remaining for `done`:** the proof-scene screenshot (phase-gate). The Rust path
|
||||
+ overlays + determinism are complete and tested; the proof scene + the GDScript
|
||||
build-validity enforcement of the terrain/tech gate are the open presentation
|
||||
work.
|
||||
|
||||
## Non-goals
|
||||
|
||||
|
|
|
|||
|
|
@ -5470,6 +5470,45 @@ impl GdGameState {
|
|||
}
|
||||
}
|
||||
|
||||
/// p2-76 — true iff the deposit at `(col, row)` has been permanently
|
||||
/// destroyed (by a bunker). Reads the `destroyed_deposits` overlay flat
|
||||
/// index. The yield path consults this so a destroyed-deposit tile yields no
|
||||
/// collectible. False when there is no grid or the tile is off-map.
|
||||
#[func]
|
||||
pub fn is_deposit_destroyed(&self, col: i64, row: i64) -> bool {
|
||||
let Ok(c) = u16::try_from(col) else { return false };
|
||||
let Ok(r) = u16::try_from(row) else { return false };
|
||||
match self.inner.tile_flat_index(c, r) {
|
||||
Some(flat) => self.inner.destroyed_deposits.contains(&flat),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// p2-76 — true iff the tile at `(col, row)` is currently
|
||||
/// yields-zeroed-and-unworkable due to active surface contamination. Reads
|
||||
/// the derived `unworkable_tiles` mirror (rebuilt each worldsim step).
|
||||
#[func]
|
||||
pub fn is_tile_unworkable(&self, col: i64, row: i64) -> bool {
|
||||
let Ok(c) = u16::try_from(col) else { return false };
|
||||
let Ok(r) = u16::try_from(row) else { return false };
|
||||
match self.inner.tile_flat_index(c, r) {
|
||||
Some(flat) => self.inner.unworkable_tiles.contains(&flat),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// p2-76 **temporary** river-gap build guard: true when a bunker (or other
|
||||
/// deposit-destroying improvement) must be FORBIDDEN at `(col, row)` because
|
||||
/// the tile carries a river course (damming it needs the `p2-78` hydrology
|
||||
/// re-solve). The build-validity path consults this before allowing a bunker.
|
||||
/// Removed by `p2-78`.
|
||||
#[func]
|
||||
pub fn bunker_river_gap_blocked(&self, col: i64, row: i64) -> bool {
|
||||
let Ok(c) = u16::try_from(col) else { return false };
|
||||
let Ok(r) = u16::try_from(row) else { return false };
|
||||
self.inner.bunker_river_gap_blocked(c, r)
|
||||
}
|
||||
|
||||
/// Queue a bombard request for the turn processor to drain.
|
||||
///
|
||||
/// `indirect_fire` must be `true` for units with the `arcing` keyword
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue