From 5610d173b062456f3f52cc176b052bff285ba3a9 Mon Sep 17 00:00:00 2001 From: ReaperOfVeriod Date: Mon, 12 Oct 2026 02:49:25 +0200 Subject: [PATCH] Narrow the collision header to two fields plus a third used by five levels Range measured: all 27 levels that carry collision, reading the 0x40-byte header each. Method: read +0x00, +0x04 and +0x08, then check +0x08..+0x40 for zero. Result: u32@+0x00 is 0x40 on 27 of 27 -- the header is self-sizing u32@+0x04 is the payload size, 20 distinct values across the disc bytes +0x08..+0x40 zero on 22 of 27 Five levels carry a large non-zero word at +0x08: level 7 1248048 level 12 2541616 level 9 1766832 level 13 3376592 level 14 4060640 So the header is not two fields and 56 bytes of padding: there is a third field most levels leave at zero and five use. Whether it is a size, an offset or a second block's length is not established, and nothing measured here separates those readings. Exposed as CollisionHeader::field_08. Correction: the collision header was previously described as self-describing across its full 0x40, which is true for 22 levels and overstates the case for the other five. Still not decoded: the payload. It contains ascending u32 runs on levels 0 and 2 -- level 0 at +280 with deltas 12, 12, 12 then a jump, level 2 at +13868 with deltas 204, 204, 200, 196 -- but irregular deltas fit neither a flat table nor a simple stride, and level 1's apparent run is a byte-shift false positive. Nothing about the payload is asserted. --- crates/gc-formats/src/collision.rs | 11 +++++++++ docs/plan/level-assets.md | 38 ++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/crates/gc-formats/src/collision.rs b/crates/gc-formats/src/collision.rs index 3ad91d4..95046e4 100644 --- a/crates/gc-formats/src/collision.rs +++ b/crates/gc-formats/src/collision.rs @@ -27,6 +27,15 @@ pub struct CollisionHeader { pub header_size: u32, /// Size of the payload that follows. pub payload_size: u32, + /// The word at `+0x08`. + /// + /// Measured on all 27 levels that carry collision: the rest of the 0x40 + /// header is zero padding on 22 of them, but **5 levels carry a large + /// non-zero word here** -- 1,248,048, 1,766,832, 2,541,616, 3,376,592 and + /// 4,060,640, on levels 7, 9, 12, 13 and 14. Meaning not established; it + /// is a size, an offset, or a second block's length, and nothing here + /// distinguishes those. + pub field_08: u32, } impl CollisionHeader { @@ -39,6 +48,7 @@ impl CollisionHeader { Ok(CollisionHeader { header_size: b.u32("collision header size")?, payload_size: b.u32("collision payload size")?, + field_08: b.u32("collision +0x08")?, }) } } @@ -108,6 +118,7 @@ mod tests { const BASE: usize = 16; fn core(header_size: u32, payload_size: u32) -> Vec { + #[allow(clippy::let_and_return)] let mut v = vec![0u8; BASE + 64]; v[BASE..BASE + 4].copy_from_slice(&header_size.to_le_bytes()); v[BASE + 4..BASE + 8].copy_from_slice(&payload_size.to_le_bytes()); diff --git a/docs/plan/level-assets.md b/docs/plan/level-assets.md index e320ba5..cd1f69c 100644 --- a/docs/plan/level-assets.md +++ b/docs/plan/level-assets.md @@ -1331,3 +1331,41 @@ be small on most levels and is not a count at all. `gc_formats::sky` parses the header and the sub-objects, and the gated test asserts the sphere is finite, the radius positive where it is positive at all, the dimensions non-zero, and the sub-object list non-empty on every level. + +## The collision header is two fields, plus a third on five levels + +**Range measured:** all 27 levels that carry collision, reading the 0x40-byte +header each. + +**Method:** read `+0x00`, `+0x04` and `+0x08`, then check `+0x08..+0x40` for +zero. + +**Result:** + +- `u32@+0x00` is `0x40` on **27 of 27** -- the header is self-sizing. +- `u32@+0x04` is the payload size, 20 distinct values across the disc. +- **Bytes `+0x08..+0x40` are zero on 22 of 27**, but five levels carry a large + non-zero word at `+0x08`: + +| level | `+0x08` | +|---|---| +| 7 | 1,248,048 | +| 9 | 1,766,832 | +| 12 | 2,541,616 | +| 13 | 3,376,592 | +| 14 | 4,060,640 | + +So the header is not simply "two fields and 56 bytes of padding" -- there is a +third field that most levels leave at zero and five use. Whether it is a size, +an offset or a second block's length is not established; nothing measured here +separates those readings. It is exposed as `CollisionHeader::field_08`. + +**Correction to the framing description.** The collision header was previously +described as self-describing across its full 0x40, which is true for 22 levels +and overstates the case for the other five. + +**Still not decoded:** the payload. It contains ascending `u32` runs on levels +0 and 2 -- level 0 at `+280` with deltas 12, 12, 12 then a jump, level 2 at +`+13868` with deltas 204, 204, 200, 196 -- but the irregular deltas fit neither +a flat table nor a simple stride, and level 1's apparent run is a byte-shift +false positive. Nothing about the payload is asserted.