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>
140 lines
6.1 KiB
Rust
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()
|
|
);
|
|
}
|