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