fix(mesh): distinct assignment now covers grouped pools too

anchor_grouped_meshes takes the same taken set; a colliding grouped model is
re-placed whole past everything claimed, or keeps what it had. Runtime oracle
unchanged (45/46 exact, 0 unclaimed), coverage unchanged (6069), cross-container
inconsistency 62 -> 56, suite green.

It does not clear the n206 twin collapse: no alternative pool validates for the
loser, so that pair is a validator case (like eng_02_l before the cap move), not
a selection one. Also fixes a stale '0.28 (default)' label in edge_cap_sweep.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 03:53:10 +00:00
parent fd34d350f2
commit bb0c94c0ce
3 changed files with 50 additions and 9 deletions

View File

@@ -589,7 +589,8 @@ impl Xbg7Model {
} else {
// Several sub-meshes sharing grouped index/vertex pools → the
// deterministic grouped-pool decode (hero ships et al.).
let grouped = anchor_grouped_meshes(bytes, data_base, starts, &r.markers, &r.decl);
let grouped =
anchor_grouped_meshes(bytes, data_base, starts, &r.markers, &r.decl, &empty_taken);
if !grouped.is_empty() {
grouped
} else {
@@ -652,10 +653,10 @@ impl Xbg7Model {
let mut models: Vec<Xbg7Model> = Vec::with_capacity(decoded.len());
for (i, mut m) in decoded {
let r = &resources[i];
if m.meshes.len() == 1 && r.markers.len() == 1 {
if let Some(vb) = m.meshes[0].vbuf_offset {
if taken.contains(&vb) {
let starts = &starts_by_stride[&r.decl.stride];
let starts = &starts_by_stride[&r.decl.stride];
if let Some(vb) = m.meshes[0].vbuf_offset {
if taken.contains(&vb) {
if m.meshes.len() == 1 && r.markers.len() == 1 {
let (vtx_count, index_count) = r.markers[0];
if let Some(alt) = anchor_pool_mesh(
bytes,
@@ -668,10 +669,21 @@ impl Xbg7Model {
) {
m.meshes[0] = alt;
}
// No free candidate → keep the collided decode rather
// than drop the resource; coverage never regresses.
} else {
// Grouped pool: re-place the WHOLE pool past everything
// claimed, or keep what we had.
let alt = anchor_grouped_meshes(
bytes, data_base, starts, &r.markers, &r.decl, &taken,
);
if !alt.is_empty() {
m.meshes = alt;
}
}
if let Some(vb2) = m.meshes[0].vbuf_offset {
// No free candidate → keep the collided decode rather than
// drop the resource; coverage never regresses.
}
for sub in &m.meshes {
if let Some(vb2) = sub.vbuf_offset {
taken.insert(vb2);
}
}
@@ -1147,6 +1159,7 @@ fn anchor_grouped_meshes(
starts: &[usize],
markers: &[(usize, usize)], // (vtx_count, idx_count) in descriptor/file order
decl: &VertexDecl,
taken: &std::collections::HashSet<usize>,
) -> Vec<GameMesh> {
let n = markers.len();
if n == 0 {
@@ -1182,6 +1195,11 @@ fn anchor_grouped_meshes(
let (vck, ick) = markers[kmax];
for &vb0 in starts {
// Distinct assignment: a pool another resource already claimed is not a
// candidate (see the collision resolution in `anchor_models_filtered`).
if taken.contains(&vb0) {
continue;
}
for pad in 0..=3usize {
if vb0 < span + pad {
continue;