facett-map3d/tests/zoom_ladder.rs
//! **Where the 3D pane's zoom coupling actually lives, in korp's own camera units.**
//!
//! [`extrude::town_window`] takes the viewport's half-span and clamps it into
//! `[TOWN_HALF_MIN_M, TOWN_HALF_MAX_M]`. `camera_picks_the_town::
//! the_window_follows_the_zoom_and_is_clamped_at_both_ends` proves that clamp is
//! monotone and strictly moving **in the band** — it samples viewport spans of 1 200,
//! 2 000 and 2 600 m. Those are correct numbers and they are also numbers korp's map
//! camera does not produce at any rung it serves: on a 1900x1150 pane the z12 viewport
//! is **37 042 m** (korp `5f0ba51`), so a 2 000 m viewport is camera ~16.2.
//!
//! The consequence is not stated anywhere, and it is the first thing a user meets:
//! **from z0 to z15.6 the extruded scene does not change at all.** Not approximately —
//! identically. MEASURED here on a synthetic 2.4 km town, and MEASURED on the live
//! korp-server through a real focused 1900x1150 Xvfb window (2026-08-05, RTX 4090
//! [discrete/Vulkan], `korp-ui` ORIGINAL atom): `metres_per_design_unit` **1 874.0** at
//! camera 9.30 and **1 875.0** at camera 14.10 — the same scene scale five zoom levels
//! apart, with `town_window.half_m = 1500.0` at both. It first moves at camera 16.10,
//! where `half_m` reads 715.1 and the design unit drops to 893.9.
//!
//! So this file does not assert that the flat band is wrong — the cap is deliberate and
//! [`extrude::TOWN_HALF_MAX_M`] argues for it from a measurement. It asserts **where the
//! band is**, in camera units, so the claim "the window follows the zoom" can never
//! again read wider than the evidence under it.
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);
const SIGTUNA: (f64, f64) = (17.7239, 59.6169);
/// korp's MEASURED z12 viewport span on a 1900x1150 pane (korp `5f0ba51`:
/// `156543.03 * cos(59.33) / 2^12 = 19.49 m/px × 1900 px`). Every camera zoom below is
/// derived from this one measurement, so a pane-size change moves the whole ladder
/// together instead of half of it.
const KORP_Z12_SPAN_M: f64 = 37_042.0;
/// The viewport span, in metres, korp's map camera has at zoom `z`.
fn span_at(z: f64) -> f64 {
KORP_Z12_SPAN_M * 2f64.powf(12.0 - z)
}
/// The camera zoom at which korp's viewport span is `span_m` — the inverse of
/// [`span_at`], so the two cannot drift.
fn zoom_at(span_m: f64) -> f64 {
12.0 + (KORP_Z12_SPAN_M / span_m).log2()
}
use facett_map3d::extrude::m_per_deg_lon;
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()
}
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
}
/// A region load: a dense inner city (60x60 houses on a 40 m pitch, **~2.4 km across**,
/// deliberately WIDER than [`extrude::TOWN_HALF_MAX_M`] so a narrowing window really
/// does change what is in frame — a town smaller than the cap would hold the scene span
/// fixed for a second, unrelated reason and this file could not tell the two apart),
/// plus region ground cover and a far suburb.
fn region() -> Vec<Way> {
let (clon, clat) = STOCKHOLM;
let mpdl = m_per_deg_lon(clat);
let half_lat = 0.5 * KORP_Z12_SPAN_M / M_PER_DEG_LAT;
let half_lon = 0.5 * KORP_Z12_SPAN_M / mpdl;
let theme = Theme::default();
let mut ways = Vec::new();
ways.extend(Way::from_lonlat(
1,
&ring(clon - half_lon * 0.3, clat + half_lat * 0.2, half_lon * 0.3, half_lat * 0.3),
&tags(&[("natural", "water")]),
&theme,
));
ways.push(Way::line(
2,
"road",
vec![(clon - half_lon, clat), (clon + half_lon, clat)],
egui::Color32::GRAY,
2.0,
));
ways.extend(houses(1_000, STOCKHOLM, 60, 40.0));
ways.extend(houses(90_000, SIGTUNA, 26, 32.0));
ways
}
fn view(centre: (f64, f64), span_m: f64) -> extrude::ViewWindow {
extrude::ViewWindow { lon: centre.0, lat: centre.1, span_m }
}
/// **The ladder, printed.** Not an assertion — the table a human reads next to the
/// screenshots. Every column here was reproduced on the live server through a real
/// window; see the module docs for the three readings that matter.
#[test]
fn print_the_ladder() {
let ways = region();
println!(
"{:>5} {:>10} {:>9} {:>8} {:>10} {:>10} {:>22} {:>8}",
"zoom", "span_m", "half_m", "houses", "scene_m", "m/du", "p50/p90/max px", "tris",
);
for zi in 9..=19 {
let z = zi as f64;
let v = view(STOCKHOLM, span_at(z));
let w = extrude::town_window(&ways, Some(v)).expect("buildings");
let mut map = Map3D::from_geometry_in_view("sweep", &ways, Some(v));
let mpdu = map.metres_per_design_unit();
let (p50, p90, mx) = (
map.skyline_percentile_px(1150.0, 0.5),
map.skyline_percentile_px(1150.0, 0.9),
map.skyline_percentile_px(1150.0, 1.0),
);
map.cam.goal_distance = facett_map3d::camera::OrbitCamera::MIN_DISTANCE;
map.cam.settle();
let (near_p50, near_max) =
(map.skyline_percentile_px(1150.0, 0.5), map.skyline_percentile_px(1150.0, 1.0));
println!(
"{zi:>5} {:>10.0} {:>9.0} {:>8} {:>10.0} {mpdu:>10.1} {p50:>6.2}/{p90:>6.2}/{mx:>6.2} {:>8} \
| dollied-in p50 {near_p50:.0} / max {near_max:.0} px",
v.span_m,
w.half_m,
w.buildings,
(mpdu * extrude::DESIGN_FILL) as f64,
map.triangle_count(),
);
}
}
/// **THE CLAIM: the extruded scene moves at EVERY camera zoom korp can reach — the
/// window's travel is now inside the reachable range instead of below it.**
///
/// # What this test used to claim, and why that claim had to be retired
///
/// It used to assert the opposite, correctly: with `half_m = clamp(0.5·span, MIN, MAX)`
/// the window only moves for cameras whose viewport half-span lies between the bounds,
/// which put the band at camera (15.63, 17.53) — **past z15, the deepest rung korp's
/// pyramid cuts**. Its own module docs recorded the consequence on the live server:
/// `metres_per_design_unit` 1874.0 at camera 9.30 and 1875.0 at camera 14.10. Five zoom
/// levels, one scene.
///
/// Then the drive measured the other end: korp's map camera **stops at 16.30**
/// ([`extrude::TOWN_ZOOM_DEEP_SPAN_M`], viewport 1245 m — `aim 17` does not converge).
/// So the band was not merely late, it was UNREACHABLE: there is no camera at which the
/// old relation moved the window at all. The identity relation could not be rescued by
/// choosing better bounds, and [`extrude::town_half_for_span`] replaced it with a log
/// interpolation between the two anchors.
///
/// RED-WHEN-BROKEN, seen red both ways (2026-08-05):
/// * making `town_half_for_span` return `TOWN_HALF_MAX_M` unconditionally (the flat
/// band, i.e. the shipped defect) fails (a) and (b);
/// * equalising `TOWN_HALF_MIN_M` and `TOWN_HALF_MAX_M` collapses the travel to 1.00x
/// and fails (b).
#[test]
fn the_scene_moves_at_every_zoom_korp_can_reach() {
let ways = region();
let half_at = |z: f64| {
extrude::town_window(&ways, Some(view(STOCKHOLM, span_at(z))))
.expect("buildings")
.half_m
};
// The reachable range, in camera zoom, from the two MEASURED anchors — not literals
// beside them (L5), so moving an anchor moves the samples with it.
let (wide, deep) = (
zoom_at(extrude::TOWN_ZOOM_WIDE_SPAN_M),
zoom_at(extrude::TOWN_ZOOM_DEEP_SPAN_M),
);
println!(
"reachable camera range z{wide:.2} → z{deep:.2}; window {:.0} m → {:.0} m",
half_at(wide),
half_at(deep),
);
// (a) MONOTONE AND STRICT at every whole rung inside the reachable range — the
// sentence "the 3D pane does not zoom" restated as something the build fails on.
// Asserted rung by rung, because a pair of endpoints would pass on a relation
// that is flat for five levels and then steps.
let rungs: Vec<f64> = ((wide.ceil() as i32)..=(deep.floor() as i32)).map(f64::from).collect();
assert!(rungs.len() >= 4, "the reachable range must span several rungs: {rungs:?}");
for w in rungs.windows(2) {
let (a, b) = (half_at(w[0]), half_at(w[1]));
assert!(
b < a * 0.95,
"the window must narrow at EVERY rung the user can reach: z{:.0} {a:.0} m → \
z{:.0} {b:.0} m is the flat band this file exists to keep out",
w[0],
w[1],
);
}
// (b) …BY A REAL AMOUNT over the range, so (a) cannot be satisfied by a 1 % crawl.
let travel = half_at(wide) / half_at(deep);
println!("total travel across the reachable range: {travel:.2}x");
assert!(
travel > 3.0,
"the scene must really change across the range the user can drive: {travel:.2}x",
);
// (c) …stated on APPLIED OUTPUT, not on the clamp that produced it: the extruded
// scene's own design unit at the rungs korp's pyramid actually cuts.
let du = |z: f64| Map3D::from_geometry_in_view("rung", &ways, Some(view(STOCKHOLM, span_at(z))))
.metres_per_design_unit();
let (du9, du12, du15) = (du(9.0), du(12.0), du(15.0));
println!("metres_per_design_unit: z9 {du9:.1} z12 {du12:.1} z15 {du15:.1}");
assert!(
du15 < du12 * 0.75,
"z12 and z15 must extrude DIFFERENT scene scales ({du12:.1} → {du15:.1} m per \
design unit); equal is the flat band, which is what shipped",
);
// (d) …and the far end stays clamped, because a 159 km z9 viewport is not a scene.
assert!(
(du9 - du12).abs() < 1e-3,
"above the wide anchor the window is capped and the scene is one scene \
({du9:.1} vs {du12:.1}) — that part is deliberate and must not drift",
);
}
/// **THE CLAIM THE CAP OWES: at the cap — where the user spends z0 through z17 — the
/// median house must actually be legible.**
///
/// [`extrude::TOWN_HALF_MAX_M`] is the constant that decides what the 3D pane shows at
/// every zoom korp's pyramid serves, because the window is pinned to it through the
/// whole pyramid (the test above). It was derived from a legibility target of
/// [`extrude::LEGIBLE_MEDIAN_HOUSE_PX`] and then never measured against it: at the old
/// 1500 m the ladder's applied `p50` is **2.99 px**, 2.7x short of the 8 px the constant's
/// own docstring claimed for it — inside that same docstring's definition of the pancake.
///
/// So this asserts the target on APPLIED OUTPUT (a projected wall height in pixels
/// through the render camera), not on the constant. The constant is free to move; what
/// may not move is what the user sees at it.
///
/// RED-WHEN-BROKEN, seen red both ways (2026-08-05):
/// * `TOWN_HALF_MAX_M = 1500.0` (the old value) → `p50 2.99 px` → (a) fails.
/// * `TOWN_HALF_MIN_M = 400.0` (the old value) → deepest rung `p50 10.90 px` → (b) fails.
#[test]
fn the_capped_scene_is_legible_which_is_what_the_cap_owes() {
const VIEWPORT_PX: f32 = 1150.0;
let ways = region();
let p50 = |span_m: f64| {
Map3D::from_geometry_in_view("rung", &ways, Some(view(STOCKHOLM, span_m)))
.skyline_percentile_px(VIEWPORT_PX, 0.5)
};
// (a) AT THE CAP. Any zoom wide enough to be clamped shows this one scene, so this
// single reading covers z0..z17 — every rung the tile pyramid actually cuts.
let at_cap = p50(2.0 * extrude::TOWN_ZOOM_WIDE_SPAN_M); // a region overview: pinned at the cap
println!(
"median house at the cap ({} m half-window): {at_cap:.2} px on a {VIEWPORT_PX:.0} px pane \
(target {})",
extrude::TOWN_HALF_MAX_M,
extrude::LEGIBLE_MEDIAN_HOUSE_PX,
);
assert!(
f64::from(at_cap) >= extrude::LEGIBLE_MEDIAN_HOUSE_PX,
"the capped scene is what the user sees at every rung korp serves, and its median \
house stands {at_cap:.2} px — under the {} px this cap was derived from. A cap \
that misses its own legibility target is the pancake with a smaller number on it",
extrude::LEGIBLE_MEDIAN_HOUSE_PX,
);
// (b) AT THE FLOOR — the deep end is what "zoom down among the houses" means, and a
// floor that still frames a whole town from outside it has not got there. Held
// well above the cap's target so the two ends cannot be satisfied by one value.
let at_floor = p50(extrude::TOWN_ZOOM_DEEP_SPAN_M); // the closest korp can be driven
println!("median house at the floor ({} m): {at_floor:.2} px", extrude::TOWN_HALF_MIN_M);
assert!(
f64::from(at_floor) >= 3.0 * extrude::LEGIBLE_MEDIAN_HOUSE_PX,
"at the deepest window the pane can reach the median house is {at_floor:.2} px — \
still a model on a table, not a street you are standing in",
);
// (c) …and the RED CONTROL that keeps (a) and (b) from being one claim twice: the
// floor must be a strictly closer place than the cap, by a wide margin.
assert!(
at_floor > at_cap * 2.0,
"cap {at_cap:.2} px → floor {at_floor:.2} px: the two ends of the band must be \
different places, or this file is asserting one reading twice",
);
}