Find the tie LOD block terminator; packets == 0 means no block at that LOD
parse_tie_block walks to a 0xf6 terminator that every real block carries, on
2177 of 2177 across all 27 levels, followed by zero padding. This is what makes
the blocks parseable: packets[1] bounds one on only 461 of 1864 classes, so the
terminator is the only general way to find one.
The terminator was worth a control, because 0xf6 could have been byte-frequency
coincidence. It occurs in payloads at 0.83x the uniform rate -- slightly LESS
often than chance -- while the last non-zero byte is 0xf6 on 909 of 922 blocks in
the first subset, against 17 of 922 for the same blocks cut at a random point. So
it is a terminator, not a common byte.
packets == 0 means the class has NO block at that LOD: of 3,728 (class, LOD)
slots, 2177 point at a block and 1551 are exactly zero.
Corrected header, over all 2177:
+0x00 count multiple of 16 on all; 42 distinct; never zero
+0x02 zero on all
+0x04 exactly four values: 0x501 (1069), 0xa02 (720), 0xf03 (350),
0x1404 (38)
+0x06 16 distinct, never zero
+0x08 180 distinct, never zero
+0x0a 61 distinct, never zero
+0x0c 125 distinct, never zero, clustered 0x043f..0x0443
+0x0e 11 distinct, zero on 2162 -- the only non-universal field
+0x0c clusters around 1087-1091, close enough to a block length to be worth
stating, but it is NOT the length: the difference against the true payload length
scatters from -12 to -172.
A correction to the previous commit. It reported 3,728 blocks all terminating and
packed_texture taking 842 distinct values, and on that basis retracted the
four-value claim. Both numbers were wrong the same way: the scan did not skip
packets == 0, so it computed class_base + 0 and walked forward from the CLASS
BODY, finding a terminator there by accident. That contaminated 1551 of the 3728.
With the zeros excluded the slot takes exactly four values on all 2177 blocks.
The four-value claim was right; my retraction of it was wrong; and the retraction
was made on a number from the same contaminated set. A retraction is not
automatically better than the claim it retracts -- it needs its own bounds.
Still open: the payload's records. Established this round: blocks are
self-delimiting, absent LODs are marked by a zero packets word, and the header is
fully characterised. Not established: any record boundary inside the payload,
which stays variable-length.
This commit is contained in:
@@ -2702,15 +2702,19 @@ 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};
|
||||
// Tie LOD blocks are self-delimiting, which is what makes them parseable
|
||||
// without trusting packets[1] to bound the block.
|
||||
use gc_formats::tie::{
|
||||
parse_tie_block, TIE_BLOCK_HEADER_SIZE, TIE_BLOCK_TERMINATOR, TIE_PACKED_TEXTURE_VALUES,
|
||||
};
|
||||
|
||||
let mut blocks = 0usize;
|
||||
let mut absent = 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 padding_all_zero = 0usize;
|
||||
let mut packed_seen: Vec<u16> = Vec::new();
|
||||
|
||||
for n in 0..27 {
|
||||
@@ -2720,43 +2724,58 @@ fn tie_classes_resolve_through_both_indirections() {
|
||||
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 {
|
||||
for lod in [1usize, 2] {
|
||||
let off = r.header.packets[lod] as i64;
|
||||
if off == 0 {
|
||||
// packets == 0 means this class has no block at this LOD.
|
||||
absent += 1;
|
||||
continue;
|
||||
}
|
||||
let bh = TieBlockHeader::parse(&core[s..e]).expect("block header");
|
||||
let s = (base as i64 + off) as usize;
|
||||
if s + TIE_BLOCK_HEADER_SIZE > core.len() {
|
||||
continue;
|
||||
}
|
||||
let Some(blk) = parse_tie_block(&core, s) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
blocks += 1;
|
||||
if bh.count % 16 == 0 {
|
||||
if blk.header.count % 16 == 0 {
|
||||
count_mult16 += 1
|
||||
}
|
||||
if bh.reserved_02 == 0 {
|
||||
if blk.header.reserved_02 == 0 {
|
||||
reserved_02_zero += 1
|
||||
}
|
||||
if TIE_PACKED_TEXTURE_VALUES.contains(&bh.packed_texture) {
|
||||
if TIE_PACKED_TEXTURE_VALUES.contains(&blk.header.packed_texture) {
|
||||
packed_ok += 1
|
||||
}
|
||||
if bh.reserved_0e == 0 {
|
||||
reserved_0e_zero += 1
|
||||
}
|
||||
if bh.invariants_hold() {
|
||||
if blk.header.invariants_hold() {
|
||||
hdr_ok += 1
|
||||
}
|
||||
if !packed_seen.contains(&bh.packed_texture) {
|
||||
packed_seen.push(bh.packed_texture);
|
||||
if blk.padding.iter().all(|&b| b == 0) {
|
||||
padding_all_zero += 1
|
||||
}
|
||||
assert_eq!(
|
||||
blk.payload.last(),
|
||||
Some(&TIE_BLOCK_TERMINATOR),
|
||||
"level{n} class{c} lod{lod}: payload does not end on the terminator"
|
||||
);
|
||||
if !packed_seen.contains(&blk.header.packed_texture) {
|
||||
packed_seen.push(blk.header.packed_texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
packed_seen.sort_unstable();
|
||||
|
||||
assert!(blocks >= 900, "only {blocks} measurable tie blocks");
|
||||
assert!(blocks >= 2100, "only {blocks} tie blocks located");
|
||||
assert!(
|
||||
absent > 1000,
|
||||
"only {absent} absent (packets == 0) LOD blocks"
|
||||
);
|
||||
assert_eq!(blocks + absent, 2 * classes,
|
||||
"expected two LOD slots per class, got {blocks} present + {absent} absent for {classes} classes");
|
||||
assert_eq!(
|
||||
count_mult16,
|
||||
blocks,
|
||||
@@ -2778,13 +2797,25 @@ fn tie_classes_resolve_through_both_indirections() {
|
||||
assert_eq!(
|
||||
hdr_ok,
|
||||
blocks,
|
||||
"invariants failed on {} of {blocks}",
|
||||
"header invariants failed on {} of {blocks}",
|
||||
blocks - hdr_ok
|
||||
);
|
||||
assert_eq!(
|
||||
padding_all_zero,
|
||||
blocks,
|
||||
"non-zero padding on {} of {blocks}",
|
||||
blocks - padding_all_zero
|
||||
);
|
||||
assert_eq!(
|
||||
packed_seen.len(),
|
||||
TIE_PACKED_TEXTURE_VALUES.len(),
|
||||
"packed_texture took {} distinct values, expected the four {packed_seen:?}",
|
||||
packed_seen.len()
|
||||
);
|
||||
|
||||
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"
|
||||
"tie blocks: {blocks} present, {absent} absent (packets == 0); all end on 0xf6 with zero padding; \
|
||||
count %16 on all; reserved_02 == 0 on all; packed_texture in {packed_seen:?} on all"
|
||||
);
|
||||
|
||||
assert!(classes >= 1800, "only {classes} tie classes resolved");
|
||||
|
||||
Reference in New Issue
Block a user