re(xbg7): a capture names a never-decoding family, and the cause is mixed strides in one pool

capture_ib_truth now proposes an identity for each drawn buffer our decoder cannot
place, by matching (vertex, index) counts against declared-but-not-decoded
resources. That identified n201_01/_02/_03 in Stage_S02.xpr — three of the 63
resources that decode in no container — and the capture pins all four sub-meshes:

  #0 vb 0x32BA71C ib 0x32B39FC 4464 idx 777 v stride 24
  #1 vb 0x32BEFF4 ib 0x32B5CDC 4464 idx 869 v stride 24
  #2 vb 0x32C416C ib 0x32B7FBC  576 idx 192 v stride 24
  #3 vb 0x32C536C ib 0x32B843C 4464 idx 869 v stride 28   <- different

The layout matches our assumptions exactly (tight index packing, last buffer flush
against vb0 so pad 0, span 27936 == align4-summed markers, contiguous vertex
buffers, max index == verts-1 everywhere). The defect is that sub-mesh #3 has a
different stride AND its own vertex shader: anchor_grouped_meshes parses one
declaration per resource and applies its stride to every sub-mesh, so it reads #3
out of phase (372 non-finite position components of 2607), and since the pivot is
the largest index count with ties going to the last marker, the pivot IS that
sub-mesh — so the whole resource is declined.

Also adds examples/miss_targets.rs (which container to aim a capture at): 63
resources decode nowhere, 58 of them in exactly one container, clustering as
Stage_S16 21 (e901_wing_05_*), ptc_pack 12, Base 6, then per-stage n2xx groups.
Flying stage 16 did not draw the e901 wings — the container is resident but the
unit must also be on screen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
This commit is contained in:
2026-08-13 10:52:35 +00:00
parent 92d36ec10f
commit 27577538cb
5 changed files with 179 additions and 2 deletions

View File

@@ -17,7 +17,7 @@
//!
//! Usage:
//! cargo run --release --example capture_ib_truth -- <Stage_SNN.xpr> <capture.log>...
use sylpheed_formats::mesh::Xbg7Model;
use sylpheed_formats::mesh::{debug_resource_params, xbg7_resource_names, Xbg7Model};
use sylpheed_formats::ship_capture::{parse_capture, CapturedDraw};
use std::collections::HashMap;
@@ -113,6 +113,25 @@ fn main() {
}
println!("decoded {} resources, {} distinct vertex offsets\n", models.len(), by_off.len());
// Declared-but-not-decoded resources, indexed by their first marker's
// (vertex, index) counts. A drawn buffer our decoder cannot name is the one
// thing a capture can give the residual misses: ground truth for where the
// block actually is. Matching on counts is enough to propose an identity —
// then `debug_try_anchor` at that offset says which gate rejects it.
let decoded_names: std::collections::HashSet<String> =
models.iter().map(|m| m.name.clone()).collect();
let mut undecoded_by_counts: HashMap<(usize, usize), Vec<String>> = HashMap::new();
for n in xbg7_resource_names(&bytes) {
if decoded_names.contains(&n) {
continue;
}
if let Some((markers, _)) = debug_resource_params(&bytes, &n) {
if let Some(&(v, i)) = markers.first() {
undecoded_by_counts.entry((v, i)).or_default().push(n);
}
}
}
// ── The report: one row per drawn BUFFER, aggregating its index batches.
let mut per_buf: HashMap<u32, (usize, Vec<sylpheed_formats::ship_capture::CapturedIndexBuffer>, u32)> =
HashMap::new();
@@ -177,7 +196,14 @@ fn main() {
.map(|(n, p, i)| format!("{n}(v{p},i{i})"))
.collect::<Vec<_>>()
.join(" "))
.unwrap_or_else(|| "-".into())
.unwrap_or_else(|| {
// Nothing of ours sits here — is it a resource that never
// decodes? Propose it by (vertex, index) counts.
undecoded_by_counts
.get(&(*vcount as usize, total as usize))
.map(|v| format!("MISSED? {}", v.join(" ")))
.unwrap_or_else(|| "-".into())
})
),
));
}