Decode the tie LOD block header; establish the payload is variable-length
TieBlockHeader parses the block's own 16 bytes, measured over the 922 blocks where "the next packets offset ends this block" holds, across all 27 levels. A real-disc test asserts every figure. +0x00 count multiple of 16 on 922/922; 16..2096; 72 distinct disc-wide +0x02 zero on 922/922 +0x04 ONE OF FOUR VALUES DISC-WIDE: 0x501, 0xa02, 0xf03, 0x1404 +0x06 15 distinct, 0x0206..0x0512 +0x08 135 distinct, 0x0b05..0x4ee4 +0x0a 45 distinct +0x0c 94 distinct, 0x010e..0x0553 +0x0e zero on 914/922 +0x04 is packed rather than a magnitude: four values across the whole disc with the two bytes moving independently, low 1-4 and high 5/10/15/20 in lockstep. That it is a texture index paired with a scale is a guess; that it is two fields is not. The count is not a record count -- block_size minus the header is divisible by it on 0 of 922, for any record size and any header up to 512 bytes. On the payload, a control was needed and changed the answer. Reading blocks as 3 x f32 triples, 53-64% of reads land within +/-5000 of origin, against 16% for a uniform-random control of identical length. So the content is float-shaped and genuinely not random. But the rate is flat across every start offset and every stride tried, 53-65% throughout. A statistic that cannot separate the right stride from the wrong one cannot identify a stride -- rule 4's exact case. Flatness plus a count that does not tile means the records are variable length. That killed two readings. A VIF1 command walk consumes 0 of 88 blocks exactly, so it is not a VIF1 CPU-format stream. wad::decompress rejects the block at every start offset, so it is not WAD LZ77. Scanning every (start, stride) pair from 0x10 to 0x7f for byte positions with a small distinct-value vocabulary finds none: every position takes all 256 values at every alignment. For float payload that is expected, so the scan found nothing rather than confirming anything -- worth saying, because "no fields found" here is uninformative, not evidence against fields. Still open: what the payload holds. Established: variable-length, float-shaped, non-random, not LZ77, not VIF1. Not established: any record boundary.
This commit is contained in:
@@ -2702,6 +2702,91 @@ fn tie_classes_resolve_through_both_indirections() {
|
||||
}
|
||||
}
|
||||
|
||||
// Tie LOD block headers: measured invariants across all 27 levels.
|
||||
use gc_formats::tie::{TieBlockHeader, TIE_BLOCK_HEADER_SIZE, TIE_PACKED_TEXTURE_VALUES};
|
||||
|
||||
let mut blocks = 0usize;
|
||||
let mut hdr_ok = 0usize;
|
||||
let mut count_mult16 = 0usize;
|
||||
let mut reserved_02_zero = 0usize;
|
||||
let mut packed_ok = 0usize;
|
||||
let mut reserved_0e_zero = 0usize;
|
||||
let mut packed_seen: Vec<u16> = Vec::new();
|
||||
|
||||
for n in 0..27 {
|
||||
let idx = std::fs::read(data.join(format!("level{n}/core_index.bin"))).expect("core_index");
|
||||
let core = std::fs::read(data.join(format!("level{n}/core_data.bin"))).expect("core_data");
|
||||
let h = LevelCoreHeader::parse(&idx).expect("core header");
|
||||
let t = h.tie_classes;
|
||||
for c in 0..t.count as usize {
|
||||
let r = resolve_tie_class(&idx, &core, t, c).expect("tie class");
|
||||
let p = r.header.packets;
|
||||
if !(p[0] < p[1] && p[1] < p[2]) {
|
||||
continue;
|
||||
}
|
||||
let base = r.entry.offset_in_asset_wad as usize;
|
||||
for i in 0..2 {
|
||||
let s = base + p[i] as usize;
|
||||
let e = base + p[i + 1] as usize;
|
||||
if e > core.len() || e <= s + TIE_BLOCK_HEADER_SIZE {
|
||||
continue;
|
||||
}
|
||||
let bh = TieBlockHeader::parse(&core[s..e]).expect("block header");
|
||||
blocks += 1;
|
||||
if bh.count % 16 == 0 {
|
||||
count_mult16 += 1
|
||||
}
|
||||
if bh.reserved_02 == 0 {
|
||||
reserved_02_zero += 1
|
||||
}
|
||||
if TIE_PACKED_TEXTURE_VALUES.contains(&bh.packed_texture) {
|
||||
packed_ok += 1
|
||||
}
|
||||
if bh.reserved_0e == 0 {
|
||||
reserved_0e_zero += 1
|
||||
}
|
||||
if bh.invariants_hold() {
|
||||
hdr_ok += 1
|
||||
}
|
||||
if !packed_seen.contains(&bh.packed_texture) {
|
||||
packed_seen.push(bh.packed_texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
packed_seen.sort_unstable();
|
||||
|
||||
assert!(blocks >= 900, "only {blocks} measurable tie blocks");
|
||||
assert_eq!(
|
||||
count_mult16,
|
||||
blocks,
|
||||
"count not a multiple of 16 on {} of {blocks}",
|
||||
blocks - count_mult16
|
||||
);
|
||||
assert_eq!(
|
||||
reserved_02_zero,
|
||||
blocks,
|
||||
"reserved_02 non-zero on {} of {blocks}",
|
||||
blocks - reserved_02_zero
|
||||
);
|
||||
assert_eq!(
|
||||
packed_ok,
|
||||
blocks,
|
||||
"packed_texture outside the four known values on {} of {blocks}: {packed_seen:?}",
|
||||
blocks - packed_ok
|
||||
);
|
||||
assert_eq!(
|
||||
hdr_ok,
|
||||
blocks,
|
||||
"invariants failed on {} of {blocks}",
|
||||
blocks - hdr_ok
|
||||
);
|
||||
|
||||
eprintln!(
|
||||
"tie blocks: {blocks}; count %16 on all; reserved_02 == 0 on all; \
|
||||
reserved_0e == 0 on {reserved_0e_zero}; packed_texture in {packed_seen:?} on all"
|
||||
);
|
||||
|
||||
assert!(classes >= 1800, "only {classes} tie classes resolved");
|
||||
assert_eq!(
|
||||
packets0_constant,
|
||||
|
||||
Reference in New Issue
Block a user