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
|
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
|
||||||
|
|
||||||
|
`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
|
### 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**
|
Four evidence-driven changes took the decoder from 5 480 to **6 212 of 6 294**
|
||||||
|
|||||||
Reference in New Issue
Block a user