This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/structures/asteroid-fields.md
Sylpheed RE agent 1f0f90e304 re: the 8 values per asteroid are index, quaternion, position
Measured over every Frame_*Asteroid* record on the disc - 384 records, 37518
items (6253 per language pack x 6; the remaining 380 per pack belong to the
seven record-less Alpha frames, the same 57-vs-64 split as before).

  slot 0    equals the running index 0..N-1   37518 / 37518, max 587, and the
            largest FrameCount is 588, so the last index is N-1
  slots 1-4 a unit quaternion                 37518 / 37518 within 1e-4 of 1.0
            (min 0.999999, max 1.000001)
  slot 4    sign of the real part             min +0.001, never negative -
            canonical w-last, so the order is (x, y, z, w)
  slots 5-7 position in metres                -163858..+79572, -114308..+76422,
            -105224..+110263 - inside the +/-250 km half-extent of
            mapmesh_box_500km

len(anonymous fields) == 8 * FrameCount on 384 of 384, 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 37518 does. Two further cross-checks fell
out - the maximum index is exactly max(FrameCount) - 1, and every position lies
inside the 500 km box the corpus already knows (world unit = 1 metre).

There is no scale slot: an asteroid's size comes from its group's
AsteroidModelName, which is why a group is one model plus one placement list.

Not settled: whether the quaternion is object->world or world->object and its
handedness - a norm test cannot separate those; and why S28_p1 has an asteroid
volume with no definition.

All fifteen artefacts byte-identical.
2026-08-28 02:03:23 +00:00

149 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
* **`EnumAsteroid` equals that record's `FrameCount` in 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 — and `EnumAsteroid` counts 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.
## ✅ 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). 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…N1` | **37 518 / 37 518**, max **587** — and the largest `EnumAsteroid`/`FrameCount` is **588**, so the last index is `N1` |
| 14 | 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)` |
| 57 | 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.
## 🟡 Not settled
* Why `S28_p1` has an asteroid volume and no definition (S28 is the challenge
mission that alone flies `rou_f004`, see [[stage-numbering-and-player-craft]]).
* Whether the quaternion is object→world or world→object, and its handedness —
the norm test cannot tell them apart.