capture_truth_scan places every drawn buffer across all 166 containers by modal vbase-offset. Stage_S02 wins with 64 matches at one constant against Stage_S01's 16, and the logs carry f101/f105/f106/e105 -- a Stage-02 cast. Stage_S01 looked consistent because the shared block is duplicated verbatim (twins 0x116F0 apart in both), so the earlier structural findings hold; only the loaded-container claim was wrong. The S02 table places 46 buffers, 12 claimed by nobody, and resolves the six Stage_S01 mystery buffers as e105 parts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
113 lines
4.6 KiB
Rust
113 lines
4.6 KiB
Rust
//! 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 <resource3d_dir> <capture.log>...
|
||
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<String> = std::env::args().collect();
|
||
let dir = &a[1];
|
||
let mut draws: Vec<CapturedDraw> = 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<u32>> = 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<i64, Vec<(u32, u32)>> = 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");
|
||
}
|