fix(xbg7): the index run was one element late for 575 sub-meshes
Extending the capture comparison from index COUNTS to index VALUES (`examples/capture_index_bytes.rs`, using the batch offsets the new ib logging gives) showed 76 of 93 Stage_S02 index runs identical to the GPU's and 17 differing — every difference a shift by exactly one element, on buffers whose index data sits at pad 2. `anchor_pool_mesh` returned the FIRST pad that validated, and pad 0 is tried first with the looser winding gate (0.70 vs 0.85). Read at pad 0, a pad-2 block yields [true[1], true[2], …, garbage]: every index in range, the pool covered, the positions right, the winding often just above 0.70 — so it validated, and every triangle was mis-wired. Nothing count-based could see it. The signature is decidable without the capture: a shifted run wires arbitrary vertices, so triangles come out degenerate. 282 of 283 correctly anchored Stage_S02 blocks have zero degenerate triangles, while the shifted readings carry 1–2 156. So score every validating pad by (degenerate triangles, then winding) and keep the best. `XBG7_PAD_FIRST_MATCH=1` restores the old behaviour. captured index runs identical: 76/93 -> 93/93 (2 025 elements) decoded runs with a degenerate triangle: 579 -> 16 (disc-wide) sub-meshes whose index run changed: 575 of 8 850 resources decoded / vertex anchors / consistency: unchanged (6 209 / same vb / 89) Locked in by tests/mesh_disc.rs::decoded_index_runs_have_almost_no_degenerate_triangles. Suite green with --include-ignored apart from the pre-existing known-failing cross-container consistency test (the 24-vertex bounding-box class). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
This commit is contained in:
@@ -424,3 +424,57 @@ fn stage_models_quality_audit() {
|
||||
assert!(huge < models.len() / 20, "few huge (skybox-plane) models");
|
||||
assert!(worst_deg < 0.35, "no model should be mostly-degenerate");
|
||||
}
|
||||
|
||||
/// A correctly located index run has **no degenerate triangles**. That is the
|
||||
/// signature the 2026-08-13 pad-scoring fix keys on: an index list read one
|
||||
/// element late still passes every count-based gate but wires arbitrary
|
||||
/// vertices, which produces triangles with a repeated index. Before the fix
|
||||
/// **579** decoded runs on the disc carried such triangles (and a runtime
|
||||
/// capture confirmed 17 of 93 index batches disagreed with the GPU); after it,
|
||||
/// 16 sub-meshes do — all in resources that are separately known-broken. Locking the
|
||||
/// number in, because the defect is invisible to coverage and to the anchor
|
||||
/// oracle: every count stays correct while the geometry is mis-wired.
|
||||
#[test]
|
||||
#[ignore = "requires extracted disc models — set SYLPHEED_RES3D"]
|
||||
fn decoded_index_runs_have_almost_no_degenerate_triangles() {
|
||||
let Some(dir) = res3d_dir() else {
|
||||
eprintln!("SKIP: resource3d dir not found (set SYLPHEED_RES3D)");
|
||||
return;
|
||||
};
|
||||
let mut files: Vec<PathBuf> = std::fs::read_dir(&dir)
|
||||
.expect("resource3d/")
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
|
||||
.collect();
|
||||
files.sort();
|
||||
|
||||
let mut offenders: Vec<(String, String, usize)> = Vec::new();
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let where_ = f.file_name().unwrap().to_string_lossy().to_string();
|
||||
for m in Xbg7Model::stage_models(&bytes) {
|
||||
for sm in &m.meshes {
|
||||
let d = sm
|
||||
.indices
|
||||
.chunks_exact(3)
|
||||
.filter(|t| t[0] == t[1] || t[1] == t[2] || t[0] == t[2])
|
||||
.count();
|
||||
if d > 0 {
|
||||
offenders.push((m.name.clone(), where_.clone(), d));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The known 16 sub-meshes: the grouped `.dat` break composites in `ptc_pack`
|
||||
// (`f102`/`f104`/`e107`, whose marker lists are documented not to map onto the
|
||||
// stored blocks — 5 sub-meshes), plus `e201_bdy_03_m` (2 containers) and
|
||||
// `_rou_f402_dead` (9). Every one has an independently recorded anchoring
|
||||
// problem; a NEW name appearing here is a regression.
|
||||
assert!(
|
||||
offenders.len() <= 16,
|
||||
"{} decoded index runs contain degenerate triangles (expected ≤ 16): {:?}",
|
||||
offenders.len(),
|
||||
&offenders[..offenders.len().min(20)]
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user