facett-map3d/tests/ssao_proof.rs
//! **SSAO / GTAO render PROOF (T3.1, LAW #4).**
//!
//! A normal (G-buffer) target is added to the map3d offscreen; a screen-space
//! ambient-occlusion pass reads it and the depth, and the main pass modulates the
//! ambient term by the result. Creases + wall/ground contacts come back darker.
//!
//! This renders a tight **courtyard** of blocks (an inner concave well whose floor
//! and inner walls are heavily self-occluded) through egui_kittest's wgpu backend,
//! AO ON vs OFF, at a fixed clock + sun. Shadows are turned OFF so the ONLY variable
//! is the ambient occlusion (otherwise cast shadows would confound the luma). It then
//! asserts, as DATA:
//!
//! 1. **the concave well darkens** — mean luma of the courtyard region drops with
//! AO on (the contact occlusion the SSAO pass computed); AO-off is the control;
//! 2. **AO only subtracts** — the whole geometry's mean luma is <= AO-off (AO never
//! brightens) and the well drops more than the open scene (it is the discriminator);
//! 3. **deterministic** — two independent AO-on renders are byte-identical (FC-7).
//!
//! Run: `cargo test -p facett-map3d --features wgpu --test ssao_proof -- --nocapture`
#![cfg(feature = "wgpu")]
use egui_kittest::wgpu::{create_render_state, default_wgpu_setup, WgpuTestRenderer};
use egui_kittest::Harness;
use facett_core::Facet as _;
use facett_map::Way;
use facett_map3d::{Map3D, SunSky, ViewPreset};
/// A tight ring of tall blocks around a small open centre — a courtyard whose floor
/// + inner faces are deeply ambient-occluded (the SSAO target's job).
fn courtyard() -> Vec<Way> {
let (lon0, lat0) = (9.50_f64, 47.10_f64);
let step = 0.0016; // tight spacing → a small well
let bsz = 0.0012;
let mut ways = Vec::new();
let mut id = 1i64;
for r in 0..3 {
for c in 0..3 {
if r == 1 && c == 1 {
continue; // leave the centre open → the courtyard well
}
let bx = lon0 + c as f64 * step;
let by = lat0 + r as f64 * step;
let ring = [(bx, by), (bx + bsz, by), (bx + bsz, by + bsz), (bx, by + bsz), (bx, by)];
let tags = vec![
("building".to_string(), "yes".to_string()),
("height".to_string(), "120".to_string()),
];
ways.push(Way::from_lonlat(id, &ring, &tags, &facett_core::Theme::default()).unwrap());
id += 1;
}
}
ways
}
fn scene(ssao: bool) -> Map3D {
// An AMBIENT-DOMINATED environment: a weak sun + a strong sky fill, so the
// courtyard is lit mostly by the hemisphere ambient the SSAO pass modulates
// (a strong direct sun would swamp the AO term). Shadows OFF so AO is the only
// variable between the two renders. A near-top-down camera looks into the well.
let sun = SunSky {
dir: facett_map3d::SunSky::from_angles(0.4, 0.9).dir,
sun_color: [1.0, 0.98, 0.92],
sun_intensity: 0.35,
sky_color: [0.55, 0.62, 0.80],
ambient: 0.95,
};
let mut m = Map3D::from_ways("ssao", &courtyard(), 0.4)
.with_gpu(true)
.with_sun(sun)
.with_shadows(0.0)
.with_ssao(ssao);
m.spin = false;
m.set_clock(0.0);
m.view_preset(ViewPreset::Top);
m.cam.goal_distance = 2.4;
m.cam.goal_target = facett_map3d::V3::new(0.0, 0.05, 0.0);
m.cam.settle();
m
}
fn render(ssao: bool) -> image::RgbaImage {
// THE GPU TURN (`facett_core::render::gputurn`) — held for the whole render, not just
// the bring-up. This file brings a device up from TWO `#[test]`s and took no turn:
// MEASURED 2026-08-31, it died with `signal: 11, SIGSEGV` in a whole-workspace
// `--release` run. That is not an assertion failure, it is a hard crash that fails the
// FILE for a reason that has nothing to do with SSAO. facett-map has enforced this rule
// since 2026-08-27 (`tests/one_gpu_turn_is_the_only_one.rs`); the lock lived in that
// crate's test directory, so this one could not reach it. It lives in facett-core now.
let _gpu = facett_core::render::gputurn::hold();
let rs = create_render_state(
facett_map3d::gpu::proof_wgpu_setup().unwrap_or_else(default_wgpu_setup),
Default::default(),
);
facett_map3d::gpu::install_renderer(&rs);
let mut view = scene(ssao);
let mut harness = Harness::builder()
.with_size(egui::vec2(640.0, 640.0))
.renderer(WgpuTestRenderer::from_render_state(rs))
.build_ui(move |ui| {
facett_core::set_theme(ui.ctx(), facett_core::Theme::sci_fi());
view.ui(ui);
});
harness.run_steps(4);
harness.render().expect("wgpu render")
}
fn luma(p: &image::Rgba<u8>) -> f64 {
0.2126 * p.0[0] as f64 + 0.7152 * p.0[1] as f64 + 0.0722 * p.0[2] as f64
}
/// Mean luma of GEOMETRY pixels (not the dark background) across the whole frame.
fn geom_mean(img: &image::RgbaImage) -> (f64, usize) {
let mut sum = 0.0;
let mut n = 0usize;
for p in img.pixels() {
let l = luma(p);
if l > 10.0 {
sum += l;
n += 1;
}
}
(if n > 0 { sum / n as f64 } else { 0.0 }, n)
}
/// Mean luma of geometry pixels in the **central courtyard region** (the inner well)
/// — a centred box where the floor + inner faces are most occluded.
fn well_mean(img: &image::RgbaImage) -> (f64, usize) {
let (w, h) = (img.width(), img.height());
let (x0, x1) = (w * 36 / 100, w * 64 / 100);
let (y0, y1) = (h * 40 / 100, h * 72 / 100);
let mut sum = 0.0;
let mut n = 0usize;
for y in y0..y1 {
for x in x0..x1 {
let p = img.get_pixel(x, y);
let l = luma(p);
if l > 10.0 {
sum += l;
n += 1;
}
}
}
(if n > 0 { sum / n as f64 } else { 0.0 }, n)
}
#[test]
fn ssao_darkens_the_concave_courtyard_and_is_deterministic() {
let on = render(true);
let off = render(false);
image::DynamicImage::ImageRgba8(on.clone()).save("/tmp/map3d_ssao_on.png").ok();
image::DynamicImage::ImageRgba8(off.clone()).save("/tmp/map3d_ssao_off.png").ok();
let (on_well, on_wn) = well_mean(&on);
let (off_well, off_wn) = well_mean(&off);
let (on_all, _) = geom_mean(&on);
let (off_all, _) = geom_mean(&off);
let well_drop = off_well - on_well;
let all_drop = off_all - on_all;
eprintln!(
"EMIT-SSAO well_on={on_well:.2} well_off={off_well:.2} well_drop={well_drop:.2} all_on={on_all:.2} all_off={off_all:.2} all_drop={all_drop:.2} well_n_on={on_wn} well_n_off={off_wn}"
);
// The courtyard region is real geometry in both frames.
assert!(on_wn > 2_000 && off_wn > 2_000, "the courtyard well is visible in both frames");
// (1) AO darkens the concave well (the contact/crease occlusion subtracts ambient).
assert!(
well_drop > 1.5,
"SSAO ON darkens the concave courtyard ({on_well:.2} < {off_well:.2}, drop {well_drop:.2})"
);
// (2) AO only subtracts (never brightens) the whole geometry, and the WELL drops
// more than the frame-wide average — the occlusion concentrates in the concavity.
assert!(all_drop >= -0.05, "SSAO never brightens the geometry on average ({all_drop:.2})");
assert!(
well_drop > all_drop + 0.5,
"the AO darkening concentrates in the concave well ({well_drop:.2} > {all_drop:.2})"
);
// (3) Determinism (FC-7): an independent AO-on re-render is byte-identical.
let on2 = render(true);
assert_eq!(on.as_raw(), on2.as_raw(), "the SSAO render is deterministic (two renders byte-identical)");
#[cfg(feature = "testmatrix")]
{
let ok = well_drop > 1.5 && all_drop >= -0.05 && well_drop > all_drop + 0.5 && on.as_raw() == on2.as_raw();
facett_core::testmatrix::emit(
"facett-map3d",
"ssao_darkens_concave_courtyard",
ok,
&format!("blob=/tmp/map3d_ssao_on.png well_drop={well_drop:.2} all_drop={all_drop:.2} well_on={on_well:.2} well_off={off_well:.2}"),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// GFX_V2 item 7 — THE MISSING CONTROL: does SSAO leave a FLAT PLANE alone?
//
// The test above proves AO darkens a concave well and that the darkening
// CONCENTRATES there. What it does not prove is the other half of the definition:
// that an unoccluded flat surface is left alone. Its frame-wide `all_drop` was 1.16
// luma — small, but not zero, and "small on a frame that is mostly walls and
// creases" cannot distinguish "AO is correctly local" from "AO darkens everything a
// little". A pass returning a constant 0.9 occlusion would satisfy every assertion
// up there.
//
// So this renders ONE frame containing both: a wide flat-roofed slab whose roof
// centre sits many AO radii from any edge, and the same concave courtyard beside it.
// AO must darken the well and NOT the roof, in the same frame, under the same light.
// ─────────────────────────────────────────────────────────────────────────────
/// A tight courtyard cluster (the concave well) beside a WIDE flat-roofed slab (the
/// flat plane). Same block height and tags, so the two regions differ only in shape.
///
/// The courtyard ring is the same construction as [`courtyard`], offset — it is the
/// shape whose concavity is already proven, reused rather than re-invented.
fn slab_and_courtyard() -> Vec<Way> {
let (lon0, lat0) = (9.50_f64, 47.10_f64);
let step = 0.0016;
let bsz = 0.0012;
let theme = facett_core::Theme::default();
let tags = vec![
("building".to_string(), "yes".to_string()),
("height".to_string(), "120".to_string()),
];
let mut ways = Vec::new();
let mut id = 1i64;
// ── RIGHT: the concave courtyard (3x3 minus the centre). ──
for r in 0..3 {
for c in 0..3 {
if r == 1 && c == 1 {
continue;
}
let bx = lon0 + c as f64 * step;
let by = lat0 + r as f64 * step;
let ring = [(bx, by), (bx + bsz, by), (bx + bsz, by + bsz), (bx, by + bsz), (bx, by)];
ways.push(Way::from_lonlat(id, &ring, &tags, &theme).unwrap());
id += 1;
}
}
// ── LEFT: one wide slab. Its footprint spans the same extent as the whole 3x3
// cluster, so its roof CENTRE is ~1.5 block-widths from the nearest edge in every
// direction — far enough that no edge can occlude it at the SSAO radius.
let span = 2.0 * step + bsz;
let sx = lon0 - span - 2.0 * step; // a clear gap between the two structures
let ring = [
(sx, lat0),
(sx + span, lat0),
(sx + span, lat0 + span),
(sx, lat0 + span),
(sx, lat0),
];
ways.push(Way::from_lonlat(id, &ring, &tags, &theme).unwrap());
ways
}
fn slab_scene(ssao: bool) -> Map3D {
// Identical lighting to `scene()`: ambient-dominated, shadows OFF, so AO is the
// only variable between the two renders.
let sun = SunSky {
dir: facett_map3d::SunSky::from_angles(0.4, 0.9).dir,
sun_color: [1.0, 0.98, 0.92],
sun_intensity: 0.35,
sky_color: [0.55, 0.62, 0.80],
ambient: 0.95,
};
let mut m = Map3D::from_ways("ssao_flat", &slab_and_courtyard(), 0.4)
.with_gpu(true)
.with_sun(sun)
.with_shadows(0.0)
.with_ssao(ssao);
m.spin = false;
m.set_clock(0.0);
m.view_preset(ViewPreset::Top);
m.cam.goal_distance = 3.4;
m.cam.goal_target = facett_map3d::V3::new(0.0, 0.05, 0.0);
m.cam.settle();
m
}
fn render_slab(ssao: bool) -> image::RgbaImage {
// THE GPU TURN (`facett_core::render::gputurn`) — held for the whole render, not just
// the bring-up. This file brings a device up from TWO `#[test]`s and took no turn:
// MEASURED 2026-08-31, it died with `signal: 11, SIGSEGV` in a whole-workspace
// `--release` run. That is not an assertion failure, it is a hard crash that fails the
// FILE for a reason that has nothing to do with SSAO. facett-map has enforced this rule
// since 2026-08-27 (`tests/one_gpu_turn_is_the_only_one.rs`); the lock lived in that
// crate's test directory, so this one could not reach it. It lives in facett-core now.
let _gpu = facett_core::render::gputurn::hold();
let rs = create_render_state(
facett_map3d::gpu::proof_wgpu_setup().unwrap_or_else(default_wgpu_setup),
Default::default(),
);
{
// ATTRIBUTION. `egui_kittest`'s `default_wgpu_setup()` does its OWN adapter
// selection — it is NOT facett's `request_best_adapter` — so which device
// answered has to be read, not assumed. Printed because it changes what the
// measured luma numbers are evidence about.
let i = rs.adapter.get_info();
eprintln!("[ssao_proof] device: {} ({:?}, {:?})", i.name, i.backend, i.device_type);
}
facett_map3d::gpu::install_renderer(&rs);
let mut view = slab_scene(ssao);
let mut harness = Harness::builder()
.with_size(egui::vec2(640.0, 640.0))
.renderer(WgpuTestRenderer::from_render_state(rs))
.build_ui(move |ui| {
facett_core::set_theme(ui.ctx(), facett_core::Theme::sci_fi());
view.ui(ui);
});
harness.run_steps(4);
harness.render().expect("wgpu render")
}
/// Mean luma of geometry pixels in a fractional box of the frame, with the count.
fn box_mean(img: &image::RgbaImage, fx0: f64, fy0: f64, fx1: f64, fy1: f64) -> (f64, usize) {
let (w, h) = (img.width() as f64, img.height() as f64);
let (x0, x1) = ((fx0 * w) as u32, (fx1 * w) as u32);
let (y0, y1) = ((fy0 * h) as u32, (fy1 * h) as u32);
let mut sum = 0.0;
let mut n = 0usize;
for y in y0..y1.min(img.height()) {
for x in x0..x1.min(img.width()) {
let l = luma(img.get_pixel(x, y));
if l > 10.0 {
sum += l;
n += 1;
}
}
}
(if n > 0 { sum / n as f64 } else { 0.0 }, n)
}
#[test]
fn ssao_darkens_the_concave_well_but_leaves_the_flat_roof_alone() {
let on = render_slab(true);
let off = render_slab(false);
image::DynamicImage::ImageRgba8(on.clone()).save("/tmp/map3d_ssao_flat_on.png").ok();
image::DynamicImage::ImageRgba8(off.clone()).save("/tmp/map3d_ssao_flat_off.png").ok();
// A 2-D survey, printed, so the region choice is evidence rather than a guess.
// Measured on an RTX 4090: the AO drop is ~6.5 in the courtyard well, ~0.3 on the
// slab roof and ~0.2 on the open ground.
for j in 0..8 {
let fy0 = 0.12 + j as f64 * 0.095;
let mut row = String::new();
for i in 0..10 {
let fx0 = i as f64 / 10.0;
let (a, _) = box_mean(&on, fx0, fy0, fx0 + 0.1, fy0 + 0.095);
let (b, n) = box_mean(&off, fx0, fy0, fx0 + 0.1, fy0 + 0.095);
let d = if n > 200 { b - a } else { f64::NAN };
row.push_str(&format!("{d:7.2}"));
}
eprintln!("EMIT-SSAO-GRID y={fy0:.3} drops:{row}");
}
// ── The three regions. ──
// CONCAVE: the courtyard well — floor and inner faces, deeply self-occluded.
let (well_on, well_n) = box_mean(&on, 0.60, 0.42, 0.70, 0.58);
let (well_off, _) = box_mean(&off, 0.60, 0.42, 0.70, 0.58);
// FLAT #1: the centre of the wide slab roof. A plane, ~1.5 block-widths from its
// nearest edge in every direction, so nothing can occlude it at the AO radius.
let (roof_on, roof_n) = box_mean(&on, 0.30, 0.42, 0.40, 0.58);
let (roof_off, _) = box_mean(&off, 0.30, 0.42, 0.40, 0.58);
// FLAT #2: open ground, well clear of both structures. A second, independent flat
// plane — at a different height and orientation in the depth buffer, so a bug that
// happened to spare one has to spare the other too.
let (grd_on, grd_n) = box_mean(&on, 0.25, 0.19, 0.75, 0.25);
let (grd_off, _) = box_mean(&off, 0.25, 0.19, 0.75, 0.25);
let well_drop = well_off - well_on;
let roof_drop = roof_off - roof_on;
let grd_drop = grd_off - grd_on;
eprintln!(
"EMIT-SSAO-FLAT well {well_off:.2}->{well_on:.2} drop={well_drop:.2} n={well_n} | \
roof {roof_off:.2}->{roof_on:.2} drop={roof_drop:.2} n={roof_n} | \
ground {grd_off:.2}->{grd_on:.2} drop={grd_drop:.2} n={grd_n} | \
well/roof={:.1}x well/ground={:.1}x",
well_drop / roof_drop.abs().max(0.01),
well_drop / grd_drop.abs().max(0.01)
);
// ── NON-VACUITY: all three regions are real lit surfaces in both frames. ──
// Without this, a region that fell off the geometry would have drop 0 and would
// "prove" the flat plane is untouched by containing nothing at all.
assert!(well_n > 5_000, "the courtyard well is real geometry ({well_n} px)");
assert!(roof_n > 5_000, "the slab roof is real geometry ({roof_n} px)");
assert!(grd_n > 5_000, "the open ground is real geometry ({grd_n} px)");
for (name, l) in [("well", well_off), ("roof", roof_off), ("ground", grd_off)] {
assert!(l > 40.0, "the {name} region is a LIT surface, not near-black ({l:.2})");
}
// ── (1) AO DARKENS THE CONCAVITY — AGAINST THIS FRAME'S OWN FLAT CONTROLS. ──
//
// This clause used to read `well_drop > 4.0`, an absolute luma calibrated when the
// SSAO sampling radius was the flat `0.16` design units this crate shipped until
// 2026-08-04. `b7b75fc5` re-derived it in METRES — `SSAO_RADIUS_METRES = 12.0`
// through the scene's own `du_per_m`, because 0.16 du on a metric scene is a **300 m
// contact-shadow radius**, three football pitches of "contact". Ambient occlusion
// integrates over that radius, so shrinking it 25x necessarily shrinks the luma drop:
// measured 2026-08-22, `well_drop` 1.27 on both lavapipe and an RTX 4090. The pass is
// not weaker than it should be; the radius is right and the constant was calibrated
// against the wrong one.
//
// So the discriminator is the RATIO the two flat controls below already establish,
// which is what "AO is a CONCAVITY term" actually claims and is independent of the
// radius, the exposure and the tonemap: the well must darken by a real amount AND by
// an order of magnitude more than a plane does. Measured: well 1.27 against roof 0.08
// and ground 0.00 — 16x and 127x.
let flat_noise = roof_drop.abs().max(grd_drop.abs());
assert!(
well_drop > 0.5,
"SSAO must darken the concave courtyard well ({well_drop:.2} luma) — a pass that darkens \
NOTHING fails here"
);
assert!(
well_drop > 8.0 * flat_noise.max(0.02),
"…and it must darken the CONCAVITY, not the frame: well {well_drop:.2} against a flat \
control of {flat_noise:.2} luma is only {:.1}x, and AO that darkens a plane as much as \
a well is a constant, not an occlusion term",
well_drop / flat_noise.max(0.02)
);
// ── (2) AO LEAVES A FLAT PLANE ALONE. THIS IS THE CONTROL THAT WAS MISSING. ──
// A pass returning a constant occlusion — the most plausible way for this to be
// quietly broken, and one that satisfies every assertion in the courtyard test
// above — darkens these two regions just as much as the well.
assert!(
roof_drop < 1.0,
"SSAO must NOT darken the flat slab roof ({roof_drop:.2} luma) — a pass that darkens \
EVERYTHING fails here"
);
assert!(
grd_drop < 1.0,
"…nor the open flat ground ({grd_drop:.2} luma)"
);
// ── (3) AND IT ONLY SUBTRACTS. ──
for (name, d) in [("well", well_drop), ("roof", roof_drop), ("ground", grd_drop)] {
assert!(d >= -0.05, "AO never BRIGHTENS the {name} region ({d:.2})");
}
// ── (4) THE RATIO — the assertion that survives a change of scene scale. ──
// Absolute luma thresholds move with exposure and albedo; the ratio between a
// concavity and a plane in the SAME frame does not.
assert!(
well_drop > roof_drop.abs() * 5.0,
"the concave well must darken at least 5x more than the flat roof \
({well_drop:.2} vs {roof_drop:.2})"
);
assert!(
well_drop > grd_drop.abs() * 5.0,
"…and at least 5x more than the flat ground ({well_drop:.2} vs {grd_drop:.2})"
);
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-map3d",
"ssao_spares_the_flat_plane",
well_drop > 4.0 && roof_drop < 1.0 && grd_drop < 1.0,
&format!(
"blob=/tmp/map3d_ssao_flat_on.png well_drop={well_drop:.2} roof_drop={roof_drop:.2} ground_drop={grd_drop:.2}"
),
);
}