test(simulation): cover the Some(basin_dirs) threading + clarify on-demand default (T-1047)

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) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 10:11:55 +02:00
co-authored by Claude Opus 4.8
parent 7d45d78674
commit 7c09f23b9a
2 changed files with 43 additions and 2 deletions
+3
View File
@@ -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);
}
}
+40 -2
View File
@@ -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,
&params,
@@ -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(), &params, &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<DistrictPos, BasinDirection> = BTreeMap::new();
basin_dirs.insert(pos_east, BasinDirection::East);
basin_dirs.insert(pos_south, BasinDirection::South);
let districts =
derive_all_districts(test_seed(), &params, &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 {