facett-map3d/tests/the_design_mapping_does_not_staircase.rs
//! **A smoothly moving body must land on a smoothly moving design point** — the root
//! cause of *"korpen fladdrar i 3D fågelläge"*.
//!
//! # What flickered, and why nothing caught it
//!
//! A footprint vertex goes through `extrude::to_design` ONCE, at build time, and then
//! sits still; f32 costs it nothing. The rider's eye and the raven's overlay pose are a
//! different animal — `Map3D::design_from_lonlat` re-derives them from moving f64
//! lon/lat on EVERY frame. So a rounding step in the middle of that derivation is not a
//! static error, it is a staircase the pose walks up and down while the body glides.
//!
//! The old code cast the Mercator coordinate to f32 **before** the bbox subtraction, and
//! one f32 ULP of Mercator y at 59.33° N is `2.98e-8 unit × 20.44e6 m/unit` = **0.61 m
//! of ground**. A korp body cruising at ~45 m/s advances ~0.75 m per 60 Hz frame — the
//! same order as the tread — so the eye and the korp kept landing on the same step, then
//! two steps at once, and each on its own schedule. Measured over 600 flying frames in a
//! korp harness: **60 one-frame reversals (10.0%) in the eye→korp distance and 52 (8.7%)
//! in the overlay's depth, while the body's own f64 `chase_lag_m` reversed 0 times.**
//! On the real wgpu lane the raven's centroid jittered with a per-frame standard
//! deviation of **9.29 px**, with **35.6% of frames moving it more than 10 px**, under a
//! seat whose entire job is to hold her still.
//!
//! `facett-map3d/tests/overlay_frame_stability.rs` could never see it: it renders
//! `Map3D::synthetic`, which has no `geo_bbox`, so `design_from_lonlat` returns `None`
//! and this whole path is structurally unreachable there. That guard is not weak — it
//! guards a different road.
//!
//! # What this file asserts
//!
//! Two shapes of the same staircase, each measured on the shipped mapping AND on the old
//! spelling in the same run — the red half is the arithmetic that shipped, reproduced
//! against the same scene's bbox, not a mock of it.
//!
//! * **Step unevenness**: equal real steps must arrive as equal design steps. MEASURED
//! here — old `0.812` of the mean step, shipped `0.00017`, a factor of ~4 800.
//! * **Formation gap**: two bodies holding station must hold a steady design-space gap.
//! MEASURED — old `0.609 m` of breathing, shipped `0.0015 m`. That 0.609 m is the
//! 0.61 m ULP tread arriving intact, which is what makes the diagnosis a measurement
//! rather than a story.
mod common;
use facett_map3d::Map3D;
const STOCKHOLM: (f64, f64) = (18.0686, 59.3293);
/// The ONE writer of the flat-Earth ruler these fixtures are built on: the extruder's
/// own [`facett_map3d::extrude::M_PER_DEG_LAT`]. A fixture built on a private copy of
/// 111_320 measures a DIFFERENT earth from the code under test the moment either side is
/// edited — and the copies agree until then, which is exactly how this survived in nine
/// files (`granska-20`).
use facett_map3d::extrude::M_PER_DEG_LAT;
/// One 60 Hz frame of a korp at cruise — the step size the tread has to beat.
const STEP_M: f64 = 0.75;
const FRAMES: usize = 600;
/// The design z of a northward walk through the OLD spelling, at an arbitrary lateral
/// offset — so the same helper serves both the korp and the seat behind her.
fn walk_old_at(m: &Map3D, back_m: f64) -> Vec<f32> {
let (lon0, lat0, lon1, lat1) = m.geo_bbox().expect("a geo scene has a bbox");
let (ax, ay) = facett_map::mercator(lon0, lat0);
let (bx, by) = facett_map::mercator(lon1, lat1);
let bb = (
ax.min(bx) as f32,
ay.min(by) as f32,
ax.max(bx) as f32,
ay.max(by) as f32,
);
(0..FRAMES)
.map(|i| {
let lat = STOCKHOLM.1 + (i as f64 * STEP_M - back_m) / M_PER_DEG_LAT;
let (mx, my) = facett_map::mercator(STOCKHOLM.0, lat);
facett_map3d::extrude::to_design(mx as f32, my as f32, bb).1
})
.collect()
}
/// The same, through the shipped mapping.
fn walk_new_at(m: &Map3D, back_m: f64) -> Vec<f32> {
(0..FRAMES)
.map(|i| {
let lat = STOCKHOLM.1 + (i as f64 * STEP_M - back_m) / M_PER_DEG_LAT;
m.design_from_lonlat(STOCKHOLM.0, lat).expect("placeable")[1]
})
.collect()
}
/// How unevenly a walk of equal real steps advances, as a fraction of its mean step.
/// A perfect mapping returns ~0; a quantised one returns the tread divided by the step.
fn step_unevenness(v: &[f32]) -> f64 {
let steps: Vec<f64> = v.windows(2).map(|w| f64::from(w[1] - w[0]).abs()).collect();
let mean = steps.iter().sum::<f64>() / steps.len() as f64;
let lo = steps.iter().copied().fold(f64::INFINITY, f64::min);
let hi = steps.iter().copied().fold(f64::NEG_INFINITY, f64::max);
(hi - lo) / mean
}
/// The metres a formation gap breathes over the walk.
fn gap_swing_m(m: &Map3D, korp: &[f32], eye: &[f32]) -> f64 {
let du_per_m = m.design_units_per_metre().expect("a geo scene has a scale");
let gap: Vec<f32> = korp.iter().zip(eye).map(|(k, e)| e - k).collect();
let lo = gap.iter().copied().fold(f32::INFINITY, f32::min);
let hi = gap.iter().copied().fold(f32::NEG_INFINITY, f32::max);
f64::from(hi - lo) / f64::from(du_per_m)
}
/// **A body moving at a constant speed must advance by a constant step.**
///
/// The old mapping quantised to a 0.61 m tread while the body advanced 0.75 m a frame,
/// so the design point lurched one tread on some frames and two on others — the same
/// real velocity arriving as an uneven one.
#[test]
fn a_constant_velocity_maps_to_a_constant_design_step() {
let m = Map3D::from_geometry("staircase", &common::town(1_000.0));
assert!(m.geo_bbox().is_some(), "precondition: the scene has a geographic span");
let old = step_unevenness(&walk_old_at(&m, 0.0));
let new = step_unevenness(&walk_new_at(&m, 0.0));
eprintln!(
"DESIGN STEP unevenness over {FRAMES} frames of {STEP_M} m: \
old(f32-first) {old:.3} · shipped(f64-subtract) {new:.5}",
);
// RED: the old spelling really does lurch. If this stops firing, the f32 cast costs
// nothing on this fixture and the green half has become a tautology.
assert!(
old > 0.5,
"the old f32-first mapping must visibly lurch or this guard is blind: {old:.3}",
);
assert!(
new < old / 20.0,
"the shipped mapping must advance evenly: {new:.5} against the old {old:.3}",
);
}
/// **The separation between two bodies moving TOGETHER must not twitch.**
///
/// This is the flicker as the eye actually meets it: the seat holds a fixed offset
/// behind the korp, so their design-space distance should be constant to the millimetre.
/// On the old mapping the two landed on different treads on different frames and the gap
/// breathed — a wing beat nobody asked for, on a bird already flapping.
#[test]
fn two_bodies_holding_formation_keep_a_steady_gap() {
let m = Map3D::from_geometry("formation", &common::town(1_000.0));
/// The rider sits this far behind her; the gap is what must stay still.
const BACK_M: f64 = 12.0;
let old = gap_swing_m(&m, &walk_old_at(&m, 0.0), &walk_old_at(&m, BACK_M));
let new = gap_swing_m(&m, &walk_new_at(&m, 0.0), &walk_new_at(&m, BACK_M));
eprintln!("FORMATION gap swing: old {old:.3} m · shipped {new:.4} m");
// RED first: the old mapping must be seen breathing, or the green half proves
// nothing. The tread is 0.61 m and the two bodies quantise independently, so the
// gap swings by about that much.
assert!(
old > 0.3,
"the old mapping must visibly breathe or this guard is blind: {old:.3} m",
);
assert!(
new < 0.01,
"two bodies in formation must hold a steady design-space gap: it breathed \
{new:.4} m",
);
}