Parse the collision container framing
Collision sits inside core_data at the offset the core index gives, behind a 0x40-byte header that self-describes: it carries its own size and the payload size. That pairing is the invariant -- the declared header size is exactly 0x40 on all 27 levels and header + payload stays inside the lump. 41,481,520 payload bytes across the 19 levels that have any; 8 declare zero length, which is present-but-empty rather than absent, so the two are counted separately. The cells inside the payload are not decoded. They do not walk as fixed-stride cells, and treating faces as variable-length records stops partway into the first cell on level 0 rather than tiling the payload. The first cell reads as 11 verts and 33 faces under every hypothesis tried, which is exactly the kind of agreement that looks like a result and is not one -- three different strides all produce the same first cell and all of them are wrong. Left open rather than committed as a guess.
This commit is contained in:
@@ -1459,3 +1459,70 @@ fn sound_bank_framing_holds_on_every_level() {
|
||||
unexplained tail {narrowest}..{widest} bytes"
|
||||
);
|
||||
}
|
||||
|
||||
/// Collision container framing.
|
||||
///
|
||||
/// The invariant is the header's own self-description: `header_size` is 0x40
|
||||
/// on every level, and `header_size + payload_size` stays inside `core_data`.
|
||||
/// The payload is deliberately left undecoded -- see `collision.rs`.
|
||||
#[test]
|
||||
fn collision_framing_holds_on_every_level() {
|
||||
require_disc!();
|
||||
let data = std::env::var_os("GC_EXTRACTED")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| {
|
||||
let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
|
||||
PathBuf::from(home).join("Documents/gc-extracted")
|
||||
});
|
||||
if !data.exists() {
|
||||
eprintln!("skipped: unpack the disc first");
|
||||
return;
|
||||
}
|
||||
|
||||
use gc_formats::collision::{CollisionHeader, COLLISION_HEADER_SIZE};
|
||||
use gc_formats::level_core::LevelCoreHeader;
|
||||
let mut levels = 0usize;
|
||||
let mut empty = 0usize;
|
||||
let mut payload_bytes = 0usize;
|
||||
|
||||
for n in 0..27 {
|
||||
let dir = data.join(format!("level{n}"));
|
||||
let (Ok(core), Ok(index)) = (
|
||||
std::fs::read(dir.join("core_data.bin")),
|
||||
std::fs::read(dir.join("core_index.bin")),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
let h = LevelCoreHeader::parse(&index).expect("core header");
|
||||
let region = CollisionHeader::region(&core, h.collision)
|
||||
.unwrap_or_else(|e| panic!("level{n}: collision: {e}"));
|
||||
let Some(r) = region else {
|
||||
empty += 1;
|
||||
continue;
|
||||
};
|
||||
assert_eq!(r.header.header_size, COLLISION_HEADER_SIZE, "level{n}");
|
||||
assert!(
|
||||
r.header.payload_size as usize <= r.payload.len(),
|
||||
"level{n}"
|
||||
);
|
||||
if r.payload.is_empty() {
|
||||
// Present but empty. Counted once, not twice.
|
||||
empty += 1;
|
||||
continue;
|
||||
}
|
||||
assert_eq!(r.payload.len(), r.header.payload_size as usize, "level{n}");
|
||||
payload_bytes += r.payload.len();
|
||||
levels += 1;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
levels + empty,
|
||||
27,
|
||||
"expected all 27 levels, got {levels}+{empty}"
|
||||
);
|
||||
assert!(
|
||||
payload_bytes > 10_000_000,
|
||||
"only {payload_bytes} collision bytes"
|
||||
);
|
||||
eprintln!("collision: {payload_bytes} payload bytes across {levels} levels, {empty} empty");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user