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

nordisk/facett

facett-map/tests/rail_tier_pixels.rs

//! **THE RAILS ARE ON THE CANVAS AT THE CAMERA HE WAS LOOKING FROM** — pixels, not tiers
//! (`railtier-1`).
//!
//! `style::tests::a_national_railway_is_drawn_at_the_camera_a_country_is_looked_at_from`
//! asserts the DRAWN PREDICATE (`layer::lod_visible`). That is the right unit test and it is
//! still one step short of the claim: `lod_visible` returning `true` is a statement about a
//! comparison, and what the owner reported is a statement about a canvas. So this file paints
//! the picture and counts the ink.
//!
//! # What it measures
//!
//! One straight `railway=rail` line across a pane at camera **9.33** — the exact reading off
//! `$KORP_STATE.map.scene.applied.layers` on his live thin client, where the `rails` layer
//! was fed **7 217** ways and drew **0**. The measurement is the count of pixels whose hue
//! is `MapPalette::rail()` (the ink the new arm resolves), inside the canvas rows, against a
//! reference render with no rail layer at all.
//!
//! # Why the CPU painter, deliberately
//!
//! It is not a degraded mode. It is what draws under a bearing, under a tilt, on a host whose
//! adapter bring-up was refused, and in every `egui_kittest` harness test in this repo — and
//! it is what makes this file runnable with no GPU and no lock to contend for. `cpu_casing.rs`
//! makes the same argument at greater length and this file follows its harness exactly rather
//! than growing a second one.
//!
//! # THE RED
//!
//! Before `classify_with` grew its `railway` arm, `railway=rail` fell through to the generic
//! arm's `Lod::City` (camera 12) and the ink count at camera 9.33 was **0** — the pane
//! painted land and nothing else, which is what "fed 7 217, drew 0" looks like as a picture.
//!
//! Run: `cargo test -p facett-map --test rail_tier_pixels -- --test-threads=1`

use facett_map::layer::{LatLon, MapFacet, MapTheme, MapView, WaysLayer};
use facett_map::Way;

// **This file SIGSEGVs under the default test runner and always has** — two
// `Harness::render()` calls on two threads, one wgpu device each. MEASURED 2026-08-27:
// `signal: 11, SIGSEGV` on `cargo test -p facett-map --test rail_tier_pixels`, 2 passed at
// `--test-threads=1`. The header's "Run: … --test-threads=1" is true and reaches nobody who
// types a plain `cargo test`, so the flag is now a lock.
#[path = "common/one_gpu.rs"]
mod one_gpu;

const CANVAS: egui::Vec2 = egui::vec2(900.0, 640.0);

/// **The camera off `$KORP_STATE`.** 9.33, tier `District` — the zoom at which a Swede
/// looking for the rail network is looking at the rail network.
const HIS_CAMERA: f32 = 9.33;

/// A dead-straight east-west trunk line across the middle of the pane.
///
/// Straight and horizontal so the band is a clean run of pixels in any vertical column, and
/// long enough (0.4° of longitude, ~22 km) that it crosses the whole canvas at this zoom
/// rather than ending inside it.
///
/// The width is bumped the way `cpu_casing.rs` bumps its road's and for the same reason: at
/// the arm's own 1.5 px a rail's core is narrower than its antialias feather, so no pixel
/// comes back PURE `rail()` and a hue count measures the blend rather than the ink. The TIER
/// is what this file is about, and the tier is untouched by the width.
fn trunk_line(pal: &facett_map::MapPalette) -> Vec<Way> {
    let lat = 59.32;
    let pts: Vec<(f64, f64)> = (0..=20).map(|i| (17.90 + f64::from(i) * 0.02, lat)).collect();
    let tags = vec![("railway".to_owned(), "rail".to_owned())];
    let mut w = Way::from_lonlat_with(1, &pts, &tags, pal).expect("a straight rail line");
    w.style.width = 12.0;
    vec![w]
}

/// Render a pane at `zoom`, with the rail layer or without it.
///
/// **One theme for both halves**, the trap `cpu_casing.rs` documents: a way's colours are
/// resolved at construction from the palette handed to `Way::from_lonlat_with`, while the
/// pane paints with `MapTheme`'s own palette. Building the way from one and the pane from
/// another gives a correct picture and a test looking for the wrong hue.
fn render(zoom: f32, with_rail: bool) -> (MapTheme, image::RgbaImage) {
    let theme = facett_core::Theme::default();
    let map_theme = MapTheme::day(&theme);
    let pal = map_theme.palette;
    let view = MapView::new(LatLon::new(59.32, 18.10), zoom, (CANVAS.x, CANVAS.y));
    let mut f = MapFacet::new("rail-tier-pixels", view, map_theme.clone());
    if with_rail {
        f.push_layer(Box::new(WaysLayer::new("net-rails", trunk_line(&pal))));
    }
    let mut h = egui_kittest::Harness::builder().with_size(CANVAS).build_ui(move |ui| {
        f.ui(ui);
    });
    h.run_steps(3);
    (map_theme, h.render().expect("the CPU painter rendered into a framebuffer"))
}

/// Chebyshev distance between an sRGB triple and a colour.
fn dist(a: [u8; 3], b: egui::Color32) -> i32 {
    (i32::from(a[0]) - i32::from(b.r()))
        .abs()
        .max((i32::from(a[1]) - i32::from(b.g())).abs())
        .max((i32::from(a[2]) - i32::from(b.b())).abs())
}

/// The rows of the image that are MAP CANVAS — mostly the land colour.
///
/// The harness draws the facet inside its own chrome and that chrome is pure black; without
/// finding the canvas first, a census counts the frame. `cpu_casing.rs` was measured wrong
/// exactly this way once (a 4.5 px road reported as a 640 px band).
fn canvas_rows(img: &image::RgbaImage, land: egui::Color32) -> (u32, u32) {
    let mut rows: Vec<u32> = Vec::new();
    for y in 0..img.height() {
        let n = (0..img.width())
            .filter(|&x| {
                let p = img.get_pixel(x, y).0;
                dist([p[0], p[1], p[2]], land) <= 6
            })
            .count();
        if n as f32 > img.width() as f32 * 0.5 {
            rows.push(y);
        }
    }
    assert!(!rows.is_empty(), "no row of this render is mostly land — the pane never painted");
    (rows[0], rows[rows.len() - 1])
}

/// Pixels within the canvas rows whose hue is `ink`, within `tol`.
fn ink_count(img: &image::RgbaImage, ink: egui::Color32, rows: (u32, u32), tol: i32) -> u64 {
    let mut n = 0u64;
    for y in rows.0..=rows.1 {
        for x in 0..img.width() {
            let p = img.get_pixel(x, y).0;
            if dist([p[0], p[1], p[2]], ink) <= tol {
                n += 1;
            }
        }
    }
    n
}

/// **A NATIONAL RAIL LINE PUTS INK ON THE CANVAS AT CAMERA 9.33.**
///
/// Three renders, because one number is not a measurement:
///
/// 1. **with the rail layer, at his camera** — the claim. Ink must be there.
/// 2. **without the rail layer, same camera** — the reference. Whatever the palette's rail
///    hue happens to coincide with elsewhere on an empty pane is subtracted, so "the land is
///    a shade of grey too" cannot be what passes this.
/// 3. **with the rail layer, at camera 5** — one rung below the tier ladder's start for
///    `Lod::Country`… which is 0, so this render must ALSO have ink. It is the non-vacuity
///    for the tier being `Country` and not merely "some tier that happens to include 9.33":
///    a trunk line is a country-scale object and must survive a country-scale camera.
#[test]
fn a_trunk_rail_line_paints_at_his_camera_and_at_a_country_camera() {
    let _gpu = one_gpu::hold();
    let (theme, with) = render(HIS_CAMERA, true);
    let (_, without) = render(HIS_CAMERA, false);
    let ink = theme.palette.rail();
    let land = theme.palette.land;

    let rows_with = canvas_rows(&with, land);
    let rows_without = canvas_rows(&without, land);

    // Tolerance 10, the same order `cpu_casing.rs` uses for a hue assert: wide enough that a
    // pixel at the band's core counts, narrow enough that the land (a very different value —
    // asserted below) cannot.
    let n_with = ink_count(&with, ink, rows_with, 10);
    let n_without = ink_count(&without, ink, rows_without, 10);

    assert!(
        dist([land.r(), land.g(), land.b()], ink) > 20,
        "the rail ink {ink:?} and the land {land:?} are within 20 of each other — the census \
         below cannot tell them apart, and a passing count would mean nothing"
    );
    assert_eq!(
        n_without, 0,
        "an empty pane already carries {n_without} px of the rail hue — the reference render \
         is not a reference"
    );
    assert!(
        n_with > 500,
        "the rail layer put {n_with} px of {ink:?} on the canvas at camera {HIS_CAMERA}. \
         On the owner's live client this layer was fed 7 217 ways and drew 0: `railway=*` fell \
         through `classify_with` to the generic arm's Lod::City, a tier LOD_TIER_ZOOMS does \
         not enter until camera 12, so every rail way was culled at every camera below it."
    );

    // ── AND AT A COUNTRY CAMERA. The tier is `Lod::Country`, floor 0, which is korp's own
    // first Rails rung — so the line has to survive a camera three rungs wider than his.
    //
    // **The number here is a FOOTPRINT, not a band.** At camera 5 the fixture's 0.4° of
    // longitude is ~9 px long (0.4/360 × 256·2⁵), so what lands on the canvas is a 9 px blob,
    // `line_width_px(12, zoom 5)` tall — **6 px** since GG #27 made the CPU lane read the one
    // width formula (0.5× floor at country scale) instead of a pinned 1.0×. The old threshold
    // of 100 was cleared by a 9 × 12 blob by a hair and failed the 9 × 6 one at 60, which is
    // the line being DRAWN THINNER at a wide camera, not the line being culled — exactly the
    // distinction this arm exists to make. So: a reference render with no rail at this camera
    // must carry no ink, and the rail render must carry more than a third of the 9 × 6
    // footprint. A culled line reads 0 here; a tier that stops short of Country reads 0 here.
    let (_, country) = render(5.0, true);
    let (_, country_without) = render(5.0, false);
    let rows_country = canvas_rows(&country, land);
    let n_country = ink_count(&country, ink, rows_country, 10);
    let n_country_without = ink_count(&country_without, ink, canvas_rows(&country_without, land), 10);
    assert_eq!(
        n_country_without, 0,
        "an empty pane at camera 5 already carries {n_country_without} px of the rail hue"
    );
    assert!(
        n_country > 18,
        "at camera 5 the trunk line put only {n_country} px on the canvas (a 9 px long, \
         6 px tall footprint is ~54). Lod::Country means every camera there is; a tier that \
         merely happens to include 9.33 would pass the assertion above and read 0 here."
    );
}

/// **A YARD IS NOT A TRUNK LINE — the declutter, in pixels.**
///
/// The test above would pass on a build that lifted every `railway=*` value to
/// `Lod::Country`, which is the clutter defect from the other side: `rails.parquet` carries
/// platforms, sidings, turntables and signal boxes, and korp's own ladder holds them to
/// camera 12. So the same pane, the same camera, one tag changed: the platform must put NO
/// ink on the canvas at 9.33 and must put ink on it at 12.
#[test]
fn a_platform_stays_off_the_canvas_until_the_camera_earns_it() {
    let _gpu = one_gpu::hold();
    let theme = facett_core::Theme::default();
    let map_theme = MapTheme::day(&theme);
    let pal = map_theme.palette;
    let ink = pal.rail();
    let land = pal.land;

    let paint = |zoom: f32, value: &str| -> u64 {
        let lat = 59.32;
        let pts: Vec<(f64, f64)> = (0..=20).map(|i| (17.90 + f64::from(i) * 0.02, lat)).collect();
        let tags = vec![("railway".to_owned(), value.to_owned())];
        let mut w = Way::from_lonlat_with(1, &pts, &tags, &pal).expect("a straight rail way");
        w.style.width = 12.0;
        let view = MapView::new(LatLon::new(59.32, 18.10), zoom, (CANVAS.x, CANVAS.y));
        let mut f = MapFacet::new("rail-tier-pixels", view, map_theme.clone());
        f.push_layer(Box::new(WaysLayer::new("net-rails", vec![w])));
        let mut h = egui_kittest::Harness::builder().with_size(CANVAS).build_ui(move |ui| {
            f.ui(ui);
        });
        h.run_steps(3);
        let img = h.render().expect("the CPU painter rendered into a framebuffer");
        let rows = canvas_rows(&img, land);
        ink_count(&img, ink, rows, 10)
    };

    let platform_at_his_camera = paint(HIS_CAMERA, "platform");
    let platform_at_city = paint(12.0, "platform");
    let trunk_at_his_camera = paint(HIS_CAMERA, "rail");

    assert_eq!(
        platform_at_his_camera, 0,
        "a railway=platform put {platform_at_his_camera} px on the canvas at camera \
         {HIS_CAMERA}. korp holds platforms, sidings and yards to camera 12; a painter that \
         draws them at 9 is the clutter defect this ladder exists to prevent."
    );
    assert!(
        platform_at_city > 500,
        "…and at camera 12 the platform drew only {platform_at_city} px. A tier that never \
         opens is a wall, not a rung."
    );
    assert!(
        trunk_at_his_camera > 500,
        "the trunk line drew {trunk_at_his_camera} px at the same camera the platform drew \
         none — this is the pair that proves the ladder is a ladder and not one threshold"
    );
}