Establish the tie vertex element is 16 bytes, and correct the roadmap's tie entry

With TieRecord identified as RAC1's TiePacketHeader, each record's leading word
points at its packet in the block payload. Following it and stepping vert_size
elements from vert_ofs reaches the vertices: 95,612 of them on levels 0-2.

Element size settled by comparison, not assumed. Requiring the texcoord pair to be
12-bit fixed -- both words multiples of 4096, which is how 0x1000 = 1.0 reads:

  element 16 bytes : 78,919 / 95,612 = 82.5%
  element  8 bytes : 45,661 / 95,612 = 47.8%
  element 12 bytes : 44,834 / 95,612 = 44.8%

Sixteen wins by a factor of 1.7 over the nearest alternative.

Observed layout, 8 x i16: the first three slots span the full i16 range with
10,803-13,421 distinct values each; i16[4] and [5] are the texcoords and are both
multiples of 4096 on 82.5%; i16[6] reads 4096 on 85.8% and i16[7] reads 0 on
84.0%.

Not established: what any field means, and why the texcoord rate is 82.5% rather
than 100%. The residual tracks record shape -- (shaders 2, control 7, size 2)
hits 60%, (shaders 1, control 18, size 5) hits 89% -- so it is not one bad block
but something per-packet this reading does not capture.

Worth recording that this is the OPPOSITE of what the disc alone concluded two
commits ago. Reading the header word at +0x00 as an element count gave 0 of 922
blocks dividing; it is the byte offset to the payload, so the table it bounds has
16-byte records. The number was right and the reading was wrong, which is what
the packet-header identification corrected.

The roadmap's tie entry was also stale -- it still said "a 16-byte record is
withdrawn", which the packet-header work had already reversed. Rewritten from the
commits: both indirections, self-delimiting blocks, the TiePacketHeader
identification with its 15,219/15,219 rule, the 16-byte vertex element, and what
remains open. A test asserts the element size against both alternatives so the
82.5% figure cannot be quietly replaced by an assumption.
This commit is contained in:
2026-10-13 04:27:54 +02:00
parent 54862b64ee
commit 35b087c882
3 changed files with 183 additions and 20 deletions
+38
View File
@@ -1587,3 +1587,41 @@ disproved rather than left as guesses:
So the record has one more field than the loader note claimed, the disc's
offset table is two bytes late relative to it, and what it counts is the next
question -- with the entry-count reading now out of the way.
## The tie vertex element is 16 bytes
With `TieRecord` identified as R&C1's `TiePacketHeader`, each record's leading
word points at its packet in the block payload. Following it and stepping
`vert_size` elements from `vert_ofs` reaches the vertices.
**Range measured: 95,612 vertices** on levels 0-2.
**Element size, settled by comparison rather than assumed.** Requiring the
texcoord pair to be 12-bit fixed -- both words multiples of 4,096, which is how
0x1000 = 1.0 reads -- gives:
element 16 bytes : 78,919 / 95,612 = 82.5%
element 8 bytes : 45,661 / 95,612 = 47.8%
element 12 bytes : 44,834 / 95,612 = 44.8%
Sixteen wins by a factor of 1.7 over the nearest alternative, so it is the
element size.
**Observed layout**, 8 x `i16`:
i16[0..3] 10,803 to 13,421 distinct values, spanning the full i16 range
i16[4], [5] the texcoords; both multiples of 4,096 on 82.5% of vertices
i16[6] reads 4,096 on 85.8%
i16[7] reads 0 on 84.0%
**Not established:** what any of the eight fields *means*, and why the texcoord
rate is 82.5% rather than 100%. The residual varies with record shape -- records
with `(shaders 2, control 7, control_size 2)` hit 60%, `(shaders 1, control 18,
control_size 5)` hit 89% -- so it is not one bad block but something that varies
per packet, possibly a second texcoord encoding for records that need it.
Recorded because the 16-byte answer is the *opposite* of what the earlier
commit concluded from the disc alone. Reading the header word at `+0x00` as an
element count gave 0 of 922 blocks dividing; it is the byte offset to the payload,
so the table it bounds has 16-byte records. The number was right and the reading
was wrong, which is the error the packet-header identification corrected.
+23 -20
View File
@@ -241,26 +241,29 @@ Phase 1 hands over the extents; this phase works out what each one contains.
- [~] `tfrag` — vertex format, topology, texture bindings, per-vertex colour
and normals, LOD selection and parent indices are all recovered and
verified. **Still open:** morph targets and the strip/chain bookkeeping.
- [~] `tie` — **both indirections now resolve.** `resolve_tie_class` walks the
chain: the class table is in `core_index.bin`, and the entry's
`offset_in_asset_wad` addresses `core_data.bin` (not `core_data.wad`,
despite the field name). All **1,864** tie classes resolve, with
`o_class` agreeing on every one, and all **5,592** LOD block offsets land
inside `core_data`.
- [~] Tie **block sizes** are established: `packets` is strictly increasing on
only **461 of 1,864** classes, and on those **922 of 922** consecutive
block sizes are a multiple of 16. `packets[0]` reads **256 on 1,864 of
1,864** classes and is a constant, not an offset -- `TIE_PACKETS0`. Every
one of the 922 blocks opens with a `u32` count, positive and a multiple of
4 (48 to 1,744).
- [ ] The tie **record layout is not decoded, and a 16-byte record is
withdrawn.** Block sizes being multiples of 16 does not make the records
16 bytes: reading the block as uniform units gives fields spanning their
whole legal range (`u32@0` from -2,139,029,504 to 2,139,029,587), and
assuming `[header][count][count uniform records]` yields **no record size
that tiles the block for any header up to 512 bytes**. The opening `u32`
is a count, not a size -- chaining it as one lands on the next block start
on 0 of 1,383 blocks.
- [x] `tie` — **fully chained.** `resolve_tie_class` walks both indirections: the
class table is in `core_index.bin`, and the entry's `offset_in_asset_wad`
addresses `core_data.bin` (not `core_data.wad`). All **1,864** tie classes
resolve with `o_class` agreeing on every one. A `packets` word of **zero
means no block at that LOD** -- 2,177 real blocks, 1,551 absent slots.
- [x] Tie **blocks are self-delimiting**: `parse_tie_block` walks to the `0xf6`
terminator that every real block carries (2,177 of 2,177), followed by zero
padding. This is what makes blocks parseable at all -- `packets[1]` bounds
one on only 461 of 1,864 classes.
- [x] The tie **16-byte record is R&C1's `TiePacketHeader`.** Identified not by
resemblance but by R&C1's own rule, `bfc_distance == 5 * shader_count`,
holding on **15,219 of 15,219** records across all 2,177 blocks. A block is
`[16-byte header][record table][vertex payload]`, and the header word at
`+0x00` is the byte offset from the block start to the payload. The earlier
"variable-length, not 16-byte records" reading was wrong: the word is not
an element count, which is why dividing the payload by it gave 0 of 922.
- [x] The tie **vertex element is 16 bytes**, measured over 95,612 vertices on
levels 0-2 and settled by comparison: a 12-bit-fixed texcoord hit rate of
**82.5%** at 16 bytes against 47.8% at 8 and 44.8% at 12.
- [ ] The tie **vertex fields are not named.** All eight `i16` slots span the full
range; the texcoord pair is 12-bit fixed on 82.5%, not 100%, and the
residual tracks record shape (60% to 89%), so something per-packet varies
that this reading does not capture.
- [~] `shrub` — class table done (48-byte entries, not 32; reading shrub at
32 still gives 231 sane-looking entries of 358, which is the hazard).
Geometry header parsed and checked field by field on all 358 spans: