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

//! **The 3D pane draws the viewport, continuously, and fades — it is not a rectangle.**
//!
//! [`Map3D::from_geometry_in_view_roofed`] extrudes the town under the crosshair and
//! nothing else, a box of at most `2 ·`[`extrude::TOWN_HALF_MAX_M`] = **1.16 km**. On
//! korp's z12 viewport (37 km) that is 3 % of the width and 0.1 % of the area, and it is
//! what the operator was looking at: *"why is 3d only a tile / rectangle rotating? you
//! need to make it continously but cut grafiks at a distant nelgnht"*. Out in open
//! country it extrudes **nothing** and the pane paints a sentence over a viewport that
//! has roads and water in it: *"its ok to go 3d from 2d and there is no buildings, but
//! draw the rest"*.
//!
//! [`Map3D::from_viewport_roofed`] is the other question — *what is in the viewport, and
//! how much of it can this frame afford?* — and this file is the measurement under it.
//!
//! # The readings this file takes, on t14s (AMD 890M / RADV STRIX1)
//!
//! All DEBUG-profile: the release build was owned by another session and could not be
//! run, which is said here rather than implied by a number nobody took.
//!
//! ```text
//!   the extruder emits             4.0 – 7.8 M tris/s     (`throughput`)
//!   the mesh costs                 67 – 71 bytes/triangle  (`throughput`)
//!   z12, town path:                 14 106 tris, p50 house 10.07 px, scene  1.16 km
//!   z12, continuous path:          198 852 tris, p90 house  9.57 px, scene 16.58 km
//!   dense city, 600 k budget:      full detail to 2.29 km, boxes to 3.09 km,
//!                                  34 266 buildings dropped past that, p90 8.00 px
//!   the triangle ESTIMATE tracks the applied mesh to within 0.5 %
//! ```

use facett_core::Theme;
use facett_map::Way;
use facett_map3d::{extrude, Map3D};

/// The ONE writer of the flat-Earth ruler these fixtures are built on: the extruder's
/// own [`facett_map3d::extrude::M_PER_DEG_LAT`]. A fixture built on a private copy of
/// 111_320 measures a DIFFERENT earth from the code under test the moment either side is
/// edited — and the copies agree until then, which is exactly how this survived in nine
/// files (`granska-20`).
use facett_map3d::extrude::M_PER_DEG_LAT;
const STOCKHOLM: (f64, f64) = (18.0686, 59.3293);
/// korp's MEASURED z12 viewport span on a 1900x1150 pane — the same anchor
/// `tests/zoom_ladder.rs` derives its ladder from.
const KORP_Z12_SPAN_M: f64 = 37_042.0;

use facett_map3d::extrude::m_per_deg_lon;

fn span_at(z: f64) -> f64 {
    KORP_Z12_SPAN_M * 2f64.powf(12.0 - z)
}

fn ring(lon: f64, lat: f64, half_lon: f64, half_lat: f64) -> Vec<(f64, f64)> {
    vec![
        (lon - half_lon, lat - half_lat),
        (lon + half_lon, lat - half_lat),
        (lon + half_lon, lat + half_lat),
        (lon - half_lon, lat + half_lat),
        (lon - half_lon, lat - half_lat),
    ]
}

fn tags(kv: &[(&str, &str)]) -> Vec<(String, String)> {
    kv.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()
}

/// `n x n` 12 m houses on a `pitch_m` grid, centred on `centre`.
fn houses(id0: i64, centre: (f64, f64), n: usize, pitch_m: f64) -> Vec<Way> {
    let (clon, clat) = centre;
    let mpdl = m_per_deg_lon(clat);
    let theme = Theme::default();
    let (h_lon, h_lat) = (6.0 / mpdl, 6.0 / M_PER_DEG_LAT);
    let mut ways = Vec::new();
    let mut id = id0;
    for j in 0..n {
        for i in 0..n {
            let dx = (i as f64 - (n as f64 - 1.0) * 0.5) * pitch_m;
            let dy = (j as f64 - (n as f64 - 1.0) * 0.5) * pitch_m;
            ways.extend(Way::from_lonlat(
                id,
                &ring(clon + dx / mpdl, clat + dy / M_PER_DEG_LAT, h_lon, h_lat),
                &tags(&[("building", "yes")]),
                &theme,
            ));
            id += 1;
        }
    }
    ways
}

/// `n x n` **hexagonal** 12 m houses — a footprint that is NOT its own bounding box, so
/// `Detail::Full` and `Detail::Coarse` are distinguishable by shape.
fn hex_houses(id0: i64, centre: (f64, f64), n: usize, pitch_m: f64) -> Vec<Way> {
    let (clon, clat) = centre;
    let mpdl = m_per_deg_lon(clat);
    let theme = Theme::default();
    let mut ways = Vec::new();
    let mut id = id0;
    for j in 0..n {
        for i in 0..n {
            let dx = (i as f64 - (n as f64 - 1.0) * 0.5) * pitch_m;
            let dy = (j as f64 - (n as f64 - 1.0) * 0.5) * pitch_m;
            let (cx, cy) = (clon + dx / mpdl, clat + dy / M_PER_DEG_LAT);
            let mut hexagon: Vec<(f64, f64)> = (0..6)
                .map(|k| {
                    let a = std::f64::consts::TAU * k as f64 / 6.0;
                    (cx + 6.0 * a.cos() / mpdl, cy + 6.0 * a.sin() / M_PER_DEG_LAT)
                })
                .collect();
            hexagon.push(hexagon[0]);
            ways.extend(Way::from_lonlat(id, &hexagon, &tags(&[("building", "yes")]), &theme));
            id += 1;
        }
    }
    ways
}

/// **A z12 viewport of real shape**: a dense inner city, three rings of suburbs out to
/// the viewport edge, a road grid spanning the whole viewport, and a lake. Deliberately
/// NOT one compact town — the whole point of the continuous path is what it does with the
/// geometry a town window throws away.
fn city_region() -> Vec<Way> {
    let (clon, clat) = STOCKHOLM;
    let mpdl = m_per_deg_lon(clat);
    let theme = Theme::default();
    let mut ways = houses(1_000, STOCKHOLM, 60, 40.0); // 2.4 km inner city
    let mut id = 200_000i64;
    for (r_km, count, side, pitch) in [(3.0, 6, 26, 45.0), (7.0, 8, 20, 55.0), (13.0, 8, 14, 60.0)] {
        for k in 0..count {
            let a = std::f64::consts::TAU * k as f64 / count as f64;
            let (dx, dy) = (r_km * 1000.0 * a.cos(), r_km * 1000.0 * a.sin());
            ways.extend(houses(id, (clon + dx / mpdl, clat + dy / M_PER_DEG_LAT), side, pitch));
            id += 10_000;
        }
    }
    let half_lat = 0.5 * KORP_Z12_SPAN_M / M_PER_DEG_LAT;
    let half_lon = 0.5 * KORP_Z12_SPAN_M / mpdl;
    for i in 0..24 {
        let t = i as f64 / 23.0 - 0.5;
        let h: Vec<(f64, f64)> = (0..12)
            .map(|k| (clon - half_lon + 2.0 * half_lon * k as f64 / 11.0, clat + 2.0 * half_lat * t))
            .collect();
        let v: Vec<(f64, f64)> = (0..12)
            .map(|k| (clon + 2.0 * half_lon * t, clat - half_lat + 2.0 * half_lat * k as f64 / 11.0))
            .collect();
        ways.push(Way::line(id, "road", h, egui::Color32::GRAY, 2.0));
        ways.push(Way::line(id + 1, "road", v, egui::Color32::GRAY, 2.0));
        id += 2;
    }
    ways.extend(Way::from_lonlat(
        id,
        &ring(clon - half_lon * 0.1, clat + half_lat * 0.05, half_lon * 0.06, half_lat * 0.06),
        &tags(&[("natural", "water")]),
        &theme,
    ));
    ways
}

fn view(centre: (f64, f64), span_m: f64) -> extrude::ViewWindow {
    extrude::ViewWindow { lon: centre.0, lat: centre.1, span_m }
}

// ─────────────────────────────────────────────────────────────────────────────
//  THE CLAIMS
// ─────────────────────────────────────────────────────────────────────────────

/// **THE CLAIM: the scene is the viewport, not a 1.16 km box — and the near band is still
/// legible.**
///
/// The two halves are one assertion, because either alone is trivially satisfiable: a
/// wider scene is one line ([`extrude::view_town_subset`] with a bigger window) and it
/// costs the skyline — MEASURED before [`extrude::scene_reach_half_m`] existed, windowing
/// the whole 37 km viewport put the median house at **0.77 px** against the town path's
/// 10.07.
///
/// RED-WHEN-BROKEN: making `scene_reach_half_m` return `TOWN_HALF_MAX_M` fails (a);
/// making it return `span_m / 2` fails (b).
#[test]
fn the_scene_is_the_viewport_and_the_near_band_is_legible() {
    let ways = city_region();
    let budget = extrude::SceneBudget::default();
    println!(
        "{:>5} {:>9} | {:>8} {:>8} {:>7} | {:>8} {:>8} {:>7} {:>7} {:>9} {:>6}",
        "zoom", "span_m", "OLDtris", "OLD m/du", "OLDp50", "NEWtris", "NEW m/du", "NEWp90",
        "NEWmax", "reach_km", "whole",
    );
    for zi in 9..=16 {
        let z = f64::from(zi);
        let v = view(STOCKHOLM, span_at(z));
        let old = Map3D::from_geometry_in_view_roofed("town", &ways, Some(v));
        let new = Map3D::from_viewport_roofed("continuous", &ways, v, budget);
        let s = new.viewport_scene().expect("the continuous path always reports");
        let (p90, mx) = (new.skyline_percentile_px(1150.0, 0.9), new.skyline_percentile_px(1150.0, 1.0));
        println!(
            "{z:>5} {:>9.0} | {:>8} {:>8.1} {:>7.2} | {:>8} {:>8.1} {p90:>7.2} {mx:>7.2} {:>9.1} {:>6}",
            v.span_m,
            old.triangle_count(),
            old.metres_per_design_unit(),
            old.skyline_percentile_px(1150.0, 0.5),
            new.triangle_count(),
            new.metres_per_design_unit(),
            s.ground_radius_m / 1000.0,
            s.reach_is_the_viewport,
        );

        // (a) The scene really is wider. At the cap the town window is 1.16 km across; the
        //     continuous reach must be MULTIPLES of that at every zoom the operator can
        //     drive, or nothing has changed.
        let town_across = 2.0 * extrude::town_half_for_span(v.span_m);
        let scene_across = 2.0 * s.ground_radius_m;
        assert!(
            scene_across > 6.0 * town_across,
            "z{z}: the continuous scene must be far wider than the town box \
             ({scene_across:.0} m vs {town_across:.0} m)",
        );

        // (b) …and the houses in the near band still stand up. `p50` is deliberately NOT
        //     the metric here: the population it is taken over is now the WHOLE visible
        //     scene out to the horizon, so most of it is far away by construction and a
        //     falling median is the falloff working, not a regression. The near band is
        //     the upper decile.
        assert!(
            f64::from(p90) >= extrude::LEGIBLE_MEDIAN_HOUSE_PX,
            "z{z}: the near band must stay legible — p90 {p90:.2} px against a {:.0} px \
             target (max {mx:.2})",
            extrude::LEGIBLE_MEDIAN_HOUSE_PX,
        );
    }
}

/// **THE CLAIM: from the zoom where a viewport fits, the scene covers ALL of it.**
///
/// The reach is capped at `span_m / 2` — there is no point extruding ground the 2D pane
/// is not showing — so at street/town zoom the 3D pane and the 2D pane show the same box.
/// That is "continuously across the viewport" made assertable.
#[test]
fn at_town_zoom_the_scene_covers_the_whole_viewport() {
    let ways = city_region();
    let mut covered = 0;
    for zi in 9..=17 {
        let v = view(STOCKHOLM, span_at(f64::from(zi)));
        let s = Map3D::from_viewport_roofed("c", &ways, v, extrude::SceneBudget::default())
            .viewport_scene()
            .expect("reports");
        println!(
            "z{zi}: viewport {:.2} km, scene {:.2} km, whole {}",
            v.span_m / 1000.0,
            2.0 * s.ground_radius_m / 1000.0,
            s.reach_is_the_viewport,
        );
        if s.reach_is_the_viewport {
            covered += 1;
            assert!(
                (2.0 * s.ground_radius_m - v.span_m).abs() < 2.0,
                "z{zi}: 'whole viewport' must mean exactly the viewport",
            );
        }
    }
    assert!(covered >= 4, "the reach must cover the whole viewport at the deep end (only {covered} rungs)");
}

/// **THE CLAIM: the budget, not a metre constant, sets how deep the detail goes — and it
/// degrades smoothly.**
///
/// Four budgets over one 7.8 km solid city (67 600 footprints). Halving the budget must
/// halve the geometry and pull the bands IN; it must never empty the scene. That is what
/// "self-limiting, degrades on a weak GPU instead of falling off a cliff" means, stated
/// as something the build fails on.
#[test]
fn the_budget_sets_the_bands() {
    let ways = houses(1, STOCKHOLM, 260, 30.0);
    let v = view(STOCKHOLM, KORP_Z12_SPAN_M);
    let mut last: Option<(usize, f64)> = None;
    for tris in [150_000usize, 300_000, 600_000, 1_200_000] {
        let budget = extrude::SceneBudget { triangles: tris, ..Default::default() };
        let map = Map3D::from_viewport_roofed("dense", &ways, v, budget);
        let s = map.viewport_scene().unwrap();
        println!(
            "budget {tris:>9}: mesh {:>9} tris (est {:>9})  full {:>6} houses to {:>6.0} m | \
             boxes {:>6} to {:>6.0} m | {:>6} dropped   p90 {:.2} px",
            map.triangle_count(),
            s.triangles,
            s.buildings_full,
            s.full_radius_m,
            s.buildings_coarse,
            s.coarse_radius_m,
            s.buildings_dropped,
            map.skyline_percentile_px(1150.0, 0.9),
        );

        // The budget is HONOURED: the applied mesh lands on it, not near it. (Ground cover
        // and the road network are unconditional and can push past — see
        // `extrude::viewport_scene` — but on a pure city they do not.)
        assert!(
            map.triangle_count() <= tris + tris / 10,
            "the mesh must respect the budget: {} against {tris}",
            map.triangle_count(),
        );
        // The bands are real: some buildings got boxes, some got dropped.
        assert!(
            s.buildings_full > 0 && s.buildings_coarse > 0 && s.buildings_dropped > 0,
            "a budget under the scene must produce all three bands: {s:?}",
        );
        // …and MONOTONE in the budget: more triangles ⇒ the full band reaches further.
        if let Some((prev_tris, prev_radius)) = last {
            assert!(
                s.full_radius_m > prev_radius,
                "a bigger budget must reach further: {tris} reached {:.0} m, {prev_tris} \
                 reached {prev_radius:.0} m",
                s.full_radius_m,
            );
        }
        last = Some((tris, s.full_radius_m));
    }
}

/// **THE CLAIM: the triangle estimate the budget spends is the mesh the extruder emits.**
///
/// A budget is only self-limiting if the number it counts is the number that arrives.
/// [`extrude::estimated_triangles`] is read off `build_with_water_terrain_styled`'s own
/// branches, and this is where that reading is checked against the applied mesh.
#[test]
fn the_estimate_is_the_mesh() {
    let ways = city_region();
    for zi in [10, 12, 14, 16] {
        let scene = extrude::viewport_scene(
            &ways,
            view(STOCKHOLM, span_at(f64::from(zi))),
            extrude::SceneBudget::default(),
        );
        let (mesh, ..) = extrude::build_with_water_terrain_styled(
            &scene.ways,
            0.22,
            extrude::GroundTerrain::Flat,
            extrude::RoofStyle::Pitched,
        );
        // The terrain quad is 2 triangles the estimator does not count (it is not a way).
        let real = mesh.triangle_count().saturating_sub(2);
        let est = scene.stats.triangles;
        let err = (est as f64 - real as f64) / real.max(1) as f64;
        println!("z{zi}: estimate {est} vs mesh {real}  ({:+.2} %)", err * 100.0);
        assert!(err.abs() < 0.02, "z{zi}: the estimate must be the mesh within 2 % — {est} vs {real}");
    }
}

/// **THE CLAIM: a viewport with no buildings in it still draws the country it is over.**
///
/// The old path answered `TownChoice::TooCoarse` with an EMPTY ways set, and the pane
/// returned before it painted anything. So the operator got one sentence on a blank canvas
/// over a viewport holding six roads and a lake.
///
/// It also asserts the second half of that defect: the refusal used to say *"zoomed too
/// far out … Zoom in."* when the count was a hard **zero**, which is advice that cannot
/// possibly work. Two facts, two sentences ([`extrude::TownRefusal::message`]).
#[test]
fn open_country_still_draws_the_ground() {
    let (clon, clat) = (16.5, 62.5); // korp's country centre — forest near Ånge, no houses
    let mpdl = m_per_deg_lon(clat);
    let theme = Theme::default();
    let span = KORP_Z12_SPAN_M;
    let (half_lat, half_lon) = (0.5 * span / M_PER_DEG_LAT, 0.5 * span / mpdl);
    let mut ways = Vec::new();
    for i in 0..6 {
        let t = i as f64 / 5.0 - 0.5;
        let line: Vec<(f64, f64)> = (0..10)
            .map(|k| (clon - half_lon + 2.0 * half_lon * k as f64 / 9.0, clat + 2.0 * half_lat * t))
            .collect();
        ways.push(Way::line(i, "road", line, egui::Color32::GRAY, 2.0));
    }
    ways.extend(Way::from_lonlat(
        99,
        &ring(clon, clat, half_lon * 0.1, half_lat * 0.1),
        &tags(&[("natural", "water")]),
        &theme,
    ));

    let v = view((clon, clat), span);
    let old = Map3D::from_geometry_in_view_roofed("town", &ways, Some(v));
    let new = Map3D::from_viewport_roofed("continuous", &ways, v, extrude::SceneBudget::default());
    println!(
        "open country — OLD: {} tiles, {} tris\n               NEW: {} tiles, {} tris, {:?}",
        old.tile_count(),
        old.triangle_count(),
        new.tile_count(),
        new.triangle_count(),
        new.viewport_scene(),
    );

    // The old path drew the terrain quad and nothing else — `tile_count <= 1` is
    // `Map3D::view`'s own definition of "nothing to extrude".
    assert_eq!(old.tile_count(), 1, "this fixture must reproduce the empty pane");
    // The new one draws the roads and the water that ARE there.
    assert!(new.tile_count() > 1, "the ground must be drawn: {} tiles", new.tile_count());
    assert!(new.triangle_count() > 100, "…and be real geometry: {} tris", new.triangle_count());
    let s = new.viewport_scene().unwrap();
    assert!(s.lines_full + s.lines_coarse + s.lines_ground > 0, "the roads must survive: {s:?}");
    assert_eq!(s.buildings_full, 0, "there are no houses here — that part was never in doubt");

    // …and the refusal now says WHICH fact it is.
    let r = new.town_refused().expect("the choice still reports");
    assert!(r.no_buildings_at_all(), "zero buildings, not a thin sample");
    let msg = r.message();
    println!("refusal: {msg}");
    assert!(!msg.contains("Zoom in"), "must not tell the operator to do the impossible: {msg}");
    assert!(msg.contains("no buildings"), "must say which fact it is: {msg}");

    // The other fact still reads the other way: a real city sampled too thinly.
    let sparse = extrude::TownRefusal { half_m: 580.0, buildings: 7, needed: 24, span_m: 37_042.0 };
    assert!(!sparse.no_buildings_at_all());
    assert!(sparse.message().contains("Zoom in"), "{}", sparse.message());
}

/// **THE CLAIM: the falloff is a falloff.** Detail must be monotone in distance — a
/// nearer building can never be coarser than a farther one, or the "fade" is a
/// checkerboard of whatever happened to be cheap.
#[test]
fn detail_never_climbs_back_with_distance() {
    // HEXAGONAL footprints, deliberately: a rectangular one IS its own bounding box, so a
    // shape test over the `houses` grid cannot tell `Detail::Full` from `Detail::Coarse`
    // and the assertion below would be vacuously true (it was, on the first run).
    let ways = hex_houses(1, STOCKHOLM, 200, 30.0);
    let v = view(STOCKHOLM, KORP_Z12_SPAN_M);
    let scene =
        extrude::viewport_scene(&ways, v, extrude::SceneBudget { triangles: 200_000, ..Default::default() });
    let s = scene.stats;
    assert!(s.full_radius_m < s.coarse_radius_m, "the bands must nest: {s:?}");
    assert!(s.coarse_radius_m <= s.ground_radius_m, "…inside the reach: {s:?}");

    // A `Detail::Coarse` building is its bounding BOX, so its ring has exactly two
    // distinct longitudes and two distinct latitudes; a full-detail one is the tiler's
    // own ring. Compared by shape rather than by vertex count, because the fixture's
    // footprints are already rectangles and the counts are equal.
    let boxy = |w: &Way| {
        let mut lons: Vec<f64> = w.lonlat.iter().map(|p| p.0).collect();
        let mut lats: Vec<f64> = w.lonlat.iter().map(|p| p.1).collect();
        lons.sort_by(f64::total_cmp);
        lats.sort_by(f64::total_cmp);
        lons.dedup_by(|a, b| (*a - *b).abs() < 1e-12);
        lats.dedup_by(|a, b| (*a - *b).abs() < 1e-12);
        lons.len() <= 2 && lats.len() <= 2
    };
    // The distance is looked up on the SOURCE way by `osm_id`, not recomputed from the
    // kept ring: a reduced ring has a slightly different centroid (a hexagon's bounding
    // box is not centred on the hexagon's vertex-average), and comparing a walk ordered by
    // one centroid against a test ordered by another produces a metre-scale false alarm at
    // the band boundary — which is exactly what the first run of this test reported.
    let dist_of: std::collections::HashMap<i64, f64> = ways
        .iter()
        .map(|w| {
            let n = w.lonlat.len() as f64;
            let c = w.lonlat.iter().fold((0.0, 0.0), |(a, b), &(x, y)| (a + x, b + y));
            (w.osm_id, extrude::geo_distance_m((v.lon, v.lat), (c.0 / n, c.1 / n)))
        })
        .collect();
    let mut nearest_box = f64::INFINITY;
    let mut farthest_full = 0.0f64;
    for w in scene.ways.iter().filter(|w| w.kind == "building") {
        let d = dist_of[&w.osm_id];
        if boxy(w) {
            nearest_box = nearest_box.min(d);
        } else {
            farthest_full = farthest_full.max(d);
        }
    }
    assert!(nearest_box.is_finite(), "the fixture must actually exhaust the full-detail budget");
    println!("full detail out to {farthest_full:.0} m; first box at {nearest_box:.0} m; bands {s:?}");
    assert!(
        farthest_full <= nearest_box + 1.0,
        "a full-detail house at {farthest_full:.0} m sits BEYOND a boxed one at \
         {nearest_box:.0} m — the falloff is not monotone",
    );
}

/// **The throughput reading [`extrude::VIEWPORT_TRIANGLE_BUDGET`] is derived from.** Not
/// an assertion with a threshold — a machine number, printed, so the constant beside it
/// can be re-derived rather than believed. DEBUG profile (see the module docs).
#[test]
fn throughput() {
    for side in [40usize, 80, 120] {
        let mut ways = houses(1, STOCKHOLM, side, 30.0);
        let mpdl = m_per_deg_lon(STOCKHOLM.1);
        for (n, r) in (0..side).step_by(4).enumerate() {
            let dy = (r as f64 - side as f64 * 0.5) * 30.0;
            let line: Vec<(f64, f64)> = (0..40)
                .map(|k| {
                    let dx = (k as f64 / 39.0 - 0.5) * side as f64 * 30.0;
                    (STOCKHOLM.0 + dx / mpdl, STOCKHOLM.1 + dy / M_PER_DEG_LAT)
                })
                .collect();
            ways.push(Way::line(900_000 + n as i64, "road", line, egui::Color32::GRAY, 2.0));
        }
        let t0 = std::time::Instant::now();
        let (mesh, ..) = extrude::build_with_water_terrain_styled(
            &ways,
            0.22,
            extrude::GroundTerrain::Flat,
            extrude::RoofStyle::Pitched,
        );
        let ms = t0.elapsed().as_secs_f64() * 1e3;
        let bytes =
            mesh.positions.len() * 24 + mesh.indices.len() * 4 + mesh.colors.len() * 4 + mesh.building.len();
        println!(
            "side {side:>4}: {:>7} ways -> {:>9} tris in {ms:>7.1} ms = {:>5.2} M tris/s (DEBUG), \
             {:.0} bytes/triangle",
            ways.len(),
            mesh.triangle_count(),
            mesh.triangle_count() as f64 / (ms / 1e3) / 1e6,
            bytes as f64 / mesh.triangle_count() as f64,
        );
    }
}

/// **THE CLAIM korp's `the_zoom_slider_moves_the_3d_scene` is really about: zooming in
/// brings the scene DOWN among the houses — and on this path that is the CAMERA, not the
/// design unit.**
///
/// # Why the mechanism moved, and what the honest reading is
///
/// On the town path the zoom is expressed in the **data**: [`extrude::view_town_subset`]
/// clips the geometry to [`extrude::town_half_for_span`] (580 m at z12, 253 m at z15),
/// the design box is fitted to whatever survives, and `metres_per_design_unit` therefore
/// tracks the zoom. That is a true statement about that mechanism and korp asserts it.
///
/// This path does not use it. The window is [`extrude::scene_reach_half_m`] — kilometres,
/// not hundreds of metres — so a town SMALLER THAN THE REACH is never clipped and the
/// design box does not move at all. Reproduced below on korp's own fixture (a 892 m town
/// plus one 37 km trunk road): the reach is 8 292 m at z12 and 2 315 m at z15, the 892 m
/// town fits inside both, and `m/du` reads **557.5 at both**. Nothing is broken; the
/// quantity simply stopped being the one that carries the zoom.
///
/// `m/du` still tracks wherever the reach really bites — a region-scale ways set goes
/// 10 365 → 2 894 m/du over the same two rungs
/// (`the_scene_is_the_viewport_and_the_near_band_is_legible`). It is no longer the
/// quantity that is *guaranteed* to.
///
/// What is guaranteed is [`extrude::ViewportStats::camera_distance_m`], published under
/// `$KORP_STATE.map.map3d.viewport.camera_distance_m`. This test asserts it on korp's
/// fixture, in korp's units, so korp's test has a reading to point at.
#[test]
fn zooming_in_brings_the_camera_down_among_the_houses() {
    // korp's fixture verbatim: a DENSE 45x45 grid of 12 m houses on a 20 m pitch (a
    // ~892 m town), plus one trunk road across the whole z12 viewport.
    let (clon, clat) = STOCKHOLM;
    let m = m_per_deg_lon(clat);
    let theme = Theme::default();
    let mut ways: Vec<Way> = Vec::new();
    let mut id = 1_000i64;
    for j in 0..45 {
        for i in 0..45 {
            let dx = (f64::from(i) - 22.0) * 20.0;
            let dy = (f64::from(j) - 22.0) * 20.0;
            ways.extend(Way::from_lonlat(
                id,
                &ring(clon + dx / m, clat + dy / M_PER_DEG_LAT, 6.0 / m, 6.0 / M_PER_DEG_LAT),
                &tags(&[("building", "yes")]),
                &theme,
            ));
            id += 1;
        }
    }
    let half_lon = 0.5 * KORP_Z12_SPAN_M / m;
    ways.push(Way::line(
        9,
        "road",
        vec![(clon - half_lon, clat), (clon + half_lon, clat)],
        egui::Color32::GRAY,
        2.0,
    ));
    let town_span = extrude::world_span_metres(&ways[..ways.len() - 1]).expect("a town");
    println!("korp's fixture: {} ways, town span {town_span:.0} m", ways.len());

    let at = |z: f64| {
        let v = view(STOCKHOLM, span_at(z));
        let map = Map3D::from_viewport_roofed("korp fixture", &ways, v, extrude::SceneBudget::default());
        let s = map.viewport_scene().unwrap();
        let (mdu, p50) = (map.metres_per_design_unit(), map.skyline_percentile_px(1150.0, 0.5));
        println!(
            "z{z:>4}: reach {:>8.0} m, town_half {:>6.0} m | m/du {mdu:>7.1}  tris {:>7}  \
             camera {:>7.0} m  frame {:>6.0} m  p50 {p50:>6.2} px",
            s.ground_radius_m,
            map.town_window().map(|w| w.half_m).unwrap_or(0.0),
            map.triangle_count(),
            s.camera_distance_m,
            s.frame_extent_m,
        );
        (map, s, f64::from(mdu), f64::from(p50))
    };
    let (wide_map, wide, wide_mdu, wide_px) = at(12.0);
    let (deep_map, deep, deep_mdu, deep_px) = at(15.0);

    // The DIAGNOSIS, asserted so it cannot quietly stop being the explanation: the town is
    // smaller than the reach at BOTH rungs, which is exactly why the design box is fixed.
    assert!(
        f64::from(town_span) < wide.ground_radius_m && f64::from(town_span) < deep.ground_radius_m,
        "this fixture's town ({town_span:.0} m) must fit inside the reach at both rungs \
         ({:.0} m, {:.0} m) — that is the premise of everything below",
        wide.ground_radius_m,
        deep.ground_radius_m,
    );
    assert!(
        (wide_mdu - deep_mdu).abs() < 1.0,
        "…and therefore `metres_per_design_unit` is CONSTANT here by construction, not by \
         accident: {wide_mdu:.1} → {deep_mdu:.1}. If this ever fails the diagnosis above is \
         stale and korp's own assertion may be assertable again.",
    );

    // The town decision is still asked, still published, and still tracks the zoom — so a
    // consumer reading `town_window.half_m` gets the same ladder it always did.
    let (hw, hd) = (
        wide_map.town_window().expect("the choice still reports").half_m,
        deep_map.town_window().expect("the choice still reports").half_m,
    );
    assert!(
        (hw - extrude::town_half_for_span(span_at(12.0))).abs() < 1.0
            && (hd - extrude::town_half_for_span(span_at(15.0))).abs() < 1.0,
        "`town_window` must still be the ladder's answer: {hw:.1} / {hd:.1}",
    );
    assert!(hd < hw * 0.8, "…and it must still move with the zoom: {hw:.1} → {hd:.1}");

    // **THE CLAIM.** The camera comes down, by the magnitude korp's test asks of the scene.
    assert!(
        deep.camera_distance_m < wide.camera_distance_m * 0.8,
        "zooming in must bring the camera DOWN among the houses: {:.0} m → {:.0} m \
         ({:.3}x)",
        wide.camera_distance_m,
        deep.camera_distance_m,
        deep.camera_distance_m / wide.camera_distance_m,
    );
    // …and it is not a number in a struct: the houses really do get bigger on the pane.
    assert!(
        deep_px > wide_px * 1.25,
        "…and the applied skyline must grow with it: {wide_px:.2} px → {deep_px:.2} px",
    );
}