//! Cross-container consistency for XBG7 geometry. //! //! A geometry resource shared by several stage containers must decode to the //! same bounds in each. This needs no ground truth, and on 2026-08-11 it found //! **125 of 681** shared resources decoding to different bounds while reporting //! identical vertex and triangle counts — the anchor scan locating a different //! buffer of the same size (see `docs/re/structures/xbg7-mesh.md`). //! //! The test is `#[ignore]`d because the decoder does not satisfy it yet. It is //! written as the *target* state so that fixing the anchor scan makes it pass, //! rather than as a snapshot of the bug. use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use sylpheed_formats::mesh::Xbg7Model; fn disc_root() -> Option { if let Ok(p) = std::env::var("SYLPHEED_DISC") { let p = PathBuf::from(p); if p.join("dat").is_dir() { return Some(p); } } let default = Path::new( "/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)", ); if default.join("dat").is_dir() { return Some(default.to_path_buf()); } None } /// Rounded (w, h, d) of a model's own geometry. fn span(m: &Xbg7Model) -> Option<[i64; 3]> { let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]); for s in &m.meshes { for q in &s.positions { for k in 0..3 { lo[k] = lo[k].min(q[k]); hi[k] = hi[k].max(q[k]); } } } if lo[0] == f32::MAX { return None; } Some([ (hi[0] - lo[0]).round() as i64, (hi[1] - lo[1]).round() as i64, (hi[2] - lo[2]).round() as i64, ]) } #[test] #[ignore = "known-failing: 63 of 681 shared resources still decode inconsistently (was 125 before the neighbourhood anchor, 2026-08-12)"] fn shared_resources_decode_identically_in_every_container() { let Some(root) = disc_root() else { eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)"); return; }; let dir = root.join("hidden/resource3d"); let mut files: Vec = std::fs::read_dir(&dir) .expect("resource3d/") .flatten() .map(|e| e.path()) .filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr")) .collect(); files.sort(); // name -> (verts, tris) -> set of spans seen let mut seen: BTreeMap> = BTreeMap::new(); for f in &files { let Ok(bytes) = std::fs::read(f) else { continue }; let where_ = f.file_name().unwrap().to_string_lossy().to_string(); for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) { let Some(sp) = span(&m) else { continue }; let v: usize = m.meshes.iter().map(|s| s.positions.len()).sum(); let t: usize = m.meshes.iter().map(|s| s.indices.len() / 3).sum(); seen.entry(m.name.clone()).or_default().push((sp, v, t, where_.clone())); } } let mut bad: Vec = Vec::new(); for (name, list) in &seen { if list.len() < 2 { continue; } // Only compare decodes that agree on how much geometry they found; // a differing vertex/triangle count is a different question. if !list.iter().all(|e| e.1 == list[0].1 && e.2 == list[0].2) { continue; } let spans: std::collections::BTreeSet<[i64; 3]> = list.iter().map(|e| e.0).collect(); if spans.len() > 1 && bad.len() < 10 { bad.push(format!("{name}: {spans:?}")); } } assert!( bad.is_empty(), "{} shared resources decode to different bounds; first: {bad:?}", bad.len() ); }