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_liquid_shot.rs

//! **A picture of the REAL LIQUID system map** (the opt-in `SystemMap::with_liquid`),
//! rendered on a live GPU through egui_kittest's wgpu backend.
//!
//! This is deliberately **not** the containment proof — that lives in
//! `facett-graph3d/src/pipes.rs` and tumbles the map through 360 orientations at 97 %
//! charge, checking every painted liquid vertex against its own tube. A single
//! screenshot confirms one camera at one instant and would never visit the orientation
//! where a containment bug shows. See `facett/.nornir/systemmap-fluid-reasoning.md` f7.
//!
//! What it IS: something to look at, plus the two pixel-level facts a picture *can*
//! honestly carry —
//!
//! 1. **the map drew** (many foreground pixels over the ground), and
//! 2. **tipping it pours the liquid** — two renders at the **same fixed camera and the
//!    same fluid time**, differing only in `tilt`, differ in pixels. If `Tilt` stopped
//!    reaching the solver, or gravity stopped being re-aimed from the camera, the two
//!    frames become identical → RED.
//!
//! Run: `cargo test -p facett-demo --test systemmap_liquid_shot`.
//! The picture lands at `facett-demo/tests/snapshots/system_map_liquid.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_liquid;

/// 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| {
        facett::set_theme(ui.ctx(), Theme::nordic_aurora());
        m.ui(ui);
    });
    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
}

/// The liquid map at a **pinned camera** (spin off, yaw fixed), tipped by `tilt` and
/// settled for a fixed number of host-supplied steps. Two of these differ **only** in
/// the tilt — no clock is read on this path, so they are otherwise identical.
fn tipped(tilt: f32, steps: usize) -> SystemMap {
    let mut m = demo_systemmap_liquid();
    let _ = m.update(SystemMapMsg::Camera(CamMsg::SetSpin(false)));
    let _ = m.update(SystemMapMsg::Camera(CamMsg::SetYaw(0.6)));
    let _ = m.update(SystemMapMsg::SetTilt(tilt));
    for _ in 0..steps {
        m.tick_fluid(1.0 / 60.0);
    }
    m
}

#[test]
fn liquid_system_map_renders_and_tipping_it_pours() {
    // Same camera, same number of steps, same seed — only the tilt differs.
    let level = tipped(0.0, 240);
    let over = tipped(1.6, 240);

    // The invariants still hold in the exact states being photographed.
    for (name, m) in [("level", &level), ("tipped", &over)] {
        let j = m.state_json();
        let l = &j["liquid"];
        assert!(l["max_fill"].as_f64().unwrap() <= 1.0, "{name}: nothing left the pipes");
        assert!(l["total_volume"].as_f64().unwrap() > 1.0, "{name}: there is liquid");
    }

    let img_a = shot(level);
    let img_b = shot(over);
    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})");

    // ── 1. NON-BLANK ────────────────────────────────────────────────────────────────
    let bg_l = luma(img_a.get_pixel(3, 3)) as i32;
    let foreground = img_a.pixels().filter(|px| (luma(px) as i32 - bg_l).abs() > 8).count();
    assert!(foreground > 1_500, "the liquid map drew (foreground px={foreground}) — not blank");

    // ── 2. TIPPING IT POURS ─────────────────────────────────────────────────────────
    let changed = img_a
        .pixels()
        .zip(img_b.pixels())
        .filter(|(a, b)| {
            (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() > 16
        })
        .count();
    assert!(
        changed > 200,
        "tipping the map moved the liquid (changed px={changed}) — RED if Tilt stops reaching the solver"
    );

    // Save the tipped frame — it is the one that shows liquid lying in the troughs.
    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_liquid.png");
    image::DynamicImage::ImageRgba8(img_b).save(&path).expect("save PNG");
    let bytes = std::fs::metadata(&path).unwrap().len();
    assert!(bytes > 5_000, "the picture must be non-trivial (got {bytes} bytes)");
    eprintln!(
        "liquid system-map picture → {} ({bytes} bytes) · foreground={foreground} poured={changed}",
        path.display()
    );
}