From 077d787c4a2ead857799b4d9c824993f2acfb1bd Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 25 Jul 2026 17:22:16 +0200 Subject: [PATCH] =?UTF-8?q?fix(simulation):=20one=20shared=20invent=5Fcoas?= =?UTF-8?q?tal=5Fposition=20=E2=80=94=20PR=20#208=20review=20round=20(T-11?= =?UTF-8?q?60)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoshe found the concrete residual: the hand-copied driver block called derive_temperature_c unconditionally where invent_primitives prefers the lapse-adjusted region baseline on non-airless bodies — driver_temp feeds glaciation/moisture into the warp magnitude, so orbital displacement could still differ from District at the same position (the ticket's defect class, one step upstream); the copy structurally couldn't branch right because region_baseline_at_district was computed after it. Tyre demanded the structural cure: steps 1-3 now live in ONE shared helper (invent_coastal_position -> warped position + CoastCharacter); invent_primitives composes helper + detail-scatter; the orbital path hoists the baseline and makes a single helper call. The audit test's own 'District-style' side turned out to be a THIRD copy carrying the same bug — rewired to the real helper (raw pre-fix side untouched as the historical baseline; refreshed: 5.01% disagreement, mean 5,957.9 m, 748.9 ns/cell). Golden verified byte-identical under forced regen, with the reason traced: the wire temperature never flowed through the buggy internal branch, and the fixture's probe positions cross no discrete boundary — the 267-body audit is the instrument that sees the drift. Co-Authored-By: Claude Fable 5 --- server/src/atlas/district_profile.rs | 374 +++++++++++++++++---------- 1 file changed, 231 insertions(+), 143 deletions(-) diff --git a/server/src/atlas/district_profile.rs b/server/src/atlas/district_profile.rs index 088db9380..d10c492d8 100644 --- a/server/src/atlas/district_profile.rs +++ b/server/src/atlas/district_profile.rs @@ -1148,10 +1148,14 @@ struct InventedPrimitives { ocean_fraction_q: i32, } -/// Compose the invented district primitives at one position — the shared front -/// half of BOTH derivation paths ([`derive_district`] on-demand and -/// [`derive_district_profile`] batch), so the D-227 invention can never diverge -/// between them again (the pre-T-1125 failure: the batch path was scatter-free). +/// Compose steps 1-3 of the invented-coastline pipeline — the driver-tier +/// climate estimate, the body/position coast character, and the warped +/// sampling position those two produce. **The single shared implementation** +/// (T-1160 PR #208 review, hoshe + tyre): every caller that needs the +/// invented coastline's WARPED POSITION (never a second hand-synced copy of +/// this branch) goes through here — [`invent_primitives`] (steps 1-3, then +/// composes step 4 on top) and [`derive_orbital_at_metres`] (steps 1-3 only, +/// no step 4). /// /// Steps: /// 1. **Driver tier (one-step-stale, T-1125 circularity ruling):** the raw @@ -1160,23 +1164,43 @@ struct InventedPrimitives { /// these stale drivers; it never reads the warped values it produces. The /// raw inputs are the coarse ~40–160 km/pixel envelope anyway, so one step /// of staleness is far below the signal's own resolution. +/// +/// **Region-baseline branch (T-1160 PR #208 fix):** `driver_temp` prefers +/// the lapse-adjusted [`derive_district_temperature_c`] whenever +/// `region_baseline_c` is `Some` — the SAME branch [`derive_district_profile`]'s +/// batch path and [`derive_at_metres`]'s on-demand path have always used — +/// falling back to the body-level [`derive_temperature_c`] only for airless +/// bodies (`region_baseline_c: None`, no region climate to lapse-adjust). +/// Before this fix, a hand-copied version of this block inside +/// `derive_orbital_at_metres` called `derive_temperature_c` +/// UNCONDITIONALLY — wrong on every non-airless body (the entire coastline +/// population), silently producing a different `driver_temp` → +/// `driver_moisture` → `driver_glaciation` (a discrete band) → +/// `coast_character_at` → warp displacement than District/Quarter at the +/// identical position, reintroducing the exact T-1160 defect class one +/// step upstream of the fix that closed it for the sampling position +/// itself. /// 2. **Character:** tier-1 body envelope ([`coast_invention::body_coast_envelope`]; /// `derive_tectonic_class` hoisted here — it needs only `BodyParams`) + /// tier-2 position character ([`coast_invention::coast_character_at`]). -/// 3. **Invented coastline:** every envelope field (elevation, slope, ocean -/// mask) is sampled at the SAME warped position, so terrain moves coherently -/// — a warped-in bay carries its sea-level elevation with it. -/// 4. **Slope-independent scatter:** the character's `scatter_floor` breaks the -/// old amplitude-slaved-to-coarse-slope collapse (invention ≈ 0 exactly on -/// low-relief coasts); `ridge` carries fjord/tectonic sharpness. +/// 3. **Invented coastline:** the warped `(spx, spy)` position — every +/// envelope field (elevation, slope, ocean mask) a caller samples at this +/// position moves coherently — a warped-in bay carries its sea-level +/// elevation with it. /// /// `body_params` must already carry the district's `latitude_deg`. /// /// `min_wavelength_m` (T-1149, zoom ladder §2): threaded straight to the -/// `terrain_detail` scatter call — octaves finer than this are truncated. -/// `0.0` = no cutoff = today's behavior. +/// coast-warp call — octaves finer than this are truncated. `0.0` = no +/// cutoff = today's behavior. +/// +/// Returns `(spx, spy, ch)`: the warped position, plus the resolved +/// [`coast_invention::CoastCharacter`] — [`invent_primitives`]' step 4 +/// (slope-independent scatter) reads `ch.scatter_floor`/`ch.ridge`, so the +/// character has to come back out rather than be recomputed a second time. +/// [`derive_orbital_at_metres`] (steps 1-3 only) uses just the position. #[allow(clippy::too_many_arguments)] -fn invent_primitives( +fn invent_coastal_position( seed: SeedChain, body_params: &BodyParams, climate: &ClimateConstants, @@ -1187,7 +1211,7 @@ fn invent_primitives( world_y_m: f64, region_baseline_c: Option, min_wavelength_m: f64, -) -> InventedPrimitives { +) -> (f64, f64, coast_invention::CoastCharacter) { // ── 1. Driver tier: UNWARPED raw-bilinear climate (one-step-stale). ───── let raw_elev_q = ((bilinear(&ta.elev_pct, ta.w, ta.h, px, py) as f64 * 100.0).round() as i32).clamp(0, 100); @@ -1220,11 +1244,58 @@ fn invent_primitives( // ── 3. Invented coastline: warp the whole envelope sampling. ──────────── // T-1162: the coast warp gets the SAME min_wavelength_m cutoff as the - // terrain-detail scatter below — one rung, one cutoff, applied to both - // continuous fields that compose the invented coastline. + // terrain-detail scatter (invent_primitives' step 4) — one rung, one + // cutoff, applied to both continuous fields that compose the invented + // coastline. let (wdx, wdy) = coast_invention::coast_warp_px(seed.seed(), world_x_m, world_y_m, &ch, min_wavelength_m); - let (spx, spy) = (px + wdx, py + wdy); + (px + wdx, py + wdy, ch) +} + +/// Compose the invented district primitives at one position — the shared front +/// half of BOTH derivation paths ([`derive_district`] on-demand and +/// [`derive_district_profile`] batch), so the D-227 invention can never diverge +/// between them again (the pre-T-1125 failure: the batch path was scatter-free). +/// +/// Steps 1-3 (driver tier, character, invented warped position) are +/// [`invent_coastal_position`] — this function calls it, samples the +/// envelope at the returned position, then composes step 4 (slope-independent +/// scatter) on top: +/// 4. **Slope-independent scatter:** the character's `scatter_floor` breaks the +/// old amplitude-slaved-to-coarse-slope collapse (invention ≈ 0 exactly on +/// low-relief coasts); `ridge` carries fjord/tectonic sharpness. +/// +/// `body_params` must already carry the district's `latitude_deg`. +/// +/// `min_wavelength_m` (T-1149, zoom ladder §2): threaded straight to the +/// `terrain_detail` scatter call — octaves finer than this are truncated. +/// `0.0` = no cutoff = today's behavior. +#[allow(clippy::too_many_arguments)] +fn invent_primitives( + seed: SeedChain, + body_params: &BodyParams, + climate: &ClimateConstants, + ta: &TerrainAnalysis, + px: f64, + py: f64, + world_x_m: f64, + world_y_m: f64, + region_baseline_c: Option, + min_wavelength_m: f64, +) -> InventedPrimitives { + // ── Steps 1-3: driver tier, character, invented warped position. ──────── + let (spx, spy, ch) = invent_coastal_position( + seed, + body_params, + climate, + ta, + px, + py, + world_x_m, + world_y_m, + region_baseline_c, + min_wavelength_m, + ); let elev_pct = bilinear(&ta.elev_pct, ta.w, ta.h, spx, spy) as f64; let slope_deg = bilinear(&ta.slope_deg, ta.w, ta.h, spx, spy) as f64; let ocean_frac = bilinear_bool(&ta.ocean_mask, ta.w, ta.h, spx, spy) as f64; @@ -1931,12 +2002,16 @@ fn derive_at_metres_with_riparian( /// coarse-granularity twin of [`derive_at_metres`] that skips MOST of /// [`invent_primitives`] — **no detail-scatter octave sum, no classification /// noise call of any kind** — but DOES apply the coast-warp position shift -/// (T-1160). Per the design doc's orbital row: "`region_baseline_at_district` -/// only — bilinear blend of 4 region baselines, no [detail-scatter] -/// invention, no classification [driver]." Orbital sample spacing (≥205 km, -/// D-243's region rung and coarser) sits below `detail_scatter`'s own octave -/// floor (`OCTAVE_WAVELENGTHS_M`'s coarsest entry is 32,768 m ≈ 32.8 km — an -/// order of magnitude finer than a region), so the invented RELIEF texture has +/// (T-1160) via [`invent_coastal_position`], the SAME shared steps 1-3 +/// helper [`invent_primitives`] composes on top of (never a hand-copied +/// replica of that logic — see the helper's own doc for why that class of +/// bug is now structurally closed, not just fixed once). Per the design +/// doc's orbital row: "`region_baseline_at_district` only — bilinear blend +/// of 4 region baselines, no [detail-scatter] invention, no classification +/// [driver]." Orbital sample spacing (≥205 km, D-243's region rung and +/// coarser) sits below `detail_scatter`'s own octave floor +/// (`OCTAVE_WAVELENGTHS_M`'s coarsest entry is 32,768 m ≈ 32.8 km — an order +/// of magnitude finer than a region), so the invented RELIEF texture has /// nothing left to contribute at this spacing; running it would burn cycles /// synthesizing detail no orbital pixel can resolve. The coastline WARP is /// different: `WARP_OCTAVE_WAVELENGTHS_M`'s coarsest entries run up to 262,144 m @@ -1951,20 +2026,30 @@ fn derive_at_metres_with_riparian( /// position**, plus the **region climate baseline** — this function samples /// exactly those, nothing else. /// +/// **Ordering (T-1160 PR #208 fix):** `region_baseline_at_district` is +/// called BEFORE [`invent_coastal_position`], not after — the helper's +/// driver-tier branch (step 1) needs the resolved baseline to pick the SAME +/// region-baseline-preferring formula `invent_primitives`/`derive_at_metres` +/// use. A structurally-forced-wrong hand-copy of the driver tier (computed +/// before the baseline existed in scope) is exactly the bug PR #208 review +/// caught: it could not branch correctly no matter how carefully it was +/// hand-synced, because the value it needed to branch on didn't exist yet. +/// /// **Cost model (T-1160 audit, measured on 267 real bodies, release build):** /// the added driver-tier climate + `coast_character_at` + `coast_warp_px` work -/// costs ~784 ns/cell (`coast_warp_px` alone: ~528 ns/cell) — roughly half of -/// what `invent_primitives`' FULL pipeline costs on top of this function's own -/// ~903 ns/cell baseline (`bench_derive_orbital_at_metres_region_spacing`: -/// orbital 902.6 ns/cell vs. full `derive_at_metres` 1940.6 ns/cell at the same -/// spacing — a 2.15x gap, of which detail-scatter/classification-noise is the -/// remainder this function still skips). At the D-255(a) fixed-rung ceiling -/// (3,840×2,160 = 8.3M cells) the added cost is ~6.5 s single-threaded / low -/// hundreds of ms on the row-chunked parallel serving path (T-1151); at a -/// realistic Global-rung Earth-sized-body canvas (195×97 ≈ 18.9K cells) it is -/// ~15 ms. Accepted: coastline correctness at the orbital/Region seam is worth -/// a sub-millisecond-to-low-second cost that scales with (and is bounded by) -/// the same canvas-size ceiling every other rung's cost already respects. +/// (now inside [`invent_coastal_position`]) costs ~784 ns/cell (`coast_warp_px` +/// alone: ~528 ns/cell) — roughly half of what `invent_primitives`' FULL +/// pipeline costs on top of this function's own ~903 ns/cell baseline +/// (`bench_derive_orbital_at_metres_region_spacing`: orbital 902.6 ns/cell vs. +/// full `derive_at_metres` 1940.6 ns/cell at the same spacing — a 2.15x gap, of +/// which detail-scatter/classification-noise is the remainder this function +/// still skips). At the D-255(a) fixed-rung ceiling (3,840×2,160 = 8.3M cells) +/// the added cost is ~6.5 s single-threaded / low hundreds of ms on the +/// row-chunked parallel serving path (T-1151); at a realistic Global-rung +/// Earth-sized-body canvas (195×97 ≈ 18.9K cells) it is ~15 ms. Accepted: +/// coastline correctness at the orbital/Region seam is worth a +/// sub-millisecond-to-low-second cost that scales with (and is bounded by) the +/// same canvas-size ceiling every other rung's cost already respects. /// /// Produces the SAME six-field tail every other rung produces (`morphology_zone`, /// `elev_q`, `temperature_c`, `moisture_q`, `vegetation_class`, `glaciation_grade`) @@ -2028,61 +2113,14 @@ pub fn derive_orbital_at_metres( ..body_params.clone() }; - // T-1160: coast-warp the sampling POSITION (steps 1-3 of - // `invent_primitives`), but skip step 4 (slope-independent detail-scatter) - // — the envelope carries no per-cell slope signal at orbital spacing (see - // the function doc's note on `slope_q`, unchanged). Before this fix, this - // function sampled `ta.elev_pct`/`ta.ocean_mask` RAW while District/Quarter - // sampled the SAME arrays at the coast-warped position — a structurally - // different coastline at the orbital-to-district seam (audit: 5.0% - // land/ocean classification disagreement at coastal-band cells across 267 - // real bodies, mean displacement ~6.0 km / max ~18.9 km at region - // spacing). Applying the warp here makes the orbital rung sample the SAME - // invented coastline every finer rung already draws — coarser density, - // same line, not a different line (T-1160). - // - // Driver tier: raw-bilinear reads feed the coast character exactly as - // `invent_primitives` step 1 does (one-step-stale climate driving the - // coast personality, never circular on its own warped output). - let raw_elev_q = - ((bilinear(&ta.elev_pct, ta.w, ta.h, px, py) as f64 * 100.0).round() as i32).clamp(0, 100); - let raw_ocean_q = ((bilinear_bool(&ta.ocean_mask, ta.w, ta.h, px, py) as f64 * 100.0).round() - as i32) - .clamp(0, 100); - let driver_params = BodyParams { - elevation_km: (raw_elev_q as f64 / 100.0) * MAX_REGION_ELEVATION_KM, - ..params.clone() - }; - let driver_temp = derive_temperature_c(&driver_params, climate, seed.seed()); - let driver_moisture = derive_moisture_q(&driver_params, raw_elev_q, raw_ocean_q, climate); - let driver_glaciation = derive_glaciation_grade_from_climate(driver_temp, driver_moisture); - - // Character + invented coastline (steps 2-3): same body-personality - // envelope + position character + warp displacement District/Quarter use, - // applied to the SAME `(px, py)` this function already resolved. - let tectonic = derive_tectonic_class(body_params); - let envelope = coast_invention::body_coast_envelope(body_params, tectonic); - let ch = coast_invention::coast_character_at( - &envelope, - seed.seed(), - world_x_m, - world_y_m, - lat_deg, - driver_glaciation, - driver_moisture, - ); - let (wdx, wdy) = coast_invention::coast_warp_px(seed.seed(), world_x_m, world_y_m, &ch, 0.0); - let (spx, spy) = (px + wdx, py + wdy); - - let elev_q = ((bilinear(&ta.elev_pct, ta.w, ta.h, spx, spy) as f64 * 100.0).round() as i32) - .clamp(0, 100); - let ocean_fraction_q = ((bilinear_bool(&ta.ocean_mask, ta.w, ta.h, spx, spy) as f64 * 100.0) - .round() as i32) - .clamp(0, 100); - // No invented ruggedness at orbital spacing (see the function doc's note - // on slope_q) — the envelope carries no per-cell slope signal this coarse. - let slope_q = 0; - + // T-1160 PR #208 fix: `region_baseline_at_district` is hoisted ABOVE the + // coastal-position work below — the shared helper's driver tier (step 1) + // needs it to pick the SAME region-baseline-preferring branch + // `invent_primitives`/`derive_at_metres` use. The pre-fix structure + // computed this AFTER a hand-copied driver-tier block, which is exactly + // why that copy could not branch correctly and silently called the + // airless-only `derive_temperature_c` unconditionally (see + // `invent_coastal_position`'s doc for the full defect story). let district_pos: DistrictPos = ( (wx / scale::DISTRICT_M as f64).floor() as i32, (wy / scale::DISTRICT_M as f64).floor() as i32, @@ -2097,6 +2135,41 @@ pub fn derive_orbital_at_metres( None, // no pre-built cache; derive on-the-fly, same posture as derive_at_metres ); + // T-1160: coast-warp the sampling POSITION via the SAME shared helper + // (`invent_coastal_position`, steps 1-3 of `invent_primitives`) District/ + // Quarter use — step 4 (slope-independent detail-scatter) is skipped + // (the envelope carries no per-cell slope signal at orbital spacing, see + // the function doc's note on `slope_q`). Before T-1160, this function + // sampled `ta.elev_pct`/`ta.ocean_mask` RAW while District/Quarter + // sampled the SAME arrays at the coast-warped position — a structurally + // different coastline at the orbital-to-district seam (audit: 5.0% + // land/ocean classification disagreement at coastal-band cells across 267 + // real bodies, mean displacement ~6.0 km / max ~18.9 km at region + // spacing). Routing through the ONE shared helper (rather than a + // hand-copied replica) makes the region-baseline branch structurally + // impossible to diverge again. + let (spx, spy, _ch) = invent_coastal_position( + seed, + ¶ms, + climate, + ta, + px, + py, + world_x_m, + world_y_m, + region_baseline_c, + 0.0, + ); + + let elev_q = ((bilinear(&ta.elev_pct, ta.w, ta.h, spx, spy) as f64 * 100.0).round() as i32) + .clamp(0, 100); + let ocean_fraction_q = ((bilinear_bool(&ta.ocean_mask, ta.w, ta.h, spx, spy) as f64 * 100.0) + .round() as i32) + .clamp(0, 100); + // No invented ruggedness at orbital spacing (see the function doc's note + // on slope_q) — the envelope carries no per-cell slope signal this coarse. + let slope_q = 0; + // T-1184: same continuous filled-surface comparison every rung samples, // at the orbital rung's own (px, py) — lake edges refine at Region // spacing exactly as they do at every finer rung (D-227 amendment (4)). @@ -3572,19 +3645,34 @@ mod tests { /// orbital-vs-district coastline positional divergence that existed /// BEFORE this ticket: `derive_orbital_at_metres` used to sample /// `ta.ocean_mask` raw (no `coast_warp_px`) while District/Quarter warped - /// the same mask first (`invent_primitives` step 3). This test - /// independently reimplements both the pre-fix raw read (a) and the - /// District-style warped read (b) — deliberately NOT calling - /// `derive_orbital_at_metres` itself, so the measurement stays valid as a - /// historical baseline regardless of that function's current - /// implementation — loads every real `heightmap.png` under - /// `wiki/star-systems/`, and for a grid of region-spacing sample points on - /// each body reports how often (a) and (b) disagree on land/ocean - /// classification, the warp displacement converted to real metres, and - /// the per-cell cost of computing (b). These are the numbers T-1160's - /// fix (applying the warp at orbital sampling, see - /// `derive_orbital_at_metres`'s doc) is based on. `#[ignore]`d — reads - /// ~267 real wiki heightmap files, run explicitly: + /// the same mask first (`invent_primitives` step 3). + /// + /// Side (a), the pre-fix raw read, deliberately stays a hand-rolled + /// historical baseline (NOT a call into current production code) — it + /// exists to measure what the BUG used to do, so it must not silently + /// track whatever `derive_orbital_at_metres` does today. + /// + /// Side (b), the "District-style warped read," calls the REAL production + /// [`region_baseline_at_district`] + [`invent_coastal_position`] (PR #208 + /// review fix) rather than hand-copying their steps a third time — the + /// original version of this test independently reimplemented the + /// driver-tier branch inline (unconditional `derive_temperature_c`, + /// never the region-baseline-preferring `derive_district_temperature_c`), + /// which was itself a THIRD copy of the exact defect class T-1160 exists + /// to close, undetected because this test's own numbers still "looked + /// plausible" without ever exercising the region-baseline branch. Routing + /// through the shared helper means this test's post-fix side can never + /// drift from what `derive_orbital_at_metres`/`invent_primitives` + /// actually do. + /// + /// Loads every real `heightmap.png` under `wiki/star-systems/`, and for a + /// grid of region-spacing sample points on each body reports how often + /// (a) and (b) disagree on land/ocean classification, the warp + /// displacement converted to real metres, and the per-cell cost of + /// computing (b). These are the numbers T-1160's fix (applying the warp + /// at orbital sampling, see `derive_orbital_at_metres`'s doc) is based + /// on. `#[ignore]`d — reads ~267 real wiki heightmap files, run + /// explicitly: /// `cargo test --release -p settled-reach-server derive_orbital_coastline_divergence_audit -- --ignored --nocapture` #[test] #[ignore] @@ -3614,7 +3702,6 @@ mod tests { let mut displacement_samples = 0u64; let mut warp_evals = 0u64; - let mut warp_only_nanos = 0u64; let mut warp_plus_character_nanos = 0u64; for hm_path in &heightmaps { @@ -3639,9 +3726,7 @@ mod tests { }; let circumference_m = std::f64::consts::TAU * 6371.0 * 1000.0; let meridian_m = std::f64::consts::PI * 6371.0 * 1000.0; - - let tectonic = derive_tectonic_class(&body_params); - let envelope = coast_invention::body_coast_envelope(&body_params, tectonic); + let body_id = "audit_body"; // Sample on a region-spacing grid across the whole body — same // spacing the orbital/Region rung actually renders at. @@ -3692,43 +3777,54 @@ mod tests { total_cells += 1; // (b) District-style warped read at the SAME position — - // mirrors invent_primitives' driver-tier + character + - // warp steps, using the real per-body envelope. Timed + // calls the REAL production region_baseline_at_district + + // invent_coastal_position (PR #208 review fix) rather + // than hand-copying their steps a third time (see this + // test's own doc for why that was the bug). Timed // separately from the file-I/O/drainage/TerrainAnalysis // setup above — this is the actual per-cell cost T-1160 // would add at orbital sampling, not body-load overhead. - let cell_start = Instant::now(); - let raw_elev_q = ((bilinear(&ta.elev_pct, ta.w, ta.h, px, py) as f64 * 100.0) - .round() as i32) - .clamp(0, 100); - let driver_params = BodyParams { - elevation_km: (raw_elev_q as f64 / 100.0) * MAX_REGION_ELEVATION_KM, + let params = BodyParams { latitude_deg: lat_deg, ..body_params.clone() }; - let driver_temp = derive_temperature_c(&driver_params, &climate, seed.seed()); - let driver_moisture = - derive_moisture_q(&driver_params, raw_elev_q, raw_ocean_q, &climate); - let driver_glaciation = - derive_glaciation_grade_from_climate(driver_temp, driver_moisture); - - let ch = coast_invention::coast_character_at( - &envelope, + let district_pos: DistrictPos = ( + (wx / scale::DISTRICT_M as f64).floor() as i32, + (wy / scale::DISTRICT_M as f64).floor() as i32, + ); + let region_baseline_c = region_profile::region_baseline_at_district( seed.seed(), - wx, - wy, - lat_deg, - driver_glaciation, - driver_moisture, + body_id, + district_pos, + ¶ms, + &climate, + seed, + None, ); - warp_plus_character_nanos += cell_start.elapsed().as_nanos() as u64; warp_evals += 1; - let warp_call_start = Instant::now(); - let (wdx, wdy) = coast_invention::coast_warp_px(seed.seed(), wx, wy, &ch, 0.0); - warp_only_nanos += warp_call_start.elapsed().as_nanos() as u64; - warp_plus_character_nanos += warp_call_start.elapsed().as_nanos() as u64; - let (spx, spy) = (px + wdx, py + wdy); + let cell_start = Instant::now(); + let (spx, spy, _ch) = invent_coastal_position( + seed, + ¶ms, + &climate, + &ta, + px, + py, + wx, + wy, + region_baseline_c, + 0.0, + ); + // The helper is one atomic call now (steps 1-3 fused, PR + // #208 review fix) — a single timing captures the full + // per-cell added cost; there is no longer a meaningful + // way to isolate "just coast_warp_px" from "driver tier + + // character" as two separate measurements the way the + // pre-fix hand-inlined version allowed. + warp_plus_character_nanos += cell_start.elapsed().as_nanos() as u64; + let (wdx, wdy) = (spx - px, spy - py); + let warped_ocean_q = ((bilinear_bool(&ta.ocean_mask, ta.w, ta.h, spx, spy) as f64 * 100.0) .round() as i32) @@ -3765,11 +3861,6 @@ mod tests { } else { 0.0 }; - let ns_per_warp_call = if warp_evals > 0 { - warp_only_nanos as f64 / warp_evals as f64 - } else { - 0.0 - }; let ns_per_full_cell_cost = if warp_evals > 0 { warp_plus_character_nanos as f64 / warp_evals as f64 } else { @@ -3783,10 +3874,7 @@ mod tests { eprintln!("mean warp displacement: {mean_displacement_m:.1} m"); eprintln!("max warp displacement: {max_displacement_m:.1} m"); eprintln!( - "coast_warp_px call cost: {ns_per_warp_call:.1} ns/call ({warp_evals} calls, {warp_only_nanos} ns total)" - ); - eprintln!( - "full added cost/cell: {ns_per_full_cell_cost:.1} ns/cell (driver temp/moisture/glaciation + coast_character_at + coast_warp_px)" + "full added cost/cell: {ns_per_full_cell_cost:.1} ns/cell (invent_coastal_position: driver temp/moisture/glaciation + coast_character_at + coast_warp_px, one production call)" ); // Sanity floor — this audit is meaningless if it silently sampled