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

nordisk/facett

facett-demo/tests/osm_tabs.rs

//! **OSM tab non-blank render PROOF** (CPU path, headless — no GPU in CI).
//!
//! The "200k points in state but the pane is BLACK" class of bug: a map view can
//! report a fat `state_json` (lots of points/ways) yet draw *nothing* (a blank
//! pane). This test renders both OSM tabs through facett's **CPU** headless
//! harness (egui's software tessellator — no wgpu, so it runs on a GPU-less CI
//! box) and asserts that real geometry actually reached the tessellator:
//! `vertices > 0` **and** the view's own `state_json` reports the geometry it
//! should be drawing (`geometry > 0`). A blank pane → `vertices == 0` → RED.
//!
//! It runs on **real liechtenstein GeoParquet** when present on `/home` (the
//! default the demo ships with), and otherwise on the synthetic in-memory sample
//! so the test always runs and always asserts. Either way each tab emits one
//! `nornir_testmatrix::functional_status` row (the EMIT-DOCTRINE "did the map
//! render non-blank" check) under `--features testmatrix`.
//!
//! Run:
//!   cargo test -p facett-demo --test osm_tabs
//!   cargo test -p facett-demo --test osm_tabs --features testmatrix   # feeds the matrix

use facett::Facet;
use facett::core::harness;
use facett_demo::osm_tabs::{self, OsmSource};

/// Render a facet through the CPU headless harness and return `(vertices, geometry,
/// state)`. `geometry` is the view's own count of what it should draw — points +
/// ways for 2D, triangles for 3D — read from its `state_json`.
fn render_geometry(facet: &mut dyn Facet, geom_keys: &[&str]) -> (usize, usize, serde_json::Value) {
    let r = harness::headless_render(facet);
    let geometry: usize = geom_keys
        .iter()
        .filter_map(|k| r.state.get(*k).and_then(|v| v.as_u64()))
        .map(|n| n as usize)
        .sum();
    (r.vertices, geometry, r.state)
}

#[test]
fn osm_2d_and_3d_tabs_render_non_blank() {
    // Load the real liechtenstein data if it's on /home (the demo's default),
    // else the synthetic sample. Either way we render + assert non-blank.
    let geo = osm_tabs::load();
    let on_real = geo.source == OsmSource::Liechtenstein || geo.source == OsmSource::Env;
    eprintln!(
        "osm_tabs test: source = {:?} ({} points, {} ways)",
        geo.source,
        geo.points.len(),
        geo.ways_2d.len()
    );

    // ── 2D OSM tab ────────────────────────────────────────────────────────────
    // Force the CPU painter lane for the headless proof: the GPU line-ways
    // fast-path emits a paint callback that no-ops without an installed renderer
    // (no GPU in CI), which would leave the line layer undrawn in the harness. The
    // CPU lane is the one we can PROVE non-blank here; the GPU lane is verified on
    // a real GPU box (see the report). `with_gpu` only exists under `map-wgpu`.
    let mut v2 = osm_tabs::osm2d_view(&geo);
    #[cfg(feature = "map-wgpu")]
    {
        v2 = v2.with_gpu(false);
    }
    let (verts2, geom2, st2) = render_geometry(&mut v2, &["points", "ways"]);
    eprintln!("  OSM 2D → {verts2} verts, geometry={geom2}, visible_points={}", st2["visible_points"]);
    // The non-blank contract: real tessellated vertices AND real geometry behind
    // them (this is exactly what catches "fat state, black pane").
    assert!(geom2 > 0, "OSM 2D has geometry to draw (points + ways)");
    assert!(verts2 > 0, "OSM 2D tessellated to vertices — the pane is NOT blank");
    // After the camera fits the data, some points fall inside the viewport (so the
    // dots layer actually drew, not just the background rect).
    assert!(
        st2["visible_points"].as_u64().unwrap() > 0,
        "OSM 2D shows points after the fit (the dot layer is live, not a black pane)"
    );

    // ── 3D OSM tab ────────────────────────────────────────────────────────────
    let mut v3 = osm_tabs::osm3d_view(&geo);
    let (verts3, geom3, st3) = render_geometry(&mut v3, &["triangles"]);
    eprintln!(
        "  OSM 3D → {verts3} verts, geometry={geom3} tris, visible_triangles={}",
        st3["visible_triangles"]
    );
    assert!(geom3 > 0, "OSM 3D extruded a real mesh (triangles)");
    assert!(verts3 > 0, "OSM 3D tessellated to vertices — the pane is NOT blank");
    assert!(
        st3["visible_triangles"].as_u64().unwrap() > 0,
        "OSM 3D camera projected visible triangles (the lit model drew, not a black pane)"
    );

    // If we DID load the real liechtenstein data, prove it's the big real set, not
    // a fallback masquerading as real (353k nodes / 41k ways).
    if on_real {
        assert!(
            st2["points"].as_u64().unwrap() > 100_000,
            "real liechtenstein 2D loaded its ~353k nodes, got {}",
            st2["points"]
        );
        assert!(
            st2["ways"].as_u64().unwrap() > 5_000,
            "real liechtenstein 2D loaded thousands of ways, got {}",
            st2["ways"]
        );
    }

    // EMIT-DOCTRINE: one functional row per tab — "did the map render non-blank".
    // OK iff vertices > 0 AND the view reports geometry (emit_render's contract).
    #[cfg(feature = "testmatrix")]
    {
        facett::core::testmatrix::emit_render("facett_demo.osm_2d", verts2, geom2);
        facett::core::testmatrix::emit_render("facett_demo.osm_3d", verts3, geom3);
        // A second, sharper row carrying the source + the visible-feature count, so
        // the matrix shows the map was alive (visible features), not merely drawn.
        facett::core::testmatrix::emit(
            "facett_demo.osm_2d",
            "renders_non_blank",
            verts2 > 0 && st2["visible_points"].as_u64().unwrap() > 0,
            &format!(
                "source={:?} points={} ways={} visible_points={} verts={verts2}",
                geo.source, st2["points"], st2["ways"], st2["visible_points"]
            ),
        );
        facett::core::testmatrix::emit(
            "facett_demo.osm_3d",
            "renders_non_blank",
            verts3 > 0 && st3["visible_triangles"].as_u64().unwrap() > 0,
            &format!(
                "source={:?} triangles={} visible_triangles={} verts={verts3}",
                geo.source, st3["triangles"], st3["visible_triangles"]
            ),
        );
    }
}