feat(simulation): cross-district parameter blending at chunk/voxel scale (T-1042)
Replaces the single-DistrictProfile walking-skeleton restriction so continuous terrain parameters no longer step at the 2 km district pitch (D-239 §4/§7/§8). ChunkContext gains secondary: Option<DistrictProfile> + blend_weight: u8; derive_chunk_context detects a chunk within one chunk of a district border, looks up the adjacent profile, and integer-blends meander/channel params on the context. derive_voxel_column integer-blends elev_q/moisture_q before material selection, reusing the same warp offset so the seam cannot align with the chunk edge (Cow fast-path keeps interior chunks bit-identical — golden seed unchanged). Morphology FAMILY selection is never blended — stays sharp per D-239 §7 (family dispatch reads the primary district only). Climate feathering is the separate T-1078 path. Adds cross_district_elevation_blend_reduces_seam_step and cross_district_morphology_family_seams_stay_sharp to the derivation harness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+90
-49
@@ -338,6 +338,9 @@ pub fn derive_voxel_column(
|
||||
// ── 1. Domain warp (D-239 §4) ─────────────────────────────────────────
|
||||
// Apply warp and truncate to integer voxel address. This is the ONLY f64
|
||||
// in the structural path; all decisions below use integer voxel coords.
|
||||
// The warp is applied BEFORE the blend boundary, so the seam cannot align
|
||||
// with a chunk edge (T-1042: "reusing the same warp offset so the blend
|
||||
// seam cannot align with the chunk edge").
|
||||
let (dx, dy) = domain_warp(world_seed, body_id, (tile_x, tile_y));
|
||||
let voxel_x = (tile_x as f64 + dx) as i32;
|
||||
let voxel_y = (tile_y as f64 + dy) as i32;
|
||||
@@ -351,31 +354,67 @@ pub fn derive_voxel_column(
|
||||
.derive(SeedDomain::Voxel, voxel_pos_to_id(voxel_pos))
|
||||
.seed();
|
||||
|
||||
// ── 2b. Cross-district terrain blending (T-1042, D-239 §4/§7) ─────────
|
||||
// When a secondary district is present AND blend_weight < 255, blend the
|
||||
// continuous terrain scalars (`elev_q`, `moisture_q`) between the primary
|
||||
// and secondary district. This is done BEFORE family dispatch so the
|
||||
// blended values propagate through ALL family generators.
|
||||
//
|
||||
// Morphology family is NOT changed — family dispatch always uses the
|
||||
// primary district's `morphology_zone` (D-239 §7: morphology seams stay
|
||||
// sharp). Only `elev_q` and `moisture_q` are blended; all structural
|
||||
// fields (glaciation_grade, tectonic_class, precipitation_class, etc.)
|
||||
// stay primary.
|
||||
//
|
||||
// Integer blend formula: `(a * w + b * (255 - w) + 127) / 255` (D-010).
|
||||
// This is a zero-cost branch when blend_weight == 255 (no secondary active).
|
||||
let effective_district: std::borrow::Cow<DistrictProfile> = if chunk.blend_weight < 255 {
|
||||
if let Some(ref sec) = chunk.secondary {
|
||||
let w = chunk.blend_weight as i32;
|
||||
let w_sec = 255 - w;
|
||||
let blended_elev_q = (district.elev_q * w + sec.elev_q * w_sec + 127) / 255;
|
||||
let blended_moisture_q = (district.moisture_q * w + sec.moisture_q * w_sec + 127) / 255;
|
||||
let mut blended = district.clone();
|
||||
blended.elev_q = blended_elev_q;
|
||||
blended.moisture_q = blended_moisture_q;
|
||||
std::borrow::Cow::Owned(blended)
|
||||
} else {
|
||||
std::borrow::Cow::Borrowed(district)
|
||||
}
|
||||
} else {
|
||||
std::borrow::Cow::Borrowed(district)
|
||||
};
|
||||
|
||||
let district_eff: &DistrictProfile = &effective_district;
|
||||
|
||||
// ── 3. Family dispatch (D-239 §5) ─────────────────────────────────────
|
||||
// Family selection uses the PRIMARY district's morphology_zone (D-239 §7:
|
||||
// seams stay sharp). Blended `district_eff` is passed to the generators
|
||||
// so their `elev_q`/`moisture_q` reads get the blended values.
|
||||
let family = zone_to_family(&district.morphology_zone);
|
||||
|
||||
let mut column = match family {
|
||||
MorphologyFamily::AlluvialPlain => {
|
||||
generate_alluvial_plain(district, chunk, voxel_pos, sub_chunk_seed)
|
||||
generate_alluvial_plain(district_eff, chunk, voxel_pos, sub_chunk_seed)
|
||||
}
|
||||
MorphologyFamily::LavaField => generate_lava_field(district, voxel_pos, sub_chunk_seed),
|
||||
MorphologyFamily::LavaField => generate_lava_field(district_eff, voxel_pos, sub_chunk_seed),
|
||||
MorphologyFamily::FjordWall => {
|
||||
generate_fjord_wall(district, chunk, voxel_pos, sub_chunk_seed)
|
||||
generate_fjord_wall(district_eff, chunk, voxel_pos, sub_chunk_seed)
|
||||
}
|
||||
MorphologyFamily::CliffCoast => {
|
||||
generate_cliff_coast(district, chunk, voxel_pos, sub_chunk_seed)
|
||||
generate_cliff_coast(district_eff, chunk, voxel_pos, sub_chunk_seed)
|
||||
}
|
||||
MorphologyFamily::BraidedDelta => {
|
||||
generate_braided_delta(district, chunk, voxel_pos, sub_chunk_seed)
|
||||
generate_braided_delta(district_eff, chunk, voxel_pos, sub_chunk_seed)
|
||||
}
|
||||
MorphologyFamily::DuneStrand => {
|
||||
generate_dune_strand(district, chunk, voxel_pos, sub_chunk_seed)
|
||||
generate_dune_strand(district_eff, chunk, voxel_pos, sub_chunk_seed)
|
||||
}
|
||||
MorphologyFamily::IncisedGorge => {
|
||||
generate_incised_gorge(district, chunk, voxel_pos, sub_chunk_seed)
|
||||
generate_incised_gorge(district_eff, chunk, voxel_pos, sub_chunk_seed)
|
||||
}
|
||||
MorphologyFamily::MeanderReach => {
|
||||
generate_meander_reach(district, chunk, voxel_pos, sub_chunk_seed)
|
||||
generate_meander_reach(district_eff, chunk, voxel_pos, sub_chunk_seed)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -384,6 +423,8 @@ pub fn derive_voxel_column(
|
||||
// axes (terrain/water/vegetation/elevation); cover is a separate orthogonal
|
||||
// axis derived from the district's mean temperature + water/terrain + coherent
|
||||
// cluster scatter. One site, set here — no family generator needs changing.
|
||||
// Cover derives from the primary district's temperature (not blended) since
|
||||
// temperature blending is the sibling ticket's domain (T-1078, D-243 §4).
|
||||
column.cover = derive_cover(world_seed, body_id, district, &column, voxel_pos);
|
||||
|
||||
column
|
||||
@@ -1951,13 +1992,13 @@ mod tests {
|
||||
/// Feature placement is district-anchored, so tests sample around
|
||||
/// `chunk.channel_anchor_m` instead of assuming chunk-frame positions.
|
||||
fn anchor_chunk(seed: u64, body: &str, district: &DistrictProfile) -> ChunkContext {
|
||||
let probe = derive_chunk_context(seed, body, district, (0, 0));
|
||||
let probe = derive_chunk_context(seed, body, district, (0, 0), None);
|
||||
let idx = probe.channel_anchor_m.div_euclid(64);
|
||||
let pos = match probe.basin_direction {
|
||||
BasinDirection::North | BasinDirection::South => (idx, 0),
|
||||
BasinDirection::East | BasinDirection::West => (0, idx),
|
||||
};
|
||||
derive_chunk_context(seed, body, district, pos)
|
||||
derive_chunk_context(seed, body, district, pos, None)
|
||||
}
|
||||
|
||||
/// Map a (cross, along) coordinate pair to (tile_x, tile_y) for the
|
||||
@@ -1986,7 +2027,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn alluvial_chunk(district: &DistrictProfile) -> ChunkContext {
|
||||
derive_chunk_context(42, "GJ1c", district, (10, 20))
|
||||
derive_chunk_context(42, "GJ1c", district, (10, 20), None)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -2109,8 +2150,8 @@ mod tests {
|
||||
#[test]
|
||||
fn different_seeds_produce_different_outputs() {
|
||||
let district = alluvial_district();
|
||||
let chunk1 = derive_chunk_context(1, "GJ1c", &district, (10, 20));
|
||||
let chunk2 = derive_chunk_context(2, "GJ1c", &district, (10, 20));
|
||||
let chunk1 = derive_chunk_context(1, "GJ1c", &district, (10, 20), None);
|
||||
let chunk2 = derive_chunk_context(2, "GJ1c", &district, (10, 20), None);
|
||||
let a = derive_voxel_column(1, "GJ1c", &district, &chunk1, 100, 100);
|
||||
let b = derive_voxel_column(2, "GJ1c", &district, &chunk2, 100, 100);
|
||||
assert!(
|
||||
@@ -2269,7 +2310,7 @@ mod tests {
|
||||
#[test]
|
||||
fn lava_field_terrain_is_lava() {
|
||||
let district = lava_district();
|
||||
let chunk = derive_chunk_context(42, "Io", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "Io", &district, (0, 0), None);
|
||||
for pos in [(10, 10), (0, 0), (50, 25), (-5, 5)] {
|
||||
let col = derive_voxel_column(42, "Io", &district, &chunk, pos.0, pos.1);
|
||||
assert_eq!(
|
||||
@@ -2284,7 +2325,7 @@ mod tests {
|
||||
#[test]
|
||||
fn lava_field_vegetation_is_barren() {
|
||||
let district = lava_district();
|
||||
let chunk = derive_chunk_context(42, "Io", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "Io", &district, (0, 0), None);
|
||||
for tx in 0..20i32 {
|
||||
let col = derive_voxel_column(42, "Io", &district, &chunk, tx, 10);
|
||||
assert_eq!(
|
||||
@@ -2299,7 +2340,7 @@ mod tests {
|
||||
fn lava_field_no_deep_water() {
|
||||
// Immature drainage law (D-239 §8): no deep organised channels on lava.
|
||||
let district = lava_district();
|
||||
let chunk = derive_chunk_context(42, "Io", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "Io", &district, (0, 0), None);
|
||||
for (tx, ty) in (0..50).map(|i| (i * 7, i * 3)) {
|
||||
let col = derive_voxel_column(42, "Io", &district, &chunk, tx, ty);
|
||||
assert_ne!(
|
||||
@@ -2313,7 +2354,7 @@ mod tests {
|
||||
#[test]
|
||||
fn lava_field_is_deterministic() {
|
||||
let district = lava_district();
|
||||
let chunk = derive_chunk_context(7, "Io", &district, (5, 3));
|
||||
let chunk = derive_chunk_context(7, "Io", &district, (5, 3), None);
|
||||
for (tx, ty) in [(0, 0), (10, 20), (-5, 7)] {
|
||||
let a = derive_voxel_column(7, "Io", &district, &chunk, tx, ty);
|
||||
let b = derive_voxel_column(7, "Io", &district, &chunk, tx, ty);
|
||||
@@ -2324,7 +2365,7 @@ mod tests {
|
||||
#[test]
|
||||
fn lava_field_elevation_non_negative() {
|
||||
let district = lava_district();
|
||||
let chunk = derive_chunk_context(42, "Io", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "Io", &district, (0, 0), None);
|
||||
for (tx, ty) in [(100, 100), (0, 0), (-50, 25), (200, -10)] {
|
||||
let col = derive_voxel_column(42, "Io", &district, &chunk, tx, ty);
|
||||
assert!(col.elevation_m >= 0, "LavaField elevation must be >= 0");
|
||||
@@ -2352,7 +2393,7 @@ mod tests {
|
||||
#[test]
|
||||
fn fjord_wall_terrain_is_rock() {
|
||||
let district = fjord_district();
|
||||
let chunk = derive_chunk_context(42, "Fjordheim", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "Fjordheim", &district, (0, 0), None);
|
||||
for (tx, ty) in [(10, 10), (0, 0), (50, 25), (30, -5)] {
|
||||
let col = derive_voxel_column(42, "Fjordheim", &district, &chunk, tx, ty);
|
||||
assert_eq!(
|
||||
@@ -2369,7 +2410,7 @@ mod tests {
|
||||
// water. Sample a small window around the anchor — the domain warp can
|
||||
// displace any single tile off the 4–8 m floor.
|
||||
let district = fjord_district();
|
||||
let chunk = derive_chunk_context(42, "Fjordheim", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "Fjordheim", &district, (0, 0), None);
|
||||
let anchor = chunk.channel_anchor_m;
|
||||
let any_deep = (anchor - 4..=anchor + 4).any(|c| {
|
||||
let (tx, ty) = cross_along_to_tile(&chunk, c, 50);
|
||||
@@ -2385,7 +2426,7 @@ mod tests {
|
||||
fn fjord_wall_walls_are_high_relative_to_floor() {
|
||||
// Wall elevation must be substantially higher than fjord floor.
|
||||
let district = fjord_district();
|
||||
let chunk = derive_chunk_context(42, "Fjordheim", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "Fjordheim", &district, (0, 0), None);
|
||||
let anchor = chunk.channel_anchor_m;
|
||||
// Floor: the lowest tile in the trough window around the anchor.
|
||||
let floor_elev = (anchor - 4..=anchor + 4)
|
||||
@@ -2409,7 +2450,7 @@ mod tests {
|
||||
#[test]
|
||||
fn fjord_wall_is_deterministic() {
|
||||
let district = fjord_district();
|
||||
let chunk = derive_chunk_context(11, "Fjordheim", &district, (2, 3));
|
||||
let chunk = derive_chunk_context(11, "Fjordheim", &district, (2, 3), None);
|
||||
for (tx, ty) in [(32, 50), (0, 50), (63, 20)] {
|
||||
let a = derive_voxel_column(11, "Fjordheim", &district, &chunk, tx, ty);
|
||||
let b = derive_voxel_column(11, "Fjordheim", &district, &chunk, tx, ty);
|
||||
@@ -2420,7 +2461,7 @@ mod tests {
|
||||
#[test]
|
||||
fn fjord_wall_elevation_non_negative() {
|
||||
let district = fjord_district();
|
||||
let chunk = derive_chunk_context(42, "Fjordheim", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "Fjordheim", &district, (0, 0), None);
|
||||
for (tx, ty) in [(100, 100), (0, 0), (-50, 25), (200, -10)] {
|
||||
let col = derive_voxel_column(42, "Fjordheim", &district, &chunk, tx, ty);
|
||||
assert!(col.elevation_m >= 0, "FjordWall elevation must be >= 0");
|
||||
@@ -2434,7 +2475,7 @@ mod tests {
|
||||
// total Deep-water width = 2×floor_half + noise ∈ [4, 8] m.
|
||||
// Cross-section spans the district-anchored trough (T-1041).
|
||||
let district = fjord_district(); // glaciation_grade = Moderate (= 2)
|
||||
let chunk = derive_chunk_context(42, "Fjordheim", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "Fjordheim", &district, (0, 0), None);
|
||||
let anchor = chunk.channel_anchor_m;
|
||||
// Count Deep-water tiles across a 64-tile cross-section around the anchor.
|
||||
let deep_tiles = (anchor - 32..anchor + 32)
|
||||
@@ -2469,7 +2510,7 @@ mod tests {
|
||||
#[test]
|
||||
fn cliff_coast_terrain_is_rock() {
|
||||
let district = cliff_district();
|
||||
let chunk = derive_chunk_context(42, "Velen", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "Velen", &district, (0, 0), None);
|
||||
for (tx, ty) in [(10, 10), (0, 0), (50, 25), (30, -5)] {
|
||||
let col = derive_voxel_column(42, "Velen", &district, &chunk, tx, ty);
|
||||
assert_eq!(
|
||||
@@ -2487,7 +2528,7 @@ mod tests {
|
||||
// The coast face sits on the district-anchored line `coast_anchor_m` along
|
||||
// the seaward (basin) axis (T-1041) — sample a transect across it.
|
||||
let district = cliff_district();
|
||||
let chunk = derive_chunk_context(42, "Velen", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "Velen", &district, (0, 0), None);
|
||||
let coast = chunk.coast_anchor_m;
|
||||
// Sample 64 positions along the seaward axis, crossing the coast line.
|
||||
let cols: Vec<VoxelColumn> = (coast - 32..coast + 32)
|
||||
@@ -2517,7 +2558,7 @@ mod tests {
|
||||
#[test]
|
||||
fn cliff_coast_is_deterministic() {
|
||||
let district = cliff_district();
|
||||
let chunk = derive_chunk_context(5, "Velen", &district, (1, 2));
|
||||
let chunk = derive_chunk_context(5, "Velen", &district, (1, 2), None);
|
||||
for (tx, ty) in [(5, 50), (30, 20), (62, 50)] {
|
||||
let a = derive_voxel_column(5, "Velen", &district, &chunk, tx, ty);
|
||||
let b = derive_voxel_column(5, "Velen", &district, &chunk, tx, ty);
|
||||
@@ -2528,7 +2569,7 @@ mod tests {
|
||||
#[test]
|
||||
fn cliff_coast_elevation_non_negative() {
|
||||
let district = cliff_district();
|
||||
let chunk = derive_chunk_context(42, "Velen", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "Velen", &district, (0, 0), None);
|
||||
for (tx, ty) in [(100, 100), (0, 0), (-50, 25), (200, -10)] {
|
||||
let col = derive_voxel_column(42, "Velen", &district, &chunk, tx, ty);
|
||||
assert!(col.elevation_m >= 0, "CliffCoast elevation must be >= 0");
|
||||
@@ -2554,7 +2595,7 @@ mod tests {
|
||||
#[test]
|
||||
fn braided_delta_terrain_is_gravel() {
|
||||
let district = delta_district();
|
||||
let chunk = derive_chunk_context(42, "delta_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "delta_body", &district, (0, 0), None);
|
||||
for (tx, ty) in [(10, 10), (0, 0), (50, 25), (30, -5)] {
|
||||
let col = derive_voxel_column(42, "delta_body", &district, &chunk, tx, ty);
|
||||
assert_eq!(
|
||||
@@ -2569,7 +2610,7 @@ mod tests {
|
||||
fn braided_delta_low_elevation() {
|
||||
// Delta is near sea level — D-239 §8 drainage monotonicity: mouths at sea level.
|
||||
let district = delta_district();
|
||||
let chunk = derive_chunk_context(42, "delta_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "delta_body", &district, (0, 0), None);
|
||||
for (tx, ty) in [(10, 10), (0, 0), (50, 25), (30, -5), (63, 0)] {
|
||||
let col = derive_voxel_column(42, "delta_body", &district, &chunk, tx, ty);
|
||||
assert!(
|
||||
@@ -2634,7 +2675,7 @@ mod tests {
|
||||
#[test]
|
||||
fn braided_delta_is_deterministic() {
|
||||
let district = delta_district();
|
||||
let chunk = derive_chunk_context(99, "delta_body", &district, (3, 1));
|
||||
let chunk = derive_chunk_context(99, "delta_body", &district, (3, 1), None);
|
||||
for (tx, ty) in [(10, 10), (0, 100), (-5, 7)] {
|
||||
let a = derive_voxel_column(99, "delta_body", &district, &chunk, tx, ty);
|
||||
let b = derive_voxel_column(99, "delta_body", &district, &chunk, tx, ty);
|
||||
@@ -2645,7 +2686,7 @@ mod tests {
|
||||
#[test]
|
||||
fn braided_delta_elevation_non_negative() {
|
||||
let district = delta_district();
|
||||
let chunk = derive_chunk_context(42, "delta_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "delta_body", &district, (0, 0), None);
|
||||
for (tx, ty) in [(100, 100), (0, 0), (-50, 25), (200, -10)] {
|
||||
let col = derive_voxel_column(42, "delta_body", &district, &chunk, tx, ty);
|
||||
assert!(col.elevation_m >= 0, "BraidedDelta elevation must be >= 0");
|
||||
@@ -2671,7 +2712,7 @@ mod tests {
|
||||
#[test]
|
||||
fn dune_strand_terrain_is_sand() {
|
||||
let district = dune_district();
|
||||
let chunk = derive_chunk_context(42, "dune_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "dune_body", &district, (0, 0), None);
|
||||
for (tx, ty) in [(10, 10), (0, 0), (50, 25), (30, -5)] {
|
||||
let col = derive_voxel_column(42, "dune_body", &district, &chunk, tx, ty);
|
||||
assert_eq!(
|
||||
@@ -2689,7 +2730,7 @@ mod tests {
|
||||
// We verify by checking that elevation differences between adjacent tiles
|
||||
// don't exceed the physics cap.
|
||||
let district = dune_district();
|
||||
let chunk = derive_chunk_context(42, "dune_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "dune_body", &district, (0, 0), None);
|
||||
// Sample a row of tiles along the dune wave direction.
|
||||
let elevations: Vec<i32> = (0..40i32)
|
||||
.map(|y| derive_voxel_column(42, "dune_body", &district, &chunk, 10, y).elevation_m)
|
||||
@@ -2711,7 +2752,7 @@ mod tests {
|
||||
fn dune_strand_mostly_dry() {
|
||||
// Dunes are mostly dry; only the strand toe may be Shallow.
|
||||
let district = dune_district();
|
||||
let chunk = derive_chunk_context(42, "dune_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "dune_body", &district, (0, 0), None);
|
||||
let dry_or_shallow_count = (0..100i32)
|
||||
.filter(|&x| {
|
||||
let col = derive_voxel_column(42, "dune_body", &district, &chunk, x, 50);
|
||||
@@ -2727,7 +2768,7 @@ mod tests {
|
||||
#[test]
|
||||
fn dune_strand_is_deterministic() {
|
||||
let district = dune_district();
|
||||
let chunk = derive_chunk_context(3, "dune_body", &district, (1, 0));
|
||||
let chunk = derive_chunk_context(3, "dune_body", &district, (1, 0), None);
|
||||
for (tx, ty) in [(10, 10), (0, 30), (-5, 7)] {
|
||||
let a = derive_voxel_column(3, "dune_body", &district, &chunk, tx, ty);
|
||||
let b = derive_voxel_column(3, "dune_body", &district, &chunk, tx, ty);
|
||||
@@ -2738,7 +2779,7 @@ mod tests {
|
||||
#[test]
|
||||
fn dune_strand_elevation_non_negative() {
|
||||
let district = dune_district();
|
||||
let chunk = derive_chunk_context(42, "dune_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "dune_body", &district, (0, 0), None);
|
||||
for (tx, ty) in [(100, 100), (0, 0), (-50, 25), (200, -10)] {
|
||||
let col = derive_voxel_column(42, "dune_body", &district, &chunk, tx, ty);
|
||||
assert!(col.elevation_m >= 0, "DuneStrand elevation must be >= 0");
|
||||
@@ -2765,7 +2806,7 @@ mod tests {
|
||||
#[test]
|
||||
fn incised_gorge_terrain_is_rock() {
|
||||
let district = gorge_district();
|
||||
let chunk = derive_chunk_context(42, "gorge_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "gorge_body", &district, (0, 0), None);
|
||||
for (tx, ty) in [(10, 10), (0, 0), (50, 25), (30, -5)] {
|
||||
let col = derive_voxel_column(42, "gorge_body", &district, &chunk, tx, ty);
|
||||
assert_eq!(
|
||||
@@ -2783,7 +2824,7 @@ mod tests {
|
||||
// across a cross-section is within [2, 8] m. Cross-section spans the
|
||||
// district-anchored gorge centreline (T-1041).
|
||||
let district = gorge_district();
|
||||
let chunk = derive_chunk_context(42, "gorge_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "gorge_body", &district, (0, 0), None);
|
||||
let anchor = chunk.channel_anchor_m;
|
||||
// The floor's intrinsic width is the D-239 §9 quantity. A single warped
|
||||
// cross-section is noisy (the ±8 m domain warp can split the band at some
|
||||
@@ -2812,7 +2853,7 @@ mod tests {
|
||||
fn incised_gorge_walls_much_higher_than_floor() {
|
||||
// Walls must be substantially higher than the gorge floor.
|
||||
let district = gorge_district();
|
||||
let chunk = derive_chunk_context(42, "gorge_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "gorge_body", &district, (0, 0), None);
|
||||
let anchor = chunk.channel_anchor_m;
|
||||
// Floor: the lowest tile in the trough window around the anchor
|
||||
// (the warp can displace any single tile off the 2–8 m floor).
|
||||
@@ -2837,7 +2878,7 @@ mod tests {
|
||||
#[test]
|
||||
fn incised_gorge_is_deterministic() {
|
||||
let district = gorge_district();
|
||||
let chunk = derive_chunk_context(17, "gorge_body", &district, (4, 2));
|
||||
let chunk = derive_chunk_context(17, "gorge_body", &district, (4, 2), None);
|
||||
for (tx, ty) in [(32, 100), (62, 100), (10, 50)] {
|
||||
let a = derive_voxel_column(17, "gorge_body", &district, &chunk, tx, ty);
|
||||
let b = derive_voxel_column(17, "gorge_body", &district, &chunk, tx, ty);
|
||||
@@ -2848,7 +2889,7 @@ mod tests {
|
||||
#[test]
|
||||
fn incised_gorge_elevation_non_negative() {
|
||||
let district = gorge_district();
|
||||
let chunk = derive_chunk_context(42, "gorge_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "gorge_body", &district, (0, 0), None);
|
||||
for (tx, ty) in [(100, 100), (0, 0), (-50, 25), (200, -10)] {
|
||||
let col = derive_voxel_column(42, "gorge_body", &district, &chunk, tx, ty);
|
||||
assert!(col.elevation_m >= 0, "IncisedGorge elevation must be >= 0");
|
||||
@@ -2878,7 +2919,7 @@ mod tests {
|
||||
// for saturated floodplains (slope_q ≤ 3, moisture_q ≥ 70 in the meander_district).
|
||||
// This test checks that NO other material appears (no Rock, Sand, Gravel, Lava).
|
||||
let district = meander_district();
|
||||
let chunk = derive_chunk_context(42, "meander_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "meander_body", &district, (0, 0), None);
|
||||
for (tx, ty) in [
|
||||
(0, 50),
|
||||
(5, 50),
|
||||
@@ -2913,7 +2954,7 @@ mod tests {
|
||||
vegetation_class: VegetationClass::Forest,
|
||||
..alluvial_district()
|
||||
};
|
||||
let chunk = derive_chunk_context(42, "meander_body2", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "meander_body2", &district, (0, 0), None);
|
||||
let mut soil_count = 0;
|
||||
for tx in [0, 5, 10, 15, 100, 200] {
|
||||
let col = derive_voxel_column(42, "meander_body2", &district, &chunk, tx, 50);
|
||||
@@ -3074,7 +3115,7 @@ mod tests {
|
||||
#[test]
|
||||
fn meander_reach_is_deterministic() {
|
||||
let district = meander_district();
|
||||
let chunk = derive_chunk_context(23, "meander_body", &district, (2, 5));
|
||||
let chunk = derive_chunk_context(23, "meander_body", &district, (2, 5), None);
|
||||
for (tx, ty) in [(0, 50), (50, 200), (-20, 100)] {
|
||||
let a = derive_voxel_column(23, "meander_body", &district, &chunk, tx, ty);
|
||||
let b = derive_voxel_column(23, "meander_body", &district, &chunk, tx, ty);
|
||||
@@ -3085,7 +3126,7 @@ mod tests {
|
||||
#[test]
|
||||
fn meander_reach_elevation_non_negative() {
|
||||
let district = meander_district();
|
||||
let chunk = derive_chunk_context(42, "meander_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "meander_body", &district, (0, 0), None);
|
||||
for (tx, ty) in [(100, 100), (0, 0), (-50, 25), (200, -10)] {
|
||||
let col = derive_voxel_column(42, "meander_body", &district, &chunk, tx, ty);
|
||||
assert!(col.elevation_m >= 0, "MeanderReach elevation must be >= 0");
|
||||
@@ -3127,7 +3168,7 @@ mod tests {
|
||||
moisture_q: 50,
|
||||
..alluvial_district()
|
||||
};
|
||||
let chunk = derive_chunk_context(42, "stress_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "stress_body", &district, (0, 0), None);
|
||||
for (tx, ty) in &positions {
|
||||
// Must not panic.
|
||||
let col = derive_voxel_column(42, "stress_body", &district, &chunk, *tx, *ty);
|
||||
@@ -3163,7 +3204,7 @@ mod tests {
|
||||
vegetation_class: VegetationClass::Barren,
|
||||
..alluvial_district()
|
||||
};
|
||||
let chunk = derive_chunk_context(42, "mat_check", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "mat_check", &district, (0, 0), None);
|
||||
let col = derive_voxel_column(42, "mat_check", &district, &chunk, 10, 10);
|
||||
assert_eq!(
|
||||
col.terrain, *expected_terrain,
|
||||
@@ -3563,7 +3604,7 @@ mod tests {
|
||||
fn cover_end_to_end_via_derive_voxel_column_is_deterministic() {
|
||||
// Cover must be deterministic through the full derive_voxel_column path.
|
||||
let district = cover_district(MorphologyZone::AlluvialPlain, -12.0, 60);
|
||||
let chunk = derive_chunk_context(42, "cold_body", &district, (0, 0));
|
||||
let chunk = derive_chunk_context(42, "cold_body", &district, (0, 0), None);
|
||||
for (tx, ty) in [(0, 0), (50, 100), (-20, 30), (200, -10)] {
|
||||
let a = derive_voxel_column(42, "cold_body", &district, &chunk, tx, ty);
|
||||
let b = derive_voxel_column(42, "cold_body", &district, &chunk, tx, ty);
|
||||
|
||||
Reference in New Issue
Block a user