fix(simulation): one absolute-metre derive core (D-256, T-1174)

derive_district_profile is now a thin wrapper over the shared
derive_at_metres_with_riparian core at survey-cell-centre world metres
— one derive core, two position sets. The batch pseudo-grid and the
true D-243 district grid were two coordinate systems sharing one bare
(i32,i32) type; the new SurveyCellPos newtype re-keys every batch
product (BodyWorldState.districts, Layer1Output.survey_basin_dirs) so
the compiler rejects cross-namespace passing.

Fixes two latent same-position divergences the T-1174 investigation
surfaced: three inconsistent latitude conventions collapse into the
core's single inverse mapping, and the region-climate baseline now
floor-divides true world metres instead of collapsing the whole body
onto region (0,0)'s baseline — batch climate becomes latitude/region
graded (D-245 direction: every changed believability metric increased).

Binding preservations per D-256(c): basin_direction rides a post-call
override with the true L1 D8 survey-cell aggregate (layer1's map
re-keyed to SurveyCellPos, identity lookup — a floor-divide lookup
against the pseudo-keyed map would have silently defaulted every cell
North); the riparian verdict comes from near_perennial_water_at, never
the empty-slice default (which would have flipped riverside
vegetation_class).

Quarter-skeleton morphology_zone now resolves at the settlement's
exact world position via derive_at_metres at work-item execution
(where TerrainAnalysisCache lives), replacing the survey-cell-centre
map lookup (D-256(d)); settlement_district_pos fixed to true-district
floor-division in passing (same doc/impl mismatch class). Second
pixel-vs-metre conflation fixed in aliveness_probe's anchor-walk math.

Window path byte-unchanged (window_derivation_golden 6/6 byte-
identical); derivation_harness golden untouched; believability golden
regenerated. Full lib + integration suites green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 00:40:13 +02:00
co-authored by Claude Fable 5
parent 35a4137304
commit 933e1f4ee4
15 changed files with 1272 additions and 479 deletions
+293 -27
View File
@@ -667,19 +667,33 @@ pub struct AtlasLayerResponse {
/// Build the coarse [`DistrictGridLayer`] from a body's cached state (T-1046).
/// Returns `None` when the DistrictProfile layer has not run (empty `districts`).
/// The grid is dense `[0, cols) × [0, rows)` (the cascade tiles the full
/// heightmap), so the extent comes from the maximum `DistrictPos`.
/// heightmap), so the extent comes from the maximum `SurveyCellPos` (D-256(b)
/// — this is the coarse survey raster, not the true district grid).
pub fn build_district_grid(
state: &crate::atlas::body_world_state::BodyWorldState,
) -> Option<DistrictGridLayer> {
if state.districts.is_empty() {
return None;
}
let cols = state.districts.keys().map(|(x, _)| *x).max().unwrap_or(0) as u32 + 1;
let rows = state.districts.keys().map(|(_, y)| *y).max().unwrap_or(0) as u32 + 1;
let cols = state
.districts
.keys()
.map(|p| p.0)
.max()
.unwrap_or(0) as u32
+ 1;
let rows = state
.districts
.keys()
.map(|p| p.1)
.max()
.unwrap_or(0) as u32
+ 1;
let n = (cols * rows) as usize;
let mut morphology = vec![0u8; n];
let mut elev_q = vec![0u8; n];
for (&(x, y), profile) in &state.districts {
for (pos, profile) in &state.districts {
let (x, y) = (pos.0, pos.1);
if x < 0 || y < 0 {
continue;
}
@@ -2286,13 +2300,13 @@ pub fn handle_atlas_request(
// working grid all Layer-1 positions are expressed in (#960).
grid_w: state.heightmap_width,
grid_h: state.heightmap_height,
// district_basin_dirs is transient — it is aggregated during run_layer1
// survey_basin_dirs is transient — it is aggregated during run_layer1
// and consumed by derive_all_districts before being stored on
// BodyWorldState. When reconstructing Layer1Output from the cache for
// the client response, the per-district direction is already encoded in
// DistrictProfile.basin_direction (BodyWorldState.districts) and is not
// needed again here. Supply an empty map.
district_basin_dirs: std::collections::BTreeMap::new(),
// the client response, the per-survey-cell direction is already encoded
// in DistrictProfile.basin_direction (BodyWorldState.districts) and is
// not needed again here. Supply an empty map.
survey_basin_dirs: std::collections::BTreeMap::new(),
};
let district_grid = build_district_grid(state);
let road_graph = build_road_graph_layer(state);
@@ -2450,6 +2464,7 @@ mod tests {
heightmap: vec![],
heightmap_width: 16,
heightmap_height: 8,
sea_level: 0.3,
river_network: RiverNetwork::default(),
drainage_basins: vec![],
attractors: vec![],
@@ -2461,12 +2476,14 @@ mod tests {
last_accessed: 0,
};
// 3×2 grid with two distinct zones at the corners.
state
.districts
.insert((0, 0), dp(MorphologyZone::AlluvialPlain, 10));
state
.districts
.insert((2, 1), dp(MorphologyZone::Alpine, 90));
state.districts.insert(
crate::atlas::scale::SurveyCellPos(0, 0),
dp(MorphologyZone::AlluvialPlain, 10),
);
state.districts.insert(
crate::atlas::scale::SurveyCellPos(2, 1),
dp(MorphologyZone::Alpine, 90),
);
let grid = build_district_grid(&state).expect("districts present → Some grid");
assert_eq!((grid.cols, grid.rows), (3, 2));
@@ -2661,7 +2678,7 @@ mod tests {
assert_eq!(layer.glaciation[0], prof.glaciation_grade as u8);
}
/// T-1170/T-1168 A5 integration: the batch path
/// T-1170/T-1168 A5 integration, strengthened by D-256: the batch path
/// (`derive_district_profile`, sourcing courses via `near_perennial_water_at`
/// on demand) and the window path (`build_district_window_layer`,
/// sourcing courses via the pre-invented `Vec<InventedCourse>`) must
@@ -2670,17 +2687,21 @@ mod tests {
/// binding requirement, checked end to end (not just at the
/// `near_perennial_water`/`near_perennial_water_at` unit level).
///
/// **Design note:** this test deliberately does NOT compare the batch
/// and window paths' full `DistrictProfile` output for "the same
/// district" — `derive_district_profile`'s cell-aggregate-centre
/// sampling and the window path's district-origin sampling are
/// legitimate, PRE-EXISTING different world positions for the same
/// `DistrictPos` (a real quirk of the two derivation strategies,
/// unrelated to T-1168/T-1170), so `morphology_zone`/`elev_q`/etc.
/// routinely differ between them even before this batch's riparian work.
/// Instead this test isolates the ONE signal this batch actually wires
/// (`near_perennial_water`) at a SHARED, EXACT world position, proving
/// the two paths' independent riparian derivations agree there.
/// **Design note (D-256):** pre-D-256 this test deliberately did NOT
/// compare the batch and window paths' full `DistrictProfile` output for
/// "the same district" — `derive_district_profile`'s cell-aggregate-centre
/// sampling and the window path's district-origin sampling resolved
/// genuinely different world positions for a shared nominal `DistrictPos`
/// (the batch namespace collision D-256 rules on). With sampling unified
/// (one derive core, D-256(c)), that framing is now FALSE: batch and
/// window positions agree BY CONSTRUCTION — `derive_district_profile` is
/// a thin wrapper over the exact same [`crate::atlas::district_profile::derive_at_metres`]
/// family the window path calls, at the survey cell's own D-256(b) centre
/// world metres. `full_district_profile_matches_derive_at_metres_at_shared_survey_cell_centre`
/// below is the strengthened full-profile bit-identical check this design
/// note used to explicitly rule out; this test keeps the riparian-signal
/// check as a focused shared-exact-position regression (the ONE hand-wired
/// signal T-1168 added, worth its own targeted assertion).
#[test]
fn window_and_batch_paths_agree_on_riparian_signal_near_a_real_river_edge() {
use crate::atlas::drainage;
@@ -2763,6 +2784,249 @@ mod tests {
);
}
/// D-256(c): the batch survey-cell profile == `derive_at_metres` at the
/// SAME survey-cell-centre world metres — bit-identical, field for field
/// (basin overridden on the expectation since the core always returns the
/// default and the wrapper post-call-overrides it; riparian threaded
/// equivalently through both paths' own mechanism). This is the
/// strengthened replacement for the pre-D-256 "legitimate different
/// positions" design note on the riparian-only test above — positions now
/// agree by construction, so the FULL profile must too.
#[test]
fn full_district_profile_matches_derive_at_metres_at_shared_survey_cell_centre() {
use crate::atlas::district_profile::{self, ClimateConstants};
use crate::atlas::drainage;
use crate::atlas::heightmap::load_heightmap_png;
use crate::atlas::scale::{self, BasinDirection, SurveyCellPos};
use std::collections::BTreeMap;
let src = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png");
let heightmap =
load_heightmap_png(&src, "GJ1c", 0.3).expect("decode committed GJ1c heightmap");
let small = heightmap.downsample(256, 128);
let dr = drainage::analyze(&small.data, small.width, small.height, small.sea_level);
let ta = crate::atlas::features::TerrainAnalysis::analyze(&small, &dr);
let rn = &dr.river_network;
let params = crate::atlas::district_profile::BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
};
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 1);
let climate = ClimateConstants::default();
let gcpr = scale::HEIGHTMAP_CELLS_PER_DISTRICT;
// An interior survey cell (not clamped at the grid edge — that case
// is covered by the dedicated edge-truncation test below).
let cell = SurveyCellPos(10, 6);
let basin = BasinDirection::East;
let batch_profile = district_profile::derive_district_profile(
seed,
&params,
&ta,
cell,
gcpr,
&climate,
"GJ1c",
&BTreeMap::new(),
basin,
Some(rn),
);
// The SAME survey-cell-centre world metres, resolved via the SAME
// D-256(b) bridge function derive_district_profile uses internally.
let (world_x_m, world_y_m) =
district_profile::survey_cell_centre_world_m(cell, gcpr, ta.w, ta.h, params.body_radius_km);
// Riparian equivalent: near_perennial_water_at against the same
// RiverNetwork, same station spacing/cutoff derive_district_profile
// uses internally — the SAME on-demand course invention, not the
// window path's pre-invented slice (this is the batch-vs-on-demand
// core identity, not the batch-vs-window riparian check above).
//
// derive_at_metres's PUBLIC signature only accepts a pre-invented
// `nearby_courses` slice (never a raw bool — Ruling 4a), so to force
// the SAME riparian verdict through the public path a single-point
// synthetic course exactly at (world_x_m, world_y_m) is threaded when
// the on-demand signal is true; an empty slice when false. Either way
// `near_perennial_water((wx,wy), courses)` evaluates to the identical
// bool the batch core read.
let near_perennial_water = river_course::near_perennial_water_at(
seed,
&ta,
&params,
rn,
(world_x_m, world_y_m),
DISTRICT_M as f64,
0.0,
);
let riparian_equivalent: Vec<InventedCourse> = if near_perennial_water {
vec![InventedCourse {
edge_id: 0,
class: 0,
terminus: EdgeTerminusKind::Interior,
points: vec![(world_x_m, world_y_m)],
bbox: (world_x_m, world_y_m, world_x_m, world_y_m),
}]
} else {
Vec::new()
};
let mut expected = district_profile::derive_at_metres(
seed,
"GJ1c",
&params,
&ta,
world_x_m,
world_y_m,
&climate,
0.0,
&riparian_equivalent,
);
// basin_direction (inert field, D-256(c) proof) — the core always
// returns the default; the wrapper post-call-overrides it.
expected.basin_direction = basin;
assert_eq!(batch_profile.morphology_zone as u8, expected.morphology_zone as u8);
assert_eq!(batch_profile.tectonic_class as u8, expected.tectonic_class as u8);
assert_eq!(batch_profile.glaciation_grade as u8, expected.glaciation_grade as u8);
assert_eq!(
batch_profile.precipitation_class as u8,
expected.precipitation_class as u8
);
assert_eq!(batch_profile.slope_q, expected.slope_q);
assert_eq!(batch_profile.elev_q, expected.elev_q);
assert_eq!(batch_profile.ocean_fraction_q, expected.ocean_fraction_q);
assert_eq!(batch_profile.river_threshold, expected.river_threshold);
assert_eq!(batch_profile.temperature_c, expected.temperature_c);
assert_eq!(batch_profile.moisture_q, expected.moisture_q);
assert_eq!(
batch_profile.vegetation_class as u8,
expected.vegetation_class as u8
);
assert_eq!(batch_profile.basin_direction as u8, expected.basin_direction as u8);
}
/// D-256(c) invariant: the wrapper (`derive_district_profile`) ≡ core
/// (`derive_at_metres`) identity holds at survey-grid edge cases — the
/// anti-meridian column (`px` near the wrap boundary), the pole rows
/// (latitude clamp), and edge-truncated cells (the covering pixel block
/// itself clamped at `w`/`h`, not just the resulting position). No
/// riparian signal threaded here (both sides get `river_network: None` /
/// `nearby_courses: &[]` — the riparian equivalence is the DEDICATED
/// concern of the test above; this one isolates the geometric position
/// mapping across the grid's hard edges).
#[test]
fn wrapper_matches_core_at_survey_grid_edge_cases() {
use crate::atlas::district_profile::{self, ClimateConstants};
use crate::atlas::heightmap::load_heightmap_png;
use crate::atlas::scale::{self, BasinDirection, SurveyCellPos};
use std::collections::BTreeMap;
// The committed GJ1c heightmap (same fixture the other integration
// tests in this module use), downsampled to the standard 256×128
// working grid so the survey raster is a real body's shape.
let src = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png");
let heightmap =
load_heightmap_png(&src, "GJ1c", 0.3).expect("decode committed GJ1c heightmap");
let hm = heightmap.downsample(256, 128);
let dr = crate::atlas::drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level);
let ta = crate::atlas::features::TerrainAnalysis::analyze(&hm, &dr);
let params = crate::atlas::district_profile::BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
};
let seed = SeedChain::root(0xC0FFEE_u64).derive(SeedDomain::Body, 2);
let climate = ClimateConstants::default();
let gcpr = scale::HEIGHTMAP_CELLS_PER_DISTRICT;
let survey_cols = (hm.width as usize).div_ceil(gcpr) as i32; // 32
let survey_rows = (hm.height as usize).div_ceil(gcpr) as i32; // 16
let cases: &[(&str, SurveyCellPos)] = &[
("west edge / row 0 corner", SurveyCellPos(0, 0)),
(
"anti-meridian column (max col)",
SurveyCellPos(survey_cols - 1, survey_rows / 2),
),
("north pole row", SurveyCellPos(survey_cols / 2, 0)),
(
"south pole row",
SurveyCellPos(survey_cols / 2, survey_rows - 1),
),
(
"edge-truncated SE corner",
SurveyCellPos(survey_cols - 1, survey_rows - 1),
),
];
for &(label, cell) in cases {
let batch_profile = district_profile::derive_district_profile(
seed,
&params,
&ta,
cell,
gcpr,
&climate,
"GJ1c",
&BTreeMap::new(),
BasinDirection::North, // default — no override under test here
None,
);
let (world_x_m, world_y_m) = district_profile::survey_cell_centre_world_m(
cell,
gcpr,
ta.w,
ta.h,
params.body_radius_km,
);
let expected = district_profile::derive_at_metres(
seed,
"GJ1c",
&params,
&ta,
world_x_m,
world_y_m,
&climate,
0.0,
&[],
);
assert_eq!(
batch_profile.morphology_zone as u8, expected.morphology_zone as u8,
"[{label}] morphology_zone mismatch at {cell:?}"
);
assert_eq!(
batch_profile.elev_q, expected.elev_q,
"[{label}] elev_q mismatch at {cell:?}"
);
assert_eq!(
batch_profile.slope_q, expected.slope_q,
"[{label}] slope_q mismatch at {cell:?}"
);
assert_eq!(
batch_profile.ocean_fraction_q, expected.ocean_fraction_q,
"[{label}] ocean_fraction_q mismatch at {cell:?}"
);
assert_eq!(
batch_profile.temperature_c, expected.temperature_c,
"[{label}] temperature_c mismatch at {cell:?}"
);
assert_eq!(
batch_profile.moisture_q, expected.moisture_q,
"[{label}] moisture_q mismatch at {cell:?}"
);
}
}
/// Discipline item 3(a), mandatory: two overlapping windows sharing a
/// stretch of the same edge must produce BYTE-IDENTICAL course points
/// for that shared stretch (Ruling 1e, the window-independence
@@ -5226,6 +5490,7 @@ mod tests {
heightmap: vec![],
heightmap_width: 16,
heightmap_height: 8,
sea_level: 0.3,
river_network: RiverNetwork::default(),
drainage_basins: vec![],
attractors: vec![],
@@ -5572,6 +5837,7 @@ mod tests {
heightmap: vec![0.0; 4],
heightmap_width: 2,
heightmap_height: 2,
sea_level: 0.3,
river_network: RiverNetwork::default(),
drainage_basins: vec![],
attractors: vec![],