Files
settled-reach/server/tests/step_canvas_acceptance_gate.rs
T

415 lines
14 KiB
Rust

//! T-1181 acceptance gate — cache-hit derive path == cache-miss derive path,
//! byte-identical, for EVERY D-255(a) rung.
//!
//! This is the mandatory gate the ticket names as its most important
//! deliverable (D-227 amendment (3), D-255(f)): a step canvas is
//! `derive(seed, position)`, a pure function — evicting a cache entry and
//! re-deriving it must produce byte-identical output, never merely
//! "close enough." The same shape as T-1170's window-independence invariant.
//!
//! **What "cache-hit" vs "cache-miss" means here, precisely:** the SERVER
//! derive core (`step_canvas::build_step_canvas`) never itself reads a
//! cache — `StepCanvasCache`/`GlobalTierCache` are populated OUTSIDE the
//! derive core, by the gen_queue completion handler (`plugin.rs`). So
//! "cache-hit == cache-miss" is proven by construction (there is only ONE
//! code path that produces a canvas — `build_step_canvas` — and every
//! caller, whether serving a first request or a Nth repeat, calls it
//! identically) UNLESS a future change introduces a genuine second path
//! (e.g. D-255(f) mechanism-B acceleration reading a resident coarser
//! canvas as an input). This suite pins the invariant directly at the
//! derive-core level (call `build_step_canvas` twice, independently, same
//! inputs, assert byte-identical) AND at the cache level (insert once,
//! `get` it back via `StepCanvasCache`/`GlobalTierCache`, assert the
//! round-tripped bytes match a fresh independent derive) — so this test
//! remains the correctness gate even after a future acceleration path
//! lands, per D-227 amendment (3)'s "mandatory determinism test."
//!
//! Run: `cargo test --test step_canvas_acceptance_gate`
use settled_reach_server::atlas::body_world_state::RiverNetwork;
use settled_reach_server::atlas::district_profile::{BodyParams, ClimateConstants};
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::step_canvas::{
build_step_canvas, decode_step_canvas, encode_step_canvas, BodyDrivingClockClass,
GlobalTierCache, StepCanvasCache, StepCanvasRung,
};
use settled_reach_server::seed::{SeedChain, SeedDomain};
/// Same fixture-building convention `bmv_gridunit_bench.rs`/
/// `window_derivation_golden.rs` already establish — a deterministic
/// gradient heightmap, small enough to run every rung's canvas at a modest
/// (non-benchmark) extent in a plain `cargo test` run.
fn fixture_hm() -> BodyHeightmap {
let (w, h) = (128u32, 64u32);
let n = (w * h) as usize;
let data = (0..n)
.map(|i| {
let r = (i / w as usize) as f32 / h as f32;
let c = (i % w as usize) as f32 / w as f32;
let ripple = (c * std::f32::consts::TAU * 3.0).sin() * 0.08;
(r * 0.6 + c * 0.4 + ripple).clamp(0.0, 1.0)
})
.collect();
BodyHeightmap {
body_id: "gate-body".into(),
width: w,
height: h,
data,
sea_level: 0.3,
}
}
fn fixture_ta(hm: &BodyHeightmap) -> TerrainAnalysis {
let dr = drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level);
TerrainAnalysis::analyze(hm, &dr)
}
fn fixture_river_network(hm: &BodyHeightmap) -> RiverNetwork {
drainage::analyze(&hm.data, hm.width, hm.height, hm.sea_level).river_network
}
fn fixture_params() -> BodyParams {
BodyParams {
hydrosphere: Some("ocean".into()),
atmosphere: Some("breathable".into()),
planet_class: Some("temperate".into()),
body_radius_km: Some(6371.0),
..Default::default()
}
}
/// A small, fast canvas extent for the gate suite — the invariant being
/// tested (byte-identical repeat derivation) does not require production
/// canvas sizes; those are already covered by the workshop's dedicated
/// bench files (`bmv_gridunit_bench.rs`, `bmv_global_tier_bench.rs`).
const GATE_EXTENT: (u32, u32) = (24, 18);
/// Every D-255(a) rung, in ladder order — the gate's "for EVERY rung"
/// requirement, checked exhaustively rather than sampled.
const ALL_RUNGS: [StepCanvasRung; 6] = [
StepCanvasRung::Global,
StepCanvasRung::Region,
StepCanvasRung::District,
StepCanvasRung::Quarter,
StepCanvasRung::Block,
StepCanvasRung::Chunk,
];
/// Core gate: call `build_step_canvas` twice, independently (two fresh
/// `TerrainAnalysis`/`RiverNetwork` builds, not two reads of one shared
/// value — the strongest form of "cache-miss twice"), same
/// `(seed, body, position)` inputs, and assert byte-identical `RawStepCanvas`
/// output for every rung.
#[test]
fn cache_hit_equals_cache_miss_every_rung() {
let seed = SeedChain::root(0xACCE97_u64).derive(SeedDomain::Body, 1);
let climate = ClimateConstants::default();
let center = (12_288_i64, -8_192_i64); // an arbitrary non-origin centre
for rung in ALL_RUNGS {
// Independent build #1 ("cache-miss" run A) — fresh heightmap load,
// fresh drainage analysis, fresh TerrainAnalysis.
let hm_a = fixture_hm();
let ta_a = fixture_ta(&hm_a);
let rn_a = fixture_river_network(&hm_a);
let params_a = fixture_params();
let canvas_a = build_step_canvas(
seed,
"gate-body",
&params_a,
&ta_a,
&rn_a,
&[],
rung,
center,
GATE_EXTENT,
&climate,
0,
);
// Independent build #2 ("cache-miss" run B — simulating what a
// second, later request after eviction would recompute) — every
// input rebuilt from scratch again, not reused from run A.
let hm_b = fixture_hm();
let ta_b = fixture_ta(&hm_b);
let rn_b = fixture_river_network(&hm_b);
let params_b = fixture_params();
let canvas_b = build_step_canvas(
seed,
"gate-body",
&params_b,
&ta_b,
&rn_b,
&[],
rung,
center,
GATE_EXTENT,
&climate,
0,
);
assert_eq!(
canvas_a, canvas_b,
"rung {rung:?}: two independent derive_step_canvas builds diverged — \
D-227 purity violated (cache-hit/cache-miss byte-identity gate)"
);
}
}
/// The same gate at the ENCODED wire level (PNG-per-field round-trip) — a
/// separate property from the raw-derive gate above: even if the derive
/// core is pure, a non-deterministic or lossy encoder would still break the
/// "same request, same bytes on the wire" guarantee a client's persistent
/// cache (D-227 amendment (2)) depends on.
#[test]
fn encoded_canvas_round_trip_is_lossless_every_rung() {
let seed = SeedChain::root(0xACCE97_u64).derive(SeedDomain::Body, 2);
let climate = ClimateConstants::default();
let center = (2_048_i64, 4_096_i64);
let hm = fixture_hm();
let ta = fixture_ta(&hm);
let rn = fixture_river_network(&hm);
let params = fixture_params();
for rung in ALL_RUNGS {
let raw = build_step_canvas(
seed,
"gate-body",
&params,
&ta,
&rn,
&[],
rung,
center,
GATE_EXTENT,
&climate,
0,
);
let encoded = encode_step_canvas(&raw);
let decoded = decode_step_canvas(&encoded);
assert_eq!(
raw, decoded,
"rung {rung:?}: PNG-per-field encode/decode round-trip lost data"
);
// Encoding itself is deterministic — encode the SAME raw canvas
// twice and expect byte-identical PNG output (never "usually
// matches"). This is the property a persistent client cache
// (D-227 amendment (2)) needs: identical input bytes -> identical
// stored bytes, so a schema/version-tag comparison is even
// meaningful.
let encoded_again = encode_step_canvas(&raw);
assert_eq!(
encoded.morphology.png_bytes, encoded_again.morphology.png_bytes,
"rung {rung:?}: PNG encoder is non-deterministic on identical input"
);
}
}
/// The gate at the CACHE-TIER level: insert a derived canvas into
/// `GlobalTierCache`/`StepCanvasCache`, read it back, and confirm the
/// round-tripped bytes match a fresh independent derive — proving the cache
/// layer is a transparent accelerator (D-227 amendment (3): "an
/// optimization, not a semantic dependency") rather than a second source of
/// truth that could silently diverge from the derive core.
#[test]
fn global_tier_cache_round_trip_matches_fresh_derive() {
let seed = SeedChain::root(0xACCE97_u64).derive(SeedDomain::Body, 3);
let climate = ClimateConstants::default();
let hm = fixture_hm();
let ta = fixture_ta(&hm);
let rn = fixture_river_network(&hm);
let params = fixture_params();
let fresh_raw = build_step_canvas(
seed,
"gate-body",
&params,
&ta,
&rn,
&[],
StepCanvasRung::Global,
(0, 0),
GATE_EXTENT, // ignored for Global — extent is the body's region grid
&climate,
0,
);
let fresh_encoded = encode_step_canvas(&fresh_raw);
let mut cache = GlobalTierCache::new();
assert!(cache.get("gate-body").is_none());
cache.insert("gate-body".to_string(), fresh_encoded.clone());
assert!(cache.contains("gate-body"));
let cached = cache
.get("gate-body")
.expect("global tier cache hit after insert");
assert_eq!(
cached, &fresh_encoded,
"GlobalTierCache round-trip diverged from the freshly-derived canvas"
);
// Re-derive fresh AGAIN (simulating a cold-cache request that never
// touched this cache instance at all) and confirm it still matches the
// cached bytes — this is the eviction->recompute->byte-identical
// property D-227 requires.
let hm2 = fixture_hm();
let ta2 = fixture_ta(&hm2);
let rn2 = fixture_river_network(&hm2);
let params2 = fixture_params();
let recomputed_raw = build_step_canvas(
seed,
"gate-body",
&params2,
&ta2,
&rn2,
&[],
StepCanvasRung::Global,
(0, 0),
GATE_EXTENT,
&climate,
0,
);
let recomputed_encoded = encode_step_canvas(&recomputed_raw);
assert_eq!(
cached, &recomputed_encoded,
"eviction->recompute path diverged from the cached global-tier canvas"
);
}
/// Same cache-tier gate for `StepCanvasCache` (fixed rungs 1-5), across
/// every fixed rung.
#[test]
fn step_canvas_cache_round_trip_matches_fresh_derive_every_fixed_rung() {
let seed = SeedChain::root(0xACCE97_u64).derive(SeedDomain::Body, 4);
let climate = ClimateConstants::default();
let center = (512_i64, -1_024_i64);
let extent = GATE_EXTENT;
let min_wl_m = 0u32;
let body_class = BodyDrivingClockClass::Moonless;
for rung in ALL_RUNGS {
if rung.is_global() {
continue; // covered by global_tier_cache_round_trip_matches_fresh_derive
}
let hm = fixture_hm();
let ta = fixture_ta(&hm);
let rn = fixture_river_network(&hm);
let params = fixture_params();
let fresh_raw = build_step_canvas(
seed,
"gate-body",
&params,
&ta,
&rn,
&[],
rung,
center,
extent,
&climate,
min_wl_m,
);
let fresh_encoded = encode_step_canvas(&fresh_raw);
let mut cache = StepCanvasCache::new(8);
let key = ("gate-body".to_string(), rung, center, extent, min_wl_m);
assert!(cache.get(&key, 0, body_class).is_none());
cache.insert(key.clone(), fresh_encoded.clone(), 0);
let cached = cache
.get(&key, 1, body_class)
.unwrap_or_else(|| panic!("rung {rung:?}: cache hit expected after insert"));
assert_eq!(
cached, fresh_encoded,
"rung {rung:?}: StepCanvasCache round-trip diverged from the freshly-derived canvas"
);
// Independent re-derive (simulating eviction) still matches.
let hm2 = fixture_hm();
let ta2 = fixture_ta(&hm2);
let rn2 = fixture_river_network(&hm2);
let params2 = fixture_params();
let recomputed_raw = build_step_canvas(
seed,
"gate-body",
&params2,
&ta2,
&rn2,
&[],
rung,
center,
extent,
&climate,
min_wl_m,
);
let recomputed_encoded = encode_step_canvas(&recomputed_raw);
assert_eq!(
cached, recomputed_encoded,
"rung {rung:?}: eviction->recompute path diverged from the cached canvas"
);
}
}
/// Determinism across DIFFERENT positions is not the gate's job (that's
/// what makes derive believable, not what makes cache/derive agree) — but a
/// sanity check that two DIFFERENT centres do NOT produce identical output
/// is worth pinning here too, so this suite can't be satisfied by an
/// accidentally-constant derive function.
#[test]
fn different_centers_produce_different_canvases_sanity_check() {
let seed = SeedChain::root(0xACCE97_u64).derive(SeedDomain::Body, 5);
let climate = ClimateConstants::default();
let hm = fixture_hm();
let ta = fixture_ta(&hm);
let rn = fixture_river_network(&hm);
let params = fixture_params();
// Global ignores `center` by construction (whole-body canvas), so this
// sanity check only applies to fixed rungs.
for rung in [
StepCanvasRung::Region,
StepCanvasRung::District,
StepCanvasRung::Quarter,
StepCanvasRung::Block,
StepCanvasRung::Chunk,
] {
let canvas_a = build_step_canvas(
seed,
"gate-body",
&params,
&ta,
&rn,
&[],
rung,
(0, 0),
GATE_EXTENT,
&climate,
0,
);
let canvas_b = build_step_canvas(
seed,
"gate-body",
&params,
&ta,
&rn,
&[],
rung,
(200_000, 100_000),
GATE_EXTENT,
&climate,
0,
);
assert_ne!(
canvas_a.elev_q, canvas_b.elev_q,
"rung {rung:?}: two far-apart centres produced identical elev_q — \
suspiciously constant derive (this suite should not pass on a stub)"
);
}
}