facett-demo/tests/osm3d_city.rs
//! **Priority #3 — make buildings VISIBLE (LOD + framing) + the depth-tested GPU
//! city render, end to end.**
//!
//! At country scale the whole [-1,1] design box is in view and every footprint is
//! sub-pixel — the viewer reads flat-grey. `Map3D::from_geometry` now frames the
//! DENSEST building cluster and dollies in, and the OSM 3D tab draws through the
//! GPU depth buffer. This test renders the REAL liechtenstein OSM 3D tab exactly
//! as the demo builds it (framed + GPU-installed) through kittest's wgpu backend
//! and asserts, as DATA + a PNG the agent reads:
//! 1. `visible_building_count > 0` (the framed mesh has risen footprints);
//! 2. the framed camera is dollied IN (distance ≪ the whole-country default 3.2);
//! 3. a real content-ratio from the rendered frame (the city fills the pane, not
//! flat-grey) — the discriminator vs the old whole-country sub-pixel render.
//!
//! Run: `cargo test -p facett-demo --test osm3d_city -- --nocapture`
#![cfg(feature = "map3d-wgpu")]
use egui_kittest::Harness;
use egui_kittest::wgpu::{WgpuTestRenderer, create_render_state, default_wgpu_setup};
use facett::Facet;
use facett_demo::osm_tabs;
/// Fraction of pixels far from the modal (≈ background) colour — "does it look
/// like a city, not flat grey?" as a number.
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 total = (img.width() * img.height()) as usize;
let mut content = 0usize;
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 real_osm3d_frames_a_visible_city() {
let geo = osm_tabs::load();
eprintln!("EMIT3 source={:?} ways_3d={} points={}", geo.source, geo.ways_3d.len(), geo.points.len());
// Build the tab EXACTLY as the demo does (framed + GPU opt-in). The demo tab now
// ships the cinematic Sigtuna TOUR on (air-circle → descend → drive street); its
// clock-driven pose would otherwise override the STATIC framed camera this half
// reads (the "did the framing dolly in?" check), so turn the auto-pilot off for the
// distance read. The tour has its own descend proof (`osm3d_street_tour`).
let mut view = osm_tabs::osm3d_view(&geo);
view.set_tour(false);
let s = view.state_json();
let total_tris = s["triangles"].as_u64().unwrap_or(0);
let dist = s["camera"]["distance"].as_f64().unwrap_or(0.0);
// Count risen (building) vertices in the mesh = visible_building footprints.
let visible_buildings = view.mesh().positions.iter().filter(|p| p[1] > 1e-3).count();
eprintln!("EMIT3 total_tris={total_tris} framed_distance={dist:.3} visible_building_verts={visible_buildings}");
// (1) buildings exist + (2) the camera dollied IN from the whole-country 3.2.
assert!(visible_buildings > 0, "the framed mesh has risen building footprints");
// Only assert the dolly-in when there are real buildings (synthetic rabbit
// fallback keeps the default framing).
if geo.source != osm_tabs::OsmSource::Synthetic {
assert!(dist < 3.0, "framed camera dollied into the dense cluster (distance {dist:.3} < 3.0)");
}
// (3) render it through the GPU depth path + read the PNG ourselves.
let rs = create_render_state(default_wgpu_setup(), Default::default());
facett::map3d::gpu::install_renderer(&rs);
let mut v = osm_tabs::osm3d_view(&geo);
let mut harness = Harness::builder()
.with_size(egui::vec2(900.0, 900.0))
.renderer(WgpuTestRenderer::from_render_state(rs))
.build_ui(move |ui| {
facett::core::set_theme(ui.ctx(), facett::core::Theme::sci_fi());
v.ui(ui);
});
harness.run_steps(6);
let img = harness.render().expect("wgpu render");
let ratio = content_ratio(&img);
image::DynamicImage::ImageRgba8(img).save("/tmp/osm3d_city.png").ok();
eprintln!("EMIT3 content_ratio={ratio:.4} → /tmp/osm3d_city.png");
// The framed GPU city fills a real fraction of the pane — NOT the old flat-grey
// whole-country render (which sat near 0). 5% is a conservative floor.
assert!(ratio > 0.05, "the framed 3D city fills the pane (content_ratio {ratio:.4} > 0.05)");
#[cfg(feature = "testmatrix")]
facett::core::testmatrix::emit(
"facett-map3d",
"osm3d_frames_visible_city",
visible_buildings > 0 && ratio > 0.05,
&format!("source={:?} visible_building_verts={visible_buildings} framed_distance={dist:.3} content_ratio={ratio:.4}", geo.source),
);
}
/// Framing must multiply the risen-pixel share by at least this much over the
/// whole-box pose (calibration: see the test doc).
const RISEN_FACTOR: f64 = 4.0;
/// …and the framed pane must carry at least this share of risen-building pixels.
const RISEN_FLOOR: f64 = 0.10;
/// Fraction of pixels that are **building gray** — mid-bright, near-neutral (r≈g≈b).
/// Kept as a READOUT only (it is in the EMIT3 line): it is an ACHROMATIC-SHARE
/// measure, and at the whole-box pose the achromatic share is the TERRAIN QUAD, not
/// the houses — see [`risen_pixel_ratio`] for the discriminator that actually isolates
/// buildings, and the test doc for the 2026-08-21 finding that retired this as the
/// assertion.
fn building_ratio(img: &image::RgbaImage) -> f64 {
let total = (img.width() * img.height()) as usize;
let mut buildings = 0usize;
for p in img.pixels() {
let (r, g, b) = (p.0[0] as i32, p.0[1] as i32, p.0[2] as i32);
let max = r.max(g).max(b);
let min = r.min(g).min(b);
// Gray-ish (low saturation) AND mid-bright → a lit building face/wall.
if max >= 70 && (max - min) <= 28 {
buildings += 1;
}
}
buildings as f64 / total as f64
}
/// **Pixels the building HEIGHT changes** — the fraction of the pane that differs
/// between a render and the SAME scene rendered with every building flattened to the
/// ground (same footprints, same colours, same terrain, same camera; only the walls,
/// the lifted roofs and their shadows are gone). A pixel that changes is a pixel where
/// a resolvable 3D building is: at a pose where the houses are sub-pixel the two
/// frames are near-identical, at a pose that fills the pane with the town they are not.
/// Colour-blind by construction, so the terrain quad's shade, the theme and the albedo
/// transfer function cannot move it — the three things `building_ratio` measured.
fn risen_pixel_ratio(full: &image::RgbaImage, flat: &image::RgbaImage) -> f64 {
assert_eq!(full.dimensions(), flat.dimensions());
let total = (full.width() * full.height()) as usize;
let mut changed = 0usize;
for (a, b) in full.pixels().zip(flat.pixels()) {
let d = (0..3).map(|i| (a.0[i] as i32 - b.0[i] as i32).abs()).max().unwrap_or(0);
// > 8/255 on any channel: above the tonemap/dither noise floor, below any
// lit-wall-vs-ground or shadow-vs-lit difference.
if d > 8 {
changed += 1;
}
}
changed as f64 / total as f64
}
/// SENSITIVITY (fails-on-bug/passes-on-fix for framing): the SAME real city + GPU,
/// rendered at the OLD whole-box framing (distance 3.2) vs the new densest-cluster
/// framing. At whole-box scale the town is a small patch on the terrain quad and its
/// houses are sub-pixel; cluster framing fills the pane with them. We assert the framed
/// RISEN-pixel ratio ([`risen_pixel_ratio`]: pixels the building height changes) is
/// far larger — proving the framing makes the city ITSELF visible, not just "more
/// content".
///
/// # Why the discriminator is a flattened-twin DIFF and not `building_ratio` (2026-08-21)
///
/// Until today this asserted `building_ratio(framed) > 2× building_ratio(whole)`, where
/// `building_ratio` counts mid-bright low-saturation pixels. That is an ACHROMATIC-SHARE
/// measure, and at the whole-box pose the achromatic share IS the terrain quad (≈40 % of
/// the pane), not the houses. Swept on oden (RTX 4090, real clip):
///
/// ```text
/// 2026-08-01 1a48bfe5 whole 0.3147 framed 0.7152 2.27× (pale near-white quad + pancake town)
/// 2026-08-05 632b7133 whole 0.2964 framed 0.6045 2.04×
/// 2026-08-07 7dfeda07 whole 0.2074 framed 0.3127 1.51× RED
/// 2026-08-21 77ffb8bb whole 0.2002 framed 0.3167 1.58× RED
/// ```
///
/// The drop is f8772ad1 (2026-08-05): the GPU lane started decoding the mesh's sRGB
/// albedo to LINEAR before lighting, which is what the shader had always assumed — a
/// deliberate fix whose own measurement is "achromatic share 46.85 % → 36.07 %". The
/// near-white terrain quad became the slate it should be, so the whole-box "building"
/// count fell from 0.31 to 0.20 — and the "2.3× margin" the 2026-07-09 recalibration
/// (8a0b10cc, itself a 3×→2× loosening for the same reason) had banked was gone. The
/// test never measured buildings; it measured how white the picture was, and it went red
/// when the picture got less wrong. Loosening the factor again would have been the
/// same mistake a third time, so the discriminator is replaced by one the terrain
/// cannot contribute to, and the old number stays in the EMIT3 line as a readout.
///
/// MEASURED (oden, RTX 4090, the real Sigtuna clip, 2026-08-21, run twice, identical):
/// whole risen 0.0143, framed risen 0.1847 → **12.9×**; [`RISEN_FACTOR`] = 4 keeps a
/// 3× cushion against shading/terrain drift while a framing that stops dollying in
/// (→ ≈1×) or dollies somewhere without houses (→ below [`RISEN_FLOOR`] = 0.10, half
/// the measured 0.18) still trips it.
#[test]
fn framing_beats_whole_country_for_visibility() {
let geo = osm_tabs::load();
if geo.source == osm_tabs::OsmSource::Synthetic {
return; // no real footprints to frame
}
// The flattened twin: every building keeps its footprint, colour and position, but
// carries a TAGGED height of 0 m. `building_design_height` uses a tagged height
// as-is (clamped to 1e-5 du — no degenerate prism), so the walls collapse below a
// pixel while nothing else in the scene moves. Only `kind == "building"` is touched:
// the other risen rings (untagged closed ways) are not what framing is about.
let mut flat_ways = geo.ways_3d.clone();
for w in &mut flat_ways {
if w.kind == "building" {
w.height_m = Some(0.0);
}
}
// Render ONE scene at ONE pose and return (content_ratio, building_ratio, frame).
let render = |ways: &[facett::map::Way], cam: Option<facett::map3d::OrbitCamera>, whole_country: bool, tag: &str| {
let rs = create_render_state(default_wgpu_setup(), Default::default());
facett::map3d::gpu::install_renderer(&rs);
// This test guards the FRAMING feature: dollying into the densest cluster
// resolves the city where the whole-[-1,1]-box pose leaves it sub-pixel. That is
// a property of `from_geometry_relief` on the FULL clip — the demo TAB
// (`osm3d_view`) CROPS to the town core for its street-level tour, which has no
// box scale left to compare against. So build the comparison from the uncropped
// clip the SAME way the tab extrudes its mesh (no auto-pilot on this
// constructor), keeping the box-vs-cluster discriminator intact.
let (elev, _real) = facett::map3d::TerrainField::from_env();
let mut v = facett::map3d::Map3D::from_geometry_relief(osm_tabs::TITLE_3D, ways, &elev, 48, 3.5)
.with_gpu(true);
if whole_country {
// Undo the cluster framing → the old whole-[-1,1]-box camera.
v.reset_view();
v.cam.goal_distance = 3.2;
v.cam.goal_target = facett::map3d::V3::new(0.0, 0.1, 0.0);
v.cam.settle();
}
// The flattened twin has NO risen vertices, so `frame_densest_cluster` leaves it
// on the default pose; it must look through the FULL scene's camera, or the diff
// would measure two poses instead of one height.
if let Some(cam) = cam {
v.cam = cam;
}
let cam_out = v.cam.clone();
let mut h = Harness::builder()
.with_size(egui::vec2(900.0, 900.0))
.renderer(WgpuTestRenderer::from_render_state(rs))
.build_ui(move |ui| {
facett::core::set_theme(ui.ctx(), facett::core::Theme::sci_fi());
v.ui(ui);
});
h.run_steps(6);
let img = h.render().expect("render");
image::DynamicImage::ImageRgba8(img.clone()).save(format!("/tmp/osm3d_{tag}.png")).ok();
(content_ratio(&img), building_ratio(&img), cam_out, img)
};
let (whole_c, whole_b, whole_cam, whole_img) = render(&geo.ways_3d, None, true, "whole");
let (_, _, _, whole_flat) = render(&flat_ways, Some(whole_cam), true, "whole_flat");
let (framed_c, framed_b, framed_cam, framed_img) = render(&geo.ways_3d, None, false, "framed");
let (_, _, _, framed_flat) = render(&flat_ways, Some(framed_cam), false, "framed_flat");
let whole_r = risen_pixel_ratio(&whole_img, &whole_flat);
let framed_r = risen_pixel_ratio(&framed_img, &framed_flat);
eprintln!(
"EMIT3 whole(content={whole_c:.4} building={whole_b:.4} risen={whole_r:.4}) \
framed(content={framed_c:.4} building={framed_b:.4} risen={framed_r:.4}) \
→ /tmp/osm3d_{{whole,whole_flat,framed,framed_flat}}.png"
);
// The discriminator is RISEN pixels: the pane area the building height changes.
assert!(
framed_r > whole_r * RISEN_FACTOR,
"cluster framing resolves far more building pixels than the whole-box pose (risen framed {framed_r:.4} vs whole {whole_r:.4}, need >{RISEN_FACTOR}×)"
);
assert!(framed_r > RISEN_FLOOR, "framed render shows a substantial building skyline (risen {framed_r:.4} > {RISEN_FLOOR})");
}