Files
settled-reach/server/tests/believability_harness.rs
jpmschweitzerandClaude Fable 5 933e1f4ee4 fix(simulation): one absolute-metre derive core (D-256, T-1174)
derive_district_profile is now a thin wrapper over the shared
derive_at_metres_with_riparian core at survey-cell-centre world metres
— one derive core, two position sets. The batch pseudo-grid and the
true D-243 district grid were two coordinate systems sharing one bare
(i32,i32) type; the new SurveyCellPos newtype re-keys every batch
product (BodyWorldState.districts, Layer1Output.survey_basin_dirs) so
the compiler rejects cross-namespace passing.

Fixes two latent same-position divergences the T-1174 investigation
surfaced: three inconsistent latitude conventions collapse into the
core's single inverse mapping, and the region-climate baseline now
floor-divides true world metres instead of collapsing the whole body
onto region (0,0)'s baseline — batch climate becomes latitude/region
graded (D-245 direction: every changed believability metric increased).

Binding preservations per D-256(c): basin_direction rides a post-call
override with the true L1 D8 survey-cell aggregate (layer1's map
re-keyed to SurveyCellPos, identity lookup — a floor-divide lookup
against the pseudo-keyed map would have silently defaulted every cell
North); the riparian verdict comes from near_perennial_water_at, never
the empty-slice default (which would have flipped riverside
vegetation_class).

Quarter-skeleton morphology_zone now resolves at the settlement's
exact world position via derive_at_metres at work-item execution
(where TerrainAnalysisCache lives), replacing the survey-cell-centre
map lookup (D-256(d)); settlement_district_pos fixed to true-district
floor-division in passing (same doc/impl mismatch class). Second
pixel-vs-metre conflation fixed in aliveness_probe's anchor-walk math.

Window path byte-unchanged (window_derivation_golden 6/6 byte-
identical); derivation_harness golden untouched; believability golden
regenerated. Full lib + integration suites green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 00:40:13 +02:00

150 lines
6.4 KiB
Rust

//! Believability regression harness (T-1083) — the D-245 acceptance-gate enforcer.
//!
//! D-245 makes "the nature layers read alive *anywhere*" the deliverable of the
//! cascade's nature half. This harness is its repeatable instrument: for a set of
//! validation bodies at fixed seeds it runs the real deterministic cascade, computes
//! the [`BelievabilityReport`] (contrast + coherence — never marginal per-tile counts,
//! the T-1083 lesson), and:
//!
//! 1. **Determinism** — derives each body twice and asserts the reports are identical
//! (the D-010 contract; the strongest invariant, platform-independent).
//! 2. **Golden baseline** — snapshots the reports so any future cascade change that
//! moves believability (a regression *or* an improvement, e.g. when T-1080/T-1082
//! land) is caught and must be acknowledged. Regenerate with
//! `UPDATE_GOLDEN=1 cargo test --test believability_harness`.
//! 3. **D-245 criteria (advisory → strict)** — prints each criterion's pass/fail per
//! body. Per D-245 the gate starts *budgeted + advisory* (thresholds are Q-123-TBD);
//! set `BELIEVABILITY_STRICT=1` to make unmet criteria *fail* the test (they do
//! today — that is the point: T-1080/T-1081/T-1082 are the open work to flip them).
//!
//! Distinct from `derivation_harness` (T-1031), which checks the binding *laws*; this
//! is its believability sibling — laws are necessary, this is the sufficiency gate.
//!
//! Bodies whose committed data (systems.db / heightmap) is absent are **skipped** with
//! a logged note (matching `derivation_harness`), so the harness is robust in trimmed
//! checkouts. The golden is an x86_64 reference (the cascade has f32 warp paths, per
//! `cascade_golden`).
use settled_reach_server::atlas::believability::{
analyze, cascade_snapshot_for_body, evaluate_criteria, seed_to_u64, BelievabilityReport,
};
const GOLDEN_FILE: &str = "tests/golden/believability.json";
/// `(label, body_id, seed)` — lore-anchored validation bodies spanning the climate
/// extremes the gate must handle: a temperate ocean world (should read lush) and a
/// frozen ice world (must read as a living *cold* landscape, not blank — D-245).
const VALIDATION_BODIES: &[(&str, &str, &str)] = &[
("Arbour (temperate/ocean)", "GJ338Bd", "believability-v1"),
("Edict (frozen/ice)", "GJ244Ad", "believability-v1"),
];
/// Run the cascade + analyze for one body, or `None` if its committed data is absent.
fn report_for(body_id: &str, seed: &str) -> Option<BelievabilityReport> {
let world_seed = seed_to_u64(seed);
match cascade_snapshot_for_body(world_seed, body_id) {
Ok((snapshot, params)) => {
let bws = snapshot.into_body_world_state();
Some(analyze(
world_seed,
body_id,
&bws.districts,
bws.heightmap_width,
bws.heightmap_height,
params.body_radius_km,
))
}
Err(e) => {
eprintln!("[believability] SKIP {body_id}: {e}");
None
}
}
}
#[test]
fn believability_determinism_and_golden() {
let mut reports: Vec<BelievabilityReport> = Vec::new();
for (label, body_id, seed) in VALIDATION_BODIES {
let Some(first) = report_for(body_id, seed) else {
continue;
};
// Determinism (D-010): a second full cascade + analyze must match exactly.
let second = report_for(body_id, seed).expect("body resolved once, must resolve again");
assert_eq!(
first, second,
"[{label}] non-deterministic believability report — D-010 broken"
);
// Structural sanity that holds regardless of believability quality or platform.
assert!(
first.district_count > 0,
"[{label}] cascade produced no districts"
);
assert!(
first.coherence.water_districts_wet <= first.coherence.water_districts,
"[{label}] wet water districts exceed total"
);
// Advisory D-245 criteria report (strict via BELIEVABILITY_STRICT=1).
let strict = std::env::var("BELIEVABILITY_STRICT").is_ok();
let crit = evaluate_criteria(&first);
let passes = crit.iter().filter(|c| c.pass).count();
eprintln!(
"[believability] {label}: {passes}/{} D-245 criteria pass",
crit.len()
);
for c in &crit {
eprintln!(
" [{}] {}{}",
if c.pass { "PASS" } else { "FAIL" },
c.name,
c.detail
);
if strict {
assert!(
c.pass,
"[{label}] D-245 criterion failed (strict): {}",
c.name
);
}
}
reports.push(first);
}
if reports.is_empty() {
eprintln!("[believability] no validation bodies available — skipping golden");
return;
}
// ── Golden baseline ─────────────────────────────────────────────────────
let golden_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(GOLDEN_FILE);
let actual_json = serde_json::to_string_pretty(&reports).expect("serialize reports") + "\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: {}", golden_path.display());
return;
}
let golden_json = std::fs::read_to_string(&golden_path).unwrap_or_else(|e| {
panic!(
"Golden not found: {}.\n\
First run: UPDATE_GOLDEN=1 cargo test --test believability_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");
assert!(
actual_v == golden_v,
"Believability golden mismatch — the cascade moved believability (regression OR \
improvement). If intended (e.g. T-1080/T-1082 landed), update:\n\
UPDATE_GOLDEN=1 cargo test --test believability_harness\n\nActual:\n{}",
actual_json.trim()
);
}