feat(xbg7): read the per-sub-mesh vertex declarations (XBG7_SUBMESH_DECLS, off by default)
desc_dump shows each index marker is followed by its OWN element triples: n201_01 declares strides 24, 24, 24 and 28 (the last sub-mesh has a fourth element), which matches the runtime capture's stride=28 on that draw and the four distinct vertex shaders. parse_vertex_decl read the first declaration for the whole pool. all_vertex_decls reads one per marker; anchor_grouped_meshes uses each sub-mesh's own stride for the pool walk, the pivot validation and the read. debug_grouped_report follows the same setting so the diagnostic cannot blame the wrong gate — at n201_01's capture-proven pool start it now reports "pad 0: ACCEPTED" instead of a NaN position. With XBG7_SUBMESH_DECLS=1: resources that never decode 85 -> 47, resources decoding in no container 63 -> 30, capture oracles unchanged (93/93 index runs, 42/42 index counts), consistency unchanged at 96. Off by default because selection has not caught up: the three n201_0x copies then settle on one pool (twin_pairs_do_not_share_a_buffer fails), production still picks a start 4 bytes before sub-mesh #1 rather than the proven one even though the proven start validates and is unclaimed, and four newly decoded ptc_pack .dat composites carry degenerate triangles. Format truth is settled; choosing among candidates is the remaining work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
This commit is contained in:
71
crates/sylpheed-formats/examples/desc_dump.rs
Normal file
71
crates/sylpheed-formats/examples/desc_dump.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
//! Dump an XBG7 resource's descriptor around each index marker, to see whether a
|
||||
//! grouped pool carries a declaration PER sub-mesh (the 2026-08-13 mixed-stride
|
||||
//! finding says it must: n201's four sub-meshes use strides 24/24/24/28 and four
|
||||
//! different vertex shaders).
|
||||
//! Usage: desc_dump <container.xpr> <resource>
|
||||
|
||||
fn be32(b: &[u8], o: usize) -> u32 {
|
||||
u32::from_be_bytes(b[o..o + 4].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
let bytes = std::fs::read(&a[1]).unwrap();
|
||||
let want = &a[2];
|
||||
// Walk the XPR2 directory by hand (the crate's reader is private).
|
||||
// 16-byte directory entries at 0x10: type tag, data offset, descriptor size,
|
||||
// name offset — all relative to 0x10 (see texture::Xpr2ResourceEntry).
|
||||
let num = be32(&bytes, 12) as usize;
|
||||
for i in 0..num {
|
||||
let e = &bytes[0x10 + i * 16..0x10 + i * 16 + 16];
|
||||
let tag = &e[0..4];
|
||||
let data_off = be32(e, 4) as usize + 0x10;
|
||||
let desc_size = be32(e, 8) as usize;
|
||||
let name_off = be32(e, 12) as usize + 0x10;
|
||||
if tag != b"XBG7" {
|
||||
continue;
|
||||
}
|
||||
let name = {
|
||||
let s = name_off.min(bytes.len());
|
||||
let end = bytes[s..].iter().position(|&c| c == 0).unwrap_or(0) + s;
|
||||
String::from_utf8_lossy(&bytes[s..end]).to_string()
|
||||
};
|
||||
if name != *want {
|
||||
continue;
|
||||
}
|
||||
let desc = &bytes[data_off..(data_off + desc_size).min(bytes.len())];
|
||||
println!("{name}: descriptor at 0x{data_off:X}, {} bytes", desc.len());
|
||||
// markers: a == c*2, c%3==0, vertex count 32 bytes earlier
|
||||
let mut rel = 0usize;
|
||||
while rel + 8 <= desc.len() {
|
||||
let (x, c) = (be32(desc, rel), be32(desc, rel + 4));
|
||||
if c >= 3 && c % 3 == 0 && c < 400_000 && x == c * 2 && rel >= 32 {
|
||||
let vc = be32(desc, rel - 32);
|
||||
if (3..=200_000).contains(&vc) {
|
||||
println!("\n marker @0x{rel:X}: {vc} verts / {c} indices — declaration triples after it:");
|
||||
let mut r = rel + 8;
|
||||
let mut stride = 0u32;
|
||||
for _ in 0..12 {
|
||||
if r + 12 > desc.len() {
|
||||
break;
|
||||
}
|
||||
let (o, code, usage) = (be32(desc, r), be32(desc, r + 4), be32(desc, r + 8) >> 16);
|
||||
if o == 0x00FF_0000 || code == 0xFFFF_FFFF || o > 0x1000 {
|
||||
println!(" end marker @0x{r:X}: off=0x{o:X} code=0x{code:X}");
|
||||
break;
|
||||
}
|
||||
println!(" off {o:>3} code 0x{:06X} usage {usage}", code & 0xFF_FFFF);
|
||||
stride = stride.max(o + 4);
|
||||
r += 12;
|
||||
}
|
||||
println!(" (min stride from element offsets: {stride})");
|
||||
rel += 8;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
rel += 4;
|
||||
}
|
||||
return;
|
||||
}
|
||||
eprintln!("resource not found");
|
||||
}
|
||||
@@ -5,7 +5,9 @@ fn main(){
|
||||
for n in sylpheed_formats::mesh::xbg7_resource_names(&bytes) {
|
||||
if !filter.is_empty() && !n.contains(&filter) { continue }
|
||||
if let Some((m,stride))=sylpheed_formats::mesh::debug_resource_params(&bytes,&n) {
|
||||
println!("{n:<28} stride {stride} markers {:?}", &m[..m.len().min(4)]);
|
||||
let strides = sylpheed_formats::mesh::debug_decl_strides(&bytes, &n);
|
||||
println!("{n:<28} decl-stride {stride} markers {:?} per-sub-mesh strides {:?}",
|
||||
&m[..m.len().min(4)], strides);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
crates/sylpheed-formats/examples/starts_probe.rs
Normal file
7
crates/sylpheed-formats/examples/starts_probe.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
fn main(){let a:Vec<String>=std::env::args().collect();let b=std::fs::read(&a[1]).unwrap();
|
||||
let stride:usize=a[2].parse().unwrap(); let want:usize=a[3].parse().unwrap();
|
||||
let s=sylpheed_formats::mesh::debug_vertex_run_starts(&b,stride);
|
||||
println!("{} candidate starts at stride {}", s.len(), stride);
|
||||
println!("contains {:#x}: {}", want, s.contains(&want));
|
||||
let near:Vec<String>=s.iter().filter(|&&o| o.abs_diff(want)<0x200).map(|o|format!("{o:#x}")).collect();
|
||||
println!("nearby: {}", near.join(" "));}
|
||||
Reference in New Issue
Block a user