re: the capture is Stage_S02 -- provenance corrected, oracle grows to 46 buffers
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>
This commit is contained in:
112
crates/sylpheed-formats/examples/capture_truth_scan.rs
Normal file
112
crates/sylpheed-formats/examples/capture_truth_scan.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
//! 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");
|
||||||
|
}
|
||||||
47
docs/re/captures/stage-s02-capture-truth-offsets.txt
Normal file
47
docs/re/captures/stage-s02-capture-truth-offsets.txt
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
file offset vcount candidate claimed by our decode our resources with that vcount
|
||||||
|
0x135036c 240 yes _rou_f101(240) — none _rou_f101 @ -0x0
|
||||||
|
0x13519ec 240 NO _rou_f101(240) — none _rou_f101 @ -0x0
|
||||||
|
0x13fd6e8 25448 yes f101_bdy_01(25448) f101_bdy_01 f101_bdy_01 @ -0x0
|
||||||
|
0x192961c 13172 yes f101_bdy_02(13172) f101_bdy_02 f101_bdy_02 @ -0x0
|
||||||
|
0x1a0befc 2296 yes f101_bdy_03(2296) f101_bdy_03 f101_bdy_03 @ -0x0
|
||||||
|
0x1a22c1c 7694 yes f101_eng_01(7694) f101_eng_01 f101_eng_01 @ -0x0
|
||||||
|
0x1be5df4 628 yes f101_wep_01_m(628) f101_wep_01_m f101_wep_01_m @ -0x0
|
||||||
|
0x1bf40f4 261 yes _rou_f105_break(261), f105_bdy_03_m(2336), f105_eng_01_m(1402) f106_eng_01_l _rou_f105_break @ -0x0
|
||||||
|
0x1bf596c 2446 NO _rou_f105_break(2446) f105_bdy_01_m _rou_f105_break @ -0x0
|
||||||
|
0x1c03ebc 1960 NO _rou_f105_break(1960) f105_bdy_02_m _rou_f105_break @ -0x0
|
||||||
|
0x1c0f67c 82 NO _rou_f105_break(82) e106_wep_02_01_l _rou_f105_break @ -0x0
|
||||||
|
0x1c0fe2c 2336 NO _rou_f105_break(2336) f105_bdy_03_m _rou_f105_break @ -0x0
|
||||||
|
0x1c1d92c 220 NO _rou_f105_break(220) — none _rou_f105_break @ -0x0
|
||||||
|
0x1c1edcc 80 NO _rou_f105_break(80) f001_bdy_24_m _rou_f105_break @ -0x0
|
||||||
|
0x1c1f54c 148 NO _rou_f105_break(148) — none _rou_f105_break @ -0x0
|
||||||
|
0x1c2032c 156 NO _rou_f105_break(156) e105_bdy_01_l _rou_f105_break @ -0x0
|
||||||
|
0x1c211cc 1402 NO _rou_f105_break(1402) f105_eng_01_m _rou_f105_break @ -0x0
|
||||||
|
0x1c2953c 84 NO _rou_f105_break(84) — none _rou_f105_break @ -0x0
|
||||||
|
0x1c29d1c 192 NO _rou_f105_break(192) f105_sld_01_b01 _rou_f105_break @ -0x0
|
||||||
|
0x1c2af1c 122 NO _rou_f105_break(122) e106_eng_02_m _rou_f105_break @ -0x0
|
||||||
|
0x1c2ba8c 24 NO _rou_f105_break(24) — none _rou_f105_break @ -0x0
|
||||||
|
0x227ac94 1692 yes f106_bdy_02_l(1692) f106_bdy_02_l f106_bdy_02_l @ -0x0
|
||||||
|
0x22c2ef0 686 yes f106_bdy_03_l(686) f106_bdy_03_l f106_bdy_03_l @ -0x0
|
||||||
|
0x22ed420 261 yes f106_eng_01_l(261) f106_eng_01_l f106_eng_01_l @ -0x0
|
||||||
|
0x2326c9c 254 yes f106_sld_01_l(254), f106_sld_02_l(254), e303_wep_02_m(254) f106_sld_01_l, f106_sld_02_l, e303_wep_02_m f106_sld_01_l @ -0x0
|
||||||
|
0x233b3fc 254 yes — NOBODY f106_sld_01_l, f106_sld_02_l, e303_wep_02_m f106_sld_02 @ -0xcc44
|
||||||
|
0x24ce5f4 32 yes — NOBODY e302_barrel_l f303_body @ -0x1638
|
||||||
|
0x24fa1c8 128 yes — NOBODY _rou_f402 _rou_f401_dead @ -0x90fc
|
||||||
|
0x28ea1d4 156 yes e105_bdy_01_l(156) e105_bdy_01_l e105_bdy_01_l @ -0x0
|
||||||
|
0x28f7514 107 yes — NOBODY e105_bdy_02_l, e105_brg_m e105_bdy_02_d @ -0x14e8
|
||||||
|
0x291e1e8 185 yes e105_bdy_03_l(185) e105_bdy_03_l e105_bdy_03_l @ -0x0
|
||||||
|
0x29a4788 181 yes e105_bdy_04_l(181) e105_bdy_04_l e105_bdy_04_l @ -0x0
|
||||||
|
0x29b78b8 93 yes — NOBODY e105_bdy_05_l e105_bdy_05_d @ -0x9094
|
||||||
|
0x2a22574 41 yes — NOBODY — none e105_bdy_06_d @ -0x4863c
|
||||||
|
0x2a47bac 77 yes e105_brg_l(77) e105_brg_l e105_brg_l @ -0x0
|
||||||
|
0x2a9fda0 76 yes — NOBODY e105_eng_01_l, e303_wep_01_m e105_eng_01_d @ -0x27a14
|
||||||
|
0x2ace840 60 yes — NOBODY e105_wep_01_l e105_wep_01 @ -0x2b63c
|
||||||
|
0x2d1fee8 119 yes e106_bdy_01_l(119), e106_bdy_02_l(119) e106_bdy_01_l, e106_bdy_02_l e106_bdy_01_l @ -0x0
|
||||||
|
0x2d315d8 119 yes — NOBODY e106_bdy_01_l, e106_bdy_02_l e106_bdy_01_m @ -0x104e8
|
||||||
|
0x2d492c4 146 yes e106_bdy_03_l(146) e106_bdy_03_l e106_bdy_03_l @ -0x0
|
||||||
|
0x2d7363c 179 yes — NOBODY e106_bdy_04_l e106_bdy_04 @ -0x25bd4
|
||||||
|
0x2d7a418 51 yes — NOBODY e106_brg_01_b_02, e106_brg_01_l e106_brg_01_b_02 @ -0x5d0
|
||||||
|
0x2db0ccc 58 yes e105_wep_01_l(60) e106_eng_01_l e105_wep_01_l @ -0x0
|
||||||
|
0x2db632c 44 yes — NOBODY e106_eng_02_l, e302_base_m e106_eng_02 @ -0x35b8
|
||||||
|
0x2dc305c 82 yes e106_wep_02_01_l(82), e201_bdy_02_l(85) e106_wep_02_01_l e106_wep_02_01_l @ -0x0
|
||||||
|
0x329077c 6000 yes n006_02(6000) n006_02 n006_02 @ -0x0
|
||||||
@@ -781,6 +781,38 @@ capture covering more ships and stages (the same `--truth` method extends to any
|
|||||||
container the engine drew from) or a discriminator that does not degrade for
|
container the engine drew from) or a discriminator that does not degrade for
|
||||||
coarse geometry. Both are recorded as the next step rather than guessed at.
|
coarse geometry. Both are recorded as the next step rather than guessed at.
|
||||||
|
|
||||||
|
**8. Provenance correction — the capture is `Stage_S02`, and the oracle is
|
||||||
|
4× bigger than reported.** `examples/capture_truth_scan.rs` runs the
|
||||||
|
offset-locating pass over **every** container in `resource3d` and reports each
|
||||||
|
one's modal `vbase − offset`. `Stage_S02` places **64** buffer-matches at a
|
||||||
|
single constant (`0x17FE3FF4`); `Stage_S01`, which sections 1–7 above used,
|
||||||
|
places only 16. The logs also contain `f101` (the ACROPOLIS escort, 25 448
|
||||||
|
verts), `f105`/`f106` (TCAF cruiser and destroyer) and `e105` — a Stage-02 cast.
|
||||||
|
**The loaded container was `Stage_S02`.**
|
||||||
|
|
||||||
|
Why `Stage_S01` nevertheless produced a consistent constant: the two containers
|
||||||
|
carry the shared block **verbatim and contiguously**. The e106 twin buffers sit
|
||||||
|
at `0x3b3ee8`/`0x3c55d8` in `Stage_S01` and `0x2d1fee8`/`0x2d315d8` in
|
||||||
|
`Stage_S02` — the same `0x116F0` apart. So the earlier findings are still true
|
||||||
|
statements about `Stage_S01`'s content (both mirrored buffers are in it, and our
|
||||||
|
decoder collapses them), and they reproduce in `Stage_S02`; only the claim that
|
||||||
|
`0x1A94FFF4` was *the loaded container's* base was an artifact.
|
||||||
|
|
||||||
|
The `Stage_S02` table
|
||||||
|
([`captures/stage-s02-capture-truth-offsets.txt`](../captures/stage-s02-capture-truth-offsets.txt))
|
||||||
|
places **46 drawn buffers**: 34 have a claimant (29 of them a single resource of
|
||||||
|
exactly the drawn size), **12 are claimed by nobody**. It also resolves the six
|
||||||
|
mystery buffers of the `Stage_S01` table — 181, 93, 77, 76, 60, 41 vertices —
|
||||||
|
as **`e105` parts** (`bdy_04_l`, `bdy_05_l`, `brg_l`, `eng_01_l`, `wep_01_l`),
|
||||||
|
another ship in the same mission, several of which are the same "right size,
|
||||||
|
wrong place" failure as `eng_02_l`.
|
||||||
|
|
||||||
|
❔ **A new defect class to chase**: `_rou_f105_break` — a destruction composite —
|
||||||
|
claims a run of **twelve consecutive** drawn offsets whose sizes match *other*
|
||||||
|
ships' LODs (`f105_bdy_01_m`, `f105_bdy_03_m`, `e106_wep_02_01_l`, …). Either the
|
||||||
|
break model genuinely mirrors those buffers or our decoder is handing a whole
|
||||||
|
address range to one composite. Not resolved here.
|
||||||
|
|
||||||
Not settled: `e106_brg_01_b_02` ≡ `e106_brg_01_l` (51 verts). A second 51-vertex
|
Not settled: `e106_brg_01_b_02` ≡ `e106_brg_01_l` (51 verts). A second 51-vertex
|
||||||
`vbase` exists in the logs but is **not** from this container, and the container
|
`vbase` exists in the logs but is **not** from this container, and the container
|
||||||
holds three near-identical 51-vertex runs, so the pair has no oracle yet.
|
holds three near-identical 51-vertex runs, so the pair has no oracle yet.
|
||||||
|
|||||||
Reference in New Issue
Block a user