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

//! **ROBOT-UI** — drive the LIVE OSM-3D `Map3D` Facet through egui_kittest exactly
//! as the wasm web demo does (CPU painter, the path the live `nordisk.codeberg.page`
//! OSM-3D tab runs), orbit/dolly to the screenshot's failing street-level pose, read
//! back `state_json`, and assert the rendered frame is **CONFINED** (no ink outside
//! the widget rect) AND **non-blank** — a smeared/escaped frame would be a RED row.
//!
//! "You know the drill — robot auto UI." This is the live-surface proof (not an
//! isolated unit call): it builds the real city, allocates the view a SUB-rect of a
//! larger sentinel-filled canvas, drives input, renders, and diffs the surrounding
//! margin band against a no-view baseline. The clip fix (near clip + screen-extent
//! bound + ground-floor elevation) must leave the margin byte-identical.
//!
//! Run: `cargo test -p facett-map3d --features testmatrix --test robot_ui`

use egui::Color32;
use egui_kittest::Harness;
use facett_core::{Facet, Theme};
use facett_map::Way;
use facett_map3d::Map3D;

fn building(id: i64, ring: &[(f64, f64)]) -> Way {
    let tags = vec![("building".to_string(), "yes".to_string())];
    Way::from_lonlat(id, ring, &tags, &Theme::default()).expect("a building way")
}

/// A denser Sigtuna-ish city (the real `from_geometry` demo path: cluster + frame).
fn sigtuna_city() -> Vec<Way> {
    let mut ways = Vec::new();
    let mut id = 1;
    for r in 0..10 {
        for c in 0..10 {
            let bx = 17.70 + c as f64 * 0.0009;
            let by = 59.61 + r as f64 * 0.0009;
            ways.push(building(
                id,
                &[(bx, by), (bx + 0.0005, by), (bx + 0.0005, by + 0.0005), (bx, by + 0.0005), (bx, by)],
            ));
            id += 1;
        }
    }
    ways
}

fn lit_pixels(img: &image::RgbaImage) -> usize {
    let mut hist = std::collections::HashMap::<u32, u32>::new();
    for p in img.pixels() {
        let key = ((p.0[0] as u32 >> 3) << 10) | ((p.0[1] as u32 >> 3) << 5) | (p.0[2] as u32 >> 3);
        *hist.entry(key).or_default() += 1;
    }
    let (modal, _) = hist.iter().max_by_key(|(_, c)| **c).map(|(k, c)| (*k, *c)).unwrap();
    let (mr, mg, mb) = (((modal >> 10) & 0x1f) as i32, ((modal >> 5) & 0x1f) as i32, (modal & 0x1f) as i32);
    let mut lit = 0usize;
    for p in img.pixels() {
        let (r, g, b) = ((p.0[0] >> 3) as i32, (p.0[1] >> 3) as i32, (p.0[2] >> 3) as i32);
        if (r - mr).abs() + (g - mg).abs() + (b - mb).abs() > 3 {
            lit += 1;
        }
    }
    lit
}

/// Drive the live OSM-3D tab to the failing street-level pose and prove the frame
/// is confined to its rect and non-blank. The view is built once OUTSIDE the
/// closures and mutated by the robot (orbit-drag + dolly) so the same camera the
/// user reaches is the one rendered.
#[test]
fn robot_drives_osm3d_to_failing_pose_frame_confined_and_non_blank() {
    const CANVAS: f32 = 600.0;
    const MARGIN: f32 = 90.0;
    let sentinel = Color32::from_rgb(255, 0, 255); // magenta — the map never paints this

    // Build the real city ONCE; the robot orbits/dollies it to the failing pose.
    let make_view = || -> Map3D {
        let mut v = Map3D::from_geometry("OSM 3D", &sigtuna_city());
        v.spin = false;
        // Robot navigation: orbit toward a low heading + dolly all the way in to the
        // street-level pose the screenshot shows (the smear pose). Using the public
        // camera API the live drag/wheel handlers call.
        v.cam.orbit(800.0, -400.0); // big drag: swing azimuth + push elevation DOWN
        v.cam.dolly(600.0, None); // wheel in hard (toward min_distance)
        v.cam.settle();
        v
    };

    let render = |draw_view: bool| -> (image::RgbaImage, serde_json::Value) {
        use std::cell::RefCell;
        use std::rc::Rc;
        let view = Rc::new(RefCell::new(make_view()));
        let v2 = view.clone();
        let mut harness = Harness::builder().with_size(egui::vec2(CANVAS, CANVAS)).build_ui(move |ui| {
            let inner = ui.max_rect().shrink(MARGIN);
            ui.painter().rect_filled(inner, egui::CornerRadius::ZERO, sentinel);
            if draw_view {
                let mut child = ui.new_child(egui::UiBuilder::new().max_rect(inner));
                Facet::ui(&mut *v2.borrow_mut(), &mut child);
            }
        });
        harness.run_steps(3);
        let img = harness.render().expect("headless render produced an image");
        let state = view.borrow().state_json();
        (img, state)
    };

    let (drawn, state) = render(true);
    let (baseline, _) = render(false);
    assert_eq!(drawn.dimensions(), baseline.dimensions());

    // ── read back state_json (the robot reads what the user sees, as data) ──
    eprintln!("[robot_ui] state = {state}");
    assert_eq!(state["title"], "OSM 3D");
    // The robot drove the camera below street-level; clamp_elev must keep it ABOVE
    // ground (elevation floor ≈ 0.02 rad) — never a below-horizon below-ground view.
    let elev_deg = state["camera"]["elevation_deg"].as_f64().expect("elevation in state");
    assert!(elev_deg >= 1.0, "the ground-floor clamp keeps the eye above the terrain (elev={elev_deg:.2}°)");
    // The camera dollied in (close distance) and projected real triangles.
    let dist = state["camera"]["distance"].as_f64().unwrap();
    assert!(dist > 0.0, "camera distance reported");
    let vis = state["visible_triangles"].as_u64().unwrap();
    assert!(vis > 0, "the dolled-in pose still projects visible triangles (vis={vis})");

    // ── (a) non-blank: a lit city fills many pixels ──
    let lit = lit_pixels(&drawn);
    assert!(lit > 300, "the live frame is non-blank (lit_px={lit})");

    // ── (b) CONFINED: the margin band is byte-identical to the no-view baseline —
    //     NO ink escaped the widget rect (a smear would change margin pixels) ──
    let (w, h) = drawn.dimensions();
    let m_px = MARGIN.round() as u32;
    let mut leaked = 0u64;
    let mut band = 0u64;
    for y in 0..h {
        for x in 0..w {
            let outside = x < m_px || x >= w.saturating_sub(m_px) || y < m_px || y >= h.saturating_sub(m_px);
            if !outside {
                continue;
            }
            band += 1;
            if drawn.get_pixel(x, y) != baseline.get_pixel(x, y) {
                leaked += 1;
            }
        }
    }
    assert!(band > 0);
    let frac = leaked as f64 / band as f64;
    eprintln!("[robot_ui] lit_px={lit}, margin band {band}px, leaked {leaked} ({:.4}%), elev={elev_deg:.2}° dist={dist:.2}", frac * 100.0);

    // Write the robot's frame so an agent can Read it.
    let dir = std::env::var_os("NORNIR_WAREHOUSE_ROOT")
        .map(|r| std::path::PathBuf::from(r).join("facett-map3d"))
        .unwrap_or_else(|| std::env::temp_dir().join("facett-map3d"));
    std::fs::create_dir_all(&dir).ok();
    let png = dir.join("robot_ui_failing_pose.png");
    drawn.save(&png).ok();
    eprintln!("[robot_ui] frame written: {}", png.display());

    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-map3d",
        "robot_ui_confined",
        frac < 0.0005 && lit > 300,
        &format!("lit_px={lit} margin_leaked={leaked} frac={:.5} elev_deg={elev_deg:.2} png={}", frac, png.display()),
    );

    assert!(frac < 0.0005, "the live OSM-3D frame is confined — {leaked}px ({:.3}%) escaped the rect", frac * 100.0);
}