fix(simulation): orbital rung applies coast_warp_px — one coastline at every rung (T-1160)

The Global/Region rungs sampled the heightmap ocean mask raw while every
finer rung samples it at the coast-warped position — a structurally
different coastline at the orbital-to-district seam. Audited on all 267
real body heightmaps at region spacing, coastal band only: 5.01%
land/ocean classification disagreement (2,911 of 58,073 cells), mean
displacement ~6.0 km, max ~18.9 km. Fork taken: APPLY the warp —
derive_orbital_at_metres now runs invent_primitives' steps 1-3 (driver
climate -> coast character -> coast_warp_px) before sampling, still
skipping detail-scatter (its octave ceiling, 32.8 km, is below region
spacing; the WARP's octaves reach 262 km, which is why skipping it was
wrong). Cost measured: ~784 ns/cell added (~15 ms on an Earth-class
Global canvas; low hundreds of ms parallel at the 8.3M-cell ceiling).
The ignored audit test preserves the pre-fix baseline for the record.
Golden re-pinned — region rows only, district/quarter byte-identical.
project.yaml 0.4.2 -> 0.4.3: orbital canvas bytes changed, client disk
caches must miss. Awaiting Araminta's review-seat sign-off per ticket.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 16:09:46 +02:00
co-authored by Claude Fable 5
parent 0d8984fe53
commit befdb689c1
3 changed files with 355 additions and 44 deletions
+5 -1
View File
@@ -4,7 +4,11 @@ name: The Settled Reach
# 0.4.1-tagged step canvases carried lake_margin_q's flat absolute-ceiling
# semantics (pre-per-basin-normalization) and were written to real disk
# caches during T-1188 eyeball runs — 0.4.2 forces those entries to miss.
version: 0.4.2
# 0.4.2-tagged Global/Region-rung step canvases sampled the coastline
# position raw (no coast_warp_px) — T-1160 applies the warp at orbital
# sampling, changing orbital-rung canvas bytes (elev_q/temperature_dc/
# moisture_q shift near coasts) — 0.4.3 forces those cached entries to miss.
version: 0.4.3
repository: settled-reach
+338 -31
View File
@@ -1928,26 +1928,43 @@ fn derive_at_metres_with_riparian(
}
/// The orbital-rung derivation (T-1152, zoom ladder design doc §2/§4): the
/// coarse-granularity twin of [`derive_at_metres`] that skips [`invent_primitives`]
/// entirely — **no coastline warp, no detail-scatter octave sum, no classification
/// noise call of any kind**. Per the design doc's orbital row: "`region_baseline_at_district`
/// only — bilinear blend of 4 region baselines, no `invent_primitives`, 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 terrain has nothing left to
/// contribute at this spacing; calling it would burn cycles synthesizing detail
/// no orbital pixel can resolve. What DOES vary at orbital spacing is the
/// **envelope** the heightmap itself carries (the `TerrainAnalysis` continental
/// shape) and the **region climate baseline** — this function samples exactly
/// those two, nothing else.
/// 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
/// 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
/// (262 km) — coarser than a region — so the warp is NOT below this rung's
/// resolution floor, and (T-1160 audit) skipping it produced a structurally
/// different coastline position than every finer rung (5.0% land/ocean
/// classification disagreement at coastal-band cells, 267 real bodies; mean
/// displacement ~6.0 km / max ~18.9 km at region spacing — see
/// `derive_orbital_coastline_divergence_audit`). What varies at orbital
/// spacing is therefore the **envelope** the heightmap carries (the
/// `TerrainAnalysis` continental shape), sampled at the **coast-warped
/// position**, plus the **region climate baseline** — this function samples
/// exactly those, nothing else.
///
/// **Cost model (design doc R1 — measure first):** one `bilinear` (elevation),
/// one `bilinear_bool` (ocean mask), one `region_baseline_at_district` call (its
/// own cost is 4×`derive_region_baseline_c` on a cache miss, O(1) on a cache hit)
/// — no octave sum, no coast-warp trig, no character/envelope computation. See
/// `server/tests/zoom_ladder_bench.rs`'s `bench_derive_orbital_at_metres` for the
/// measured per-cell figure this claim rests on.
/// **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.
///
/// Produces the SAME six-field tail every other rung produces (`morphology_zone`,
/// `elev_q`, `temperature_c`, `moisture_q`, `vegetation_class`, `glaciation_grade`)
@@ -2011,12 +2028,56 @@ pub fn derive_orbital_at_metres(
..body_params.clone()
};
// The envelope only — no coast-warp, no detail-scatter. This is exactly
// `invent_primitives`' step-1 "driver tier" raw bilinear reads, promoted to
// be the FINAL primitives instead of a one-step-stale input to invention.
let elev_q =
// 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 ocean_fraction_q = ((bilinear_bool(&ta.ocean_mask, ta.w, ta.h, px, py) as f64 * 100.0)
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
@@ -3384,13 +3445,14 @@ mod tests {
assert_district_profiles_eq(&a, &b);
}
/// The orbital path must NOT run `invent_primitives` — the design doc's
/// central constraint (§2: "no invent_primitives at orbital wavelengths").
/// Direct proof: `slope_q` is always exactly 0 (invention is the only
/// source of nonzero slope_q at this call depth — see
/// `derive_orbital_at_metres`'s doc on why slope_q is fixed), sampled
/// across enough distinct positions that a nonzero value appearing even
/// once would falsify the claim.
/// The orbital path must NOT run `invent_primitives`'s detail-scatter/
/// relief step (step 4) — the design doc's central constraint (§2: "no
/// [relief] invention at orbital wavelengths"), even though it DOES now
/// run steps 1-3 (coast-warp, T-1160). Direct proof: `slope_q` is always
/// exactly 0 (detail-scatter is the only source of nonzero slope_q at
/// this call depth — see `derive_orbital_at_metres`'s doc on why slope_q
/// is fixed), sampled across enough distinct positions that a nonzero
/// value appearing even once would falsify the claim.
#[test]
fn derive_orbital_at_metres_never_invents_slope() {
let hm = test_hm();
@@ -3505,6 +3567,251 @@ mod tests {
let _ = prof.glaciation_grade;
}
/// T-1160 audit (historical record — the fix landed; this test documents
/// the numbers that justified it and stays as a standing measurement, not
/// a regression gate with a hardcoded threshold). Quantifies the
/// 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:
/// `cargo test --release -p settled-reach-server derive_orbital_coastline_divergence_audit -- --ignored --nocapture`
#[test]
#[ignore]
fn derive_orbital_coastline_divergence_audit() {
use std::path::Path;
use std::time::Instant;
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../wiki/star-systems");
let mut heightmaps: Vec<std::path::PathBuf> = Vec::new();
collect_heightmaps(&root, &mut heightmaps);
heightmaps.sort();
assert!(
!heightmaps.is_empty(),
"expected to find real heightmap.png files under {:?}",
root
);
let climate = ClimateConstants::default();
let seed = test_seed();
let region_m = scale::REGION_M as f64;
let mut bodies_sampled = 0usize;
let mut total_cells = 0u64;
let mut disagreements = 0u64;
let mut max_displacement_m = 0.0f64;
let mut sum_displacement_m = 0.0f64;
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 {
let Ok(file) = std::fs::File::open(hm_path) else {
continue;
};
let Ok(hm) = crate::atlas::heightmap::load_heightmap_reader(file, "audit", 0.4) else {
continue;
};
if hm.width == 0 || hm.height == 0 {
continue;
}
let dr = drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level);
let ta = TerrainAnalysis::analyze(&hm, &dr);
let body_params = BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
};
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);
// Sample on a region-spacing grid across the whole body — same
// spacing the orbital/Region rung actually renders at.
let cols = (circumference_m / region_m).floor() as i64;
let rows = (meridian_m / region_m).floor() as i64;
if cols < 1 || rows < 1 {
continue;
}
bodies_sampled += 1;
for row in 0..rows {
for col in 0..cols {
let wx = col as f64 * region_m;
let wy = row as f64 * region_m;
let px = (wx / circumference_m).rem_euclid(1.0) * ta.w as f64;
let lat_frac = (wy / meridian_m).clamp(-0.5, 0.5);
let py = (0.5 + lat_frac) * ta.h.saturating_sub(1) as f64;
let lat_deg = -lat_frac * 180.0;
// (a) raw orbital read — today's derive_orbital_at_metres.
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);
// Only worth measuring displacement near a coastline — a
// cell deep in open ocean or deep inland can't flip
// classification no matter the (sub-pixel) warp, so
// restrict the expensive per-cell warp eval + metre
// conversion to the coastal band (0 < raw_ocean_q < 100
// OR any 4-neighbor disagrees) — this is also exactly
// the "coastal cell" population the ticket's cost
// estimate ("~one warp eval per coastal cell") means.
let is_coastal_band = {
let n = bilinear_bool(&ta.ocean_mask, ta.w, ta.h, px, py - 1.0) > 0.5;
let s = bilinear_bool(&ta.ocean_mask, ta.w, ta.h, px, py + 1.0) > 0.5;
let e = bilinear_bool(&ta.ocean_mask, ta.w, ta.h, px + 1.0, py) > 0.5;
let w = bilinear_bool(&ta.ocean_mask, ta.w, ta.h, px - 1.0, py) > 0.5;
let here = raw_ocean_q > 50;
here != n || here != s || here != e || here != w
};
if !is_coastal_band {
continue;
}
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
// 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,
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,
seed.seed(),
wx,
wy,
lat_deg,
driver_glaciation,
driver_moisture,
);
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 warped_ocean_q = ((bilinear_bool(&ta.ocean_mask, ta.w, ta.h, spx, spy)
as f64
* 100.0)
.round() as i32)
.clamp(0, 100);
if (raw_ocean_q > 50) != (warped_ocean_q > 50) {
disagreements += 1;
}
// Displacement converted to real metres at this body's
// equator-circumference-derived px scale (constant across
// latitude for an equirectangular map's column spacing;
// using the equatorial scale is the conservative /
// largest-metres-per-pixel case for row spacing too since
// meridian_m/ta.h <= circumference_m/ta.w for ta.h<=ta.w/2).
let m_per_px = circumference_m / ta.w as f64;
let disp_px = (wdx * wdx + wdy * wdy).sqrt();
let disp_m = disp_px * m_per_px;
sum_displacement_m += disp_m;
displacement_samples += 1;
if disp_m > max_displacement_m {
max_displacement_m = disp_m;
}
}
}
}
let mean_displacement_m = if displacement_samples > 0 {
sum_displacement_m / displacement_samples as f64
} else {
0.0
};
let disagreement_pct = if total_cells > 0 {
100.0 * disagreements as f64 / total_cells as f64
} 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 {
0.0
};
eprintln!("=== T-1160 orbital coastline divergence audit ===");
eprintln!("bodies sampled: {bodies_sampled}");
eprintln!("coastal-band cells sampled: {total_cells}");
eprintln!("land/ocean disagreements: {disagreements} ({disagreement_pct:.2}%)");
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)"
);
// Sanity floor — this audit is meaningless if it silently sampled
// zero real bodies or zero coastal cells (e.g. a bad path).
assert!(bodies_sampled > 10, "too few bodies sampled — check path");
assert!(total_cells > 0, "no coastal-band cells found — check masks");
}
fn collect_heightmaps(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect_heightmaps(&path, out);
} else if path.file_name().and_then(|n| n.to_str()) == Some("heightmap.png") {
out.push(path);
}
}
}
#[test]
fn derive_district_profile_is_deterministic() {
let hm = test_hm();
@@ -95,10 +95,10 @@
"glaciation": 0,
"precipitation": 3,
"slope_q": 0,
"elev_q": 26,
"elev_q": 27,
"ocean_fraction_q": 0,
"temperature_dc": 127,
"moisture_q": 57,
"temperature_dc": 122,
"moisture_q": 56,
"vegetation": 3
},
{
@@ -248,7 +248,7 @@
"glaciation": 2,
"precipitation": 1,
"slope_q": 0,
"elev_q": 62,
"elev_q": 63,
"ocean_fraction_q": 0,
"temperature_dc": -120,
"moisture_q": 43,
@@ -452,7 +452,7 @@
"glaciation": 0,
"precipitation": 0,
"slope_q": 0,
"elev_q": 41,
"elev_q": 42,
"ocean_fraction_q": 0,
"temperature_dc": -2147483648,
"moisture_q": 0,
@@ -503,7 +503,7 @@
"glaciation": 0,
"precipitation": 0,
"slope_q": 0,
"elev_q": 42,
"elev_q": 43,
"ocean_fraction_q": 0,
"temperature_dc": -2147483648,
"moisture_q": 0,
@@ -707,9 +707,9 @@
"glaciation": 0,
"precipitation": 3,
"slope_q": 0,
"elev_q": 28,
"elev_q": 29,
"ocean_fraction_q": 0,
"temperature_dc": 728,
"temperature_dc": 723,
"moisture_q": 59,
"vegetation": 3
},
@@ -809,9 +809,9 @@
"glaciation": 0,
"precipitation": 2,
"slope_q": 0,
"elev_q": 23,
"elev_q": 24,
"ocean_fraction_q": 0,
"temperature_dc": 697,
"temperature_dc": 691,
"moisture_q": 49,
"vegetation": 3
},
@@ -860,10 +860,10 @@
"glaciation": 0,
"precipitation": 2,
"slope_q": 0,
"elev_q": 66,
"elev_q": 67,
"ocean_fraction_q": 0,
"temperature_dc": 300,
"moisture_q": 30,
"moisture_q": 29,
"vegetation": 2
},
{