facett-graph3d/tests/rabbit3d_snapshot.rs
//! **Screenshot PROOF (LAW #4)** — the **3D znippy rabbit logo**.
//!
//! The extruded rabbit ([`facett_core::rabbit::rabbit_mesh`]) is rendered by
//! [`RabbitLogo3D`] through egui_kittest's wgpu backend at a **fixed clock** (so
//! the spin is deterministic), and the framebuffer is asserted to be:
//! 1. **non-blank** — a real lit solid drew (not a black pane), and
//! 2. **lit with depth** — many distinct shades (the `normal·light` shading +
//! depth-sorted faces produce a gradient, not one flat colour),
//!
//! then the golden PNG is committed so the 3D mascot is visibly a lit logo.
//!
//! This drives the **CPU painter** path (the one the glow demo + most hosts use);
//! egui's wgpu backend rasterises its tessellated, depth-sorted, shaded triangles
//! on a real GPU, so the proof is a genuine GPU render of the lit 3D logo. The
//! optional `wgpu`-feature GPU pipeline shares the same mesh + shading model.
//!
//! Run: `cargo test -p facett-graph3d --test rabbit3d_snapshot`
//! Golden: `facett-graph3d/tests/snapshots/rabbit_3d.png`.
use egui_kittest::Harness;
use facett_core::Facet;
use facett_graph3d::RabbitLogo3D;
/// `(content_ratio, distinct_shades)` of the image. `content_ratio` = fraction of
/// pixels far from the modal (≈background) colour; `distinct_shades` = number of
/// non-background colour buckets present (a flat-coloured blob → tiny; a lit,
/// depth-shaded solid → many).
fn measure(img: &image::RgbaImage) -> (f64, usize) {
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 total = (img.width() * img.height()) as usize;
let mut content = 0usize;
let mut shades = std::collections::HashSet::<u32>::new();
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;
shades.insert(((r as u32) << 10) | ((g as u32) << 5) | b as u32);
}
}
(content as f64 / total as f64, shades.len())
}
#[test]
fn rabbit_3d_renders_lit_with_depth_and_saves_golden() {
// Inject-assert the mesh first: a real extruded solid, deterministically.
let logo = RabbitLogo3D::new("rabbit3d").with_clock(1.2);
let s = logo.state_json();
assert!(s["triangles"].as_u64().unwrap() > 50, "real extruded mesh");
assert!(s["vertices"].as_u64().unwrap() > 100);
let mut view = RabbitLogo3D::new("rabbit3d").with_clock(1.2); // fixed spin → stable
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 — 3D extruded logo (lit, auto-rotate)");
view.ui(ui);
});
harness.run_steps(3);
let img_out = harness.render().expect("wgpu render produced an image");
let (ratio, shades) = measure(&img_out);
eprintln!("[rabbit-3d] content ratio {ratio:.4}, distinct shades {shades}");
// Non-blank: the lit solid fills a visible region.
assert!(ratio > 0.05, "the 3D logo must fill a visible region (got {ratio:.4})");
// Lit + depth: many shades, not a single flat fill (proves the lighting +
// depth-sorted faces actually shaded the solid).
assert!(shades >= 8, "the lit, depth-shaded logo must show many shades (got {shades})");
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_3d.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-graph3d rabbit-3d snapshot → {} ({bytes} bytes)", path.display());
// Feed the matrix: ok iff the lit 3D logo rendered non-blank with depth shades.
#[cfg(feature = "testmatrix")]
{
let tris = s["triangles"].as_u64().unwrap_or(0) as usize;
facett_core::testmatrix::emit(
"RabbitLogo3D",
"renders_lit_with_depth",
ratio > 0.05 && shades >= 8 && tris > 50,
&format!("content_ratio={ratio:.4} shades={shades} triangles={tris}"),
);
}
}