`cargo fmt --all -- --check` has failed on every run in this repository's history, identically on `main` and on every branch. This is #12. Mechanical: `cargo fmt --all`, nothing else. 154 files, all `.rs`, no other extension touched. `cargo check --workspace` exits 0 afterwards, so nothing changed semantically. ON THE ORDERING, WHICH WAS THE REAL QUESTION. HANDOFF-2026-09-06 section 7 warns this is the expensive fix: a whole-tree reformat before #7 and #8 return "would put a conflict in every file of 861 commits and make the reviews those items exist to enable unreadable". That is measurably too pessimistic, and it had been reasoned rather than tested. Measured here by three-way merging a rustfmt'd `main` against both unmerged branches, file by file: file/branch pairs tested 32 merges CLEAN 28 merges CONFLICTING 4 (8 conflict hunks total) sylpheed-cli/src/main.rs 1 hunk sylpheed-export/src/check.rs 1 sylpheed-export/src/screen.rs 4 sylpheed-export/src/video.rs 2 All four are against `auto/frame-blend-draw-path` only; `auto/port-p6-audio` does not conflict anywhere. The earlier framing -- 154 dirty files, 133 that cannot collide, 21 that can, the collision set carrying 147 of 774 hunks (19%) -- reproduces exactly. What it did not say is that most of the 21 still merge cleanly, because rustfmt's edits and the branches' edits rarely land on the same lines. So the cost of sweeping now is 4 files and 8 hunks for one branch, against a check that is otherwise red forever. Deliberately NOT folded into the WASM PR: 154 reformatted files would make that one unreviewable. Closes #12
168 lines
6.5 KiB
Rust
168 lines
6.5 KiB
Rust
//! Recover exact capital-ship part placement from a Canary capture log
|
|
//! and (optionally) emit a checked-in placement-table block.
|
|
//!
|
|
//! The correlation math lives in [`sylpheed_formats::ship_capture`]; this example
|
|
//! is the CLI wrapper: it reads the log, decodes the ship's parts from the disc to
|
|
//! get their vertex counts + leading positions (the match keys), correlates, and
|
|
//! prints the result. With `--emit` it prints the `ship …` block to paste into
|
|
//! `crates/sylpheed-formats/data/ship_placements.txt`.
|
|
//!
|
|
//! Matching is **LOD-aware**: a ship on screen at distance is drawn with its
|
|
//! `_m`/`_l` LOD copies, which live in the same local frame as the base part — so
|
|
//! each base part tries its own vcount first, then its LOD variants', and the
|
|
//! recovered transform is recorded under the base name. Same-vcount twins
|
|
//! (mirrored port/starboard hulls) are routed by the draw's position dump.
|
|
//!
|
|
//! Usage:
|
|
//! SYLPHEED_ISO=... cargo run --release --example correlate_capture -- \
|
|
//! <capture.log> <Stage_SNN> <ship_id> [ref_part_substr] [--emit]
|
|
//! e.g. SYLPHEED_ISO="/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of
|
|
//! Deception (USA, Europe) (En,Ja).iso" \
|
|
//! cargo run --release --example correlate_capture -- \
|
|
//! xenia_ship_capture.log Stage_S01 e106 bdy_04 --emit
|
|
|
|
use std::collections::HashSet;
|
|
use std::path::Path;
|
|
use sylpheed_formats::mesh::{xbg7_resource_names, Xbg7Model};
|
|
use sylpheed_formats::ship::{is_base_part, ship_id_of};
|
|
use sylpheed_formats::ship_capture::{
|
|
correlate, parse_capture, parse_drawlog, serialize_table, PartKey,
|
|
};
|
|
use sylpheed_formats::xiso::open_iso;
|
|
|
|
fn main() {
|
|
let args: Vec<String> = std::env::args().collect();
|
|
let positional: Vec<&String> = args[1..].iter().filter(|a| !a.starts_with("--")).collect();
|
|
let emit = args.iter().any(|a| a == "--emit");
|
|
if positional.len() < 3 {
|
|
eprintln!(
|
|
"usage: correlate_capture <capture.log> <Stage_SNN> <ship_id> [ref_part_substr] [--emit]"
|
|
);
|
|
std::process::exit(2);
|
|
}
|
|
let (log, stage, id) = (positional[0], positional[1], positional[2]);
|
|
let ref_sub = positional.get(3).map(|s| s.as_str()).unwrap_or("bdy_04");
|
|
let iso = std::env::var("SYLPHEED_ISO").expect("SYLPHEED_ISO");
|
|
|
|
// Accept either the F10 ship-capture format or the draw-logger format;
|
|
// auto-detect by trying the F10 parser first.
|
|
let text = std::fs::read_to_string(log).expect("read log");
|
|
let mut draws = parse_capture(&text);
|
|
if draws.is_empty() {
|
|
draws = parse_drawlog(&text);
|
|
eprintln!("parsed {} draws (draw-logger format)", draws.len());
|
|
} else {
|
|
eprintln!("parsed {} draws (F10 capture format)", draws.len());
|
|
}
|
|
|
|
// Decode the ship's base parts AND their LOD copies (vcount + leading
|
|
// positions are the match keys; LODs share the base part's local frame).
|
|
let bytes = {
|
|
let rt = tokio::runtime::Builder::new_current_thread()
|
|
.enable_all()
|
|
.build()
|
|
.unwrap();
|
|
rt.block_on(async {
|
|
let mut r = open_iso(Path::new(&iso)).await.unwrap();
|
|
r.read_file(&format!("hidden/resource3d/{stage}.xpr"))
|
|
.await
|
|
.unwrap()
|
|
})
|
|
};
|
|
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();
|
|
// Candidate resources per base part: itself + `_m`/`_l`/`_d` LOD copies.
|
|
let mut want: HashSet<String> = base_parts.iter().cloned().collect();
|
|
for p in &base_parts {
|
|
for suf in ["_m", "_l", "_d"] {
|
|
let cand = format!("{p}{suf}");
|
|
if names.contains(&cand) {
|
|
want.insert(cand);
|
|
}
|
|
}
|
|
}
|
|
let models = Xbg7Model::models_named(&bytes, &want, &|| false);
|
|
let positions_of = |name: &str| -> Option<Vec<[f32; 3]>> {
|
|
let m = models.iter().find(|m| m.name == name)?;
|
|
Some(
|
|
m.meshes
|
|
.iter()
|
|
.flat_map(|s| s.positions.iter().copied())
|
|
.collect(),
|
|
)
|
|
};
|
|
|
|
// One PartKey per (part, variant-vcount present in the capture) — correlate
|
|
// takes the first that validates. Validation uses the UNION of the part's
|
|
// variant position sets: a draw's `vcount` can match one LOD while its
|
|
// buffer holds another variant's geometry (seen on the real e106: the
|
|
// 558-vcount draw carried the FULL 1633-vert bdy_04 buffer), and every
|
|
// variant is the same part in the same local frame.
|
|
let mut keys: Vec<PartKey> = Vec::new();
|
|
for part in &base_parts {
|
|
let variants = [
|
|
part.clone(),
|
|
format!("{part}_m"),
|
|
format!("{part}_l"),
|
|
format!("{part}_d"),
|
|
];
|
|
let union: Vec<[f32; 3]> = variants
|
|
.iter()
|
|
.filter_map(|v| positions_of(v))
|
|
.flatten()
|
|
.collect();
|
|
let mut any = false;
|
|
for cand in &variants {
|
|
if let Some(pos) = positions_of(cand) {
|
|
let vcount = pos.len() as u32;
|
|
if draws.iter().any(|d| d.vcount == vcount) {
|
|
let lod = if cand == part {
|
|
"full"
|
|
} else {
|
|
cand.rsplit('_').next().unwrap_or("?")
|
|
};
|
|
eprintln!(" {part:20} try vcount={vcount:6} [{lod}]");
|
|
keys.push(PartKey {
|
|
part: part.clone(),
|
|
vcount,
|
|
ref_pos: union.clone(),
|
|
});
|
|
any = true;
|
|
}
|
|
}
|
|
}
|
|
if !any {
|
|
eprintln!(" {part:20} — no draw matches any LOD (culled/off-screen?)");
|
|
}
|
|
}
|
|
|
|
let Some(ship) = correlate(id, &draws, &keys, ref_sub) else {
|
|
eprintln!("no parts matched a captured draw");
|
|
return;
|
|
};
|
|
|
|
eprintln!(
|
|
"\nreference = {} → ship-relative placement:",
|
|
ship.reference
|
|
);
|
|
for p in &ship.parts {
|
|
eprintln!(
|
|
" {:18} T=[{:9.1}{:9.1}{:9.1}]",
|
|
p.part, p.t[0], p.t[1], p.t[2]
|
|
);
|
|
}
|
|
for part in &base_parts {
|
|
if !ship.parts.iter().any(|p| &p.part == part) {
|
|
eprintln!(" {part:18} — NOT placed (culled, or its only vcount hit failed position validation)");
|
|
}
|
|
}
|
|
|
|
if emit {
|
|
print!("{}", serialize_table(std::slice::from_ref(&ship)));
|
|
}
|
|
}
|