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

nordisk/facett

facett-demo/tests/systemmap_shot.rs

//! **Screenshot / robot PROOF (LAW #4) for the 🫧 fluid System Map.** Render the
//! populated `SystemMap` (glass tanks + glass pipes with fluid inside) to a real PNG
//! on the live GPU (egui_kittest's wgpu backend — the RTX 5080 on Loki) and assert, at
//! the PIXEL level:
//!
//! 1. **NON-BLANK** — the map really drew (many foreground pixels over the ground).
//! 2. **THE FLUID MOVES** — two renders at the **same fixed camera** but different
//!    fluid times differ (a later frame ≠ an earlier one). Camera spin is turned OFF
//!    and the yaw pinned, so the ONLY thing that can change the pixels is the fluid —
//!    if the gatling fluid step were removed/frozen, the two frames become identical → RED.
//! 3. **TRANSLUCENT GLASS PIPES** — the render carries a rich spread of *intermediate*
//!    luminances (the alpha ramps of the translucent pipe walls, fresnel rims and the
//!    fluid column) that a flat opaque render would not produce; plus the material is
//!    intrinsically translucent as DATA (`glass_alpha < 1`).
//!
//! Run just this: `cargo test -p facett-demo --test systemmap_shot`.
//! The golden lands at `facett-demo/tests/snapshots/system_map.png`.

use egui_kittest::Harness;

use facett::Facet;
use facett::Theme;
use facett::graph3d::{Msg as CamMsg, SystemMap, SystemMapMsg};
use facett_demo::demo_systemmap::demo_systemmap;

/// Render a `SystemMap` to an RGBA image on the wgpu backend at a fixed size.
fn shot(mut m: SystemMap) -> image::RgbaImage {
    let mut harness = Harness::builder().with_size(egui::vec2(720.0, 540.0)).build_ui(move |ui| {
        // A striking palette so the glass + luminous fluid read (also proves palette
        // consumption — the map paints from `theme(ui)`).
        facett::set_theme(ui.ctx(), Theme::nordic_aurora());
        m.ui(ui);
    });
    // A few fixed frames so the projection + fluid column settle.
    harness.run_steps(4);
    harness.render().expect("wgpu render produced an image")
}

/// Per-pixel luminance (0..255) of an RGBA image.
fn luma(px: &image::Rgba<u8>) -> u16 {
    (px[0] as u16 * 54 + px[1] as u16 * 183 + px[2] as u16 * 19) >> 8
}

/// A fixed-camera `SystemMap` pre-advanced by `steps` fluid steps (spin OFF, yaw pinned
/// — so between two such maps the camera is identical and only the fluid differs).
fn fixed_camera_map(steps: usize) -> SystemMap {
    let mut m = demo_systemmap();
    let _ = m.update(SystemMapMsg::Camera(CamMsg::SetSpin(false)));
    let _ = m.update(SystemMapMsg::Camera(CamMsg::SetYaw(0.6)));
    for _ in 0..steps {
        let _ = m.update(SystemMapMsg::StepFluid(0.03));
    }
    m
}

#[test]
fn system_map_renders_nonblank_with_moving_translucent_fluid() {
    // Two identical maps at the SAME fixed camera, different fluid times.
    let early = fixed_camera_map(8);
    let late = fixed_camera_map(56);

    let img_a = shot(early);
    let img_b = shot(late);
    assert_eq!(img_a.dimensions(), img_b.dimensions(), "same-size frames");
    let (w, h) = img_a.dimensions();
    assert!(w >= 700 && h >= 520, "rendered at the requested size ({w}x{h})");

    // The ground colour (a corner pixel well outside the map).
    let bg = *img_a.get_pixel(3, 3);
    let bg_l = luma(&bg) as i32;

    // ── 1. NON-BLANK: lots of foreground pixels differ from the ground ──────────────
    let mut foreground = 0u32;
    // ── 3a. TRANSLUCENCY (pixel): count INTERMEDIATE luminances — the alpha ramps of
    //        the translucent glass + fluid. A flat opaque fill is nearly bimodal
    //        (ground vs solid); translucent compositing fills the middle band. ──
    let mut mid_band = 0u32;
    let mut peak_l = bg_l;
    for px in img_a.pixels() {
        let l = luma(px) as i32;
        if (l - bg_l).abs() > 8 {
            foreground += 1;
        }
        peak_l = peak_l.max(l);
    }
    let hi = peak_l.max(bg_l + 20);
    for px in img_a.pixels() {
        let l = luma(px) as i32;
        // "mid" = clearly above the ground but clearly below the brightest highlight —
        // i.e. a partially-composited (translucent) pixel, not ground and not a solid peak.
        if l > bg_l + 12 && l < hi - 12 {
            mid_band += 1;
        }
    }
    assert!(foreground > 1_500, "the system map drew (foreground px={foreground}) — not blank");
    assert!(mid_band > 800, "translucent glass/fluid produce many intermediate-alpha pixels (mid-band px={mid_band})");

    // ── 2. THE FLUID MOVES: same camera, later frame differs ────────────────────────
    let mut changed = 0u32;
    for (a, b) in img_a.pixels().zip(img_b.pixels()) {
        let da = (a[0] as i32 - b[0] as i32).abs() + (a[1] as i32 - b[1] as i32).abs() + (a[2] as i32 - b[2] as i32).abs();
        if da > 16 {
            changed += 1;
        }
    }
    assert!(changed > 200, "the fluid moved between two same-camera frames (changed px={changed}) — RED if the fluid step is frozen");

    // ── 3b. TRANSLUCENCY (data): the pipe material is intrinsically translucent ──────
    let j = demo_systemmap().state_json();
    let alpha = j["glass_alpha"].as_f64().unwrap();
    assert!(alpha < 1.0, "glass pipes are translucent (glass_alpha={alpha} < 1)");
    assert!(j["fluid"]["pipes"].as_u64().unwrap() > 0, "there are fluid pipes");

    // Save the golden for visual inspection.
    let out_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/snapshots");
    std::fs::create_dir_all(&out_dir).unwrap();
    let path = out_dir.join("system_map.png");
    image::DynamicImage::ImageRgba8(img_a).save(&path).expect("save PNG");
    let bytes = std::fs::metadata(&path).unwrap().len();
    assert!(bytes > 5_000, "golden PNG must be non-trivial (got {bytes} bytes)");
    eprintln!(
        "system-map snapshot → {} ({bytes} bytes) · foreground={foreground} mid_band={mid_band} moved={changed}",
        path.display()
    );
}