Files
Sylpheed/crates/sylpheed-formats/examples/capture_verify.rs
MechaCat02 ccd49ac31f fix(lint): clear the clippy gate across examples and tests
80 findings, not the 14 the first run showed -- clippy stops at the first
failing compilation unit, so `--keep-going` is what makes the list complete.

60 were machine-applicable (`cargo clippy --fix`). The rest by hand:

* five descending `sort_by` -> `sort_by_key(Reverse(..))`
* `chunks_exact(4)` on both sides of four zips, so the compared items stay
  `[u8; 4]` rather than one array against one slice
* three `type` aliases for the census maps and the captured-quad tuple
* `&PathBuf` -> `&Path` in two disc tests
* two range loops; one of them keeps `#[allow(needless_range_loop)]` with the
  reason -- the index is into a map's value, which changes each iteration
* the module doc list in `invert_capture` re-indented to markdown's rules
* `blit`'s eight arguments get `#[allow(too_many_arguments)]`, not a struct

One dead `let off = b.len();` in a `ratc` test is dropped rather than renamed.
The sibling test at :162 is the one that asserts an offset; if this one was
meant to as well, that is a test change and not a lint fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 16:42:41 +02:00

211 lines
7.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Verify static ship assembly against multi-snapshot, multi-instance F10
//! captures: cluster every part's ship-relative transform across ALL ships in
//! ALL snapshots, and compare the dominant clusters with `assemble_ship`.
//!
//! cargo run --release --example capture_verify -- <stage.xpr> <ship_id> <log> [log…]
//!
//! Every draw is position-validated against the ship's parts (any LOD, set
//! based). Each `bdy_04`(-LOD) draw acts as a ship reference; every validated
//! part draw within ship range contributes `rel = WV_ref⁻¹ ∘ WV_part`. The true
//! per-part placements appear as tight clusters repeated once PER SHIP per
//! snapshot; cross-ship pairings scatter and stay below the cluster threshold.
use std::collections::HashSet;
use sylpheed_formats::mesh::{xbg7_resource_names, Xbg7Model};
use sylpheed_formats::ship::{assemble_ship, is_base_part, ship_id_of};
use sylpheed_formats::ship_capture::{parse_capture, CapturedDraw};
type M3 = [[f64; 3]; 3];
fn transpose(m: &M3) -> M3 {
[
[m[0][0], m[1][0], m[2][0]],
[m[0][1], m[1][1], m[2][1]],
[m[0][2], m[1][2], m[2][2]],
]
}
fn mmul(a: &M3, b: &M3) -> M3 {
let mut o = [[0.0; 3]; 3];
for i in 0..3 {
for j in 0..3 {
o[i][j] = (0..3).map(|k| a[i][k] * b[k][j]).sum();
}
}
o
}
fn mv(m: &M3, v: [f64; 3]) -> [f64; 3] {
[
m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
]
}
fn main() {
let a: Vec<String> = std::env::args().collect();
let bytes = std::fs::read(&a[1]).unwrap();
let id = &a[2];
// Part position sets (base + LODs share the local frame) + per-variant vcounts.
let names = xbg7_resource_names(&bytes);
let base_parts: Vec<String> = names
.iter()
.filter(|n| is_base_part(n) && ship_id_of(n) == Some(id.as_str()))
.cloned()
.collect();
let mut want: HashSet<String> = base_parts.iter().cloned().collect();
for p in &base_parts {
for suf in ["_m", "_l", "_d"] {
let c = format!("{p}{suf}");
if names.contains(&c) {
want.insert(c);
}
}
}
let models = Xbg7Model::models_named(&bytes, &want, &|| false);
struct Part {
base: String,
vcounts: Vec<u32>,
pos: Vec<[f32; 3]>, // union of variants
}
let mut parts: Vec<Part> = Vec::new();
for p in &base_parts {
let mut vcounts = Vec::new();
let mut pos = Vec::new();
for cand in [
p.clone(),
format!("{p}_m"),
format!("{p}_l"),
format!("{p}_d"),
] {
if let Some(m) = models.iter().find(|m| m.name == cand) {
let mp: Vec<[f32; 3]> = m
.meshes
.iter()
.flat_map(|s| s.positions.iter().copied())
.collect();
vcounts.push(mp.len() as u32);
pos.extend(mp);
}
}
parts.push(Part {
base: p.clone(),
vcounts,
pos,
});
}
// Validate a draw against a part (direct or X-mirrored), like ship_capture.
let validate = |d: &CapturedDraw, p: &Part| -> Option<bool> {
if !p.vcounts.contains(&d.vcount) || d.pos.is_empty() {
return None;
}
let near = |a: &[f32; 3], b: &[f32; 3]| {
(a[0] - b[0]).abs() <= 1e-2
&& (a[1] - b[1]).abs() <= 1e-2
&& (a[2] - b[2]).abs() <= 1e-2
};
let (mut direct, mut mirror) = (0usize, 0usize);
for q in &d.pos {
if p.pos.iter().any(|v| near(q, v)) {
direct += 1;
}
let qm = [-q[0], q[1], q[2]];
if p.pos.iter().any(|v| near(&qm, v)) {
mirror += 1;
}
}
let need = d.pos.len().div_ceil(2);
if direct >= need && direct >= mirror {
Some(false)
} else if mirror >= need {
Some(true)
} else {
None
}
};
// Collect rel samples per part across all logs.
let mut samples: std::collections::BTreeMap<String, Vec<[f64; 3]>> = Default::default();
for log in &a[3..] {
let text = std::fs::read_to_string(log).unwrap();
let draws = parse_capture(&text);
// label validated draws
let mut labeled: Vec<(usize, usize, bool)> = Vec::new(); // (draw, part, mirrored)
for (di, d) in draws.iter().enumerate() {
for (pi, p) in parts.iter().enumerate() {
if let Some(mir) = validate(d, p) {
labeled.push((di, pi, mir));
break;
}
}
}
let refs: Vec<usize> = labeled
.iter()
.filter(|(_, pi, _)| parts[*pi].base.ends_with("bdy_04"))
.map(|(di, ..)| *di)
.collect();
for &ri in &refs {
let rf = &draws[ri];
let rt = transpose(&rf.r);
for &(di, pi, _mir) in &labeled {
let d = &draws[di];
let dt = [d.t[0] - rf.t[0], d.t[1] - rf.t[1], d.t[2] - rf.t[2]];
let rel = mv(&rt, dt);
if rel.iter().map(|v| v * v).sum::<f64>().sqrt() < 2600.0 {
// also require the rel rotation be finite-sane
let _rm = mmul(&rt, &d.r);
samples.entry(parts[pi].base.clone()).or_default().push(rel);
}
}
}
eprintln!(
"{log}: {} validated draws, {} bdy_04 refs",
labeled.len(),
refs.len()
);
}
// Cluster per part (greedy, 8-unit radius), print clusters with ≥3 samples.
println!("\n== dominant ship-relative placements (clusters ≥3 samples) ==");
for (part, mut v) in samples {
v.sort_by(|x, y| x[0].partial_cmp(&y[0]).unwrap());
let mut clusters: Vec<([f64; 3], usize)> = Vec::new();
for s in &v {
match clusters.iter_mut().find(|(c, _)| {
(c[0] - s[0]).abs() < 8.0 && (c[1] - s[1]).abs() < 8.0 && (c[2] - s[2]).abs() < 8.0
}) {
Some((c, n)) => {
for k in 0..3 {
c[k] = (c[k] * *n as f64 + s[k]) / (*n as f64 + 1.0);
}
*n += 1;
}
None => clusters.push((*s, 1)),
}
}
clusters.sort_by_key(|x| std::cmp::Reverse(x.1));
let tops: Vec<String> = clusters
.iter()
.filter(|(_, n)| *n >= 3)
.take(6)
.map(|(c, n)| format!("[{:7.1} {:7.1} {:7.1}]×{n}", c[0], c[1], c[2]))
.collect();
println!(" {part:20} {}", tops.join(" "));
}
// Static assembly rel bdy_04 for comparison.
println!("\n== static assemble_ship rel bdy_04 ==");
let placed = assemble_ship(&bytes, id, true);
if let Some(r4) = placed.iter().find(|p| p.resource == format!("{id}_bdy_04")) {
for p in &placed {
println!(
" {:20} [{:7.1} {:7.1} {:7.1}]",
p.resource,
p.t[0] - r4.t[0],
p.t[1] - r4.t[1],
p.t[2] - r4.t[2]
);
}
}
}