facett-map/tests/callchain_matrix.rs
//! **Render call-chain coverage matrix** for `facett-map` (LAW: every fn in the
//! input→pixel chain TRACKED + VERIFIED, return-asserted where it proves
//! correctness, emit where it can't).
//!
//! The 2D map chain is:
//!
//! ```text
//! read ways/points (lon,lat)
//! → project Mercator (mercator / project::to_mercator_f32 / Way::project)
//! → classify/style (style::classify → WayStyle{is_area,fill,lod,kind})
//! → ring close test (Way::from_lonlat → fill iff is_area && closed)
//! → tessellate areas (tessellate::triangulate / filled_mesh ear-clip)
//! → LOD partition + cull (index::MapIndex::build / query_visible / gpu_ways)
//! → fit-to-data bbox (MapView::merc_bbox via bbox + Way.bbox)
//! → paint lines+fills+pts (Facet::ui CPU painter)
//! → pixels (egui_kittest wgpu render — robot/headless)
//! ```
//!
//! Each stage gets an **inject real input → assert the real return** case; the
//! painted-pixel stage (where the return doesn't expose the rasterized output) is
//! covered by the EMIT-doctrine non-blank / fills-not-just-lines / confined
//! checks (the `testmatrix`-gated `emit*` rows + the egui_kittest robot drive).
//!
//! ~1000 matrix cases: geometry kinds {road/water/landuse/building/coastline} ×
//! zoom levels × extents × LOD tiers × canvas sizes, fed real WKB/GeoParquet-
//! shaped `(lon,lat)` input.
//!
//! Run: `cargo test -p facett-map --test callchain_matrix`
//! Matrix rows: `cargo test -p facett-map --features testmatrix --test callchain_matrix`
use facett_core::{Theme, harness};
use facett_map::style::classify;
use facett_map::tessellate::{filled_mesh, triangulate};
use facett_map::{MapView, Way, WayKind, index::MapIndex, mercator, project};
// ── matrix axes ───────────────────────────────────────────────────────────────
/// The five geometry kinds the LAW names, each as a (tag-pair, is-area-expected,
/// expected WayKind) triple — the classify oracle.
const KINDS: &[(&str, &str, bool, WayKind)] = &[
("highway", "primary", false, WayKind::Road),
("natural", "water", true, WayKind::Water),
("landuse", "forest", true, WayKind::Park),
("building", "yes", true, WayKind::Building),
("natural", "coastline", false, WayKind::Coastline),
];
/// A spread of lon/lat extents (degrees) — country / region / city / street.
const EXTENTS: &[f64] = &[8.0, 2.0, 0.5, 0.1, 0.02];
/// Canvas sizes the host might hand the pane (incl. a squeezed one).
const CANVASES: &[(f32, f32)] = &[(160.0, 120.0), (400.0, 300.0), (800.0, 600.0), (1280.0, 720.0), (240.0, 900.0)];
/// Base centres across the globe (lon, lat) — varied Mercator y (incl. both
/// hemispheres + high-latitude). 8 centres so the full sweep clears 1000 cells.
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),
];
/// Build a closed square ring of `ext` degrees around `(lon,lat)`.
fn ring(lon: f64, lat: f64, ext: f64) -> Vec<(f64, f64)> {
let h = ext / 2.0;
vec![(lon - h, lat - h), (lon + h, lat - h), (lon + h, lat + h), (lon - h, lat + h), (lon - h, lat - h)]
}
/// Build an open 2-vertex line of `ext` degrees from `(lon,lat)`.
fn line(lon: f64, lat: f64, ext: f64) -> Vec<(f64, f64)> {
vec![(lon, lat), (lon + ext, lat + ext * 0.5)]
}
fn tagvec(k: &str, v: &str) -> Vec<(String, String)> {
vec![(k.to_string(), v.to_string())]
}
// ── stage 1: projection (mercator / to_mercator_f32 / Way::project) ────────────
/// INJECT-ASSERT — the projection's RETURN proves correctness: x is monotone in
/// lon, y is monotone (inverted) in lat, centre→(0.5,0.5), and the f32 GPU
/// projection matches the f64 viewer projection. Walked over every centre/extent.
#[test]
fn projection_returns_correct_monotone_mercator() {
let mut cases = 0usize;
let mut ok = 0usize;
for &(lon, lat) in CENTRES {
// centre identity at the origin
let (cx, cy) = mercator(0.0, 0.0);
assert!((cx - 0.5).abs() < 1e-9 && (cy - 0.5).abs() < 1e-9);
for &ext in EXTENTS {
// east is +x
let (xw, _) = mercator(lon, lat);
let (xe, _) = mercator((lon + ext).min(179.0), lat);
assert!(xe > xw, "lon+{ext} → larger x at lon {lon}");
// north is smaller y (up)
let (_, ys) = mercator(lon, (lat - ext).max(-84.0));
let (_, yn) = mercator(lon, (lat + ext).min(84.0));
assert!(yn < ys, "north → smaller y at lat {lat}");
// f32 GPU projection matches f64 viewer projection (same shape)
let (fx, fy) = project::to_mercator_f32(lon as f32, lat as f32);
let (dx, dy) = mercator(lon, lat);
assert!((fx as f64 - dx).abs() < 1e-4, "f32 x matches f64");
assert!((fy as f64 - dy).abs() < 1e-4, "f32 y matches f64");
// Way::project (via from_lonlat) computes a bbox that contains the verts
let w = Way::from_lonlat(1, &ring(lon, lat, ext), &tagvec("natural", "water"), &Theme::default()).unwrap();
// The bbox is f64 and derived from the f64 Mercator of `lonlat`
// (stinky `seam-15`); `points` is the deliberately coarse f32 stream. So
// the containment holds to the f32 stream's OWN tolerance, not to the
// bbox's — which is the honest statement of what these two are.
for &(px, py) in &w.points {
assert!(f64::from(px) >= w.bbox[0] - 1e-6 && f64::from(px) <= w.bbox[2] + 1e-6, "vertex x in bbox");
assert!(f64::from(py) >= w.bbox[1] - 1e-6 && f64::from(py) <= w.bbox[3] + 1e-6, "vertex y in bbox");
}
cases += 1;
ok += 1;
}
}
assert_eq!(cases, CENTRES.len() * EXTENTS.len());
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-map.project",
"mercator_monotone_and_f32_matches_f64",
ok == cases,
&format!("{ok}/{cases} projection cases"),
);
assert_eq!(ok, cases, "every case in the sweep passed ({ok}/{cases})");
}
// ── stage 2+3: classify → style → ring-close → fill (the WKB-shaped reader) ────
/// INJECT-ASSERT — `classify`'s RETURN (`WayStyle`) carries the correct kind +
/// is_area + fill, and `Way::from_lonlat` only fills a CLOSED area ring (an open
/// area still strokes). Walked over every kind × extent × centre = the classify
/// matrix.
#[test]
fn classify_and_ring_close_decide_fill_correctly() {
let th = Theme::default();
let mut cases = 0usize;
let mut ok = 0usize;
for &(k, v, want_area, want_kind) in KINDS {
// pure classify return
let style = classify(&tagvec(k, v), &th);
assert_eq!(style.is_area, want_area, "{k}={v} is_area");
assert_eq!(style.kind, want_kind, "{k}={v} kind");
assert_eq!(style.fill.is_some(), want_area, "fill set iff area for {k}={v}");
for &(lon, lat) in CENTRES {
for &ext in EXTENTS {
// CLOSED ring: an area fills, a line never does.
let closed = Way::from_lonlat(1, &ring(lon, lat, ext), &tagvec(k, v), &th).unwrap();
assert_eq!(closed.fill.is_some(), want_area, "closed {k}={v} fill matches area-ness");
assert_eq!(closed.style.kind, want_kind);
// OPEN way of the SAME tags: an area must NOT fill (it strokes
// gracefully); a line stays a line.
let open = Way::from_lonlat(1, &line(lon, lat, ext), &tagvec(k, v), &th).unwrap();
assert!(open.fill.is_none(), "an OPEN {k}={v} way never fills (graceful)");
cases += 2;
ok += 2;
}
}
}
// road width ordering (the style return drives the painter's stroke width)
let mw = classify(&tagvec("highway", "motorway"), &th).width;
let rs = classify(&tagvec("highway", "residential"), &th).width;
assert!(mw > rs, "motorway wider than residential");
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-map.classify",
"kind_area_fill_and_ring_close",
ok == cases,
&format!("{ok}/{cases} classify/ring cases over {} kinds", KINDS.len()),
);
assert_eq!(ok, cases, "every case in the sweep passed ({ok}/{cases})");
}
// ── stage 4: tessellation (ear-clip) — area equals the true shoelace area ──────
/// INJECT-ASSERT — the ear-clip's RETURN is provably correct: the summed triangle
/// area equals the polygon's true shoelace area (a convex fan would over-count a
/// concave ring), and a simple ring yields exactly n-2 triangles. Walked over a
/// family of synthetic rings (convex squares + concave notches) at varied scales.
#[test]
fn tessellation_area_equals_true_area_concave_safe() {
use egui::Pos2;
let scales = [1.0_f32, 10.0, 100.0, 1000.0];
// Build rings at several scales: a convex quad and a concave "arrow".
for &scale in &scales {
let quad =
[Pos2::new(0.0, 0.0), Pos2::new(scale, 0.0), Pos2::new(scale, scale), Pos2::new(0.0, scale)];
let tq = triangulate(&quad);
assert_eq!(tq.len(), 2, "quad → 2 tris at scale {scale}");
let area_q: f32 = tq
.iter()
.map(|&[a, b, c]| tri_area(quad[a as usize], quad[b as usize], quad[c as usize]))
.sum();
assert!((area_q - scale * scale).abs() < scale * scale * 1e-3, "quad area at {scale}");
// concave pentagon with an inward notch
let poly = [
Pos2::new(0.0, 0.0),
Pos2::new(2.0 * scale, 0.0),
Pos2::new(2.0 * scale, 2.0 * scale),
Pos2::new(scale, 0.5 * scale), // the notch
Pos2::new(0.0, 2.0 * scale),
];
let tp = triangulate(&poly);
assert_eq!(tp.len(), poly.len() - 2, "n-2 tris for a simple polygon at {scale}");
let true_area = shoelace(&poly);
let tp_area: f32 = tp
.iter()
.map(|&[a, b, c]| tri_area(poly[a as usize], poly[b as usize], poly[c as usize]))
.sum();
assert!(
(tp_area - true_area).abs() < true_area * 1e-2,
"concave area {tp_area} == true {true_area} at {scale} (no spill into the notch)"
);
// filled_mesh produces a real mesh for a valid ring, None for a degenerate one
assert!(filled_mesh(&quad, egui::Color32::RED).is_some(), "valid ring → mesh");
assert!(filled_mesh(&quad[..2], egui::Color32::RED).is_none(), "2-vert ring → None");
}
// All scales above passed their asserts (else we'd have panicked).
let cases = scales.len();
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-map.tessellate",
"earclip_area_equals_shoelace",
true,
&format!("{cases} ear-clip scales (concave-safe)"),
);
let _ = cases;
}
fn tri_area(a: egui::Pos2, b: egui::Pos2, c: egui::Pos2) -> f32 {
((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)).abs() / 2.0
}
fn shoelace(p: &[egui::Pos2]) -> f32 {
let mut a = 0.0;
for i in 0..p.len() {
let q = p[(i + 1) % p.len()];
a += p[i].x * q.y - q.x * p[i].y;
}
a.abs() / 2.0
}
// ── stage 5: LOD partition + viewport cull (index::MapIndex) ───────────────────
/// INJECT-ASSERT — `MapIndex` partitions ways into the right LOD tier, `gpu_ways`
/// walks every tier (count conserved), and `query_visible`'s RETURN is monotone:
/// a higher LOD never returns FEWER ways, and a tighter viewport never returns
/// MORE. Walked over a synthetic basemap built across kinds × extents.
#[test]
fn lod_partition_and_cull_counts_are_monotone() {
let th = Theme::default();
let mut ways = Vec::new();
for &(k, v, _, _) in KINDS {
for (i, &(lon, lat)) in CENTRES.iter().enumerate() {
for (j, &ext) in EXTENTS.iter().enumerate() {
let geom = if classify(&tagvec(k, v), &th).is_area { ring(lon, lat, ext) } else { line(lon, lat, ext) };
if let Some(w) = Way::from_lonlat((i * 100 + j) as i64, &geom, &tagvec(k, v), &th) {
ways.push(w);
}
}
}
}
let total = ways.len();
assert!(total > 50, "built a real basemap of {total} ways");
let idx = MapIndex::build(ways);
let sizes = idx.lod_sizes();
assert_eq!(
sizes.iter().sum::<usize>(),
total,
"every way landed in exactly one of the {} tiers ({sizes:?})",
sizes.len()
);
assert_eq!(idx.gpu_ways().count(), total, "gpu_ways walks every tier");
assert_eq!(idx.len(), total);
// LOD monotonicity: visible(lod=0) ⊆ visible(lod=1) ⊆ visible(lod=2).
let vp = [0.0_f32, 0.0, 1.0, 1.0];
// Walk EVERY tier, not the three that existed when this was written. The tier
// count is `lod_sizes().len()`, so a fourth tier is covered here by construction —
// the old form stopped at `lod=2` and would have reported a clean monotone pass
// over a map whose deepest tier it never asked about.
let counts: Vec<usize> =
(0..sizes.len() as u32).map(|l| idx.query_visible(vp, l).len()).collect();
assert!(
counts.windows(2).all(|w| w[0] <= w[1]),
"higher LOD never shows fewer ways: {counts:?}"
);
assert_eq!(
*counts.last().unwrap(),
total,
"the whole-world viewport at the DEEPEST tier sees every way: {counts:?}"
);
// Viewport monotonicity: a tighter box never returns more than a wider box.
let wide = idx.query_visible([0.0, 0.0, 1.0, 1.0], 2).len();
let tight = idx.query_visible([0.5, 0.5, 0.55, 0.55], 2).len();
assert!(tight <= wide, "tighter viewport returns no more ways: {tight} <= {wide}");
// An empty viewport off the data returns nothing.
let off = idx.query_visible([0.99, 0.99, 1.0, 1.0], 2).len();
assert!(off <= wide);
// The row restates the SAME quantities the asserts above walked — `sizes` and
// `counts`, both of which follow `lod_sizes().len()`. The form that stood here
// named `c`, `r`, `t`, `n0`, `n1` — five bindings that stopped existing when the
// three hardcoded tiers became the `counts` walk, so this file has not COMPILED
// under `--features testmatrix` since. A row that cannot be built emits nothing,
// and nothing is indistinguishable from a row nobody wrote.
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-map.index",
"lod_partition_and_cull_monotone",
sizes.iter().sum::<usize>() == total
&& counts.windows(2).all(|w| w[0] <= w[1])
&& *counts.last().unwrap() == total
&& tight <= wide,
&format!(
"tiers={sizes:?} total={total} visible_per_lod={counts:?} tight={tight}<=wide={wide}"
),
);
}
// ── stage 6: fit-to-data bbox (MapView::merc_bbox via bbox) ────────────────────
/// INJECT-ASSERT — the fit-to-data bbox (`MapView::bbox`) returns a box that
/// CONTAINS every injected lon/lat across both layers, growing to cover a basemap
/// added on top. Walked over centre/extent combos.
#[test]
fn fit_to_data_bbox_contains_all_features() {
let th = Theme::default();
let mut cases = 0usize;
let mut ok = 0usize;
for &(lon, lat) in CENTRES {
for &ext in EXTENTS {
let pts = vec![(lon, lat), (lon + ext, lat + ext)];
let ways = vec![Way::from_lonlat(1, &ring(lon, lat, ext), &tagvec("natural", "water"), &th).unwrap()];
let v = MapView::new("m", pts.clone()).with_ways(ways);
let (mnlon, mnlat, mxlon, mxlat) = v.bbox().expect("non-empty map has a bbox");
// contains every point
for &(px, py) in &pts {
assert!(px >= mnlon - 1e-9 && px <= mxlon + 1e-9, "lon {px} in bbox");
assert!(py >= mnlat - 1e-9 && py <= mxlat + 1e-9, "lat {py} in bbox");
}
// contains the way ring (the ring reaches lon-ext/2 west of centre)
assert!(mnlon <= lon - ext / 2.0 + 1e-9, "bbox grew to cover the way west edge");
cases += 1;
ok += 1;
}
}
// an empty map has no bbox (graceful)
assert!(MapView::new("e", vec![]).bbox().is_none());
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-map.fit",
"merc_bbox_contains_all_features",
ok == cases,
&format!("{ok}/{cases} fit-to-data cases"),
);
assert_eq!(ok, cases, "every case in the sweep passed ({ok}/{cases})");
}
// ── stage 7: paint (headless CPU painter) — the ~1000-case render sweep ────────
/// **THE MATRIX** (kinds × extents × centres × canvases). For each cell: build a
/// real WKB/GeoParquet-shaped MapView (a closed area ring of the kind + a line
/// way + a few points), render it headless at the canvas size, and:
/// - inject-ASSERT the state_json (ways/points/filled counts) is exactly right,
/// - EMIT (return can't expose the raster): the headless render DREW vertices
/// (non-blank), with non-zero geometry.
///
/// This is the input→pixel proof for the CPU painter across the whole axis grid.
#[test]
fn render_matrix_kinds_x_extents_x_centres_x_canvases() {
let th = Theme::default();
let mut cases = 0usize;
let mut drew = 0usize;
let mut filled_correct = 0usize;
for &(k, v, want_area, _) in KINDS {
for &(lon, lat) in CENTRES {
for &ext in EXTENTS {
// a closed ring of this kind + a road line + 3 points
let area = Way::from_lonlat(1, &ring(lon, lat, ext), &tagvec(k, v), &th).unwrap();
let road = Way::from_lonlat(2, &line(lon, lat, ext), &tagvec("highway", "primary"), &th).unwrap();
let pts = vec![(lon, lat), (lon + ext * 0.3, lat), (lon, lat + ext * 0.3)];
let want_filled = if want_area { 1 } else { 0 };
for &(cw, ch) in CANVASES {
let mut view = MapView::new("cell", pts.clone()).with_ways(vec![area.clone(), road.clone()]);
let r = harness::render_sized(&mut view, (cw, ch));
// inject-assert state
assert_eq!(r.state["points"], 3, "3 points {k}={v} {cw}x{ch}");
assert_eq!(r.state["ways"], 2, "area + road");
assert_eq!(
r.state["filled_ways"].as_u64().unwrap() as i64,
want_filled,
"{k}={v} filled_ways == {want_filled}"
);
// EMIT-doctrine: the painter actually drew (non-blank proxy).
if r.drew() {
drew += 1;
}
if (r.state["filled_ways"].as_u64().unwrap() as i64) == want_filled {
filled_correct += 1;
}
cases += 1;
}
}
}
}
assert!(cases >= 1000, "the matrix has >= 1000 cases, got {cases}");
assert_eq!(drew, cases, "every populated cell drew vertices (non-blank), {drew}/{cases}");
assert_eq!(filled_correct, cases, "every cell's filled-vs-line classification held");
eprintln!("[map-matrix] {cases} cells, all drew, all filled-classified correctly");
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"facett-map.render_matrix",
"all_cells_draw_with_correct_fill",
drew == cases && filled_correct == cases,
&format!("{cases} cells (kinds×extents×centres×canvases), {drew} drew, {filled_correct} fill-correct"),
);
}
/// Empty-map negative cell: a populated cell draws, an EMPTY map is (correctly)
/// near-blank — proving the non-blank proxy is not vacuous on the headless path.
#[test]
fn empty_cell_is_distinguishable_from_a_populated_one() {
let mut empty = MapView::new("empty", vec![]);
let re = harness::headless_render(&mut empty);
assert_eq!(re.state["points"], 0);
assert_eq!(re.state["ways"], 0);
// The hint text draws a little, but a populated map draws far more vertices.
let mut full = MapView::new("full", (0..200).map(|i| (9.5 + i as f64 * 0.001, 47.1)).collect());
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-map.render_matrix",
"empty_vs_populated_separable",
rf.vertices > re.vertices * 2,
&format!("empty={} populated={} verts", re.vertices, rf.vertices),
);
}