Record shrub geometry findings and the per-table entry strides

The three class tables do not share an entry size. Measured by how many
entries give a geometry offset inside core_data plus a plausible o_class,
over all 27 levels:

  moby  stride 0x20   5769 / 5796
  tie   stride 0x20   1864 / 1864
  shrub stride 0x30    356 / 358

Reading shrub at 0x20 still yields 231 sane-looking entries out of 358,
which is the hazard: wrong often enough to look right. The committed code
already handles this -- ShrubClassEntry is 48 bytes and carries billboard
texture fields the other two lack -- so this is measurement, not a fix.

Shrub geometry is not moby's part-table format: all 358 spans have a
leading word of 0 or one too large to index a table into, so none contain
a moby-style part table.

Two header fields do self-identify, both exact across all 358 spans:
+0x24 equals the class entry's own o_class, and +0x2c is a data offset
landing inside the span. +0x10 reads 5.0f on 281 of 358, so it is a value
rather than a marker and carries no structural weight.

Rest of the shrub header undecoded.
This commit is contained in:
2026-10-11 02:32:55 +02:00
parent bbb10c678d
commit 3ca8522be1
+31
View File
@@ -823,3 +823,34 @@ geometry.
Vertices are 16 bytes of VIF-packed half-float quads, cycling through four
distinct 32-bit words. Not unpacked -- handed back raw.
## Shrub geometry (2026-10-11)
Class entry strides differ between the three tables. Measured by how many
entries yield a geometry offset inside `core_data` and a plausible `o_class`,
across all 27 levels:
| table | stride | sane entries |
|-------|--------|--------------|
| moby | 0x20 | 5769 / 5796 |
| tie | 0x20 | 1864 / 1864 |
| shrub | 0x30 | 356 / 358 |
Reading shrub at 0x20 still yields 231 sane-looking entries out of 358, which
is the problem: it is wrong often enough to look right. The stride is already
handled -- `ShrubClassEntry` is 48 bytes, carrying billboard texture fields the
other two do not have.
Shrub geometry is **not** the moby part-table format. All 358 spans have a
leading word of 0 or one too large to index a table into, so none of them
contain a moby-style part table.
The header does self-identify in two places, both exact across all 358 spans:
- `span + 0x24` is a `u32` equal to the class entry's own `o_class`. 358/358.
- `span + 0x2c` is a `u32` data offset that lands inside the span. 358/358.
`span + 0x10` reads 5.0f on 281 of 358 spans, so it is a value rather than a
constant marker and carries no structural weight. The header is otherwise
undecoded: the first 0x20 bytes are packed data rather than floats, despite
`+0x10` landing exactly on 5.0.