From bb0c94c0cec0f8c3c485506beff3003389df2425 Mon Sep 17 00:00:00 2001 From: "Claude (auto-RE)" Date: Wed, 12 Aug 2026 03:53:10 +0000 Subject: [PATCH] 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) --- .../examples/edge_cap_sweep.rs | 2 +- crates/sylpheed-formats/src/mesh.rs | 34 ++++++++++++++----- docs/re/structures/xbg7-mesh.md | 23 +++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/crates/sylpheed-formats/examples/edge_cap_sweep.rs b/crates/sylpheed-formats/examples/edge_cap_sweep.rs index a310ecc..d874a5e 100644 --- a/crates/sylpheed-formats/examples/edge_cap_sweep.rs +++ b/crates/sylpheed-formats/examples/edge_cap_sweep.rs @@ -75,6 +75,6 @@ fn main() { } println!( "cap={} models={models} verts={verts} shared={shared} inconsistent={inconsistent}", - std::env::var("XBG7_EDGE_CAP").unwrap_or_else(|_| "0.28 (default)".into()) + std::env::var("XBG7_EDGE_CAP").unwrap_or_else(|_| "0.42 (default)".into()) ); } diff --git a/crates/sylpheed-formats/src/mesh.rs b/crates/sylpheed-formats/src/mesh.rs index 196fc47..e3087da 100644 --- a/crates/sylpheed-formats/src/mesh.rs +++ b/crates/sylpheed-formats/src/mesh.rs @@ -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 = 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, ) -> Vec { 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; diff --git a/docs/re/structures/xbg7-mesh.md b/docs/re/structures/xbg7-mesh.md index 84d7f42..dd9edca 100644 --- a/docs/re/structures/xbg7-mesh.md +++ b/docs/re/structures/xbg7-mesh.md @@ -930,6 +930,29 @@ of one buffer, a resource landing on a **mirrored** copy would be a real defect and would look identical to a correct decode in every count-based metric. Only a capture (or the twin-pair invariant) can catch it. +### Distinct assignment extended to grouped pools (2026-08-12) + +`anchor_grouped_meshes` now takes the same `taken` set: a pool whose start another +resource already claimed is skipped, and a grouped model that collides is +**re-placed whole** past everything claimed (or keeps what it had, so coverage +cannot regress). + +| | oracle exact / 46 | unclaimed | resources decoded | shared inconsistent | +|---|---|---|---|---| +| single-mesh distinctness only | 45 | 0 | 6 069 | 62 | +| **+ grouped pools (now)** | 45 | 0 | 6 069 | **56** | + +The runtime oracle is unchanged and cross-container inconsistency falls another +10 %. The suite, including the disc- and ISO-gated tests, stays green. + +**It does *not* clear the `n206` collapse**, and that is informative: both twins +are grouped, the loser is re-placed past the taken pool, and no alternative pool +**validates** — so it keeps the collided decode. `n206_02` is therefore the same +class as `e106_eng_02_l` was before the cap moved: the correct block is rejected +by the validator, not lost to selection. Its correct pool is one of +`0x342d984` (direct) or `0x33b7754` / `0x342e284` (mirrored); a capture of a +stage containing `n206` would say which, and is the cheapest way to settle it. + ### The twin invariant, checked disc-wide (2026-08-12) The capture gave a rule that needs no capture to apply: a `…_01`/`…_02` pair of