//! Screen every ship family for a part that is wildly out of scale with its //! siblings — the "slab" signature of a mis-anchored block. //! //! Rendering `e106` found such a part (`bdy_03`, 600×1600×998 beside parts of //! ~250) that coverage, cross-container consistency, the capture oracle and the //! twin invariant were all blind to. Eyeballing does not scale to 166 containers; //! this does the same comparison numerically. //! //! Usage: slab_screen [factor] use sylpheed_formats::mesh::Xbg7Model; use sylpheed_formats::ship::{is_base_part, ship_id_of}; use std::collections::BTreeMap; fn main() { let dir = std::env::args().nth(1).expect("resource3d dir"); let factor: f32 = std::env::args().nth(2).and_then(|s| s.parse().ok()).unwrap_or(4.0); let mut files: Vec<_> = std::fs::read_dir(&dir) .unwrap() .flatten() .map(|e| e.path()) .filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr")) .collect(); files.sort(); let mut flagged = 0usize; for f in &files { let Ok(bytes) = std::fs::read(f) else { continue }; let mut by_ship: BTreeMap> = BTreeMap::new(); for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) { if !is_base_part(&m.name) { continue; } let Some(id) = ship_id_of(&m.name) else { continue }; let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]); for s in &m.meshes { for q in &s.positions { for k in 0..3 { lo[k] = lo[k].min(q[k]); hi[k] = hi[k].max(q[k]); } } } if lo[0] == f32::MAX { continue; } // Compare the part's SMALLEST axis span, not its diagonal. A long // thin part (an antenna, a 100 000-unit tether on `f002`) is // legitimately huge in one axis and would swamp a diagonal test; a // mis-anchored block is bulky in all three, which is what the e106 // slab looked like (600×1600×998 beside siblings of ~250). let thin = (hi[0] - lo[0]).min(hi[1] - lo[1]).min(hi[2] - lo[2]); by_ship.entry(id.to_string()).or_default().push((m.name.clone(), thin)); } for (id, parts) in &by_ship { if parts.len() < 3 { continue; // no meaningful median } let mut d: Vec = parts.iter().map(|(_, x)| *x).collect(); d.sort_by(|a, b| a.partial_cmp(b).unwrap()); let median = d[d.len() / 2]; for (name, diag) in parts { if *diag > median * factor { flagged += 1; println!( "{:<22} {name:<22} min-axis {diag:>8.0} vs ship median {median:>8.0} ({:.1}×)", f.file_name().unwrap().to_string_lossy(), diag / median ); } } } } println!("{flagged} parts flagged at {factor}× the ship median"); }