diff --git a/crates/sylpheed-formats/src/mesh.rs b/crates/sylpheed-formats/src/mesh.rs index 876ce50..355e0bf 100644 --- a/crates/sylpheed-formats/src/mesh.rs +++ b/crates/sylpheed-formats/src/mesh.rs @@ -583,6 +583,7 @@ impl Xbg7Model { &r.decl, min_consistency, &empty_taken, + 0, ) .into_iter() .collect() @@ -607,6 +608,7 @@ impl Xbg7Model { &r.decl, min_consistency, &empty_taken, + 0, ) .into_iter() .collect() @@ -650,10 +652,39 @@ impl Xbg7Model { // (the adjacency-anchor path) take part; grouped-pool models are left // exactly as they were. let mut taken: std::collections::HashSet = std::collections::HashSet::new(); + // Opt-in monotone pass: the last offset handed to each (stride, vtx, idx) + // signature. Resources that share a signature are interchangeable to the + // validator — many containers hold dozens of identical 24-vertex bound + // boxes — so file order is the only thing that can pin which is which. + let monotone = std::env::var("XBG7_MONOTONE").is_ok(); + let mut last_by_sig: std::collections::HashMap<(usize, usize, usize), usize> = + std::collections::HashMap::new(); let mut models: Vec = Vec::with_capacity(decoded.len()); for (i, mut m) in decoded { let r = &resources[i]; let starts = &starts_by_stride[&r.decl.stride]; + if monotone && m.meshes.len() == 1 && r.markers.len() == 1 { + let (vc, ic) = r.markers[0]; + let sig = (r.decl.stride, vc, ic); + let floor = last_by_sig.get(&sig).map_or(0, |o| o + 1); + if m.meshes[0].vbuf_offset.map_or(false, |o| o < floor) { + if let Some(alt) = anchor_pool_mesh( + bytes, + starts, + ic, + vc, + &r.decl, + min_consistency, + &taken, + floor, + ) { + m.meshes[0] = alt; + } + } + if let Some(o) = m.meshes[0].vbuf_offset { + last_by_sig.insert(sig, o); + } + } if let Some(vb) = m.meshes[0].vbuf_offset { if taken.contains(&vb) { if m.meshes.len() == 1 && r.markers.len() == 1 { @@ -666,6 +697,7 @@ impl Xbg7Model { &r.decl, min_consistency, &taken, + 0, ) { m.meshes[0] = alt; } @@ -1169,9 +1201,15 @@ fn anchor_pool_mesh( decl: &VertexDecl, min_consistency: f32, taken: &std::collections::HashSet, + min_vb: usize, ) -> Option { let idx_bytes = index_count * 2; for &vb in starts { + // Monotone assignment (opt-in): resources of one signature are laid out + // in descriptor order, so a later one may not take an earlier block. + if vb < min_vb { + continue; + } // A buffer another resource already claimed is not a candidate: the // engine draws each part from its own buffer (proved for the mirrored // `e106_bdy_0{1,2}_l` twins by a runtime capture), so two resources diff --git a/docs/re/structures/xbg7-mesh.md b/docs/re/structures/xbg7-mesh.md index b3c2589..eb0c9d3 100644 --- a/docs/re/structures/xbg7-mesh.md +++ b/docs/re/structures/xbg7-mesh.md @@ -1128,10 +1128,21 @@ rather than being the "one real disagreement" first written here. different containers.** What remains is 24-vertex boxes swapping identities: many of them exist, they are structurally identical, and distinct assignment gives each a *distinct* block without pinning *which* block belongs to which name. Fixing -that needs an ordering rule (descriptor order ↔ ascending offset). Monotone -anchoring was refuted once before, but that was measured under a decoder with -several other defects, so it is worth re-testing — and it is now cheap to judge, -because the target is a single number: 89 → 0. +that needs an ordering rule (descriptor order ↔ ascending offset). + +**Re-tested, and refuted again.** `XBG7_MONOTONE=1` adds exactly that rule: per +`(stride, vtx_count, idx_count)` signature, a later resource may not take an +earlier block than the previous one of the same signature. Result: **89 minority +decodes — unchanged.** The reason is structural. Monotonicity constrains the +order *within* a container, but the disagreement is *between* containers, which +hold different numbers of these boxes in different arrangements; a consistent +within-container order does not force a consistent name↔box mapping across them. + +So the remaining 89 are **bounding-box identity ambiguities**, and pinning them +needs information from the descriptor itself (a composite's own bounds or node +data), not another anchoring heuristic. The knob stays, default off, with this +measurement recorded so the idea is not tried a third time. Defaults verified +unchanged: all ten test suites green. (The boxes are harmless in themselves — nothing draws them. But no consistency figure should be quoted without saying whether it counts them; the ignored