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/zfight.rs

//! **REGRESSION (render-based) — the 3D ground FLICKERS GREEN (z-fighting).**
//!
//! The terrain plane sits at `y = 0`. Flat green ground cover (landuse / park /
//! water) was extruded with its cap ALSO at `y = 0` — exactly coplanar. The GPU
//! depth test (`CompareFunction::Less`) then cannot decide which fragment wins and
//! flip-flops per pixel frame to frame: the green flicker. The fix lifts every flat
//! cover a deterministic hair above the terrain ([`facett_map3d::GROUND_LIFT`]).
//!
//! This proves it through the REAL GPU depth path (egui_kittest wgpu), reading the
//! rendered pixels at TWO cameras, because the defect does not look the same twice:
//!   * the FIXED (lifted) scene → the ground is CLEAN park-green at both cameras
//!     (the cover wins the depth test consistently — no terrain bleed, no flicker);
//!   * a manually-built COPLANAR control (green cap + terrain BOTH at y=0) seen
//!     almost straight down → the cover is never delivered clean, but HOW it breaks
//!     is the rasteriser's choice: llvmpipe ERASES it (0 green px of 160 000, flicker
//!     0.0000), the 4090 SPECKLES it (141 182 px, flicker 0.3385); and
//!   * the same control seen OBLIQUELY → "speckle" on both: the ground is a noisy MIX
//!     of green and dark terrain, flicker ratio 0.3834 / 0.3685 vs 0.0000 lifted.
//!
//! The discriminating EMITs are numbers (cover-kept fraction, speckle ratio), not
//! human pixel judgements — and each is asserted where it means the same thing on
//! every device. Run it on the real card with `FACETT_TEST_GPU=discrete`; the adapter
//! is printed either way, because these numbers are only evidence about one device.
//!
//! PNGs are written to /tmp so the agent can SEE both ("test the UI myself").
//!
//! Run: `cargo test -p facett-map3d --features wgpu --test zfight -- --nocapture`

#![cfg(feature = "wgpu")]

use egui_kittest::Harness;
use egui_kittest::wgpu::{WgpuTestRenderer, create_render_state, default_wgpu_setup};
use facett_core::Facet as _;
use facett_map::Way;
use facett_map3d::extrude::{Mesh3, build};
use facett_map3d::{GROUND_LIFT, Map3D, ViewPreset};

/// A large green PARK ring (filled area → flat ground cover) covering the scene.
fn green_park() -> Way {
    Way::filled(
        1,
        "park",
        vec![
            (9.495, 47.095),
            (9.520, 47.095),
            (9.520, 47.120),
            (9.495, 47.120),
            (9.495, 47.095),
        ],
        egui::Color32::from_rgb(60, 200, 70), // unmistakable green
        egui::Color32::from_rgb(20, 80, 30),
        1.0,
    )
}

/// Frame the scene so the flat ground cover fills the pane. `az` nudges the azimuth
/// a hair to emulate a *frame-to-frame* camera change; `el` picks which FACE of the
/// coplanar defect is under the lens ([`EL_TOP`] or [`EL_OBLIQUE`]).
///
/// **The one writer of the camera for every arm of this test** — fixed and control,
/// top and oblique — so no arm can drift onto a different view than the one it is
/// being compared against.
fn framed(mut m: Map3D, az: f32, el: f32) -> Map3D {
    m.spin = false;
    m.set_clock(0.0);
    m.view_preset(ViewPreset::Top);
    m.cam.goal_azimuth = az;
    m.cam.goal_elevation = el;
    m.cam.goal_distance = 2.6;
    m.cam.goal_target = facett_map3d::V3::new(0.0, 0.0, 0.0);
    m.cam.settle();
    m
}

/// Render a Map3D through the GPU depth path; returns the framebuffer.
///
/// Device selection goes through the tree's ONE writer for it,
/// `gpu::proof_wgpu_setup()` (`FACETT_TEST_GPU=discrete` picks the real card), and the
/// adapter is EMITted once — a DEPTH-PRECISION proof whose log does not name the device
/// it ran on cannot be reproduced or argued with, and `egui_kittest`'s
/// `default_wgpu_setup()` does its own selection, which lands on `llvmpipe` even on a
/// box with a 4090 in it.
fn render(mut view: Map3D) -> image::RgbaImage {
    let rs = create_render_state(
        facett_map3d::gpu::proof_wgpu_setup().unwrap_or_else(default_wgpu_setup),
        Default::default(),
    );
    static SAID: std::sync::Once = std::sync::Once::new();
    SAID.call_once(|| {
        let i = rs.adapter.get_info();
        eprintln!("EMITZF adapter: {} [{:?}/{:?}] driver={}", i.name, i.device_type, i.backend, i.driver_info);
    });
    facett_map3d::gpu::install_renderer(&rs);
    view = view.with_gpu(true);
    let mut harness = Harness::builder()
        .with_size(egui::vec2(600.0, 600.0))
        .renderer(WgpuTestRenderer::from_render_state(rs))
        .build_ui(move |ui| {
            facett_core::set_theme(ui.ctx(), facett_core::Theme::sci_fi());
            view.ui(ui);
        });
    harness.run_steps(5);
    harness.render().expect("wgpu render")
}

/// Classify a ground pixel as green-cover, dark-terrain, or other.
fn is_green(p: &[u8; 4]) -> bool {
    // Clean green park cover: green channel clearly dominant. (Recalibrated for the
    // ACES tonemap + aerial-perspective fog, which desaturate the cover toward the
    // bluish sky — the green now reads ~(130,190,160), a ~+20 dominance rather than
    // the old +40, but still unambiguously green vs the cool terrain.)
    p[1] as i32 > 120 && p[1] as i32 > p[0] as i32 + 20 && p[1] as i32 > p[2] as i32 + 20
}
fn is_terrain(p: &[u8; 4]) -> bool {
    // The terrain plane, now a cool neutral grey under the fog/tonemap (~(105,120,135)):
    // present (not background), NOT the green cover (blue not far below green), and a
    // small red↔green split. (Recalibrated: the old "<90 on all channels" dark-terrain
    // test no longer holds after exposure/ACES/aerial-perspective raised & cooled it.)
    let (r, g, b) = (p[0] as i32, p[1] as i32, p[2] as i32);
    let bright = r + g + b;
    bright > 120 && bright < 480 && b + 10 >= g && (g - r).abs() < 45
}

/// **The flicker metric.** Over two renders of the SAME scene at a hair-different
/// camera angle (emulating frame-to-frame jitter), count ground pixels that FLIP
/// between green-cover and dark-terrain. A z-fighting coplanar surface flips a huge
/// number of pixels (the flicker); a properly lifted cover flips ~none (the cover
/// deterministically wins both frames). Returns `(flicker_px, ground_px)`.
fn flicker_between(a: &image::RgbaImage, b: &image::RgbaImage) -> (usize, usize) {
    let (w, h) = (a.width(), a.height());
    let (x0, x1, y0, y1) = (w / 6, w * 5 / 6, h / 6, h * 5 / 6);
    let (mut flicker, mut ground) = (0usize, 0usize);
    for y in y0..y1 {
        for x in x0..x1 {
            let pa = a.get_pixel(x, y).0;
            let pb = b.get_pixel(x, y).0;
            let a_is_ground = is_green(&pa) || is_terrain(&pa);
            let b_is_ground = is_green(&pb) || is_terrain(&pb);
            if a_is_ground || b_is_ground {
                ground += 1;
            }
            // A flip between cover and terrain at the SAME pixel = the z-fight tell.
            let flipped = (is_green(&pa) && is_terrain(&pb)) || (is_terrain(&pa) && is_green(&pb));
            if flipped {
                flicker += 1;
            }
        }
    }
    (flicker, ground)
}

/// Build a Map3D directly from a prebuilt mesh (bypasses `build`, so we can inject
/// the COPLANAR control). Reuses the public `from_ways` shell then swaps the mesh
/// in via a fresh construction from a single way set is not possible — instead we
/// expose the coplanar case by building a mesh with a green cap forced to y=0.
fn coplanar_control_mesh() -> Mesh3 {
    use egui::Color32;
    // Terrain plane (y=0) + a green quad ALSO at y=0 (the bug: coplanar).
    let mut m = Mesh3::default();
    // terrain (dark) — same as terrain_into.
    let dark = Color32::from_rgb(38, 44, 40);
    let up = [0.0f32, 1.0, 0.0];
    let g = 1.05;
    let a = push(&mut m, [-g, 0.0, -g], up);
    let b = push(&mut m, [g, 0.0, -g], up);
    let c = push(&mut m, [g, 0.0, g], up);
    let d = push(&mut m, [-g, 0.0, g], up);
    tri(&mut m, [a, c, b], dark);
    tri(&mut m, [a, d, c], dark);
    // green cover quad at EXACTLY y=0 (coplanar → z-fight).
    let green = Color32::from_rgb(60, 200, 70);
    let s = 0.8;
    let e = push(&mut m, [-s, 0.0, -s], up);
    let f = push(&mut m, [s, 0.0, -s], up);
    let h = push(&mut m, [s, 0.0, s], up);
    let i = push(&mut m, [-s, 0.0, s], up);
    tri(&mut m, [e, h, f], green);
    tri(&mut m, [e, i, h], green);
    m
}

fn push(m: &mut Mesh3, p: [f32; 3], n: [f32; 3]) -> u32 {
    let idx = m.positions.len() as u32;
    m.positions.push(p);
    m.normals.push(n);
    idx
}
fn tri(m: &mut Mesh3, t: [u32; 3], col: egui::Color32) {
    m.indices.extend_from_slice(&t);
    m.colors.push(col);
}

/// Count clean green-cover pixels in the central ground window (the park field).
fn green_px(img: &image::RgbaImage) -> usize {
    let (w, h) = (img.width(), img.height());
    let (x0, x1, y0, y1) = (w / 6, w * 5 / 6, h / 6, h * 5 / 6);
    let mut g = 0;
    for y in y0..y1 {
        for x in x0..x1 {
            if is_green(&img.get_pixel(x, y).0) {
                g += 1;
            }
        }
    }
    g
}

/// Two camera azimuths a hair apart — the frame-to-frame jitter that triggers
/// z-fight flicker on coplanar surfaces (and leaves a properly-lifted cover stable).
const AZ_A: f32 = 0.0;
const AZ_B: f32 = 0.0009; // ~0.05° — sub-pixel motion, the flicker amplifier

/// **Two cameras, because the coplanar defect wears a different face on each device.**
///
/// Looking almost straight down (`EL_TOP`) the whole plane sits at one depth, and what
/// the tie does then is up to the rasteriser. MEASURED 2026-08-22, same mesh, same
/// camera, same commit, two devices:
///
/// | device | coplanar green | flicker ratio | the face |
/// |---|---|---|---|
/// | `llvmpipe (LLVM 21.1.8) [Cpu/Vulkan]` | **0** of 160 000 | 0.0000 | **ERASED** — depths come out bit-identical, `Less` rejects the later fragment on every pixel of every frame |
/// | `NVIDIA GeForce RTX 4090 [DiscreteGpu/Vulkan]`, driver 595.84 | 141 182 | **0.3385** | **SPECKLED** — the interpolators disagree per pixel, so the tie breaks differently frame to frame |
///
/// Both are the bug; neither is the whole bug; and a guard that names ONE of them is a
/// guard that fails on the other box. The erased face flickers exactly 0.0000 — not
/// because the defect is absent but because it is total — which is why the top-down
/// assert below is a disjunction and not a flicker threshold.
///
/// Tilt to `EL_OBLIQUE` and the depths spread across the plane, so BOTH devices
/// speckle and the flicker metric means the same thing on each: ratio **0.3834**
/// (llvmpipe) / **0.3685** (4090) against **0.0000** lifted on both. That is the
/// device-independent discriminator, and it is the one that carries the regression.
///
/// The version before 2026-08-22 measured only the flicker ratio and only at the
/// top-down camera — the one pairing that is dead on a CPU rasteriser — so on the
/// adapter `egui_kittest` actually picks it could only ever fail. That is what it did,
/// on `main`, for three weeks.
const EL_TOP: f32 = 1.30; // steep, looking almost straight down
const EL_OBLIQUE: f32 = 0.55; // tilted — where coplanar depth interleaves on ANY device

/// What one arm of the comparison measures: a scene rendered TWICE at a hair-apart
/// azimuth, reduced to the numbers the asserts read.
struct Arm {
    /// Clean green-cover pixels in frame A (does the cover survive the depth test?).
    green: usize,
    /// Ground pixels (cover or terrain) — the non-vacuity denominator.
    ground: usize,
    /// Fraction of ground pixels that FLIP cover↔terrain across the jitter.
    ratio: f64,
}

/// Render one scene twice through the real GPU depth path and measure it. `make`
/// builds a fresh `Map3D` per frame, so both frames of an arm are constructed
/// identically and the only difference between them is the azimuth.
fn measure(el: f32, png: &str, make: impl Fn(&str) -> Map3D) -> Arm {
    let a = render(framed(make("a"), AZ_A, el));
    let b = render(framed(make("b"), AZ_B, el));
    image::DynamicImage::ImageRgba8(a.clone()).save(png).ok();
    let (flicker, ground) = flicker_between(&a, &b);
    Arm { green: green_px(&a), ground, ratio: flicker as f64 / ground.max(1) as f64 }
}

/// The lifted (production) scene: the real `build` path from a single green park.
fn fixed_scene(tag: &str) -> Map3D {
    Map3D::from_ways(tag, std::slice::from_ref(&green_park()), 0.22)
}

/// The same scene with the cover forced back down onto the terrain — the bug.
fn coplanar_scene(tag: &str) -> Map3D {
    let mut m = fixed_scene(tag);
    m.set_mesh_for_test(coplanar_control_mesh());
    m
}

#[test]
fn fixed_ground_has_no_flicker_coplanar_control_flickers() {
    // ── FIXED (data): the real extrusion lifts the flat park above terrain ──
    let (mesh_fixed, _t) = build(std::slice::from_ref(&green_park()), 0.22);
    assert!(mesh_fixed.positions[4..].iter().all(|p| p[1] > 0.0), "the fixed park cap clears the terrain plane");
    assert!(
        mesh_fixed.positions[4..].iter().map(|p| p[1]).fold(0.0f32, f32::max) <= GROUND_LIFT * 1.5,
        "but only by a sub-pixel hair"
    );

    let fixed_top = measure(EL_TOP, "/tmp/map3d_zfix_fixed.png", fixed_scene);
    let ctrl_top = measure(EL_TOP, "/tmp/map3d_zfix_coplanar.png", coplanar_scene);
    let fixed_obl = measure(EL_OBLIQUE, "/tmp/map3d_zfix_fixed_oblique.png", fixed_scene);
    let ctrl_obl = measure(EL_OBLIQUE, "/tmp/map3d_zfix_coplanar_oblique.png", coplanar_scene);
    for (name, a) in
        [("fixed_top", &fixed_top), ("ctrl_top", &ctrl_top), ("fixed_oblique", &fixed_obl), ("ctrl_oblique", &ctrl_obl)]
    {
        eprintln!("EMITZF {name}: green={} ground={} flicker_ratio={:.4}", a.green, a.ground, a.ratio);
    }

    // ── NON-VACUITY, and it goes BEFORE every discriminator ──────────────────
    //
    // A control that draws nothing cannot flicker and cannot lose its cover, and
    // "did neither" then reads exactly like "the fix works". MEASURED 2026-08-22 (see
    // the EMITZF adapter line for the device): this control put **407** ground pixels on screen against the fixed
    // scene's **97 994** — 0.4 % — and the 407 were the LETTERS of "no ways to
    // extrude", because `set_mesh_for_test` left `tile_count` at 1 and the pane
    // refuses to draw at `<= 1` (the `facet-map3d-1` frame). Every pixel measured
    // below was being measured off a sentence. Measure that the control DREW before
    // believing anything about what it did.
    for (name, ctrl, fixed, png) in [
        ("top", &ctrl_top, &fixed_top, "/tmp/map3d_zfix_coplanar.png"),
        ("oblique", &ctrl_obl, &fixed_obl, "/tmp/map3d_zfix_coplanar_oblique.png"),
    ] {
        let coverage = ctrl.ground as f64 / fixed.ground.max(1) as f64;
        eprintln!("EMITZF {name} control coverage: ctrl_ground={} fixed_ground={} coverage={coverage:.3}", ctrl.ground, fixed.ground);
        assert!(
            coverage > 0.5,
            "the {name} coplanar control covers only {coverage:.3} of the ground the fixed scene does \
             ({} px vs {} px) — it did not render a scene, so whatever it does or does not do below \
             proves nothing. Look at {png}.",
            ctrl.ground,
            fixed.ground
        );
    }

    // ── The FIXED scene: a large clean green field, stable at BOTH cameras ──
    assert!(fixed_top.green > 5_000, "the fixed ground shows a large clean green field (green={})", fixed_top.green);
    assert!(fixed_obl.green > 5_000, "…from the oblique view too (green={})", fixed_obl.green);
    for (name, a) in [("top", &fixed_top), ("oblique", &fixed_obl)] {
        assert!(a.ratio < 0.02, "the fixed ground does NOT flicker at the {name} camera (flicker_ratio={:.4})", a.ratio);
    }

    // ── TOP-DOWN: the coplanar cover is never delivered CLEAN ────────────────
    // A tie at one depth breaks per rasteriser (see EL_TOP): llvmpipe erases the cover
    // (0 px kept, flicker 0.0000), the 4090 speckles it (141 182 px kept, flicker
    // 0.3385). The guard must accept either FACE and reject only the third outcome —
    // a clean stable cover, which is what the LIFTED scene delivers and a coplanar one
    // cannot on any device. Asserted as a disjunction on purpose: a flicker threshold
    // here is dead on a CPU rasteriser, and an erasure threshold is dead on the 4090.
    let top_kept = ctrl_top.green as f64 / fixed_top.green.max(1) as f64;
    eprintln!("EMITZF top face: kept={top_kept:.3} flicker={:.4}", ctrl_top.ratio);
    assert!(
        top_kept < 0.05 || ctrl_top.ratio > 0.05,
        "top-down, the coplanar control came out CLEAN — it kept {:.1} % of the lifted cover's green \
         ({} px of {}) AND held still across the jitter (ratio {:.4}). Coplanar geometry cannot do \
         that: either the depth tie erases the cover or it speckles it. The control is not coplanar, \
         or the depth test is not running.",
        100.0 * top_kept,
        ctrl_top.green,
        fixed_top.green,
        ctrl_top.ratio
    );

    // ── OBLIQUE: coplanar SPECKLES, on every device ──────────────────────────
    // The visible flicker the owner reported: a large fraction of ground pixels flip
    // green↔terrain across a 0.05° jitter. Measured 0.3834 (llvmpipe) / 0.3685 (4090)
    // against 0.0000 lifted on both — the device-independent discriminator.
    assert!(
        ctrl_obl.ratio > fixed_obl.ratio + 0.05,
        "obliquely, the coplanar control flickers (ratio {:.4}) FAR more than the fixed scene ({:.4}) — \
         z-fight reproduced + fixed",
        ctrl_obl.ratio,
        fixed_obl.ratio
    );

    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-map3d",
        "ground_no_zfight_flicker",
        fixed_top.green > 5_000
            && fixed_top.ratio < 0.02
            && fixed_obl.ratio < 0.02
            && (top_kept < 0.05 || ctrl_top.ratio > 0.05)
            && ctrl_obl.ratio > fixed_obl.ratio + 0.05,
        &format!(
            "top: fixed_green={} ctrl_kept={top_kept:.3} ctrl_flicker={:.4} | oblique: fixed_flicker={:.4} coplanar_flicker={:.4}",
            fixed_top.green, ctrl_top.ratio, fixed_obl.ratio, ctrl_obl.ratio
        ),
    );
}