diff --git a/docs/plan/level-assets.md b/docs/plan/level-assets.md index 4162c6f..9352053 100644 --- a/docs/plan/level-assets.md +++ b/docs/plan/level-assets.md @@ -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.