From b9643c63f6b6b1abb3f986c7f4eaf45d0e32d396 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Wed, 26 Aug 2026 07:53:13 +0000 Subject: [PATCH] =?UTF-8?q?re:=20REGN=20is=20a=20tetrahedral=20navigation?= =?UTF-8?q?=20mesh=20=E2=80=94=20the=20cell=E2=86=92geometry=20link,=20dec?= =?UTF-8?q?oded?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found the reader. It is the deserialiser, not a consumer, and it answers the question twice: the fixup base is chunk+0x10 (82465198 addi r3,r31,16), so every offset previously recorded was read 16 bytes early — which is why twenty correlation tests sat at chance — and the POF0 table names every pointer word in the file. Six sections, not four. cell {count,item*} → item {n@+0x10, refs*@+0x14} → array of pointers into section 1 → a 96-byte tetrahedron. Section 2 is a face: plane, its three vertices, the two tetrahedra either side (0xFFFF = hull) and their face slots. All 11 objects: face passes through exactly 3 of its tet's 4 vertices in 253 722/253 722 (random control 0.07–2.2 %); portal cost == face-centroid distance in 380 460/380 460; sphere reaches its cell 98.7–100 % vs 18–28 % with transposed axes. Refuted and kept: 'REGN'/'MCOL' are never built as constants in the executable (0x474E occurs zero times in 1.87 M instructions), so no magic-dispatch site exists; and "zero portal-pair float marks a hull edge" shows no lift at all. Still open: the runtime consumer of the grid, the second portal float, and the four flag bytes at tetrahedron +0x54. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE --- docs/re/BACKLOG.md | 21 ++ docs/re/INDEX.md | 2 +- docs/re/structures/regn-map-grid.md | 299 +++++++++++++++++++++++++++- 3 files changed, 316 insertions(+), 6 deletions(-) diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index c0253eba..f6b28681 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -359,6 +359,27 @@ search cannot find a *schedule*. index-shaped field in the cell payload has been followed into all three sections and checked for spatial agreement against controls — nothing above chance. Settling it needs the PE code that reads `REGN`, not more correlation. + ✅✅ **2026-08-26 — SETTLED, and `REGN` is a tetrahedral navigation mesh.** The + reader is the deserialiser `sub_82465110`/`sub_82465138`/`sub_82465200`, and it + gives the answer twice over: the fixup base is **`chunk + 0x10`** + (`82465198 addi r3, r31, 16`), so **every offset above was read 16 bytes + early** — which is why twenty correlation tests returned chance — and the + `POF0` table is an exact list of which words are pointers, so the pointer + graph needs no guessing. There are **six** sections, not four. A cell holds + `{count, item*}`; the item holds `{n @+0x10, refs* @+0x14}`; `refs` is an + array of pointers into section 1, which is a **96-byte tetrahedron** + (bounding sphere, 4 vertex indices, 4 face indices, 6 face-pair portal costs). + Section 2 is a **face**: plane + its 3 vertices + the two tetrahedra either + side (`0xFFFF` = hull) + their face slots. Checks, all 11 objects: each face + passes through exactly 3 of its tetrahedron's 4 vertices in **253 722/253 722** + (random control 0.07–2.2 %); portal cost == face-centroid distance in + **380 460/380 460**; a listed tetrahedron's sphere reaches its cell in + 98.7–100 % against 18–28 % for transposed axes. 🔴 Also recorded: **`'REGN'` + and `'MCOL'` are never built as constants in the executable** — `0x474E` + occurs zero times in 1.87 M instructions — so no magic-dispatch site exists. + ❔ Still open: the runtime *consumer* of the grid, the second float of each + portal pair, and the 4 flag bytes at tetrahedron `+0x54`. Tool: + `tools/re-capture/regn_decode.py`. See [`structures/regn-map-grid.md`](structures/regn-map-grid.md). * 🔴 **`hidden/DefTables.pak` is NOT it** (checked 2026-08-24). The three unnamed schemas are more **model/render** tables in the same vocabulary as the diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index c5e1cd48..fd120640 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -101,7 +101,7 @@ files, which is how the same ground got covered twice. | [`structures/isl-message-dialogue-link.md`](structures/isl-message-dialogue-link.md) | Mission scripts as dialogue — built-in 64 -> message id -> caption text | ✅ CONFIRMED total, 2 683/2 683 call sites across all 28 stages resolve, no residue | | [`structures/mission-objective-counter.md`](structures/mission-objective-counter.md) | `REMAINING OB` — the mission's own objective counter, in RAM | ✅ CONFIRMED for one Stage 02 run: a big-endian u32 whose value | | [`structures/movie-subtitles.md`](structures/movie-subtitles.md) | Movie subtitles & the movie ↔ mission ↔ text chain | — | -| [`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; | diff --git a/docs/re/structures/regn-map-grid.md b/docs/re/structures/regn-map-grid.md index 94821e96..ee8401ff 100644 --- a/docs/re/structures/regn-map-grid.md +++ b/docs/re/structures/regn-map-grid.md @@ -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 @@ -373,4 +381,285 @@ nothing rises above chance. 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. \ No newline at end of file +twenty-first correlation. +--- + +# ✅ 2026-08-26 — the reader found: `REGN` is a **tetrahedral navigation mesh** + +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. + +## ✅ The reader — `sub_82465110` / `sub_82465138` / `sub_82465200` + +Three functions, all in the resource module around `0x82460000`: + +``` +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 +``` + +``` +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 +``` + +``` +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 +``` + +Two things follow, and both are load-bearing: + +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`. **Everything on this page above was read 16 bytes early.** + That single error is why twenty correlation tests returned chance. +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. + +`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