Record core index field order, sky and moby findings

Three things this session that are cheap to rediscover wrongly and
expensive to rediscover wrongly twice.

The core index interleaves single-word region offsets with count/offset
pairs, so gs_ram and the three class tables are two words each while
tfrags..collision are one. Treating the pairs as single words shifts
every field after them, which is how collision got read as sky and
produced a page of implausible collision headers on all 27 levels.

Sky is not uniform. Most levels share a header shape, but level 2 carries
extra fields and the count word at +0x0a over-reports against the number
of real offset slots, so no single fixed header fits.

Moby geometry starts with zero words rather than a header and then packed
half-float VIF data. Not decoded; recorded so the next attempt starts
from the data instead of re-deriving the layout.
This commit is contained in:
2026-10-11 02:25:47 +02:00
parent dc941a8eec
commit 1f96ef09b9
+68
View File
@@ -730,3 +730,71 @@ A tfrag can now be turned into positioned, UV-mapped, textured, lit triangles,
told which LOD to draw, and morphed toward its parents at the right rate.
Everything needed for a first frame is parsed, and the frame is verified by
screenshot rather than by assertion.
## Core index field order (2026-10-11)
Repeatedly re-deriving these by hand wasted real time this session, so the
order is written out. It matches `LevelCoreHeader::parse` in
`crates/gc-formats/src/level_core.rs`:
| offset | field |
|--------|-------|
| 0x00 | gs_ram count |
| 0x04 | gs_ram offset |
| 0x08 | tfrags |
| 0x0c | occlusion |
| 0x10 | sky |
| 0x14 | collision |
| 0x18 | moby_classes count |
| 0x1c | moby_classes offset |
| 0x20 | tie_classes count |
| 0x24 | tie_classes offset |
| 0x28 | shrub_classes count |
| 0x2c | shrub_classes offset |
Note `gs_ram` and the three class tables are **count/offset pairs**, so every
one of them is two words. The region offsets (`tfrags` through `collision`) are
single words. Counting the class tables as one word each shifts every field
after them, which is how `collision` got read as `sky` and produced a page of
implausible collision headers on all 27 levels.
## Sky (2026-10-11)
Region is `[sky, collision)` and is well defined, but the header is **not**
yet decoded and the layout is not uniform.
Reading `u16` at the head, most levels give:
```
+0x00 0
+0x02 0
+0x04 4 or 6 varies per level
+0x06 0
+0x08 0 level 2 is the exception
+0x0a 3..6 looks like a layer count
+0x0c 0
+0x0e 64 (0x40)
+0x10 128 or 192 varies
+0x12 64 (0x40)
```
Level 2 breaks the pattern: `+0x08` is 0x0109 and the three words after the
apparent count are shifted and different (`80 00 00 00 00 01 00 00 40 00`).
So a single fixed header does not fit every level, and the count at `+0x0a`
cannot be trusted to be one.
A table of offsets follows at `+0x20` for the layers. The offsets ascend and
sit in the last 8-15% of the region, but the count of *non-zero* offsets is
sometimes less than the declared count (level 26 declares 6, has 4 real
offsets and 2 zero slots). Unresolved.
## Moby geometry (2026-10-11)
Class entries are 0x20 bytes in `core_index.bin`, fields
`geom_offset, o_class, unknown_8, unknown_c, textures[16]`; all 238 level-0
moby classes carry `textures == ffff..ff`, i.e. no texture slots.
`geom_offset` points into `core_data`, and the spans tile without overlap. The
data at that offset does not start with a recognisable header -- it opens with
zero words and then packed half-float VIF data (`55 55 15 3e` shows up 4 bytes
in). Not decoded.