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()
);
}