//! Minority report: which container decodes a shared resource differently from //! all the others? //! //! Cross-container consistency has been measured as "do the spans agree", which //! only says a resource is inconsistent — not which copy is wrong. With three or //! more copies the majority is the reference, and the minority names the //! container AND the resource to look at. That is what caught the `n054`/`n056` //! shift chain after the exact-coverage fix. //! //! Usage: consensus_check [--list] use sylpheed_formats::mesh::Xbg7Model; use std::collections::{BTreeMap, HashMap}; fn main() { let dir = std::env::args().nth(1).expect("resource3d dir"); let list = std::env::args().any(|a| a == "--list"); 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(); // name -> [(container, verts, tris, span)] let mut seen: BTreeMap> = BTreeMap::new(); for f in &files { let Ok(bytes) = std::fs::read(f) else { continue }; let where_ = f.file_name().unwrap().to_string_lossy().to_string(); for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) { 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; } let v: usize = m.meshes.iter().map(|s| s.positions.len()).sum(); let t: usize = m.meshes.iter().map(|s| s.indices.len() / 3).sum(); let span = [ (hi[0] - lo[0]).round() as i64, (hi[1] - lo[1]).round() as i64, (hi[2] - lo[2]).round() as i64, ]; seen.entry(m.name.clone()).or_default().push((where_.clone(), v, t, span)); } } let (mut resources, mut minority) = (0usize, 0usize); let mut rows: Vec = Vec::new(); for (name, list_) in &seen { // Only compare decodes that agree on how much geometry they found, and // only where a majority can exist. if list_.len() < 3 || !list_.iter().all(|e| e.1 == list_[0].1 && e.2 == list_[0].2) { continue; } let mut votes: HashMap<[i64; 3], usize> = HashMap::new(); for e in list_ { *votes.entry(e.3).or_default() += 1; } let (best, n) = votes.iter().max_by_key(|(_, n)| **n).unwrap(); if *n * 2 <= list_.len() { continue; // no majority — cannot call anyone the odd one out } resources += 1; for e in list_.iter().filter(|e| e.3 != *best) { minority += 1; rows.push(format!( "{:<22} {name:<26} v={:<6} {:?} vs the other {n} containers' {:?}", e.0, e.1, e.3, best )); } } if list { for r in &rows { println!("{r}"); } } println!( "{minority} minority decodes across {resources} resources that have a majority" ); }