facett-map3d/tests/extrude_fanout.rs
//! **The extrusion fan-out is byte-identical to the serial extruder** (LAW 2).
//!
//! `build_with_water_terrain_styled` chunks its per-way loop across znippy's
//! fork-join gatling and concatenates the per-worker arenas with an index rebase.
//! The claim is not "roughly the same mesh" — it is BYTE identity: every position,
//! normal, index, per-triangle colour, per-vertex building stamp, water-grid vertex
//! and roof stat must equal the serial append order exactly, because the pixel
//! goldens, the oracle tuples and `classify_new_vertices`' invariant all sit on
//! that order. So the serial reference runs IN THE TEST (`workers = 1`, the plain
//! loop) beside the fanned build, and the comparison is on bits, not on `f32 ==`
//! (a `-0.0` or a payload-NaN drift would pass `==` and still break a golden).
//!
//! **Seen red**: with the rebase deliberately broken (`i + base + 1` in
//! `Mesh3::append_rebased`) this fails on the first fanned chunk's indices —
//! recorded in the fix's report. A fan-out that concatenated chunks out of order,
//! dropped a way, or let a worker see another's vertex base would fail the same
//! assertions.
use egui::Color32;
use facett_core::Theme;
use facett_map::Way;
use facett_map3d::Mesh3;
use facett_map3d::extrude::{
GroundTerrain, RoofStyle, build_with_water_terrain_styled,
build_with_water_terrain_styled_with_workers,
};
use facett_map3d::terrain::ProceduralTerrain;
/// A deterministic ~230-way town over a real ~2 km span (so `du_per_m` is `Some`
/// and the metric height/band/water paths all run): a grid of buildings — tagged
/// heights, gabled/hipped roofs, facade + roof colours sprinkled in — plus water
/// bodies, parks, and open road polylines. Big enough to clear the 64-way serial
/// threshold several chunks over.
fn town() -> Vec<Way> {
let theme = Theme::default();
let (lon0, lat0) = (17.70, 59.60);
let step = 0.0012; // ~70–130 m blocks
let mut ways = Vec::new();
let mut id = 1i64;
for j in 0..14 {
for i in 0..14 {
let x = lon0 + i as f64 * step;
let y = lat0 + j as f64 * step;
let s = step * 0.4;
let ring = [(x, y), (x + s, y), (x + s, y + s), (x, y + s), (x, y)];
let mut tags: Vec<(String, String)> = Vec::new();
match (i + j) % 6 {
0..=2 => {
tags.push(("building".into(), "yes".into()));
if (i * 3 + j) % 5 == 0 {
tags.push(("height".into(), format!("{}", 6 + (i + 2 * j) % 30)));
}
if (i + j) % 7 == 0 {
tags.push(("roof:shape".into(), "gabled".into()));
}
if (i + j) % 9 == 0 {
tags.push(("roof:shape".into(), "hipped".into()));
}
if i % 4 == 0 {
tags.push(("building:colour".into(), "#aa2233".into()));
}
if j % 5 == 0 {
tags.push(("roof:colour".into(), "#334455".into()));
}
}
3 => tags.push(("natural".into(), "water".into())),
4 => tags.push(("leisure".into(), "park".into())),
_ => tags.push(("landuse".into(), "residential".into())),
}
if let Some(w) = Way::from_lonlat(id, &ring, &tags, &theme) {
ways.push(w);
id += 1;
}
}
}
// Open polylines (the `line_feature` branch): a few streets crossing the grid.
for k in 0..20 {
let y = lat0 + k as f64 * step * 0.7;
ways.push(Way::line(
id,
"road",
vec![(lon0, y), (lon0 + 0.008, y + 0.0004), (lon0 + 0.016, y)],
Color32::GRAY,
2.0,
));
id += 1;
}
assert!(ways.len() > 64 * 3, "fixture must clear the serial threshold with several chunks ({} ways)", ways.len());
ways
}
/// Bit-exact mesh equality — positions/normals compared on `to_bits`, everything
/// else on exact values. Panics with the first differing lane + index.
fn assert_mesh_bits_eq(serial: &Mesh3, fanned: &Mesh3) {
assert_eq!(serial.positions.len(), fanned.positions.len(), "vertex count");
for (k, (a, b)) in serial.positions.iter().zip(&fanned.positions).enumerate() {
assert_eq!(a.map(f32::to_bits), b.map(f32::to_bits), "position bits differ at vertex {k}");
}
for (k, (a, b)) in serial.normals.iter().zip(&fanned.normals).enumerate() {
assert_eq!(a.map(f32::to_bits), b.map(f32::to_bits), "normal bits differ at vertex {k}");
}
assert_eq!(serial.indices, fanned.indices, "triangle indices");
assert_eq!(serial.colors, fanned.colors, "per-triangle colours");
assert_eq!(serial.building, fanned.building, "per-vertex building stamps");
}
/// Flat terrain + pitched roofs — the styled path with prism styles, roof fans and
/// water grids all live: the fanned mesh is bit-identical to the serial reference.
#[test]
fn fanned_extrusion_is_byte_identical_to_serial_flat_terrain() {
let ways = town();
for style in [RoofStyle::Flat, RoofStyle::Pitched] {
let (sm, st, sw, ss) =
build_with_water_terrain_styled_with_workers(&ways, 0.2, GroundTerrain::Flat, style, 1);
let (fm, ft, fw, fs) = build_with_water_terrain_styled(&ways, 0.2, GroundTerrain::Flat, style);
assert_mesh_bits_eq(&sm, &fm);
assert_eq!(st, ft, "tile count ({style:?})");
assert_eq!(ss, fs, "roof stats ({style:?})");
assert_eq!(sw.indices, fw.indices, "water indices ({style:?})");
assert_eq!(sw.positions.len(), fw.positions.len(), "water vertex count ({style:?})");
for (k, (a, b)) in sw.positions.iter().zip(&fw.positions).enumerate() {
assert_eq!(a.map(f32::to_bits), b.map(f32::to_bits), "water position bits differ at vertex {k} ({style:?})");
}
// The fixture must actually exercise the interesting branches, or the
// identity above is vacuous for them. (`Flat` pitches nothing by design —
// its non-vacuity is the water grid + the flat prisms.)
if style == RoofStyle::Pitched {
assert!(ss.pitched > 0, "fixture grew no pitched roofs — the styled branch never ran");
}
assert!(!sw.positions.is_empty(), "fixture grew no water grid — the water branch never ran");
}
}
/// **Wall-time measurement** (LAW 7 — run explicitly, release, on the real box):
/// `cargo test -p facett-map3d --release --test extrude_fanout -- --ignored --nocapture`
/// Times the serial reference vs the fanned build on a ~23 000-way town and prints
/// real ratios. Ignored in the normal suite: it is a measurement, not a guard.
#[test]
#[ignore = "wall-time measurement; run --release with --ignored --nocapture"]
fn measure_fanned_vs_serial_extrusion() {
// ~23k ways: the town grid scaled up to a city-extract-sized scene.
let theme = Theme::default();
let (lon0, lat0) = (17.5, 59.4);
let step = 0.0012;
let mut ways = Vec::new();
let mut id = 1i64;
for j in 0..150 {
for i in 0..150 {
let x = lon0 + i as f64 * step;
let y = lat0 + j as f64 * step;
let s = step * 0.4;
// 16-gon rings — real OSM footprints carry tens of vertices, and a
// 5-point square under-weighs the per-way work the fan parallelises.
let ring: Vec<(f64, f64)> = (0..=16)
.map(|k| {
let t = (k % 16) as f64 / 16.0 * std::f64::consts::TAU;
(x + s * 0.5 * (1.0 + t.cos()), y + s * 0.5 * (1.0 + t.sin()))
})
.collect();
let tags: Vec<(String, String)> = match (i + j) % 6 {
0..=2 => vec![("building".into(), "yes".into()), ("roof:shape".into(), "gabled".into())],
3 => vec![("natural".into(), "water".into())],
_ => vec![("landuse".into(), "residential".into())],
};
if let Some(w) = Way::from_lonlat(id, &ring, &tags, &theme) {
ways.push(w);
id += 1;
}
}
}
let time = |workers: usize| {
let t0 = std::time::Instant::now();
let out = build_with_water_terrain_styled_with_workers(
&ways, 0.2, GroundTerrain::Flat, RoofStyle::Pitched, workers,
);
(t0.elapsed(), out.0.triangle_count())
};
// Warm allocator/caches once, then measure best-of-3 each.
let _ = time(1);
let serial = (0..3).map(|_| time(1).0).min().unwrap();
let fanned = (0..3).map(|_| time(0).0).min().unwrap();
let tris = time(0).1;
eprintln!(
"extrude fan: {} ways / {} tris — serial {:?} vs fanned {:?} = {:.2}x",
ways.len(),
tris,
serial,
fanned,
serial.as_secs_f64() / fanned.as_secs_f64()
);
}
/// Relief terrain — `footprint_span` per way over a real heightfield: still
/// bit-identical (the relief field is shared read-only across the chunk workers).
#[test]
fn fanned_extrusion_is_byte_identical_to_serial_relief_terrain() {
let ways = town();
let elev = ProceduralTerrain::default();
let terrain = || GroundTerrain::Relief { elev: &elev, subdiv: 48, exaggeration: 1.0 };
let (sm, st, sw, ss) =
build_with_water_terrain_styled_with_workers(&ways, 0.2, terrain(), RoofStyle::Pitched, 1);
let (fm, ft, fw, fs) = build_with_water_terrain_styled(&ways, 0.2, terrain(), RoofStyle::Pitched);
assert_mesh_bits_eq(&sm, &fm);
assert_eq!(st, ft, "tile count");
assert_eq!(ss, fs, "roof stats");
assert_eq!(sw.indices, fw.indices, "water indices");
for (k, (a, b)) in sw.positions.iter().zip(&fw.positions).enumerate() {
assert_eq!(a.map(f32::to_bits), b.map(f32::to_bits), "water position bits differ at vertex {k}");
}
}