Parse the sky block: 149 sub-objects with a bounding sphere, and correct the count field

Range measured: all 27 levels, each sky region from the core index's sky offset
to the next region.

Method: read the 32-bit offset table at +0x20, bounded by staying inside the
region, and parse each sub-object it names.

Result: 149 sub-objects across 27 levels. Each opens with a count, then a
bounding sphere of four floats at +0x10, then a texture offset at +0x20 and
dimensions at +0x24/+0x26:

  +000: 26 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00
  +010: e0 8d b2 c1 | 9f d6 f6 3d | ef fc fb be | 1c 61 4e 41
  +020: 90 99 05 00 | 1d 00 | 1d 00 | 00 00 e8 00 ...

Count 1 to 38, width 3 to 81, radius 1.95 to 14.56, and the radius is positive
on 149 of 149 -- the same sphere shape and the same reasoning as the shrub class
header's.

Correction to an earlier claim: the word at +0x0c is not the sub-object count. It
reads 5 on level 0 where the table holds 6 entries, 3 on level 1 against 4, 7 on
level 3 against 8, and 196617 on level 2 against 4. Across the disc
offsets.len() == count+1 holds on 10 of 27 levels.

The field is reported as SkyHeader::field_0c and nothing depends on it; the
offset table enumerates the sub-objects and is self-limiting. That earlier claim
came from level 0 alone, where 5 against 6 looked like a consistent off-by-one.
It is not an off-by-one -- it is a field that happens to be small on most levels
and is not a count at all.

gc_formats::sky parses the header and the sub-objects. The gated test asserts the
sphere is finite, the radius positive where positive at all, the dimensions
non-zero, and the sub-object list non-empty on every level.
This commit is contained in:
2026-10-12 02:43:28 +02:00
parent bbce492c91
commit 681e7e62ef
5 changed files with 356 additions and 0 deletions
+35
View File
@@ -1296,3 +1296,38 @@ signature; the RAM header is populated state.
`ShrubPacketHeader::parse` and `Shrub::packet_headers()` expose this, and the
gated test asserts all four invariants plus the size alignment on every packet
of every bounded span.
## Sky: the sub-object table, and a count field that is not a count
**Range measured:** all 27 levels, each sky region from the core index's `sky`
offset to the next region.
**Method:** read the 32-bit offset table at `+0x20`, bounded by staying inside
the region, and parse each sub-object it names.
**Result: 149 sub-objects across 27 levels.** Each opens with a count, then a
bounding sphere of four floats at `+0x10`, then a texture offset at `+0x20` and
dimensions at `+0x24`/`+0x26`:
+000: 26 00 00 00 | 00 00 00 00 00 00 00 00 00 00 00 00
+010: e0 8d b2 c1 | 9f d6 f6 3d | ef fc fb be | 1c 61 4e 41
+020: 90 99 05 00 | 1d 00 | 1d 00 | 00 00 e8 00 ...
Count 1 to 38, width 3 to 81, radius 1.95 to 14.56, and the radius is positive
on **149 of 149** -- the same sphere shape and the same reasoning as the shrub
class header's.
**Correction to an earlier claim.** The word at `+0x0c` is **not** the
sub-object count. It reads 5 on level 0 where the table holds 6 entries, 3 on
level 1 against 4, 7 on level 3 against 8, and 196,617 on level 2 against 4.
Across the disc `offsets.len() == count + 1` holds on **10 of 27** levels. The
field is reported as `SkyHeader::field_0c` and nothing depends on it; the
offset table enumerates the sub-objects and is self-limiting.
That earlier claim came from level 0 alone, where 5 against 6 looked like a
consistent off-by-one. It is not an off-by-one -- it is a field that happens to
be small on most levels and is not a count at all.
`gc_formats::sky` parses the header and the sub-objects, and the gated test
asserts the sphere is finite, the radius positive where it is positive at all,
the dimensions non-zero, and the sub-object list non-empty on every level.