`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
165 lines
5.5 KiB
Rust
165 lines
5.5 KiB
Rust
//! Integration test: run the XPR2 texture pipeline against REAL `.xpr` files
|
|
//! read directly from the retail disc image, reproducing exactly what the
|
|
//! viewer's texture-preview path does (`identify_format` → `X360Texture::
|
|
//! from_xpr2`). Skipped unless `SYLPHEED_ISO` points at the disc (or the
|
|
//! default dev path exists).
|
|
//!
|
|
//! Run: `cargo test -p sylpheed-formats --test texture_disc -- --ignored --nocapture`
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use sylpheed_formats::texture::X360Texture;
|
|
use sylpheed_formats::vfs::identify_format;
|
|
|
|
fn iso_path() -> Option<PathBuf> {
|
|
if let Ok(p) = std::env::var("SYLPHEED_ISO") {
|
|
let p = PathBuf::from(p);
|
|
if p.is_file() {
|
|
return Some(p);
|
|
}
|
|
}
|
|
let default = PathBuf::from(
|
|
"/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso",
|
|
);
|
|
default.is_file().then_some(default)
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "requires the retail ISO — set SYLPHEED_ISO"]
|
|
async fn xpr_pipeline_over_disc_sample() {
|
|
let Some(iso) = iso_path() else {
|
|
eprintln!("SKIP: ISO not found (set SYLPHEED_ISO)");
|
|
return;
|
|
};
|
|
|
|
let mut reader = sylpheed_formats::xiso::open_iso(&iso).await.unwrap();
|
|
let all = reader.list_all_files().await.unwrap();
|
|
let mut xprs: Vec<String> = all
|
|
.into_iter()
|
|
.filter(|f| f.to_lowercase().ends_with(".xpr"))
|
|
.collect();
|
|
xprs.sort();
|
|
println!("found {} .xpr files", xprs.len());
|
|
|
|
// Sample across the set so we hit different texture sizes/formats.
|
|
let sample: Vec<String> = xprs
|
|
.iter()
|
|
.step_by((xprs.len() / 24).max(1))
|
|
.cloned()
|
|
.collect();
|
|
|
|
let mut ok = 0usize;
|
|
let mut fail = 0usize;
|
|
let mut by_format: std::collections::BTreeMap<String, usize> = Default::default();
|
|
let mut nonxpr = 0usize;
|
|
|
|
// Optionally dump the sampled .xpr bytes so the CLI can export them to PNG
|
|
// for visual de-tiling validation: `DUMP_XPR_DIR=/tmp/xpr cargo test …`.
|
|
let dump_dir = std::env::var("DUMP_XPR_DIR").ok();
|
|
if let Some(d) = &dump_dir {
|
|
std::fs::create_dir_all(d).unwrap();
|
|
}
|
|
|
|
for path in &sample {
|
|
let bytes = reader.read_file(path).await.unwrap();
|
|
if let Some(d) = &dump_dir {
|
|
let base = path.rsplit('/').next().unwrap();
|
|
std::fs::write(format!("{d}/{base}"), &bytes).unwrap();
|
|
}
|
|
let fmt = identify_format(&bytes);
|
|
let magic: String = bytes
|
|
.iter()
|
|
.take(4)
|
|
.map(|b| {
|
|
if b.is_ascii_graphic() {
|
|
*b as char
|
|
} else {
|
|
'.'
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
if fmt != sylpheed_formats::vfs::FileFormat::Xpr2Texture {
|
|
nonxpr += 1;
|
|
println!(
|
|
" [{path}] NOT XPR2 (magic {magic:?}, {} bytes)",
|
|
bytes.len()
|
|
);
|
|
continue;
|
|
}
|
|
|
|
match X360Texture::from_xpr2(&bytes) {
|
|
Ok(t) => {
|
|
ok += 1;
|
|
*by_format.entry(format!("{:?}", t.format)).or_default() += 1;
|
|
// Sanity: does the decoded data length match the descriptor
|
|
// dimensions (what the GPU upload will require)?
|
|
let bs = t.format.block_size() as u32;
|
|
let bw = ((t.width + bs - 1) / bs).max(1) as usize;
|
|
let bh = ((t.height + bs - 1) / bs).max(1) as usize;
|
|
let need = bw * bh * t.format.bytes_per_block();
|
|
let size_ok = if need == t.data.len() {
|
|
"ok"
|
|
} else {
|
|
"MISMATCH"
|
|
};
|
|
println!(
|
|
" [{path}] {:?} {}x{} mips={} data={} need={} {size_ok}",
|
|
t.format,
|
|
t.width,
|
|
t.height,
|
|
t.mip_levels,
|
|
t.data.len(),
|
|
need
|
|
);
|
|
}
|
|
Err(e) => {
|
|
fail += 1;
|
|
println!(" [{path}] from_xpr2 FAILED: {e}");
|
|
dump_xpr2_structure(&bytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
println!("\nSUMMARY: ok={ok} fail={fail} non-xpr2={nonxpr} formats={by_format:?}");
|
|
}
|
|
|
|
/// Dump the XPR2 header + resource directory the way `from_xpr2` reads it,
|
|
/// so we can see why a file's TX2D scan comes up empty.
|
|
fn dump_xpr2_structure(bytes: &[u8]) {
|
|
let be = |o: usize| u32::from_be_bytes(bytes[o..o + 4].try_into().unwrap());
|
|
let magic: String = bytes[..4].iter().map(|b| *b as char).collect();
|
|
println!(
|
|
" hdr magic={magic:?} header_size=0x{:X} data_size=0x{:X} num_resources={}",
|
|
be(0x04),
|
|
be(0x08),
|
|
be(0x0C)
|
|
);
|
|
let n = be(0x0C).min(16); // guard against garbage counts
|
|
for i in 0..n {
|
|
let base = 0x10 + i as usize * 16;
|
|
if base + 16 > bytes.len() {
|
|
break;
|
|
}
|
|
let tag: String = bytes[base..base + 4]
|
|
.iter()
|
|
.map(|b| {
|
|
if b.is_ascii_graphic() {
|
|
*b as char
|
|
} else {
|
|
'.'
|
|
}
|
|
})
|
|
.collect();
|
|
println!(
|
|
" res[{i}] tag={tag:?} data_off=0x{:X} desc_size=0x{:X} name_off=0x{:X}",
|
|
be(base + 4),
|
|
be(base + 8),
|
|
be(base + 12)
|
|
);
|
|
}
|
|
// First 48 bytes hex for orientation.
|
|
let hx: String = bytes.iter().take(48).map(|b| format!("{b:02X} ")).collect();
|
|
println!(" hex[0..48] {hx}");
|
|
}
|