diff --git a/crates/sylpheed-formats/examples/filter_consistency.rs b/crates/sylpheed-formats/examples/filter_consistency.rs new file mode 100644 index 0000000..8374ec5 --- /dev/null +++ b/crates/sylpheed-formats/examples/filter_consistency.rs @@ -0,0 +1,35 @@ +//! Does a FILTERED decode agree with the full one? +//! +//! Distinct anchor assignment resolves collisions against the set of resources +//! being decoded — so `models_named` (a filtered subset, used by the ship +//! assembler and the viewer) can reach a different answer from a whole-container +//! decode. This measures that directly. +//! Usage: filter_consistency [resource...] +use sylpheed_formats::mesh::Xbg7Model; +use std::collections::HashSet; +fn main() { + let a: Vec = std::env::args().collect(); + let bytes = std::fs::read(&a[1]).expect("container"); + let full = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false); + let names: Vec = if a.len() > 2 { + a[2..].to_vec() + } else { + full.iter().map(|m| m.name.clone()).collect() + }; + let mut differ = 0usize; + for n in &names { + let want: HashSet = std::iter::once(n.clone()).collect(); + let one = Xbg7Model::models_named(&bytes, &want, &|| false); + let (Some(f), Some(s)) = (full.iter().find(|m| &m.name == n), one.first()) else { + continue; + }; + let off = |m: &Xbg7Model| m.meshes.first().and_then(|s| s.vbuf_offset).unwrap_or(0); + if off(f) != off(s) { + differ += 1; + if differ <= 10 { + println!("{n}: full decode at 0x{:x}, filtered at 0x{:x}", off(f), off(s)); + } + } + } + println!("{differ} of {} resources decode differently when filtered", names.len()); +} diff --git a/crates/sylpheed-formats/examples/node_scale.rs b/crates/sylpheed-formats/examples/node_scale.rs new file mode 100644 index 0000000..e966ba1 --- /dev/null +++ b/crates/sylpheed-formats/examples/node_scale.rs @@ -0,0 +1,20 @@ +//! Dump a composite's scene nodes with their scale — the transform the static +//! assembler copies verbatim into a placement. +//! Usage: node_scale +use sylpheed_formats::mesh::scene_world_nodes; +fn main() { + let a: Vec = std::env::args().collect(); + let bytes = std::fs::read(&a[1]).expect("container"); + for n in scene_world_nodes(&bytes, &a[2]) { + // Row norms of `m`: a rotation has all three at 1.0; anything else is a + // scale baked into the matrix, which `s` does not show. + let norm = |r: [f32; 3]| (r[0] * r[0] + r[1] * r[1] + r[2] * r[2]).sqrt(); + println!( + "{:<26} s[{:7.3}{:7.3}{:7.3}] |m rows|[{:7.3}{:7.3}{:7.3}] t[{:9.1}{:9.1}{:9.1}]", + n.resource, + n.s[0], n.s[1], n.s[2], + norm(n.m[0]), norm(n.m[1]), norm(n.m[2]), + n.t[0], n.t[1], n.t[2] + ); + } +} diff --git a/docs/re/structures/xbg7-mesh.md b/docs/re/structures/xbg7-mesh.md index 0d92309..9c7de02 100644 --- a/docs/re/structures/xbg7-mesh.md +++ b/docs/re/structures/xbg7-mesh.md @@ -1097,33 +1097,30 @@ 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 containment screen fails too — because the static placement is worse +### The containment screen, and a correction: the assembler is NOT at fault `examples/envelope_screen.rs` is the metric the eye actually used: assemble a ship, and for each part measure how far its world box protrudes past the box of all the *other* parts, **per axis** (a bow legitimately extends the long axis, so -protrusion only means something against that axis' own envelope). +protrusion only counts against that axis' own envelope). -It does not flag `e106_bdy_03` either, before or after the coverage fix. Dumping -the static assembly shows why, and the reason is a bigger finding than the screen: +It does not flag `e106_bdy_03` under the **pre-fix** decoder, and the reason is +now established. Dumping the pre-fix static assembly shows the shared turret +`e303_wep_01` occupying **1600×2100×4800** around a ~400×400×2000 hull — an +envelope nothing can protrude past. -``` -e106_bdy_03 X[ -300, 300] Y[ 0, 1600] Z[ 188, 1186] -e303_wep_01 X[-1179, 421] Y[ -996, 1104] Z[-2368, 2432] ← shared turret -e106_bdy_04 X[ -215, 215] Y[ -165, 234] Z[ -991, 389] -``` +⚠️ **This was first written up here as a static-assembler defect. That was +wrong.** The composite's nodes are clean — `rou_e303_wep_01_root` carries scale +`1.0` and an orthonormal matrix at `t[±179, 54, 32]` — and under the **current** +decoder the same assembly places the turret as a tidy `49×23×42` box at ±179. +The inflation only appears with `XBG7_COVER_SLACK=4`, i.e. the pre-fix decode: +**the exact-coverage fix repaired the turret as well as `e106_bdy_03`.** The +error came from comparing a deliberately pre-fix render against post-fix +measurements of the same resource. -The **turret swallows the ship**: `e303_wep_01`'s world box is 1600×2100×4800 -around a hull of ~400×400×2000, so nothing can protrude past the envelope. Its -*decode* is not at fault — the resource comes out 49×23×42 in `Stage_S01` through -`Stage_S06` alike, consistent everywhere — so this is a **static-assembler** -defect: the placement blows a 49-unit turret up by 30–110× per axis (and -non-uniformly, so it is not a simple scale factor). - -Two conclusions. The screen is only meaningful once placement is trustworthy, and -**the static assembler has a worse defect than the decoder did** — one that the -capture-baked path for `e106` hides, because the baked table places the ship -correctly and is what the viewer prefers. +So the screen's blindness had a mundane cause — one mis-decode hid another by +inflating the envelope — and it is worth keeping as a forward-looking invariant, +with that caveat: it can only see a protruding part when nothing else is inflated. ### The anchor work has plateaued at 98.7 % — state and what is left