Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-geomap/tests/callchain_matrix.rs

//! **Render call-chain coverage matrix** for `facett-geomap` (LAW: every fn in
//! the input→pixel chain TRACKED + VERIFIED; return-asserted where it proves
//! correctness, EMIT where it can't).
//!
//! The 2D OSM-viewer chain (`OsmView`):
//!
//! ```text
//!   read points/ways (lon,lat)
//!     → MercatorCamera               (to_px / to_merc / viewport_merc_bbox)
//!     → zoom-to-cursor / pan / fit   (zoom_to / pan_px / fit_merc_bbox)
//!     → PointGrid cull + LOD stride  (build / visible_count / visible_into)
//!     → MapIndex LOD-tier + bbox cull (way_lod / query_visible)
//!     → point-stride budget          (point_stride / budget_stride)
//!     → clip to widget rect          (clip::clip_polyline / clip_polygon)
//!     → paint fills+lines+points     (Facet::ui CPU painter)
//!     → pixels                       (egui_kittest robot drive)
//! ```
//!
//! ~1000 matrix cases: zoom levels × extents × LOD tiers × canvas sizes ×
//! datasets, fed real WKB/GeoParquet-shaped `(lon,lat)` input, asserting at each
//! stage. EMIT-doctrine rows cover the rasterized output the return can't expose.
//!
//! Run: `cargo test -p facett-geomap --test callchain_matrix`

use egui::{Rect, pos2, vec2};
use facett_core::harness;
use facett_geomap::{MercatorCamera, OsmView, PointGrid, mercator};

// ── matrix axes ───────────────────────────────────────────────────────────────

const ZOOMS: &[f64] = &[128.0, 1_000.0, 4_000.0, 20_000.0, 100_000.0, 1_000_000.0];
const EXTENTS: &[f64] = &[8.0, 2.0, 0.5, 0.1, 0.02];
const CANVASES: &[(f32, f32)] = &[(200.0, 150.0), (400.0, 300.0), (800.0, 600.0), (1280.0, 720.0), (300.0, 900.0)];
const CENTRES: &[(f64, f64)] = &[
    (0.0, 0.0),
    (9.55, 47.15),
    (18.07, 59.33),
    (-122.4, 37.8),
    (139.7, 35.7),
    (151.2, -33.9),
    (-43.2, -22.9),
    (37.6, 55.75),
];

fn rect(w: f32, h: f32) -> Rect {
    Rect::from_min_size(pos2(0.0, 0.0), vec2(w, h))
}

/// A deterministic point cloud of `n` points in an `ext`-degree box at a centre.
fn cloud(lon: f64, lat: f64, ext: f64, n: usize) -> Vec<(f64, f64)> {
    let mut s: u64 = 0x9E3779B97F4A7C15 ^ (lon.to_bits() ^ lat.to_bits());
    let mut next = || {
        s ^= s << 13;
        s ^= s >> 7;
        s ^= s << 17;
        (s >> 11) as f64 / (1u64 << 53) as f64
    };
    (0..n).map(|_| (lon - ext / 2.0 + next() * ext, lat - ext / 2.0 + next() * ext)).collect()
}

// ── stage 1+2: MercatorCamera projection + zoom-to-cursor anchor invariance ────

/// INJECT-ASSERT — the camera's RETURN proves correctness: `to_px`/`to_merc`
/// round-trip exactly, `viewport_merc_bbox` brackets the rect corners, and
/// zoom-to-cursor keeps the anchored geo point FIXED on screen (the slippy-map
/// standard). Walked over zooms × centres × canvases × anchors.
#[test]
fn camera_roundtrip_viewport_and_anchor_invariance() {
    let mut cases = 0usize;
    let mut ok = 0usize;
    for &(cw, ch) in CANVASES {
        let r = rect(cw, ch);
        for &z in ZOOMS {
            for &(lon, lat) in CENTRES {
                let (mx, my) = mercator(lon, lat);
                let cam = MercatorCamera { center: (mx, my), zoom: z, target_zoom: z, pitch_deg: 0.0 };
                // round-trip px↔merc
                for &anchor in &[r.center(), r.min, pos2(cw * 0.75, ch * 0.25)] {
                    let m = cam.to_merc(anchor, r);
                    let back = cam.to_px(m.0, m.1, r);
                    assert!((back.x - anchor.x).abs() < 1e-2 && (back.y - anchor.y).abs() < 1e-2, "px↔merc roundtrip");
                }
                // viewport bbox brackets the rect's projected corners
                let vp = cam.viewport_merc_bbox(r);
                let a = cam.to_merc(r.min, r);
                let b = cam.to_merc(r.max, r);
                assert!(vp[0] <= a.0.min(b.0) as f32 + 1e-4 && vp[2] >= a.0.max(b.0) as f32 - 1e-4, "vp x brackets");
                // zoom-to-cursor: the anchor geo point is unchanged after a zoom.
                // The feature's contract holds while the camera centre is not
                // clamped at the world edge (the [0,1] clamp can't compensate past
                // the edge — an expected, documented limit), so we only assert
                // fixity in the unclamped regime and skip the edge cells.
                let mut c2 = cam;
                let anchor = pos2(cw * 0.7, ch * 0.3);
                let before = c2.to_merc(anchor, r);
                c2.zoom_to(2.0, anchor, r);
                let clamped = c2.center.0 <= 1e-9 || c2.center.0 >= 1.0 - 1e-9 || c2.center.1 <= 1e-9 || c2.center.1 >= 1.0 - 1e-9;
                assert!(c2.zoom >= cam.zoom, "zoom_to(2) did not shrink zoom");
                if clamped {
                    continue; // edge cell — the clamp legitimately moves the anchor
                }
                let after = c2.to_merc(anchor, r);
                // tolerance scaled to ~1px at this zoom (merc units = px / zoom).
                let tol = (2.0 / c2.zoom).max(1e-6);
                let fixed = (before.0 - after.0).abs() < tol && (before.1 - after.1).abs() < tol;
                assert!(fixed, "zoom anchored (tol {tol:.2e}): {before:?} vs {after:?}");
                cases += 1;
                if fixed {
                    ok += 1;
                }
            }
        }
    }
    let total = CANVASES.len() * ZOOMS.len() * CENTRES.len();
    assert!(cases > total / 2, "most cells in the unclamped regime were tested: {cases}/{total}");
    assert_eq!(ok, cases, "every unclamped cell kept the anchor fixed");
    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-geomap.camera",
        "roundtrip_viewport_and_anchor_fixed",
        ok == cases,
        &format!("{ok}/{cases} unclamped camera cases (zoom-to-cursor anchored, {total} total)"),
    );
}

/// INJECT-ASSERT — `fit_merc_bbox` returns a camera whose viewport CONTAINS the
/// fitted data bbox (with margin) and whose centre is the bbox centre. Walked
/// over extents × centres × canvases.
#[test]
fn fit_merc_bbox_contains_the_data() {
    let mut cases = 0usize;
    let mut ok = 0usize;
    for &(cw, ch) in CANVASES {
        let r = rect(cw, ch);
        for &(lon, lat) in CENTRES {
            for &ext in EXTENTS {
                let (x0, y0) = mercator(lon - ext / 2.0, lat + ext / 2.0);
                let (x1, y1) = mercator(lon + ext / 2.0, lat - ext / 2.0);
                let mut cam = MercatorCamera::default();
                cam.fit_merc_bbox([x0, y0, x1, y1], r);
                // centre is the bbox centre
                assert!((cam.center.0 - (x0 + x1) / 2.0).abs() < 1e-6, "centre x = bbox mid");
                // the fitted viewport contains the data bbox
                let vp = cam.viewport_merc_bbox(r);
                let contains = vp[0] as f64 <= x0 + 1e-6 && vp[1] as f64 <= y0 + 1e-6 && vp[2] as f64 >= x1 - 1e-6 && vp[3] as f64 >= y1 - 1e-6;
                assert!(contains, "fitted viewport {vp:?} contains data [{x0},{y0},{x1},{y1}]");
                cases += 1;
                if contains {
                    ok += 1;
                }
            }
        }
    }
    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-geomap.camera",
        "fit_contains_data",
        ok == cases,
        &format!("{ok}/{cases} fit cases"),
    );
    assert_eq!(ok, cases, "every case in the sweep passed ({ok}/{cases})");
}

// ── stage 3: PointGrid cull + LOD decimation ───────────────────────────────────

/// INJECT-ASSERT — `PointGrid::visible_count`'s RETURN is exact (a tight bbox
/// sees only the contained points; the whole-world bbox sees all), and
/// `visible_into` with a stride keeps ⌈visible/stride⌉ points and is monotone:
/// a bigger stride keeps no more. Walked over centres × extents × strides.
#[test]
fn point_grid_cull_is_exact_and_stride_is_monotone() {
    let mut cases = 0usize;
    let mut ok = 0usize;
    for &(lon, lat) in CENTRES {
        for &ext in EXTENTS {
            let pts = cloud(lon, lat, ext, 1_000);
            let grid = PointGrid::build(pts.clone());
            assert_eq!(grid.len(), 1_000);
            // whole-world sees all
            assert_eq!(grid.visible_count([0.0, 0.0, 1.0, 1.0]), 1_000, "whole world sees all");
            // a tight box around the centre sees a subset, and visible_into agrees
            let (mx, my) = mercator(lon, lat);
            let d = (ext * 0.25 / 360.0) as f32;
            let vp = [mx as f32 - d, my as f32 - d, mx as f32 + d, my as f32 + d];
            let n = grid.visible_count(vp);
            assert!(n <= 1_000);
            // stride monotonicity + count formula
            let mut prev = usize::MAX;
            for stride in [1usize, 2, 4, 16] {
                let mut out = Vec::new();
                grid.visible_into(vp, stride, &mut out);
                // kept == ⌈n/stride⌉ (the grid keeps every stride-th in-rect point)
                let expect = n.div_ceil(stride);
                assert_eq!(out.len(), expect, "stride {stride}: kept {} == ceil({n}/{stride})", out.len());
                assert!(out.len() <= prev || prev == usize::MAX, "bigger stride keeps no more");
                prev = out.len();
                // every kept point is inside the rect
                for &(px, py) in &out {
                    assert!(px >= vp[0] && px <= vp[2] && py >= vp[1] && py <= vp[3], "kept point in rect");
                }
            }
            cases += 1;
            ok += 1;
        }
    }
    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-geomap.point_grid",
        "cull_exact_stride_monotone",
        ok == cases,
        &format!("{ok}/{cases} grid cases"),
    );
    assert_eq!(ok, cases, "every case in the sweep passed ({ok}/{cases})");
}

// ── stage 4+5: way LOD + point stride monotone in zoom, budget cap ─────────────

/// INJECT-ASSERT — `way_lod` and `point_stride` are monotone in zoom (zooming in
/// → finer LOD, smaller stride), and `budget_stride` caps drawn points to the
/// budget while never going below the zoom-LOD floor. Driven through OsmView's
/// public navigation surface at each zoom.
#[test]
fn way_lod_and_point_stride_monotone_in_zoom_with_budget_cap() {
    let (points, ways) = facett_geomap::osm2d::sample_dataset(2_000);
    let r = rect(800.0, 600.0);
    let mut prev_stride = usize::MAX;
    let mut ok = true;
    // Walk the zoom axis from zoomed-OUT (high stride) to zoomed-IN (low stride)
    // and assert the zoom-LOD stride floor is NON-INCREASING (finer LOD as we
    // zoom in). ZOOMS is sorted ascending, so the stride must be non-increasing.
    for &z in ZOOMS {
        let mut v = OsmView::new("z", points.clone(), ways.clone());
        v.fit_to(r);
        v.camera.zoom = z;
        v.camera.target_zoom = z;
        let st = stride_floor(&mut v);
        if prev_stride != usize::MAX {
            ok &= st <= prev_stride;
            assert!(st <= prev_stride, "zoom {z}: stride {st} <= prev {prev_stride}");
        }
        prev_stride = st;
    }
    // budget cap: a dense fit caps drawn points; render proves drawn <= budget.
    let dense: Vec<(f64, f64)> = cloud(9.55, 47.15, 0.18, 120_000);
    let mut dv = OsmView::new("dense", dense, vec![]);
    dv.fit_to(r);
    let res = harness::headless_render(&mut dv);
    let drawn = res.state["drawn_points"].as_u64().unwrap() as usize;
    let max = res.state["max_drawn_points"].as_u64().unwrap() as usize;
    assert!(drawn <= max, "drawn {drawn} <= budget {max}");
    ok &= drawn <= max;
    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-geomap.lod_stride",
        "monotone_and_budget_capped",
        ok,
        &format!("strides monotone; dense drawn={drawn}<=budget={max}"),
    );
    assert!(ok, "stride monotone in zoom AND dense fit capped to the budget");
}

/// Read the OsmView's zoom-LOD point stride floor at its current zoom, via a
/// render (which writes `zoom_lod_stride` into state_json — the observable seam).
fn stride_floor(v: &mut OsmView) -> usize {
    let res = harness::headless_render(v);
    res.state["zoom_lod_stride"].as_u64().unwrap() as usize
}

// (stage 6 — the viewport clip — is a private module; it is verified end-to-end
// through the high-zoom render-matrix cells below, which route way geometry
// through `clip_polyline`/`clip_polygon`, plus its own in-module unit tests +
// testmatrix rows in `osm2d/clip.rs`.)

// ── stage 7: paint (headless) — the ~1000-case render sweep ─────────────────────

/// **THE MATRIX** (zooms × extents × centres × canvases). For each cell: build a
/// real OsmView (point cloud + the sample ways), FIT, set the zoom, render
/// headless at the canvas size, and:
///  - inject-ASSERT the state_json (points/ways present, drawn ≤ budget, stride
///    ≥ 1, visible ≤ total),
///  - EMIT: the headless render DREW vertices (non-blank).
#[test]
fn render_matrix_zooms_x_extents_x_centres_x_canvases() {
    let mut cases = 0usize;
    let mut drew = 0usize;
    let mut bounded = 0usize;
    let (_, sample_ways) = facett_geomap::osm2d::sample_dataset(4);
    for &(lon, lat) in CENTRES {
        for &ext in EXTENTS {
            let pts = cloud(lon, lat, ext, 800);
            for &z in ZOOMS {
                for &(cw, ch) in CANVASES {
                    let mut v = OsmView::new("cell", pts.clone(), sample_ways.clone());
                    v.fit_to(rect(cw, ch));
                    v.camera.zoom = z;
                    v.camera.target_zoom = z;
                    let r = harness::render_sized(&mut v, (cw, ch));
                    // inject-assert observable state
                    assert_eq!(r.state["points"], 800, "all points reported");
                    let drawn = r.state["drawn_points"].as_u64().unwrap() as usize;
                    let max = r.state["max_drawn_points"].as_u64().unwrap() as usize;
                    let stride = r.state["point_stride"].as_u64().unwrap() as usize;
                    let vis = r.state["visible_points"].as_u64().unwrap() as usize;
                    assert!(stride >= 1, "stride >= 1");
                    assert!(drawn <= max, "drawn <= budget");
                    assert!(vis <= 800, "visible <= total");
                    if drawn <= max && stride >= 1 {
                        bounded += 1;
                    }
                    if r.drew() {
                        drew += 1;
                    }
                    cases += 1;
                }
            }
        }
    }
    assert!(cases >= 1000, "the matrix has >= 1000 cases, got {cases}");
    assert_eq!(bounded, cases, "every cell stayed within the draw budget, {bounded}/{cases}");
    assert_eq!(drew, cases, "every populated cell drew vertices (non-blank), {drew}/{cases}");
    eprintln!("[geomap-matrix] {cases} cells, all drew, all within budget");
    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-geomap.render_matrix",
        "all_cells_draw_within_budget",
        drew == cases && bounded == cases,
        &format!("{cases} cells (zooms×extents×centres×canvases), {drew} drew, {bounded} budget-ok"),
    );
}

/// Negative cell: an EMPTY OsmView draws only the hint (few verts) while a
/// populated one draws far more — the non-blank proxy is not vacuous.
#[test]
fn empty_geomap_cell_is_separable_from_populated() {
    let mut empty = OsmView::new("empty", vec![], vec![]);
    let re = harness::headless_render(&mut empty);
    assert_eq!(re.state["points"], 0);
    let (pts, ways) = facett_geomap::osm2d::sample_dataset(2_000);
    let mut full = OsmView::new("full", pts, ways);
    let r = rect(800.0, 600.0);
    full.fit_to(r);
    // Zoom to a building-scale camera: at the whole-dataset FIT the building-vertex
    // LOD culls the point layer outright (that frame is a sea of corner dots — see
    // `tests/osm_point_lod.rs`), so a "populated draws more ink" probe has to look
    // at a camera where the corner layer is actually admitted.
    full.update(facett_geomap::osm2d::Msg::ZoomTo { k: 3.0, anchor: r.center(), rect: r });
    while full.camera_mut().tick() {}
    let rf = harness::headless_render(&mut full);
    assert!(rf.vertices > re.vertices * 2, "populated >> empty: {} vs {}", rf.vertices, re.vertices);
    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-geomap.render_matrix",
        "empty_vs_populated_separable",
        rf.vertices > re.vertices * 2,
        &format!("empty={} populated={} verts", re.vertices, rf.vertices),
    );
}