Withdrawing my own repeated claim that S28_p1 has an asteroid volume with no
definition. It came from a set-difference over FILE NAMES, which cannot see
reuse. Joining the fields per PHASE settles it:
S28.Phase_1 MapMesh = S28_p1_AsteroidVolume_wp.col
AsteroidDefinition = S14_p2_asteroid.tbl
S28 scatters Stage 14 phase 2's rock list inside its own volume. Nine
definition tables and nine _AsteroidVolume_wp meshes, with one table shared by
two phases: S14_p2_asteroid.tbl -> S14.Phase_2 and S28.Phase_1.
The earlier census's own arithmetic already contained the answer: the four
values counted x12 (2 per language pack) were S01, S04, S05 - two phases of ONE
stage - and S14_p2, which is two phases of TWO different stages. The count was
right; reading x12 as "two phases of the same stage" was the error.
test_s8p1_asteroid.tbl is not test-only either - it is what S02.Phase_1 ships
with. A test_ name here is a leftover, not a dead table.
The true residual is two phases and it is the opposite pairing to what I
recorded: S01.Phase_3 has the volume and no definition (S01 keeps its volume
across all three phases but scatters rocks only in 1-2), and S02.Phase_1 has a
definition and no volume (MapMesh is the plain mapmesh_box_500km).
All fifteen artefacts byte-identical.
10 KiB
The asteroid-field tables (AsteroidGroup_00N)
Status: ✅ decoded statically, 2026-08-28. Every number below is an exact
disc census over all 41 .pak archives using the corpus reader.
Where it sits in the chain
stage-definition-table and stage-mission-tables already record that a
stage's Phase_N block carries an AsteroidDefinition field naming a .tbl
(test_s8p1_asteroid.tbl in the _Test template, "" in most phases), and
unit-datasheet-static already partitions the Generic records and finds
10 per pack whose only field is {EnumAsteroidGroup} — "an asteroid-group
file". What neither says is what is inside those ten files.
Stage_SNN . Phase_N . AsteroidDefinition = "S04_asteroid.tbl"
└── that object: Generic . EnumAsteroidGroup = "7"
AsteroidGroup_000 … AsteroidGroup_006
AsteroidModelName = mob_n050
AsteroidFrameName = Frame_S04_Asteroid_01
EnumAsteroid = 39
✅ The table
10 objects per pack × 6 GP_MAIN_GAME_<lang> packs = 60, holding 384
AsteroidGroup_* records in total. Each object has 4 or 7 groups (12
objects with 4, 48 with 7). Every group record carries exactly three fields,
present on all 384 with no partials:
| field | distinct values | what it is |
|---|---|---|
AsteroidModelName |
28 | the mesh — mob_n050…mob_n056 (24 uses each) and mob_n040…mob_n043 (18 each) |
AsteroidFrameName |
64 | Frame_S<NN>[_pN]_Asteroid_NN — the placement frame |
EnumAsteroid |
37 | a count, 1 … 588; 6 633 asteroids per pack summed |
Frame_S<NN>_Asteroid is the same name regn-map-grid reports the mission
Route tables showing independently — the frame is the join to the map data.
✅ Enum<Thing> is a count; Enumerate<Thing> names a table
EnumAsteroidGroup equals the number of AsteroidGroup_* records in its own
object in 54 of 60 objects, and EnumAsteroid is a plain integer. That is
the opposite of the Enumerate* convention, where the value is a child table's
file name. The -erate is the difference, and it is worth checking the
spelling before assuming which kind a field is.
⚠️ Count-style fields still are not safe: the 6 failures are one object,
repeated once per language pack — it declares 4 and contains 7.
🔑 The tenth object is unreferenced, and it is the one that miscounts
Joining the ten objects to the nine AsteroidDefinition values by frame prefix:
| object's frame prefix | groups | declared | named by |
|---|---|---|---|
Frame_S01_Asteroid |
4 | 4 | S01_asteroid.tbl |
Frame_S04/S05/S13/S14_p1/S14_p2/S08_p1/S08_p2_Asteroid |
7 | 7 | the matching SNN[_pN]_asteroid.tbl |
Frame_Test_Asteroid |
4 | 4 | test_s8p1_asteroid.tbl |
Frame_Alpha_S01_Asteroid + Frame_Alpha_S01_Haze + Frame_TAlpha_S01_Asteroid |
7 | 4 | nothing |
Nine AsteroidDefinition names, nine matching objects, and a tenth that no
phase names — an Alpha Stage-01 variant with its own Haze frames and a
TAlpha set. It is also the only object whose declared count is wrong, which is
what an abandoned table looks like.
🔑 S28 has an asteroid volume but no asteroid definition
mcol-collision lists nine _AsteroidVolume_wp collision meshes: S01,
S04, S05, S08_p1, S08_p2, S13, S14_p1, S14_p2 and S28_p1. The nine
AsteroidDefinition values are the same eight stages plus test_s8p1. So
the two sets share eight and differ by one each: S28_p1 has the collision
volume for an asteroid field but no table saying which rocks to scatter in it,
while test_s8p1 has a table with no volume of its own.
✅ EnumAsteroid is the frame record's FrameCount — 57/57 (2026-08-28)
Each AsteroidFrameName is itself an IDXD record name. Those records carry
one named field, FrameCount, and a run of anonymous ones:
EnumAsteroidequals that record'sFrameCountin 57 of 57 frames that have a record — zero mismatches, and the map is single-valued for all 64 frame names.- The anonymous fields number exactly 8 ×
FrameCount, on all 384 occurrences (ratio histogram:{8.0: 384}). So the frame record is a table of 8 values per asteroid — the placement data — andEnumAsteroidcounts the rocks.
mission-wave-arrivals already met these records: they were the outliers that
broke its route-speed measurement, and it filtered them out by name, noting "the
corpus already records that the 8-value frame is not universal". regn-map-grid
already guessed the link — "a map's asteroids and debris, which the mission
Route tables independently show as Frame_S<NN>_Asteroid records… stated as a
reading, not a measurement; nothing here counts objects." What neither says is
that the count is exact. Now it is measured.
🔑 The abandoned table lost its placement data too. Exactly 7 of the 64
frame names have no record, and they are exactly the seven groups of the
unreferenced object: Frame_Alpha_S01_Asteroid_{01,02,04},
Frame_Alpha_S01_Haze_{01,02,03}, Frame_TAlpha_S01_Asteroid_03. 57 + 7 = 64,
zero partials on either side.
✅ The name→object join DOES reproduce by hash — with the path prefix
Withdrawing the 🔴 from the first write-up. AsteroidDefinition values resolve
to their objects' pak entry keys as name_hash("stage\" + name), 9 of 9,
each landing on the object the frame-prefix join independently predicted. My
first attempt hashed the bare file name; archive-naming's resolver hashes
prefix + name over a list of path prefixes, and stage\ is the one that
works here. (Stage\ scores identically — the hash lower-cases.)
Two independent joins, 9/9 agreement, and both leave the same tenth object unreferenced.
⚠️ Sharper still, found a turn later: stage-mission-tables already says it
in words — "The prefix is stage\ — name_hash is case-insensitive, so its
case is free" — and its stagetbl.py examples pass 'stage\AIParams_S02.tbl'.
The answer to my "🔴 does not reproduce by hash" was written in the doc that owns
the stage tables.
✅ The 8 values are index, quaternion(x,y,z,w), position(x,y,z) (2026-08-28)
Measured over every Frame_*Asteroid* record on the disc — 384 records,
37 518 items (6 253 per language pack × 6; the other 380 per pack belong to the
seven record-less Alpha frames).
🔴 The layout itself is not new — stage-mission-tables already documents
(time, quat.x, quat.y, quat.z, quat.w, pos.x, pos.y, pos.z) for Route_*, and
unit-group-table already documents 1 + 8·FrameCount for formations. I wrote
this up as if the shape were a discovery and called slot 0 an "index" without
checking; the correction and the disc-wide census are in
stage-mission-tables. What the asteroid measurement does add is the norm
test and the fact that this family sets time to the ordinal. One raw item,
for shape:
0 0.268744 0.505919 0.579670 0.579487 15667.14 -7199.61 -58794.88
1 -0.053496 -0.540765 -0.260996 0.797868 -43571.37 -60583.68 -3417.80
2 -0.696396 0.682371 …
| slot | test | result |
|---|---|---|
| 0 | equals the running index 0…N−1 |
37 518 / 37 518, max 587 — and the largest EnumAsteroid/FrameCount is 588, so the last index is N−1. ⚠️ This slot is the time of the shared keyframe layout (stage-mission-tables); the asteroid tables, having nothing that moves, set it to the ordinal — see the disc-wide census there |
| 1–4 | is it a unit quaternion? |q| |
37 518 / 37 518 within 1e-4 of 1.0 (min 0.999999, max 1.000001) |
| 4 | sign of the real part | min +0.001, never negative — the canonical w-last form, so the order is (x, y, z, w) |
| 5–7 | position, metres | ranges −163 858…+79 572, −114 308…+76 422, −105 224…+110 263 — inside the ±250 km half-extent of mapmesh_box_500km |
And len(anonymous fields) == 8 × FrameCount on 384 of 384 records, zero
mismatches.
The unit-norm test is the control: an arbitrary 4-tuple has no reason to sit on
the unit sphere, and every one of the 37 518 does. World unit = 1 metre, so
these are metres from the map origin. There is no scale slot — an asteroid's
size comes from its group's AsteroidModelName, which is why one group is one
model plus one placement list.
🔴✅ CORRECTION — S28 does have a definition: it borrows Stage 14's (2026-08-28)
Withdrawing "S28_p1 has an asteroid volume with no definition". That came from a
set-difference over file names, which cannot see reuse. Joining the fields
per phase instead:
| table | the phases that declare it |
|---|---|
S01_asteroid.tbl |
S01.Phase_1, S01.Phase_2 |
S04_asteroid.tbl |
S04.Phase_1, S04.Phase_2 |
S05_asteroid.tbl |
S05.Phase_1, S05.Phase_2 |
S08_p1 / S08_p2 |
S08.Phase_1 / S08.Phase_2 |
S13_asteroid.tbl |
S13.Phase_1 |
S14_p1_asteroid.tbl |
S14.Phase_1 |
S14_p2_asteroid.tbl |
S14.Phase_2 and S28.Phase_1 |
test_s8p1_asteroid.tbl |
S02.Phase_1 |
S28.Phase_1 declares S14_p2_asteroid.tbl while using its own
S28_p1_AsteroidVolume_wp mesh — it scatters Stage 14's rock list inside its
own volume. Nine tables, nine volumes, one table shared by two phases.
⚠️ The earlier census's own arithmetic contained this: the four values counted
×12 (2 per language pack) were S01, S04, S05 — two phases of one stage —
and S14_p2, which is two phases of two different stages. Reading ×12 as
"two phases of the same stage" was the error.
⚠️ test_s8p1_asteroid.tbl is not test-only either — it is what S02.Phase_1
ships with. A test_ name here is a leftover, not a dead table.
The true residual is two phases, and it is the opposite pairing:
S01.Phase_3 has the volume and no definition (S01 keeps its volume across all
three phases but scatters rocks only in 1–2), and S02.Phase_1 has a definition
and no volume (its MapMesh is the plain mapmesh_box_500km).
🟡 Not settled
- Whether the quaternion is object→world or world→object, and its handedness — the norm test cannot tell them apart.