# `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 `[_]_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.