re: containment screen fails too -- the static assembler inflates a shared turret
envelope_screen measures per-axis protrusion past the sibling envelope, the relationship the eye used. It still does not flag e106_bdy_03, and dumping the static assembly shows why: e303_wep_01's world box is 1600x2100x4800 around a ~400x400x2000 hull, so nothing can protrude. The decode is innocent -- that resource is 49x23x42 in every container -- so the static assembler is inflating it 30-110x per axis, non-uniformly. The screen is only meaningful once placement is trustworthy, and the assembler now has a worse defect than the decoder had. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
106
crates/sylpheed-formats/examples/envelope_screen.rs
Normal file
106
crates/sylpheed-formats/examples/envelope_screen.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
//! Does every part of a ship sit inside the envelope its siblings describe?
|
||||
//!
|
||||
//! `slab_screen` compared *scale* and could not see the `e106` slab. What the eye
|
||||
//! used when the render exposed it was **relationship**: a blocky mass sitting
|
||||
//! apart from the hull. This measures that — assemble a ship, and for each part
|
||||
//! ask how far its world box protrudes beyond the box of all the OTHER parts,
|
||||
//! relative to the ship's own size. A mis-anchored block sticks out; a real part,
|
||||
//! however big, is part of the silhouette.
|
||||
//!
|
||||
//! Usage: envelope_screen <resource3d_dir> [protrusion_fraction]
|
||||
use sylpheed_formats::mesh::Xbg7Model;
|
||||
use sylpheed_formats::ship::{assemble_ship, ship_id_of};
|
||||
use std::collections::{BTreeSet, HashSet};
|
||||
|
||||
fn main() {
|
||||
let dir = std::env::args().nth(1).expect("resource3d dir");
|
||||
let limit: f32 = std::env::args().nth(2).and_then(|s| s.parse().ok()).unwrap_or(0.35);
|
||||
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 flagged = 0usize;
|
||||
for f in &files {
|
||||
let Ok(bytes) = std::fs::read(f) else { continue };
|
||||
let ids: BTreeSet<String> = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false)
|
||||
.iter()
|
||||
.filter_map(|m| ship_id_of(&m.name).map(|s| s.to_string()))
|
||||
.collect();
|
||||
for id in &ids {
|
||||
let placed = assemble_ship(&bytes, id, true);
|
||||
if placed.len() < 3 {
|
||||
continue;
|
||||
}
|
||||
let want: HashSet<String> = placed.iter().map(|p| p.resource.clone()).collect();
|
||||
let models = Xbg7Model::models_named(&bytes, &want, &|| false);
|
||||
// World box per placement.
|
||||
let mut boxes: Vec<(String, [f32; 3], [f32; 3])> = Vec::new();
|
||||
for p in &placed {
|
||||
let Some(m) = models.iter().find(|m| m.name == p.resource) else { continue };
|
||||
let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
|
||||
for s in &m.meshes {
|
||||
for q in &s.positions {
|
||||
let w = p.apply(*q);
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(w[k]);
|
||||
hi[k] = hi[k].max(w[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if lo[0] != f32::MAX {
|
||||
boxes.push((p.resource.clone(), lo, hi));
|
||||
}
|
||||
}
|
||||
if boxes.len() < 3 {
|
||||
continue;
|
||||
}
|
||||
for i in 0..boxes.len() {
|
||||
// Envelope of every OTHER part.
|
||||
let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
|
||||
for (j, b) in boxes.iter().enumerate() {
|
||||
if i == j {
|
||||
continue;
|
||||
}
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(b.1[k]);
|
||||
hi[k] = hi[k].max(b.2[k]);
|
||||
}
|
||||
}
|
||||
// Per-AXIS: a bow legitimately extends the ship along its long
|
||||
// axis, so protrusion only means something measured against the
|
||||
// envelope's size IN THAT AXIS. The e106 slab stuck ~1 500 out in
|
||||
// Y where its siblings spanned ~800.
|
||||
let mut worst = 0.0f32;
|
||||
let mut worst_out = 0.0f32;
|
||||
for k in 0..3 {
|
||||
let size_k = hi[k] - lo[k];
|
||||
if size_k <= 1.0 {
|
||||
continue;
|
||||
}
|
||||
let out_k = (lo[k] - boxes[i].1[k]).max(boxes[i].2[k] - hi[k]).max(0.0);
|
||||
if out_k / size_k > worst {
|
||||
worst = out_k / size_k;
|
||||
worst_out = out_k;
|
||||
}
|
||||
}
|
||||
let (out, size) = (worst_out, 1.0f32);
|
||||
let _ = size;
|
||||
if worst > limit {
|
||||
flagged += 1;
|
||||
println!(
|
||||
"{:<20} {:<22} protrudes {:>7.0} beyond its siblings ({:.0}% of the ship)",
|
||||
f.file_name().unwrap().to_string_lossy(),
|
||||
boxes[i].0,
|
||||
out,
|
||||
100.0 * worst
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("{flagged} parts protrude more than {:.0}% of their ship's size", 100.0 * limit);
|
||||
}
|
||||
@@ -1097,6 +1097,34 @@ 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
|
||||
|
||||
`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).
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
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]
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### The anchor work has plateaued at 98.7 % — state and what is left
|
||||
|
||||
Four evidence-driven changes took the decoder from 5 480 to **6 212 of 6 294**
|
||||
|
||||
Reference in New Issue
Block a user