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

//! **The 3D pane must extrude where the user is LOOKING, not where the houses are
//! thickest.**
//!
//! The owner panned korp's 2D map to central Stockholm, pressed the 2D⇄3D toggle, and got
//! **Sigtuna** — a town 37 km away that he had not asked for and could not see. The
//! cause was two layers deep:
//!
//! 1. korp built the 3D scene from `self.map_ways` alone. The map camera existed, was
//!    correct, and was never passed. Where the user was looking had no bearing on what
//!    was extruded.
//! 2. `from_geometry` therefore had to guess, and it guessed by DENSITY —
//!    `densest_cluster_subset` votes on a grid of building centroids and takes the
//!    heaviest cell. A dormitory grid of small houses out-counts an inner city of large
//!    blocks, so the vote lands in a suburb. Reliably. It is not noise.
//!
//! The subsetting itself is right and must stay: at region scale one design unit is
//! ~35 km, an 8 m house is 2e-4 units, and the pane draws a flat white pancake with
//! every count green (MEASURED 2026-08-03, live korp-server, RTX 4090: 340 905 building
//! vertices, median house **0.66 px**). The bug is the *choice of town*.
//!
//! # Why this file asserts on GEOGRAPHY
//!
//! Every observable the 3D viewer published was a COUNT — triangles, building vertices,
//! `buildings_visible` — and a suburb has just as many houses as a city. A test written
//! on counts is green on Sigtuna. That is not a hypothetical: it is what shipped.
//!
//! So every claim here is a **distance in metres** between the camera's centre and the
//! extruded scene's centre, and the fixture is built so the two candidate answers are
//! 37 km apart. A test whose viewport happened to contain the densest cluster could not
//! tell a working camera from a dead one (the identity-value trap); this one can.

use facett_core::{Facet, Theme};
use facett_map::Way;
use facett_map3d::{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;

/// **Central Stockholm** (Gamla stan / Slussen) — where the owner panned to.
const STOCKHOLM: (f64, f64) = (18.0686, 59.3293);

/// **Sigtuna** — 37 km north-west, and what he got instead.
const SIGTUNA: (f64, f64) = (17.7239, 59.6169);

/// korp's MEASURED z12 viewport span at Stockholm on a 1900x1150 window (2026-08-03).
/// Both towns fall inside one such load, which is precisely why the pane can show the
/// wrong one.
const KORP_Z12_SPAN_M: f64 = 55_688.0;

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()
}

/// `n × n` untagged 12 m houses on a `pitch_m` grid centred on `(clon, clat)`, plus one
/// street through them so the place is a town and not a field of boxes.
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;
        }
    }
    let street_half = (n as f64 * pitch_m * 0.5) / mpdl;
    ways.push(Way::line(
        id,
        "road",
        vec![(clon - street_half, clat), (clon + street_half, clat)],
        egui::Color32::GRAY,
        2.0,
    ));
    ways
}

/// **The fixture: one korp z12 load holding TWO towns, and the denser one is not the one
/// the user is looking at.**
///
/// Stockholm gets 18×18 houses on a 55 m pitch (an inner city: large blocks, wide
/// spacing); Sigtuna gets 26×26 on a 32 m pitch (a dormitory grid: more houses, packed
/// tighter, in a smaller box). That is the real asymmetry — density and importance are
/// different quantities — and it is what makes the two answers differ by 37 km.
///
/// Plus a lake and two trunk roads across the whole extent, because a korp viewport is
/// mostly ground cover and that is what drags the scene scale.
fn two_towns() -> 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();

    // Region ground cover + trunk roads spanning the whole load.
    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.push(Way::line(
        3,
        "road",
        vec![(SIGTUNA.0, clat - half_lat), (SIGTUNA.0, clat + half_lat)],
        egui::Color32::GRAY,
        2.0,
    ));

    ways.extend(houses(1_000, STOCKHOLM, 18, 55.0));
    ways.extend(houses(9_000, SIGTUNA, 26, 32.0));
    ways
}

/// A [`extrude::ViewWindow`] for a korp viewport centred on `centre` spanning `span_m`.
fn view(centre: (f64, f64), span_m: f64) -> extrude::ViewWindow {
    extrude::ViewWindow {
        lon: centre.0,
        lat: centre.1,
        span_m,
    }
}

/// **THE FIXTURE IS THE TEST'S OWN RED PROBE.** Before any claim about the camera, prove
/// that the two candidate answers are far apart and that density really does pick the
/// wrong one — otherwise every assertion below is sitting on an identity value and a
/// dead camera would pass it.
#[test]
fn density_and_the_camera_give_answers_37_km_apart() {
    let ways = two_towns();

    let dens = extrude::town_window(&ways, None).expect("the region has buildings");
    assert_eq!(
        dens.source,
        extrude::WindowSource::Densest,
        "no camera ⇒ the vote decides"
    );
    let d_to_sigtuna = extrude::geo_distance_m((dens.lon, dens.lat), SIGTUNA);
    let d_to_stockholm = extrude::geo_distance_m((dens.lon, dens.lat), STOCKHOLM);
    println!(
        "density votes ({:.4}, {:.4}): {d_to_sigtuna:.0} m from Sigtuna, \
         {d_to_stockholm:.0} m from Stockholm ({} buildings)",
        dens.lon, dens.lat, dens.buildings,
    );
    assert!(
        d_to_sigtuna < 2_000.0 && d_to_stockholm > 30_000.0,
        "the fixture must reproduce the defect: density has to land on the SUBURB \
         ({d_to_sigtuna:.0} m from Sigtuna, {d_to_stockholm:.0} m from Stockholm). \
         If this fails the fixture has stopped being the Sigtuna case and every test \
         below has stopped proving anything.",
    );

    // …and the separation is real, so "within the viewport" is a claim with teeth.
    let sep = extrude::geo_distance_m(STOCKHOLM, SIGTUNA);
    assert!(sep > 30_000.0, "the two answers are {sep:.0} m apart");
}

/// **THE FIX, on applied output: with the camera over Stockholm, the extruded scene is
/// over Stockholm.**
///
/// Asserted as a distance in metres against the viewport's own extent, not as a count.
#[test]
fn the_extruded_scene_sits_under_the_camera() {
    let ways = two_towns();
    let v = view(STOCKHOLM, KORP_Z12_SPAN_M);

    let map = Map3D::from_geometry_in_view("stockholm", &ways, Some(v));
    let centre = map
        .geo_centre()
        .expect("real lon/lat data has a geographic centre");
    let d = extrude::geo_distance_m(centre, STOCKHOLM);
    println!(
        "camera ({:.4}, {:.4}) → scene ({:.4}, {:.4}): {d:.0} m  [window {:?}]",
        v.lon,
        v.lat,
        centre.0,
        centre.1,
        map.town_window().map(|w| w.source),
    );

    // 1. The camera chose the window, not the vote.
    assert_eq!(
        map.town_window().map(|w| w.source),
        Some(extrude::WindowSource::Camera),
        "a viewport full of buildings is a place the user is looking at",
    );

    // 2. The scene's centre is INSIDE the viewport — the geographic claim.
    let (bb_lon, bb_lat) = (
        0.5 * KORP_Z12_SPAN_M / m_per_deg_lon(STOCKHOLM.1),
        0.5 * KORP_Z12_SPAN_M / M_PER_DEG_LAT,
    );
    assert!(
        (centre.0 - STOCKHOLM.0).abs() <= bb_lon && (centre.1 - STOCKHOLM.1).abs() <= bb_lat,
        "the extruded scene must lie inside the viewport the user is looking at: \
         scene ({:.4}, {:.4}) vs viewport ({:.4} ± {bb_lon:.4}, {:.4} ± {bb_lat:.4})",
        centre.0,
        centre.1,
        STOCKHOLM.0,
        STOCKHOLM.1,
    );

    // 3. …and not merely inside it: CENTRED on it, to a small fraction of the viewport.
    //    A scene that clips the far corner of the viewport is still the wrong town.
    assert!(
        d < 0.1 * KORP_Z12_SPAN_M,
        "camera→scene distance {d:.0} m must be small against the {KORP_Z12_SPAN_M:.0} m \
         viewport; the wrong-town answer is 37 000 m away",
    );

    // 4. NON-VACUITY: it is a real town, not an empty box that trivially centres itself.
    assert!(
        map.building_vertices() > 0,
        "the scene has risen buildings in it"
    );
    assert!(
        map.triangle_count() > 100,
        "the scene has a real mesh, got {}",
        map.triangle_count()
    );
}

/// **The same call with no camera still lands on Sigtuna** — the control that proves the
/// assertion above is measuring the camera and not a fixture that centres itself.
///
/// This is the line that goes RED if the camera is ever unwired again: it is the current
/// shipped behaviour, pinned, so the difference between the two paths is visible in the
/// test output as a pair of numbers.
#[test]
fn without_a_camera_the_scene_is_still_the_wrong_town() {
    let ways = two_towns();
    let blind = Map3D::from_geometry("no camera", &ways);
    let bc = blind.geo_centre().expect("a geographic centre");
    let d_blind = extrude::geo_distance_m(bc, STOCKHOLM);

    let aimed =
        Map3D::from_geometry_in_view("camera", &ways, Some(view(STOCKHOLM, KORP_Z12_SPAN_M)));
    let ac = aimed.geo_centre().expect("a geographic centre");
    let d_aimed = extrude::geo_distance_m(ac, STOCKHOLM);

    println!("camera→scene: blind {d_blind:.0} m   aimed {d_aimed:.0} m");
    assert!(
        d_blind > 30_000.0,
        "the camera-blind path is the defect: it must still be the far town ({d_blind:.0} m)",
    );
    assert!(
        d_aimed * 20.0 < d_blind,
        "the aimed path must be at least an order of magnitude closer: \
         {d_aimed:.0} m vs {d_blind:.0} m",
    );
}

/// **A country overview has a centre, and that centre is open water — and the answer is
/// now to REFUSE, not to vote.**
///
/// This test used to assert the opposite (`WindowSource::Densest`), because the vote was
/// believed to be the honest fallback for "nothing under the camera". The 2026-08-05
/// measurement on oden's live sweden pyramid says otherwise — see
/// [`extrude::TownChoice::TooCoarse`] and the z9 test below. A country overview now
/// extrudes NOTHING and the pane says which way to zoom; the vote survives only for
/// callers that have no camera at all ([`extrude::densest_cluster_subset`]).
#[test]
fn a_country_overview_refuses_rather_than_naming_a_town_the_user_cannot_see() {
    let ways = two_towns();
    // A viewport spanning most of Sweden, centred out at sea (east of Gotland).
    let sea = view((19.6, 57.5), 900_000.0);
    match extrude::choose_town(&ways, Some(sea)) {
        extrude::TownChoice::TooCoarse(r) => {
            println!(
                "country overview → refused: {} buildings in {:.0} m, {} needed",
                r.buildings,
                2.0 * r.half_m,
                r.needed
            );
            assert!(
                r.buildings < r.needed,
                "the refusal carries the numbers behind it"
            );
            assert_eq!(
                r.span_m, 900_000.0,
                "…and the camera span the user must change"
            );
        }
        other => panic!("a camera that cannot be answered must refuse, got {other:?}"),
    }
    assert!(
        extrude::town_window(&ways, Some(sea)).is_none(),
        "no window is chosen"
    );

    // The pane extrudes NOTHING, and says why — not the facet-map3d-1 error state.
    let map = Map3D::from_geometry_in_view("overview", &ways, Some(sea));
    assert_eq!(map.building_vertices(), 0, "a refusal extrudes nothing");
    assert!(
        map.town_refused().is_some(),
        "and it is a refusal, not an empty load"
    );

    // RED PROBE for the threshold itself: the same camera CENTRE with a town-sized span
    // IS honoured. The two differ only in span, which is the quantity under test.
    let town_cam = extrude::town_window(&ways, Some(view(STOCKHOLM, 2_000.0))).expect("buildings");
    assert_eq!(
        town_cam.source,
        extrude::WindowSource::Camera,
        "a town-sized camera over a town wins"
    );
    // …and the camera-blind caller still gets the vote — refusal is about a camera that
    // was supplied and could not be answered, never about having no camera.
    assert_eq!(
        extrude::town_window(&ways, None).expect("buildings").source,
        extrude::WindowSource::Densest,
        "no camera ⇒ the vote still decides",
    );
}

/// **THE z9 DEFECT, as a unit test: the region view extruded a city 66 km away.**
///
/// MEASURED on oden's live sweden pyramid 2026-08-05, camera on central Stockholm at z9
/// (span 159 375 m, so the town window clamps to `TOWN_HALF_MAX_M` = 1500 m):
///
/// | what                                             | buildings in that window |
/// |--------------------------------------------------|--------------------------|
/// | what the selector actually SEES (budget-capped)  | **0**                    |
/// | the same z9 tile, uncapped                       | 155                      |
/// | the z15 pyramid over the same box                | **4 734**                |
///
/// The whole z9 viewport hands the selector 132 building centroids where the same tiles
/// hold 88 441 — **0.149 %** — because korp splits a 40 000-way budget over 15 covering
/// tiles and the rows are stored largest-extent-first. The camera therefore cannot
/// answer, the vote took over, and it named a town **66 113 m** away (ratio 0.415).
///
/// The two tempting repairs are ruled out ON NUMBERS: the window holds ZERO, so no
/// threshold above 0 can be met; and widening the window until the capped sample holds
/// 24 needs `half_m = 20 000` — a 40 km scene, 13.3x the readability cap.
///
/// This fixture reproduces the SHAPE of that measurement (a sparse capped sample under
/// the camera, a denser cluster far away) so the regression is caught without the live
/// pyramid.
#[test]
fn a_region_view_over_a_capped_sample_refuses_instead_of_extruding_a_city_66_km_away() {
    // The oden z9 numbers.
    const Z9_SPAN_M: f64 = 159_375.13;
    const OFFSET_M: f64 = 66_113.0;

    // What the cap left under the camera: a handful of buildings, far below the gate.
    let mut ways = houses(1_000, STOCKHOLM, 3, 40.0); // 9 houses in the 3 km window
    // …and the cluster the vote would name, 66 km away (11x11 = 121 houses, so the
    // vote CAN answer — the point is that it must not be asked).
    let far = (STOCKHOLM.0, STOCKHOLM.1 + OFFSET_M / M_PER_DEG_LAT);
    ways.extend(houses(50_000, far, 11, 30.0));

    let cam = view(STOCKHOLM, Z9_SPAN_M);

    // RED PROBE FIRST: the vote really would land 66 km away on this fixture. Without
    // this, "refused" could be passing because there is nothing to choose at all.
    let voted = extrude::town_window(&ways, None).expect("the fixture has buildings");
    assert_eq!(voted.source, extrude::WindowSource::Densest);
    let voted_off = extrude::geo_distance_m((STOCKHOLM.0, STOCKHOLM.1), (voted.lon, voted.lat));
    assert!(
        voted_off > 0.3 * Z9_SPAN_M,
        "the vote's answer must be far away for this test to mean anything — got {voted_off:.0} m",
    );

    // THE CLAIM: with the camera supplied, the vote is never consulted.
    match extrude::choose_town(&ways, Some(cam)) {
        extrude::TownChoice::TooCoarse(r) => {
            assert!(
                r.buildings < r.needed,
                "{} buildings, {} needed",
                r.buildings,
                r.needed
            );
            assert_eq!(
                r.half_m,
                extrude::TOWN_HALF_MAX_M,
                "the region camera clamps to the cap"
            );
        }
        other => panic!("z9 must refuse, got {other:?}"),
    }

    // APPLIED OUTPUT: nothing is extruded, and nothing points 66 km away.
    let map = Map3D::from_geometry_in_view("z9", &ways, Some(cam));
    assert_eq!(
        map.building_vertices(),
        0,
        "no city is extruded at region zoom"
    );
    assert!(map.town_window().is_none(), "and no window is claimed");
    let st = map.state_json();
    assert_eq!(st["town_window"], serde_json::Value::Null);
    assert_eq!(st["town_refused"]["reason"], "too-coarse");
    assert_eq!(
        st["error_code"],
        serde_json::Value::Null,
        "a refusal is an instruction, not the facet-map3d-1 empty-data fault",
    );

    // AND z12/z15 MUST NOT MOVE — a fix that repairs z9 and breaks z12 is not a fix.
    //
    // On the DENSE sample, because that is the other half of the measurement: at z12 the
    // viewport is 5.7 km over 1-2 covering tiles, so the budget is not binding and the
    // 3 km window really does hold thousands of buildings (the live drive reads
    // `src=camera`, offset 0 m, at both z12 and z15). Reusing the starved z9 sample here
    // would test the fixture, not the fix.
    let dense = two_towns();
    assert!(
        extrude::town_window(&dense, Some(view(STOCKHOLM, 5_720.6)))
            .expect("dense")
            .buildings
            >= extrude::MIN_BUILDINGS_IN_VIEW,
        "the dense leg must actually clear the gate, or it proves nothing",
    );
    for span in [5_720.6_f64, 1_430.2] {
        let ways = &dense;
        let w = extrude::town_window(ways, Some(view(STOCKHOLM, span)))
            .unwrap_or_else(|| panic!("a {span:.0} m camera over the town must be answered"));
        assert_eq!(w.source, extrude::WindowSource::Camera, "span {span}");
        assert_eq!(
            extrude::geo_distance_m((STOCKHOLM.0, STOCKHOLM.1), (w.lon, w.lat)),
            0.0,
            "span {span}: the camera-chosen window sits ON the camera",
        );
        let m = Map3D::from_geometry_in_view("close", ways, Some(view(STOCKHOLM, span)));
        assert!(
            m.building_vertices() > 0,
            "span {span}: the town is still extruded"
        );
        assert!(
            m.town_refused().is_none(),
            "span {span}: nothing is refused down here"
        );
    }
}

/// **The window follows the zoom, between two measured bounds.** A fixed 3 km box makes
/// the 3D pane show the same thing at z17 as at z9, which is not what a camera means.
#[test]
fn the_window_follows_the_zoom_and_is_clamped_at_both_ends() {
    let ways = two_towns();
    let at = |span_m: f64| {
        extrude::town_window(&ways, Some(view(STOCKHOLM, span_m)))
            .expect("buildings")
            .half_m
    };

    // The sample spans are DERIVED FROM THE ANCHORS, not written down beside them. They
    // used to be the literals 1 200 / 2 000 / 2 600 m with a literal 1 000 m expectation
    // — a second, silent statement of where the window travels — and when
    // `town_half_for_span` stopped being `0.5·span` clamped (2026-08-05) every one of
    // them landed outside and this test failed for a reason that had nothing to do with
    // the clamp. One writer (L5): move an anchor and the samples move with it.
    let (wide, deep) = (
        extrude::TOWN_ZOOM_WIDE_SPAN_M,
        extrude::TOWN_ZOOM_DEEP_SPAN_M,
    );
    let at_span = |span_m: f64| (wide / span_m).ln() / (wide / deep).ln();
    let span_at_t = |t: f64| wide * (deep / wide).powf(t);
    let _ = at_span;

    // Wide viewport → capped at the readability bound (a wider scene is the pancake).
    assert_eq!(
        at(4.0 * wide),
        extrude::TOWN_HALF_MAX_M,
        "a region camera is capped"
    );
    // Deeply zoomed → floored, so a rooftop camera still gets a block to orbit.
    assert_eq!(
        at(0.25 * deep),
        extrude::TOWN_HALF_MIN_M,
        "a rooftop camera is floored"
    );

    // MONOTONIC and STRICTLY moving between the anchors — a relation that always
    // returned its bound would satisfy the two points above individually.
    let (a, b, c) = (at(span_at_t(0.2)), at(span_at_t(0.5)), at(span_at_t(0.8)));
    assert!(
        c < b && b < a,
        "the window tracks the viewport between the anchors: {a} > {b} > {c}"
    );
    assert!(
        a > c * 1.5,
        "and it tracks it by a real amount, not a rounding: {a} → {c}",
    );
    // …and the step is GEOMETRIC — equal zoom steps give equal ratios — which is what
    // makes a wheel feel linear. Asserted as the two ratios agreeing, not as a constant.
    let (r1, r2) = (a / b, b / c);
    assert!(
        (r1 / r2 - 1.0).abs() < 0.02,
        "equal zoom steps must give equal ratios: {r1:.3} vs {r2:.3}",
    );
}

/// **The scale fix survives the camera fix.** The whole reason the subset exists is that
/// a region-scale scene draws houses sub-pixel; aiming it must not undo that.
#[test]
fn the_camera_chosen_town_is_still_town_scale() {
    let ways = two_towns();
    let region_span = extrude::world_span_metres(&ways).expect("a region span");
    assert!(
        region_span > 40_000.0,
        "the fixture is region-scale: {region_span:.0} m"
    );

    let map =
        Map3D::from_geometry_in_view("stockholm", &ways, Some(view(STOCKHOLM, KORP_Z12_SPAN_M)));
    let mpdu = map.metres_per_design_unit();
    let px = map.skyline_screen_height_px(1000.0);
    println!("aimed scene: metres_per_design_unit {mpdu:.0}  median house {px:.2} px");

    // The design unit is a town's, not a region's — the 33 195 m reading that drew the
    // pancake would fail this by a factor of ten.
    assert!(
        mpdu < 5_000.0,
        "the aimed subset must still bound the scene scale: {mpdu:.0} m per design unit",
    );
    // APPLIED OUTPUT: a house you can see. 0.66 px was the pancake.
    assert!(
        px > 2.0,
        "the median house must be legible on a 1000 px pane, got {px:.2} px"
    );
}

/// **★ THE OWNER'S CASE: 940 buildings on screen, none under the crosshair, and it refused.**
///
/// MEASURED live 2026-08-08, from `$KORP_STATE.map.map3d.town_refused` verbatim:
///
///     { buildings: 0, needed: 24, half_m: 250.06, span_m: 4487.7, reason: "too-coarse" }
///
/// A 4.5 km camera with 940 buildings in the viewport, a 250 m window in the dead centre,
/// and zero buildings in that particular 500 m. The pane drew `2 trianglar, 0 resta` over a
/// city he could see, and every line of the refusal was working as written — the reasoning
/// behind it is about a COUNTRY overview, where the vote would answer confidently about
/// somewhere the user is not looking.
///
/// At street zoom that reasoning does not hold: the buildings are ON SCREEN. So the window
/// slides to the densest cluster still inside the viewport, and only in a view small enough
/// to be one place.
///
/// RED directions: delete the `densest_within` call and this refuses again; drop the
/// `span_m <= 4 x DEEP` bound and the two country-overview guards above go red instead.
#[test]
fn a_street_view_with_buildings_off_centre_slides_instead_of_refusing() {
    use facett_map3d::extrude::{self, TownChoice, ViewWindow, WindowSource};

    // A town, deliberately OFF the camera centre — the shape of the live failure.
    let centre = (18.0686, 59.3293);
    let off = (centre.0 + 0.022, centre.1 + 0.008); // ~1.25 km east, ~0.9 km north — on screen, far outside the 250 m crosshair window
    let ways = houses(9000, off, 60, 25.0);

    let v = ViewWindow { lon: centre.0, lat: centre.1, span_m: 4487.7 };
    match extrude::choose_town(&ways, Some(v)) {
        TownChoice::Chosen(w) => {
            assert_eq!(
                w.source,
                WindowSource::CameraSlid,
                "the window slid rather than being chosen by the crosshair",
            );
            assert!(
                w.buildings >= extrude::MIN_BUILDINGS_IN_VIEW,
                "…onto a real cluster: {} buildings",
                w.buildings,
            );
            // …and it stayed ON SCREEN. That bound is the whole difference between this and
            // the unbounded vote that names a town 66 km away.
            let half_lat = (v.span_m * 0.5) / extrude::M_PER_DEG_LAT;
            let half_lon = (v.span_m * 0.5) / m_per_deg_lon(v.lat);
            assert!(
                (w.lon - v.lon).abs() <= half_lon && (w.lat - v.lat).abs() <= half_lat,
                "the slid window must stay inside the viewport: ({}, {}) from ({}, {})",
                w.lon, w.lat, v.lon, v.lat,
            );
        }
        other => panic!("a street view with 60 buildings on screen must not refuse: {other:?}"),
    }
}