re: CORRECTION -- the static assembler is not at fault, the pre-fix decoder was

Last iteration reported e303_wep_01 as inflated 30-110x by assemble_ship. Wrong:
the composite nodes carry scale 1.0 and orthonormal matrices, and under the
current decoder the turret places as 49x23x42 at +/-179. The 1600x2100x4800 box
only appears with XBG7_COVER_SLACK=4 -- the exact-coverage fix had already
repaired the turret too. The error came from comparing a deliberately pre-fix
render against post-fix measurements.

The containment screen's blindness therefore has a mundane cause: one mis-decode
inflated the envelope and hid another. Kept as a forward-looking invariant with
that caveat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 17:28:18 +00:00
parent 27fd9f186f
commit 095816834a
3 changed files with 72 additions and 20 deletions

View File

@@ -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 <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 full = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false);
let names: Vec<String> = 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<String> = 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());
}

View File

@@ -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 <container.xpr> <composite name>
use sylpheed_formats::mesh::scene_world_nodes;
fn main() {
let a: Vec<String> = 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]
);
}
}

View File

@@ -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 at 6× while being **capture-verified exact** in the truth table — a useful
reminder that a bulky hull is not a bug. 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 `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 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 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 It does not flag `e106_bdy_03` under the **pre-fix** decoder, and the reason is
the static assembly shows why, and the reason is a bigger finding than the screen: 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.
``` ⚠️ **This was first written up here as a static-assembler defect. That was
e106_bdy_03 X[ -300, 300] Y[ 0, 1600] Z[ 188, 1186] wrong.** The composite's nodes are clean — `rou_e303_wep_01_root` carries scale
e303_wep_01 X[-1179, 421] Y[ -996, 1104] Z[-2368, 2432] ← shared turret `1.0` and an orthonormal matrix at `t[±179, 54, 32]` — and under the **current**
e106_bdy_04 X[ -215, 215] Y[ -165, 234] Z[ -991, 389] 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 So the screen's blindness had a mundane cause — one mis-decode hid another by
around a hull of ~400×400×2000, so nothing can protrude past the envelope. Its inflating the envelope — and it is worth keeping as a forward-looking invariant,
*decode* is not at fault — the resource comes out 49×23×42 in `Stage_S01` through with that caveat: it can only see a protruding part when nothing else is inflated.
`Stage_S06` alike, consistent everywhere — so this is a **static-assembler**
defect: the placement blows a 49-unit turret up by 30110× 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.
### The anchor work has plateaued at 98.7 % — state and what is left ### The anchor work has plateaued at 98.7 % — state and what is left