facett-map3d/tests/smear_pose.rs
//! Render the EXACT camera pose the 360°×100-zoom sweep flagged as the worst
//! projected-edge blow-up (the green-spoke smear) and scan the actual pixels with
//! `facett_core::imgscan`. CPU painter path — the path the wasm demo runs.
//!
//! Run: `cargo test -p facett-map3d --test smear_pose -- --nocapture`
use egui::Color32;
use egui_kittest::Harness;
use facett_core::{imgscan, Facet, Theme};
use facett_map::Way;
use facett_map3d::Map3D;
fn building(id: i64, ring: &[(f64, f64)]) -> Way {
let tags = vec![("building".to_string(), "yes".to_string())];
Way::from_lonlat(id, ring, &tags, &Theme::default()).expect("a building way")
}
fn giant_city() -> Map3D {
let mut ways = Vec::new();
let mut id = 1;
for r in 0..20 {
for c in 0..20 {
let bx = 17.70 + c as f64 * 0.0010;
let by = 59.61 + r as f64 * 0.0010;
ways.push(building(
id,
&[(bx, by), (bx + 0.0006, by), (bx + 0.0006, by + 0.0006), (bx, by + 0.0006), (bx, by)],
));
id += 1;
}
}
Map3D::from_geometry("giant", &ways)
}
fn render_at(az_deg: f32, elev_deg: f32, dist: f32, name: &str) -> imgscan::ScanReport {
use std::cell::RefCell;
use std::rc::Rc;
let view = Rc::new(RefCell::new(giant_city()));
{
let mut m = view.borrow_mut();
m.spin = false;
let tgt = m.render_camera().target;
m.cam.target = tgt;
m.cam.goal_target = tgt;
m.cam.azimuth = az_deg.to_radians();
m.cam.goal_azimuth = az_deg.to_radians();
m.cam.elevation = elev_deg.to_radians();
m.cam.goal_elevation = elev_deg.to_radians();
m.cam.distance = dist;
m.cam.goal_distance = dist;
m.cam.settle();
}
let v2 = view.clone();
let mut harness = Harness::builder().with_size(egui::vec2(600.0, 600.0)).build_ui(move |ui| {
Facet::ui(&mut *v2.borrow_mut(), ui);
});
harness.run_steps(3);
let img = harness.render().expect("render");
let dir = std::env::temp_dir().join("facett-map3d");
std::fs::create_dir_all(&dir).ok();
let png = dir.join(format!("smear_{name}.png"));
img.save(&png).ok();
eprintln!("[smear_pose] {name}: wrote {}", png.display());
let bg = imgscan::Rgba { r: 20, g: 24, b: 40, a: 255 };
imgscan::scan(img.as_raw(), img.width(), img.height(), bg)
}
#[test]
fn worst_sweep_pose_is_not_a_spoke_smear() {
// The two worst state vectors the sweep reported.
let city = render_at(143.0, 5.0, 0.612, "city_az143_e5_d0p61");
let rabbit_pose = render_at(148.0, 55.0, 0.130, "city_az148_e55_d0p13");
eprintln!(
"[smear_pose] city : spoke={:.3} hf={:.3} coverage={:.3}",
city.spoke_score, city.high_freq_ratio, city.coverage
);
eprintln!(
"[smear_pose] close: spoke={:.3} hf={:.3} coverage={:.3}",
rabbit_pose.spoke_score, rabbit_pose.high_freq_ratio, rabbit_pose.coverage
);
// NOTE: the RIGOROUS smear oracle is `chain_sweep` (worst projected edge ≤
// viewport across the 360°×100-zoom×elev joint space — viewport-tight, the FIX).
// This pixel scan corroborates: a CATASTROPHIC smear (the pre-fix 84 000px
// slivers filling the frame) drives spoke_score toward ~1.0; a legit grazing
// grid sits ~0.3–0.4 (real thin building edges / tri diagonals, confirmed by
// eye). 0.5 separates the two; both poses must clear it AND render real content.
assert!(
city.coverage > 0.005 && rabbit_pose.coverage > 0.02,
"a pose rendered ~nothing (city cov={:.3}, close cov={:.3})",
city.coverage,
rabbit_pose.coverage
);
assert!(
city.spoke_score < 0.5,
"the worst-sweep city pose is a CATASTROPHIC spoke smear (spoke_score={:.3} ≥ 0.5)",
city.spoke_score
);
assert!(
rabbit_pose.spoke_score < 0.5,
"the close-zoom pose is a CATASTROPHIC spoke smear (spoke_score={:.3} ≥ 0.5)",
rabbit_pose.spoke_score
);
let _ = Color32::WHITE;
}