Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

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}"),
        );
    }
}