Docs audit: nine stale class-ID counts left behind by the terminator fix

Checked the docs for claims this session retracted. Found a real one: the vtbl
terminator fix (cc52060) corrected the distinct class-id count from 1,163 to 1,162,
and nine places in the format spec and two in the roadmap still said 1,163.

    docs/formats/core-data-regions.md   9 occurrences
    docs/roadmap.md                     2 occurrences

The reason they survived is the same shape as the vtbl bug itself. The test was
fixed and the new number asserted, but the prose kept the old one, and the prose is
what a reader takes away. A commit that corrects a measurement and does not correct
the document it was measured in has only half-landed.

Checked the numerators before touching them rather than blanket-replacing: 1,135 of
the ids are at or above level 0's moby_classes.count, and 1,133 are at or above the
largest per-level table. Both were already correct -- only the denominators were
wrong -- so a find-and-replace across the number would have corrupted two correct
figures. The tests confirm:

    lvl.vtbl: 1162 distinct class ids from 5822 records; max 0x13ba (5050);
    moby_classes.count is 238, and 1135 of 1162 ids (97.7%) are at or above it

Left one deliberate occurrence of "1,163" in the spec, in a note explaining that the
figure was 1,163 until the terminator fix and why, so the correction is traceable
rather than looking like a typo.

293 passing with the disc and savestate present, clippy clean. Documentation only.
This commit is contained in:
2026-10-15 03:15:02 +02:00
parent 3dbd384b59
commit 6e5486de88
2 changed files with 14 additions and 11 deletions
+12 -9
View File
@@ -3248,7 +3248,7 @@ Phase 3 port made visible. Its records are three words:
Walking all 27 levels: Walking all 27 levels:
5,878 records 5,878 records
1,163 distinct class ids 1,162 distinct class ids
range 0 .. 0x13BA (5,050) range 0 .. 0x13BA (5,050)
median 38 new ids contributed per level median 38 new ids contributed per level
@@ -3258,7 +3258,7 @@ three words -- so the addresses are function entries, not arbitrary words.
### Why this retires the earlier bound ### Why this retires the earlier bound
`LevelCoreHeader::moby_classes.count` is **238** on level 0. **1,135 of the 1,163 `LevelCoreHeader::moby_classes.count` is **238** on level 0. **1,135 of the 1,162
class ids -- 97.6% -- are at or above that number.** class ids -- 97.6% -- are at or above that number.**
So "value < moby_classes.count" was never a test for "this is a class id". It was So "value < moby_classes.count" was never a test for "this is a class id". It was
@@ -3277,10 +3277,10 @@ selected for smallness.
### Re-scored against the real id set ### Re-scored against the real id set
With the game's own 1,163 ids as the validity test, no chunk slot separates: With the game's own 1,162 ids as the validity test, no chunk slot separates:
best slot 0.375 (type f2ef4275 slot 34) best slot 0.375 (type f2ef4275 slot 34)
chance rate 0.018 (1,163 ids in 65,536 values) chance rate 0.018 (1,162 ids in 65,536 values)
a dozen slots 0.25 .. 0.38, with no gap between them a dozen slots 0.25 .. 0.38, with no gap between them
Twenty times chance is not nothing, but a dozen slots packed into the same band Twenty times chance is not nothing, but a dozen slots packed into the same band
@@ -3294,15 +3294,18 @@ is not the class count.** Asserted in
Two axes that are easy to conflate, and are not the same thing: Two axes that are easy to conflate, and are not the same thing:
lvl.vtbl oClass 1,163 distinct across 27 levels, largest 5,050 lvl.vtbl oClass 1,162 distinct across 27 levels, largest 5,050
(was 1,163 until the terminator fix -- see
"The vtbl terminator" below; the loop read past the
end of the table)
core_index moby_classes.count 136 .. 259 per level core_index moby_classes.count 136 .. 259 per level
**1,133 of the 1,163 identifiers are at or above the largest per-level class **1,133 of the 1,162 identifiers are at or above the largest per-level class
table.** So `oClass` cannot index that table -- nearly every identifier would fall table.** So `oClass` cannot index that table -- nearly every identifier would fall
off the end. They are different axes: the vtbl names a *type* of moby, the class off the end. They are different axes: the vtbl names a *type* of moby, the class
table lists the *models* a level instantiates. table lists the *models* a level instantiates.
The identifier space is shared rather than renumbered per level: 1,163 distinct The identifier space is shared rather than renumbered per level: 1,162 distinct
values come out of 5,878 records, and level 0 contributes 234 while levels 1-8 values come out of 5,878 records, and level 0 contributes 234 while levels 1-8
contribute 72, 69, 77, 54, 41, 68 and 28 -- each level roughly its own share, not contribute 72, 69, 77, 54, 41, 68 and 28 -- each level roughly its own share, not
one level dominating. An earlier note here said "most of the game's classes appear one level dominating. An earlier note here said "most of the game's classes appear
@@ -3353,7 +3356,7 @@ and its sound records:
Both identifier spaces are **global and shared**: 217 camera records carry only 14 Both identifier spaces are **global and shared**: 217 camera records carry only 14
distinct ids and 32 sound records only 6, so levels re-use ids rather than distinct ids and 32 sound records only 6, so levels re-use ids rather than
renumbering -- the same sharing as the moby table's 1,163 distinct out of 5,822. renumbering -- the same sharing as the moby table's 1,162 distinct out of 5,822.
This is what makes the tables usable rather than merely present. A per-level This is what makes the tables usable rather than merely present. A per-level
dispatch entry is now addressable by a game-wide id, and the handler addresses are dispatch entry is now addressable by a game-wide id, and the handler addresses are
@@ -3737,7 +3740,7 @@ The gate's second half is therefore met, with the caveat that "agrees" means
## The vtbl terminator, and why three counts were all defensible ## The vtbl terminator, and why three counts were all defensible
Three committed tests counted `lvl.vtbl` records and disagreed: 5,878 records / Three committed tests counted `lvl.vtbl` records and disagreed: 5,878 records /
1,163 ids, 5,822 / 1,162, and 5,788. All three were measuring something, and none 1,162 ids, 5,822 / 1,162, and 5,788. All three were measuring something, and none
of the tests was simply wrong. of the tests was simply wrong.
**5,822 records / 1,162 ids is the table.** The record array terminates on **5,822 records / 1,162 ids is the table.** The record array terminates on
+2 -2
View File
@@ -421,7 +421,7 @@ Phase 1 hands over the extents; this phase works out what each one contains.
Vertices are 16-byte VIF-packed half-float quads, handed back raw. Vertices are 16-byte VIF-packed half-float quads, handed back raw.
- [x] The **moby class-id space is known**: `lvl.vtbl` (section 3 of the level - [x] The **moby class-id space is known**: `lvl.vtbl` (section 3 of the level
program) is records of `{oClass, handler, auxiliary}`, and across 27 levels program) is records of `{oClass, handler, auxiliary}`, and across 27 levels
that is **1,163 distinct ids** reaching `0x13BA` (5,050), median 38 new per that is **1,162 distinct ids** reaching `0x13BA` (5,050), median 38 new per
level. `moby_classes.count` is 238 and **97.6% of real ids are at or above level. `moby_classes.count` is 238 and **97.6% of real ids are at or above
it**, so the count was never a valid bound -- which is the mechanism behind it**, so the count was never a valid bound -- which is the mechanism behind
the chunk class-ID artifact now retracted. Re-scored against the real set, the chunk class-ID artifact now retracted. Re-scored against the real set,
@@ -441,7 +441,7 @@ Phase 1 hands over the extents; this phase works out what each one contains.
across levels rather than renumbered. Handler addresses are function across levels rather than renumbered. Handler addresses are function
entries in the same overlay's `.text`, which is now readable. Asserted. entries in the same overlay's `.text`, which is now readable. Asserted.
- [x] `lvl.vtbl`'s **`oClass` is a global identifier**, not an index into the level's - [x] `lvl.vtbl`'s **`oClass` is a global identifier**, not an index into the level's
class table: 1,163 distinct ids across the game, largest 5,050, against class table: 1,162 distinct ids across the game, largest 5,050, against
`moby_classes.count` of 136..259. **1,133 of 1,163 are out of range** for any `moby_classes.count` of 136..259. **1,133 of 1,163 are out of range** for any
per-level table. The two are different axes -- a type id, and the models a per-level table. The two are different axes -- a type id, and the models a
level instantiates -- and the space is shared rather than renumbered level instantiates -- and the space is shared rather than renumbered