fix(simulation): PR #200 review round — cache sizing truth + endorheic assertion
Hoshe finding 1: dropping HydrologySample.elevation was PROVEN unsafe (TerrainAnalysis.elev_pct is a rank percentile, not raw elevation; HydrologyResult carries no elevation) — the copy stays, and the truth moves into the docs instead: gen_queue's TerrainAnalysisCache sizing comment corrected to real 512x256 working-grid numbers (~1.57 -> ~2.62 MB/entry, capacity-8 worst case ~21 MB), clone-on-hit cost documented, byte-safety proof recorded on HydrologySample itself. Arc<TerrainAnalysis> follow-up filed as T-1187. Hoshe finding 2: the endorheic-split test now asserts the bowl basin's BasinOutcome actually diverges (Endorheic at moisture 0, Overflow at 100, straddling ENDORHEIC_MOISTURE_CEILING=60) plus basin-count sanity — mutation-verified by stubbing is_endorheic and watching it fail. Doc/test-only round: goldens byte-unchanged, full suite 2114 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -197,20 +197,58 @@ pub enum GenWorkItem {
|
||||
/// `drain_generation_completions` documents).
|
||||
///
|
||||
/// **TerrainAnalysis availability (T-1137 binding decision, with numbers;
|
||||
/// corrected 2026-07-21 per PR #187 review — Tyre C1):** `BodyWorldState`
|
||||
/// does NOT retain `TerrainAnalysis` after cascade completion (T-1044
|
||||
/// scoped its transient-carry fix to *within-cascade* reuse only —
|
||||
/// `cascade.rs` drops it once `DistrictProfile`+`RoadGraph` finish; see the
|
||||
/// doc on `CascadeSnapshot::terrain_analysis`). Caching it alongside every
|
||||
/// `BodyWorldStateCache` entry would cost ~2 MB × 50-body capacity ≈
|
||||
/// 100 MB of PERMANENT resident cost, paid by every cached body whether or
|
||||
/// not a window is ever requested for it — the exact D-203 budget concern
|
||||
/// T-1044's own ticket text guarded against. So `run_work_item` re-derives
|
||||
/// via `run_layer1` (matching `aliveness_probe`'s existing `--render`
|
||||
/// workaround) rather than persisting a field on `BodyWorldState` — but
|
||||
/// NOT unconditionally on every work item: the actual model is a small
|
||||
/// **per-body LRU** (`TerrainAnalysisCache`, capacity 8, ~16 MB worst
|
||||
/// case), consulted before every re-derive. The FIRST `DeriveWindow` on a
|
||||
/// corrected 2026-07-21 per PR #187 review — Tyre C1; RE-CORRECTED
|
||||
/// 2026-07-25 per PR #200 review — Hoshe finding 1, T-1184's hydrology
|
||||
/// field addition):** `BodyWorldState` does NOT retain `TerrainAnalysis`
|
||||
/// after cascade completion (T-1044 scoped its transient-carry fix to
|
||||
/// *within-cascade* reuse only — `cascade.rs` drops it once
|
||||
/// `DistrictProfile`+`RoadGraph` finish; see the doc on
|
||||
/// `CascadeSnapshot::terrain_analysis`). Caching it alongside every
|
||||
/// `BodyWorldStateCache` entry would cost **~2.62 MB × 50-body capacity ≈
|
||||
/// 131 MB** of PERMANENT resident cost (real per-entry figure below —
|
||||
/// this multiplier is unchanged, only the per-entry base moved), paid by
|
||||
/// every cached body whether or not a window is ever requested for it —
|
||||
/// the exact D-203 budget concern T-1044's own ticket text guarded
|
||||
/// against. So `run_work_item` re-derives via `run_layer1` (matching
|
||||
/// `aliveness_probe`'s existing `--render` workaround) rather than
|
||||
/// persisting a field on `BodyWorldState` — but NOT unconditionally on
|
||||
/// every work item: the actual model is a small **per-body LRU**
|
||||
/// (`TerrainAnalysisCache`, capacity 8), consulted before every
|
||||
/// re-derive.
|
||||
///
|
||||
/// **Real per-entry size (T-1184, corrected from the stale "~2 MB"
|
||||
/// figure the PR #187 pass computed before hydrology existed):** at the
|
||||
/// real `GRID_W × GRID_H = 512 × 256` working grid (131,072 cells, the
|
||||
/// SAME grid every `TerrainAnalysisCache` entry is built at — NOT the
|
||||
/// 1024×512 stored-heightmap-PNG resolution, D-202, which is downsampled
|
||||
/// away before `run_layer1` ever runs) — pre-T-1184 dense fields
|
||||
/// (`ocean_mask`/`lake_mask`: `Vec<bool>`, 1 B/cell each; `water_dist`:
|
||||
/// `Vec<u16>`, 2 B/cell; `slope_deg`/`elev_pct`: `Vec<f32>`, 4 B/cell
|
||||
/// each) sum to **~1.57 MB**. T-1184's `HydrologySample` (`elevation` +
|
||||
/// `filled`, both `Vec<f32>`, 4 B/cell) adds **~1.05 MB** — **not quite a
|
||||
/// doubling** (×1.67, not ×2), but a real, non-trivial per-entry growth:
|
||||
/// **post-T-1184 total ~2.62 MB/entry.** At capacity 8 that is **~21.0 MB
|
||||
/// worst case** (was ~12.6 MB pre-T-1184; the old "~16 MB" comment was
|
||||
/// itself a round-up of the pre-T-1184 figure, not a post-hydrology one).
|
||||
/// Both this cache and `BodyWorldStateCache`'s counterfactual above use
|
||||
/// the corrected ~2.62 MB base.
|
||||
///
|
||||
/// **Clone-on-HIT, not just insert (Hoshe finding 1, binding for the next
|
||||
/// sizing decision):** `get_or_derive`'s cache-hit branch
|
||||
/// (`return (l1.clone(), ta.clone())`) deep-copies the FULL
|
||||
/// `TerrainAnalysis` — including both new `HydrologySample` `Vec<f32>`
|
||||
/// fields — on EVERY hit, not merely on the first miss/insert. A
|
||||
/// pan-heavy session hitting the same body's cache entry repeatedly pays
|
||||
/// the ~2.62 MB clone cost per `DeriveWindow` work item, not once per
|
||||
/// body. This was already true pre-T-1184 for the smaller ~1.57 MB
|
||||
/// struct; T-1184 makes the per-hit clone cost ~1.67× larger, not a new
|
||||
/// category of cost. **Not fixed this round** (an `Arc<TerrainAnalysis>`
|
||||
/// refactor — sharing one heap allocation across hits instead of
|
||||
/// deep-copying — is the obvious next step if hit-heavy clone cost ever
|
||||
/// shows up in a profile, but is out of scope for T-1184; flagged for a
|
||||
/// follow-up ticket rather than done speculatively here).
|
||||
///
|
||||
/// The FIRST `DeriveWindow` on a
|
||||
/// body pays the full ~45 ms `run_layer1` cost and populates that body's
|
||||
/// cache entry; EVERY SUBSEQUENT window on the SAME body (any `center`/`n`,
|
||||
/// not just an exact repeat — that narrower case is what
|
||||
|
||||
Reference in New Issue
Block a user