facett-map3d/tests/flight_altitude_is_continuous.rs
//! **A-2: THE FLIGHT CAMERA MUST NOT TELEPORT, AND MUST NOT BE SILENTLY PITCHED.**
//!
//! The owner's ruling, delegated and taken: **make the camera continuous.** A camera that
//! jumps breaks the flight illusion, which is the one thing flight mode is for.
//!
//! Two defects at the same two call sites — `OrbitCamera::update` and
//! `OrbitCamera::settle`, which both end in `clamp_above_ground` and both start with
//! `clamp_elev`. Neither is a bug in those functions: both are ORBIT constraints being
//! applied to a pose a 6DOF rig owns.
//!
//! ## A-2a — the ground clamp teleports the eye, then PINS it
//!
//! `clamp_above_ground` step 1 raises `target.y` to `surface + SURFACE_EPS` when it is
//! below. The eye is DERIVED (`eye = target − fwd·distance`, `fwd.y = −sin(elevation)`),
//! so raising the target by Δ raises the eye by exactly Δ.
//!
//! `Map3D::look_from` parks the target one dolly-length down the view ray
//! (`DOLLY_DU = 2.0`), so nose-down flight puts the target BELOW the eye:
//! `target.y = altitude_m·du_per_m + DOLLY_DU·sin(pitch)`, `sin(pitch) < 0`. The clamp
//! therefore fires whenever
//!
//! ```text
//! altitude_m · du_per_m < SURFACE_EPS − DOLLY_DU · sin(pitch)
//! ```
//!
//! and below that threshold it writes `goal_target.y` too, so
//! `eye.y = SURFACE_EPS − DOLLY_DU·sin(pitch)` **regardless of `altitude_m`** — the eye
//! stops following the commanded height entirely. korp's rig calls `look_from` every
//! frame, so it re-fires every frame.
//!
//! ## A-2b — a second, unconditional jump
//!
//! `clamp_elev(e) = e.clamp(MIN_ELEV = 0.02, ELEV_LIMIT)`. `look_from` writes
//! `elevation = −pitch` unclamped, so **any level or climbing pose is pitched ~1.15°
//! nose-down on the very next frame**, moving the derived eye by `distance·sin(0.02)`.
//!
//! ## The measurement
//!
//! Headless, no GPU, no adapter: sweep the commanded altitude down in 1 m steps at a
//! fixed nose-down pitch and watch where the renderer's own camera actually puts its
//! eye. Three claims, each able to fail on its own:
//!
//! 1. **monotone** — a lower commanded altitude is a lower eye, always;
//! 2. **no teleport** — no step-to-step move larger than 1.05× the commanded step;
//! 3. **tracking** — the eye is at the commanded altitude, to `SURFACE_EPS`.
//!
//! Run: `cargo test -p facett-map3d --test flight_altitude_is_continuous -- --nocapture`
use facett_map3d::{camera::OrbitCamera, Map3D};
mod common;
/// Central Stockholm — the centre `common::town` builds around.
const STOCKHOLM: (f64, f64) = (18.0686, 59.3293);
/// A scene a kilometre across.
const SPAN_M: f64 = 1_000.0;
/// Nose-down, hard enough that the dolly target is well below the eye. This is the
/// regime the 2026-08-22 measurement caught (506 m -> 637 m in ONE frame).
const PITCH: f64 = -0.25;
/// The sweep: 2000 m down to 1 m, one metre at a time.
const TOP_M: i32 = 2_000;
fn scene() -> Map3D {
let m = Map3D::from_geometry("flight-altitude", &common::town(SPAN_M));
assert!(m.geo_bbox().is_some(), "precondition: the scene has a geographic span");
m
}
/// `(commanded altitude in m, the eye height the renderer's camera reports, in m)`.
fn sweep(m: &mut Map3D, pitch: f64) -> Vec<(f64, f64)> {
let m_per_du = f64::from(m.metres_per_design_unit());
let mut out = Vec::with_capacity(TOP_M as usize);
for a in (1..=TOP_M).rev() {
let alt = f64::from(a);
assert!(
m.look_from_lonlat(STOCKHOLM.0, STOCKHOLM.1, alt, 0.0, pitch, 0.0),
"a scene with a geo bbox must accept a geographic pose"
);
// The rig commands a pose every frame and the renderer settles it every frame.
// `settle` is `update` with the damping removed, which is the same two clamps.
m.cam.settle();
out.push((alt, f64::from(m.render_camera().eye().y) * m_per_du));
}
out
}
#[test]
fn descending_flight_lowers_the_eye_by_exactly_what_was_commanded() {
let mut m = scene();
let m_per_du = f64::from(m.metres_per_design_unit());
let du_per_m = 1.0 / m_per_du;
// The predicted threshold, from the geometry above — printed so the sweep's own
// crossing can be compared against the algebra rather than merely observed.
let threshold_m =
(f64::from(OrbitCamera::SURFACE_EPS) - f64::from(Map3D::DOLLY_DU) * PITCH.sin()) * m_per_du;
eprintln!(
"[A-2a] scene: {m_per_du:.1} m/du ({du_per_m:.6} du/m), pitch {PITCH} rad, \
predicted clamp threshold {threshold_m:.1} m"
);
let s = sweep(&mut m, PITCH);
// ── (iii) TRACKING: the eye is where it was told to be.
let (worst_alt, worst_err) = s
.iter()
.map(|(a, e)| (*a, (e - a).abs()))
.fold((0.0, 0.0), |acc, x| if x.1 > acc.1 { x } else { acc });
// The clamp's own epsilon is the only slack a correct implementation needs.
let tol_m = f64::from(OrbitCamera::SURFACE_EPS) * m_per_du;
eprintln!(
"[A-2a] worst tracking error {worst_err:.1} m at a commanded {worst_alt:.0} m \
(tolerance {tol_m:.3} m)"
);
// ── (ii) NO TELEPORT: consecutive commanded metres move the eye a metre.
let mut worst_step = (0.0f64, 0.0f64);
for w in s.windows(2) {
let (a0, e0) = w[0];
let (a1, e1) = w[1];
let commanded = (a0 - a1).abs();
let got = (e0 - e1).abs();
if got > worst_step.1 {
worst_step = (a1, got);
}
let _ = commanded;
}
eprintln!(
"[A-2a] largest single-step eye move {:.1} m (commanded 1 m) at {:.0} m",
worst_step.1, worst_step.0
);
// ── (i) MONOTONE: lower command, lower eye — no flat stretch, no reversal.
let mut flat = Vec::new();
for w in s.windows(2) {
if w[1].1 >= w[0].1 - 1e-9 {
flat.push(w[1].0);
}
}
eprintln!(
"[A-2a] {} of {} steps failed to lower the eye at all (first at {:?} m)",
flat.len(),
s.len() - 1,
flat.first()
);
assert!(
flat.is_empty(),
"(i) the eye must fall whenever the commanded altitude does — {} of {} one-metre \
steps moved it by nothing or upward, starting at {:?} m. Below the clamp \
threshold the eye stops following the command entirely.",
flat.len(),
s.len() - 1,
flat.first()
);
assert!(
worst_step.1 <= 1.05,
"(ii) a one-metre command must not move the eye by {:.1} m — that is a TELEPORT, \
at a commanded altitude of {:.0} m (predicted clamp threshold {threshold_m:.1} m)",
worst_step.1,
worst_step.0
);
assert!(
worst_err <= tol_m,
"(iii) the eye must sit at the altitude it was given: worst error {worst_err:.1} m \
at a commanded {worst_alt:.0} m (tolerance {tol_m:.3} m)"
);
}
/// **A-2b: a level pose must stay level.**
///
/// `look_from` writes `elevation = −pitch`. For `pitch = 0` that is `0.0`, and the orbit
/// band's `MIN_ELEV = 0.02` then pushes it to 1.15° nose-down on the next `settle`. The
/// eye moves by `distance·sin(MIN_ELEV)` for a pose nobody changed.
#[test]
fn a_level_flight_pose_is_not_silently_pitched_nose_down() {
let mut m = scene();
let m_per_du = f64::from(m.metres_per_design_unit());
let alt_m = 800.0;
assert!(m.look_from_lonlat(STOCKHOLM.0, STOCKHOLM.1, alt_m, 0.0, 0.0, 0.0));
let before = m.render_camera();
let (el_before, eye_before) = (before.elevation, before.eye().y);
m.cam.settle();
let after = m.render_camera();
let (el_after, eye_after) = (after.elevation, after.eye().y);
let moved_m = f64::from(eye_after - eye_before).abs() * m_per_du;
eprintln!(
"[A-2b] level pose: elevation {el_before} -> {el_after} rad, eye moved {moved_m:.1} m \
({} m/du)",
m_per_du as i64
);
assert!(
el_after.abs() < 1e-6,
"a level flight pose must stay level — `settle` pitched elevation from \
{el_before} to {el_after} rad ({:.2} degrees nose-down), because the ORBIT \
band's MIN_ELEV was applied to a pose the 6DOF rig owns",
el_after.to_degrees()
);
assert!(
moved_m < 1.0,
"…and the derived eye must not move for a pose nobody changed: it jumped \
{moved_m:.1} m"
);
}
/// **A climbing pose too** — the same clamp, the other side of level.
#[test]
fn a_climbing_flight_pose_keeps_its_nose_up() {
let mut m = scene();
let pitch = 0.20_f64; // nose up
assert!(m.look_from_lonlat(STOCKHOLM.0, STOCKHOLM.1, 800.0, 0.0, pitch, 0.0));
m.cam.settle();
let el = m.render_camera().elevation;
assert!(
(f64::from(el) + pitch).abs() < 1e-5,
"a climbing pose commands elevation {} rad; the camera settled at {el} rad",
-pitch
);
}