//! Which container did each captured draw come from, and where in it? //! //! Extends the single-container `--map` check in `shared_vbase_check` to a whole //! `resource3d` directory. For each container it indexes every 4-byte-aligned //! position triple, looks up each draw's first dumped position, confirms the run //! at a fixed stride, and reports the modal `vbase − offset`. A container the //! engine loaded shows one dominant constant; an unrelated one shows noise. //! //! The output is capture-named ground truth for anchors far beyond the one ship //! `Stage_S01` gave us. //! //! Usage: capture_truth_scan ... use sylpheed_formats::ship_capture::{parse_capture, parse_drawlog, CapturedDraw}; use std::collections::HashMap; fn q(v: f32) -> i64 { (v as f64 * 1e4).round() as i64 } fn main() { let a: Vec = std::env::args().collect(); let dir = &a[1]; let mut draws: Vec = Vec::new(); let mut seen = std::collections::HashSet::new(); for log in &a[2..] { let text = std::fs::read_to_string(log).expect("log"); let mut d = parse_capture(&text); if d.is_empty() { d = parse_drawlog(&text); } for x in d { // One entry per buffer; the logs are already deduped per transform. if x.pos.len() >= 8 && x.vcount >= 20 && seen.insert((log.clone(), x.vbase)) { draws.push(x); } } } eprintln!("{} distinct (log, vbase) draws to place", draws.len()); 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 placed = 0usize; for f in &files { let Ok(bytes) = std::fs::read(f) else { continue }; // Index quantised position triples. Junk floats (NaN/huge) are skipped, // which prunes most of a texture-heavy container. let be = |at: usize| f32::from_be_bytes(bytes[at..at + 4].try_into().unwrap()); let mut index: HashMap<(i64, i64, i64), Vec> = HashMap::new(); let mut o = 0usize; while o + 12 <= bytes.len() { let (x, y, z) = (be(o), be(o + 4), be(o + 8)); if x.is_finite() && y.is_finite() && z.is_finite() && x.abs() < 1e6 && y.abs() < 1e6 && z.abs() < 1e6 { index.entry((q(x), q(y), q(z))).or_default().push(o as u32); } o += 4; } let mut deltas: HashMap> = HashMap::new(); for d in &draws { let k = (q(d.pos[0][0]), q(d.pos[0][1]), q(d.pos[0][2])); // ±1 in each axis: the log rounds, our file value may round the // other way at a tie. for dx in -1..=1i64 { for dy in -1..=1i64 { for dz in -1..=1i64 { let Some(cands) = index.get(&(k.0 + dx, k.1 + dy, k.2 + dz)) else { continue; }; for &off in cands { for stride in (12..=64).step_by(4) { let ok = (1..4).all(|j| { let at = off as usize + j * stride; at + 12 <= bytes.len() && (0..3).all(|c| { (be(at + c * 4) - d.pos[j][c]).abs() <= 1e-4 }) }); if ok { deltas .entry(d.vbase as i64 - off as i64) .or_default() .push((d.vbase, d.vcount)); break; } } } } } } } let mut top: Vec<_> = deltas.into_iter().collect(); top.sort_by_key(|(_, v)| std::cmp::Reverse(v.len())); if let Some((delta, hits)) = top.first() { if hits.len() >= 3 { placed += hits.len(); println!( "{:<22} base=0x{:<10X} buffers={}", f.file_name().unwrap().to_string_lossy(), delta, hits.len() ); } } } eprintln!("{placed} draws placed in a container"); }