facett-map3d/tests/gfx_v2_item6_parity.rs
//! **GFX_V2 item 6 — the parity proof: a 2D map IS a 3D view under constraints.**
//!
//! This is the test file the item exists for. `facett-core::render::view::View` is
//! now the ONE projection writer for `facett-map` (2D ortho Mercator) and
//! `facett-map3d` (3D orbit terrain); this proves the unification is real rather
//! than nominal, and it lives in `facett-map3d` because that is the only crate that
//! can see **all three** of `View`, `facett-map`'s 2D writers and
//! `OrbitCamera` at once (`facett-core` sits below both skins, which is exactly why
//! the guard that used to claim this could not have worked — see
//! `facett_core::render::camera`'s `camera_seam_3d_holds_the_orbit_projection_properties`).
//!
//! ## What is asserted, and how each assertion is kept from being hollow
//!
//! * **The 3D lane did not drift** — the delegated `OrbitCamera` is compared to the
//! pre-unification look-at + hand-assembled matrix, written out verbatim below as
//! an *independent* reference. Comparing `OrbitCamera` to `View` would be a
//! tautology now that it delegates, so it is deliberately not done.
//! * **2D == constrained 3D, in pixels** — the same triangles are rasterized
//! through both paths and the two frame buffers are compared byte for byte, at a
//! non-identity zoom, a non-zero translation, and starting from a genuinely
//! rotated + tilted 3D pose that is then constrained.
//! * **The constraint is load-bearing** — the mutation tests leave the tilt on, and
//! let the terrain height through, and require the parity to go RED.
//! * **The 3D path is not a no-op** — asserted to foreshorten, to run post-FX and to
//! read the heightfield when unconstrained, so "they agree" cannot mean "both do
//! nothing".
use egui::{Color32, Pos2, Rect};
use facett_core::render::view::{PostFx, Projection, Terrain, View};
use facett_map3d::camera::{OrbitCamera, V3};
use facett_map3d::{raster_zbuffer, ShadedTri};
use glam::Vec3;
// ── the scene every parity test shares (non-identity by construction) ─────────
/// A pane that is deliberately **not** square and whose centre is **not** the
/// origin, so a transform that drops the translation cannot pass.
const PANE: (f32, f32) = (800.0, 600.0);
/// Origin-local Mercator ground points. Spread over a real dataset span, and
/// deliberately none of them at the camera target.
/// Spread over a **city-scale** span so that at the test zoom the figure covers a
/// few hundred pixels: a mutation that shifts geometry by a fraction of a percent
/// has to be able to move a measurable number of pixels, or the mutation check would
/// be measuring the rasterizer's grid rather than the transform.
const GROUND: [[f32; 2]; 6] = [
[0.000_00, 0.000_00],
[0.055_00, 0.012_50],
[0.028_00, 0.061_00],
[-0.038_00, 0.033_00],
[0.062_00, 0.048_00],
[-0.044_00, -0.021_00],
];
fn pane_rect() -> Rect {
// Off-centre on purpose: the pane centre is (417, 271), not (400, 300).
Rect::from_min_max(Pos2::new(17.0, -29.0), Pos2::new(817.0, 571.0))
}
/// Rasterize a triangle fan over the projected points into a real pixel buffer —
/// **applied output**, not a state round-trip.
fn rasterize(px: &[(f32, f32)]) -> Vec<Color32> {
let mut tris = Vec::new();
for i in 1..px.len() - 1 {
let p = [
Pos2::new(px[0].0, px[0].1),
Pos2::new(px[i].0, px[i].1),
Pos2::new(px[i + 1].0, px[i + 1].1),
];
tris.push(ShadedTri {
pts: p,
vz: [1.0, 1.0, 1.0],
depth: 1.0 + i as f32 * 0.01,
col: Color32::from_rgb(40 + (i as u8) * 30, 90, 200),
});
}
raster_zbuffer(&tris, PANE.0 as usize, PANE.1 as usize, Pos2::ZERO, 1.0)
}
fn lit(buf: &[Color32]) -> usize {
buf.iter().filter(|c| c.a() != 0).count()
}
// ── 1. the 3D lane did not drift ─────────────────────────────────────────────
/// The pre-unification `OrbitCamera` math, written out verbatim as an
/// **independent** reference: the `normalize(fwd × world_up)` look-at basis and the
/// hand-assembled `P·V`. Nothing here calls the unified core.
fn historical_orbit_reference(cam: &OrbitCamera, aspect: f32) -> ([f32; 3], [f32; 16]) {
// eye = target + distance·(cos e·sin a, sin e, cos e·cos a)
let (se, ce) = cam.elevation.sin_cos();
let (sa, ca) = cam.azimuth.sin_cos();
let eye = [
cam.target.x + ce * sa * cam.distance,
cam.target.y + se * cam.distance,
cam.target.z + ce * ca * cam.distance,
];
// fwd = normalize(target − eye); right = normalize(fwd × up); up = right × fwd
let norm = |v: [f32; 3]| {
let l = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt().max(1e-9);
[v[0] / l, v[1] / l, v[2] / l]
};
let cross = |a: [f32; 3], b: [f32; 3]| {
[a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
};
let dot = |a: [f32; 3], b: [f32; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
let fwd = norm([cam.target.x - eye[0], cam.target.y - eye[1], cam.target.z - eye[2]]);
let right = norm(cross(fwd, [0.0, 1.0, 0.0]));
let up = norm(cross(right, fwd));
let (tx, ty, tz) = (-dot(right, eye), -dot(up, eye), -dot(fwd, eye));
let near = (cam.distance - 2.0).max(0.05);
// READ from the camera, not re-literalled as `distance + 4.0`. What this reference
// guards is the BASIS — the closed-form frame against the cross-product one — and
// the far plane is a shared INPUT to the projection, not part of that identity.
// Transcribing it here made this file fail the day `far_plane` grew its
// `FAR_MIN_DISTANCE` floor (A-2a: the ground-aware flight dolly can be centimetres
// long, and the frustum must not shrink with it) at the two sweep poses whose
// distance is under 2.0 — a drift of 2.58e-3 in the depth row, with the projected
// pixels unmoved at 9.2e-5. One writer instead.
let far = cam.far_plane();
let f = 1.0 / (cam.fov_y * 0.5).tan();
let (sx, sy) = (f / aspect.max(1e-6), f);
let a = far / (far - near);
let b = -far * near / (far - near);
let mut m = [0.0f32; 16];
let mut set = |row: usize, col: usize, v: f32| m[col * 4 + row] = v;
set(0, 0, sx * right[0]);
set(0, 1, sx * right[1]);
set(0, 2, sx * right[2]);
set(0, 3, sx * tx);
set(1, 0, sy * up[0]);
set(1, 1, sy * up[1]);
set(1, 2, sy * up[2]);
set(1, 3, sy * ty);
set(2, 0, a * fwd[0]);
set(2, 1, a * fwd[1]);
set(2, 2, a * fwd[2]);
set(2, 3, a * tz + b);
set(3, 0, fwd[0]);
set(3, 1, fwd[1]);
set(3, 2, fwd[2]);
set(3, 3, tz);
(eye, m)
}
/// **The real cross-crate drift guard** — the one the hollow
/// `camera_seam_3d_matches_orbit_camera_math` claimed to be.
///
/// The delegated `OrbitCamera` must still reproduce the historical look-at math it
/// replaced. The closed-form frame is an *algebraic* identity with the cross-product
/// one, not a bitwise one (`sin`/`cos` vs `cross`/`normalize`), so this asserts a
/// stated relative bound rather than `to_bits` equality — and the bound is justified
/// by measurement: the worst drift observed over the sweep is printed and asserted
/// to be under 1e-5, which at a 600 px viewport is under a hundredth of a pixel.
/// A looser epsilon is NOT acceptable here and the non-vacuity check below pins it.
#[test]
fn the_delegated_orbit_camera_still_matches_the_historical_lookat_math() {
let poses: [(f32, f32, f32, f32); 6] = [
// (azimuth_deg, elevation_deg, distance, fov_deg)
(37.0, 12.0, 3.2, 50.0),
(-115.0, 62.0, 1.4, 35.0),
(223.0, 80.0, 8.0, 65.0),
(91.0, 3.5, 0.6, 50.0),
(180.0, 45.0, 5.0, 50.0),
(-44.0, 88.0, 12.0, 20.0),
];
let mut worst_eye = 0.0f32;
let mut worst_mat = 0.0f32;
let mut worst_px = 0.0f32;
for (az, el, dist, fov) in poses {
let cam = OrbitCamera {
target: V3::new(0.35, 0.10, -0.20),
azimuth: az.to_radians(),
elevation: el.to_radians(),
distance: dist,
fov_y: fov.to_radians(),
..Default::default()
};
let aspect = PANE.0 / PANE.1;
let (ref_eye, ref_m) = historical_orbit_reference(&cam, aspect);
let e = cam.eye();
for (got, want) in [(e.x, ref_eye[0]), (e.y, ref_eye[1]), (e.z, ref_eye[2])] {
worst_eye = worst_eye.max((got - want).abs());
}
let m = cam.view_proj(aspect);
for i in 0..16 {
worst_mat = worst_mat.max((m[i] - ref_m[i]).abs());
}
// And the APPLIED OUTPUT: projected pixels, which is what actually ships.
for g in GROUND {
let p = cam.project(V3::new(g[0], 0.0, g[1]), (417.0, 271.0), 300.0);
if !p.visible {
continue;
}
// Project the same point through the historical matrix.
let v = [g[0], 0.0, g[1], 1.0];
let mut o = [0.0f32; 4];
for row in 0..4 {
o[row] = (0..4).map(|c| ref_m[c * 4 + row] * v[c]).sum();
}
if o[3] <= 1e-6 {
continue;
}
// The matrix lane is NDC-centred; compare the pixel magnitude of the
// offset from centre, which is the quantity both lanes agree on.
let ndc_x = o[0] / o[3];
let ref_px = 417.0 + ndc_x * (PANE.0 * 0.5);
worst_px = worst_px.max((p.x - ref_px).abs());
}
}
println!("[item6] worst |Δeye| = {worst_eye:e}, |Δmatrix| = {worst_mat:e}, |Δpx| = {worst_px:.6}");
assert!(worst_eye < 1e-6, "eye drifted from the historical math by {worst_eye:e}");
assert!(worst_mat < 1e-5, "view_proj drifted from the historical math by {worst_mat:e}");
assert!(worst_px < 0.01, "projected pixels drifted by {worst_px} px");
// NON-VACUITY: the bound is tight enough to catch a real error. A camera one
// degree off must blow straight through it — otherwise the epsilons above
// would be tolerating drift, not measuring its absence.
let a = OrbitCamera {
target: V3::new(0.35, 0.10, -0.20),
azimuth: 37f32.to_radians(),
elevation: 12f32.to_radians(),
distance: 3.2,
..Default::default()
};
let mut b = a.clone();
b.azimuth = 38f32.to_radians();
let (_, mb) = historical_orbit_reference(&b, PANE.0 / PANE.1);
let ma = a.view_proj(PANE.0 / PANE.1);
let off = (0..16).map(|i| (ma[i] - mb[i]).abs()).fold(0.0f32, f32::max);
assert!(off > 1e-3, "a 1-degree error must exceed the tolerance, was only {off:e}");
}
// ── 2. the centrepiece: 2D == constrained 3D, in pixels ──────────────────────
/// **The 2D lane's projected pixels, from an INDEPENDENT reference.**
///
/// This is the pre-unification affine written out verbatim — `facett-map`'s
/// `MapTransform::project` / `project::to_screen` before item 6. It deliberately
/// does NOT call `facett-map`, because those functions now delegate to `View`:
/// comparing them to `View` would be comparing X to X and would prove nothing. The
/// shipping lanes are checked against this same reference in
/// `the_shipping_2d_lanes_are_all_on_the_unified_path`.
fn reference_2d_pixels(px_per_unit: [f32; 2], ref_xy: [f32; 2], centre: (f32, f32)) -> Vec<(f32, f32)> {
GROUND
.iter()
.map(|g| {
(
(g[0] - ref_xy[0]) * px_per_unit[0] + centre.0,
(g[1] - ref_xy[1]) * px_per_unit[1] + centre.1,
)
})
.collect()
}
/// Build the 3D lane's projected pixels via the unified view, constrained.
fn three_d_pixels(v: &View, centre: (f32, f32)) -> Vec<(f32, f32)> {
GROUND
.iter()
.map(|g| {
let p = v.project_ground(*g, centre, PANE.1 * 0.5);
(p.x, p.y)
})
.collect()
}
/// **THE PARITY ASSERTION.** Start from a genuinely rotated + tilted perspective
/// 3D view, constrain it (tilt 0, bearing 0, ortho, `Z = 0`), and it must land
/// *exactly* on `facett-map`'s 2D writer — the same triangles rasterizing to
/// byte-identical frame buffers.
///
/// Every value is deliberately away from the identity: zoom is 3072 px/unit (not
/// 1), the camera target is `(0.0011, 0.0009)` (not the origin), the pane centre is
/// `(417, 271)` (not `(400, 300)` and not `(0, 0)`), and the starting pose is
/// `azimuth 127°, tilt 58°` (not `0, 0`). A parity test sitting on the identity
/// cannot tell a working transform from a dead one.
#[test]
fn two_d_ortho_equals_three_d_constrained_in_projected_pixels() {
let ref_xy = [0.001_10f32, 0.000_90f32];
let centre = (pane_rect().center().x, pane_rect().center().y);
assert!(
(centre.0 - 417.0).abs() < 1e-3 && (centre.1 - 271.0).abs() < 1e-3,
"the pane centre must be off-centre for this test to mean anything, got {centre:?}"
);
// A real 3D view: rotated, tilted, perspective, heightfield, full post stack.
let mut v = View::perspective_3d(
Vec3::new(ref_xy[0], 0.0, ref_xy[1]),
127f32.to_radians(),
58f32.to_radians(),
4.0,
50f32.to_radians(),
);
assert!(!v.is_constrained_2d(), "must start genuinely unconstrained");
assert!(v.post.any() && v.terrain == Terrain::Heightfield, "and with the 3D stack on");
// ── constrain it: THIS is the "2D is a constrained 3D" step ──
v.constrain_2d(PANE.1 * 0.5);
assert!(v.is_constrained_2d(), "the constraint must hold after collapsing");
let Projection::Ortho { .. } = v.projection else { panic!("must be ortho") };
// Override the derived scale with a big, definitely-not-1 map zoom.
let px_per_unit = [3072.0f32, 3072.0f32];
v.projection = Projection::Ortho { px_per_unit };
assert!(px_per_unit[0] > 1000.0, "the zoom must be far from the identity");
let a = reference_2d_pixels(px_per_unit, ref_xy, centre);
let b = three_d_pixels(&v, centre);
// Coordinate parity first, so a failure names the point.
let mut worst = 0.0f32;
for (i, (p, q)) in a.iter().zip(b.iter()).enumerate() {
worst = worst.max((p.0 - q.0).abs()).max((p.1 - q.1).abs());
assert_eq!(p.0.to_bits(), q.0.to_bits(), "x differs at ground point {i}: {p:?} vs {q:?}");
assert_eq!(p.1.to_bits(), q.1.to_bits(), "y differs at ground point {i}: {p:?} vs {q:?}");
}
assert_eq!(worst, 0.0, "the reduction is exact, not approximate");
// NON-IDENTITY: the projection must have actually moved these points apart and
// away from the pane centre, or we compared six copies of one pixel.
let spread = a.iter().fold(0.0f32, |m, p| m.max((p.0 - centre.0).abs().max((p.1 - centre.1).abs())));
assert!(spread > 3.0, "the projected points must spread across the pane, spread was {spread}");
let distinct: std::collections::HashSet<(u32, u32)> =
a.iter().map(|p| (p.0.to_bits(), p.1.to_bits())).collect();
assert_eq!(distinct.len(), GROUND.len(), "every ground point must project distinctly");
// ── APPLIED OUTPUT: rasterize both and compare the frame buffers ──
let fa = rasterize(&a);
let fb = rasterize(&b);
assert!(lit(&fa) > 200, "the 2D lane must actually draw pixels, lit {}", lit(&fa));
assert_eq!(lit(&fa), lit(&fb), "both lanes must light the same pixel count");
assert!(fa == fb, "the two rasterized frames must be byte-identical");
}
// ── 3. the mutations: break the constraint, the parity must go RED ────────────
/// **MUTATION — leave the tilt on.** The constraint is load-bearing: if the parity
/// still held with a non-zero tilt, it would be proving nothing.
///
/// Rather than a magic "N pixels changed" threshold, this asserts the **quantitative
/// prediction** a tilted orthographic view makes: the vertical extent must shrink by
/// exactly `cos(tilt)` and the horizontal extent must not move at all. If the
/// constraint were a no-op the ratio would be 1.0, and the assertion below brackets
/// it tightly enough that it cannot be.
#[test]
fn leaving_the_tilt_on_breaks_the_parity() {
let ref_xy = [0.001_10f32, 0.000_90f32];
let centre = (pane_rect().center().x, pane_rect().center().y);
let px_per_unit = [3072.0f32, 3072.0f32];
let tilt_deg = 12.0f32;
let mut v = View::ortho_2d_map(ref_xy, px_per_unit);
let a = reference_2d_pixels(px_per_unit, ref_xy, centre);
assert!(three_d_pixels(&v, centre) == a, "the constrained view must agree to begin with");
// Violate exactly ONE constraint.
v.tilt = tilt_deg.to_radians();
assert!(!v.is_constrained_2d(), "the predicate must notice");
let b = three_d_pixels(&v, centre);
assert!(b != a, "a {tilt_deg}-degree tilt must move the projection");
// THE PREDICTION: y foreshortens by cos(tilt), x is untouched.
let extent = |p: &[(f32, f32)]| {
let (mut x0, mut x1, mut y0, mut y1) = (f32::MAX, f32::MIN, f32::MAX, f32::MIN);
for q in p {
x0 = x0.min(q.0);
x1 = x1.max(q.0);
y0 = y0.min(q.1);
y1 = y1.max(q.1);
}
(x1 - x0, y1 - y0)
};
let (ax, ay) = extent(&a);
let (bx, by) = extent(&b);
assert!(ax > 200.0 && ay > 150.0, "the figure must be big enough to measure: {ax} x {ay}");
let want = tilt_deg.to_radians().cos();
println!("[item6] tilt {tilt_deg} deg: y extent {ay:.1} -> {by:.1} (ratio {:.5}, cos = {want:.5})", by / ay);
assert!((by / ay - want).abs() < 1e-4, "y must foreshorten by cos(tilt), ratio was {}", by / ay);
assert!((bx - ax).abs() < 1e-3, "x must be untouched by tilt, {ax} -> {bx}");
assert!(want < 0.99, "the mutation must be a real violation, cos was {want}");
// ...and it must move PIXELS, not just coordinates.
let (fa, fb) = (rasterize(&a), rasterize(&b));
assert!(lit(&fa) > 5_000, "the figure must actually cover pixels, lit {}", lit(&fa));
assert!(fa != fb, "the rasterized frames must differ");
let moved = fa.iter().zip(fb.iter()).filter(|(x, y)| x != y).count();
println!("[item6] and it moved {moved} pixels of {}", fa.len());
assert!(moved > 1_000, "only {moved} pixels moved");
}
/// **MUTATION — leave the bearing on.** The other rotational constraint, checked
/// separately so one of them cannot mask the other.
///
/// The quantitative prediction here is that the figure **rotates by exactly the
/// bearing**: the screen-space angle of a chord between two ground points must turn
/// by the bearing angle, and every distance must be preserved (a rotation is rigid).
#[test]
fn leaving_the_bearing_on_breaks_the_parity() {
let ref_xy = [0.001_10f32, 0.000_90f32];
let centre = (pane_rect().center().x, pane_rect().center().y);
let px_per_unit = [3072.0f32, 3072.0f32];
let bearing_deg = 15.0f32;
let mut v = View::ortho_2d_map(ref_xy, px_per_unit);
let a = reference_2d_pixels(px_per_unit, ref_xy, centre);
assert!(three_d_pixels(&v, centre) == a, "must agree while constrained");
v.set_compass_bearing_deg(bearing_deg);
assert!(!v.is_constrained_2d(), "the predicate must notice a {bearing_deg}-degree bearing");
let b = three_d_pixels(&v, centre);
assert!(b != a, "a {bearing_deg}-degree bearing must move the projection");
// THE PREDICTION: rigid rotation by `bearing_deg`, chord lengths preserved.
let chord = |p: &[(f32, f32)], i: usize, j: usize| (p[j].0 - p[i].0, p[j].1 - p[i].1);
let ang = |(x, y): (f32, f32)| y.atan2(x).to_degrees();
let len = |(x, y): (f32, f32)| (x * x + y * y).sqrt();
let mut turned = Vec::new();
for (i, j) in [(0usize, 1usize), (0, 2), (3, 4), (1, 5)] {
let (ca, cb) = (chord(&a, i, j), chord(&b, i, j));
assert!(len(ca) > 50.0, "the chord {i}-{j} must be long enough to measure: {}", len(ca));
assert!(
(len(cb) - len(ca)).abs() < 0.05,
"a rotation is rigid: chord {i}-{j} went {} -> {}",
len(ca),
len(cb)
);
let mut d = ang(cb) - ang(ca);
while d > 180.0 { d -= 360.0 }
while d < -180.0 { d += 360.0 }
turned.push(d);
}
println!("[item6] bearing {bearing_deg} deg turned the chords by {turned:?}");
for d in &turned {
assert!(
(d.abs() - bearing_deg).abs() < 0.05,
"every chord must turn by exactly the bearing, one turned {d}"
);
}
let (fa, fb) = (rasterize(&a), rasterize(&b));
let moved = fa.iter().zip(fb.iter()).filter(|(x, y)| x != y).count();
println!("[item6] and it moved {moved} pixels");
assert!(moved > 1_000, "only {moved} pixels moved");
}
/// **MUTATION — let `Z` through.** GFX_V2's third constraint is `Z(x,y) = 0`. With
/// `Terrain::Flat` a heightfield sample must be *discarded*; with `Heightfield` it
/// must reach the pixel. Driven with a real, non-zero sample.
#[test]
fn letting_the_terrain_height_through_breaks_the_flat_plane() {
let ref_xy = [0.001_10f32, 0.000_90f32];
let centre = (pane_rect().center().x, pane_rect().center().y);
let sample = 0.35f32; // a real hill, in design units
// The 2D view discards it: the projected pixel is identical with and without.
let flat = View::ortho_2d_map(ref_xy, [3072.0, 3072.0]);
assert_eq!(flat.terrain_height(sample), 0.0, "Flat must pin Z to 0");
let flat_lifted = flat.project_world(
Vec3::new(GROUND[2][0], flat.terrain_height(sample), GROUND[2][1]),
centre,
PANE.1 * 0.5,
);
let flat_ground = flat.project_ground(GROUND[2], centre, PANE.1 * 0.5);
assert_eq!(flat_lifted.x.to_bits(), flat_ground.x.to_bits(), "Z cannot reach a 2D pixel");
assert_eq!(flat_lifted.y.to_bits(), flat_ground.y.to_bits(), "Z cannot reach a 2D pixel");
// Now the MUTATION: flip the terrain mode and the same sample MUST move the
// pixel. (Tilted, because a height change under a perfectly top-down ortho
// camera legitimately projects to the same pixel — that is what "plan view"
// means, and a mutation test that ignored it would be testing nothing.)
let mut hf = flat;
hf.terrain = Terrain::Heightfield;
hf.tilt = 40f32.to_radians();
assert_eq!(hf.terrain_height(sample), sample, "Heightfield must pass Z through");
let lifted = hf.project_world(
Vec3::new(GROUND[2][0], hf.terrain_height(sample), GROUND[2][1]),
centre,
PANE.1 * 0.5,
);
let ground = hf.project_ground(GROUND[2], centre, PANE.1 * 0.5);
let dy = (lifted.y - ground.y).abs();
println!("[item6] a {sample}-unit hill moved the pixel {dy:.2} px once Z was let through");
assert!(dy > 10.0, "letting Z through must move the pixel, moved only {dy}");
}
// ── 4. non-vacuity: the 3D path is doing real work when unconstrained ────────
/// "2D and constrained-3D agree" would be worthless if the 3D path were itself a
/// no-op. This asserts the unconstrained 3D mode genuinely foreshortens, genuinely
/// reads the heightfield, and genuinely asks for the post stack — while the 2D mode
/// does none of those.
#[test]
fn the_unconstrained_three_d_path_is_not_a_no_op() {
let centre = (417.0f32, 271.0f32);
let half_h = PANE.1 * 0.5;
let v3 = View::perspective_3d(Vec3::ZERO, 0.0, 55f32.to_radians(), 3.0, 50f32.to_radians());
let v2 = View::ortho_2d_map([0.0, 0.0], [3072.0, 3072.0]);
// PERSPECTIVE: two points at the same lateral offset but different depths must
// NOT project to the same lateral pixel offset. Under ortho they must.
let (near, far) = (Vec3::new(0.2, 0.0, 0.6), Vec3::new(0.2, 0.0, -0.6));
let (pn, pf) = (v3.project_world(near, centre, half_h), v3.project_world(far, centre, half_h));
assert!(pn.visible && pf.visible, "both probes must be inside the 3D frustum");
let conv = ((pn.x - centre.0).abs() - (pf.x - centre.0).abs()).abs();
println!("[item6] perspective convergence over 1.2 units of depth: {conv:.2} px");
assert!(conv > 5.0, "perspective must foreshorten, converged only {conv} px");
let (on, of) = (v2.project_world(near, centre, half_h), v2.project_world(far, centre, half_h));
assert_eq!(on.x.to_bits(), of.x.to_bits(), "ortho must NOT foreshorten laterally");
// TERRAIN + POST: the modes must genuinely differ in what they ask the
// renderer to do, which is why 2D costs nothing extra.
assert_eq!(v3.terrain, Terrain::Heightfield, "3D reads the heightfield");
assert_eq!(v2.terrain, Terrain::Flat, "2D pins it flat");
assert_eq!(v3.post, PostFx::ALL, "3D runs the post stack");
assert_eq!(v2.post, PostFx::NONE, "2D bypasses it");
assert!(v3.post.any() && !v2.post.any(), "and `any()` must separate them");
}
// ── 5. the drift the unification EXPOSED, measured and pinned ────────────────
/// **The two skins turn `bearing_deg` opposite ways.** Found by building the
/// unified core, because nothing had ever compared the two cameras.
///
/// At the same nominal `bearing_deg = 90`, a point due north moves LEFT in
/// `facett-map`'s 2D camera and RIGHT in `facett-map3d`'s 3D camera. By compass
/// convention the 2D one is right: facing east puts north on your left. The 3D
/// camera's `bearing_deg` returns the turntable azimuth unchanged, and at azimuth
/// 90° the eye sits due *east* of the target — so the camera faces WEST while
/// reporting a heading of east.
///
/// This test does not fix it: flipping `OrbitCamera`'s sign would rotate a shipping
/// car-nav compass overlay and its route-follow, which is a product decision, not a
/// refactor. It pins the divergence with measured numbers so it cannot drift
/// further or be silently "fixed" in one skin only. `View::compass_bearing_deg`
/// exposes the correct clockwise sense for callers that want it.
#[test]
fn the_two_skins_disagree_about_which_way_bearing_turns() {
// ── 2D: facett-map's camera ──
let v2 = facett_map::layer::MapView::new(
facett_map::layer::LatLon { lon: 17.6, lat: 59.6 },
12.0,
(800.0, 600.0),
)
.with_bearing(90.0);
let c2 = v2.project(17.6, 59.6);
let n2 = v2.project(17.6, 59.7); // due north
let dx2 = n2.0 - c2.0;
// ── 3D: facett-map3d's camera, same nominal bearing ──
let mut cam = OrbitCamera { target: V3::new(0.0, 0.0, 0.0), ..Default::default() };
cam.set_bearing_deg(90.0);
cam.set_tilt_deg(60.0);
cam.settle();
let c3 = cam.project(V3::new(0.0, 0.0, 0.0), (400.0, 300.0), 300.0);
let n3 = cam.project(V3::new(0.0, 0.0, -1.0), (400.0, 300.0), 300.0); // due north (Z is south)
assert!(c3.visible && n3.visible, "both 3D probes must be visible");
let dx3 = n3.x - c3.x;
println!("[item6] bearing 90: north moves {dx2:+.1} px in 2D and {dx3:+.1} px in 3D");
assert!(dx2 < -50.0, "2D must put north to the LEFT at bearing 90 (compass-correct), got {dx2}");
assert!(dx3 > 50.0, "3D puts north to the RIGHT at bearing 90 (the divergence), got {dx3}");
assert!(dx2.signum() != dx3.signum(), "the senses are opposite — this is the finding");
// And the unified core names it precisely: azimuth 90 == compass bearing 270.
let v = View::perspective_3d(Vec3::ZERO, 90f32.to_radians(), 0.5, 3.0, 0.9);
assert!((v.azimuth_deg() - 90.0).abs() < 1e-3, "azimuth is 90");
assert!(
(v.compass_bearing_deg() - 270.0).abs() < 1e-3,
"and the true compass heading is 270, got {}",
v.compass_bearing_deg()
);
// The round-trip through the compass setter must land back on the same frame.
let mut w = v;
w.set_compass_bearing_deg(270.0);
let (fa, _, _) = v.basis();
let (fb, _, _) = w.basis();
assert!((fa - fb).length() < 1e-6, "compass 270 must rebuild azimuth 90's frame");
}
/// **The shipping 2D lanes really are on the unified path.** The centrepiece parity
/// test compares the constrained 3D view against a reference affine written out by
/// hand, which proves the *core* reduces correctly but says nothing about whether
/// `facett-map` actually calls it. This closes that gap: the two ungated 2D writers
/// `facett-map` ships must reproduce the same reference, bit for bit.
///
/// (`facett_map::gpu::MapTransform` — the GPU lane's writer — is behind the `wgpu`
/// feature and is covered by the same reference in the `wgpu`-enabled test below,
/// which IS run, not skipped.)
#[test]
fn the_shipping_2d_lanes_are_all_on_the_unified_path() {
let zoom = 3072.0f32;
let offset = egui::Vec2::new(-517.5, 388.25);
// Lane 1: `facett_map::project::to_screen`.
for g in GROUND {
let want = (g[0] * zoom + offset.x, g[1] * zoom + offset.y);
let got = facett_map::project::to_screen(g[0], g[1], zoom, offset);
assert_eq!(got.x.to_bits(), want.0.to_bits(), "to_screen x for {g:?}");
assert_eq!(got.y.to_bits(), want.1.to_bits(), "to_screen y for {g:?}");
}
// Lane 2: `facett_map::layer::MapView::project`, at a NON-zero bearing and tilt
// so the shared frame is genuinely exercised (at 0/0 the affine is the identity
// and this would prove nothing).
let v = facett_map::layer::MapView::new(
facett_map::layer::LatLon { lon: 17.6, lat: 59.6 },
13.0,
(PANE.0, PANE.1),
)
.with_bearing(127.0)
.with_tilt(58.0);
let ws = v.world_px();
let (cx, cy) = facett_map::mercator(17.6, 59.6);
let mut moved = 0.0f32;
for (lon, lat) in [(17.62, 59.61), (17.58, 59.59), (17.60, 59.64)] {
let (px, py) = facett_map::mercator(lon, lat);
let (dx, dy) = ((px - cx) * ws, (py - cy) * ws);
// The verbatim pre-unification arithmetic.
let (s, c) = (-(127.0f64).to_radians()).sin_cos();
let rx = dx * c - dy * s;
let ry = (dx * s + dy * c) * (58.0f64).to_radians().cos();
let want = (PANE.0 * 0.5 + rx as f32, PANE.1 * 0.5 + ry as f32);
let got = v.project(lon, lat);
assert_eq!(got.0.to_bits(), want.0.to_bits(), "MapView x for ({lon}, {lat})");
assert_eq!(got.1.to_bits(), want.1.to_bits(), "MapView y for ({lon}, {lat})");
moved = moved.max((got.0 - PANE.0 * 0.5).abs());
}
assert!(moved > 20.0, "the MapView probes must move off the pane centre, max {moved}");
}
/// The GPU lane's 2D writer, `MapTransform`, against the same independent
/// reference. Behind `wgpu` because the module is; **run** with
/// `cargo test -p facett-map3d --features wgpu`, not left to rot.
#[cfg(feature = "wgpu")]
#[test]
fn the_gpu_2d_writer_is_on_the_unified_path() {
let ref_xy = [0.001_10f32, 0.000_90f32];
let px_per_unit = [3072.0f32, 3072.0f32];
let centre = (pane_rect().center().x, pane_rect().center().y);
let t = facett_map::gpu::MapTransform::new(
[0.0, 0.0],
[ref_xy[0] as f64, ref_xy[1] as f64],
px_per_unit[0],
px_per_unit[1],
pane_rect(),
);
let want = reference_2d_pixels(px_per_unit, ref_xy, centre);
for (g, w) in GROUND.iter().zip(want.iter()) {
let p = t.project(*g);
assert_eq!(p[0].to_bits(), w.0.to_bits(), "MapTransform x for {g:?}");
assert_eq!(p[1].to_bits(), w.1.to_bits(), "MapTransform y for {g:?}");
}
// And the cull inverse must round-trip the pane corners exactly, or the compute
// cull would drop ways that are on screen.
let bbox = t.viewport_bbox(pane_rect());
assert!(bbox[0] < bbox[2] && bbox[1] < bbox[3], "cull bbox must be non-degenerate: {bbox:?}");
// The envelope is the pane corners DILATED by the widest stroke the painter can draw
// (`style::cull_pad_merc`, seam-15, `9c80a55` 2026-08-22 — three weeks after this
// test was written, which is why it compared the bare pane span and has read
// "cull span 0.27018228 vs 0.26041666" ever since: 30 px = 2 × the 15 px pad, on
// every vendor, because this is CPU arithmetic behind the `wgpu` feature, not a
// GPU measurement). The round-trip is still exact: the bare span plus both pads.
let span_x = pane_rect().width() / px_per_unit[0];
let pad = facett_map::style::cull_pad_merc(f64::from(px_per_unit[0])) as f32;
assert!(pad > 0.0, "the cull pad must widen the envelope, got {pad}");
let want = span_x + 2.0 * pad;
assert!(
(bbox[2] - bbox[0] - want).abs() < 1e-6,
"cull span {} vs pane {span_x} + 2 x pad {pad} = {want}",
bbox[2] - bbox[0]
);
}
// ── 6. the post-FX bypass seam, and the honest limit of it today ─────────────
/// **The post-FX bypass is derived FROM the view**, not set beside it: one function
/// both modes ask, so "2D skips the post chain" cannot disagree with the view it
/// describes. This is what makes GFX_V2's "2D costs nothing extra" true — the
/// pipeline is the same pipeline and the passes are simply not recorded.
#[test]
fn the_post_stack_is_bypassed_by_the_2d_constraint_and_untouched_by_3d() {
use facett_map3d::backend::SceneFlags;
// Everything on, as the 3D terrain lane configures it.
let full = SceneFlags {
sky: true,
ssao: true,
soft_shadows: true,
shadow_strength: 0.85,
water_on: true,
planar_reflection: true,
bloom: true,
flat_2d: false,
};
assert!(full.any_post(), "the 3D stack must want post passes to begin with");
// A constrained 2D view forces the whole chain off.
let v2 = View::ortho_2d_map([0.0, 0.0], [3072.0, 3072.0]);
assert!(v2.is_constrained_2d(), "the 2D view must be constrained");
let bypassed = full.constrain_to_view(&v2);
assert!(bypassed.flat_2d, "the bypass flag must be set");
assert!(!bypassed.any_post(), "and NO post pass may run");
for (name, on) in [
("sky", bypassed.sky),
("ssao", bypassed.ssao),
("soft_shadows", bypassed.soft_shadows),
("bloom", bypassed.bloom),
("planar_reflection", bypassed.planar_reflection),
] {
assert!(!on, "{name} must be off in 2D mode");
}
assert_eq!(bypassed.shadow_strength, 0.0, "and the shadow depth pass disabled");
// An unconstrained 3D view must be left EXACTLY alone — this reconciler must
// never enable a pass or silently disable one the 3D lane wanted.
let v3 = View::perspective_3d(Vec3::ZERO, 0.7, 0.8, 3.0, 50f32.to_radians());
assert!(!v3.is_constrained_2d());
assert_eq!(full.constrain_to_view(&v3), full, "3D flags must pass through untouched");
// NON-VACUITY: a view that misses ONE constraint must NOT get the bypass, or
// `constrain_to_view` would be switching the post stack off for tilted views.
let mut nearly = v2;
nearly.tilt = 0.001;
assert!(!nearly.is_constrained_2d(), "a hair of tilt breaks the constraint");
assert!(full.constrain_to_view(&nearly).any_post(), "and must NOT bypass the post stack");
}
/// **The gap item 6 pinned is now CLOSED — and this is what closed it** (GFX_V2 item 7).
///
/// Item 6 left `flat_2d` as a tested seam that `Map3D` could not reach, for two
/// independent reasons, and pinned the gap so that "the day someone adds the ortho mode
/// it goes red and points at exactly what to finish". That day is this test. It now
/// asserts BOTH halves — the orbit still cannot reach the constraint by tilting, and the
/// plan MODE reaches it exactly — because each half without the other is a half-truth:
///
/// * without the reachable half, `flat_2d` is a flag nothing can set (item 6's honest
/// limit, and the unwired-detector trap);
/// * without the unreachable half, nothing records that an ordinary 3D orbit must NEVER
/// fall into the bypass. `is_constrained_2d()` tests `tilt == 0.0` exactly, so a bypass
/// reachable by dragging would switch the whole post chain off at one float value and
/// back on at the next — a visible pop mid-drag. The clamp staying put is the design,
/// not an omission.
///
/// The clamp was deliberately NOT relaxed. Its stated reason ("so the look-at `up` never
/// degenerates") IS stale — the closed-form basis is well-conditioned at the pole, which
/// `postfx_bypass::the_closed_form_basis_is_well_conditioned_at_the_pole_and_does_not_mirror`
/// now proves on the orthonormality AND on east staying east. But relaxing it would not
/// have helped: an exact-equality constraint is only coherent for a mode you enter. And
/// `MIN_ELEV` guards something else entirely and is still live.
#[test]
#[allow(non_snake_case, reason = "the capitals carry the assertion; the test id stays as published")]
fn the_2d_constraint_is_unreachable_by_TILTING_and_reachable_by_MODE() {
let mut cam = OrbitCamera::default();
// ── HALF ONE: the orbit still cannot get there, by any route the API offers. ──
cam.set_tilt_deg(0.0);
cam.settle();
let by_setter = cam.view().tilt;
assert!(by_setter != 0.0, "set_tilt_deg(0) is still clamped away from exact top-down");
cam.apply_preset(facett_map3d::camera::ViewPreset::Top);
cam.settle();
let by_preset = cam.view().tilt;
assert!(by_preset != 0.0, "ViewPreset::Top is still clamped");
println!(
"[item7] closest tilt reachable by ORBIT: {:.5} deg via set_tilt_deg, {:.5} deg via ViewPreset::Top",
by_setter.to_degrees(),
by_preset.to_degrees()
);
assert!(by_preset.to_degrees().abs() < 1.0, "close to top-down, still never exact");
assert!(
matches!(cam.view().projection, Projection::Perspective { .. }),
"and the ORBIT is still perspective-only"
);
assert!(!cam.view().is_constrained_2d(), "so no amount of tilting reaches the constraint");
let asked = facett_map3d::backend::SceneFlags { ssao: true, bloom: true, ..Default::default() };
assert!(
asked.constrain_to_view(&cam.view()).any_post(),
"and a tilted 3D view keeps its post stack — the bypass must not be a cliff mid-drag"
);
// ── HALF TWO: the MODE gets there exactly, which is what makes the flag live. ──
cam.plan_2d = true;
cam.settle();
let v = cam.view();
assert_eq!(v.tilt, 0.0, "plan mode is EXACTLY top-down");
assert_eq!(v.azimuth, 0.0, "and unrotated");
assert!(matches!(v.projection, Projection::Ortho { .. }), "and orthographic");
assert!(v.is_constrained_2d(), "so the 2D constraint is satisfied");
let bypassed = asked.constrain_to_view(&v);
assert!(bypassed.flat_2d, "flat_2d is now something a real camera can set");
assert!(!bypassed.any_post(), "and the whole post chain goes off");
// Every individual lever, not just the aggregate — `any_post()` could be satisfied
// while one pass stayed on.
assert!(!bypassed.sky && !bypassed.ssao && !bypassed.bloom, "no sky, no AO, no bloom");
assert_eq!(bypassed.shadow_strength, 0.0, "no shadow depth pass");
assert!(!bypassed.planar_reflection, "no planar reflection");
// And leaving the mode restores the 3D lane byte for byte, so the mode is a mode and
// not a one-way door.
cam.plan_2d = false;
cam.settle();
assert!(!cam.view().is_constrained_2d(), "leaving plan mode returns to the 3D view");
assert!(asked.constrain_to_view(&cam.view()).any_post(), "…with its post stack back");
// The pass count itself is asserted ON THE DEVICE in `tests/postfx_bypass.rs`
// (0 passes in plan mode, 10 in perspective). This test is the pure-predicate half.
}