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

nordisk/facett

facett-map3d-flight/tests/facet_robot.rs

//! **The FLIGHT viewer's robot scenario + matrix cell.** `facett-map3d-flight`
//! had NO `Facet` at all (`.nornir/osm-3d-gaps.md` row C:69), so its 6DOF rig,
//! floating origin, atmosphere and day cycle were unreachable from a host and
//! unobservable by the matrix. `src/facet.rs` closes that; this drives it.
//!
//! ONE call through the shared generic-`Facet` probe (L5): the pane renders
//! headlessly through the SHARED `facett_map3d` render core (so the pixel oracle
//! — real vertices + non-zero geometry — applies unchanged) and the JSON drive
//! seam is exercised end to end.
//!
//! It also pins the FLIGHT-owned pedestrian WALK mode (osm-3d-gaps row C:72,
//! previously the last open FLIGHT camera gap) with assertions that are each
//! individually RED when the corresponding constraint is removed.

use facett_map3d_flight::{FlightFacet, Locomotion, WALK_EYE_HEIGHT_M, WALK_SPEED_MPS};

#[test]
fn flight_facet_probe_emits_render_and_input_cells() {
    let mut view = FlightFacet::local();

    let script = [
        r#"{"hour":6.5}"#,                                        // dawn
        r#"{"step":{"forward":120.0,"yaw":0.4,"dt":1.0}}"#,       // fly
        r#"{"ground_m":18.0}"#,                                   // the host's DEM
        r#"{"locomotion":"walk"}"#,                               // land and walk
        r#"{"step":{"forward":50.0,"dt":2.0}}"#,                  // walk (capped)
    ];
    let probe = facett_core::testmatrix::probe_and_emit("facett-map3d-flight", &mut view, &script);

    // PIXEL oracle — the pane draws through the shared render core.
    assert!(probe.drew(), "the FLIGHT pane must tessellate real vertices");
    assert!(probe.cardinality() > 0, "state_json carries the render core's geometry");
    assert!(probe.responded(), "update_json must drive state — a dead seam is a regression");

    // STATE oracle — exact transitions, not "something moved".
    assert_eq!(probe.initial_state["locomotion"], "fly");
    assert!((probe.initial_state["altitude_m"].as_f64().unwrap() - 400.0).abs() < 1e-6);
    assert!((probe.steps[0].state["hour"].as_f64().unwrap() - 6.5).abs() < 1e-5);
    assert!(
        probe.steps[0].state["sun_elevation_deg"].as_f64().unwrap()
            < probe.initial_state["sun_elevation_deg"].as_f64().unwrap(),
        "06:30 has a lower sun than midday"
    );
    assert_eq!(probe.steps[3].state["locomotion"], "walk");

    // Landing on the walk lane pins the eye at pedestrian height above the
    // host-supplied ground — the whole point of the mode.
    let eye = probe.steps[3].state["eye_height_m"].as_f64().unwrap();
    assert!((eye - WALK_EYE_HEIGHT_M).abs() < 1e-9, "walk plants the eye at {WALK_EYE_HEIGHT_M} m, got {eye}");
    assert!((probe.steps[3].state["altitude_m"].as_f64().unwrap() - (18.0 + WALK_EYE_HEIGHT_M)).abs() < 1e-9);
    // ...and 2 s of "50 m/s forward" is still only a walk.
    let eye_after = probe.steps[4].state["eye_height_m"].as_f64().unwrap();
    assert!((eye_after - WALK_EYE_HEIGHT_M).abs() < 1e-9, "walking does not take off");
}

/// RED-when-broken: the walk speed cap. Remove it and the pane covers 100 m in
/// the 2 s this asserts covers at most 2·`WALK_SPEED_MPS`.
#[test]
fn walking_two_seconds_covers_at_most_two_paces() {
    let mut f = FlightFacet::local();
    f.set_locomotion(Locomotion::Walk);
    let start = f.camera().position;
    facett_core::Facet::update_json(&mut f, r#"{"step":{"forward":50.0,"dt":2.0}}"#);
    let moved = f.camera().position.ground_distance(start);
    assert!(
        moved <= 2.0 * WALK_SPEED_MPS + 1e-9,
        "2 s of walking covers at most {:.2} m, covered {moved:.2} m",
        2.0 * WALK_SPEED_MPS
    );
    assert!(moved > 0.0, "and it does move");
}

/// RED-when-broken: an unknown locomotion name must NOT silently leave the pane
/// in the previous lane while a robot script believes it switched.
#[test]
fn an_unknown_locomotion_name_is_rejected() {
    let mut f = FlightFacet::local();
    facett_core::Facet::update_json(&mut f, r#"{"locomotion":"teleport"}"#);
    assert_eq!(f.locomotion(), Locomotion::Fly, "an unknown lane leaves the pane where it was");
    assert_eq!(facett_core::Facet::state_json(&f)["locomotion"], "fly");
    facett_core::Facet::update_json(&mut f, r#"{"locomotion":"orbit"}"#);
    assert_eq!(f.locomotion(), Locomotion::Orbit);
}

/// The rig's world frame is the one `WorldPos` defines — climbing changes the
/// ALTITUDE the pane publishes, not its latitude. (This is the pane-level view of
/// the up-axis bug fixed in `camera_fly::forward`.)
#[test]
fn climbing_changes_the_published_altitude_not_the_latitude() {
    let mut f = FlightFacet::local();
    let before = facett_core::Facet::state_json(&f);
    facett_core::Facet::update_json(&mut f, r#"{"step":{"up":250.0,"dt":1.0}}"#);
    let after = facett_core::Facet::state_json(&f);
    assert!(
        (after["altitude_m"].as_f64().unwrap() - before["altitude_m"].as_f64().unwrap() - 250.0).abs() < 1e-6,
        "a 250 m climb shows up as 250 m of altitude"
    );
    assert!(
        (after["lat"].as_f64().unwrap() - before["lat"].as_f64().unwrap()).abs() < 1e-12,
        "and moves the aircraft nowhere on the map"
    );
}