re: exact anchor offsets (GameMesh.vbuf_offset) -- correcting yesterday's defect table
The first read of the capture-truth table located our resources by searching the container for their leading vertices, which reads much worse than reality: the same leading run occurs at several offsets in one container. GameMesh now carries the offset the anchor scan actually chose, so the comparison is exact -- 4 of the ship's drawn buffers are anchored correctly, 2 are the twin collapse, and 2 are real mis-anchors of a size we do decode (brg 51 verts, eng_02_l 44). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -117,15 +117,27 @@ fn main() {
|
|||||||
// scan chose for each resource — a direct read-out of what we got wrong.
|
// scan chose for each resource — a direct read-out of what we got wrong.
|
||||||
if let Some(a) = args.iter().find_map(|a| a.strip_prefix("--truth=")) {
|
if let Some(a) = args.iter().find_map(|a| a.strip_prefix("--truth=")) {
|
||||||
let base = u32::from_str_radix(a.trim_start_matches("0x"), 16).expect("base");
|
let base = u32::from_str_radix(a.trim_start_matches("0x"), 16).expect("base");
|
||||||
|
// Where the anchor scan actually put each sub-mesh — exact, from the
|
||||||
|
// decoder, not inferred by searching for its leading vertices (the same
|
||||||
|
// leading run occurs at several offsets in a container, so a search
|
||||||
|
// cannot tell where a resource was anchored).
|
||||||
let mut ours: BTreeMap<usize, Vec<(String, usize)>> = BTreeMap::new();
|
let mut ours: BTreeMap<usize, Vec<(String, usize)>> = BTreeMap::new();
|
||||||
for m in &models {
|
for m in &models {
|
||||||
let pos: Vec<[f32; 3]> =
|
for sub in &m.meshes {
|
||||||
m.meshes.iter().flat_map(|s| s.positions.iter().copied()).take(8).collect();
|
if let Some(o) = sub.vbuf_offset {
|
||||||
let n: usize = m.meshes.iter().map(|s| s.positions.len()).sum();
|
ours.entry(o).or_default().push((m.name.clone(), sub.positions.len()));
|
||||||
for (o, _) in locate_run(&bytes, &pos) {
|
}
|
||||||
ours.entry(o).or_default().push((m.name.clone(), n));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if args.iter().any(|a| a == "--anchors") {
|
||||||
|
println!("{:<12} where our decode put each resource", "file offset");
|
||||||
|
for (o, v) in &ours {
|
||||||
|
for (n, c) in v {
|
||||||
|
println!("0x{o:<10x} {n} ({c} verts)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
let mut drawn: BTreeMap<usize, u32> = BTreeMap::new();
|
let mut drawn: BTreeMap<usize, u32> = BTreeMap::new();
|
||||||
for (_, draws) in &logs {
|
for (_, draws) in &logs {
|
||||||
for d in draws {
|
for d in draws {
|
||||||
@@ -135,7 +147,14 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!("{:<12} {:>7} claimed by our decode", "file offset", "vcount");
|
// Which resources have the drawn vertex count, wherever we put them?
|
||||||
|
// Right size + wrong place is a different bug from never finding it.
|
||||||
|
let mut by_count: BTreeMap<usize, Vec<String>> = BTreeMap::new();
|
||||||
|
for m in &models {
|
||||||
|
let n: usize = m.meshes.iter().map(|s| s.positions.len()).sum();
|
||||||
|
by_count.entry(n).or_default().push(m.name.clone());
|
||||||
|
}
|
||||||
|
println!("{:<12} {:>7} {:<44} our resources with that vcount", "file offset", "vcount", "claimed by our decode");
|
||||||
for (off, vcount) in &drawn {
|
for (off, vcount) in &drawn {
|
||||||
let who = ours
|
let who = ours
|
||||||
.get(off)
|
.get(off)
|
||||||
@@ -143,7 +162,18 @@ fn main() {
|
|||||||
v.iter().map(|(n, c)| format!("{n}({c})")).collect::<Vec<_>>().join(", ")
|
v.iter().map(|(n, c)| format!("{n}({c})")).collect::<Vec<_>>().join(", ")
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| "— NOBODY".into());
|
.unwrap_or_else(|| "— NOBODY".into());
|
||||||
println!("0x{off:<10x} {vcount:>7} {who}");
|
let same = by_count
|
||||||
|
.get(&(*vcount as usize))
|
||||||
|
.map(|v| v.join(", "))
|
||||||
|
.unwrap_or_else(|| "— none".into());
|
||||||
|
// Nearest resource we anchored at or before this offset — the
|
||||||
|
// likely owner of a buffer nobody claims.
|
||||||
|
let near = ours
|
||||||
|
.range(..=*off)
|
||||||
|
.next_back()
|
||||||
|
.map(|(o, v)| format!("{} @ -0x{:x}", v[0].0, off - o))
|
||||||
|
.unwrap_or_default();
|
||||||
|
println!("0x{off:<10x} {vcount:>7} {who:<44} {same:<34} {near}");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,12 @@ pub struct GameMesh {
|
|||||||
pub indices: Vec<u32>,
|
pub indices: Vec<u32>,
|
||||||
/// Sub-mesh / node name from the descriptor, when available.
|
/// Sub-mesh / node name from the descriptor, when available.
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
/// Byte offset of this sub-mesh's vertex buffer inside the container, when
|
||||||
|
/// the decode path knows it. The content-anchored stage path does — and a
|
||||||
|
/// runtime capture names the same offset (a draw's `vbase` is this plus the
|
||||||
|
/// container's load address), so this is what makes an anchor checkable
|
||||||
|
/// against ground truth. See `examples/shared_vbase_check.rs`.
|
||||||
|
pub vbuf_offset: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A model = the set of sub-meshes recovered from one XPR2 container's first
|
/// A model = the set of sub-meshes recovered from one XPR2 container's first
|
||||||
@@ -376,6 +382,7 @@ impl Xbg7Model {
|
|||||||
uvs,
|
uvs,
|
||||||
indices,
|
indices,
|
||||||
name: None,
|
name: None,
|
||||||
|
vbuf_offset: Some(vb),
|
||||||
});
|
});
|
||||||
off = align16(ve) - base;
|
off = align16(ve) - base;
|
||||||
}
|
}
|
||||||
@@ -976,6 +983,7 @@ fn read_pool_mesh(
|
|||||||
uvs,
|
uvs,
|
||||||
indices,
|
indices,
|
||||||
name: None,
|
name: None,
|
||||||
|
vbuf_offset: Some(vb),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3677,6 +3677,7 @@ fn exhaust_cone_mesh() -> sylpheed_formats::mesh::GameMesh {
|
|||||||
uvs: Vec::new(),
|
uvs: Vec::new(),
|
||||||
indices,
|
indices,
|
||||||
name: Some("exhaust".to_string()),
|
name: Some("exhaust".to_string()),
|
||||||
|
vbuf_offset: None, // procedural, not read from a container
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
file offset vcount claimed by our decode
|
file offset vcount claimed by our decode our resources with that vcount
|
||||||
0x38788 181 — NOBODY
|
0x38788 181 — NOBODY — none
|
||||||
0x4b8b8 93 — NOBODY
|
0x4b8b8 93 — NOBODY — none
|
||||||
0xb6574 41 — NOBODY
|
0xb6574 41 — NOBODY n042_bdy_m e007_bdy_01 @ -0x24864
|
||||||
0xdbbac 77 — NOBODY
|
0xdbbac 77 — NOBODY — none _rou_e010_break @ -0x5050
|
||||||
0x133da0 76 — NOBODY
|
0x133da0 76 — NOBODY e303_wep_01_m, n041_bdy_l _rou_e010_break @ -0x407bc
|
||||||
0x162840 60 — NOBODY
|
0x162840 60 — NOBODY — none _rou_e010_break @ -0x6f25c
|
||||||
0x3b3ee8 119 e106_bdy_01_l(119), e106_bdy_02_l(119)
|
0x3b3ee8 119 e106_bdy_01_l(119), e106_bdy_02_l(119) e106_bdy_01_l, e106_bdy_02_l e106_bdy_01_l @ -0x0
|
||||||
0x3c55d8 119 — NOBODY
|
0x3c55d8 119 — NOBODY e106_bdy_01_l, e106_bdy_02_l e106_bdy_01_m @ -0x104e8
|
||||||
0x3dd2c4 146 e106_bdy_03(815), e106_bdy_03_l(146)
|
0x3dd2c4 146 e106_bdy_03_l(146) e106_bdy_03_l e106_bdy_03_l @ -0x0
|
||||||
0x40763c 179 e106_bdy_04_l(179)
|
0x40763c 179 e106_bdy_04_l(179) e106_bdy_04_l e106_bdy_04_l @ -0x0
|
||||||
0x40e418 51 e106_brg_01(202), e106_brg_01_m(92)
|
0x40e418 51 — NOBODY e106_brg_01_b_02, e106_brg_01_l e106_brg_01_b_02 @ -0x5d0
|
||||||
0x444ccc 58 e106_eng_01_l(58)
|
0x444ccc 58 e106_eng_01_l(58) e106_eng_01_l e106_eng_01_l @ -0x0
|
||||||
0x44a32c 44 — NOBODY
|
0x44a32c 44 — NOBODY e106_eng_02_l e106_eng_02 @ -0x35b8
|
||||||
0x45705c 82 e106_wep_02_01_l(82), e106_wep_02_01_m(294)
|
0x45705c 82 e106_wep_02_01_l(82) e106_wep_02_01_l e106_wep_02_01_l @ -0x0
|
||||||
|
|||||||
@@ -605,25 +605,38 @@ is uploaded contiguously and **a capture names the exact file offset of every
|
|||||||
buffer the engine drew**. Log 03 loaded the container at a different address, so
|
buffer the engine drew**. Log 03 loaded the container at a different address, so
|
||||||
the constant is per-run, not baked.
|
the constant is per-run, not baked.
|
||||||
|
|
||||||
**2. Read against our anchor scan, that is a defect list**
|
**2. Read against our anchor scan, that is a defect list.** `GameMesh` now
|
||||||
|
carries `vbuf_offset` — the offset the anchor scan actually placed a sub-mesh at
|
||||||
|
— so the comparison is exact
|
||||||
([`captures/stage-s01-capture-truth-offsets.txt`](../captures/stage-s01-capture-truth-offsets.txt)):
|
([`captures/stage-s01-capture-truth-offsets.txt`](../captures/stage-s01-capture-truth-offsets.txt)):
|
||||||
|
|
||||||
| file offset | drawn vcount | claimed by our decode |
|
| drawn offset | vcount | our resource anchored there | our resources with that vcount |
|
||||||
|---|---|---|
|
|---|---|---|---|
|
||||||
| `0x3b3ee8` | 119 | `e106_bdy_01_l`(119), `e106_bdy_02_l`(119) |
|
| `0x3b3ee8` | 119 | `e106_bdy_01_l`, `e106_bdy_02_l` | `bdy_01_l`, `bdy_02_l` |
|
||||||
| `0x3c55d8` | 119 | — **nobody** |
|
| `0x3c55d8` | 119 | — **nobody** | `bdy_01_l`, `bdy_02_l` |
|
||||||
| `0x3dd2c4` | 146 | `e106_bdy_03`(815), `e106_bdy_03_l`(146) |
|
| `0x3dd2c4` | 146 | `e106_bdy_03_l` ✅ | `bdy_03_l` |
|
||||||
| `0x40763c` | 179 | `e106_bdy_04_l`(179) ✅ |
|
| `0x40763c` | 179 | `e106_bdy_04_l` ✅ | `bdy_04_l` |
|
||||||
| `0x40e418` | 51 | `e106_brg_01`(202), `e106_brg_01_m`(92) |
|
| `0x40e418` | 51 | — **nobody** (ours sit `0x5d0` earlier) | `brg_01_b_02`, `brg_01_l` |
|
||||||
| `0x444ccc` | 58 | `e106_eng_01_l`(58) ✅ |
|
| `0x444ccc` | 58 | `e106_eng_01_l` ✅ | `eng_01_l` |
|
||||||
| `0x45705c` | 82 | `e106_wep_02_01_l`(82), `e106_wep_02_01_m`(294) |
|
| `0x44a32c` | 44 | — **nobody** | `eng_02_l` |
|
||||||
| `0x38788` `0x4b8b8` `0xb6574` `0xdbbac` `0x133da0` `0x162840` `0x44a32c` | 181, 93, 41, 77, 76, 60, 44 | — **nobody** |
|
| `0x45705c` | 82 | `e106_wep_02_01_l` ✅ | `wep_02_01_l` |
|
||||||
|
| `0x38788` `0x4b8b8` `0xb6574` `0xdbbac` `0x133da0` `0x162840` | 181, 93, 41, 77, 76, 60 | — nobody | mostly none (other objects in the stage) |
|
||||||
|
|
||||||
Two resources land exactly (`bdy_04_l`, `eng_01_l`). Everywhere else a **full or
|
Four of the ship's drawn buffers are anchored exactly right. Two are the twin
|
||||||
`_m` resource starts at the offset of its own `_l` buffer** — `bdy_03` decodes
|
collapse below. **Two are mis-anchors of a size we do have**: the engine's
|
||||||
815 vertices beginning where the engine's 146-vertex LOD begins — and eight
|
51-vertex bridge buffer is at `0x40e418` while both our 51-vertex bridge
|
||||||
drawn buffers are claimed by no resource at all. This is the anchor scan taking
|
resources sit at `0x40de48`, and its 44-vertex `eng_02_l` is at `0x44a32c` while
|
||||||
the first candidate that validates, seen directly rather than inferred.
|
ours is elsewhere entirely. The remaining six belong to other objects in the
|
||||||
|
stage (`n041`, `n042`, `e303`), only two of which we decode at the right size.
|
||||||
|
|
||||||
|
> **A trap worth recording.** The first version of this table located our
|
||||||
|
> resources by *searching the container for their leading vertices* instead of
|
||||||
|
> asking the decoder, and it read much worse — full and `_m` resources appearing
|
||||||
|
> to start inside their own `_l` buffer. That was an artifact: **the same leading
|
||||||
|
> vertex run occurs at several offsets in one container** (`e106_bdy_03`'s first
|
||||||
|
> eight positions occur at four, `bdy_01`'s at three). That multiplicity is
|
||||||
|
> itself the reason the anchor scan is ambiguous — but it makes a position search
|
||||||
|
> useless for asking where a resource *was* anchored. Hence `vbuf_offset`.
|
||||||
|
|
||||||
**3. The twin pair is an anchoring error, and the mirror is in the data.** For
|
**3. The twin pair is an anchoring error, and the mirror is in the data.** For
|
||||||
`e106_bdy_01_l` ≡ `e106_bdy_02_l` the capture shows **two** 119-vertex buffers
|
`e106_bdy_01_l` ≡ `e106_bdy_02_l` the capture shows **two** 119-vertex buffers
|
||||||
|
|||||||
Reference in New Issue
Block a user