Files
settled-reach/server/tests/believability_harness.rs
T
jpmschweitzerandClaude Opus 4.8 91a079718b feat(simulation): believability test protocol — D-245 enforcer (T-1083)
Promote the throwaway aliveness probe into a committed, repeatable believability
protocol — the instrument that found T-1080/T-1081/T-1082.

- atlas/believability.rs (new): BelievabilityReport (serde) + analyze() computing
  CONTRAST (per-field min/max/distinct for moisture/elev/slope/ocean over all
  districts; distinct morphology zones / vegetation classes / terrain materials)
  and COHERENCE (water-renders-wet, drainage-monotonic, vegetation-present) — never
  marginal per-tile counts (the lesson: that called a broken uniform world ALIVE).
  evaluate_criteria() = advisory D-245 checks; cascade_for_body()/seed_to_u64()
  loader shared by the bin and the harness. Unit tests prove the metric tells a
  uniform world (fails) from a varied one (passes) + determinism.
- bin/aliveness_probe.rs: refactored to a thin CLI over the module — prints the
  body-level report + advisory D-245 criteria, drops the naive per-tile verdict.
- tests/believability_harness.rs (+ golden): runs the real cascade for Arbour
  (temperate/ocean) + Edict (frozen/ice), asserts determinism, golden-snapshots
  the reports (regression — updates when T-1080/T-1082 land), advisory criteria
  with BELIEVABILITY_STRICT=1 to fail on unmet D-245 criteria. Skips if committed
  data absent. x86_64 golden (cascade has f32 warp paths).

Baseline @ believability-v1: Arbour 3/7, Edict 3/7 criteria pass — moisture
gradient, water-renders-wet, drainage all FAIL (T-1080/T-1082); Edict vegetation
0/64 (blank tundra — the frozen 'reads dead' case D-245 targets).

PNG layer maps (ticket item 3) deferred — explicitly optional; the contrast +
coherence metrics are the core enforcer.

clippy --all-targets -D warnings clean; 1575 lib tests + the harness pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 14:55:08 +02:00

140 lines
6.1 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_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_for_body(world_seed, body_id) {
Ok(bws) => Some(analyze(world_seed, body_id, &bws.districts)),
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()
);
}