From cdaeac8e1b76b582052fde3ef0de155f14037993 Mon Sep 17 00:00:00 2001 From: "Claude (auto-RE)" Date: Wed, 12 Aug 2026 18:02:06 +0000 Subject: [PATCH] re: the consistency figure is mostly composite bounding boxes -- real disagreement is 1 consensus_check names the minority container instead of just flagging disagreement: 89 minority decodes across 477 resources with a majority, and 88 are scene composites. A composite's descriptor carries a 24-vertex bounding box (e_rou_e106 -> 22x22x22, e_rou_f106 -> 745x718x718); those boxes look interchangeable so the assignment shuffles per container. 1141 of 6209 decoded resources have scene nodes. Counting only real geometry the disc has ONE disagreement: e101_wep_01_l in Stage_S25. Co-Authored-By: Claude Opus 5 (1M context) --- .../examples/composite_geometry.rs | 34 ++++++++ .../examples/consensus_check.rs | 87 +++++++++++++++++++ docs/re/structures/xbg7-mesh.md | 30 +++++++ 3 files changed, 151 insertions(+) create mode 100644 crates/sylpheed-formats/examples/composite_geometry.rs create mode 100644 crates/sylpheed-formats/examples/consensus_check.rs diff --git a/crates/sylpheed-formats/examples/composite_geometry.rs b/crates/sylpheed-formats/examples/composite_geometry.rs new file mode 100644 index 0000000..d63e8d0 --- /dev/null +++ b/crates/sylpheed-formats/examples/composite_geometry.rs @@ -0,0 +1,34 @@ +//! How much of the "decoded geometry" belongs to SCENE COMPOSITES rather than to +//! real meshes? A composite (`rou_*`, `e_rou_*`) carries node transforms; the +//! anchor scan nevertheless finds a block for it, and that pseudo-geometry lands +//! in coverage and consistency counts. +//! Usage: composite_geometry +use sylpheed_formats::mesh::{scene_world_nodes, Xbg7Model}; +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 total, mut composite, mut composite_named) = (0usize, 0usize, 0usize); + for f in &files { + let Ok(bytes) = std::fs::read(f) else { continue }; + for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) { + total += 1; + let has_nodes = !scene_world_nodes(&bytes, &m.name).is_empty(); + let named = m.name.starts_with("rou_") || m.name.starts_with("e_rou_"); + if has_nodes { + composite += 1; + } + if named { + composite_named += 1; + } + } + } + println!( + "{total} decoded; {composite} have scene nodes (a composite), {composite_named} are named rou_/e_rou_" + ); +} diff --git a/crates/sylpheed-formats/examples/consensus_check.rs b/crates/sylpheed-formats/examples/consensus_check.rs new file mode 100644 index 0000000..eee8d67 --- /dev/null +++ b/crates/sylpheed-formats/examples/consensus_check.rs @@ -0,0 +1,87 @@ +//! Minority report: which container decodes a shared resource differently from +//! all the others? +//! +//! Cross-container consistency has been measured as "do the spans agree", which +//! only says a resource is inconsistent — not which copy is wrong. With three or +//! more copies the majority is the reference, and the minority names the +//! container AND the resource to look at. That is what caught the `n054`/`n056` +//! shift chain after the exact-coverage fix. +//! +//! Usage: consensus_check [--list] +use sylpheed_formats::mesh::Xbg7Model; +use std::collections::{BTreeMap, HashMap}; + +fn main() { + let dir = std::env::args().nth(1).expect("resource3d dir"); + let list = std::env::args().any(|a| a == "--list"); + 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(); + + // name -> [(container, verts, tris, span)] + let mut seen: BTreeMap> = BTreeMap::new(); + for f in &files { + let Ok(bytes) = std::fs::read(f) else { continue }; + let where_ = f.file_name().unwrap().to_string_lossy().to_string(); + for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) { + let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]); + for s in &m.meshes { + for q in &s.positions { + for k in 0..3 { + lo[k] = lo[k].min(q[k]); + hi[k] = hi[k].max(q[k]); + } + } + } + if lo[0] == f32::MAX { + continue; + } + let v: usize = m.meshes.iter().map(|s| s.positions.len()).sum(); + let t: usize = m.meshes.iter().map(|s| s.indices.len() / 3).sum(); + let span = [ + (hi[0] - lo[0]).round() as i64, + (hi[1] - lo[1]).round() as i64, + (hi[2] - lo[2]).round() as i64, + ]; + seen.entry(m.name.clone()).or_default().push((where_.clone(), v, t, span)); + } + } + + let (mut resources, mut minority) = (0usize, 0usize); + let mut rows: Vec = Vec::new(); + for (name, list_) in &seen { + // Only compare decodes that agree on how much geometry they found, and + // only where a majority can exist. + if list_.len() < 3 || !list_.iter().all(|e| e.1 == list_[0].1 && e.2 == list_[0].2) { + continue; + } + let mut votes: HashMap<[i64; 3], usize> = HashMap::new(); + for e in list_ { + *votes.entry(e.3).or_default() += 1; + } + let (best, n) = votes.iter().max_by_key(|(_, n)| **n).unwrap(); + if *n * 2 <= list_.len() { + continue; // no majority — cannot call anyone the odd one out + } + resources += 1; + for e in list_.iter().filter(|e| e.3 != *best) { + minority += 1; + rows.push(format!( + "{:<22} {name:<26} {:?} vs the other {n} containers' {:?}", + e.0, e.3, best + )); + } + } + if list { + for r in &rows { + println!("{r}"); + } + } + println!( + "{minority} minority decodes across {resources} resources that have a majority" + ); +} diff --git a/docs/re/structures/xbg7-mesh.md b/docs/re/structures/xbg7-mesh.md index 40a3960..b61e8c2 100644 --- a/docs/re/structures/xbg7-mesh.md +++ b/docs/re/structures/xbg7-mesh.md @@ -1097,6 +1097,36 @@ that carry it) and `t901_e01_D` is 58×59×3013 (a mast). And `f101_bdy_01` flag at 6× while being **capture-verified exact** in the truth table — a useful reminder that a bulky hull is not a bug. +### The consistency figure is mostly bounding boxes — real disagreement is ONE resource + +`examples/consensus_check.rs` sharpens the cross-container test: with three or +more copies of a resource, the majority span is the reference and the **minority +names the container that is wrong**, not just "these disagree". + +Disc-wide: **89 minority decodes across 477 resources that have a majority** — +and **88 of the 89 are scene composites** (`rou_*` / `e_rou_*`), not drawable +geometry. Inspecting one shows why: `e_rou_e106` decodes a **24-vertex, +12-triangle box** (span 22×22×22), `e_rou_f106` another (745×718×718). A +composite's descriptor carries a **bounding box**, the anchor scan finds it, and +because those boxes are interchangeable-looking the assignment shuffles between +containers. `mesh::scene_world_nodes` identifies them structurally: **1 141 of +the 6 209 decoded resources have scene nodes**, i.e. are composites (478 are +named `rou_`/`e_rou_`). + +So the headline number this file has been quoting — cross-container inconsistency +— is **dominated by composite bounding boxes**. Counting only real geometry, the +whole disc now has **one** disagreement: + +``` +Stage_S25.xpr e101_wep_01_l [779, 5769, 5769] vs the other 3 containers' [206, 545, 545] +``` + +That is the sharper metric to work against, and the concrete next target. (The +composite boxes are harmless — nothing draws them — but they should be excluded +from any consistency figure quoted in future, and the ignored +`shared_resources_decode_identically_in_every_container` test measures the mixed +population, so its number is not comparable to this one.) + ### What the exact-coverage fix actually reached The fix was justified on one resource (`e106_bdy_03`) and one render. Comparing