`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
188 lines
7.5 KiB
Rust
188 lines
7.5 KiB
Rust
//! Invert the capture↔part match: instead of asking, per ship part, "is there a
|
||
//! draw with this vertex count?", ask of the **capture's** biggest draws "which
|
||
//! decoded resource in this stage container has that vertex count?".
|
||
//!
|
||
//! This is the diagnostic for the 2026-07-31 negative result (Stage_S02 capture,
|
||
//! zero parts correlated). It separates three hypotheses:
|
||
//! 1. LOD/variant vcount not covered by the correlator's variant list
|
||
//! → the big draws DO map to named resources, just not to the `_m`/`_l`/`_d`
|
||
//! set the correlator tries;
|
||
//! 2. position validation over-rejects
|
||
//! → the vcounts match the very parts we asked for (so the vcount key was
|
||
//! fine and the rejection happened later);
|
||
//! 3. a different draw path (instanced/batched/merged buffers)
|
||
//! → the big draws match NO resource in the container at all.
|
||
//!
|
||
//! Usage:
|
||
//! SYLPHEED_ISO=... cargo run --release --example invert_capture -- \
|
||
//! <capture.log> <Stage_SNN> [top_n] [--all]
|
||
//! `--all` lists every capture vcount, not just the `top_n` (default 40) largest.
|
||
|
||
use std::collections::{HashMap, HashSet};
|
||
use std::path::Path;
|
||
use sylpheed_formats::mesh::{xbg7_resource_names, Xbg7Model};
|
||
use sylpheed_formats::ship_capture::{parse_capture, parse_drawlog};
|
||
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 all = args.iter().any(|a| a == "--all");
|
||
if positional.len() < 2 {
|
||
eprintln!("usage: invert_capture <capture.log> <Stage_SNN> [top_n] [--all]");
|
||
std::process::exit(2);
|
||
}
|
||
let (log, stage) = (positional[0], positional[1]);
|
||
let top_n: usize = positional.get(2).and_then(|s| s.parse().ok()).unwrap_or(40);
|
||
let iso = std::env::var("SYLPHEED_ISO").expect("SYLPHEED_ISO");
|
||
|
||
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);
|
||
println!("parsed {} draws (draw-logger format)", draws.len());
|
||
} else {
|
||
println!("parsed {} draws (F10 capture format)", draws.len());
|
||
}
|
||
|
||
// Decode EVERY geometry resource in the stage container, not just one ship's.
|
||
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);
|
||
println!("{stage}.xpr: {} XBG7 resources", names.len());
|
||
let want: HashSet<String> = names.iter().cloned().collect();
|
||
let models = Xbg7Model::models_named(&bytes, &want, &|| false);
|
||
println!("decoded {} models", models.len());
|
||
|
||
// vcount -> resource names with that many vertices.
|
||
let mut by_vcount: HashMap<u32, Vec<String>> = HashMap::new();
|
||
for m in &models {
|
||
let v: usize = m.meshes.iter().map(|s| s.positions.len()).sum();
|
||
by_vcount.entry(v as u32).or_default().push(m.name.clone());
|
||
}
|
||
// Per-submesh counts too: a draw may be one sub-mesh of a multi-mesh resource.
|
||
let mut by_sub_vcount: HashMap<u32, Vec<String>> = HashMap::new();
|
||
for m in &models {
|
||
for (i, s) in m.meshes.iter().enumerate() {
|
||
if m.meshes.len() > 1 {
|
||
by_sub_vcount
|
||
.entry(s.positions.len() as u32)
|
||
.or_default()
|
||
.push(format!("{}#{i}", m.name));
|
||
}
|
||
}
|
||
}
|
||
|
||
// Capture vcounts, de-duped by (vbase, vcount) so a re-drawn part counts once
|
||
// per distinct buffer.
|
||
let mut draw_count: HashMap<u32, usize> = HashMap::new();
|
||
let mut bufs: HashMap<u32, HashSet<u32>> = HashMap::new();
|
||
for d in &draws {
|
||
*draw_count.entry(d.vcount).or_default() += 1;
|
||
bufs.entry(d.vcount).or_default().insert(d.vbase);
|
||
}
|
||
let mut vcounts: Vec<u32> = draw_count.keys().copied().collect();
|
||
vcounts.sort_unstable_by(|a, b| b.cmp(a));
|
||
|
||
let matched_draws: usize = draws
|
||
.iter()
|
||
.filter(|d| by_vcount.contains_key(&d.vcount) || by_sub_vcount.contains_key(&d.vcount))
|
||
.count();
|
||
println!(
|
||
"\n{} distinct vcounts; {}/{} draws have a vcount present in {stage}.xpr ({:.1}%)",
|
||
vcounts.len(),
|
||
matched_draws,
|
||
draws.len(),
|
||
100.0 * matched_draws as f64 / draws.len().max(1) as f64
|
||
);
|
||
|
||
let shown = if all {
|
||
vcounts.len()
|
||
} else {
|
||
top_n.min(vcounts.len())
|
||
};
|
||
println!("\nlargest capture vcounts (draws / distinct vbufs) → matching resources:");
|
||
for &v in vcounts.iter().take(shown) {
|
||
let n = draw_count[&v];
|
||
let b = bufs[&v].len();
|
||
let mut hit: Vec<String> = by_vcount.get(&v).cloned().unwrap_or_default();
|
||
let sub: Vec<String> = by_sub_vcount.get(&v).cloned().unwrap_or_default();
|
||
hit.extend(sub.into_iter().map(|s| format!("{s} (sub)")));
|
||
let label = if hit.is_empty() {
|
||
"— no resource".to_string()
|
||
} else {
|
||
let mut h = hit.clone();
|
||
h.sort();
|
||
h.truncate(6);
|
||
format!("{}{}", h.join(", "), if hit.len() > 6 { ", …" } else { "" })
|
||
};
|
||
println!(" vcount {v:6} draws {n:4} bufs {b:3} {label}");
|
||
}
|
||
|
||
// `--ship <id>`: every resource of one ship family, with its vertex count and
|
||
// whether the capture drew it — this is what shows an all-`_l` (far-LOD) frame.
|
||
if let Some(i) = args.iter().position(|a| a == "--ship") {
|
||
if let Some(id) = args.get(i + 1) {
|
||
let mut rows: Vec<(String, u32, usize)> = models
|
||
.iter()
|
||
.filter(|m| m.name.contains(id.as_str()))
|
||
.map(|m| {
|
||
let v = m.meshes.iter().map(|s| s.positions.len()).sum::<usize>() as u32;
|
||
(m.name.clone(), v, draw_count.get(&v).copied().unwrap_or(0))
|
||
})
|
||
.collect();
|
||
rows.sort_by(|a, b| a.0.cmp(&b.0));
|
||
let drawn = rows.iter().filter(|r| r.2 > 0).count();
|
||
println!(
|
||
"\n{id} resources in {stage}.xpr ({drawn}/{} with a drawn vcount):",
|
||
rows.len()
|
||
);
|
||
for (name, v, n) in rows {
|
||
println!(
|
||
" {name:28} vcount {v:6} {}",
|
||
if n > 0 {
|
||
format!("DRAWN ×{n}")
|
||
} else {
|
||
"—".into()
|
||
}
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
// The other direction, for orientation: the container's biggest resources and
|
||
// whether the capture ever drew that many vertices.
|
||
let mut sizes: Vec<(u32, String)> = models
|
||
.iter()
|
||
.map(|m| {
|
||
(
|
||
m.meshes.iter().map(|s| s.positions.len()).sum::<usize>() as u32,
|
||
m.name.clone(),
|
||
)
|
||
})
|
||
.collect();
|
||
sizes.sort_unstable_by(|a, b| b.0.cmp(&a.0));
|
||
println!("\nlargest resources in {stage}.xpr → drawn in the capture?");
|
||
for (v, name) in sizes.iter().take(top_n.min(sizes.len())) {
|
||
let n = draw_count.get(v).copied().unwrap_or(0);
|
||
println!(
|
||
" {name:28} vcount {v:6} {}",
|
||
if n > 0 {
|
||
format!("DRAWN ×{n}")
|
||
} else {
|
||
"not drawn".into()
|
||
}
|
||
);
|
||
}
|
||
}
|