Decode the tie class header's +0x0c word: three LOD record counts
Range measured: every tie class on the disc, 1,864 classes over all 27 levels. Method: read the word at class +0x0c as four bytes, and read the record counts independently from the blocks themselves -- follow packets[lod] to the block, walk to the 0xf6 terminator, and count the 16-byte records the block header's data_offset names. Two paths to the same number, so the check is not circular. Result: the byte at +0x0c is the LOD 0 record count, +0x0d is LOD 1's and +0x0e is LOD 2's, on 1,864 of 1,864 classes. Classes with an absent LOD agree too: a zero packets[lod] gives a zero count and the byte is zero. This closes the file's oldest lie about the tie header. The comment in TieClassHeader::parse read "0x0c: the 64 light-slot normals offset. Read past, not decoded" -- inherited from R&C1's layout, where the word genuinely is an offset. On R&C2 it is not an offset at all; it is three counts, and R&C1 carries the same quadruple at +0x20. Skipping the field was the right action on the wrong information: the field was unknown only because R&C1's map was applied to R&C2's bytes. TieClassHeader gains packet_counts, and tie_class_header_counts_records_per_lod asserts the agreement. The fourth byte, +0x0f, is left unnamed. It ranges 1..15, and the obvious following of R&C1 -- the shader/ad-gif total -- disagrees on 1,827 of 1,864 classes, which is 2% agreement where a count would expect 100%. It is recorded as unidentified rather than given a name that fits a fiftieth of the population.
This commit is contained in:
@@ -54,6 +54,21 @@ pub struct TieClassHeader {
|
||||
/// They are strictly increasing on only 461 of 1,864 classes, so neither one
|
||||
/// bounds the other -- the `0xf6` terminator does that instead.
|
||||
pub packets: [i32; 3],
|
||||
/// `0x0c`: the record table's length of each LOD, as three `u8` counts
|
||||
/// followed by one unidentified byte.
|
||||
///
|
||||
/// The word was read as R&C1's 64-light-slot *normals offset* and skipped,
|
||||
/// which was the right thing to do with the wrong information: the byte at
|
||||
/// `+0x0c` is not an offset at all. It is the **record count of the LOD 0
|
||||
/// block**, `+0x0d` of LOD 1, `+0x0e` of LOD 2, verified on **1,864 of
|
||||
/// 1,864** classes on all 27 levels. R&C1 carries the same quadruple at
|
||||
/// `+0x20`; R&C2 has it here.
|
||||
///
|
||||
/// `+0x0f` is not the shader (ad-gif) total, which disagrees on 1,827 of
|
||||
/// the classes measured; it is left unnamed rather than guessed at.
|
||||
pub packet_counts: [u8; 3],
|
||||
/// `0x0f`: unidentified. Not the shader total.
|
||||
pub unknown_0f: u8,
|
||||
/// `0x10`, `0x14`, `0x18`: LOD switch distances in world units.
|
||||
pub near_dist: f32,
|
||||
pub mid_dist: f32,
|
||||
@@ -73,8 +88,13 @@ impl TieClassHeader {
|
||||
for slot in packets.iter_mut() {
|
||||
*slot = b.i32("tie packet offset")?;
|
||||
}
|
||||
// 0x0c: the 64 light-slot normals offset. Read past, not decoded.
|
||||
b.skip(4, "tie normals offset")?;
|
||||
// 0x0c: the three LOD record counts and one unidentified byte.
|
||||
let packet_counts = [
|
||||
b.u8("tie LOD0 record count")?,
|
||||
b.u8("tie LOD1 record count")?,
|
||||
b.u8("tie LOD2 record count")?,
|
||||
];
|
||||
let unknown_0f = b.u8("tie class word 0x0f")?;
|
||||
let near_dist = b.f32("tie near distance")?;
|
||||
let mid_dist = b.f32("tie mid distance")?;
|
||||
let far_dist = b.f32("tie far distance")?;
|
||||
@@ -85,6 +105,8 @@ impl TieClassHeader {
|
||||
let o_class = b.i32("tie class number")?;
|
||||
Ok(TieClassHeader {
|
||||
packets,
|
||||
packet_counts,
|
||||
unknown_0f,
|
||||
near_dist,
|
||||
mid_dist,
|
||||
far_dist,
|
||||
|
||||
Reference in New Issue
Block a user