test: lock in the twin invariant -- 0 unrelated pairs disc-wide, 1 known collapse

twin_mirror_audit applies the capture-derived rule to all 166 containers: of 34
equal-count twin pairs, 18 are exact X-mirrors, 15 related another way, 1
identical, 0 unrelated. Two calibration fixes were needed first (authored halves
need a tolerance, and a mirrored pair may be stored in another vertex order).
The one collapse, n206_01/_02, is a grouped-pool pair -- the path distinct
assignment excludes -- so it names the next target. Added a disc-gated
regression test; refreshed the stale ignore message on the consistency test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 03:44:29 +00:00
parent 55e1fe3783
commit fd34d350f2
4 changed files with 227 additions and 1 deletions

View File

@@ -0,0 +1,44 @@
//! Does the container hold an X-mirrored copy of a resource's decoded buffer?
//!
//! The twin invariant (see `twin_mirror_audit`) flags `…_01`/`…_02` pairs that
//! decode to unrelated geometry. If the mirror of one twin's buffer exists
//! somewhere else in the container, that offset is where the other twin belongs
//! and the pair is a mis-anchor; if it does not exist, the pair is simply not a
//! mirrored pair.
//!
//! Usage: find_mirror <container.xpr> <resource>...
use sylpheed_formats::mesh::Xbg7Model;
use std::collections::HashSet;
fn main() {
let a: Vec<String> = std::env::args().collect();
let bytes = std::fs::read(&a[1]).expect("container");
let want: HashSet<String> = a[2..].iter().cloned().collect();
let models = Xbg7Model::models_named(&bytes, &want, &|| false);
let be = |at: usize| f32::from_be_bytes(bytes[at..at + 4].try_into().unwrap());
for m in &models {
let pos: Vec<[f32; 3]> = m.meshes.iter().flat_map(|s| s.positions.clone()).take(8).collect();
if pos.len() < 8 {
continue;
}
let anchored = m.meshes[0].vbuf_offset.unwrap_or(0);
let (mut direct, mut mirror) = (Vec::new(), Vec::new());
for o in (0..bytes.len().saturating_sub(12 + 8 * 24)).step_by(4) {
for (flip, out) in [(1.0f32, &mut direct), (-1.0f32, &mut mirror)] {
if (0..8).all(|k| {
let at = o + k * 24;
(be(at) - flip * pos[k][0]).abs() <= 1e-4
&& (be(at + 4) - pos[k][1]).abs() <= 1e-4
&& (be(at + 8) - pos[k][2]).abs() <= 1e-4
}) {
out.push(o);
}
}
}
println!(
"{:<20} anchored 0x{anchored:x} direct copies {:x?} mirrored copies {:x?}",
m.name, direct, mirror
);
}
}

View File

@@ -0,0 +1,110 @@
//! Do port/starboard twins decode to mirror images of each other?
//!
//! A runtime capture proved the container stores both halves of the `e106` hull
//! as separate, X-reflected buffers. That gives a **capture-free invariant**:
//! a `…_01`/`…_02` pair of equal vertex count should decode to geometry that is
//! an exact X-mirror — never to identical geometry (that is the collapse the
//! distinct-assignment fix targets), and never to something unrelated (that is a
//! mis-anchor no count-based metric can see).
//!
//! Usage: twin_mirror_audit <resource3d_dir>
use sylpheed_formats::mesh::Xbg7Model;
use std::collections::BTreeMap;
fn key(p: [f32; 3]) -> (i64, i64, i64) {
(
(p[0] * 1e3).round() as i64,
(p[1] * 1e3).round() as i64,
(p[2] * 1e3).round() as i64,
)
}
/// Elementwise equality with a tolerance. Truncating keys is too strict for a
/// mirrored pair: the halves are authored, not bit-negated, so they differ in
/// the last digits and an exact key test reports them as unrelated.
fn near(p: [f32; 3], q: [f32; 3]) -> bool {
(0..3).all(|c| (p[c] - q[c]).abs() <= 1e-3 * (1.0 + q[c].abs()))
}
fn main() {
let dir = std::env::args().nth(1).expect("resource3d dir");
let mut files: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
.collect();
files.sort();
let (mut same, mut mirrored, mut unrelated, mut related) = (0usize, 0usize, 0usize, 0usize);
let mut examples: Vec<String> = Vec::new();
for f in &files {
let Ok(bytes) = std::fs::read(f) else { continue };
let models = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false);
let by_name: BTreeMap<&str, &Xbg7Model> =
models.iter().map(|m| (m.name.as_str(), m)).collect();
for m in &models {
let Some(stem) = m.name.strip_suffix("_01") else { continue };
let Some(t) = by_name.get(format!("{stem}_02").as_str()) else { continue };
let a: Vec<[f32; 3]> = m.meshes.iter().flat_map(|s| s.positions.clone()).collect();
let b: Vec<[f32; 3]> = t.meshes.iter().flat_map(|s| s.positions.clone()).collect();
if a.len() != b.len() || a.is_empty() {
continue;
}
let ident = a.iter().zip(&b).all(|(p, q)| near(*p, *q));
let mirr = a.iter().zip(&b).all(|(p, q)| near([-p[0], p[1], p[2]], *q));
// A pair that is neither may still be RELATED: mirrored on another
// axis, or the same point cloud in a different vertex order. Only a
// pair that is none of these is evidence of a mis-anchor.
let mirr_y = a.iter().zip(&b).all(|(p, q)| near([p[0], -p[1], p[2]], *q));
let mirr_z = a.iter().zip(&b).all(|(p, q)| near([p[0], p[1], -p[2]], *q));
let set = |v: &Vec<[f32; 3]>| {
let mut s: Vec<_> = v.iter().map(|p| key(*p)).collect();
s.sort_unstable();
s
};
let same_cloud = set(&a) == set(&b);
// …and the same point cloud after mirroring, for a pair whose
// halves are authored in different vertex order.
let mirrored_cloud = {
let am: Vec<[f32; 3]> = a.iter().map(|p| [-p[0], p[1], p[2]]).collect();
set(&am) == set(&b)
};
if !ident && !mirr && (mirr_y || mirr_z || same_cloud || mirrored_cloud) {
related += 1;
continue;
}
if ident {
same += 1;
if examples.len() < 6 {
examples.push(format!(
"identical: {} / {}_02 in {}",
m.name,
stem,
f.file_name().unwrap().to_string_lossy()
));
}
} else if mirr {
mirrored += 1;
} else {
unrelated += 1;
if examples.len() < 6 {
examples.push(format!(
"unrelated: {} / {}_02 in {}",
m.name,
stem,
f.file_name().unwrap().to_string_lossy()
));
}
}
}
}
println!("twin pairs of equal vertex count: {}", same + mirrored + unrelated + related);
println!(" exact X-mirror (expected) : {mirrored}");
println!(" IDENTICAL (collapse) : {same}");
println!(" related other way (Y/Z mirror, reordered): {related}");
println!(" unrelated (mis-anchor?) : {unrelated}");
for e in examples {
println!(" e.g. {e}");
}
}

View File

@@ -53,7 +53,7 @@ fn span(m: &Xbg7Model) -> Option<[i64; 3]> {
} }
#[test] #[test]
#[ignore = "known-failing: 125 of 681 shared resources decode inconsistently. A neighbourhood anchor took this to 51 but regressed the e106 twin-mirror decision and was withdrawn — see docs/re/structures/xbg7-mesh.md"] #[ignore = "known-failing: 62 of 714 shared resources decode inconsistently (was 125 of 681; distinct anchor assignment + the 0.42 connectivity cap fixed the rest). Note this metric is the WEAKER witness — a systematic mis-anchor is consistent — see docs/re/structures/xbg7-mesh.md"]
fn shared_resources_decode_identically_in_every_container() { fn shared_resources_decode_identically_in_every_container() {
let Some(root) = disc_root() else { let Some(root) = disc_root() else {
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)"); eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
@@ -102,3 +102,45 @@ fn shared_resources_decode_identically_in_every_container() {
bad.len() bad.len()
); );
} }
/// Port/starboard twins must not decode to the *same* buffer.
///
/// A runtime capture showed the container stores both halves of the `e106` hull
/// as separate X-reflected buffers, so a `…_01`/`…_02` pair of equal vertex
/// count should come out mirrored (or related by another axis / vertex order) —
/// never identical, which is the collapse distinct assignment fixes. Disc-wide
/// this holds for every such pair except `n206`, whose twins are grouped-pool
/// resources (4 sub-meshes) and so are excluded from distinct assignment; that
/// one is the remaining known case, asserted explicitly so it cannot grow.
#[test]
fn twin_pairs_do_not_share_a_buffer() {
let Some(root) = disc_root() else {
eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
return;
};
let mut files: Vec<PathBuf> = std::fs::read_dir(root.join("hidden/resource3d"))
.expect("resource3d/")
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
.collect();
files.sort();
let mut collapsed: Vec<String> = Vec::new();
for f in &files {
let Ok(bytes) = std::fs::read(f) else { continue };
let models = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false);
let by_name: BTreeMap<&str, &Xbg7Model> =
models.iter().map(|m| (m.name.as_str(), m)).collect();
for m in &models {
let Some(stem) = m.name.strip_suffix("_01") else { continue };
let Some(t) = by_name.get(format!("{stem}_02").as_str()) else { continue };
let (a, b) = (m.meshes[0].vbuf_offset, t.meshes[0].vbuf_offset);
if a.is_some() && a == b {
collapsed.push(format!("{}/{stem}_02 in {}", m.name, f.file_name().unwrap().to_string_lossy()));
}
}
}
collapsed.retain(|c| !c.starts_with("n206_01"));
assert!(collapsed.is_empty(), "twin pairs sharing one buffer: {collapsed:?}");
}

View File

@@ -930,6 +930,36 @@ 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 and would look identical to a correct decode in every count-based metric. Only a
capture (or the twin-pair invariant) can catch it. capture (or the twin-pair invariant) can catch 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
equal vertex count should decode to **mirrored** geometry, never to the same
buffer. `examples/twin_mirror_audit.rs` applies it to all 166 containers:
| twin pairs of equal vertex count | 34 |
|---|---|
| exact X-mirror | **18** |
| related another way (Y/Z mirror, or the same cloud in another vertex order) | 15 |
| identical — a collapse | **1** |
| unrelated — no relation at all | **0** |
Two calibration notes, because the first run of this audit got both wrong.
Comparing quantised keys **exactly** reported four false "unrelated" pairs
(`e101_eng_01/_02`): the halves are authored, not bit-negated, so they differ in
the last digits — a tolerance is required. And a mirrored pair may be stored in a
**different vertex order**, so the multiset has to be compared mirrored as well
as directly. With both fixed, nothing on the disc is unrelated.
The single collapse is `n206_01`/`n206_02` (`Stage_S08`), both anchored at
`0x33b6e54` while the container holds a second direct copy at `0x342d984` and
mirrors at `0x33b7754`/`0x342e284`. It survives because both twins are
**grouped-pool** resources (4 sub-meshes), and distinct assignment excludes that
path — so this is the concrete next target, and the fix direction is to extend
distinctness across grouped models.
`tests/mesh_consistency_disc.rs::twin_pairs_do_not_share_a_buffer` locks this in:
no twin pair may share a buffer, with `n206` the one asserted exception.
Not settled: `e106_brg_01_b_02``e106_brg_01_l` (51 verts). A second 51-vertex Not settled: `e106_brg_01_b_02``e106_brg_01_l` (51 verts). A second 51-vertex
`vbase` exists in the logs but is **not** from this container, and the container `vbase` exists in the logs but is **not** from this container, and the container
holds three near-identical 51-vertex runs, so the pair has no oracle yet. holds three near-identical 51-vertex runs, so the pair has no oracle yet.