Files
Syplheed-Reborn/crates/sylpheed-formats/examples/envelope_screen.rs
Claude (auto-RE) 27fd9f186f 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>
2026-08-12 17:20:52 +00:00

107 lines
4.5 KiB
Rust

//! 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);
}