Files
jpmschweitzerandClaude Opus 5 5eb394b36f feat(simulation): relief_q — a local relief signal the deep rungs can resolve (T-1213)
The District and Quarter rungs rendered as flat colour, and the cause was not
the biome work everyone assumed. Measured on Ferrath through the production
canvas builder: at District the mean |elev_q delta| between neighbouring
gridunits is 0.02, and NOT ONE PAIR in a 1290x540 frame differs by 2.

elev_q spans 0-100 across the body's whole 8 km elevation range, so ONE STEP IS
80 METRES. A District canvas covers 2,048 m of ground, where the rolling relief
a walker navigates by is metres to tens of metres -- a fraction of a single
step. The sub-district detail IS generated (invent_primitives' scatter and
relief bands compute it) and then rounded away. Confirmed by running the
diagnostic with the octave cutoff disabled: still 0.02.

relief_q carries that same invented fine component against a scale chosen to
resolve it: 0-100 about a flat 50, RELIEF_FULL_SCALE_M = 400 m either side, so
8 m per step -- ten times finer than elev_q. elev_q keeps its body-absolute
meaning and the Atlas legend stays true.

Measured effect, elev_q vs relief_q (distinct values / mean 4-cell delta):

  Region     49 / 2.38   ->   101 / 21.86
  District   10 / 0.08   ->    35 / 0.35
  Quarter     8 / 0.02   ->    19 / 0.06

FIXED metre scale, never per-canvas normalization: the value for a piece of
ground must not depend on what else is in frame, or the same hillside changes
tone as the viewer pans. And it excludes elev_pct deliberately -- this is the
departure from the surrounding land, not height above sea level; including the
base would re-introduce the body-scale dominance that makes elev_q unusable
down here.

50 at the orbital rungs, which skip invent_primitives by design. Nothing is
lost: Global and Region still have varied elev_q (101 and 49 distinct values),
and the client takes whichever field carries signal via a max, with no
rung-name branching.

The client's ruggedness driver changes with it. It was an elev_q GRADIENT,
which cannot work across rungs -- the same 4-cell delta reads 21.86 at Region
and 0.08 at District, so any single full-scale constant either saturates one or
vanishes on the other. relief_q states relief outright, so |relief_q - 50| is
the answer directly and a fixed metre scale is immune to that by construction.

An absent plane reads FLAT, not zero -- 0 on this field means maximum relief
BELOW flat, so a payload without it would have stippled the entire map. That is
reachable: the field is #[serde(default)] so old-shape payloads decode. Two
colorize tests whose fixtures predate the plane caught it.

2004 server tests, 1838 client tests, 0 failed. clippy clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 16:11:23 +02:00

2308 lines
83 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! D-239 Tile-Derivation Verification Harness (T-1031).
//!
//! Capstone test for the T-974 tile-derivation epic. Covers four domains:
//!
//! **1. Golden-seed determinism regression (D-239 §1 / D-010).**
//! Pins (seed, district, chunk_pos, tile_pos) → VoxelColumn bindings as a JSON
//! golden. Any future change to the derivation chain is caught immediately.
//! Pattern mirrors cascade_golden.rs. Regenerate:
//! `UPDATE_GOLDEN=1 cargo test --test derivation_harness`
//!
//! **2. §8 binding-law assertions (D-239 §8).**
//! Sweeps representative `DistrictProfile` inputs and asserts the four
//! believability laws hold across the parameter space:
//! - Drainage monotonicity (channel tiles at or below surrounding terrain)
//! - Lithology→landform (family emits its mandated TerrainMaterial)
//! - Glaciation→form (FjordWall only at GlaciationGrade ≥ 2; grade 0 ≠ fjord)
//! - Climate→vegetation (Forest→Scrub→Barren no-skip; riparian band present)
//!
//! **3. Per-family <5 ms/chunk budget (D-239 §10).**
//! Derives a full 64×64 chunk (4096 voxels) for each of the 8 families.
//! Hard gate (< 5 ms) is activated by `BUDGET_ASSERT=1` env var (release build).
//! Debug builds print timings without failing. VoxelCache is exercised to
//! measure LRU eviction overhead (O(capacity) scan, noted on T-1031).
//!
//! **4. Validation body cases (D-239 §1).**
//! Lore-anchored bodies verified from wiki body params (DB-free). Bodies absent
//! from the wiki are skipped with an explicit logged note.
//!
//! - Kallast (GJ144d): alluvial plain / Soil — VALIDATED
//! - Glødberg (GJ581c): volcanic immature drainage — VALIDATED (Cygni-B proxy)
//! - Marevna (GJ447c): ocean island / OpenOcean — VALIDATED (Ross 128)
//! - Velen (tidal-flat/dune coast): NOT IN WIKI — SKIPPED
//! - Gruenfeld (marginal Gravel/Scrub): NOT IN WIKI — SKIPPED
//!
//! Run all: `cargo test --test derivation_harness`
//! Update golden: `UPDATE_GOLDEN=1 cargo test --test derivation_harness`
//! Budget gate: `BUDGET_ASSERT=1 cargo test --test derivation_harness -- budget`
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::time::Instant;
use settled_reach_server::atlas::chunk_context::{
derive_chunk_context, district_boundary_blend_weight, BasinDirection, ChunkPos,
};
use settled_reach_server::atlas::district_profile::{
derive_district_profile, derive_morphology_zone, derive_precipitation_class_from_climate,
derive_river_threshold, derive_vegetation, BodyParams, ClimateConstants, DistrictProfile,
GlaciationGrade, TectonicClass, VegetationClass,
};
use settled_reach_server::atlas::drainage;
use settled_reach_server::atlas::features::TerrainAnalysis;
use settled_reach_server::atlas::heightmap::BodyHeightmap;
use settled_reach_server::atlas::scale;
use settled_reach_server::atlas::voxel::{derive_voxel_column, TerrainMaterial, VoxelCache, Water};
use settled_reach_server::seed::{SeedChain, SeedDomain};
use settled_reach_server::simulation::generator::MorphologyZone;
// ---------------------------------------------------------------------------
// §1 — Golden-seed determinism regression
// ---------------------------------------------------------------------------
const GOLDEN_FILE: &str = "tests/golden/derivation_harness.json";
/// Compact representation of a VoxelColumn for golden pinning.
/// All fields are their integer-discriminant u8 values (D-010) or i32 elevation.
#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone)]
struct GoldenEntry {
label: String,
seed: u64,
body_id: String,
tile_x: i32,
tile_y: i32,
terrain: u8,
vegetation: u8,
water: u8,
elevation_m: i32,
cover: u8,
}
/// Derive a GoldenEntry for a fixed (seed, district, chunk_pos, tile_pos) tuple.
fn derive_golden(
label: &str,
seed: u64,
body_id: &str,
district: &DistrictProfile,
chunk_pos: (i32, i32),
tile_x: i32,
tile_y: i32,
) -> GoldenEntry {
let chunk = derive_chunk_context(seed, body_id, district, chunk_pos, None);
let col = derive_voxel_column(seed, body_id, district, &chunk, tile_x, tile_y);
GoldenEntry {
label: label.to_string(),
seed,
body_id: body_id.to_string(),
tile_x,
tile_y,
terrain: col.terrain as u8,
vegetation: col.vegetation as u8,
water: col.water as u8,
elevation_m: col.elevation_m,
cover: col.cover as u8,
}
}
/// Compute the chunk position + tile coordinates sitting ON the district's
/// channel anchor at a given along-axis chunk index (T-1040/T-1041): feature
/// placement is district-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 district (0, 0) — i.e. in `0..scale::CHUNKS_PER_DISTRICT` — so the
/// probed anchor belongs to the same district as the returned chunk.
fn anchor_golden_pos(
seed: u64,
body_id: &str,
district: &DistrictProfile,
along_chunk: i32,
) -> ((i32, i32), i32, i32) {
assert!(
(0..scale::CHUNKS_PER_DISTRICT).contains(&along_chunk),
"along_chunk must stay within district (0, 0)"
);
let probe = derive_chunk_context(seed, body_id, district, (0, 0), None);
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,
u64,
&'static str,
DistrictProfile,
(i32, i32),
i32,
i32,
)> {
// Cases A and C pin voxels on the district channel anchor (T-1040/T-1041):
// the channel/trough centreline is district-anchored, not at the world
// origin (A) or the chunk centre (C).
let alluvial_district = 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_district, 12);
let fjord_district = 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_district, 5);
vec![
// Case A: AlluvialPlain — temperate forest, on the active-channel anchor.
(
"alluvial_forest_active_channel",
0xdeadbeef_cafebabe_u64,
"GJ144d",
alluvial_district,
alluvial_chunk_pos,
alluvial_tx,
alluvial_ty,
),
// Case B: LavaField — barren volcanic, no channel.
(
"lava_field_barren",
0x12345678_90abcdef_u64,
"GJ581c",
make_region(
MorphologyZone::Volcanic,
TectonicClass::Volcanic,
GlaciationGrade::None,
12,
38,
0,
22,
Some(45.0),
VegetationClass::Barren,
),
(5, 7),
320,
448,
),
// Case C: FjordWall — glaciated, rocky walls; tile on the district-anchored
// trough centreline (Deep water unless the warp nudges it onto the floor edge).
(
"fjord_wall_glaciated",
0xfeedface_0badc0de_u64,
"GJ447c",
fjord_district,
fjord_chunk_pos,
fjord_tx,
fjord_ty,
),
]
}
#[test]
fn golden_seed_determinism_regression() {
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let golden_path = manifest.join(GOLDEN_FILE);
// Derive once, then derive again — the two must be identical before we
// compare against the golden. This is the core D-010 determinism contract.
let run1: Vec<GoldenEntry> = golden_cases()
.into_iter()
.map(|(lbl, seed, body, district, cp, tx, ty)| {
derive_golden(lbl, seed, body, &district, cp, tx, ty)
})
.collect();
let run2: Vec<GoldenEntry> = golden_cases()
.into_iter()
.map(|(lbl, seed, body, district, cp, tx, ty)| {
derive_golden(lbl, seed, body, &district, cp, tx, ty)
})
.collect();
assert_eq!(
run1, run2,
"double-derivation mismatch — determinism is broken (D-010)"
);
let actual_json = serde_json::to_string_pretty(&run1).expect("serialize") + "\n";
if std::env::var("UPDATE_GOLDEN").is_ok() {
std::fs::create_dir_all(golden_path.parent().unwrap()).expect("mkdir golden");
std::fs::write(&golden_path, &actual_json).expect("write golden");
eprintln!(
"Golden written: {} ({} bytes)",
golden_path.display(),
actual_json.len()
);
return;
}
let golden_json = std::fs::read_to_string(&golden_path).unwrap_or_else(|e| {
panic!(
"Golden file not found: {}.\n\
First run: UPDATE_GOLDEN=1 cargo test --test derivation_harness\n{e}",
golden_path.display()
)
});
let actual_v: serde_json::Value = serde_json::from_str(&actual_json).expect("reparse actual");
let golden_v: serde_json::Value = serde_json::from_str(&golden_json).expect("parse golden");
if actual_v != golden_v {
panic!(
"Derivation golden mismatch — derivation chain changed.\n\
Update: UPDATE_GOLDEN=1 cargo test --test derivation_harness\n\
Golden: {}\nActual: {}",
golden_json.trim(),
actual_json.trim()
);
}
}
// ---------------------------------------------------------------------------
// §2 — §8 Binding-law assertions
// ---------------------------------------------------------------------------
// ── §8 Law 1: Drainage monotonicity ─────────────────────────────────────────
//
// Channel / water tiles must sit at or below surrounding dry terrain.
// D-239 §8: "drainage monotonicity: tributaries join upstream; mouths at sea
// level; BraidedDelta/CliffCoast/FjordWall floors at sea level."
// Tested by deriving a full 64×64 chunk and checking min(wet_elev) ≤ max(dry_elev).
/// Returns `true` if the monotonicity assertion was actually exercised (the chunk
/// produced both wet and dry tiles). A `false` return means the chunk had no wet
/// tiles, so the law was trivially satisfied without checking anything — callers
/// sweep several chunks and assert at least one returned `true`, so a derivation
/// regression that silently zeroes all channels fails loudly instead of passing.
#[must_use]
fn assert_drainage_monotonicity(
seed: u64,
body_id: &str,
label: &str,
district: &DistrictProfile,
chunk_pos: (i32, i32),
) -> bool {
let chunk = derive_chunk_context(seed, body_id, district, chunk_pos, None);
if !chunk.has_active_channel {
return false; // No channel → monotonicity trivially satisfied.
}
let mut max_dry_elev = i32::MIN;
let mut min_wet_elev = i32::MAX;
let mut wet_count = 0usize;
let mut dry_count = 0usize;
let base_x = chunk_pos.0 * 64;
let base_y = chunk_pos.1 * 64;
for dy in 0..64i32 {
for dx in 0..64i32 {
let col =
derive_voxel_column(seed, body_id, district, &chunk, base_x + dx, base_y + dy);
match col.water {
Water::Dry => {
max_dry_elev = max_dry_elev.max(col.elevation_m);
dry_count += 1;
}
Water::Shallow | Water::Deep => {
min_wet_elev = min_wet_elev.min(col.elevation_m);
wet_count += 1;
}
}
}
}
if wet_count > 0 && dry_count > 0 {
// Tolerance +3 m: meander levees are elevated above the floodplain
// (D-239 §9 ElevationDelta), so a channel tile adjacent to a levee tile
// will appear "slightly below" but the levee reads higher. We allow a
// small tolerance to avoid false positives at the levee-channel boundary.
let tolerance = 3;
assert!(
min_wet_elev <= max_dry_elev + tolerance,
"§8 drainage monotonicity VIOLATED in '{label}' chunk ({},{}): \
min wet-tile elevation {min_wet_elev} m > max dry-tile elevation \
{max_dry_elev} m (tolerance +{tolerance} m).",
chunk_pos.0,
chunk_pos.1
);
return true;
}
false
}
#[test]
fn law_drainage_monotonicity_alluvial_sweep() {
let district = make_region(
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
5,
20,
18,
60,
Some(15.0),
VegetationClass::Forest,
);
let mut checked = false;
// Sweep a spread of district chunks plus the channel-anchor band (T-1040:
// the channel is district-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", &district, (0, 0)));
for pos in positions {
checked |= assert_drainage_monotonicity(42, "GJ144d", "AlluvialPlain", &district, pos);
}
assert!(
checked,
"AlluvialPlain sweep exercised no wet tiles — the monotonicity law was never \
actually checked; a derivation regression could silently pass this test."
);
}
#[test]
fn law_drainage_monotonicity_meander_sweep() {
let district = make_region(
MorphologyZone::MeanderReach,
TectonicClass::Stable,
GlaciationGrade::None,
8,
18,
20,
65,
Some(14.0),
VegetationClass::Forest,
);
let mut checked = false;
let mut positions = vec![(0, 0), (2, 1), (5, 5)];
positions.extend(anchor_band_chunks(99, "GJ447c", &district, (0, 0)));
for pos in positions {
checked |= assert_drainage_monotonicity(99, "GJ447c", "MeanderReach", &district, pos);
}
assert!(
checked,
"MeanderReach sweep exercised no wet tiles — the monotonicity law was never \
actually checked; a derivation regression could silently pass this test."
);
}
#[test]
fn law_drainage_monotonicity_fjord_floor_at_sea_level() {
// D-239 §8: fjord/cliff/delta floors at sea level (drainage monotonicity).
// FjordWall deep-water channel must be near elevation 0.
let district = make_region(
MorphologyZone::Fjord,
TectonicClass::Active,
GlaciationGrade::Moderate,
55,
60,
28,
55,
Some(-8.0),
VegetationClass::Barren,
);
// The fjord trough is district-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", &district, (0, 0), None);
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", &district, chunk_pos, None);
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 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",
&district,
&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),
_ => {}
}
}
}
// A glaciated fjord chunk MUST carve a deep-water trough — if it doesn't, the
// derivation regressed and the sea-level law below would never run. Fail loudly.
assert!(
!deep_elevs.is_empty(),
"§8 FjordWall: no deep-water tiles found in the fjord chunk — the trough \
derivation regressed; the sea-level floor law was never exercised."
);
assert!(
!dry_elevs.is_empty(),
"§8 FjordWall: no dry wall tiles found in the fjord chunk."
);
let max_deep = *deep_elevs.iter().max().unwrap();
let min_dry = *dry_elevs.iter().min().unwrap();
// Fjord floor (deep water) must be below wall elevation.
assert!(
max_deep <= min_dry,
"§8 FjordWall drainage: deep-water max elev {max_deep} m must be \
≤ dry wall min elev {min_dry} m"
);
// Fjord floor must be near sea level (D-239 §8).
assert!(
max_deep <= 5,
"§8 FjordWall: deep-water floor elev {max_deep} m must be near sea level (≤5 m)"
);
}
#[test]
fn law_drainage_monotonicity_braided_delta() {
let district = make_region(
MorphologyZone::Delta,
TectonicClass::Active,
GlaciationGrade::None,
3,
8,
25,
50,
Some(18.0),
VegetationClass::Scrub,
);
let mut checked = false;
// The braid belt sits on the district'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", &district, (0, 0)));
for pos in positions {
checked |= assert_drainage_monotonicity(17, "delta_body", "BraidedDelta", &district, pos);
}
assert!(
checked,
"BraidedDelta sweep exercised no wet tiles — the monotonicity law was never \
actually checked; a derivation regression could silently pass this test."
);
}
// ── §8 Law 2: Lithology → Landform ──────────────────────────────────────────
//
// D-239 §8: Lava→Lava; Cliff/Fjord/Gorge→Rock; Sand→Dune; Gravel→Braided;
// Soil→Meander/Alluvial; Wetland→Wetland.
/// Derive 64 tile samples (every 8th tile in a 64×64 chunk) and assert all
/// have the expected TerrainMaterial.
fn assert_all_terrain_is(
seed: u64,
body_id: &str,
district: &DistrictProfile,
chunk_pos: (i32, i32),
expected: TerrainMaterial,
label: &str,
) {
let chunk = derive_chunk_context(seed, body_id, district, chunk_pos, None);
let base_x = chunk_pos.0 * 64;
let base_y = chunk_pos.1 * 64;
for dy in (0..64i32).step_by(8) {
for dx in (0..64i32).step_by(8) {
let col =
derive_voxel_column(seed, body_id, district, &chunk, base_x + dx, base_y + dy);
assert_eq!(
col.terrain,
expected,
"§8 lithology VIOLATED for '{label}': voxel ({},{}) returned {:?}, expected {:?}",
base_x + dx,
base_y + dy,
col.terrain,
expected
);
}
}
}
#[test]
fn law_lithology_lava_emits_lava() {
// D-239 §8: Lava family → TerrainMaterial::Lava everywhere.
let district = make_region(
MorphologyZone::Volcanic,
TectonicClass::Volcanic,
GlaciationGrade::None,
15,
35,
0,
20,
Some(40.0),
VegetationClass::Barren,
);
assert_all_terrain_is(
42,
"GJ581c",
&district,
(3, 3),
TerrainMaterial::Lava,
"LavaField",
);
}
#[test]
fn law_lithology_fjord_emits_rock() {
let district = make_region(
MorphologyZone::Fjord,
TectonicClass::Active,
GlaciationGrade::Moderate,
55,
60,
28,
55,
Some(-8.0),
VegetationClass::Barren,
);
assert_all_terrain_is(
42,
"fjord_body",
&district,
(0, 0),
TerrainMaterial::Rock,
"FjordWall",
);
}
#[test]
fn law_lithology_cliff_coast_emits_rock() {
let district = make_region(
MorphologyZone::CliffCoast,
TectonicClass::Active,
GlaciationGrade::None,
60,
40,
20,
30,
Some(10.0),
VegetationClass::Scrub,
);
assert_all_terrain_is(
42,
"cliff_body",
&district,
(0, 0),
TerrainMaterial::Rock,
"CliffCoast",
);
}
#[test]
fn law_lithology_incised_gorge_emits_rock() {
// D-239 §8: IncisedGorge/MountainPass → TerrainMaterial::Rock.
let district = make_region(
MorphologyZone::MountainPass,
TectonicClass::Active,
GlaciationGrade::None,
50,
65,
5,
40,
Some(5.0),
VegetationClass::Scrub,
);
assert_all_terrain_is(
42,
"gorge_body",
&district,
(0, 0),
TerrainMaterial::Rock,
"IncisedGorge",
);
}
#[test]
fn law_lithology_dune_strand_emits_sand() {
// D-239 §8: DuneStrand → TerrainMaterial::Sand (≤32° angle of repose).
let district = make_region(
MorphologyZone::DuneStrand,
TectonicClass::Stable,
GlaciationGrade::None,
12,
15,
20,
25,
Some(22.0),
VegetationClass::Barren,
);
assert_all_terrain_is(
42,
"dune_body",
&district,
(0, 0),
TerrainMaterial::Sand,
"DuneStrand",
);
}
#[test]
fn law_lithology_braided_delta_emits_gravel() {
// D-239 §8: BraidedDelta (Gravel→braided channels/fans) → TerrainMaterial::Gravel.
let district = make_region(
MorphologyZone::Delta,
TectonicClass::Active,
GlaciationGrade::None,
3,
8,
25,
50,
Some(18.0),
VegetationClass::Scrub,
);
assert_all_terrain_is(
42,
"delta_body",
&district,
(0, 0),
TerrainMaterial::Gravel,
"BraidedDelta",
);
}
#[test]
fn law_lithology_alluvial_plain_emits_soil() {
// D-239 §8: AlluvialPlain → Soil (non-wetland params: slope_q>5, moisture_q<60).
let district = make_region(
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
10,
25,
12,
45,
Some(16.0),
VegetationClass::Forest,
);
assert_all_terrain_is(
42,
"alluvial_body",
&district,
(0, 0),
TerrainMaterial::Soil,
"AlluvialPlain",
);
}
#[test]
fn law_lithology_meander_reach_emits_soil() {
// D-239 §8: MeanderReach (Soil→rolling/floodplain) → TerrainMaterial::Soil.
let district = make_region(
MorphologyZone::MeanderReach,
TectonicClass::Stable,
GlaciationGrade::None,
8,
18,
20,
55,
Some(14.0),
VegetationClass::Forest,
);
assert_all_terrain_is(
42,
"meander_body",
&district,
(0, 0),
TerrainMaterial::Soil,
"MeanderReach",
);
}
// ── §8 Law 3: Glaciation → Form ──────────────────────────────────────────────
//
// D-239 §5/§8: FjordWall requires GlaciationGrade ≥ 2 (Moderate).
// Grade 0 must NEVER produce Fjord regardless of slope/coastal params.
// Grade 1 (Light) also must NOT produce Fjord (gate is ≥ 2, not ≥ 1).
#[test]
fn law_glaciation_grade_0_never_produces_fjord() {
// Sweep slope_q and ocean_fraction_q at grade 0. No combination should yield Fjord.
for slope_q in [30, 40, 50, 55, 60, 70] {
for ocean_q in [15, 25, 35, 45, 55] {
let zone = derive_morphology_zone(
TectonicClass::Active,
GlaciationGrade::None, // grade 0
slope_q,
60,
ocean_q,
55,
false, // T-1184: no hydrology solve in this synthetic-gate sweep
);
assert_ne!(
zone,
MorphologyZone::Fjord,
"§8 glaciation law VIOLATED: GlaciationGrade::None + slope_q={slope_q} \
+ ocean_q={ocean_q} produced Fjord zone. Gate requires ≥ 2."
);
}
}
}
#[test]
fn law_glaciation_grade_1_never_produces_fjord() {
// Grade 1 (Light) is below the fjord gate. Must also not produce Fjord.
for slope_q in [40, 55, 70] {
for ocean_q in [20, 35] {
let zone = derive_morphology_zone(
TectonicClass::Active,
GlaciationGrade::Light, // grade 1 — gate does NOT open
slope_q,
60,
ocean_q,
55,
false, // T-1184: no hydrology solve in this synthetic-gate sweep
);
assert_ne!(
zone,
MorphologyZone::Fjord,
"§8 glaciation law VIOLATED: GlaciationGrade::Light (grade 1) + \
slope_q={slope_q} + ocean_q={ocean_q} produced Fjord. Gate requires ≥ 2."
);
}
}
}
#[test]
fn law_glaciation_grade_2_enables_fjord_with_correct_params() {
// Grade 2 (Moderate): with the correct slope + coastal params, the fjord gate opens.
// This is the positive test — the gate MUST open at exactly grade 2.
let zone = derive_morphology_zone(
TectonicClass::Active,
GlaciationGrade::Moderate, // grade 2 — gate opens
55, // steep enough
60,
25, // coastal
55,
false, // T-1184: no hydrology solve in this synthetic-gate test
);
assert_eq!(
zone,
MorphologyZone::Fjord,
"§8 glaciation law: GlaciationGrade::Moderate (grade 2) with slope_q=55 + \
ocean_q=25 should produce Fjord zone — gate should be open at grade 2."
);
}
#[test]
fn law_glaciation_grade_4_also_enables_fjord() {
// IceCap (grade 4): also above the gate. Should produce Fjord with correct params.
let zone = derive_morphology_zone(
TectonicClass::Active,
GlaciationGrade::IceCap,
55,
60,
25,
55,
false, // T-1184: no hydrology solve in this synthetic-gate test
);
assert_eq!(
zone,
MorphologyZone::Fjord,
"§8 glaciation law: GlaciationGrade::IceCap (grade 4) should also enable Fjord gate"
);
}
// ── §8 Law 4: Climate → Vegetation ──────────────────────────────────────────
//
// D-239 §8: "treeline Forest→Scrub→Barren, no skip."
// Rule: as temperature decreases (or elevation increases), the sequence must
// pass through Scrub before reaching Barren. A direct Forest→Barren jump is
// forbidden. Riparian variants are permitted anywhere they're produced.
#[test]
fn law_climate_vegetation_no_skip_temperature_sweep() {
// Sweep temperature from +30°C to -60°C at fixed mid-elevation.
// The sequence must have no Forest→Barren jump.
let moisture_q = 50;
let elev_q = 40;
let mut last: Option<VegetationClass> = None;
for t_i in (-60i32..=30).rev().step_by(5) {
let temp = Some(t_i as f32);
let vc = derive_vegetation(temp, moisture_q, elev_q, false, false);
if let Some(prev) = last {
if prev == VegetationClass::Forest && vc == VegetationClass::Barren {
panic!(
"§8 vegetation no-skip VIOLATED: Forest→Barren at temp={t_i}°C \
(elev_q={elev_q}, moisture_q={moisture_q})"
);
}
}
last = Some(vc);
}
}
#[test]
fn law_climate_vegetation_no_skip_elevation_sweep() {
// Sweep elevation from 0 to 100 at fixed warm temperature.
let temp = Some(15.0f32);
let moisture_q = 55;
let mut last: Option<VegetationClass> = None;
for elev_q in (0i32..=100).step_by(5) {
let vc = derive_vegetation(temp, moisture_q, elev_q, false, false);
if let Some(prev) = last {
if prev == VegetationClass::Forest && vc == VegetationClass::Barren {
panic!(
"§8 vegetation no-skip VIOLATED: Forest→Barren at elev_q={elev_q} \
(temp=+15°C, moisture_q={moisture_q})"
);
}
}
last = Some(vc);
}
}
#[test]
fn law_climate_vegetation_full_grid_no_skip() {
// Full grid sweep: all (moisture_q, elev_q) combinations, temperature from warm to cold.
// At each slice, verify no Forest→Barren jump as temperature drops.
let temps_desc: Vec<Option<f32>> = [
Some(30.0),
Some(20.0),
Some(10.0),
Some(5.0),
Some(0.0),
Some(-5.0),
Some(-10.0),
Some(-20.0),
Some(-30.0),
Some(-40.0),
Some(-55.0),
None,
]
.to_vec();
for moisture_q in (0i32..=100).step_by(10) {
for elev_q in (0i32..=100).step_by(10) {
let mut last: Option<VegetationClass> = None;
for &temp in &temps_desc {
let vc = derive_vegetation(temp, moisture_q, elev_q, false, false);
if let Some(prev) = last {
if prev == VegetationClass::Forest && vc == VegetationClass::Barren {
panic!(
"§8 full-grid no-skip VIOLATED: Forest→Barren at \
temp={temp:?}, moisture_q={moisture_q}, elev_q={elev_q}"
);
}
}
last = Some(vc);
}
}
}
}
#[test]
fn law_climate_vegetation_riparian_near_perennial_water() {
// D-239 §8: "riparian Thicket/Scrub 13 tiles along perennial waterways."
// near_perennial_water=true must produce a Riparian variant in viable zones.
// Forest zone → RiparianThicket.
let vc_forest = derive_vegetation(Some(18.0), 60, 20, true, false);
assert!(
matches!(
vc_forest,
VegetationClass::RiparianThicket | VegetationClass::RiparianScrub
),
"§8 riparian: Forest zone near water should → Riparian variant, got {:?}",
vc_forest
);
// Scrub zone → RiparianScrub.
let vc_scrub = derive_vegetation(Some(5.0), 30, 50, true, false);
assert!(
matches!(
vc_scrub,
VegetationClass::RiparianScrub | VegetationClass::RiparianThicket
),
"§8 riparian: Scrub zone near water should → Riparian variant, got {:?}",
vc_scrub
);
// Hyper-arid Barren zone + perennial water → RiparianScrub oasis (D-239 §8).
let vc_arid = derive_vegetation(Some(20.0), 3, 10, true, false);
assert_eq!(
vc_arid,
VegetationClass::RiparianScrub,
"§8 riparian: hyper-arid zone (moisture_q=3) near perennial water → \
RiparianScrub oasis, got {:?}",
vc_arid
);
}
#[test]
fn law_climate_vegetation_airless_always_absent() {
// D-239 §2: airless body (temperature_c = None) → VegetationClass::Absent always.
for moisture_q in [0, 30, 70, 100] {
for elev_q in [0, 50, 100] {
for near_water in [false, true] {
let vc = derive_vegetation(None, moisture_q, elev_q, near_water, false);
assert_eq!(
vc,
VegetationClass::Absent,
"§8 vegetation: airless body must produce Absent, got {:?} \
(moisture_q={moisture_q}, elev_q={elev_q})",
vc
);
}
}
}
}
// ---------------------------------------------------------------------------
// §2b — District-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 district 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 — district-scale landforms
// repeated every chunk. Both are fixed by district-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 district containing chunk (1000, 750) at that chunk's along index.
let district = 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, &district, (1000, -750));
let cross_base = if ns {
(1000 >> scale::CHUNK_DISTRICT_SHIFT) << scale::CHUNK_DISTRICT_SHIFT
} else {
(-750i32 >> scale::CHUNK_DISTRICT_SHIFT) << scale::CHUNK_DISTRICT_SHIFT
};
let mut any_gated_wet = false;
let mut any_gate_off = false;
let mut total_wet = 0usize;
for i in 0..scale::CHUNKS_PER_DISTRICT {
let pos: ChunkPos = if ns {
(cross_base + i, -750)
} else {
(1000, cross_base + i)
};
let chunk = derive_chunk_context(seed, body, &district, pos, None);
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, &district, &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 district 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 district — 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 district = 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, &district, (1000, -750), None);
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, &district, ns, c, along).water != Water::Dry
})
.collect();
wet.first().map(|f| (f + wet.last().unwrap()) / 2)
};
// An interior along-boundary of the district 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);
}
}
// ---------------------------------------------------------------------------
// T-1042 — Cross-district parameter blending at chunk/voxel scale
// ---------------------------------------------------------------------------
//
// Acceptance criteria (from the ticket brief):
// 1. Elevation step across a district seam ≤ typical step between adjacent
// interior chunks of the same district (seam is invisible in practice).
// 2. Morphology family seams remain sharp (no blending of family selection).
// 3. Golden-seed determinism unchanged for chunks far from any district border.
//
// The test constructs two adjacent AlluvialPlain districts with a significant
// `elev_q` contrast (20 vs 70) and measures:
// - Average elevation of the last chunk of district A (using blend toward B).
// - Average elevation of the last+1 chunk, which is the first chunk of district
// B (no blend — it reads cleanly from district B).
// - Average elevation of a pure interior chunk in district A (far from any seam).
// - Average elevation of a pure interior chunk in district B (far from any seam).
//
// Pass condition: the seam step (last-of-A vs first-of-B) ≤ typical interior
// step (interior-A vs interior-B), because the blend reduces the apparent jump.
// We also confirm that chunks far from any boundary match exact unblended output
// (golden-seed determinism preserved, T-1042 acceptance criterion 3).
#[test]
fn cross_district_elevation_blend_reduces_seam_step() {
// Two AlluvialPlain districts with a large elev_q contrast to make the
// seam measurable. Chose distinct seeds so the morphology family stays
// AlluvialPlain for both (gates trivially satisfied at grade=0, stable).
let district_a = make_region(
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
5, // slope_q
20, // elev_q — low
15, // ocean_fraction_q (water present for a channel)
55, // moisture_q
Some(15.0),
VegetationClass::Forest,
);
let district_b = make_region(
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
5, // slope_q
70, // elev_q — high (50-unit contrast with A)
15, // ocean_fraction_q
55, // moisture_q
Some(15.0),
VegetationClass::Forest,
);
let (seed, body) = (42u64, "blend_test_body");
// District A occupies chunk columns [0, 31]; district B = [32, 63].
// CHUNKS_PER_DISTRICT = 32.
//
// The LAST chunk of district A: chunk x=31 (within-district index 31 =
// CHUNKS_PER_DISTRICT-1). `district_boundary_blend_weight` returns
// (true, 128) for this position — a 50-50 blend with district B.
//
// The FIRST chunk of district B: chunk x=32 (within-district index 0).
// `district_boundary_blend_weight` returns (false, 255) — no blend.
let last_a_chunk: ChunkPos = (31, 0);
let first_b_chunk: ChunkPos = (32, 0);
// Interior: well inside district A and B, far from any district boundary.
let interior_a_chunk: ChunkPos = (15, 0);
let interior_b_chunk: ChunkPos = (48, 0);
// Derive boundary detection for the last-A chunk.
let (near_boundary, blend_w) = district_boundary_blend_weight(last_a_chunk);
assert!(
near_boundary,
"T-1042: chunk {:?} must be detected as near a district boundary",
last_a_chunk
);
assert_eq!(
blend_w, 128,
"T-1042: boundary blend weight must be 128 (50-50)"
);
// Interior-A context retained for the determinism sub-check (Criterion 3).
let ctx_interior_a = derive_chunk_context(seed, body, &district_a, interior_a_chunk, None);
// Average elevation over a tall multi-wavelength y-transect at a given x-column,
// deriving a ChunkContext per y-chunk. T-1081 adds a zero-mean voxel-relief term to
// elevation_m; averaged over ≥4× the coarsest relief wavelength (1024 m) it cancels,
// leaving the elev_q-derived base — the quantity T-1042's blend actually smooths. A
// single 64 m row would carry a per-chunk relief offset that swamps the seam signal.
let avg_elev =
|x_chunk: i32, blend: Option<(&DistrictProfile, u8)>, dist: &DistrictProfile| -> i64 {
const Y_CHUNKS: i32 = 128; // 8192 m ≈ 8× the 1024 m coarsest relief octave
let base_x = x_chunk * scale::CHUNK_M;
let mut sum = 0i64;
let mut n = 0i64;
for yc in 0..Y_CHUNKS {
let cp: ChunkPos = (x_chunk, yc);
let ctx = derive_chunk_context(seed, body, dist, cp, blend);
let base_y = yc * scale::CHUNK_M;
for dx in (0..scale::VOXELS_PER_CHUNK).step_by(8) {
for dy in (0..scale::VOXELS_PER_CHUNK).step_by(8) {
let col =
derive_voxel_column(seed, body, dist, &ctx, base_x + dx, base_y + dy);
sum += col.elevation_m as i64;
n += 1;
}
}
}
sum / n
};
// Force the 50-50 blend with B across the whole last-A column (x=31); the interiors
// and first-B column are unblended (per the boundary detection asserted above).
let elev_last_a = avg_elev(last_a_chunk.0, Some((&district_b, blend_w)), &district_a);
let elev_first_b = avg_elev(first_b_chunk.0, None, &district_b);
let elev_interior_a = avg_elev(interior_a_chunk.0, None, &district_a);
let elev_interior_b = avg_elev(interior_b_chunk.0, None, &district_b);
// Seam step = elevation gap between the blended last-A chunk and the clean first-B chunk.
let seam_step = (elev_last_a - elev_first_b).unsigned_abs() as i64;
// Unblended step = elevation gap between pure interior chunks.
let interior_step = (elev_interior_a - elev_interior_b).unsigned_abs() as i64;
// Criterion 1: the seam step must be strictly less than the interior step.
// The blend reduces the apparent jump — if blending were absent the seam
// step would equal the interior step (both districts differ by 50 elev_q units).
assert!(
seam_step < interior_step,
"T-1042: cross-district seam step ({seam_step} m) must be < unblended \
interior step ({interior_step} m) — blend is not reducing the seam"
);
// Criterion 3: interior chunks produce IDENTICAL output to an unblended context.
// `ctx_interior_a` has blend_weight=255, secondary=None — same as the
// pre-T-1042 path. Derive twice; must match.
let ctx_interior_a2 = derive_chunk_context(seed, body, &district_a, interior_a_chunk, None);
for dx in 0..scale::VOXELS_PER_CHUNK {
let base_x = interior_a_chunk.0 * scale::CHUNK_M;
let base_y = interior_a_chunk.1 * scale::CHUNK_M;
let col1 = derive_voxel_column(
seed,
body,
&district_a,
&ctx_interior_a,
base_x + dx,
base_y,
);
let col2 = derive_voxel_column(
seed,
body,
&district_a,
&ctx_interior_a2,
base_x + dx,
base_y,
);
assert_eq!(
col1.elevation_m, col2.elevation_m,
"T-1042: interior chunk elevation must be deterministic across two derivations \
at voxel offset {dx}"
);
assert_eq!(
col1.terrain, col2.terrain,
"T-1042: interior chunk terrain must be deterministic at voxel offset {dx}"
);
}
}
#[test]
fn cross_district_morphology_family_seams_stay_sharp() {
// T-1042 Criterion 2: morphology family is NEVER blended across a district
// boundary (D-239 §7). An AlluvialPlain district adjacent to a FjordWall
// district must produce strictly AlluvialPlain (Soil terrain) in the
// last chunk of the alluvial district, even at 50-50 blend weight.
//
// The secondary district (FjordWall) has Rock terrain; the primary (Alluvial)
// has Soil. After blending, if family selection were inadvertently reading
// the secondary's zone, some tiles would switch to Rock — catch that here.
let district_alluvial = make_region(
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
5,
20,
0, // no channel — simpler tile layout for a clean terrain check
40,
Some(15.0),
VegetationClass::Forest,
);
let district_fjord = make_region(
MorphologyZone::Fjord,
TectonicClass::Active,
GlaciationGrade::Moderate,
55,
60,
28,
55,
Some(-8.0),
VegetationClass::Barren,
);
let (seed, body) = (99u64, "seam_sharp_test");
// Last chunk of the alluvial district — blend with the fjord at 50-50.
let boundary_chunk: ChunkPos = (31, 0);
let (_, blend_w) = district_boundary_blend_weight(boundary_chunk);
let ctx = derive_chunk_context(
seed,
body,
&district_alluvial,
boundary_chunk,
Some((&district_fjord, blend_w)),
);
// Every voxel in this chunk must have Soil terrain (AlluvialPlain primary family).
// If family dispatch accidentally picked up the secondary (FjordWall → Rock),
// this assertion fails.
let base_x = boundary_chunk.0 * scale::CHUNK_M;
let base_y = boundary_chunk.1 * scale::CHUNK_M;
for dy in (0..scale::VOXELS_PER_CHUNK).step_by(8) {
for dx in (0..scale::VOXELS_PER_CHUNK).step_by(8) {
let col = derive_voxel_column(
seed,
body,
&district_alluvial,
&ctx,
base_x + dx,
base_y + dy,
);
assert_eq!(
col.terrain,
TerrainMaterial::Soil,
"T-1042 §7 VIOLATED: cross-district boundary chunk must keep primary \
morphology (AlluvialPlain→Soil) at voxel ({},{}) — got {:?}",
base_x + dx,
base_y + dy,
col.terrain
);
}
}
}
#[test]
fn fjord_district_has_one_valley_spanning_chunks() {
// T-1041 (b): a FjordWall district 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 districts × three along rows.
let district = make_region(
MorphologyZone::Fjord,
TectonicClass::Active,
GlaciationGrade::Moderate,
55,
60,
28,
55,
Some(-8.0),
VegetationClass::Barren,
);
let (seed, body) = (42u64, "fjord_body");
for district_chunk in [(640, -480), (-336, 992)] {
let probe = derive_chunk_context(seed, body, &district, district_chunk, None);
let ns = matches!(
probe.basin_direction,
BasinDirection::North | BasinDirection::South
);
let (cross_chunk, along_chunk) = if ns {
(district_chunk.0, district_chunk.1)
} else {
(district_chunk.1, district_chunk.0)
};
let cross_base = (cross_chunk >> scale::CHUNK_DISTRICT_SHIFT) * scale::DISTRICT_M;
let along_base = (along_chunk >> scale::CHUNK_DISTRICT_SHIFT) * scale::DISTRICT_M;
let positions: Vec<i32> = (cross_base..cross_base + scale::DISTRICT_M).collect();
for along in [
along_base + 32,
along_base + scale::DISTRICT_M / 2,
along_base + scale::DISTRICT_M - 32,
] {
let clusters = count_feature_clusters(
&positions,
|c| derive_at_cross_along(seed, body, &district, ns, c, along).water == Water::Deep,
16,
);
assert_eq!(
clusters, 1,
"T-1041: FjordWall district {district_chunk:?} must contain exactly ONE \
deep-water trough across its 2048 m cross extent at along={along} \
(got {clusters}; pre-fix: one per 64 m chunk)"
);
}
}
}
#[test]
fn gorge_district_has_one_valley_spanning_chunks() {
// T-1041 (b): an IncisedGorge district contains ONE shallow-floor gorge
// spanning its chunks — not one per chunk.
let district = make_region(
MorphologyZone::MountainPass,
TectonicClass::Active,
GlaciationGrade::None,
50,
65,
5,
40,
Some(5.0),
VegetationClass::Scrub,
);
let (seed, body) = (42u64, "gorge_body");
for district_chunk in [(640, -480), (-336, 992)] {
let probe = derive_chunk_context(seed, body, &district, district_chunk, None);
let ns = matches!(
probe.basin_direction,
BasinDirection::North | BasinDirection::South
);
let (cross_chunk, along_chunk) = if ns {
(district_chunk.0, district_chunk.1)
} else {
(district_chunk.1, district_chunk.0)
};
let cross_base = (cross_chunk >> scale::CHUNK_DISTRICT_SHIFT) * scale::DISTRICT_M;
let along_base = (along_chunk >> scale::CHUNK_DISTRICT_SHIFT) * scale::DISTRICT_M;
let positions: Vec<i32> = (cross_base..cross_base + scale::DISTRICT_M).collect();
for along in [
along_base + 32,
along_base + scale::DISTRICT_M / 2,
along_base + scale::DISTRICT_M - 32,
] {
let clusters = count_feature_clusters(
&positions,
|c| {
derive_at_cross_along(seed, body, &district, ns, c, along).water
== Water::Shallow
},
16,
);
assert_eq!(
clusters, 1,
"T-1041: IncisedGorge district {district_chunk:?} must contain exactly ONE \
shallow gorge floor across its 2048 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 district has ONE continuous (warp-displaced)
// coast line on the district-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 district.
let district = make_region(
MorphologyZone::CliffCoast,
TectonicClass::Active,
GlaciationGrade::None,
60,
40,
20,
30,
Some(10.0),
VegetationClass::Scrub,
);
let (seed, body) = (42u64, "cliff_body");
for district_chunk in [(656, -464), (-256, 768)] {
let probe = derive_chunk_context(seed, body, &district, district_chunk, None);
let coast = probe.coast_anchor_m;
let ns = matches!(
probe.basin_direction,
BasinDirection::North | BasinDirection::South
);
let (cross_chunk, along_chunk) = if ns {
(district_chunk.0, district_chunk.1)
} else {
(district_chunk.1, district_chunk.0)
};
let along_base = (along_chunk >> scale::CHUNK_DISTRICT_SHIFT) * scale::DISTRICT_M;
let cross_fixed = cross_chunk * 64 + 32;
let positions: Vec<i32> = (along_base..along_base + scale::DISTRICT_M).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, &district, ns, cross_fixed, a).water
== Water::Deep
},
16,
);
assert_eq!(
clusters, 1,
"T-1041: CliffCoast district {district_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, &district, 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 district-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
// district's cross extent.
let district = make_region(
MorphologyZone::Delta,
TectonicClass::Active,
GlaciationGrade::None,
3,
8,
25,
50,
Some(18.0),
VegetationClass::Scrub,
);
let (seed, body) = (17u64, "delta_body");
for district_chunk in [(800, -592)] {
let probe = derive_chunk_context(seed, body, &district, district_chunk, None);
let ns = matches!(
probe.basin_direction,
BasinDirection::North | BasinDirection::South
);
let anchor = probe.channel_anchor_m;
let (cross_chunk, along_chunk) = if ns {
(district_chunk.0, district_chunk.1)
} else {
(district_chunk.1, district_chunk.0)
};
let cross_base = (cross_chunk >> scale::CHUNK_DISTRICT_SHIFT) * scale::DISTRICT_M;
let along = along_chunk * 64 + 32;
let wet: Vec<i32> = (cross_base..cross_base + scale::DISTRICT_M)
.filter(|&c| {
derive_at_cross_along(seed, body, &district, ns, c, along).water == Water::Shallow
})
.collect();
assert!(
!wet.is_empty(),
"T-1041: BraidedDelta district {district_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 district 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)
// ---------------------------------------------------------------------------
/// Derive all 4096 voxels in a 64×64 chunk and return (count, elapsed_µs).
fn derive_chunk_timed(
seed: u64,
body_id: &str,
district: &DistrictProfile,
chunk_pos: (i32, i32),
) -> (usize, u128) {
let chunk = derive_chunk_context(seed, body_id, district, chunk_pos, None);
let base_x = chunk_pos.0 * 64;
let base_y = chunk_pos.1 * 64;
let t0 = Instant::now();
let mut count = 0usize;
for dy in 0..64i32 {
for dx in 0..64i32 {
let _ = derive_voxel_column(seed, body_id, district, &chunk, base_x + dx, base_y + dy);
count += 1;
}
}
(count, t0.elapsed().as_micros())
}
/// All 8 family inputs for the budget sweep.
fn budget_families() -> Vec<(&'static str, DistrictProfile)> {
vec![
(
"AlluvialPlain",
make_region(
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
5,
20,
18,
60,
Some(15.0),
VegetationClass::Forest,
),
),
(
"LavaField",
make_region(
MorphologyZone::Volcanic,
TectonicClass::Volcanic,
GlaciationGrade::None,
12,
35,
0,
20,
Some(40.0),
VegetationClass::Barren,
),
),
(
"FjordWall",
make_region(
MorphologyZone::Fjord,
TectonicClass::Active,
GlaciationGrade::Moderate,
55,
60,
28,
55,
Some(-8.0),
VegetationClass::Barren,
),
),
(
"CliffCoast",
make_region(
MorphologyZone::CliffCoast,
TectonicClass::Active,
GlaciationGrade::None,
60,
40,
20,
30,
Some(10.0),
VegetationClass::Scrub,
),
),
(
"BraidedDelta",
make_region(
MorphologyZone::Delta,
TectonicClass::Active,
GlaciationGrade::None,
3,
8,
25,
50,
Some(18.0),
VegetationClass::Scrub,
),
),
(
"DuneStrand",
make_region(
MorphologyZone::DuneStrand,
TectonicClass::Stable,
GlaciationGrade::None,
12,
15,
20,
25,
Some(22.0),
VegetationClass::Barren,
),
),
(
"IncisedGorge",
make_region(
MorphologyZone::MountainPass,
TectonicClass::Active,
GlaciationGrade::None,
50,
65,
5,
40,
Some(5.0),
VegetationClass::Scrub,
),
),
(
"MeanderReach",
make_region(
MorphologyZone::MeanderReach,
TectonicClass::Stable,
GlaciationGrade::None,
8,
18,
20,
55,
Some(14.0),
VegetationClass::Forest,
),
),
]
}
#[test]
fn budget_per_family_chunk_derivation() {
let seed = 0xdeadbeef_12345678_u64;
let body_id = "budget_test_body";
let hard_gate = std::env::var("BUDGET_ASSERT").is_ok();
// Warm up with the fallback family first (also establishes baseline).
let (_, _) = derive_chunk_timed(seed, body_id, &budget_families()[0].1, (0, 0));
let (_, baseline_us) = derive_chunk_timed(seed, body_id, &budget_families()[0].1, (0, 0));
eprintln!(
"[budget] AlluvialPlain baseline: {} µs / 4096 voxels",
baseline_us
);
let mut results: Vec<(&str, u128)> = vec![];
for (label, district) in budget_families() {
let (cnt, us) = derive_chunk_timed(seed, body_id, &district, (1, 1));
let ms = us as f64 / 1000.0;
eprintln!("[budget] {label}: {ms:.2} ms / {cnt} voxels");
assert_eq!(cnt, 4096, "chunk must produce exactly 4096 voxels");
// Hard gate: < 5 ms. Only enforced when BUDGET_ASSERT=1 (release build).
// D-239 §10 target: ~2.24.2 ms/chunk.
if hard_gate {
assert!(
ms < 5.0,
"§10 budget EXCEEDED for '{label}': {ms:.2} ms > 5 ms. \
(Ensure release mode: cargo test --test derivation_harness --release)"
);
}
results.push((label, us));
}
// Relative assertion: FjordWall/IncisedGorge are noted as costlier than
// AlluvialPlain (D-239 §10), but must not be pathologically worse (>40×).
// In debug builds this catches O(n²) algorithmic defects regardless of
// absolute timing.
for (label, us) in &results {
if *label == "FjordWall" || *label == "IncisedGorge" {
let ratio = *us as f64 / baseline_us.max(1) as f64;
assert!(
ratio < 40.0,
"§10 relative budget: '{label}' is {ratio:.1}× slower than AlluvialPlain \
({:.2} ms vs {:.2} ms baseline). Check for algorithmic regression.",
*us as f64 / 1000.0,
baseline_us as f64 / 1000.0
);
}
}
}
// ── VoxelCache LRU eviction scan ─────────────────────────────────────────────
//
// D-239 §10 / T-1031 note: VoxelCache uses an O(capacity) LRU eviction scan
// (linear BTreeMap iteration for min access_gen). This is accepted behaviour
// for Phase 4 (no production render loop yet). We measure the overhead here
// to document it empirically.
//
// T-1031 baseline: ~4055× overhead in debug builds at capacity=1024, 4096 voxels.
// The gate is 200× (catastrophic regression detection only).
// Phase 5 upgrade: replace the BTreeMap linear scan with a min-heap or
// generation-indexed secondary structure to achieve <5× overhead.
#[test]
fn budget_voxel_cache_lru_overhead() {
let seed = 0xfeedface_12345678_u64;
let body_id = "cache_budget_body";
let district = make_region(
MorphologyZone::AlluvialPlain,
TectonicClass::Stable,
GlaciationGrade::None,
5,
20,
18,
60,
Some(15.0),
VegetationClass::Forest,
);
let chunk_pos = (2, 2);
let chunk = derive_chunk_context(seed, body_id, &district, chunk_pos, None);
let base_x = chunk_pos.0 * 64;
let base_y = chunk_pos.1 * 64;
// Adversarial capacity: one-quarter of a chunk (1024) forces frequent
// LRU evictions on every sequential pass through the full 4096 voxels.
let capacity = 1024;
let mut cache = VoxelCache::new(capacity);
let t0 = Instant::now();
for dy in 0..64i32 {
for dx in 0..64i32 {
let _ = cache.get_or_derive(seed, body_id, &district, &chunk, base_x + dx, base_y + dy);
}
}
let cache_us = t0.elapsed().as_micros();
// Direct derivation for comparison.
let (_, direct_us) = derive_chunk_timed(seed, body_id, &district, chunk_pos);
let overhead_ratio = cache_us as f64 / direct_us.max(1) as f64;
eprintln!(
"[cache_lru] direct: {direct_us} µs | cache (cap={capacity}): {cache_us} µs | \
overhead: {overhead_ratio:.1}× \
[T-1031 note: O(capacity) BTreeMap scan; accepted for Phase 4, ~4055× measured]"
);
// Measured overhead in debug builds: ~4055× (capacity=1024, 4096 voxels).
// Root cause: BTreeMap linear scan for min access_gen on every eviction — O(n) per
// insert when cache is full. Accepted for Phase 4 (no production render loop).
//
// Gate at 200× to catch catastrophic regressions only (e.g. nested scans,
// quadratic growth). The 4055× figure is intentionally documented here as the
// T-1031 baseline so future maintainers know what "acceptable" looks like.
// When a render loop is wired (Phase 5), upgrade to a min-heap or generation
// bitmap and lower this gate to <5×.
assert!(
overhead_ratio < 200.0,
"VoxelCache LRU overhead {overhead_ratio:.1}× exceeds 200×. \
The O({capacity}) BTreeMap scan has regressed beyond the T-1031 Phase 4 baseline. \
Consider upgrading the eviction strategy."
);
}
// ---------------------------------------------------------------------------
// §4 — Validation body cases (D-239 §1)
// ---------------------------------------------------------------------------
//
// Body params are sourced directly from wiki frontmatter (DB-free).
// If a body is absent from the wiki, the test SKIPS with a logged note.
/// Kallast (GJ144d) params — wiki: star-systems/GJ-144/bodies/GJ144d/index.md
fn kallast_body_params() -> BodyParams {
// planet_class: temperate | atmosphere: standard | hydrosphere: ocean | tectonics: active
// (orbit/star data is non-canonical per D-240 and no longer feeds temperature)
BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("standard".into()),
planet_class: Some("temperate".into()),
tectonic_activity: Some("active".into()),
latitude_deg: 0.0,
elevation_km: 0.0,
body_radius_km: None,
}
}
/// Glødberg (GJ581c) params — wiki: star-systems/GJ-581/bodies/GJ581c/index.md
fn gloedberg_body_params() -> BodyParams {
// planet_class: volcanic | atmosphere: standard | hydrosphere: subsurface_liquid | tectonics: extreme
// (orbit/star data is non-canonical per D-240 and no longer feeds temperature)
BodyParams {
hydrosphere: Some("subsurface".into()),
atmosphere: Some("standard".into()),
planet_class: Some("volcanic".into()),
tectonic_activity: Some("volcanic".into()),
latitude_deg: 0.0,
elevation_km: 0.5,
body_radius_km: None,
}
}
/// Marevna (GJ447c) params — wiki: star-systems/GJ-447/bodies/GJ447c/index.md
/// GJ-447 is the real-world identifier for Ross 128.
fn marevna_body_params() -> BodyParams {
// planet_class: oceanic | atmosphere: standard | hydrosphere: ocean | tectonics: active
// (orbit/star data is non-canonical per D-240 and no longer feeds temperature)
BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("standard".into()),
planet_class: Some("oceanic".into()),
tectonic_activity: Some("active".into()),
latitude_deg: 0.0,
elevation_km: 0.0,
body_radius_km: None,
}
}
/// Derive a DistrictProfile for a body at given latitude/elevation,
/// using the full derive_district_profile pipeline over a minimal dry-land heightmap.
///
/// Uses a heightmap where all cells are above sea level (data = 0.5, sea_level = 0.3),
/// so ocean_fraction_q = 0 at all positions. This ensures the morphology classifier
/// reaches the tectonic/glaciation gates without being short-circuited by the Tier-0
/// ocean check (which fires at ocean_fraction_q ≥ 60).
fn derive_profile_for_body(params: &BodyParams) -> DistrictProfile {
let climate = ClimateConstants::default();
let seed = SeedChain::root(42).derive(SeedDomain::Body, 1);
let ta = make_dry_terrain_analysis();
derive_district_profile(
seed,
params,
&ta,
scale::SurveyCellPos(0, 0),
8,
&climate,
"test_body",
&BTreeMap::new(),
settled_reach_server::atlas::scale::BasinDirection::default(),
None,
)
}
#[test]
fn validation_kallast_alluvial_plain() {
// D-239 §1: Kallast = alluvial plain / Soil. GJ144d, K-star, temperate.
eprintln!("[validation] Kallast (GJ144d): checking alluvial plain / non-volcanic / non-fjord");
let profile = derive_profile_for_body(&kallast_body_params());
// Not volcanic — Kallast is a temperate world.
assert_ne!(
profile.tectonic_class,
TectonicClass::Volcanic,
"Kallast: TectonicClass must not be Volcanic (param: active)"
);
// Not fjord — no glaciation at temperate K-star orbit.
assert_ne!(
profile.morphology_zone,
MorphologyZone::Fjord,
"Kallast: temperate body must not produce Fjord"
);
// Not volcanic morphology.
assert_ne!(
profile.morphology_zone,
MorphologyZone::Volcanic,
"Kallast: temperate body must not produce Volcanic"
);
// Plausible terrestrial/coastal zone for a temperate body with ocean hydro.
let plausible = matches!(
profile.morphology_zone,
MorphologyZone::AlluvialPlain
| MorphologyZone::MeanderReach
| MorphologyZone::RiverBank
| MorphologyZone::Delta
| MorphologyZone::Estuarine
| MorphologyZone::TidalFlat
| MorphologyZone::Lake
| MorphologyZone::OpenOcean
| MorphologyZone::Wetland
);
assert!(
plausible,
"Kallast: expected alluvial/coastal zone, got {:?}. \
If wrong, fix body params — not the derivation (D-239 §1).",
profile.morphology_zone
);
// Temperature plausible for K-star temperate world.
if let Some(t) = profile.temperature_c {
assert!(
(-20.0..=50.0).contains(&t),
"Kallast: temperature {t}°C outside plausible range [-20, 50]"
);
}
eprintln!(
"[validation] Kallast: zone={:?} temp={:?}°C glaciation={:?} tectonic={:?} — PASS",
profile.morphology_zone,
profile.temperature_c,
profile.glaciation_grade,
profile.tectonic_class
);
}
#[test]
fn validation_gloedberg_volcanic_immature_drainage() {
// D-239 §1: "Cygni B = volcanic immature drainage."
// No body named "Cygni B" exists in the current wiki. GJ581c (Glødberg) is
// the closest volcanic match. NOTE: update this test if a "Cygni B" body is
// added to the wiki in the future.
eprintln!(
"[validation] Glødberg (GJ581c): volcanic proxy — checking LavaField + immature drainage"
);
let profile = derive_profile_for_body(&gloedberg_body_params());
// Assert volcanic tectonic class.
assert_eq!(
profile.tectonic_class,
TectonicClass::Volcanic,
"Glødberg: volcanic body must have TectonicClass::Volcanic"
);
// Assert Volcanic morphology (LavaField classifier fires first — §5 gate order).
assert_eq!(
profile.morphology_zone,
MorphologyZone::Volcanic,
"Glødberg: volcanic body must produce Volcanic zone (LavaField family)"
);
// No glaciation on a hot close-orbit volcanic world.
assert_eq!(
profile.glaciation_grade,
GlaciationGrade::None,
"Glødberg: hot volcanic body at 12-day orbit must have no glaciation"
);
// Derive a voxel and check TerrainMaterial::Lava (§8 lithology law).
let chunk = derive_chunk_context(42, "GJ581c", &profile, (0, 0), None);
let col = derive_voxel_column(42, "GJ581c", &profile, &chunk, 100, 100);
assert_eq!(
col.terrain,
TerrainMaterial::Lava,
"Glødberg: voxel must be Lava (§8 lava law)"
);
// Immature drainage: no active channels on lava fields (D-239 §8).
// The LavaField generator explicitly ignores has_active_channel.
assert_eq!(
col.water,
Water::Dry,
"Glødberg: volcanic immature drainage — voxel at (100,100) must be Dry \
(LavaField ignores active channel per §8 immature drainage law)"
);
eprintln!(
"[validation] Glødberg: zone={:?} temp={:?}°C terrain={:?} — PASS",
profile.morphology_zone, profile.temperature_c, col.terrain
);
}
#[test]
fn validation_marevna_ocean_island() {
// D-239 §1: "Ross 128 = ocean island." GJ-447 is Ross 128; GJ447c = Marevna.
eprintln!(
"[validation] Marevna (GJ447c / Ross 128): ocean island — checking coastal/ocean zone"
);
let profile = derive_profile_for_body(&marevna_body_params());
// Not volcanic.
assert_ne!(
profile.morphology_zone,
MorphologyZone::Volcanic,
"Marevna: oceanic world must not produce Volcanic"
);
// Not fjord — warm M-star body, no glaciation expected.
assert_ne!(
profile.morphology_zone,
MorphologyZone::Fjord,
"Marevna: warm oceanic world must not produce Fjord (no glaciation)"
);
// Plausible zone for an oceanic high-moisture world on a flat dry-land test terrain.
// moisture_q = 80 (ocean hydro + standard atmo) + slope_q ≈ 0 → Wetland is the
// dominant output from the derivation (§8 Wetland ≤5° flats). AlluvialPlain and
// coastal zones are also plausible depending on ocean_fraction_q signal.
let plausible = matches!(
profile.morphology_zone,
MorphologyZone::OpenOcean
| MorphologyZone::Lake
| MorphologyZone::Delta
| MorphologyZone::Estuarine
| MorphologyZone::TidalFlat
| MorphologyZone::MeanderReach
| MorphologyZone::AlluvialPlain
| MorphologyZone::RiverBank
| MorphologyZone::Wetland // Expected on flat high-moisture terrain (moisture_q=80, slope≈0)
);
assert!(
plausible,
"Marevna: oceanic world expected coastal/wetland zone, got {:?}. \
If wrong, fix body params — not the derivation (D-239 §1).",
profile.morphology_zone
);
// Temperature is deterministically derived from the planet_class envelope (D-240),
// not orbit/star data: oceanic band [-12, 28]°C, maritime factor 0.6 for an "ocean"
// hydrosphere. At the equator (latitude 0) the latitude lerp sits at the warm sub-band
// edge (~+20°C) and the standard-atmosphere greenhouse fraction lifts it to ~+26°C —
// a habitable result, as expected for an oceanic world. The assertion stays loose: we
// verify the pipeline ran without panic and produced a physically plausible value.
if let Some(t) = profile.temperature_c {
assert!(
t > -90.0 && t < 90.0,
"Marevna: temperature {t}°C is outside the oceanic-class plausible range [-90, 90]"
);
}
eprintln!(
"[validation] Marevna: zone={:?} temp={:?}°C moisture_q={} — PASS \
(oceanic class-envelope temp at equator per D-240)",
profile.morphology_zone, profile.temperature_c, profile.moisture_q
);
}
#[test]
fn validation_velen_skipped_not_in_wiki() {
// D-239 §1: "Velen = tidal-flat/dune coast."
// D-239 §1 caveat: "tidal flats require a moon param for the D-228 tidal
// term — verify Velen's params before treating its coast as a contract."
//
// STATUS: Body named "Velen" does NOT exist in the current wiki.
// Searched wiki/star-systems/**/*.md for `name: Velen` → NOT FOUND.
// The tidal/moon param cannot be verified (no body to inspect).
// This test is a deliberate skip with documented reason.
// Re-enable when Velen is created in the wiki and systems.db.
eprintln!(
"[validation] SKIPPED Velen (tidal-flat/dune coast):\n \
Body 'Velen' not found in wiki/star-systems/**/*.md. Lore anchor not\n \
yet materialised as a named body in systems.db. D-239 §1 caveat:\n \
moon/tidal param not verifiable. Re-enable when Velen is created."
);
}
#[test]
fn validation_gruenfeld_skipped_not_in_wiki() {
// D-239 §1: "Gruenfeld = marginal Gravel/Scrub."
//
// STATUS: Body named "Gruenfeld" does NOT exist in the current wiki.
// Searched wiki/star-systems/**/*.md for `name: Gruenfeld` → NOT FOUND.
// Re-enable when Gruenfeld is created in the wiki and systems.db.
eprintln!(
"[validation] SKIPPED Gruenfeld (marginal Gravel/Scrub):\n \
Body 'Gruenfeld' not found in wiki/star-systems/**/*.md. Lore anchor\n \
not yet materialised. Re-enable when Gruenfeld is created."
);
}
// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------
/// Whether the district's basin runs north/south (cross axis = x). District-scale
/// property — identical for every chunk of the district containing `district_chunk`.
fn basin_is_ns(
seed: u64,
body_id: &str,
district: &DistrictProfile,
district_chunk: ChunkPos,
) -> bool {
let probe = derive_chunk_context(seed, body_id, district, district_chunk, None);
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 district containing `district_chunk` (T-1040).
///
/// The channel/landform centreline is district-anchored: it lives in the anchor
/// chunk column, swinging up to one meander amplitude sideways. The meander
/// wavelength (≤ ~650 m) fits inside the district's 2048 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,
district: &DistrictProfile,
district_chunk: ChunkPos,
) -> Vec<ChunkPos> {
let probe = derive_chunk_context(seed, body_id, district, district_chunk, None);
let anchor_idx = probe.channel_anchor_m.div_euclid(64);
let ns = matches!(
probe.basin_direction,
BasinDirection::North | BasinDirection::South
);
// District base index on the along axis (16-chunk districts; arithmetic shift
// = floor division, correct for negative chunks).
let along_base = if ns {
(district_chunk.1 >> scale::CHUNK_DISTRICT_SHIFT) << scale::CHUNK_DISTRICT_SHIFT
} else {
(district_chunk.0 >> scale::CHUNK_DISTRICT_SHIFT) << scale::CHUNK_DISTRICT_SHIFT
};
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,
district: &DistrictProfile,
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, district, chunk_pos, None);
derive_voxel_column(seed, body_id, district, &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 `DistrictProfile` directly from parameters, deriving the
/// dependent fields (precipitation_class, river_threshold) consistently.
fn make_region(
zone: MorphologyZone,
tectonic: TectonicClass,
glaciation: GlaciationGrade,
slope_q: i32,
elev_q: i32,
ocean_fraction_q: i32,
moisture_q: i32,
temperature_c: Option<f32>,
vegetation_class: VegetationClass,
) -> DistrictProfile {
let precip = derive_precipitation_class_from_climate(temperature_c, moisture_q);
DistrictProfile {
morphology_zone: zone,
tectonic_class: tectonic,
glaciation_grade: glaciation,
precipitation_class: precip,
slope_q,
elev_q,
relief_q: 50,
ocean_fraction_q,
lake_margin_q: 0,
river_threshold: derive_river_threshold(tectonic, precip),
temperature_c,
moisture_q,
vegetation_class,
basin_direction: settled_reach_server::atlas::scale::BasinDirection::default(),
}
}
/// Build a flat all-dry `TerrainAnalysis` for validation body derivation.
///
/// All cells have elevation = 0.5 with sea_level = 0.3 → ocean_fraction_q = 0 at
/// every district position. This prevents the Tier-0 ocean short-circuit in
/// `derive_morphology_zone` (ocean_fraction_q ≥ 60) from overriding the tectonic
/// and glaciation gates that we're testing.
///
/// Uses a gentle constant slope (no variation) so slope_q ~ 0 and elev_q ~ 75,
/// which places the morphology classifier in the AlluvialPlain fallback unless the
/// tectonic/glaciation gates fire first. That is the correct pre-condition for
/// lore body tests.
fn make_dry_terrain_analysis() -> TerrainAnalysis {
let (w, h) = (64u32, 32u32);
let n = (w * h) as usize;
// Constant 0.5: all above sea_level=0.3 → zero ocean cells.
let data: Vec<f32> = vec![0.5_f32; n];
let hm = BodyHeightmap {
body_id: "dry_test".into(),
width: w,
height: h,
data,
sea_level: 0.3,
};
let dr = drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level);
TerrainAnalysis::analyze(&hm, &dr)
}