fix(simulation): region-anchor channels and linear landforms (T-1040, T-1041)

derive_chunk_context now derives channel_anchor_m and coast_anchor_m from
the Region-or-higher seed path (D-239 §10); all five voxel placement sites
measure distance to these anchors in continuous world coordinates:

- T-1040: compute_channel_state / compute_meander_reach_channel /
  in_levee_band subtract the region anchor instead of measuring from the
  world origin axis — active channels now exist in their chunks anywhere
  on the body, continuous across chunk boundaries. has_active_channel is
  honest in both directions (band-crossing gate; floor covers the
  BraidedDelta belt at short wavelengths).
- T-1041: fjord/cliff/gorge/delta drop their rem_euclid(64) chunk-frame
  folds — one valley/coastline/fan per region instead of one per chunk.
  D-239 §9 chokepoint widths preserved; two i32 wall-rise overflows on
  region-scale distances fixed by clamping before multiply.

Harness: 6 new believability tests (channel presence + gate honesty at
chunk (1000,-750), cross-boundary continuity, one-landform-per-region,
braid-belt confinement); drainage sweeps strengthened with anchor-band
chunks. Voxel golden regenerated deliberately: case A now pins a genuinely
in-channel Shallow voxel on the anchor, case C a Deep trough voxel
matching its label; case B byte-identical and L0/L1 cascade golden
untouched (no upstream leak). Review: approved, three minors addressed
(truncation-convention docs, delta gate floor, case C relocation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 13:46:38 +02:00
co-authored by Claude Fable 5
parent 49a74e93d4
commit 408bd39e63
4 changed files with 1007 additions and 282 deletions
+199 -26
View File
@@ -18,11 +18,20 @@
//! `RegionProfile`), not from the chunk-local seed. This is enforced by structure:
//! the chunk seed is only used for sub-chunk (<64 m) scatter.
//!
//! The same rule places the feature axes themselves (T-1040/T-1041): the channel
//! centreline and landform axis (`channel_anchor_m`) and the coast-face line
//! (`coast_anchor_m`) are **world-metre coordinates derived once per region**
//! from the region-scale seed. Voxel generators measure distance to these
//! anchors in continuous world coordinates — never from the world origin and
//! never folded into the 64 m chunk frame.
//!
//! ## D-010 compliance
//!
//! All derivation is integer arithmetic. The only f64 in this module is
//! `meander_wavelength_m` (positional physics, not a gate comparison), consistent
//! with D-239 §4 ("the warp is position math, not a structural decision").
//! `meander_wavelength_m` (positional physics, D-239 §4). Structural decisions
//! consume it only via deterministic i32 truncation (the `has_active_channel`
//! band reach), mirroring the established voxel.rs truncate-before-decision
//! convention — only correctly-rounded f64 +,-,*,/ feed the truncation.
//!
//! ## SeedDomain extension
//!
@@ -66,6 +75,32 @@ pub enum BasinDirection {
/// `BTreeMap` key — implements `Ord` for D-010 determinism.
pub type ChunkPos = (i32, i32);
/// Chunk edge length in metres.
pub const CHUNK_M: i32 = 64;
/// Region edge length in chunks (region ≈ 1 km = 16 chunks). Must match the
/// `>> REGION_CHUNKS_SHIFT` region-index mapping used for the region-scale seed.
const REGION_CHUNKS_SHIFT: u32 = 4;
/// Region edge length in metres (1 024 m).
const REGION_M: i32 = CHUNK_M << REGION_CHUNKS_SHIFT;
/// Margin keeping a region's feature anchor away from the region edge, so the
/// channel's full swept band (max meander amplitude wavelength/4 ≈ 162 m +
/// channel edge + levee band + warp bound ≈ 187 m) stays inside the region.
/// Cross-region feature continuity is the stage-2 Voronoi model (T-1040).
const ANCHOR_MARGIN_M: i32 = 192;
/// Seed-addressable anchor span within a region (REGION_M 2 × margin).
const ANCHOR_SPAN_M: i32 = REGION_M - 2 * ANCHOR_MARGIN_M;
/// Maximum levee band width in metres (`voxel::in_levee_band`: 4 + 3 jitter).
const LEVEE_BAND_MAX_M: i32 = 7;
/// Domain-warp displacement bound in metres (mirrors `domain_warp::WARP_BOUND`,
/// D-239 §4 ±8 m). Integer here — used only to widen the channel gate band.
const WARP_BOUND_M: i32 = 8;
// ---------------------------------------------------------------------------
// ChunkContext
// ---------------------------------------------------------------------------
@@ -82,11 +117,15 @@ pub type ChunkPos = (i32, i32);
/// Used by MeanderReach and AlluvialPlain voxel generators to place the channel.
/// - `meander_wavelength_m` — meander wavelength in metres. Derived from
/// region-level morphology (slope, moisture), seeded at region scale (> 64 m).
/// f64 for positional physics (D-239 §4); not used in any gate comparison.
/// - `has_active_channel` — whether a water channel is present in this chunk,
/// derived from the region's `ocean_fraction_q` and `river_threshold`.
/// f64 for positional physics (D-239 §4); structural decisions consume it
/// only via deterministic i32 truncation (the `has_active_channel` band).
/// - `has_active_channel` — whether a water channel is present in this chunk:
/// the region has water presence AND the channel's swept band around
/// `channel_anchor_m` crosses this chunk (T-1040).
/// - `channel_width_m` — channel width in metres (integer; D-010). 0 if no
/// active channel.
/// - `channel_anchor_m` / `coast_anchor_m` — region-anchored feature axes in
/// world metres (T-1040/T-1041, D-239 §10).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkContext {
/// Dominant basin-flow direction for this chunk's drainage catchment.
@@ -101,13 +140,28 @@ pub struct ChunkContext {
pub meander_wavelength_m: f64,
/// Whether this chunk contains an active water channel.
/// True when the region's `ocean_fraction_q` × `river_threshold` signal
/// indicates a perennial waterway crosses this chunk.
/// True when the region has water presence (`ocean_fraction_q` ≥ 10) AND
/// the channel's swept band around `channel_anchor_m` crosses this chunk's
/// cross-axis range (T-1040 — channels exist where the region says, not
/// region-wide and not only at the world origin).
pub has_active_channel: bool,
/// Active channel width in metres (integer; D-010). 0 if no active channel.
/// Derived from region morphology and slope.
pub channel_width_m: i32,
/// Cross-axis world-metre coordinate of the region's feature centreline:
/// channel/meander axis, fjord trough, gorge floor, braid-fan axis.
/// Derived once per region from the region-scale seed (T-1040/T-1041,
/// D-239 §10) — constant across all chunks of a region, so the feature is
/// continuous across chunk boundaries. Cross axis = x for N/S basins,
/// y for E/W basins.
pub channel_anchor_m: i32,
/// Along-axis (basin-axis) world-metre coordinate of the CliffCoast face
/// line. Region-scale (T-1041): one continuous coast per region, not a
/// 64 m sawtooth. Along axis = y for N/S basins, x for E/W basins.
pub coast_anchor_m: i32,
}
// ---------------------------------------------------------------------------
@@ -155,21 +209,44 @@ pub fn derive_chunk_context(
// is positional physics (D-239 §4, not a gate comparison).
let meander_wavelength_m = derive_meander_wavelength(region);
// Active channel — present when the region has meaningful water presence
// (ocean_fraction_q >= 10 indicates a perennial waterway or water body
// covers at least 10% of the region). The river_threshold governs D8
// drainage accumulation at a finer scale; at the chunk level we use
// ocean_fraction_q as the direct proxy for water presence (D-239 §10:
// the chunk carries one basin-direction, not a per-tile flow grid).
let has_active_channel = region.ocean_fraction_q >= 10;
// Channel width — derived from ocean_fraction_q (proxy for water presence
// at region scale); integer metres; 0 when no active channel.
let channel_width_m = if has_active_channel {
derive_channel_width(region)
} else {
0
// Region-anchored feature axes (T-1040/T-1041, D-239 §10): channel and
// landform centrelines have wavelength > 64 m, so their position derives
// from the region-scale seed — never the chunk frame or the world origin.
// Cross axis ⊥ basin_direction (x for N/S, y for E/W); along axis ∥ basin.
let (cross_chunk, along_chunk) = match basin_direction {
BasinDirection::North | BasinDirection::South => (chunk_pos.0, chunk_pos.1),
BasinDirection::East | BasinDirection::West => (chunk_pos.1, chunk_pos.0),
};
let channel_anchor_m = derive_region_anchor(cross_chunk, (region_seed.seed() >> 16) & 0xFFFF);
let coast_anchor_m = derive_region_anchor(along_chunk, (region_seed.seed() >> 32) & 0xFFFF);
// Active channel — water presence (ocean_fraction_q >= 10 indicates a
// perennial waterway or water body covers at least 10% of the region) AND
// the channel's swept band around the region anchor crosses this chunk
// (T-1040). The river_threshold governs D8 drainage accumulation at a finer
// scale; at the chunk level ocean_fraction_q is the direct proxy for water
// presence (D-239 §10: one basin-direction, not a per-tile flow grid).
//
// The band is generous (it must cover every chunk that can contain channel,
// levee, or warped-channel voxels — a gate-off chunk renders dry), using
// the larger MeanderReach amplitude (wavelength/4) for both channel families.
let channel_width = derive_channel_width(region);
let has_active_channel = region.ocean_fraction_q >= 10 && {
let wavelength_i = (meander_wavelength_m as i32).max(10);
let amplitude_max = (wavelength_i / 4).max(3);
let edge_max = (channel_width / 2).max(2) + 3; // half-width + max edge jitter
// Floor: the BraidedDelta belt reaches anchor ±(32 thread-centre + 4
// thread-half) before warp regardless of wavelength — the band must
// cover it even at the short-wavelength extreme (costs ≤4 m of extra
// gate generosity for the other families).
let reach = (amplitude_max + edge_max + LEVEE_BAND_MAX_M).max(32 + 4) + WARP_BOUND_M;
let cross_lo = cross_chunk * CHUNK_M;
let cross_hi = cross_lo + CHUNK_M - 1;
cross_lo <= channel_anchor_m + reach && cross_hi >= channel_anchor_m - reach
};
// Channel width — integer metres; 0 when no active channel in this chunk.
let channel_width_m = if has_active_channel { channel_width } else { 0 };
ChunkContext {
basin_direction,
@@ -177,9 +254,26 @@ pub fn derive_chunk_context(
meander_wavelength_m,
has_active_channel,
channel_width_m,
channel_anchor_m,
coast_anchor_m,
}
}
/// World-metre anchor coordinate for a region-scale feature axis on one axis.
///
/// The anchor sits in `[region_origin + ANCHOR_MARGIN_M, region_origin +
/// REGION_M ANCHOR_MARGIN_M)` so the feature's full swept band stays inside
/// its region (no cross-region band spill; region-seam continuity is the
/// stage-2 Voronoi model, T-1040). Same value for every chunk of the region:
/// the region index is `axis_chunk >> REGION_CHUNKS_SHIFT` (arithmetic shift =
/// floor division, correct for negative chunks) and `seed_bits` comes from the
/// shared region-scale seed. Integer arithmetic (D-010).
fn derive_region_anchor(axis_chunk: i32, seed_bits: u64) -> i32 {
let region_idx = axis_chunk >> REGION_CHUNKS_SHIFT;
let origin_m = region_idx * REGION_M;
origin_m + ANCHOR_MARGIN_M + (seed_bits % ANCHOR_SPAN_M as u64) as i32
}
/// Fold `(x, y)` chunk coordinates into a single u64 id for seed derivation.
///
/// Mirrors `domain_warp::pos_to_id` — zigzag-encode + Cantor pairing.
@@ -294,6 +388,17 @@ mod tests {
}
}
/// The chunk of region (0, 0) whose cross-range contains the region's
/// channel anchor — guaranteed inside the T-1040 channel band.
fn anchor_chunk_pos(world_seed: u64, body_id: &str, region: &RegionProfile) -> ChunkPos {
let probe = derive_chunk_context(world_seed, body_id, region, (0, 0));
let idx = probe.channel_anchor_m.div_euclid(CHUNK_M);
match probe.basin_direction {
BasinDirection::North | BasinDirection::South => (idx, 0),
BasinDirection::East | BasinDirection::West => (0, idx),
}
}
#[test]
fn derive_chunk_context_is_deterministic() {
let region = alluvial_region();
@@ -321,14 +426,80 @@ mod tests {
#[test]
fn alluvial_region_has_active_channel() {
let region = alluvial_region();
let ctx = derive_chunk_context(42, "GJ1c", &region, (5, 5));
// ocean_fraction_q=15 + temperate precip → should have active channel.
// T-1040: the channel is region-anchored — the chunk under the anchor
// must claim it (ocean_fraction_q=15 → water present).
let pos = anchor_chunk_pos(42, "GJ1c", &region);
let ctx = derive_chunk_context(42, "GJ1c", &region, pos);
assert!(
ctx.has_active_channel,
"alluvial plain with ocean_fraction_q=15 should have active channel"
"anchor-covering chunk of a watered region must have active channel"
);
}
#[test]
fn chunk_outside_channel_band_has_no_active_channel() {
// T-1040: channels are region-anchored, not region-wide. A chunk whose
// cross-range lies outside the channel's swept band (max reach < 192 m
// = 3 chunks) must not claim a channel — pre-fix every chunk of a
// watered region did, while voxels rendered dry floodplain.
let region = alluvial_region();
let (anchor_pos, probe) = {
let pos = anchor_chunk_pos(42, "GJ1c", &region);
(pos, derive_chunk_context(42, "GJ1c", &region, pos))
};
// 8 cross-chunks away (512 m) is past any band reach but still inside
// region (0, 0) — the anchor margin keeps the anchor chunk in [3, 12].
let anchor_idx = anchor_pos.0.max(anchor_pos.1);
let far_idx = if anchor_idx < 8 {
anchor_idx + 8
} else {
anchor_idx - 8
};
let far_pos = match probe.basin_direction {
BasinDirection::North | BasinDirection::South => (far_idx, 0),
BasinDirection::East | BasinDirection::West => (0, far_idx),
};
let far_ctx = derive_chunk_context(42, "GJ1c", &region, far_pos);
assert!(
!far_ctx.has_active_channel,
"chunk {far_pos:?} outside the channel band must not claim a channel"
);
assert_eq!(
far_ctx.channel_width_m, 0,
"no active channel → channel_width_m must be 0"
);
// Region-scale params stay constant across the region's chunks.
assert_eq!(far_ctx.channel_anchor_m, probe.channel_anchor_m);
assert_eq!(far_ctx.meander_phase, probe.meander_phase);
}
#[test]
fn anchors_constant_within_region_and_inside_it() {
// T-1040/T-1041: feature anchors are a region property — identical for
// every chunk of the region, and positioned inside the region's extent.
let region = alluvial_region();
let base = derive_chunk_context(42, "GJ1c", &region, (0, 0));
for pos in [(1, 0), (0, 1), (15, 15), (7, 12)] {
let ctx = derive_chunk_context(42, "GJ1c", &region, pos);
assert_eq!(
ctx.channel_anchor_m, base.channel_anchor_m,
"channel anchor must be region-constant (chunk {pos:?})"
);
assert_eq!(
ctx.coast_anchor_m, base.coast_anchor_m,
"coast anchor must be region-constant (chunk {pos:?})"
);
}
// Region (0, 0) spans [0, 1024) m on both axes.
assert!((0..1024).contains(&base.channel_anchor_m));
assert!((0..1024).contains(&base.coast_anchor_m));
// A different region derives its anchors inside its own extent.
let far = derive_chunk_context(42, "GJ1c", &region, (1000, -750));
assert!((62 * 1024..63 * 1024).contains(&far.channel_anchor_m) || (-47 * 1024..-46 * 1024).contains(&far.channel_anchor_m),
"far region anchor {} must lie inside its region extent (cross axis depends on basin direction)",
far.channel_anchor_m);
}
#[test]
fn dry_region_has_no_active_channel() {
let region = RegionProfile {
@@ -353,9 +524,11 @@ mod tests {
#[test]
fn channel_width_in_game_feel_range() {
// D-239 §9: river crossings 315 m.
// D-239 §9: river crossings 315 m. Measured on a chunk that carries
// the channel (T-1040 gating zeroes the width elsewhere).
let region = alluvial_region();
let ctx = derive_chunk_context(42, "GJ1c", &region, (5, 5));
let pos = anchor_chunk_pos(42, "GJ1c", &region);
let ctx = derive_chunk_context(42, "GJ1c", &region, pos);
assert!(
(3..=15).contains(&ctx.channel_width_m),
"channel_width_m {} out of game-feel range [3, 15]",
+242 -205
View File
@@ -413,10 +413,11 @@ pub fn derive_voxel_column(
///
/// ## Meander channel placement
///
/// The channel is placed by a sine-wave approximation centred on the chunk's
/// `basin_direction`. The perpendicular distance from the voxel to the wave
/// determines whether the voxel is in-channel. This avoids per-tile flow
/// grids while producing a spatially coherent channel (D-239 §10).
/// The channel is a sine-wave approximation running along `basin_direction`,
/// centred on the region-anchored `channel_anchor_m` (T-1040). The
/// perpendicular distance from the voxel to the wave determines whether the
/// voxel is in-channel. This avoids per-tile flow grids while producing a
/// spatially coherent channel continuous across chunk boundaries (D-239 §10).
///
/// Channel width is `chunk.channel_width_m` (game-feel range 315 m).
fn generate_alluvial_plain(
@@ -570,8 +571,9 @@ fn generate_lava_field(
/// - Valley floor is narrow (chokepoint — D-239 §9) with `Deep` water.
/// - Walls rise steeply; rocky, very little vegetation.
///
/// The fjord inlet occupies the centre of the chunk (along `basin_direction`).
/// Cross-section: wall | moraine | Deep water | moraine | wall.
/// The fjord trough runs along `basin_direction` at the region-anchored
/// centreline `chunk.channel_anchor_m` (T-1041, D-239 §10): ONE valley spans
/// the region's chunks. Cross-section: wall | moraine | Deep water | moraine | wall.
fn generate_fjord_wall(
region: &RegionProfile,
chunk: &ChunkContext,
@@ -580,8 +582,7 @@ fn generate_fjord_wall(
) -> VoxelColumn {
// ── Cross-valley coordinate ───────────────────────────────────────────
// Fjord runs along basin_direction; cross = perpendicular distance from
// the centre-line. We fold the absolute cross-distance to get a positive
// distance in [0, 32] (chunk half-width = 32 m).
// the centre-line.
let (along, cross) = match chunk.basin_direction {
crate::atlas::chunk_context::BasinDirection::North
| crate::atlas::chunk_context::BasinDirection::South => (voxel_pos.1, voxel_pos.0),
@@ -589,12 +590,10 @@ fn generate_fjord_wall(
| crate::atlas::chunk_context::BasinDirection::West => (voxel_pos.0, voxel_pos.1),
};
// Cross-channel absolute distance from centreline (fold into positive).
// We use modulo 64 to map the absolute coordinate into the chunk frame.
let cross_from_centre = {
let rel = cross.rem_euclid(64) - 32; // 32..+31
rel.abs() // 0..32
};
// Cross-channel distance from the region-anchored trough centreline
// (T-1041): continuous world coordinates — folding into the 64 m chunk
// frame repeated a complete fjord in every chunk (D-239 §10 violation).
let cross_from_centre = (cross - chunk.channel_anchor_m).abs();
// ── U-valley geometry ─────────────────────────────────────────────────
// The fjord bottom is 48 m wide (chokepoint, D-239 §9 "gorge floors 28 m").
@@ -646,8 +645,11 @@ fn generate_fjord_wall(
// Rock→vertical faces (D-239 §8). Elevation rises steeply from
// moraine_outer to the wall top.
let wall_dist = cross_from_centre - moraine_outer;
// Steep linear rise: 6 m per metre of cross-distance (approximates vertical).
let wall_rise = wall_dist * 6;
// Steep linear rise: 6 m per metre of cross-distance (approximates
// vertical), saturating at a 120 m plateau 20 m out — cross distance
// is region-scale post-T-1041, so the rise must not grow unbounded to
// the region edge (clamp before multiplying: i32 overflow otherwise).
let wall_rise = wall_dist.min(20) * 6;
// Cirques (grade ≥ 2): occasional hollowed pocket (24 m depression) at
// upper wall. seed-gated, ~1-in-8 frequency.
let cirque_depression = if glacier_grade >= 2 && (sub_chunk_seed >> 6) & 0x7 == 0 {
@@ -696,8 +698,10 @@ fn generate_fjord_wall(
/// Vertical rock face at the coastal water edge:
/// - `Rock` terrain (D-239 §8 Rock→vertical faces law).
/// - Sharp elevation drop to `Shallow`/`Deep` water at the base.
/// - The coast runs perpendicular to `basin_direction`; inland is high,
/// seaward is near-zero.
/// - The coast face sits on the region-anchored line `chunk.coast_anchor_m`
/// along the seaward axis (T-1041, D-239 §10): ONE continuous coast line
/// per region (warp-displaced), not a 64 m sawtooth. Inland of the line is
/// high ground; seaward is ocean.
/// - Narrow ledge/platform at the cliff base (passable ground, Shallow).
/// - Virtually no flat ground (D-239 §8 — Rock→vertical, not terraced).
fn generate_cliff_coast(
@@ -707,25 +711,21 @@ fn generate_cliff_coast(
sub_chunk_seed: u64,
) -> VoxelColumn {
// ── Coast orientation ─────────────────────────────────────────────────
// basin_direction points seaward (water flows to ocean). The seaward axis
// is the "along-flow" direction; cross = coast face direction.
// Cross-coordinate from the chunk centre in [32, +31].
let seaward_coord = match chunk.basin_direction {
crate::atlas::chunk_context::BasinDirection::North => -voxel_pos.1,
crate::atlas::chunk_context::BasinDirection::South => voxel_pos.1,
crate::atlas::chunk_context::BasinDirection::East => voxel_pos.0,
crate::atlas::chunk_context::BasinDirection::West => -voxel_pos.0,
// basin_direction points seaward (water flows to ocean). coast_d is the
// signed seaward distance from the region-anchored coast line in
// continuous world metres (T-1041): negative = inland, positive = seaward.
let coast_d = match chunk.basin_direction {
crate::atlas::chunk_context::BasinDirection::North => chunk.coast_anchor_m - voxel_pos.1,
crate::atlas::chunk_context::BasinDirection::South => voxel_pos.1 - chunk.coast_anchor_m,
crate::atlas::chunk_context::BasinDirection::East => voxel_pos.0 - chunk.coast_anchor_m,
crate::atlas::chunk_context::BasinDirection::West => chunk.coast_anchor_m - voxel_pos.0,
};
// Normalise into chunk frame [0, 63] with seaward = 63.
let seaward_norm = (seaward_coord.rem_euclid(64)).clamp(0, 63);
// ── Cliff face ────────────────────────────────────────────────────────
// The cliff face is at seaward_norm ≈ 4855 (roughly 3/4 across the chunk).
// Inland (0..47): high ground, rocky top.
// Face (48..55): steep drop (>6 m per metre — near-vertical, D-239 §8).
// Ledge (56..60): rocky beach platform, Shallow.
// Ocean (61..63): Deep water.
// Inland (coast_d < 0): high ground, rocky top.
// Face (0..8): steep drop (>6 m per metre — near-vertical, D-239 §8).
// Ledge (8..13): rocky beach platform, Shallow.
// Ocean (≥13): Deep water.
//
// Cliff height derived from region elevation.
let cliff_top_elev = (region.elev_q / 2).max(15); // at least 15 m cliff
@@ -735,18 +735,18 @@ fn generate_cliff_coast(
// Ledge variation noise: bits [3:5] → 02 m.
let ledge_noise = ((sub_chunk_seed >> 3) & 0x3) as i32;
let (elevation_m, water) = if seaward_norm < 48 {
let (elevation_m, water) = if coast_d < 0 {
// Inland high ground — rocky plateau.
let inland_noise = ((sub_chunk_seed >> 5) & 0xF) as i32 - 7; // [7, +8]
let elev = (cliff_top_elev + inland_noise).max(cliff_top_elev / 4);
(elev, Water::Dry)
} else if seaward_norm < 56 {
} else if coast_d < 8 {
// Cliff face — steep drop. Each metre seaward drops ~cliff_top/8 metres.
let face_dist = seaward_norm - 48; // 07
let face_dist = coast_d; // 07
let drop = (face_dist * cliff_top_elev) / 8;
let elev = (cliff_top_elev - drop + face_noise).max(1);
(elev, Water::Dry)
} else if seaward_norm < 61 {
} else if coast_d < 13 {
// Rocky ledge / splash zone.
let elev = (1 + ledge_noise).max(0);
(elev, Water::Shallow)
@@ -755,17 +755,19 @@ fn generate_cliff_coast(
(0, Water::Deep)
};
// Vegetation: nearly barren on rock faces; tiny scatter of Scrub on inland top.
// Vegetation: nearly barren on rock faces; tiny scatter of Scrub on the
// inland top (≥18 m / ≥28 m inland of the face — same offsets as the old
// chunk-frame layout, now measured from the region-anchored coast line).
let vegetation = match region.vegetation_class {
VegetationClass::Forest | VegetationClass::RiparianThicket => {
if seaward_norm < 30 && (sub_chunk_seed >> 10) & 0x7 < 3 {
if coast_d < -18 && (sub_chunk_seed >> 10) & 0x7 < 3 {
Vegetation::Scrub
} else {
Vegetation::Barren
}
}
VegetationClass::Scrub | VegetationClass::RiparianScrub => {
if seaward_norm < 20 && (sub_chunk_seed >> 10) & 0x3 == 0 {
if coast_d < -28 && (sub_chunk_seed >> 10) & 0x3 == 0 {
Vegetation::Scrub
} else {
Vegetation::Barren
@@ -795,9 +797,12 @@ fn generate_cliff_coast(
/// - Multiple shallow distributary channels (NOT single-thread meander).
/// - Very low elevation — near sea level (D-239 §8 drainage monotonicity:
/// mouths at sea level).
/// - Channel determination: 3 independent braided threads, each offset by
/// ~1/3 of the chunk width and width 38 m. Thread presence is seeded at
/// region scale (meander_phase encodes thread offsets).
/// - Channel determination: 3 independent braided threads anastomosing
/// across a fan belt centred on the region-anchored axis
/// `chunk.channel_anchor_m` (T-1041, D-239 §10), each thread 38 m wide.
/// Thread offsets are seeded at region scale (meander_phase encodes them),
/// so the threads run continuously across the region's chunks instead of
/// restarting every 64 m.
/// - `Shallow` in-channel; `Dry` on gravel bars between threads.
fn generate_braided_delta(
region: &RegionProfile,
@@ -821,11 +826,9 @@ fn generate_braided_delta(
| crate::atlas::chunk_context::BasinDirection::West => (voxel_pos.0, voxel_pos.1),
};
// Map cross into chunk frame [0, 63].
let cross_norm = cross.rem_euclid(64);
// Three braided threads with well-separated centres derived from meander_phase.
// Thread centres spread across the chunk width (D-239 §8 Gravel→braided).
// Three braided threads with well-separated centres derived from meander_phase,
// spread across a 64 m fan belt centred on the region-anchored axis
// (T-1041 — world cross coordinates, constant for all chunks of the region).
// Each centre uses independent bit-mixing (splitmix64-style) so threads are
// provably separated regardless of phase value — no correlated bit-slices.
let phase_u64 = chunk.meander_phase as u64;
@@ -851,21 +854,19 @@ fn generate_braided_delta(
h ^ (h >> 31)
};
let thread_centres: [i32; 3] = [
(mix_a % 64) as i32,
(mix_b % 64) as i32,
(mix_c % 64) as i32,
chunk.channel_anchor_m - 32 + (mix_a % 64) as i32,
chunk.channel_anchor_m - 32 + (mix_b % 64) as i32,
chunk.channel_anchor_m - 32 + (mix_c % 64) as i32,
];
// Thread width: 36 m half-width derived from channel_width_m.
let thread_half = (chunk.channel_width_m / 2).clamp(2, 4);
// Check if voxel is in any braided thread.
// True modular distance on a [0, 64) ring — handles wrap at both edges.
let in_any_thread = thread_centres.iter().any(|&centre| {
let d = (cross_norm - centre).rem_euclid(64);
let dist = d.min(64 - d);
dist <= thread_half
});
// Check if voxel is in any braided thread — continuous world-coordinate
// distance (the old [0, 64) ring fold restarted the threads every chunk).
let in_any_thread = thread_centres
.iter()
.any(|&centre| (cross - centre).abs() <= thread_half);
let (elevation_m, water) = if in_any_thread {
// In a distributary channel — at or just below base elevation.
@@ -1036,6 +1037,9 @@ fn generate_dune_strand(
/// - Massive elevation differential: walls are 2060 m above the floor.
/// - `Shallow` water in the incised channel at the floor.
/// - Cross-section: wall | wall | narrow floor (with channel) | wall | wall.
/// - The gorge runs along `basin_direction` at the region-anchored
/// centreline `chunk.channel_anchor_m` (T-1041, D-239 §10): ONE gorge
/// spans the region's chunks.
fn generate_incised_gorge(
region: &RegionProfile,
chunk: &ChunkContext,
@@ -1050,11 +1054,10 @@ fn generate_incised_gorge(
| crate::atlas::chunk_context::BasinDirection::West => (voxel_pos.0, voxel_pos.1),
};
// Distance from gorge centre-line in [0, 32].
let cross_from_centre = {
let rel = cross.rem_euclid(64) - 32; // 32..+31
rel.abs()
};
// Distance from the region-anchored gorge centreline (T-1041): continuous
// world coordinates — folding into the 64 m chunk frame carved a gorge in
// every chunk (D-239 §10 violation).
let cross_from_centre = (cross - chunk.channel_anchor_m).abs();
// ── Gorge floor width (D-239 §9: 28 m) ──────────────────────────────
// Floor half-width: 14 m (total 28 m). Derived from channel_width_m
@@ -1083,8 +1086,11 @@ fn generate_incised_gorge(
// Elevation rises steeply: each metre of cross-distance from floor edge
// adds wall_step metres. We model this as a steep ramp.
let wall_dist = cross_from_centre - floor_half;
// Steep wall: 8 m rise per metre of wall distance (near-vertical, §8 Rock law).
let wall_rise = wall_dist * 8;
// Steep wall: 8 m rise per metre of wall distance (near-vertical, §8
// Rock law). The rise saturates at the wall_top clamp below; clamp the
// region-scale distance (T-1041) before multiplying — i32 overflow at
// extreme coordinates otherwise.
let wall_rise = wall_dist.min(16) * 8;
let elev = (floor_elev + wall_rise + wall_noise).clamp(floor_elev, wall_top);
(elev, Water::Dry)
};
@@ -1580,7 +1586,8 @@ fn compute_meander_reach_channel(
amplitude - (2 * amplitude * (angle_mod - half_wl)) / half_wl.max(1)
};
let perp_distance = (cross - meander_displacement).abs();
// Region-anchored centreline (T-1040) — same anchor as AlluvialPlain.
let perp_distance = (cross - chunk.channel_anchor_m - meander_displacement).abs();
// Edge jitter from sub-chunk seed (±1 m), same as AlluvialPlain.
let edge_jitter = (sub_chunk_seed & 0x3) as i32;
@@ -1621,7 +1628,8 @@ fn in_levee_band(voxel_pos: VoxelPos, chunk: &ChunkContext, sub_chunk_seed: u64)
amplitude - (2 * amplitude * (angle_mod - half_wl)) / half_wl.max(1)
};
let perp_distance = (cross - meander_displacement).abs();
// Region-anchored centreline (T-1040) — same anchor as the channel itself.
let perp_distance = (cross - chunk.channel_anchor_m - meander_displacement).abs();
let edge_jitter = (sub_chunk_seed & 0x3) as i32;
let half_width = (chunk.channel_width_m / 2).max(2);
@@ -1690,8 +1698,10 @@ fn compute_channel_state(
amplitude - (2 * amplitude * (angle_mod - half_wl)) / half_wl
};
// Perpendicular distance from voxel to channel centreline.
let perp_distance = (cross - meander_displacement).abs();
// Perpendicular distance from voxel to the region-anchored channel
// centreline (T-1040): the channel axis is `chunk.channel_anchor_m` on the
// cross axis — a region-scale world coordinate, never the world origin.
let perp_distance = (cross - chunk.channel_anchor_m - meander_displacement).abs();
// Sub-chunk noise: ±1 m jitter on the channel edge (wavelength < 64 m).
// Use low bits of seed for integer jitter — D-010.
@@ -1935,7 +1945,7 @@ pub const DEFAULT_VOXEL_CACHE_CAPACITY: usize = 8192;
#[cfg(test)]
mod tests {
use super::*;
use crate::atlas::chunk_context::derive_chunk_context;
use crate::atlas::chunk_context::{derive_chunk_context, BasinDirection};
use crate::atlas::region_profile::{
GlaciationGrade, PrecipitationClass, TectonicClass, VegetationClass,
};
@@ -1944,6 +1954,29 @@ mod tests {
// Test fixtures
// -----------------------------------------------------------------------
/// The ChunkContext of the region-(0,0) chunk whose cross-range contains
/// the channel anchor — guaranteed inside the T-1040 channel band.
/// Feature placement is region-anchored, so tests sample around
/// `chunk.channel_anchor_m` instead of assuming chunk-frame positions.
fn anchor_chunk(seed: u64, body: &str, region: &RegionProfile) -> ChunkContext {
let probe = derive_chunk_context(seed, body, region, (0, 0));
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, region, pos)
}
/// Map a (cross, along) coordinate pair to (tile_x, tile_y) for the
/// chunk's basin axes — cross ⊥ basin_direction, along ∥ basin_direction.
fn cross_along_to_tile(chunk: &ChunkContext, cross: i32, along: i32) -> (i32, i32) {
match chunk.basin_direction {
BasinDirection::North | BasinDirection::South => (cross, along),
BasinDirection::East | BasinDirection::West => (along, cross),
}
}
fn alluvial_region() -> RegionProfile {
RegionProfile {
morphology_zone: MorphologyZone::AlluvialPlain,
@@ -2340,33 +2373,19 @@ mod tests {
#[test]
fn fjord_wall_centre_has_deep_water() {
// The fjord inlet (centreline of the chunk) should have Deep water.
// The fjord trough (region-anchored centreline, T-1041) must have Deep
// water. Sample a small window around the anchor — the domain warp can
// displace any single tile off the 48 m floor.
let region = fjord_region();
// Use a chunk centred around x=0, basin direction North/South.
// The fjord centre is at cross = 0 relative to chunk centre.
// Cross coordinate = x, so at x=32 (the mid-chunk point in [0,63]).
// We sample at tile_x=32 to hit the centre.
let chunk = derive_chunk_context(42, "Fjordheim", &region, (0, 0));
// Guard: this sweep assumes N/S basin (cross axis = tile_x). If the seed
// yields E/W the test would sweep the wrong axis and pass vacuously.
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);
derive_voxel_column(42, "Fjordheim", &region, &chunk, tx, ty).water == Water::Deep
});
assert!(
matches!(
chunk.basin_direction,
crate::atlas::chunk_context::BasinDirection::North
| crate::atlas::chunk_context::BasinDirection::South
),
"test assumes N/S basin direction; pick a different seed if this fires"
);
// Find a position near the fjord centreline. We pick x positions that
// map to cross_from_centre = 0 (chunk centre). At x=0 in the grid,
// rem_euclid(64) - 32 = -32 → abs=32. At x=32, rem_euclid(64)=32, 32-32=0.
// So tile_x near 32 (in basin_direction perpendicular axis for North).
// For North/South basin direction: cross = voxel_pos.0.
let col_centre = derive_voxel_column(42, "Fjordheim", &region, &chunk, 32, 50);
assert_eq!(
col_centre.water,
Water::Deep,
"FjordWall centreline must have Deep water (the fjord inlet)"
any_deep,
"FjordWall centreline (anchor {anchor}) must have Deep water (the fjord inlet)"
);
}
@@ -2375,24 +2394,23 @@ mod tests {
// Wall elevation must be substantially higher than fjord floor.
let region = fjord_region();
let chunk = derive_chunk_context(42, "Fjordheim", &region, (0, 0));
// Guard: this test assumes N/S basin (cross axis = tile_x).
let anchor = chunk.channel_anchor_m;
// Floor: the lowest tile in the trough window around the anchor.
let floor_elev = (anchor - 4..=anchor + 4)
.map(|c| {
let (tx, ty) = cross_along_to_tile(&chunk, c, 50);
derive_voxel_column(42, "Fjordheim", &region, &chunk, tx, ty).elevation_m
})
.min()
.unwrap();
// Wall: 30 m off the centreline (past the moraine band at ≤14 m).
let (wx, wy) = cross_along_to_tile(&chunk, anchor - 30, 50);
let wall_col = derive_voxel_column(42, "Fjordheim", &region, &chunk, wx, wy);
assert!(
matches!(
chunk.basin_direction,
crate::atlas::chunk_context::BasinDirection::North
| crate::atlas::chunk_context::BasinDirection::South
),
"test assumes N/S basin direction; pick a different seed if this fires"
);
// Floor at centre (x=32 for North/South basin).
let floor_col = derive_voxel_column(42, "Fjordheim", &region, &chunk, 32, 50);
// Wall far from centre (x = 0 or x = 63 = cross_from_centre ≥ 32).
let wall_col = derive_voxel_column(42, "Fjordheim", &region, &chunk, 0, 50);
assert!(
wall_col.elevation_m > floor_col.elevation_m,
wall_col.elevation_m > floor_elev,
"FjordWall walls ({}) must be higher than the floor ({})",
wall_col.elevation_m,
floor_col.elevation_m
floor_elev
);
}
@@ -2422,21 +2440,15 @@ mod tests {
// D-239 §9: fjord floor (Deep water) must be 28 m wide across a cross-section.
// With floor_half = glacier_grade.clamp(2,4) and grade ≥ 2 (upstream gate),
// total Deep-water width = 2×floor_half + noise ∈ [4, 8] m.
// Cross-section spans the region-anchored trough (T-1041).
let region = fjord_region(); // glaciation_grade = Moderate (= 2)
let chunk = derive_chunk_context(42, "Fjordheim", &region, (0, 0));
// Guard: sweep assumes N/S basin (cross axis = tile_x).
assert!(
matches!(
chunk.basin_direction,
crate::atlas::chunk_context::BasinDirection::North
| crate::atlas::chunk_context::BasinDirection::South
),
"test assumes N/S basin direction; pick a different seed if this fires"
);
// Count Deep-water tiles across the 64-tile cross-section.
let deep_tiles = (0..64i32)
.filter(|&x| {
derive_voxel_column(42, "Fjordheim", &region, &chunk, x, 50).water == Water::Deep
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)
.filter(|&c| {
let (tx, ty) = cross_along_to_tile(&chunk, c, 50);
derive_voxel_column(42, "Fjordheim", &region, &chunk, tx, ty).water == Water::Deep
})
.count();
assert!(
@@ -2480,15 +2492,17 @@ mod tests {
fn cliff_coast_steep_elevation_drop_near_water() {
// Verify the CliffCoast structural property: there must be tiles with Deep
// water AND there must be a significant elevation range (high ground + ocean).
// We don't hard-code which tile_x is "inland" vs "ocean" because basin_direction
// and domain warp interact — instead we sample a full transect and verify the
// property that: (a) some tiles have Deep water, (b) the max elevation in
// the transect exceeds the ocean elevation by > 10 m (the "vertical face" law).
// The coast face sits on the region-anchored line `coast_anchor_m` along
// the seaward (basin) axis (T-1041) — sample a transect across it.
let region = cliff_region();
let chunk = derive_chunk_context(42, "Velen", &region, (0, 0));
// Sample 64 cross-coast positions at a fixed along-coast coordinate.
let cols: Vec<VoxelColumn> = (0..64i32)
.map(|x| derive_voxel_column(42, "Velen", &region, &chunk, x, 50))
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)
.map(|a| {
let (tx, ty) = cross_along_to_tile(&chunk, 50, a);
derive_voxel_column(42, "Velen", &region, &chunk, tx, ty)
})
.collect();
let has_deep = cols.iter().any(|c| c.water == Water::Deep);
let max_elev = cols.iter().map(|c| c.elevation_m).max().unwrap();
@@ -2577,18 +2591,22 @@ mod tests {
#[test]
fn braided_delta_has_multiple_shallow_channels() {
// Gravel→braided (D-239 §8): NOT single-thread. Multiple channels means
// a range of positions across the chunk width should contain some Shallow water.
// a range of positions across the fan belt should contain some Shallow
// water. The belt is centred on the region-anchored fan axis (T-1041).
let region = delta_region();
let chunk = derive_chunk_context(42, "delta_body", &region, (0, 0));
// Sample 64 positions across the chunk width (cross-section).
let shallow_count = (0..64i32)
.filter(|&x| {
derive_voxel_column(42, "delta_body", &region, &chunk, x, 100).water
let chunk = anchor_chunk(42, "delta_body", &region);
let anchor = chunk.channel_anchor_m;
// Sample 80 positions across the fan belt (anchor ± 40 covers the
// 64 m belt plus thread width).
let shallow_count = (anchor - 40..anchor + 40)
.filter(|&c| {
let (tx, ty) = cross_along_to_tile(&chunk, c, 100);
derive_voxel_column(42, "delta_body", &region, &chunk, tx, ty).water
== Water::Shallow
})
.count();
// Three braided threads, each 38 m wide → expect at least 6 shallow tiles
// in a 64-tile transect.
// in the belt transect.
assert!(
shallow_count >= 6,
"BraidedDelta must have multiple braided channels; only {shallow_count} shallow in transect"
@@ -2602,9 +2620,13 @@ mod tests {
// We verify by checking that Shallow water appears in at least two
// disjoint groups separated by Dry ground.
let region = delta_region();
let chunk = derive_chunk_context(42, "delta_body", &region, (0, 0));
let waters: Vec<Water> = (0..64i32)
.map(|x| derive_voxel_column(42, "delta_body", &region, &chunk, x, 100).water)
let chunk = anchor_chunk(42, "delta_body", &region);
let anchor = chunk.channel_anchor_m;
let waters: Vec<Water> = (anchor - 40..anchor + 40)
.map(|c| {
let (tx, ty) = cross_along_to_tile(&chunk, c, 100);
derive_voxel_column(42, "delta_body", &region, &chunk, tx, ty).water
})
.collect();
// Count Dry→Shallow transitions = number of channel entrances.
let transitions = waters
@@ -2766,25 +2788,15 @@ mod tests {
fn incised_gorge_floor_width_in_spec() {
// D-239 §9: gorge floors 28 m wide. We verify by checking that the
// total width of the Shallow water zone (the incised channel on the floor)
// across a cross-section is within [2, 8] m.
// across a cross-section is within [2, 8] m. Cross-section spans the
// region-anchored gorge centreline (T-1041).
let region = gorge_region();
let chunk = derive_chunk_context(42, "gorge_body", &region, (0, 0));
// Guard: this sweep assumes N/S basin (cross axis = tile_x). If the seed
// yields E/W the sweep would be along the gorge, not across it.
assert!(
matches!(
chunk.basin_direction,
crate::atlas::chunk_context::BasinDirection::North
| crate::atlas::chunk_context::BasinDirection::South
),
"test assumes N/S basin direction; pick a different seed if this fires"
);
// Sample 64 cross-positions at a fixed along-axis position.
// For North/South basin direction: cross = tile_x.
// The floor should be ≤ 8 tiles (= 8 m) wide.
let floor_tiles = (0..64i32)
.filter(|&x| {
derive_voxel_column(42, "gorge_body", &region, &chunk, x, 100).water
let anchor = chunk.channel_anchor_m;
let floor_tiles = (anchor - 32..anchor + 32)
.filter(|&c| {
let (tx, ty) = cross_along_to_tile(&chunk, c, 100);
derive_voxel_column(42, "gorge_body", &region, &chunk, tx, ty).water
== Water::Shallow
})
.count();
@@ -2799,15 +2811,24 @@ mod tests {
// Walls must be substantially higher than the gorge floor.
let region = gorge_region();
let chunk = derive_chunk_context(42, "gorge_body", &region, (0, 0));
// Floor: tile at cross_from_centre = 0, i.e. tile_x = 32.
let floor_col = derive_voxel_column(42, "gorge_body", &region, &chunk, 32, 100);
// Wall: tile at cross_from_centre = 30, i.e. tile_x = 62 or tile_x = 2.
let wall_col = derive_voxel_column(42, "gorge_body", &region, &chunk, 62, 100);
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 28 m floor).
let floor_elev = (anchor - 4..=anchor + 4)
.map(|c| {
let (tx, ty) = cross_along_to_tile(&chunk, c, 100);
derive_voxel_column(42, "gorge_body", &region, &chunk, tx, ty).elevation_m
})
.min()
.unwrap();
// Wall: 30 m off the centreline.
let (wx, wy) = cross_along_to_tile(&chunk, anchor + 30, 100);
let wall_col = derive_voxel_column(42, "gorge_body", &region, &chunk, wx, wy);
assert!(
wall_col.elevation_m > floor_col.elevation_m + 10,
wall_col.elevation_m > floor_elev + 10,
"IncisedGorge walls ({}) must be >> gorge floor ({})",
wall_col.elevation_m,
floor_col.elevation_m
floor_elev
);
}
@@ -2906,18 +2927,21 @@ mod tests {
#[test]
fn meander_reach_has_active_channel() {
// MeanderReach must have a channel with Shallow/Deep water somewhere in a transect.
// MeanderReach must have a channel with Shallow/Deep water somewhere in
// a transect across the region-anchored channel band (T-1040).
let region = meander_region();
let chunk = derive_chunk_context(42, "meander_body", &region, (0, 0));
// The chunk has_active_channel = true (ocean_fraction_q=20 >= 10).
let chunk = anchor_chunk(42, "meander_body", &region);
// The anchor-covering chunk has an active channel (ocean_fraction_q=20 >= 10).
assert!(
chunk.has_active_channel,
"MeanderReach test chunk must have active channel"
"MeanderReach anchor chunk must have active channel"
);
let anchor = chunk.channel_anchor_m;
// Sample a wide cross-section and find at least one wet tile.
let wet_count = (-200..200i32)
.filter(|&x| {
let col = derive_voxel_column(42, "meander_body", &region, &chunk, x, 50);
let wet_count = (anchor - 200..anchor + 200)
.filter(|&c| {
let (tx, ty) = cross_along_to_tile(&chunk, c, 50);
let col = derive_voxel_column(42, "meander_body", &region, &chunk, tx, ty);
col.water != Water::Dry
})
.count();
@@ -2929,31 +2953,34 @@ mod tests {
#[test]
fn meander_reach_channel_below_levee() {
// D-239 §9 ElevationDelta: channel tiles must have lower elevation than
// off-channel tiles in the vicinity.
// D-239 §9 ElevationDelta: channel tiles must sit below off-channel
// tiles in the vicinity. Per-voxel micro-relief is ±4 m, so a single
// wet/dry tile pair is noise — compare the channel bottom (min wet)
// against the floodplain/levee top (max dry) across the transect.
let region = meander_region();
let chunk = derive_chunk_context(42, "meander_body", &region, (0, 0));
// Find a channel tile and a floodplain tile at the same along-axis position.
let mut channel_elev: Option<i32> = None;
let mut plain_elev: Option<i32> = None;
for tx in -200..200i32 {
let col = derive_voxel_column(42, "meander_body", &region, &chunk, tx, 50);
if col.water != Water::Dry && channel_elev.is_none() {
channel_elev = Some(col.elevation_m);
}
if col.water == Water::Dry {
plain_elev = Some(col.elevation_m);
}
if channel_elev.is_some() && plain_elev.is_some() {
break;
let chunk = anchor_chunk(42, "meander_body", &region);
let anchor = chunk.channel_anchor_m;
let mut min_wet: Option<i32> = None;
let mut max_dry: Option<i32> = None;
for c in anchor - 200..anchor + 200 {
let (tx, ty) = cross_along_to_tile(&chunk, c, 50);
let col = derive_voxel_column(42, "meander_body", &region, &chunk, tx, ty);
if col.water != Water::Dry {
min_wet = Some(min_wet.map_or(col.elevation_m, |e| e.min(col.elevation_m)));
} else {
max_dry = Some(max_dry.map_or(col.elevation_m, |e| e.max(col.elevation_m)));
}
}
if let (Some(ch_elev), Some(pl_elev)) = (channel_elev, plain_elev) {
assert!(
ch_elev <= pl_elev,
"MeanderReach: channel elevation ({ch_elev}) must be <= floodplain ({pl_elev})"
);
}
// Both tile kinds must exist — a transect across the anchor that finds
// no channel means the placement regressed (the law must be exercised).
let (min_wet, max_dry) = (
min_wet.expect("transect across the anchor must contain channel tiles"),
max_dry.expect("transect must contain floodplain tiles"),
);
assert!(
min_wet <= max_dry,
"MeanderReach: channel bottom ({min_wet}) must be <= floodplain/levee top ({max_dry})"
);
}
#[test]
@@ -2984,31 +3011,41 @@ mod tests {
morphology_zone: MorphologyZone::AlluvialPlain,
..shared_region_base
};
// Same seed + body → same ChunkContext (same wavelength, phase, channel_width).
let meander_chunk = derive_chunk_context(42, "sinuosity_body", &meander_reg, (0, 0));
let alluvial_chunk = derive_chunk_context(42, "sinuosity_body", &alluvial_reg, (0, 0));
// Same seed + body → same ChunkContext (same wavelength, phase, anchor,
// channel_width). Use the anchor-covering chunk (T-1040 band gating).
let meander_chunk = anchor_chunk(42, "sinuosity_body", &meander_reg);
let alluvial_chunk = anchor_chunk(42, "sinuosity_body", &alluvial_reg);
// Verify the chunks share the same meander params (confirming the test setup).
assert_eq!(
meander_chunk.meander_phase, alluvial_chunk.meander_phase,
"test setup requires identical chunk params"
);
assert_eq!(
meander_chunk.channel_anchor_m, alluvial_chunk.channel_anchor_m,
"test setup requires identical channel anchors"
);
assert!(
meander_chunk.has_active_channel && alluvial_chunk.has_active_channel,
"both chunks must have active channels for the test to be meaningful"
);
// Collect wet-tile x-positions over one full wavelength transect (at y=50).
// We scan far enough to capture the full meander swing — amplitude for
// MeanderReach is wl/4, for AlluvialPlain wl/6, so over a [400,+400] sweep
// both complete multiple full cycles and their centreline positions diverge.
let meander_wet: Vec<i32> = (-400..400i32)
.filter(|&x| {
derive_voxel_column(42, "sinuosity_body", &meander_reg, &meander_chunk, x, 50).water
// Collect wet-tile cross-positions over a transect at along=50, centred
// on the shared anchor. We scan far enough to capture the full meander
// swing — amplitude for MeanderReach is wl/4, for AlluvialPlain wl/6,
// so over a ±400 sweep both complete multiple full cycles and their
// centreline positions diverge.
let anchor = meander_chunk.channel_anchor_m;
let meander_wet: Vec<i32> = (anchor - 400..anchor + 400)
.filter(|&c| {
let (tx, ty) = cross_along_to_tile(&meander_chunk, c, 50);
derive_voxel_column(42, "sinuosity_body", &meander_reg, &meander_chunk, tx, ty)
.water
!= Water::Dry
})
.collect();
let alluvial_wet: Vec<i32> = (-400..400i32)
.filter(|&x| {
derive_voxel_column(42, "sinuosity_body", &alluvial_reg, &alluvial_chunk, x, 50)
let alluvial_wet: Vec<i32> = (anchor - 400..anchor + 400)
.filter(|&c| {
let (tx, ty) = cross_along_to_tile(&alluvial_chunk, c, 50);
derive_voxel_column(42, "sinuosity_body", &alluvial_reg, &alluvial_chunk, tx, ty)
.water
!= Water::Dry
})
+558 -43
View File
@@ -39,7 +39,7 @@
use std::path::PathBuf;
use std::time::Instant;
use settled_reach_server::atlas::chunk_context::derive_chunk_context;
use settled_reach_server::atlas::chunk_context::{derive_chunk_context, BasinDirection, ChunkPos};
use settled_reach_server::atlas::drainage;
use settled_reach_server::atlas::features::TerrainAnalysis;
use settled_reach_server::atlas::heightmap::BodyHeightmap;
@@ -100,6 +100,37 @@ fn derive_golden(
}
}
/// Compute the chunk position + tile coordinates sitting ON the region's
/// channel anchor at a given along-axis chunk index (T-1040/T-1041): feature
/// placement is region-anchored, so the golden pins a voxel on the anchor
/// column. Relocates automatically if the anchor derivation changes — which
/// flips the golden values anyway.
///
/// `along_chunk` must stay within region (0, 0) — i.e. in `0..16` — so the
/// probed anchor belongs to the same region as the returned chunk.
fn anchor_golden_pos(
seed: u64,
body_id: &str,
region: &RegionProfile,
along_chunk: i32,
) -> ((i32, i32), i32, i32) {
assert!(
(0..16).contains(&along_chunk),
"along_chunk must stay within region (0, 0)"
);
let probe = derive_chunk_context(seed, body_id, region, (0, 0));
let anchor = probe.channel_anchor_m;
let along_tile = along_chunk * 64 + 32;
match probe.basin_direction {
BasinDirection::North | BasinDirection::South => {
((anchor.div_euclid(64), along_chunk), anchor, along_tile)
}
BasinDirection::East | BasinDirection::West => {
((along_chunk, anchor.div_euclid(64)), along_tile, anchor)
}
}
}
/// The three fixed golden inputs. Varied families and climate states.
fn golden_cases() -> Vec<(
&'static str,
@@ -110,26 +141,45 @@ fn golden_cases() -> Vec<(
i32,
i32,
)> {
// Cases A and C pin voxels on the region channel anchor (T-1040/T-1041):
// the channel/trough centreline is region-anchored, not at the world
// origin (A) or the chunk centre (C).
let alluvial_region = make_region(
MorphologyZone::AlluvialPlain,
TectonicClass::Active,
GlaciationGrade::None,
6,
22,
18,
68,
Some(12.0),
VegetationClass::Forest,
);
let (alluvial_chunk_pos, alluvial_tx, alluvial_ty) =
anchor_golden_pos(0xdeadbeef_cafebabe_u64, "GJ144d", &alluvial_region, 12);
let fjord_region = make_region(
MorphologyZone::Fjord,
TectonicClass::Active,
GlaciationGrade::Moderate,
55,
60,
28,
55,
Some(-8.0),
VegetationClass::Barren,
);
let (fjord_chunk_pos, fjord_tx, fjord_ty) =
anchor_golden_pos(0xfeedface_0badc0de_u64, "GJ447c", &fjord_region, 5);
vec![
// Case A: AlluvialPlain — temperate forest, active channel.
// Case A: AlluvialPlain — temperate forest, on the active-channel anchor.
(
"alluvial_forest_active_channel",
0xdeadbeef_cafebabe_u64,
"GJ144d",
make_region(
MorphologyZone::AlluvialPlain,
TectonicClass::Active,
GlaciationGrade::None,
6,
22,
18,
68,
Some(12.0),
VegetationClass::Forest,
),
(10, 20),
640,
1280,
alluvial_region,
alluvial_chunk_pos,
alluvial_tx,
alluvial_ty,
),
// Case B: LavaField — barren volcanic, no channel.
(
@@ -151,26 +201,16 @@ fn golden_cases() -> Vec<(
320,
448,
),
// Case C: FjordWall — glaciated, rocky walls, deep water in trough.
// Case C: FjordWall — glaciated, rocky walls; tile on the region-anchored
// trough centreline (Deep water unless the warp nudges it onto the floor edge).
(
"fjord_wall_glaciated",
0xfeedface_0badc0de_u64,
"GJ447c",
make_region(
MorphologyZone::Fjord,
TectonicClass::Active,
GlaciationGrade::Moderate,
55,
60,
28,
55,
Some(-8.0),
VegetationClass::Barren,
),
(14, 8),
// Centre of the chunk (fjord trough) — should be Deep water, Rock.
14 * 64 + 32,
8 * 64 + 32,
fjord_region,
fjord_chunk_pos,
fjord_tx,
fjord_ty,
),
]
}
@@ -320,8 +360,12 @@ fn law_drainage_monotonicity_alluvial_sweep() {
VegetationClass::Forest,
);
let mut checked = false;
for (cx, cy) in [(0, 0), (1, 0), (0, 1), (4, 4), (8, 3)] {
checked |= assert_drainage_monotonicity(42, "GJ144d", "AlluvialPlain", &region, (cx, cy));
// Sweep a spread of region chunks plus the channel-anchor band (T-1040:
// the channel is region-anchored, so only band chunks carry wet tiles).
let mut positions = vec![(0, 0), (1, 0), (0, 1), (4, 4), (8, 3)];
positions.extend(anchor_band_chunks(42, "GJ144d", &region, (0, 0)));
for pos in positions {
checked |= assert_drainage_monotonicity(42, "GJ144d", "AlluvialPlain", &region, pos);
}
assert!(
checked,
@@ -344,8 +388,10 @@ fn law_drainage_monotonicity_meander_sweep() {
VegetationClass::Forest,
);
let mut checked = false;
for (cx, cy) in [(0, 0), (2, 1), (5, 5)] {
checked |= assert_drainage_monotonicity(99, "GJ447c", "MeanderReach", &region, (cx, cy));
let mut positions = vec![(0, 0), (2, 1), (5, 5)];
positions.extend(anchor_band_chunks(99, "GJ447c", &region, (0, 0)));
for pos in positions {
checked |= assert_drainage_monotonicity(99, "GJ447c", "MeanderReach", &region, pos);
}
assert!(
checked,
@@ -369,16 +415,27 @@ fn law_drainage_monotonicity_fjord_floor_at_sea_level() {
Some(-8.0),
VegetationClass::Barren,
);
let chunk = derive_chunk_context(42, "fjord_body", &region, (0, 0));
// The fjord trough is region-anchored (T-1041): scan the chunk whose
// cross-range contains the channel anchor — only that chunk column carries
// the deep-water trough.
let probe = derive_chunk_context(42, "fjord_body", &region, (0, 0));
let anchor_idx = probe.channel_anchor_m.div_euclid(64);
let chunk_pos = match probe.basin_direction {
BasinDirection::North | BasinDirection::South => (anchor_idx, 0),
BasinDirection::East | BasinDirection::West => (0, anchor_idx),
};
let chunk = derive_chunk_context(42, "fjord_body", &region, chunk_pos);
let (base_x, base_y) = (chunk_pos.0 * 64, chunk_pos.1 * 64);
let mut deep_elevs: Vec<i32> = vec![];
let mut dry_elevs: Vec<i32> = vec![];
// Scan the full 64×64 chunk, not a single row — the deep-water trough axis is
// not guaranteed to intersect any fixed Y, so a single-row probe could miss it
// entirely and silently pass without ever checking the sea-level claim.
// not guaranteed to intersect any fixed row, so a single-row probe could miss
// it entirely and silently pass without ever checking the sea-level claim.
for dy in 0..64i32 {
for dx in 0..64i32 {
let col = derive_voxel_column(42, "fjord_body", &region, &chunk, dx, dy);
let col =
derive_voxel_column(42, "fjord_body", &region, &chunk, base_x + dx, base_y + dy);
match col.water {
Water::Deep => deep_elevs.push(col.elevation_m),
Water::Dry => dry_elevs.push(col.elevation_m),
@@ -428,9 +485,12 @@ fn law_drainage_monotonicity_braided_delta() {
VegetationClass::Scrub,
);
let mut checked = false;
for (cx, cy) in [(0, 0), (1, 1)] {
checked |=
assert_drainage_monotonicity(17, "delta_body", "BraidedDelta", &region, (cx, cy));
// The braid belt sits on the region's channel anchor (T-1041) — sweep the
// anchor band so the law is exercised on real thread tiles.
let mut positions = vec![(0, 0), (1, 1)];
positions.extend(anchor_band_chunks(17, "delta_body", &region, (0, 0)));
for pos in positions {
checked |= assert_drainage_monotonicity(17, "delta_body", "BraidedDelta", &region, pos);
}
assert!(
checked,
@@ -898,6 +958,375 @@ fn law_climate_vegetation_airless_always_absent() {
}
}
// ---------------------------------------------------------------------------
// §2b — Region-anchored feature placement (T-1040 / T-1041, D-239 §10)
// ---------------------------------------------------------------------------
//
// T-1040: channel centrelines were anchored to the world x=0/y=0 axis — every
// chunk of a watered region claimed has_active_channel while channel voxels
// existed only near the world origin. T-1041: fjord/cliff/gorge/delta folded
// world coordinates into the 64 m chunk frame — region-scale landforms
// repeated every chunk. Both are fixed by region-anchored feature axes
// (`channel_anchor_m` / `coast_anchor_m`); these tests pin the placement
// contract far from the origin, at the ticket's example chunk (1000, 750).
#[test]
fn channel_present_in_active_chunks_far_from_origin() {
// T-1040 (a): a has_active_channel chunk at an arbitrary large world
// offset contains in-channel voxels — and the gate is honest in both
// directions (wet ⇒ gated, ungated ⇒ dry). Sweeps the 16 cross-columns of
// the region containing chunk (1000, 750) at that chunk's along index.
let region = make_region(
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
5,
20,
18,
60,
Some(15.0),
VegetationClass::Forest,
);
let (seed, body) = (42u64, "GJ144d");
let ns = basin_is_ns(seed, body, &region, (1000, -750));
let cross_base = if ns {
(1000 >> 4) << 4
} else {
(-750i32 >> 4) << 4
};
let mut any_gated_wet = false;
let mut any_gate_off = false;
let mut total_wet = 0usize;
for i in 0..16 {
let pos: ChunkPos = if ns {
(cross_base + i, -750)
} else {
(1000, cross_base + i)
};
let chunk = derive_chunk_context(seed, body, &region, pos);
let (bx, by) = (pos.0 * 64, pos.1 * 64);
let mut wet = 0usize;
for dy in 0..64i32 {
for dx in 0..64i32 {
let col = derive_voxel_column(seed, body, &region, &chunk, bx + dx, by + dy);
if col.water != Water::Dry {
wet += 1;
}
}
}
total_wet += wet;
if chunk.has_active_channel {
any_gated_wet |= wet > 0;
} else {
any_gate_off = true;
assert_eq!(
wet, 0,
"T-1040: chunk {pos:?} has no active channel but contains {wet} wet voxels \
— the chunk gate and the voxel placement disagree"
);
}
}
assert!(
total_wet > 0,
"T-1040: the region at chunk (1000, 750) must contain channel voxels \
(pre-fix: zero — channels existed only near the world-origin axis)"
);
assert!(
any_gated_wet,
"T-1040: at least one has_active_channel chunk must contain in-channel voxels"
);
assert!(
any_gate_off,
"T-1040: the channel band must not blanket the region — some chunks must gate off"
);
}
#[test]
fn channel_continuous_across_chunk_boundary_far_from_origin() {
// T-1040 (a): channel position is continuous across adjacent chunk pairs.
// Every tile derives under its PRODUCTION covering chunk; the wet band's
// midpoint may not jump at a 64 m along-boundary (chunk-frame dependence
// would jump by up to a chunk width or drop out entirely).
let region = make_region(
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
5,
20,
18,
60,
Some(15.0),
VegetationClass::Forest,
);
let (seed, body) = (42u64, "GJ144d");
let probe = derive_chunk_context(seed, body, &region, (1000, -750));
let ns = matches!(
probe.basin_direction,
BasinDirection::North | BasinDirection::South
);
let anchor = probe.channel_anchor_m;
// Wet-band midpoint of one world cross-row, sampling the full swept band.
let row_mid = |along: i32| -> Option<i32> {
let wet: Vec<i32> = (anchor - 220..anchor + 220)
.filter(|&c| {
derive_at_cross_along(seed, body, &region, ns, c, along).water != Water::Dry
})
.collect();
wet.first().map(|f| (f + wet.last().unwrap()) / 2)
};
// An interior along-boundary of the region containing chunk (1000, 750):
// between along-chunks 744 and 743 (N/S) or 1004 and 1005 (E/W).
let boundary = if ns { -743 * 64 } else { 1005 * 64 };
let mut prev: Option<i32> = None;
for along in boundary - 4..boundary + 4 {
let mid = row_mid(along).unwrap_or_else(|| {
panic!(
"T-1040: row along={along} contains no wet tiles — the channel \
dropped out at the chunk boundary {boundary}"
)
});
if let Some(p) = prev {
assert!(
(mid - p).abs() <= 12,
"T-1040: wet-band midpoint jumped {} m between adjacent rows \
{} and {} (boundary {boundary}) — channel is not continuous",
(mid - p).abs(),
along - 1,
along
);
}
prev = Some(mid);
}
}
#[test]
fn fjord_region_has_one_valley_spanning_chunks() {
// T-1041 (b): a FjordWall region contains ONE deep-water trough spanning
// its chunks — pre-fix a complete fjord cross-section repeated in every
// 64 m chunk. Representative sweep: two far regions × three along rows.
let region = make_region(
MorphologyZone::Fjord,
TectonicClass::Active,
GlaciationGrade::Moderate,
55,
60,
28,
55,
Some(-8.0),
VegetationClass::Barren,
);
let (seed, body) = (42u64, "fjord_body");
for region_chunk in [(640, -480), (-336, 992)] {
let probe = derive_chunk_context(seed, body, &region, region_chunk);
let ns = matches!(
probe.basin_direction,
BasinDirection::North | BasinDirection::South
);
let (cross_chunk, along_chunk) = if ns {
(region_chunk.0, region_chunk.1)
} else {
(region_chunk.1, region_chunk.0)
};
let cross_base = (cross_chunk >> 4) * 1024;
let along_base = (along_chunk >> 4) * 1024;
let positions: Vec<i32> = (cross_base..cross_base + 1024).collect();
for along in [along_base + 32, along_base + 512, along_base + 992] {
let clusters = count_feature_clusters(
&positions,
|c| derive_at_cross_along(seed, body, &region, ns, c, along).water == Water::Deep,
16,
);
assert_eq!(
clusters, 1,
"T-1041: FjordWall region {region_chunk:?} must contain exactly ONE \
deep-water trough across its 1024 m cross extent at along={along} \
(got {clusters}; pre-fix: one per 64 m chunk)"
);
}
}
}
#[test]
fn gorge_region_has_one_valley_spanning_chunks() {
// T-1041 (b): an IncisedGorge region contains ONE shallow-floor gorge
// spanning its chunks — not one per chunk.
let region = make_region(
MorphologyZone::MountainPass,
TectonicClass::Active,
GlaciationGrade::None,
50,
65,
5,
40,
Some(5.0),
VegetationClass::Scrub,
);
let (seed, body) = (42u64, "gorge_body");
for region_chunk in [(640, -480), (-336, 992)] {
let probe = derive_chunk_context(seed, body, &region, region_chunk);
let ns = matches!(
probe.basin_direction,
BasinDirection::North | BasinDirection::South
);
let (cross_chunk, along_chunk) = if ns {
(region_chunk.0, region_chunk.1)
} else {
(region_chunk.1, region_chunk.0)
};
let cross_base = (cross_chunk >> 4) * 1024;
let along_base = (along_chunk >> 4) * 1024;
let positions: Vec<i32> = (cross_base..cross_base + 1024).collect();
for along in [along_base + 32, along_base + 512, along_base + 992] {
let clusters = count_feature_clusters(
&positions,
|c| {
derive_at_cross_along(seed, body, &region, ns, c, along).water == Water::Shallow
},
16,
);
assert_eq!(
clusters, 1,
"T-1041: IncisedGorge region {region_chunk:?} must contain exactly ONE \
shallow gorge floor across its 1024 m cross extent at along={along} \
(got {clusters}; pre-fix: one per 64 m chunk)"
);
}
}
}
#[test]
fn cliff_coast_one_continuous_coastline_per_region() {
// T-1041 (b): a CliffCoast region has ONE continuous (warp-displaced)
// coast line on the region-anchored face — pre-fix the cliff face sat at
// intra-chunk offset 4855 in every chunk, sawtoothing the coast at 64 m
// pitch. Transect runs along the seaward (basin) axis across the region.
let region = make_region(
MorphologyZone::CliffCoast,
TectonicClass::Active,
GlaciationGrade::None,
60,
40,
20,
30,
Some(10.0),
VegetationClass::Scrub,
);
let (seed, body) = (42u64, "cliff_body");
for region_chunk in [(656, -464), (-256, 768)] {
let probe = derive_chunk_context(seed, body, &region, region_chunk);
let coast = probe.coast_anchor_m;
let ns = matches!(
probe.basin_direction,
BasinDirection::North | BasinDirection::South
);
let (cross_chunk, along_chunk) = if ns {
(region_chunk.0, region_chunk.1)
} else {
(region_chunk.1, region_chunk.0)
};
let along_base = (along_chunk >> 4) * 1024;
let cross_fixed = cross_chunk * 64 + 32;
let positions: Vec<i32> = (along_base..along_base + 1024).collect();
// Exactly one ocean cluster (the seaward side of the one coast line).
let clusters = count_feature_clusters(
&positions,
|a| derive_at_cross_along(seed, body, &region, ns, cross_fixed, a).water == Water::Deep,
16,
);
assert_eq!(
clusters, 1,
"T-1041: CliffCoast region {region_chunk:?} must have exactly ONE ocean \
side (got {clusters} Deep clusters; pre-fix: one 64 m sawtooth per chunk)"
);
// Monotone coast: well inland of the face line → Dry; well seaward →
// Deep. Margins absorb the ±8 m domain warp (face 0..8, ledge 8..13).
for &a in &positions {
// Signed seaward distance — mirrors generate_cliff_coast.
let d = match probe.basin_direction {
BasinDirection::North | BasinDirection::West => coast - a,
BasinDirection::South | BasinDirection::East => a - coast,
};
let col = derive_at_cross_along(seed, body, &region, ns, cross_fixed, a);
if d <= -9 {
assert_eq!(
col.water,
Water::Dry,
"T-1041: tile {} m inland of the coast line must be Dry (along={a})",
-d
);
} else if d >= 21 {
assert_eq!(
col.water,
Water::Deep,
"T-1041: tile {d} m seaward of the coast line must be Deep (along={a})"
);
}
}
}
}
#[test]
fn braided_threads_confined_to_region_belt() {
// T-1041: braided threads anastomose across the region-anchored fan belt
// (anchor ± 32 m + thread width + warp) — pre-fix the same three threads
// restarted in every 64 m chunk, spreading thread water across the whole
// region's cross extent.
let region = make_region(
MorphologyZone::Delta,
TectonicClass::Active,
GlaciationGrade::None,
3,
8,
25,
50,
Some(18.0),
VegetationClass::Scrub,
);
let (seed, body) = (17u64, "delta_body");
for region_chunk in [(800, -592)] {
let probe = derive_chunk_context(seed, body, &region, region_chunk);
let ns = matches!(
probe.basin_direction,
BasinDirection::North | BasinDirection::South
);
let anchor = probe.channel_anchor_m;
let (cross_chunk, along_chunk) = if ns {
(region_chunk.0, region_chunk.1)
} else {
(region_chunk.1, region_chunk.0)
};
let cross_base = (cross_chunk >> 4) * 1024;
let along = along_chunk * 64 + 32;
let wet: Vec<i32> = (cross_base..cross_base + 1024)
.filter(|&c| {
derive_at_cross_along(seed, body, &region, ns, c, along).water == Water::Shallow
})
.collect();
assert!(
!wet.is_empty(),
"T-1041: BraidedDelta region {region_chunk:?} must contain thread water"
);
// Belt confinement: thread centres ∈ anchor ± 32, half-width ≤ 4,
// warp ≤ 8 → all thread water within anchor ± 44.
for &c in &wet {
assert!(
(c - anchor).abs() <= 44,
"T-1041: thread water at cross={c} is {} m from the fan axis {anchor} \
— outside the region belt (pre-fix: threads repeated every chunk)",
(c - anchor).abs()
);
}
// Braided, not single-thread: 13 thread clusters within the belt
// (three threads, possibly merged where centres overlap; warp-scale
// gap tolerance — the ±8 m warp punches small holes in a thread).
let cluster_count = count_feature_clusters(&wet, |_| true, 8);
assert!(
(1..=3).contains(&cluster_count),
"T-1041: expected 13 braided thread clusters in the belt, got {cluster_count}"
);
}
}
// ---------------------------------------------------------------------------
// §3 — Per-family <5 ms/chunk budget (D-239 §10)
// ---------------------------------------------------------------------------
@@ -1446,6 +1875,92 @@ fn validation_gruenfeld_skipped_not_in_wiki() {
// Shared helpers
// ---------------------------------------------------------------------------
/// Whether the region's basin runs north/south (cross axis = x). Region-scale
/// property — identical for every chunk of the region containing `region_chunk`.
fn basin_is_ns(seed: u64, body_id: &str, region: &RegionProfile, region_chunk: ChunkPos) -> bool {
let probe = derive_chunk_context(seed, body_id, region, region_chunk);
matches!(
probe.basin_direction,
BasinDirection::North | BasinDirection::South
)
}
/// The chunk positions of the channel-anchor band (anchor column ± 1) across
/// the full along-extent of the region containing `region_chunk` (T-1040).
///
/// The channel/landform centreline is region-anchored: it lives in the anchor
/// chunk column, swinging up to one meander amplitude sideways. The meander
/// wavelength (≤ ~650 m) fits inside the region's 1024 m along-extent, so the
/// centreline crosses the anchor column at least once — sweeping this band
/// guarantees wet tiles are exercised somewhere in it.
fn anchor_band_chunks(
seed: u64,
body_id: &str,
region: &RegionProfile,
region_chunk: ChunkPos,
) -> Vec<ChunkPos> {
let probe = derive_chunk_context(seed, body_id, region, region_chunk);
let anchor_idx = probe.channel_anchor_m.div_euclid(64);
let ns = matches!(
probe.basin_direction,
BasinDirection::North | BasinDirection::South
);
// Region base index on the along axis (16-chunk regions; arithmetic shift
// = floor division, correct for negative chunks).
let along_base = if ns {
(region_chunk.1 >> 4) << 4
} else {
(region_chunk.0 >> 4) << 4
};
let mut out = Vec::new();
for cross in anchor_idx - 1..=anchor_idx + 1 {
for j in along_base..along_base + 16 {
out.push(if ns { (cross, j) } else { (j, cross) });
}
}
out
}
/// Derive one voxel at world cross/along coordinates under the PRODUCTION
/// covering chunk's context (per-tile chunk lookup — exactly what a consumer
/// streaming the world does). Basis-aware: cross ⊥ basin, along ∥ basin.
fn derive_at_cross_along(
seed: u64,
body_id: &str,
region: &RegionProfile,
ns: bool,
cross: i32,
along: i32,
) -> settled_reach_server::atlas::voxel::VoxelColumn {
let (tx, ty) = if ns { (cross, along) } else { (along, cross) };
let chunk_pos = (tx.div_euclid(64), ty.div_euclid(64));
let chunk = derive_chunk_context(seed, body_id, region, chunk_pos);
derive_voxel_column(seed, body_id, region, &chunk, tx, ty)
}
/// Count clusters of positions where `pred` holds across a cross/along
/// transect, merging runs separated by gaps ≤ `gap_tolerance` (domain-warp
/// jitter can fragment a single feature by a few metres; distinct per-chunk
/// repeats are ≥ ~50 m apart and never merge).
fn count_feature_clusters(
positions: &[i32],
hits: impl Fn(i32) -> bool,
gap_tolerance: i32,
) -> usize {
let mut clusters = 0usize;
let mut last_hit: Option<i32> = None;
for &p in positions {
if hits(p) {
match last_hit {
Some(prev) if p - prev <= gap_tolerance => {}
_ => clusters += 1,
}
last_hit = Some(p);
}
}
clusters
}
/// Construct a `RegionProfile` directly from parameters, deriving the
/// dependent fields (precipitation_class, river_threshold) consistently.
fn make_region(
+8 -8
View File
@@ -3,12 +3,12 @@
"label": "alluvial_forest_active_channel",
"seed": 16045690984503098046,
"body_id": "GJ144d",
"tile_x": 640,
"tile_y": 1280,
"tile_x": 223,
"tile_y": 800,
"terrain": 0,
"vegetation": 1,
"water": 0,
"elevation_m": 11,
"vegetation": 4,
"water": 1,
"elevation_m": 8,
"cover": 0
},
{
@@ -27,12 +27,12 @@
"label": "fjord_wall_glaciated",
"seed": 18369614217980264670,
"body_id": "GJ447c",
"tile_x": 928,
"tile_y": 544,
"tile_x": 228,
"tile_y": 352,
"terrain": 3,
"vegetation": 0,
"water": 2,
"elevation_m": 1,
"elevation_m": 0,
"cover": 1
}
]