Files
Sylpheed/docs/re/structures/asteroid-fields.md
Sylpheed RE agent a6e8eca436 re: the asteroid-field tables (AsteroidGroup_00N) decoded
New doc docs/re/structures/asteroid-fields.md, indexed.

Half the chain was already in the corpus: stage-definition-table and
stage-mission-tables record the per-phase AsteroidDefinition naming a .tbl,
and unit-datasheet-static partitions Generic and finds 10 per pack whose only
field is {EnumAsteroidGroup}, "an asteroid-group file". Neither says what is
inside them.

The table: 10 objects per pack x 6 packs = 60, 384 AsteroidGroup_* records,
4 or 7 groups per object. Every record carries exactly three fields, all 384,
no partials - AsteroidModelName (28 distinct; mob_n050..n056 x24 each,
mob_n040..n043 x18), AsteroidFrameName (64 distinct, Frame_S<NN>[_pN]_
Asteroid_NN, the same names regn-map-grid reports the Route tables showing),
and EnumAsteroid (37 distinct, 1..588, 6633 per pack summed).

Enum<Thing> is a COUNT; Enumerate<Thing> names a TABLE. EnumAsteroidGroup
equals its object's group count in 54 of 60 and EnumAsteroid is a plain
integer - the opposite of the Enumerate* join rule. The -erate is the
difference. Count-style fields still are not safe: the 6 failures are ONE
object repeated per language pack, declaring 4 while holding 7.

The tenth object is unreferenced and it is the one that miscounts. Joining by
frame prefix, nine objects match the nine AsteroidDefinition values one to
one; the tenth carries Frame_Alpha_S01_Asteroid, Frame_Alpha_S01_Haze and
Frame_TAlpha_S01_Asteroid and is named by nothing.

S28 has an asteroid VOLUME but no asteroid DEFINITION: mcol-collision lists
nine _AsteroidVolume_wp meshes (…, S28_p1); the nine AsteroidDefinition values
are those eight plus test_s8p1. Eight shared, one different each way.

Not settled: what EnumAsteroid counts of; why S28_p1 has a volume and no
definition. The name->object join did not reproduce by hash under name_hash,
mod 0x00FFF9D7, or a 24-bit mask - the join used is by frame prefix.

All fifteen artefacts byte-identical.
2026-08-28 01:32:52 +00:00

4.4 KiB
Raw Blame History

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_n050mob_n056 (24 uses each) and mob_n040mob_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.

🟡 Not settled

  • What EnumAsteroid counts of — instances per frame is the obvious reading and the 1…588 range fits, but it is untested.
  • Why S28_p1 has a volume and no definition (S28 is the challenge mission that alone flies rou_f004, see stage-numbering-and-player-craft).
  • 🔴 The name→object join did not reproduce by hash. AsteroidDefinition values do not map to their objects' pak entry keys under name_hash, nor under that key reduced mod 0x00FFF9D7 or masked to 24 bits — the join above is by frame prefix, which is direct. Either these keys are not name hashes or archive-naming's resolver differs from unitgroup.name_hash; not chased.