`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
148 lines
5.9 KiB
Rust
148 lines
5.9 KiB
Rust
//! Does a resource have a CLEANER home in its container than the one we picked?
|
|
//!
|
|
//! After the pad-scoring fix, only two resources on the disc still decode to an
|
|
//! index run with degenerate triangles — `e201_bdy_03_m` (2 containers) and
|
|
//! `_rou_f402_dead` (9). Degeneracy says the run does not fit the pool, so either
|
|
//! the vertex block is wrong or the candidate list never offered the right one.
|
|
//! This walks every candidate vertex-run start for the resource's declaration and
|
|
//! scores each `(start, pad)` the way the anchor now does — degenerate triangles
|
|
//! first, then winding, plus coverage — so the answer is one of:
|
|
//! * a strictly cleaner candidate exists (the selection is at fault),
|
|
//! * several are equally clean (genuinely ambiguous), or
|
|
//! * nothing is clean (the block is not in the candidate list at all).
|
|
//!
|
|
//! Usage: better_home <container.xpr> <resource-name>
|
|
use sylpheed_formats::mesh::{debug_resource_params, debug_vertex_run_starts, Xbg7Model};
|
|
|
|
fn be16(b: &[u8], at: usize) -> u32 {
|
|
((b[at] as u32) << 8) | b[at + 1] as u32
|
|
}
|
|
|
|
fn main() {
|
|
let a: Vec<String> = std::env::args().collect();
|
|
let bytes = std::fs::read(&a[1]).expect("container");
|
|
let name = &a[2];
|
|
|
|
let Some((markers, stride)) = debug_resource_params(&bytes, name) else {
|
|
eprintln!("no such XBG7 resource: {name}");
|
|
std::process::exit(1);
|
|
};
|
|
let (vc, ic) = markers[0];
|
|
println!(
|
|
"{name}: {} marker(s), first = {vc} verts / {ic} indices, stride {stride}",
|
|
markers.len()
|
|
);
|
|
|
|
// Where did the decoder put it?
|
|
let ours = Xbg7Model::stage_models(&bytes)
|
|
.into_iter()
|
|
.find(|m| m.name == *name)
|
|
.and_then(|m| m.meshes.first().and_then(|s| s.vbuf_offset));
|
|
println!("our anchor: {ours:?}");
|
|
|
|
let starts = debug_vertex_run_starts(&bytes, stride);
|
|
println!(
|
|
"{} candidate vertex-run starts for stride {stride}",
|
|
starts.len()
|
|
);
|
|
|
|
// Score every (start, pad): degenerate triangles, winding against the stored
|
|
// normals, and whether the run covers the pool exactly.
|
|
let mut rows: Vec<(usize, f32, usize, usize, usize, bool)> = Vec::new(); // degen, wind, start, pad, max_idx, covered
|
|
for &vb in &starts {
|
|
for pad in 0..=3usize {
|
|
if vb < ic * 2 + pad {
|
|
continue;
|
|
}
|
|
let ib = vb - ic * 2 - pad;
|
|
if ib + ic * 2 > bytes.len() || vb + vc * stride > bytes.len() {
|
|
continue;
|
|
}
|
|
let idx: Vec<u32> = (0..ic).map(|k| be16(&bytes, ib + k * 2)).collect();
|
|
let max_idx = *idx.iter().max().unwrap_or(&0) as usize;
|
|
if max_idx >= vc {
|
|
continue; // out of range — not a candidate at all
|
|
}
|
|
let pos: Vec<[f32; 3]> = (0..vc)
|
|
.map(|v| {
|
|
let at = vb + v * stride;
|
|
[
|
|
f32::from_be_bytes(bytes[at..at + 4].try_into().unwrap()),
|
|
f32::from_be_bytes(bytes[at + 4..at + 8].try_into().unwrap()),
|
|
f32::from_be_bytes(bytes[at + 8..at + 12].try_into().unwrap()),
|
|
]
|
|
})
|
|
.collect();
|
|
if pos
|
|
.iter()
|
|
.any(|p| p.iter().any(|c| !c.is_finite() || c.abs() > 1e6))
|
|
{
|
|
continue;
|
|
}
|
|
let mut degen = 0usize;
|
|
let (mut agree, mut counted) = (0usize, 0usize);
|
|
for t in idx.chunks_exact(3) {
|
|
let (x, y, z) = (t[0] as usize, t[1] as usize, t[2] as usize);
|
|
if x == y || y == z || x == z {
|
|
degen += 1;
|
|
continue;
|
|
}
|
|
// Winding needs normals; use the geometric centroid normal as a
|
|
// stand-in so this stays declaration-agnostic: a consistent mesh
|
|
// has all faces pointing away from the centroid on a convex-ish
|
|
// hull. Weak, so degeneracy leads the sort.
|
|
let e1 = [
|
|
pos[y][0] - pos[x][0],
|
|
pos[y][1] - pos[x][1],
|
|
pos[y][2] - pos[x][2],
|
|
];
|
|
let e2 = [
|
|
pos[z][0] - pos[x][0],
|
|
pos[z][1] - pos[x][1],
|
|
pos[z][2] - pos[x][2],
|
|
];
|
|
let f = [
|
|
e1[1] * e2[2] - e1[2] * e2[1],
|
|
e1[2] * e2[0] - e1[0] * e2[2],
|
|
e1[0] * e2[1] - e1[1] * e2[0],
|
|
];
|
|
let cx: [f32; 3] = {
|
|
let mut c = [0.0f32; 3];
|
|
for p in &pos {
|
|
for k in 0..3 {
|
|
c[k] += p[k] / pos.len() as f32;
|
|
}
|
|
}
|
|
c
|
|
};
|
|
let out = [pos[x][0] - cx[0], pos[x][1] - cx[1], pos[x][2] - cx[2]];
|
|
counted += 1;
|
|
if f[0] * out[0] + f[1] * out[1] + f[2] * out[2] > 0.0 {
|
|
agree += 1;
|
|
}
|
|
}
|
|
let w = if counted == 0 {
|
|
0.0
|
|
} else {
|
|
agree as f32 / counted as f32
|
|
};
|
|
rows.push((degen, w.max(1.0 - w), vb, pad, max_idx, max_idx + 1 == vc));
|
|
}
|
|
}
|
|
rows.sort_by(|a, b| a.0.cmp(&b.0).then(b.1.total_cmp(&a.1)));
|
|
println!(
|
|
"\n{} in-range candidates; best 12 by (degenerate, winding):",
|
|
rows.len()
|
|
);
|
|
for (d, w, vb, pad, mx, cov) in rows.iter().take(12) {
|
|
let mark = if Some(*vb) == ours { " <-- ours" } else { "" };
|
|
println!(
|
|
" vb 0x{vb:07X} pad {pad} degen {d:>4} wind {w:.3} max_idx {mx}/{} {}{mark}",
|
|
vc - 1,
|
|
if *cov { "covers" } else { "SHORT" }
|
|
);
|
|
}
|
|
let clean = rows.iter().filter(|r| r.0 == 0 && r.5).count();
|
|
println!("\n{clean} candidates are degenerate-free AND cover the pool exactly");
|
|
}
|