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.