From 7c09f23b9a39a17ec47d6c603645bb6f1d6b5f70 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 17 Jun 2026 10:11:55 +0200 Subject: [PATCH] test(simulation): cover the Some(basin_dirs) threading + clarify on-demand default (T-1047) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses PR #170 review (Hoshe H1/H2). H1: the Some(basin_dirs) path through derive_all_districts was untested (tests only used None). Added derive_all_districts_threads_supplied_basin_directions (synthetic per-district directions propagate to DistrictProfile.basin_direction; unmapped districts fall back to North), and added basin_direction to the cascade determinism loop so the full run_layer1->district chain is guarded. H2: clarified the derive_district on-demand comment — the North default is an accepted limitation (a fallback, not a computed value); the batch path threads the true D8 direction. Co-Authored-By: Claude Opus 4.8 (1M context) --- server/src/atlas/cascade.rs | 3 ++ server/src/atlas/district_profile.rs | 42 ++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/server/src/atlas/cascade.rs b/server/src/atlas/cascade.rs index 2b2d16360..b87d3e3b7 100644 --- a/server/src/atlas/cascade.rs +++ b/server/src/atlas/cascade.rs @@ -506,6 +506,9 @@ 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; + // guard the full chain's determinism (T-1047). + assert_eq!(p1.basin_direction, p2.basin_direction); } } diff --git a/server/src/atlas/district_profile.rs b/server/src/atlas/district_profile.rs index 7e76ddfdd..9affc8b53 100644 --- a/server/src/atlas/district_profile.rs +++ b/server/src/atlas/district_profile.rs @@ -1288,8 +1288,12 @@ pub fn derive_district( ); // derive_district is the on-demand path (arbitrary DistrictPos, no L1 working - // grid); basin_direction defaults to North here. The batch path - // (derive_all_districts) threads the true D8 thalweg direction from L1. + // grid). basin_direction is an ACCEPTED LIMITATION here: it defaults to North + // (a fallback, not a computed value) because the D8 thalweg is only available + // from the L1 fdir grid the batch path holds. Production voxel generation runs + // through the batch path (derive_all_districts), which threads the true D8 + // direction from L1; this on-demand path is the fallback for districts derived + // outside that pass, where a meaningful basin_direction isn't available. build_district_profile( seed, ¶ms, @@ -1505,6 +1509,40 @@ mod tests { assert_eq!(districts.len(), 32, "district count mismatch"); } + /// T-1047: the `Some(basin_dirs)` threading path — supplied per-district D8 + /// directions propagate to `DistrictProfile.basin_direction`, and districts + /// not in the map fall back to the default (North). Guards the path that the + /// production cascade actually uses (the existing tests only exercise `None`). + #[test] + fn derive_all_districts_threads_supplied_basin_directions() { + use crate::atlas::scale::BasinDirection; + let hm = test_hm(); + let ta = test_ta(&hm); + let params = BodyParams::default(); + + // Real DistrictPos keys from a baseline (None) run. + let baseline = derive_all_districts(test_seed(), ¶ms, &ta, 8, "test_body", None); + let mut keys = baseline.keys().copied(); + let pos_east = keys.next().expect("at least one district"); + let pos_south = keys.next().expect("at least two districts"); + let pos_unmapped = keys.next().expect("at least three districts"); + + let mut basin_dirs: BTreeMap = BTreeMap::new(); + basin_dirs.insert(pos_east, BasinDirection::East); + basin_dirs.insert(pos_south, BasinDirection::South); + + let districts = + derive_all_districts(test_seed(), ¶ms, &ta, 8, "test_body", Some(&basin_dirs)); + + assert_eq!(districts[&pos_east].basin_direction, BasinDirection::East); + assert_eq!(districts[&pos_south].basin_direction, BasinDirection::South); + // Unmapped districts fall back to the default direction (North). + assert_eq!( + districts[&pos_unmapped].basin_direction, + BasinDirection::North + ); + } + // --- derive_district (on-demand 2 km, interpolation + detail-scatter) ----- fn earth_params() -> BodyParams {