re: n206 was fixed after all -- the audit was classifying by geometry, not buffer

debug_grouped_report (why_rejected) shows n206_02's alternative pool is ACCEPTED
at pad 0 under production gates and is in the candidate list -- and the decoder
does take it: n206_02 now anchors at 0x342d984. The 'still collapsed' reading
came from the audit comparing decoded geometry, and that offset holds a direct
(unmirrored) copy, so a separated pair still looked identical. The audit now
requires a SHARED BUFFER to call it a collapse: disc-wide 18 exact mirrors, 16
related, 0 collapses, 0 unrelated -- and the regression test drops its exception.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 04:10:05 +00:00
parent bb0c94c0ce
commit db8104ab99
6 changed files with 159 additions and 23 deletions

View File

@@ -755,6 +755,60 @@ pub fn debug_try_anchor(
None
}
/// Diagnostic: why does the decoder refuse a grouped-pool resource at a given
/// pool start? Recomputes the pool layout exactly as [`anchor_grouped_meshes`]
/// does and reports the pivot sub-mesh's verdict for each index/vertex pad —
/// so a capture-proven pool that the decoder rejects names the gate to fix.
pub fn debug_grouped_report(bytes: &[u8], name: &str, vb0: usize) -> Vec<String> {
let Some((decl, markers)) = decl_of(bytes, name) else {
return vec!["no such XBG7 resource".into()];
};
let n = markers.len();
if n == 0 {
return vec!["no index markers".into()];
}
let (mut rel_ib, mut acc_i) = (Vec::with_capacity(n), 0usize);
for &(_, ic) in &markers {
rel_ib.push(acc_i);
acc_i = align4(acc_i + ic * 2);
}
let span = rel_ib[n - 1] + markers[n - 1].1 * 2;
let kmax = (0..n).max_by_key(|&i| markers[i].1).unwrap_or(0);
let (vck, ick) = markers[kmax];
let mut off_v = 0usize;
for &(vc, _) in markers.iter().take(kmax) {
off_v += vc * decl.stride;
}
let mut out = vec![format!(
"{name}: {n} sub-meshes, pivot #{kmax} ({vck} verts, {ick} idx), pool span {span}"
)];
for pad in 0..=3usize {
if vb0 < span + pad {
out.push(format!(" pad {pad}: pool start is before the index pool"));
continue;
}
let ib0 = vb0 - span - pad;
// Same gates the production pivot test uses: strict winding (0.85)
// AND connectivity. Reporting at 0.0 would accept blocks the decoder
// rejects and send the reader chasing the wrong gate.
let verdict = validate_block_report(
bytes,
ib0 + rel_ib[kmax],
vb0 + off_v,
vck,
ick,
&decl,
0.85,
true,
);
out.push(match verdict {
Ok(()) => format!(" pad {pad}: ACCEPTED"),
Err(why) => format!(" pad {pad}: {why}"),
});
}
out
}
/// Diagnostic: for a resource whose vertex buffer is *known* (a runtime capture
/// names it), where could its index buffer be? The anchor scan assumes the index
/// buffer sits immediately before the vertex buffer; this scans the whole
@@ -998,17 +1052,43 @@ fn validate_block(
min_consistency: f32,
strict_connectivity: bool,
) -> bool {
validate_block_report(
bytes,
ib,
vb,
vtx_count,
index_count,
decl,
min_consistency,
strict_connectivity,
)
.is_ok()
}
/// [`validate_block`], but naming the gate that rejected a block. A runtime
/// capture can prove a block is real; when the decoder still refuses it, this
/// says which test is wrong rather than leaving a threshold to be guessed at.
fn validate_block_report(
bytes: &[u8],
ib: usize,
vb: usize,
vtx_count: usize,
index_count: usize,
decl: &VertexDecl,
min_consistency: f32,
strict_connectivity: bool,
) -> Result<(), String> {
let stride = decl.stride;
let idx_bytes = index_count * 2;
if ib + idx_bytes > bytes.len() {
return false;
return Err("index buffer runs past the container".into());
}
let vtx_bytes = match vtx_count.checked_mul(stride) {
Some(v) => v,
None => return false,
None => return Err("vertex byte count overflows".into()),
};
if vb + vtx_bytes > bytes.len() {
return false;
return Err("vertex buffer runs past the container".into());
}
// ── Full index validation: every index in range, uses ~all vertices. ──
@@ -1016,12 +1096,14 @@ fn validate_block(
for k in 0..index_count {
let i = be16(bytes, ib + k * 2) as u32;
if i >= vtx_count as u32 {
return false;
return Err(format!("index {i} out of range (vtx_count {vtx_count})"));
}
max_idx = max_idx.max(i);
}
if (max_idx as usize) + 4 < vtx_count {
return false;
return Err(format!(
"indices reach only {max_idx} of {vtx_count} vertices (buffer not covered)"
));
}
// ── Triangle quality: finite, non-degenerate, real spatial extent. ──
@@ -1046,7 +1128,7 @@ fn validate_block(
for (a, slot) in pc.iter_mut().enumerate() {
let x = bef(bytes, base + a * 4);
if !x.is_finite() || x.abs() > 1.0e6 {
return false;
return Err(format!("position component {x} is not finite/plausible"));
}
*slot = x;
lo[a] = lo[a].min(x);
@@ -1088,7 +1170,9 @@ fn validate_block(
}
let extent = (hi[0] - lo[0]).max(hi[1] - lo[1]).max(hi[2] - lo[2]);
if extent < 0.5 || sampled == 0 || degenerate * 10 > sampled * 3 {
return false; // too flat, or >30% degenerate → not this block
return Err(format!(
"extent {extent:.3} (min 0.5), {degenerate}/{sampled} degenerate (max 30%)"
));
}
// Connectivity check: a correctly-anchored mesh has triangle edges that
// are SMALL relative to its overall size (~0.050.15 of the bbox
@@ -1111,7 +1195,10 @@ fn validate_block(
// `XBG7_EDGE_CAP_SMALL` instead — a targeted relaxation, off by default.
let cap = if tris < small_tris() { small_cap() } else { edge_cap() };
if mean_edge / diag > cap {
return false;
return Err(format!(
"connectivity: mean_edge/diag {:.3} > cap {cap:.2}",
mean_edge / diag
));
}
}
// Winding-consistency gate (same as `from_xpr2`): a correctly-anchored
@@ -1123,10 +1210,13 @@ fn validate_block(
if nrm_counted > 0 && min_consistency > 0.0 {
let na = nrm_agree as f32 / nrm_counted as f32;
if na.max(1.0 - na) < min_consistency {
return false;
return Err(format!(
"winding consistency {:.3} < {min_consistency:.2}",
na.max(1.0 - na)
));
}
}
true
Ok(())
}
/// Decode a **grouped-pool** XBG7 resource: one whose descriptor holds *several*