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