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]
);
}
}