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:
+33
-10
@@ -24,13 +24,13 @@ use crate::atlas::attractor_matching::{
|
||||
match_cities, territorial_status_from_faction, CityPlacement, CityRecord,
|
||||
};
|
||||
use crate::atlas::body_world_state::{BodyWorldState, RiverNetwork};
|
||||
use crate::atlas::district_profile::{self, BodyParams, DistrictPos, DistrictProfile};
|
||||
use crate::atlas::district_profile::{self, BodyParams, DistrictProfile};
|
||||
use crate::atlas::features::TerrainAnalysis;
|
||||
use crate::atlas::heightmap::{self, BodyHeightmap, HeightmapLoadError};
|
||||
use crate::atlas::layer1::{self, Layer1Output};
|
||||
use crate::atlas::region_profile::{self, RegionProfile};
|
||||
use crate::atlas::road_graph::{self, RoadGraph};
|
||||
use crate::atlas::scale::{self, RegionPos};
|
||||
use crate::atlas::scale::{self, RegionPos, SurveyCellPos};
|
||||
use crate::seed::SeedChain;
|
||||
use crate::simulation::generator::{CompatibilityMatrix, GeographicAttractor, TerritorialStatus};
|
||||
|
||||
@@ -105,11 +105,11 @@ pub struct CascadeSnapshot {
|
||||
pub terrain_analysis: Option<TerrainAnalysis>,
|
||||
}
|
||||
|
||||
/// DistrictProfile layer output (T-1023, D-239 §1): per-district (~1 km) terrain
|
||||
/// profiles covering the whole body. Stored in `BodyWorldState.districts`.
|
||||
/// DistrictProfile layer output (T-1023, D-239 §1): per-survey-cell (D-256(b))
|
||||
/// terrain profiles covering the whole body. Stored in `BodyWorldState.districts`.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LayerDistrictOutput {
|
||||
pub districts: std::collections::BTreeMap<DistrictPos, DistrictProfile>,
|
||||
pub districts: std::collections::BTreeMap<SurveyCellPos, DistrictProfile>,
|
||||
}
|
||||
|
||||
/// Region climate layer output (T-1113, D-243 §3): per-region (~205 km) climate
|
||||
@@ -120,6 +120,12 @@ pub struct LayerDistrictOutput {
|
||||
/// cache pads a neighbour ring because its edge-fuzz blend samples across
|
||||
/// boundaries; that padding is a blend implementation detail, not part of the
|
||||
/// body's own region grid, and a dense Atlas wire encoding wants exact dims.)
|
||||
///
|
||||
/// **D-256(f):** the build still keys off the SurveyCellPos-shaped pseudo-grid
|
||||
/// dims rather than the true D-243 district grid — a fenced, deliberate defer
|
||||
/// to T-1181's rung-0 Global canvas, not a fix this ticket makes. Verified
|
||||
/// safe: the sole reader is the `region_grid` Atlas overlay, which reads no
|
||||
/// `DistrictProfile` climate to disagree with.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LayerRegionOutput {
|
||||
pub regions: std::collections::BTreeMap<RegionPos, RegionProfile>,
|
||||
@@ -154,11 +160,13 @@ impl CascadeSnapshot {
|
||||
let road_graph = self.road_graph.unwrap_or_default();
|
||||
// terrain_analysis (transient) is intentionally dropped here.
|
||||
let _ = self.terrain_analysis;
|
||||
let sea_level = self.heightmap.sea_level;
|
||||
BodyWorldState {
|
||||
body_id: self.body_id,
|
||||
heightmap: self.heightmap.data,
|
||||
heightmap_width: self.heightmap.width,
|
||||
heightmap_height: self.heightmap.height,
|
||||
sea_level,
|
||||
river_network,
|
||||
drainage_basins,
|
||||
attractors,
|
||||
@@ -301,10 +309,13 @@ pub fn run_cascade_from_heightmap(
|
||||
// domain separation — derive_all_districts builds the region
|
||||
// cache internally.
|
||||
//
|
||||
// district_basin_dirs from Layer1Output threads the true D8
|
||||
// thalweg direction into each DistrictProfile.basin_direction
|
||||
// (T-1047). Pass the map through derive_all_districts.
|
||||
let basin_dirs = snapshot.layer1.as_ref().map(|l1| &l1.district_basin_dirs);
|
||||
// Layer1Output.survey_basin_dirs threads the true D8 thalweg
|
||||
// direction into each DistrictProfile.basin_direction (T-1047)
|
||||
// — the VALUES are true D8 aggregates, the KEYS are survey
|
||||
// cells (D-256(b); `derive_all_districts` looks it up by
|
||||
// identity, matching this map's own key space). Pass the map
|
||||
// through derive_all_districts.
|
||||
let basin_dirs = snapshot.layer1.as_ref().map(|l1| &l1.survey_basin_dirs);
|
||||
// T-1168 Ruling 4c: same `snapshot.layer1` source as
|
||||
// `basin_dirs`/`river_cells` (the `road_graph` precedent
|
||||
// below) — the river network for the batch-path riparian
|
||||
@@ -372,6 +383,18 @@ pub fn run_cascade_from_heightmap(
|
||||
// no TerrainAnalysis needed, so it runs outside the transient-borrow block
|
||||
// above. Gates on body_params like the DistrictProfile layer (no params →
|
||||
// no climate inputs → the layer skips, `regions` stays empty).
|
||||
//
|
||||
// D-256(f) FENCED, NOT FIXED BY THIS TICKET: this block still treats the
|
||||
// survey-raster dims (`district_cols`/`district_rows` below, really
|
||||
// SurveyCellPos counts) as if they were true district counts and maps
|
||||
// them straight through `scale::district_to_region` — the SAME pseudo-grid
|
||||
// collapse D-256(c) fixed for `DistrictProfile`'s own region baseline.
|
||||
// Deliberately deferred to T-1181's rung-0 Global canvas (D-256 ruling's
|
||||
// tripwire, verified: the sole production reader of `LayerRegionOutput`/
|
||||
// `regions` is the `region_grid` body-view overlay — no consumer reads it
|
||||
// against `DistrictProfile` climate, so this collapse never disagrees
|
||||
// with anything this ticket's scope touches). The overlay stays visibly
|
||||
// stale until T-1181 replaces it — accepted, noted, not silently ignored.
|
||||
if up_to >= CascadeLayer::Region {
|
||||
if let Some(params) = body_params {
|
||||
// The covering region grid: the same district dims the district
|
||||
@@ -589,7 +612,7 @@ mod tests {
|
||||
assert_eq!(p1.river_threshold, p2.river_threshold);
|
||||
assert_eq!(p1.tectonic_class, p2.tectonic_class);
|
||||
assert_eq!(p1.glaciation_grade, p2.glaciation_grade);
|
||||
// basin_direction threads run_layer1 -> district_basin_dirs -> here;
|
||||
// basin_direction threads run_layer1 -> survey_basin_dirs -> here;
|
||||
// guard the full chain's determinism (T-1047).
|
||||
assert_eq!(p1.basin_direction, p2.basin_direction);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user