Merge branch 'auto/regn-reader'

# Conflicts:
#	docs/re/BACKLOG.md
#	docs/re/INDEX.md
#	docs/re/structures/regn-map-grid.md
#	tools/re-capture/regn_decode.py
This commit is contained in:
Sylpheed RE agent
2026-08-28 15:26:35 +02:00
3 changed files with 329 additions and 119 deletions

View File

@@ -122,7 +122,7 @@ files, which is how the same ground got covered twice.
| [`structures/movie-subtitles.md`](structures/movie-subtitles.md) | Movie subtitles & the movie ↔ mission ↔ text chain | — |
| [`structures/asteroid-fields.md`](structures/asteroid-fields.md) | `AsteroidGroup_00N` — the asteroid-field tables a `Phase_N.AsteroidDefinition` names | ✅ 10 objects/pack, 384 group records, 3 fields each (`AsteroidModelName`, `AsteroidFrameName`, `EnumAsteroid` = **the frame record's `FrameCount`, 57/57**, and the frame holds **`index, quaternion(x,y,z,w), position(x,y,z)`** per asteroid — 37 518 items, unit norm 37 518/37 518); `Enum<Thing>` counts while `Enumerate<Thing>` names a table (54/60); the join resolves as `name_hash("stage\" + name)` 9/9; 🔑 a tenth `Frame_Alpha_S01_*` object nothing references — it miscounts AND its 7 frames have no placement records; 🔑 **S28.Phase_1 borrows `S14_p2_asteroid.tbl`** — the per-phase join shows 9 tables ↔ 9 volumes with one table shared |
| [`structures/mcol-collision.md`](structures/mcol-collision.md) | `MCOL` — same container and map parameters as `REGN` | 🟡 OPENED — `POF0` at `data_size+16` 11/11, bbox pad words 11/11, `extent == maxmin` 11/11, and bbox + cell-size distributions identical to `REGN` (2/6/3 and 2/9); everything past `0x40` ❔ |
| [`structures/regn-map-grid.md`](structures/regn-map-grid.md) | `REGN` — a per-map spatial grid (and `MCOL` beside it) | ✅ CONFIRMED for the header, which self-checks on all 11 objects on |
| [`structures/regn-map-grid.md`](structures/regn-map-grid.md) | `REGN` — a stage's tetrahedral navigation mesh (and `MCOL` beside it) | ✅ CONFIRMED — tet mesh + face adjacency + portal costs + uniform grid; decoded from the file's own `POF0` fixup table |
| [`structures/savegame-format.md`](structures/savegame-format.md) | Save file (`savedata`) — container ✅ exact, 3 fields named ✅, rest ❔ (2026-08-11) | ✅ CONFIRMED for the container and the chunk layout — parsed off the |
| [`structures/sound-slb.md`](structures/sound-slb.md) | Sound bank audio — `sound.pak` / `.slb` / XMA1 | — |
| [`structures/stage-definition-table.md`](structures/stage-definition-table.md) | Stage definition table and the squadron (`UnitGroup`) roster | ✅ for the record vocabulary and the stage→table wiring; |

View File

@@ -1,8 +1,16 @@
# `REGN` — a per-map spatial grid (and `MCOL` beside it)
# `REGN` — a stage's tetrahedral navigation mesh (and `MCOL` beside it)
**Status: ✅ `CONFIRMED` for the header**, which self-checks on all 11 objects on
the disc. ❔ the four data sections are undecoded. **New to this corpus** — no
document mentioned `REGN`, `MCOL` or `hidden/MiscBin.pak` before 2026-08-24.
**Status: ✅ `CONFIRMED`.** A `REGN` object is a **tetrahedral navigation mesh
of the whole play volume** — vertices, tetrahedra, faces with full adjacency,
per-tetrahedron portal costs — plus a uniform grid for point location. The
decode is complete except for two small fields; see
[the 2026-08-26 section](#-2026-08-26--the-reader-found-regn-is-a-tetrahedral-navigation-mesh),
which supersedes the offsets in the older sections below (they were read
**16 bytes early** — the loader's fixup base is `chunk + 0x10`). The older
sections are kept because their refutations are still instructive.
**New to this corpus** — no document mentioned `REGN`, `MCOL` or
`hidden/MiscBin.pak` before 2026-08-24.
## Where it is
@@ -131,23 +139,6 @@ thing is indexed *by*.
partition and no more; the sections are unread. Treat this as the location of the
world's spatial data, not as the wave table.
> ❌ **SETTLED 2026-08-26 — `REGN` is not the wave scheduler, and cannot be.**
> It is a **tetrahedral navigation mesh**: vertices, faces with plane equations
> and adjacency, tetrahedra with portal costs between their face pairs, and a
> grid that indexes which tetrahedra fall in each cell. Every section is
> accounted for by that structure. There is nowhere in it for "what spawns, when,
> on what trigger" to live — no time field, no unit reference, no trigger.
>
> The hedge above was right to keep the reading provisional, but the reasoning
> it hedged ("a grid is what a scheduler would be indexed by") was a **guess from
> shape**, and the shape belonged to pathfinding. Worth noting because that guess
> is what pointed this whole investigation at `REGN` in the first place.
>
> The wave-scheduler search should now treat `REGN` as **excluded**, not as
> unread. The arrival timetable already found in `Route_S<NN>.tbl` — keyframed
> `(time, quat, pos)` per squadron per phase, with `t` in seconds — remains the
> only located part of that mechanism.
## ✅ Update: `REGN` is a stage's **MapPath**
The per-stage definition record (see
@@ -391,119 +382,338 @@ object in the executable and watch which fields it dereferences. That is static
PE work (`/work/*.pe`, offset = VA 0x82000000) of the same kind that cracked
the `.slb` packing phase, and it is the honest next step rather than a
twenty-first correlation.
---
## ⚠️ Open conflict with `auto/regn-reader` over the `+0x10` base
# ✅ 2026-08-26 — the reader found: `REGN` is a **tetrahedral navigation mesh**
**2026-08-26.** Branch `auto/regn-reader` decodes `REGN` as a **tetrahedral
navigation mesh** and reports the cell→geometry link solved, with strong checks
(faces passing through 3 of 4 tet vertices, 253 722/253 722; portal cost equal to
the distance between face centroids, 380 460/380 460). Its load-bearing structural
claim is that the `POF0` fixup base is **`chunk + 0x10`**, and therefore that
"every offset previously recorded on that page was read 16 bytes early" — offered
as the reason the ~20 correlation tests on this page returned chance.
The previous section closed with "find what reads a `REGN` object in the
executable and watch which fields it dereferences". That worked, and it did not
need the consumer: the **deserialiser** answers the question, because the file
carries its own pointer map and the deserialiser tells you how to read it.
**I could not reproduce that as stated, on the one thing here that is
independently checkable.** Re-reading the plane list at both bases:
## ✅ The reader — `sub_82465110` / `sub_82465138` / `sub_82465200`
| | records | unit normals |
|---|---|---|
| section at `chunk + offset` (what this page used) | 133 573 | **133 573 (100.00 %)** |
| section at `chunk + 16 + offset` | 133 573 | **0 (0.00 %)** |
Three functions, all in the resource module around `0x82460000`:
And the plane identity `n·p + d = 0` holds to float round-off at the **unshifted**
base. A 16-byte shift destroys it completely. So for *this* record the unshifted
reading is right, and the blanket statement is not.
```
sub_82465110 find_pof0(chunk)
82465110 lwz r11, 4(r3) ; datasize
82465118 add r11, r11, r3
82465120 addi r3, r11, 16 ; -> chunk + 16 + datasize
82465124 lwz r11, 0(r3)
82465114 lis r10, 0x504F / 8246511c ori r10, r10, 0x4630 ; 'POF0'
82465128 cmplw cr6, r11, r10
8246512c beqlr cr6 ; else return 0
```
🟡 **The likely reconciliation is bookkeeping, not disagreement.** The other
branch describes a 48-byte **face** record whose plane fields sit at a different
intra-record offset; `chunk+16` with the plane at `+0` addresses the same bytes as
`chunk+0` with the plane at `+16`, which is exactly what this page uses. If so
both readings are correct and only the origin convention differs — but that is a
guess, and I am not adopting either page's wording until it is checked.
```
sub_82465138 relocate_chunk_chain(chunk)
82465164 lbz r11, 8(r31) / clrlwi r11,r11,31 ; already-relocated bit
82465178 bl 0x82465110 ; find the POF0 chunk
82465194 addi r4, r11, 16 ; POF0 payload
82465198 lwz r5, 4(r11) ; POF0 payload size
8246519c addi r3, r31, 16 ; ← THE FIXUP BASE = chunk + 0x10
824651a0 bl 0x82465200
824651a8 ori r11, r11, 0x1 / stb r11, 8(r31) ; set the bit
```
**Also confirmed here, incidentally**: my section-0 point test passes at **100 %
at both bases**, so it never had the power to distinguish them. That test could
not have caught a 16-byte error and should not be cited as if it validated the
offsets.
```
sub_82465200 apply_pof0(base=r3, table=r4, size=r5)
82465224 clrrwi r8, r10, 6 ; top two bits of the lead byte select
82465228 cmplwi cr6, r8, 0x40 ; 0x40 → 6-bit delta, 1 byte
82465230 cmplwi cr6, r8, 0x80 ; 0x80 → 14-bit delta, 2 bytes
82465238 cmplwi cr6, r8, 0xC0 ; 0xC0 → 22-bit delta, 3 bytes
82465258 add r11, r10, r11 ; running WORD index, never reset
8246525c slwi r8, r11, 2
82465260 lwzx r10, r8, r3 ; slot = base[word]
82465264 cmplwi cr6, r10, 0x0
8246526c add r10, r10, r3 ; *slot += base (skipped when *slot == 0)
82465270 stwx r10, r8, r3
```
❔ Until the conflict is resolved, treat the plane/face layout on this page as
verified by its own arithmetic and the tetrahedral decode on `auto/regn-reader`
as verified by its own — and do not merge the two offset conventions without
re-running one check from each.
Two things follow, and both are load-bearing:
## ✅ RESOLVED — the base IS `chunk + 0x10`, and my section-0 offsets were wrong
1. **The fixup base is `chunk + 0x10`, not `chunk + 0`** (`82465198`:
`addi r3, r31, 16`). Every stored "offset" in a `REGN` file is relative to
file offset `0x10`, and every **record origin** on this page above is
therefore 16 bytes early.
2. **The `POF0` table is an exact list of which words are pointers.** It is not
a heuristic — it is the data the retail loader itself walks. Decoding it
gives the pointer graph directly, with no guessing.
**2026-08-26, same day.** The conflict above is settled, and it settles against
me on the boundary while leaving the plane arithmetic intact.
### 🔴 A correction to how I first stated point 1
**The plane fields were never in dispute.** For object `3506e972`, the other
branch's face record 0 begins at `0x1c700`, and its plane normal begins at the
same address; my `chunk + offset_at_0x78 + 16` gives `0x1c700` too. **Same bytes,
different bookkeeping** — exactly the reconciliation guessed above. The
`n·p + d = 0` result stands unchanged.
The first version of this section said "everything on this page above was read
16 bytes early", which is **too broad and I withdraw it**. For the plane fields
it is *false*: the older reading put the section at `chunk + offset` and the
normal at intra-record `+16`, which addresses **exactly the same bytes** as
`chunk + 0x10 + offset` with the normal at `+0`. Its unit-normal and
`n·p + d = 0` results were measuring real planes and stand unchanged. On
`3506e972`, both conventions put face record 0's normal at file offset
**`0x1c700` (116 480)** = `0x10 + 0x1c6f0`, where the header word at `0x78` is
`0x1c6f0`.
**The base is `chunk + 0x10`**, on evidence that has power where my tests did
not:
What is genuinely wrong is the **record boundary**, and it matters for one
reason: it decides which 48-byte record the four integer words belong to — and
those integer words are the face adjacency, i.e. precisely the thing the
correlation search was hunting for. It also mis-resolves every stored pointer
by 16 bytes, twice over along the cell → item → ref → tetrahedron chain, which
is the mechanical reason that chain never landed on anything.
* the loader does it: `8246519c addi r3, r31, 16`;
* at `+0x10` the six `POF0`-relocated slots land exactly on `0x70``0x84`, the six
section pointers. At `+0` they would land on `0x60``0x74` — relocating the
`u16` **counts** and leaving two section pointers unrelocated. Not merely
wrong, non-functional;
* section-0 record 0 reads as a bbox corner at `+0x10` and as garbage at `+0`.
### ✅ Which tests actually decide the origin — and which have no power
### ❌ And my point-list evidence was vacuous
Most of the numbers on this page are **content** tests and address the same
bytes under either convention, so they cannot settle the origin. Stated
plainly, because the same trap caught the point-in-bbox test:
I reported "13 467/13 467 section-0 points lie inside the bbox" as the check that
made section 0 a decode. Read at the correct base, record 0 of four objects is:
base +0 (1.4e-41, 5.3e-41, 0) (2.2e-40, 2.7e-40, 0)
base +16 (-50000,-50000,-50000) (-250000, 250000, -250000)
So my offsets were 16 bytes early throughout. **Only 11 of 13 467 points read as
denormal at the wrong base** — the rest were still plausible coordinates inside
the box, because a 16-byte shift within a packed array of `f32` triples yields
*other floats from the same array*. That is the general form of the failure and
it is worth stating plainly:
> **A containment test cannot detect a shift inside a homogeneous array.** The
> shifted values are drawn from the same distribution as the correct ones, so the
> test passes at 100 % either way. It is not a weak check — for this class of
> error it is no check at all.
The conclusion "section 0 is a point list" happens to be right. The evidence I
gave for it was not evidence.
**Adopt `chunk + 0x10`.** The plane *fields* need no change; the record
*boundary* moves, and the four integer words I described as "four zeros" move
with it into the record they describe — which is where the face adjacency, and
the answer to the cell→geometry question, was hiding. See branch
`auto/regn-reader` for the tetrahedral decode.
## ✅ Independent confirmation of the face decode
**2026-08-26.** I re-derived the other branch's central check with my own code,
my own reading of the record, and my own control — rather than accepting the
number.
Reading at base `chunk + 0x10`, taking the plane at intra-record `+0` and the
**three `u16` vertex indices at `+32`**, and asking whether each named vertex
satisfies its own face's plane equation:
| | |
| test | discriminates the base? |
|---|---|
| faces where **all three** named vertices lie on the plane | **133 573 / 133 573 (100.00 %)** |
| a **random** vertex lying on that plane (control) | 2 782 / 400 719 = **0.69 %** |
| section-5 targets on 96-byte boundaries; ref-array packing | ❌ no — `target start` cancels the base |
| face passes through 3 of 4 vertices; portal-cost identity | ❌ no — same absolute bytes either way |
| sphere reaches its cell (98.7 % vs 1828 %) | ❌ no for the base; it discriminates the **axis order** |
| points inside the bbox (the older section) | ❌ no — a denormal reads as ≈ 0, which is inside a ±250 km box |
| **the loader's own `addi r3, r31, 16`** | ✅ direct |
| **where `POF0` says the header pointers are** | ✅ at `+0x10` the six relocated slots are exactly `0x70``0x84`, the six section pointers. At `+0` they would be `0x60``0x74`: the loader would relocate the `u16` **counts** and would leave the pointers at `0x78``0x84` unrelocated. Not merely wrong — non-functional. |
| **what section-0 record 0 contains** | ✅ at `+0x10` a real vertex (`-250000, 250000, -250000` on `d4c89536`); at `+0` the two remaining header pointer words read as floats — `2.2e-40, 2.7e-40, 0` |
| **the section-2 record boundary** | ✅ see below, 100.000 % against 0.000 % |
A 100 % against a 0.69 % baseline is not a fit — the face record genuinely names
the vertices of its own plane, and the `+0x10` base together with this layout is
right.
### ✅ The record boundary, settled
That also settles the record-boundary question from my side: the `u16`s that
describe a plane sit **after** it in the same 48-byte record. My earlier reading
of "four zeros at the start of each record" was those integers seen 16 bytes out
of position, one record late.
A tetrahedron names face `f`; the plane at record `f` is independently verified
(three of the tet's four vertices lie on it, 100 %). The question is whether the
adjacency block that says *"I am face `f`, my tetrahedra are A and B, at their
face slots i and j"* sits in the **same** 48-byte record as that plane.
This is worth having in the corpus independently of the other branch: two
different implementations, two different guesses at the intra-record layout, the
same 100 %.
Requiring `own == f` **and** `tet[A].faces[i] == f`:
adjacency block taken from record f (base + 0x10) : 100.000 %
adjacency block taken from record f + 1 (base + 0) : 0.000 %
11 of 11 objects, every face, both sides. The plane and the four integer words
that describe it are one record, and that record starts at `chunk + 0x10 +
offset + 48·f`.
`sub_82465138` is reached from exactly two callers, `sub_82461018` (vtable slot
9 of the class at `0x820af8bc`, the pak/resource file class) and
`sub_82461DE8`; both store `chunk + 16` as the object's data pointer, which
confirms the same `+0x10` base from the other side.
### 🔴 …and the magic is never compared
Worth recording because it is what sent the search to the fixup table:
**`'REGN'` and `'MCOL'` are not constructed anywhere in the executable.** The
title builds its four-character tags as `lis`/`ori` pairs, and a sweep of every
such pair recovers 156 tags — `RATC`, `T8aD`, `XBG7`, `IPFB`, `IDXD`, `LSTA`,
`POF0`, `PRMD`, `TBMD`, `WMV3` … — but neither of these. Nor does either half
appear as an immediate anywhere: `0x474E` (`'GN'`) occurs **zero** times in
1 865 751 instructions, and the flat PE image contains the byte string `REGN`
**zero** times. So no magic-dispatch site exists to find; `.rgn` objects are
handed to the map code by the stage record, not identified by their tag.
## ✅ The header is 0x70 bytes at `chunk + 0x10`, with **six** sections
Corrected, and re-derived from the `POF0` table, which relocates exactly six
header words (`0x70`, `0x74`, `0x78`, `0x7c`, `0x80`, `0x84`) on 11 of 11:
```
chunk +0x00 char[4] 'REGN'
+0x04 u32 data size (POF0 chunk at +0x04-value + 0x10)
+0x08 u8 flags; bit0 = "already relocated" (set by sub_82465138)
data = chunk + 0x10:
+0x00 f32[4] bbox min (w = 1.0)
+0x10 f32[4] bbox max
+0x20 f32[4] extent
+0x30 f32[4] cell size
+0x40 u32[4] grid dims
+0x50 u16[6] record counts, one per section
+0x60 ptr[6] section pointers
```
The old page had four sections because it read the last two pointers as data.
There are six, their strides are `12, 96, 48, 8, 32, 4`, and each section's
span divided by its stride is its `counts[]` entry exactly — 11 of 11, with the
only slack being 16-byte alignment padding and, for the face list, **two
all-zero sentinel records** (which is the unexplained "constant 96-byte tail"
from the section above: 2 × 48).
| # | contents | stride | count |
|---|---|---|---|
| 0 | vertices | 12 | `counts[0]` |
| 1 | **tetrahedra** | 96 | `counts[1]` |
| 2 | **faces** (plane + adjacency) | 48 | `counts[2]` |
| 3 | cell index, one per cell | 8 | `counts[3]` = cells |
| 4 | cell items, one per **occupied** cell | 32 | `counts[4]` |
| 5 | tetrahedron references | 4 | `counts[5]` |
## ✅ How a cell reaches its geometry — the question, answered
The `POF0` table places every pointer in the file, and there are only four
kinds. Verified on all 11 objects by `tools/re-capture/regn_decode.py verify`:
* the six header words, and nothing else in the header;
* **one pointer per occupied cell**, at section-3 record offset **`+4`**
— so a cell is `{ u32 count; item* }` and the *count* word is not a pointer;
* **exactly one pointer per section-4 record, at offset `+0x14`** — the 32-byte
cell item is `{ …, u32 n @+0x10, tetref* @+0x14, … }`;
* **every word of section 5**, all `counts[5]` of them.
So the chain is:
```
position ─▶ cell (x,y,z) index = (z·dimY + y)·dimX + x
─▶ sec3[index] {count, item*}
─▶ item {…, n @+0x10, refs* @+0x14, …}
─▶ refs[0 .. n) each a pointer into section 1
─▶ tetrahedron
```
Four independent checks, all 11 of 11 objects:
* **section-5 targets land on section-1 record boundaries** — every one of
**261 000** pointers is `sec1 + 96·k` with zero remainder;
* **the reference arrays are packed contiguously in cell order** —
`item[i].refs == sec5 + 4·Σ item[j<i].n`, exact, and the total equals
`counts[5]`;
* `counts[4]` equals the number of relocated section-3 pointers equals the
number of occupied cells;
* **the cell ordering is x-fastest, then y, then z.** A tetrahedron's bounding
sphere reaches the box of a cell that lists it in **98.7 100 %** of all
261 000 references; transposing the axes — the natural control — drops that
to **18 28 %**.
The list is a strict subset of "every cell the bounding sphere touches"
(Jaccard ≈ 0.4 1.0), i.e. the build used a tighter test than sphere-vs-box.
That is expected and is not claimed as decoded.
## ✅ Section 1 is a **tetrahedron** — 96 bytes
```
+0x00 f32[3] bounding-sphere centre
+0x0c f32 bounding-sphere radius
+0x10 u16[4] vertex indices → section 0
+0x18 u16[4] face indices → section 2
+0x20 6 × { f32 cost; f32 ❔ } one pair per face pair, in the order
(0,1) (0,2) (0,3) (1,2) (1,3) (2,3)
+0x50 u32 the record's own index
+0x54 u8[4] flags, values 0/1
```
**The check that makes this a decode, not a reading**: each of a tetrahedron's
four faces must pass through exactly three of its four vertices. Over
**253 722** (tetrahedron, face) pairs on the disc, the count of vertices lying
on the plane is **3 in 100.00 % of cases**. The control — the same test with a
randomly chosen face of the same object — gives 3 in **0.07 2.2 %**, and
touches no vertex at all 96 % of the time.
Two more, both 11 of 11: the bounding sphere encloses all four vertices in
100 % of the 63 410 records (with ~0.1 % padding), and the word at `+0x50` is
the record's own index in 100 %.
### ✅ The six floats at `+0x20` are the portal-graph edge costs
For face `i` of a tetrahedron, let `omit(i)` be the one vertex not on it. Then
cost[k] == | V[omit(i)] V[omit(j)] | / 3 for the k-th pair (i<j)
which is exactly the **distance between the centroids of faces `i` and `j`**
the A\* step cost of crossing the tetrahedron from one portal to another. It
holds to `1e-4` relative in **380 460 of 380 460** values, all 11 objects.
❔ The second float of each pair is unread. It is a genuine varied float, zero
in 8 26 % of slots. **🔴 Refuted**: "zero marks a hull edge" — `P(zero)` is
the same for edges on the hull as for interior ones (e.g. 2.9 % / 16.4 %
against 16.7 % / 64.0 % — no lift at all). A clearance width is the obvious
reading and is *not* established.
## ✅ Section 2 is a **face** — a plane plus the adjacency graph
```
+0x00 f32[3] unit normal
+0x0c f32 d
+0x10 f32[3] a point on the plane
+0x1c f32 1.0
+0x20 u16[3] the face's three vertex indices → section 0
+0x26 u16 the face's own index
+0x28 u16[2] the tetrahedra on either side; 0xFFFF = hull (no neighbour)
+0x2c u16[2] which of that tetrahedron's four face slots this is
```
The old page's plane reading was the same 16 bytes early, which is why it
reported "four zeros" at the front: those were the *previous* record's four
integer words, read as floats.
Verified over every face of every object (11 of 11, 100.00 % each):
* the word at `+0x26` is the record's own index;
* for each non-`0xFFFF` side, `tet[side].faces[slot[side]] == this face`;
* the face's three vertices are a subset of that tetrahedron's four.
And the Euler relation closes: `4·T == 2·(F B) + B` exactly on every object
(e.g. `4 × 1172 = 4688`, `F = 2584`, `B = 480` hull halves).
## ✅ What a `REGN` object is
A stage's `MapPath` is a **tetrahedral navigation mesh of the whole play
volume**, plus a uniform grid for point location:
* vertices, tetrahedra, and faces form a conforming tet mesh with full
face adjacency and hull markers;
* each tetrahedron carries the six face-to-face traversal costs, so A\* over
the portal graph needs no geometry at query time;
* the uniform grid answers "which tetrahedra could contain this point" in O(1),
which is what a flight game needs to localise a ship into the mesh each frame.
The 100 km-cube smoke-test map (`e993b93e`, `counts = [8, 6, 18, …]`) is the
textbook **six-tetrahedron decomposition of a cube** — 8 corners, 6 tets, 18
distinct faces (24 faces less the 6 shared internally) — which is as strong a
confirmation of the reading as the statistics are.
## 🔴 Corrections to the sections above, kept rather than deleted
Every one of these was a real observation; each fails for the same single
reason, and that is worth having on the page.
| earlier claim | status |
|---|---|
| "four section offsets at `0x70`" | 🔴 there are **six**, `0x70``0x84` |
| "the first section offset is always `0x80`" | 🔴 the *value* is `0x80`; the section is at `0x90` — the base is `+0x10` |
| "`counts[4]` = occupied cells **+ 2**, unexplained" | 🔴 artefact of the base error; `counts[4]` = occupied cells exactly |
| "the constant 96-byte tail after the plane list" | ✅ explained: two all-zero sentinel face records |
| "record `+0x1c` is a bounding-sphere radius (ratio 1.001 on one object, 0.130.27 on the rest)" | 🟡 **half right**`+0x0c` *is* a bounding-sphere radius, of the *tetrahedron*; the 1.001 was a coincidence of that map, where every tet shares the cube's circumsphere |
| "`(count, offset)` pairs point at leaf arrays of `count × 4` bytes" | 🟡 **right mechanism, wrong record** — it is the cell *item* at `+0x10`/`+0x14` pointing into section 5, not the cell index |
| "slots 811 are integers (denormal as float)" | ✅ correct, and they are the four vertex + four face `u16` indices |
| "section 2 records are 48 bytes, unit normal, `n·p + d = 0` in 133 573/133 573" | ✅ **stands** — the same bytes under either origin convention; only the record boundary moves, see the correction above |
| "section 0 points are inside the bbox, 13 467/13 467" | ⚠️ true but **uninformative** — it passes at either origin, because a denormal reads as ≈ 0 and 0 is inside the box |
| "slot 7 is a local scale, ~14× smaller than the inter-node distance" | ✅ correct as measured — it is a *tetrahedron's* bounding radius, which is exactly that small |
| "the BVH reading is refuted, no node sphere contains another" | ✅ stands, and is now explained: these are sibling tetrahedra, not a tree |
| "the cell payload holds section-1 indices" | ✅ stands — it holds a **pointer to an array of pointers**, and no index anywhere |
| "the static-correlation avenue is exhausted" | ✅ stands for correlations; the file's own fixup table was never a correlation |
## ❔ Still open
* the second float of each portal pair at tetrahedron `+0x24`, `+0x2c`, … ;
* the four flag bytes at tetrahedron `+0x54` (values 0/1);
* the exact predicate the tools used to decide cell membership (tighter than
sphere-vs-box);
* **the runtime consumer.** The deserialiser is found and proven; the code that
*queries* the grid is not. Searches that came up empty, so they are not
repeated: functions loading the header field groups off one base register
(only stack frames match); `vctsxs`/`vctuxs` float→int conversion (16
functions, only one outside the XDK ranges); and the magic-comparison route,
which cannot exist (above). The likely reason the field-offset search fails
is that a `float4`-aligned header is read with VMX loads, which carry no
useful displacement signature.
## Tooling
`tools/re-capture/regn_decode.py` — standalone, stdlib only.
```
regn_decode.py list hidden/MiscBin.pak
regn_decode.py dump hidden/MiscBin.pak [name_hash]
regn_decode.py verify hidden/MiscBin.pak # every check quoted above
```
`verify` prints, per object: the `POF0` pointer-slot shape, the reference-array
packing, the face/vertex incidence with its random control, the sphere/cell
agreement with its transposed-axis control, and the portal-cost identity.