coverage_audit measures index coverage per decoded sub-mesh. 8586 of them reference their last vertex exactly, so the 'buffer not covered' gate is well founded -- but 18 had NEGATIVE slack: indices up to 364 vertices past the end of their own buffer, emitted because anchor_grouped_meshes reads pre-pivot sub-meshes unconditionally. Quality gates stay relaxed there (a tiny flat lead part is legitimately poor) but index range is now required. Coverage 6069/6294 unchanged, inconsistency 56 unchanged, truth table still 46/46 claimed, suite green; the vertex total drops by exactly the 1546 garbage verts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
49 lines
2.0 KiB
Rust
49 lines
2.0 KiB
Rust
//! Do real index buffers address their whole vertex pool?
|
||
//!
|
||
//! `validate_block` rejects a block whose indices reach fewer than `vtx_count−4`
|
||
//! vertices ("buffer not covered"). That gate is the furthest-reached rejection
|
||
//! for a handful of resources that never decode — so the question is whether it
|
||
//! is well founded. This measures the slack on every block that DOES decode: if
|
||
//! real geometry always covers its pool, under-coverage is good evidence of a
|
||
//! wrong candidate and the gate stands.
|
||
use sylpheed_formats::mesh::Xbg7Model;
|
||
use std::collections::BTreeMap;
|
||
|
||
fn main() {
|
||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||
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 hist: BTreeMap<i64, usize> = BTreeMap::new();
|
||
let mut worst: Vec<(i64, String)> = Vec::new();
|
||
for f in &files {
|
||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||
for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) {
|
||
for sub in &m.meshes {
|
||
if sub.positions.is_empty() || sub.indices.is_empty() {
|
||
continue;
|
||
}
|
||
let max_idx = *sub.indices.iter().max().unwrap() as i64;
|
||
let slack = sub.positions.len() as i64 - 1 - max_idx;
|
||
*hist.entry(slack.min(20)).or_default() += 1;
|
||
if slack > 4 {
|
||
worst.push((slack, format!("{} in {}", m.name, f.file_name().unwrap().to_string_lossy())));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
println!("unreferenced tail vertices (vtx_count − 1 − max index), over decoded sub-meshes:");
|
||
for (slack, n) in &hist {
|
||
println!(" {:>3}{} : {n}", slack, if *slack == 20 { "+" } else { " " });
|
||
}
|
||
worst.sort_by_key(|(s, _)| std::cmp::Reverse(*s));
|
||
for (s, w) in worst.iter().take(5) {
|
||
println!(" largest slack {s}: {w}");
|
||
}
|
||
}
|