CORRECTION FIRST: a row in name-block-bases.txt is a (function, REGISTER) pair,
not a function. Every one-line label I carried for these blocks quoted one of
two rows, and the other row of the same function is an unrelated schema:
sub_822E3EC8 r11 = 29 shader constants + techniques, r10 = 15 material map slots;
sub_822AFA50 r11 = XDK shader-compiler tokens, r10 = 13 menu camera tags;
sub_823AE908 r11 = the 43-name S16 boss collision table, r31 = 33 shader tokens
(the dense-block false positive). Quote the register.
The engine's material vocabulary (into xbg7-mesh.md, which owns the
_col/_spc/_gls/_lum suffixes): nine base map slots -- ColorMap, LuminosityMap,
SpecularityMap, GlossinessMap, ReflectionMap, TransparencyMap, NormalMap,
FresnelMap, EdgeTransparencyMap -- and seven *MapArray twins; 9 - 7 = 2, and the
two without a twin are FresnelMap and EdgeTransparencyMap. Four of the nine are
exactly the four documented suffixes; the other five have no suffix in the
shipped meshes, so the engine supports more channels than the assets use. Plus
17 g_* shader constants, three cube samplers, and DepthOnly / MotionVector /
Transparent techniques -- the ...Last matrices with the motion-vector technique
say the renderer keeps the previous frame's per-object transform.
The S16 boss's collision parts (into collisionset.md): sub_823AE908 r11 is an
interleaved (mesh, logical-part) table -- rou_e901_body_02_c ->
Boss16CollisionBody02, five wing pairs, both tails, barrier, rou_e910_core_cmesh
-> Boss16CollisionCore, and mob_n900{,_b01,_b02,_gear}_cmesh -> Room00/01/02 +
Gear00/01/02. 23 logical parts + 20 meshes = 43, differing by exactly three:
Body01 opens with no mesh and the one gear mesh carries three gear names. Cross-
links the S16 Guardian/Core object found during the PlayerParams work.
Not settled: whether those meshes are CollisionSet entries, why Body01 has no
mesh, and no shader slot was traced to a shipped shader.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
189 lines
9.1 KiB
Markdown
189 lines
9.1 KiB
Markdown
# `CollisionSet_*.bin` — the per-object collision-mesh library
|
||
|
||
**✅ Decoded 2026-08-26.** The 18 remaining `hidden/MiscBin.pak` entries, named
|
||
in the previous iteration as `CollisionSet_S01…S16.bin`,
|
||
`CollisionSet_Tutorial.bin` and `CollisionSet_test.bin` from the `StageResource`
|
||
objects' `CollisionMeshes` field. See
|
||
[`mcol-collision.md`](mcol-collision.md) for the naming and for `MCOL`, which is
|
||
the *map* hull; this is the per-*object* library.
|
||
|
||
## ✅ All 18 are byte-identical
|
||
|
||
CollisionSet blobs: 18 distinct contents: 1
|
||
|
||
The per-stage naming is nominal. Every stage points at the same 1 675 148-byte
|
||
library, stored eighteen times — which is why the sizes were identical, the
|
||
observation that made this worth opening.
|
||
|
||
## ✅ The record layout
|
||
|
||
u32 size bytes following this word's own 8-byte prefix
|
||
u32 name_len 16, 24 or 32 — the name field, padded to a multiple of 8
|
||
char name[name_len]
|
||
u32 vertex_count
|
||
u32 triangle_count
|
||
f32 vertex[3] × vertex_count
|
||
u32 index[3] × triangle_count
|
||
|
||
and the next record starts at `offset + 8 + size`. Note the indices are **`u32`**
|
||
here, where `MCOL` uses `u16` — these are two different serialisers.
|
||
|
||
| | |
|
||
|---|---|
|
||
| records | **158** |
|
||
| bytes consumed | **1 675 148 / 1 675 148 — exact** |
|
||
| `size == 16 + name_len + 12·nv + 12·nt − 8` | **158 / 158** |
|
||
| every triangle index `<` vertex count | **158 / 158** |
|
||
|
||
The walk consuming the file to the byte over 158 variable-length records, with
|
||
the size word predicted from the two counts every time, is what makes this a
|
||
decode rather than a plausible reading — a wrong field would desynchronise the
|
||
walk within a few records and could not land exactly on the end.
|
||
|
||
## ✅ And they are meshes
|
||
|
||
| | |
|
||
|---|---|
|
||
| edges shared by exactly two triangles | **132 167 / 134 529 = 98.24 %** |
|
||
| fully manifold meshes (every edge shared by two) | **147 / 158** |
|
||
|
||
92 of the 158 have Euler characteristic **2** — a single closed surface. The
|
||
remainder are multi-component (χ = 4, 6, 8) or open shells, which is expected
|
||
for parts like `rou_e901_wing_01_L` that are one piece of a larger hull.
|
||
|
||
## ✅ What is in it
|
||
|
||
158 meshes, **48 241 vertices and 90 836 triangles**, in four families:
|
||
|
||
| prefix | count | what the names look like |
|
||
|---|---|---|
|
||
| `rou_` | 78 | `rou_e101_bdy`, `rou_e101_brg_01`, `rou_e106_eng`, `rou_f104_sld_02` |
|
||
| `mob_` | 30 | `mob_n040`, `mob_n110_bdy`, `mob_n900_gear` |
|
||
| `rob_` | 4 | `rob_e001`, `rob_e005_bdy`, `rob_f001_bdy`, `rob_f002` |
|
||
| stage asteroids | 46 | `S01_Asteroid_cmesh_01a`, `S14_p1_Asteroid_cmesh_05` |
|
||
|
||
The `_bdy` / `_brg` / `_eng` / `_wep` / `_sld` suffixes are body, bridge, engine,
|
||
weapon and shield — the same sub-part vocabulary as the `XBG7` render models, so
|
||
these are per-part collision proxies. The asteroid meshes carry exactly the
|
||
stage prefixes that have an `_AsteroidVolume_wp` `MCOL`.
|
||
|
||
### ✅ The names *are* the game's own resource ids (upgraded from 🟡 the same day)
|
||
|
||
The previous write-up recorded this link as **untested**, because matching
|
||
`_cmesh` stems against `.xbg` object names covered only 4 of 158 — the disc keeps
|
||
just one build manifest (`machines\rou_e104\…`), so that test had no power in
|
||
either direction.
|
||
|
||
The right corpus is not the manifests, it is the **`GameResourceID` field** of
|
||
the `DefTables` / `GP_MAIN_GAME` records — 480 distinct values, e.g.
|
||
`GameResourceID = 'rou_e010'`. Against those, with a control that shuffles the
|
||
characters of each stem:
|
||
|
||
| | |
|
||
|---|---|
|
||
| ship / mob stems prefixed by a real `GameResourceID` | **108 / 112 = 96.4 %** (55 exact) |
|
||
| same stems, characters shuffled (control) | **0 / 112 = 0.0 %** |
|
||
| asteroid stems prefixed (expected none — they are not resources) | **0 / 46** |
|
||
|
||
So a `CollisionSet` entry is `<GameResourceID>[_<part>]_cmesh`: the collision
|
||
proxy for one part of one game resource. The four unmatched stems are
|
||
`rou_e303_wep`, `rou_f301_body`, `rou_f302`, `rou_f303_body`.
|
||
|
||
The 0 / 46 on asteroids is worth keeping: a test that fires on everything would
|
||
be the bound-check hazard again. It separates the two families correctly.
|
||
|
||
## 🟡 The world unit — the metre reading does not survive this file
|
||
|
||
[`mcol-collision.md`](mcol-collision.md) inferred **1 unit = 1 metre** from
|
||
`mapmesh_box_500km.col` spanning exactly 500 000 units. The arithmetic is exact,
|
||
but this library is the first chance to check what that scale *implies*, and the
|
||
implications are large:
|
||
|
||
| mesh | bbox diagonal, if 1 unit = 1 m |
|
||
|---|---|
|
||
| `rou_e010` (`e010_ADAN_Attacker_S` — an "attacker, small") | 133 m |
|
||
| `rou_e106_bdy` | 2 368 m |
|
||
| `rou_e101_bdy` | 9 371 m |
|
||
| `rob_f001_bdy` | 50 179 m |
|
||
| `S04_Asteroid_cmesh_04` | 84 714 m |
|
||
| `rob_f002` | 447 522 m — 89 % of the arena's width |
|
||
|
||
A craft the game's own tables call *small* would be 133 m, and the largest object
|
||
would nearly span the arena. That is not impossible for the genre, but it is not
|
||
the corroboration I claimed. **Downgrading the scale claim to 🟡**: either the
|
||
game's units really are metres and its ships are kilometre-scale, or `500km` in
|
||
that filename names something other than the box's span. The 500 000 arithmetic
|
||
stands; the *interpretation* does not have independent support.
|
||
|
||
**And there is no second instance of the convention to check against.** Sweeping
|
||
every `.pak` on the disc for a name carrying a kilometre figure returns
|
||
`mapmesh_box_500km.col` / `.rgn` and nothing else — 162 references, all to that
|
||
one pair. So the reading rests on a single filename, with no corroborating
|
||
example anywhere in the data. That is not a refutation; it is the absence of any
|
||
static way to settle it.
|
||
|
||
My objection's own premise did survive its check: `rou_e010` is a real
|
||
`GameResourceID`, and `e010_ADAN_Attacker_S` appears in the stage tables, so the
|
||
133-unit mesh does belong to a craft the game calls an attacker. (Whether the
|
||
trailing `_S` means "small" is a further guess — there are `_EX4`, `_HF` and
|
||
`_HF_Wayne` variants of the same unit — so it is suggestive, not evidence.)
|
||
|
||
🟡 **Not blocked — just not run yet.** An earlier version of this line said
|
||
"🔴 blocked on the oracle", and that was a mislabel. 🔴 is for what the container
|
||
*cannot* do; `run-canary` works here, and measuring the game is what it is for.
|
||
What the sweep above actually established is narrower and worth keeping: **no
|
||
*static* test can settle this**, because the disc contains exactly one
|
||
size-bearing asset name.
|
||
|
||
The experiment is well-supported by tooling that already exists:
|
||
|
||
1. `findplayer.py` recovers the player craft's live position triple from guest
|
||
RAM by its motion, so a mission run yields positions in world units.
|
||
2. The HUD prints a **distance to the selected target** in the game's own units
|
||
of measure.
|
||
3. The same separation read both ways — Euclidean distance between the two
|
||
position triples, against the number the HUD displays — is the conversion,
|
||
measured rather than inferred from a filename.
|
||
|
||
`pad.py` scripts the target-select input (Ⓐ pressed **twice**, per `AGENT.md`),
|
||
and `screenshot` crops to the game surface. Nothing here needs anything the box
|
||
lacks. It is the next run.
|
||
|
||
Reproduce with `tools/re-capture/collisionset.py verify` / `list`
|
||
([`../data/collisionset.txt`](../data/collisionset.txt)); `collisionset.py obj`
|
||
exports any named mesh.
|
||
|
||
❔ Still open: the meshes carry no material, flag or surface-type field — the
|
||
record is exactly counts, positions and indices — so whatever distinguishes a
|
||
hull hit from a shield hit is not stored here. And the library is not indexed:
|
||
lookup is presumably by name from the unit tables, which has not been traced.
|
||
|
||
## ✅ The Stage 16 boss's collision parts, named in the executable (2026-08-27)
|
||
|
||
Found by the base-solver ([[player-tuning-tables]]): **`sub_823AE908`, `r11`,
|
||
43 names**, and the list is an **interleaved (mesh, logical-part) table**:
|
||
|
||
```
|
||
rou_e901_body_02_c → Boss16CollisionBody02 rou_e901_wing_0N_{L,R}_c → …Wing0N{L,R}
|
||
rou_e901_tail_{L,R}_c→ Boss16CollisionTail{L,R} rou_e901_barrier_cmesh → …Barrier
|
||
rou_e910_core_cmesh → Boss16CollisionCore mob_n900_cmesh → …Room00
|
||
mob_n900_b01_cmesh → …Room01 mob_n900_b02_cmesh → …Room02
|
||
mob_n900_gear_cmesh → …Gear00 / …Gear01 / …Gear02
|
||
```
|
||
|
||
**23 logical parts + 20 meshes = 43**, and the two counts differ by exactly
|
||
three: `Boss16CollisionBody01` opens the list with no mesh before it, and
|
||
`mob_n900_gear_cmesh` is followed by **three** gear names. A second row
|
||
(`sub_823C0260`, 15 names) lists the hull parts alone — `Body01…03`, `TailL/R`,
|
||
`Wing01L…Wing05R` — with no meshes.
|
||
|
||
🔑 **This is the same boss the `PlayerParams` work turned up**: the S16
|
||
`Guardian` / `Core` object ([[player-tuning-tables]]) names its weapons
|
||
`Shell_S16Boss_*`, and here `rou_e910_core_cmesh → Boss16CollisionCore` gives the
|
||
`Core` record a collision hull. `rou_e901` is the boss craft; `mob_n900` is the
|
||
station/room structure it sits in, whose three `Gear` parts share one mesh.
|
||
|
||
🟡 Not settled: whether these meshes are entries of the `CollisionSet` library
|
||
above (all 18 blobs are byte-identical, so a name-level join was not attempted),
|
||
and why `Body01` has no mesh in the list.
|