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

nordisk/facett

facett-map3d/tests/render_proof.rs

//! **Screenshot PROOF (LAW #4)** — the extruded **3D map** (the znippy-rabbit
//! synthetic skyline).
//!
//! [`Map3D::synthetic`] is rendered through egui_kittest's wgpu backend at a
//! **fixed clock** (deterministic orbit), 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 map is visibly a lit model.
//!
//! Run: `cargo test -p facett-map3d --test render_proof`
//! Golden: `facett-map3d/tests/snapshots/map3d.png`.

use egui_kittest::Harness;
use facett_core::Facet;
use facett_map3d::Map3D;

/// `(content_ratio, distinct_shades)` — fraction of pixels far from the modal
/// (≈background) colour, and how many distinct non-background shade buckets show.
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 map3d_renders_lit_with_depth_and_saves_golden() {
    // Inject-assert the mesh first: a real extruded skyline, deterministically.
    let probe = Map3D::synthetic("map3d").with_clock(1.2);
    let s = probe.state_json();
    assert!(s["triangles"].as_u64().unwrap() > 50, "real extruded mesh");
    assert!(s["tiles"].as_u64().unwrap() >= 5, "rabbit prisms + terrain");

    let mut view = Map3D::synthetic("map3d").with_clock(1.2); // fixed orbit → 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 map (lit, orbiting)");
        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!("[map3d] content ratio {ratio:.4}, distinct shades {shades}");
    assert!(ratio > 0.05, "the 3D map must fill a visible region (got {ratio:.4})");
    assert!(shades >= 8, "the lit, depth-shaded map 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("map3d.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-map3d map3d snapshot → {} ({bytes} bytes)", path.display());

    // Feed the matrix: ok iff the lit 3D map rendered non-blank with depth shades.
    #[cfg(feature = "testmatrix")]
    {
        let tris = s["triangles"].as_u64().unwrap_or(0) as usize;
        let tiles = s["tiles"].as_u64().unwrap_or(0) as usize;
        facett_core::testmatrix::emit(
            "facett-map3d",
            "renders_lit_with_depth",
            ratio > 0.05 && shades >= 8 && tris > 50 && tiles > 0,
            &format!("content_ratio={ratio:.4} shades={shades} triangles={tris} tiles={tiles}"),
        );
    }
}