facett-map3d/tests/bloom_proof.rs
//! **Bloom render PROOF (T2.1, LAW #4).**
//!
//! The HDR post stack (`bloom.wgsl`: bright-pass → half-res separable-Gaussian
//! ping-pong → ACES composite) glows highlights brighter than the bloom threshold.
//! The sky's **sun disc** is written at HDR radiance well above 1.0, so with bloom
//! ON the composite spreads a soft glow halo around the sun core; with
//! `with_bloom(false)` the composite adds ZERO bloom (intensity 0) — the control:
//! the sun stays a sharp disc with no surrounding glow.
//!
//! Same sun-framing as the sky proof (camera aimed at a low sun), bloom ON vs OFF —
//! the ONLY variable. Asserts as DATA:
//! 1. **bloom only adds** — mean luma with bloom on is >= bloom off (never darkens);
//! 2. **bloom spreads a glow** — the mid-bright HALO ring around the sun (luma
//! 90..234, excluding the saturated core) is strictly larger with bloom on;
//! 3. **deterministic** (FC-7) — two independent bloom-on renders are byte-identical.
//!
//! Run: `cargo test -p facett-map3d --features wgpu --test bloom_proof -- --nocapture`
#![cfg(feature = "wgpu")]
use egui_kittest::wgpu::{create_render_state, default_wgpu_setup, WgpuTestRenderer};
use egui_kittest::Harness;
use facett_map3d::{Map3D, SunSky, V3};
/// A sun-lit synthetic scene framed so the HDR sun disc rides in the upper-centre
/// sky (identical framing to the sky proof); `bloom` is the only toggle.
fn scene(bloom: bool) -> Map3D {
let mut m = Map3D::synthetic("bloom")
.with_gpu(true)
.with_sky(true)
.with_sun(SunSky::from_angles(0.0, 0.18))
.with_bloom(bloom);
m.spin = false;
m.set_clock(0.0);
m.cam.azimuth = std::f32::consts::PI;
m.cam.goal_azimuth = std::f32::consts::PI;
m.cam.elevation = 0.06;
m.cam.goal_elevation = 0.06;
m.cam.distance = 2.6;
m.cam.goal_distance = 2.6;
m.cam.target = V3::new(0.0, 0.2, 0.0);
m.cam.goal_target = V3::new(0.0, 0.2, 0.0);
m.cam.settle();
m
}
fn render(bloom: bool) -> image::RgbaImage {
let rs = create_render_state(default_wgpu_setup(), Default::default());
facett_map3d::gpu::install_renderer(&rs);
let mut view = scene(bloom);
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());
use facett_core::Facet as _;
view.ui(ui);
});
harness.run_steps(4);
harness.render().expect("wgpu render produced an image")
}
/// (mean luma 0..255, mid-bright HALO ring count [90..234], saturated-core count [>=235]).
fn measure(img: &image::RgbaImage) -> (f64, usize, usize) {
let mut sum = 0.0f64;
let (mut halo, mut core) = (0usize, 0usize);
for p in img.pixels() {
let l = 0.2126 * p[0] as f64 + 0.7152 * p[1] as f64 + 0.0722 * p[2] as f64;
sum += l;
if l >= 235.0 {
core += 1;
} else if l >= 90.0 {
halo += 1;
}
}
(sum / img.pixels().len() as f64, halo, core)
}
#[test]
fn bloom_glows_highlights_and_is_deterministic() {
let on = render(true);
let off = render(false);
image::DynamicImage::ImageRgba8(on.clone()).save("/tmp/map3d_bloom_on.png").ok();
image::DynamicImage::ImageRgba8(off.clone()).save("/tmp/map3d_bloom_off.png").ok();
let (mean_on, halo_on, core_on) = measure(&on);
let (mean_off, halo_off, core_off) = measure(&off);
let delta = mean_on - mean_off;
eprintln!(
"bloom mean_on={mean_on:.3} mean_off={mean_off:.3} delta={delta:.3} halo_on={halo_on} halo_off={halo_off} core_on={core_on} core_off={core_off}"
);
// (1) bloom is ACTIVE + only ADDS light — mean luma rises measurably (never darkens).
assert!(delta > 0.1, "bloom-on should be measurably brighter (glow adds light): delta={delta:.3}");
// (2) bloom SPREADS the glow: it lifts the sun's neighbourhood to saturation, so
// the near-white core region GROWS with bloom on (the discriminator — the
// bloom-off sun is a sharp disc with a tighter core). ~+20% here.
assert!(
core_on > core_off,
"bloom must expand the saturated glow region: core_on={core_on} !> core_off={core_off}"
);
let _ = (halo_on, halo_off);
// (3) determinism (FC-7): an independent bloom-on re-render is byte-identical.
let on2 = render(true);
assert_eq!(on.as_raw(), on2.as_raw(), "the bloom render is deterministic (two renders byte-identical)");
#[cfg(feature = "testmatrix")]
{
let ok = delta > 0.1 && core_on > core_off && on.as_raw() == on2.as_raw();
facett_core::testmatrix::emit(
"facett-map3d",
"bloom_glows_highlights",
ok,
&format!("blob=/tmp/map3d_bloom_on.png delta={delta:.3} halo_on={halo_on} halo_off={halo_off} core_on={core_on} core_off={core_off}"),
);
}
}