facett-map/tests/rabbit_snapshot.rs
//! **Screenshot PROOF (LAW #4)** — the **2D znippy rabbit** synthetic basemap.
//!
//! facett-map's synthetic/test basemap is the znippy rabbit (filled silhouette +
//! crisp outline), not a square grid. This renders the rabbit `MapView` through
//! egui_kittest's wgpu backend, asserts the framebuffer is **non-blank** (a real
//! drawn silhouette, not a black pane), and commits the golden PNG so the mascot
//! is visibly recognizable.
//!
//! Run: `cargo test -p facett-map --test rabbit_snapshot`
//! Golden: `facett-map/tests/snapshots/rabbit_2d.png`.
use egui_kittest::Harness;
use facett_core::Facet;
use facett_core::rabbit::Rabbit;
use facett_map::{MapView, Way};
/// **The shared one-device-at-a-time lock** — `common/one_gpu.rs`. `egui_kittest::Harness`
/// brings up its own wgpu device, libtest runs `#[test]` bodies on parallel threads, and
/// two device bring-ups at once take this binary out with a hard `signal: 11` — not a test
/// failure, a SIGSEGV whose output looks nothing like the assertion that was running.
/// MEASURED 2026-08-27 on oden by taking the lock back OUT of the two files that had it:
/// `rail_tier_pixels` 2/5 runs and `place_label_tier_pixels` 3/5 runs died that way. This
/// file rendered 0/8 without it, which is a LOWER rate and not a safe one — the hazard is
/// the concurrent bring-up, and every file that does one takes the same turn.
#[path = "common/one_gpu.rs"]
mod one_gpu;
/// Build the rabbit as filled `Way`s placed in lon/lat around a centre — the same
/// shape `facett-demo`'s `data::rabbit_ways` produces, kept self-contained here so
/// facett-map's proof needs no demo dependency.
fn rabbit_map() -> MapView {
let (clon, clat) = (13.0_f64, 58.0_f64);
let (sx, sy) = (5.0_f64, 4.6_f64);
let to_lonlat = |(x, y): (f64, f64)| (clon + x * sx, clat + y * sy);
let fur = egui::Color32::from_rgb(196, 201, 214);
let outline = egui::Color32::from_rgb(28, 32, 44);
let eye = egui::Color32::from_rgb(20, 22, 30);
let r = Rabbit::default();
let kinds = ["body", "head", "ear", "ear"];
let mut ways: Vec<Way> = Vec::new();
for (i, lp) in r.silhouette().into_iter().enumerate() {
let lonlat: Vec<(f64, f64)> = lp.into_iter().map(to_lonlat).collect();
ways.push(Way::filled(i as i64, kinds.get(i).copied().unwrap_or("body"), lonlat, fur, outline, 1.8));
}
let eye_loop: Vec<(f64, f64)> = r.eye().into_iter().map(to_lonlat).collect();
ways.push(Way::filled(900, "eye", eye_loop, eye, eye, 0.8));
// A few "vessel" points so the point layer is exercised on top of the rabbit.
let points = vec![(clon, clat), (clon + 1.0, clat + 1.0), (clon - 1.0, clat - 0.5)];
MapView::new("znippy rabbit (synthetic basemap)", points).with_ways(ways)
}
/// Fraction of pixels that differ from the modal (≈background) colour — a
/// generalizable "did real content draw?" measure. A blank pane scores ~0.
fn content_ratio(img: &image::RgbaImage) -> f64 {
let mut hist = std::collections::HashMap::<u32, u32>::new();
for p in img.pixels() {
let key = ((p.0[0] as u32 >> 3) << 10) | ((p.0[1] as u32 >> 3) << 5) | (p.0[2] as u32 >> 3);
*hist.entry(key).or_default() += 1;
}
let (modal, _) = hist.iter().max_by_key(|(_, c)| **c).map(|(k, c)| (*k, *c)).unwrap();
let (mr, mg, mb) = (((modal >> 10) & 0x1f) as i32, ((modal >> 5) & 0x1f) as i32, (modal & 0x1f) as i32);
let mut content = 0usize;
let total = (img.width() * img.height()) as usize;
for p in img.pixels() {
let (r, g, b) = ((p.0[0] >> 3) as i32, (p.0[1] >> 3) as i32, (p.0[2] >> 3) as i32);
if (r - mr).abs() + (g - mg).abs() + (b - mb).abs() > 3 {
content += 1;
}
}
content as f64 / total as f64
}
#[test]
fn rabbit_2d_renders_non_blank_and_saves_golden() {
// Inject-assert the geometry first: the synthetic basemap is the rabbit
// (body + head + 2 ears + eye = 5 filled loops), deterministically.
let map = rabbit_map();
let s = map.state_json();
assert_eq!(s["ways"], 5, "body + head + 2 ears + eye");
assert_eq!(s["filled_ways"], 5, "every rabbit loop is a filled silhouette");
assert_eq!(s["way_kinds"]["ear"], 2, "two upright ears");
let mut view = rabbit_map();
let _gpu = one_gpu::hold();
let mut harness = Harness::builder().with_size(egui::vec2(720.0, 720.0)).build_ui(move |ui| {
facett_core::set_theme(ui.ctx(), facett_core::Theme::sci_fi());
ui.heading("znippy rabbit — synthetic map basemap (2D)");
view.ui(ui);
});
harness.run_steps(3);
let img_out = harness.render().expect("wgpu render produced an image");
let ratio = content_ratio(&img_out);
eprintln!("[rabbit-2d] content ratio {ratio:.4}");
// The filled silhouette covers a healthy chunk of the pane — well above blank.
assert!(ratio > 0.05, "the 2D rabbit silhouette must fill a visible region (got {ratio:.4})");
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("rabbit_2d.png");
image::DynamicImage::ImageRgba8(img_out).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!("facett-map rabbit-2d snapshot → {} ({bytes} bytes)", path.display());
// Feed the matrix: ok iff the rabbit basemap (5 filled ways) rendered non-blank.
#[cfg(feature = "testmatrix")]
{
let ways = s["ways"].as_u64().unwrap_or(0) as usize;
facett_core::testmatrix::emit_non_blank("MapView.rabbit_basemap", ratio, 0.05, ways);
}
}