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

//! **The triangle budget must be a BOUND, not a caption.**
//!
//! # The defect, from a real drive
//!
//! 41 consecutive `korp::map3d` DRAW lines from the owner driving korp-ui against oden on
//! 2026-08-11 reported **890 073 / 896 446 / 897 748 / 903 252** estimated triangles
//! against a stated **600 000** budget — consistently ~1.5x over, on every single draw.
//! The falloff was working (22 682 buildings dropped past the boxes band, bands at 1.1 km
//! and 1.8 km); it simply stopped tightening while the scene was still over, and the line
//! printed the over-budget number as if it were fine.
//!
//! [`extrude::SceneBudget`] was introduced as the thing that makes the scene
//! "self-limiting, so it degrades smoothly on a weak GPU instead of falling off a cliff".
//! A bound that is printed and not honoured reads as a guarantee and is not one.
//!
//! # The cause, MEASURED here rather than reasoned
//!
//! `viewport_scene`'s three bands enforce the budget at the two transitions between them
//! and then stop: past the coarse band everything left is admitted **unconditionally**, so
//! the scene costs `budget + the whole ground band`. `extrude.rs` justified that as "a
//! bounded cost — a `Detail::Ground` line is two vertices", which counted VERTICES. A
//! two-vertex ROAD costs 54 triangles: [`extrude::estimated_triangles`]'s three
//! `ribbon_rounded` passes (glow, casing, fill), each with an eight-segment join fan at
//! each end. A city viewport holds ~15 000 line ways.
//!
//! The first run of `the_estimate_lands_inside_the_budget` on the pre-fix crate, on the
//! fixture below (the log's own counts):
//!
//! ```text
//! span 6200 m: est 1357801 / budget 600000 (2.26x)
//!              | buildings 97182  ground 68425  lines 1192194
//!              | full 1701 coarse 2186 dropped 28874 | lines 14641 cover 2975
//! ```
//!
//! **88 % of the scene was lines**, and they were already decimated to two vertices — no
//! amount of further tightening of the BANDS could have reached them. The cost was in the
//! passes, not in the geometry the falloff was pulling on.
//!
//! # What the fix is, and how each half is watched
//!
//! * [`extrude::GROUND_ROAD_KIND`] — a road in the ground band drops to one pass. Delete
//!   the `out.kind = GROUND_ROAD_KIND` line in `into_detail` and
//!   `the_ground_band_road_is_one_pass` goes red, and the budget assertion follows it.
//! * `ViewportStats::ground_floor` is reserved BEFORE the walk, so the detail bands are
//!   chosen inside `budget − floor`. Restore `spent >= budget.triangles` as the
//!   Coarse→Ground test and `the_estimate_lands_inside_the_budget` goes red.
//! * `ViewportStats::budget_met` — the honest verdict when it genuinely cannot fit.
//!   `a_budget_under_the_floor_says_so` drives that case on purpose.

use facett_core::Theme;
use facett_map::Way;
use facett_map3d::extrude;

/// 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);
const KORP_Z12_SPAN_M: f64 = 37_042.0;

use facett_map3d::extrude::m_per_deg_lon;

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

/// A wobbly closed ring of `n` vertices, `r_m` across — a lake or a landuse polygon as
/// the tiler actually delivers one (OSM ground cover is not a rectangle).
fn blob(centre: (f64, f64), r_m: f64, n: usize) -> Vec<(f64, f64)> {
    let (clon, clat) = centre;
    let mpdl = m_per_deg_lon(clat);
    let mut v: Vec<(f64, f64)> = (0..n)
        .map(|k| {
            let a = std::f64::consts::TAU * k as f64 / n as f64;
            let r = r_m * (0.7 + 0.3 * (a * 3.0).sin());
            (clon + r * a.cos() / mpdl, clat + r * a.sin() / M_PER_DEG_LAT)
        })
        .collect();
    v.push(v[0]);
    v
}

/// The viewport the DRAW line was taken over, in the counts the line itself reports:
/// **32 694 buildings** (4 540 full + 5 472 boxed + 22 682 dropped), **14 713 line ways**
/// and **2 974 ground-cover rings**, inside a **6.2 km** viewport whose reach is the whole
/// of it (`scene 3.1 km radius (ALL of the 6 km viewport)`).
const LOG_SPAN_M: f64 = 6_200.0;

/// **A viewport of the shape the real drive produced** — a city's worth of small ways, not
/// one lake and a road grid. The counts are the log's own.
fn swedish_viewport() -> Vec<Way> {
    let (clon, clat) = STOCKHOLM;
    let mpdl = m_per_deg_lon(clat);
    let theme = Theme::default();
    let mut ways: Vec<Way> = Vec::new();
    let mut id = 1_000i64;
    // 181x181 = 32 761 eight-cornered 12 m houses on a 34 m pitch — a solid 6.2 km city.
    let hex = |cx: f64, cy: f64| -> Vec<(f64, f64)> {
        let mut v: Vec<(f64, f64)> = (0..8)
            .map(|k| {
                let a = std::f64::consts::TAU * k as f64 / 8.0;
                (cx + 6.0 * a.cos() / mpdl, cy + 6.0 * a.sin() / M_PER_DEG_LAT)
            })
            .collect();
        v.push(v[0]);
        v
    };
    for j in 0..181 {
        for i in 0..181 {
            let dx = (f64::from(i) - 90.0) * 34.0;
            let dy = (f64::from(j) - 90.0) * 34.0;
            ways.extend(Way::from_lonlat(
                id,
                &hex(clon + dx / mpdl, clat + dy / M_PER_DEG_LAT),
                &tags(&[("building", "yes")]),
                &theme,
            ));
            id += 1;
        }
    }
    // 14 700 street ways — the OSM road network at city density (a "way" is a block, not a
    // highway). **Every seventh is a TWO-NODE way**, because OSM is full of them (a
    // driveway, a service spur, a bridge segment) and they are the one shape `into_detail`
    // short-circuits on: below three vertices it hands the way back untouched, so a
    // two-node road is never demoted to `GROUND_ROAD_KIND` and keeps all three ribbon
    // passes. A fixture of 12-vertex ways only would never exercise it, and
    // `the_floor_is_exactly_what_the_ground_band_costs` is the assertion that needs it.
    for k in 0..14_700i64 {
        let (gx, gy) = (k % 121, k / 121);
        let dx = (gx as f64 - 60.0) * 51.0;
        let dy = (gy as f64 - 60.0) * 51.0;
        let (cx, cy) = (clon + dx / mpdl, clat + dy / M_PER_DEG_LAT);
        let n = if k % 7 == 0 { 2 } else { 12 };
        let line: Vec<(f64, f64)> = (0..n)
            .map(|v| {
                let t = f64::from(v) / f64::from(n - 1);
                if k % 2 == 0 {
                    (cx + t * 51.0 / mpdl, cy)
                } else {
                    (cx, cy + t * 51.0 / M_PER_DEG_LAT)
                }
            })
            .collect();
        ways.push(Way::line(id, "road", line, egui::Color32::GRAY, 2.0));
        id += 1;
    }
    // 2 975 ground-cover rings of 25 vertices — lakes, parks, landuse.
    for k in 0..2_975i64 {
        let (gx, gy) = (k % 55, k / 55);
        let dx = (gx as f64 - 27.0) * 112.0;
        let dy = (gy as f64 - 27.0) * 112.0;
        let c = (clon + dx / mpdl, clat + dy / M_PER_DEG_LAT);
        let tag = if k % 2 == 0 { ("natural", "water") } else { ("leisure", "park") };
        ways.extend(Way::from_lonlat(id, &blob(c, 45.0, 25), &tags(&[tag]), &theme));
        id += 1;
    }
    ways
}

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

/// Split the kept scene's cost by what it is, so "over budget" has a cause and not just a
/// number.
fn breakdown(scene: &extrude::ViewportScene) -> (usize, usize, usize) {
    let (mut b, mut g, mut l) = (0usize, 0usize, 0usize);
    for w in &scene.ways {
        let t = extrude::estimated_triangles(w, extrude::RoofStyle::Pitched);
        if w.kind == "building" {
            b += t;
        } else if matches!(w.kind, "water" | "park" | "coastline") {
            g += t;
        } else {
            l += t;
        }
    }
    (b, g, l)
}

/// **THE CLAIM: the estimate lands inside the budget, on the viewport that blew it.**
///
/// Not "near it" and not "a little over": the number the line prints must be the number
/// the budget bounds. And the scene must still HAVE a near band — a bound satisfied by
/// drawing nothing is the cliff the budget exists to prevent.
#[test]
fn the_estimate_lands_inside_the_budget() {
    let ways = swedish_viewport();
    println!("fixture: {} ways", ways.len());
    let budget = extrude::SceneBudget::default();
    for span in [LOG_SPAN_M, KORP_Z12_SPAN_M, KORP_Z12_SPAN_M / 4.0] {
        let scene = extrude::viewport_scene(&ways, view(span), budget);
        let s = scene.stats;
        let (b, g, l) = breakdown(&scene);
        println!(
            "span {:>7.0} m: est {:>9} / budget {:>7} ({:.2}x) met {:<5} floor {:>7} | buildings {:>8} \
             ground {:>7} lines {:>8} | full {} coarse {} dropped {} | lines {} cover {} | reach {:.0} m \
             full_r {:.0} coarse_r {:.0}",
            span,
            s.triangles,
            s.budget,
            s.triangles as f64 / s.budget as f64,
            s.budget_met,
            s.ground_floor,
            b,
            g,
            l,
            s.buildings_full,
            s.buildings_coarse,
            s.buildings_dropped,
            s.lines_full + s.lines_coarse + s.lines_ground,
            s.ground_cover,
            s.ground_radius_m,
            s.full_radius_m,
            s.coarse_radius_m,
        );
        assert!(
            s.triangles <= s.budget,
            "span {span:.0}: the estimate must land INSIDE the budget: {} against {} \
             (floor {}, met {})",
            s.triangles,
            s.budget,
            s.ground_floor,
            s.budget_met,
        );
        // …and the verdict must AGREE with the arithmetic, or it is a second number that
        // can drift from the first.
        assert_eq!(s.budget_met, s.triangles <= s.budget, "span {span:.0}: `budget_met` must be the reading");
        // The floor is real and it is the dominant term — if this ever reads 0 the
        // reservation above is inert and the test below it proves nothing.
        assert!(s.ground_floor > 0, "span {span:.0}: the unconditional ground has a price");
        // The bound is not met by emptying the scene: the near band still stands up.
        assert!(
            s.buildings_full >= extrude::MIN_BUILDINGS_IN_VIEW,
            "span {span:.0}: fitting the budget must not cost the skyline — {} full houses",
            s.buildings_full,
        );
        // …and the pane is still continuous: the ground band survives past the coarse one.
        assert!(
            s.lines_ground > 0 && s.ground_cover > 0,
            "span {span:.0}: the ground must still reach the border — {} ground lines, {} cover",
            s.lines_ground,
            s.ground_cover,
        );
    }
}

/// **THE CLAIM: the ground band's road is ONE ribbon pass, and the estimator knows it.**
///
/// This is the half the band-tightening could never reach: the lines were already at two
/// vertices when the scene was 2.26x over. RED against the pre-fix crate — every kept road
/// there is `kind == "road"` and costs 54.
#[test]
fn the_ground_band_road_is_one_pass() {
    let ways = swedish_viewport();
    let scene = extrude::viewport_scene(&ways, view(LOG_SPAN_M), extrude::SceneBudget::default());
    let far: Vec<&Way> = scene.ways.iter().filter(|w| w.kind == extrude::GROUND_ROAD_KIND).collect();
    let near: Vec<&Way> = scene.ways.iter().filter(|w| w.kind == "road").collect();
    println!(
        "{} roads at full/coarse detail, {} demoted to {} in the ground band",
        near.len(),
        far.len(),
        extrude::GROUND_ROAD_KIND,
    );
    assert!(!far.is_empty(), "the ground band must actually contain roads, or nothing was demoted");
    for w in &far {
        assert_eq!(w.lonlat.len(), 2, "a ground-band line keeps its two ends");
        assert_eq!(
            extrude::estimated_triangles(w, extrude::RoofStyle::Pitched),
            18,
            "one pass over two vertices — the three-pass ribbon is 54",
        );
    }
    // …and a near road is still the full car-navigator ribbon. Without this the test is
    // satisfied by demoting EVERY road, which would be a different (and wrong) change.
    assert!(!near.is_empty(), "roads near the camera must keep their casing and glow");
}

/// **THE CLAIM: the reserved floor is EXACTLY what the ground band costs — not an
/// estimate of it, and in particular not an under-estimate.**
///
/// `ViewportStats::ground_floor` is subtracted from the budget before the walk spends
/// anything, so it is load-bearing in one direction: reserve too little and the walk is
/// handed a budget it cannot honour, which is the original defect wearing a smaller number.
/// It is computed in closed form (no allocation per candidate way), and this is what stops
/// the closed form from drifting from the reduction it predicts — the same way round
/// `the_estimate_is_the_mesh` stops the estimate drifting from the mesh.
///
/// Driven at the 37 km span, where every way in the fixture is a candidate, so the sum over
/// the fixture is the sum over the candidates.
///
/// **RED against the floor as first written**: it returned a flat `18` for every line, and
/// `into_detail` short-circuits on `lonlat.len() < 3` — so the fixture's 2 100 two-node
/// roads keep three ribbon passes and cost 54 apiece. Confirmed by restoring the flat `18`:
/// the floor comes out 75 600 (2 100 × 36) below the ground band it is reserving for.
#[test]
fn the_floor_is_exactly_what_the_ground_band_costs() {
    let ways = swedish_viewport();
    let two_node = ways.iter().filter(|w| w.kind == "road" && w.lonlat.len() == 2).count();
    assert!(two_node > 0, "the fixture must contain the shape `into_detail` short-circuits on");

    let scene = extrude::viewport_scene(&ways, view(KORP_Z12_SPAN_M), extrude::SceneBudget::default());
    let candidates = scene.stats.buildings_full
        + scene.stats.buildings_coarse
        + scene.stats.buildings_dropped
        + scene.stats.lines_full
        + scene.stats.lines_coarse
        + scene.stats.lines_ground
        + scene.stats.ground_cover;
    assert_eq!(candidates, ways.len(), "at this span every way is a candidate, so the two sums are over one set");

    let costed: usize = ways
        .iter()
        .map(|w| {
            extrude::at_detail(w, extrude::Detail::Ground)
                .map_or(0, |g| extrude::estimated_triangles(&g, extrude::RoofStyle::Pitched))
        })
        .sum();
    println!("{two_node} two-node roads; floor {} vs ground band {costed}", scene.stats.ground_floor);
    assert_eq!(
        scene.stats.ground_floor, costed,
        "the reserved floor must BE the ground band's price, not an approximation of it",
    );
}

/// **THE CLAIM: when the scene genuinely cannot fit, it says so — it does not print an
/// over-budget number as if it were fine.**
///
/// Driven on purpose with a budget below the floor: the unconditional ground alone costs
/// more than the host allowed, so there is nowhere left for the falloff to go.
#[test]
fn a_budget_under_the_floor_says_so() {
    let ways = swedish_viewport();
    let probe = extrude::viewport_scene(&ways, view(LOG_SPAN_M), extrude::SceneBudget::default());
    let floor = probe.stats.ground_floor;
    assert!(probe.stats.budget_met, "the default budget is met on this viewport (floor {floor})");

    let tiny = extrude::SceneBudget { triangles: floor / 2, ..Default::default() };
    let s = extrude::viewport_scene(&ways, view(LOG_SPAN_M), tiny).stats;
    println!("floor {floor}, budget {}: est {} met {}", tiny.triangles, s.triangles, s.budget_met);
    assert!(s.triangles > s.budget, "this budget is under the floor by construction");
    assert!(
        !s.budget_met,
        "a scene that cannot fit must REPORT that it did not fit: {} against {}",
        s.triangles,
        s.budget,
    );
    assert_eq!(s.ground_floor, floor, "the floor is a property of the viewport, not of the budget");
    // The pane is not blanked to satisfy an impossible budget — that is the whole reason
    // the floor is unconditional in the first place.
    assert!(s.lines_ground > 0, "the ground still reaches the border");
}