diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index b8ad18d1..2a9b98de 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -502,6 +502,38 @@ unknown, what evidence exists, and what the first step would be. Move an item in found this pass, and `grep`ping the extracted disc does not help — the pak names are hashed, so neither `Stage16` nor `Stage17` appears as a plain token. +* ✅✅🔴 **(2026-08-27) THE VTABLE IS 113 SLOTS, THE CLASS IS + `silph::SilphScriptPhase`, THERE IS A SECOND ONE — and the test I proposed last + iteration does not exist. [structures/isl-builtin-dispatch](structures/isl-builtin-dispatch.md).** + 🔴 **Dropped first, honestly:** the plan was to settle the stage-index lead at + the `Stage%02d` construction site. **There isn't one.** The `'%s%02d'` at + `0x820A9C8C` has one xref into a generic string helper; listing *every* short + `%d` string in the image (43) finds no stage pattern; and the `name_hash` of + `Stage_S00…30` / `Stage00…30` / `UnitGroup_S00…30` appears **nowhere as a 4-byte + word**. The executable never builds a stage script name — the mapping is on the + data side. **Do not retry this route.** + ✅ **Vtable LENGTH settled: 113 slots (0…112).** The RTTI locator at + `vtable[-1]` = `0x820A84B8` → type descriptor `0x8289CD18` = + **`.?AVSilphScriptPhase@silph@@`**. ⚠️ The earlier "≥200 slots, no non-code word + in the first 200" was a **bad terminator** — the scan ran into the next vtable. + The real terminator is the next class's COL at `0x820A8680`. Independent + cross-check: the 147 built-in stubs use **109 distinct slots, min 0, max 110** — + all inside 0…112. + ✅ **A SECOND class: `silph::SilphScriptPhaseDemo`** (`0x8289CCC0`), vtable + `0x820A8684`, also 113 slots, **overriding 109 of them** — nearly all pointing at + one shared stub `0x8226C160`. The Demo phase implements almost none of the script + surface, keeping slot 15 → `sub_82391BA8`, slot 111 `Update` → `sub_82275800`, + slot 112 → `sub_8237EF08`. + 🔑 **That closes a loose end from the timers work**: `sub_822710D0` (the + stopwatch advance) has two callers, `sub_82263408` and `sub_82275800` — they are + the two classes' `Update`, same slot 111, base and derived. + ✅ **And both "unread spawner callers" are placed** — `sub_82264058` is + `SilphScriptPhase` **slot 0** and `sub_82273910` is `SilphScriptPhaseDemo` + **slot 0**: the two scalar deleting destructors, a matched pair rather than two + mysteries. 🟡 Why a destructor reaches the spawner is not read. + 🟡 Still open: `X+12`'s domain — the one remaining static handle is another hop, + to whatever the GamePartTask manager passes into `GamePart_MainGame` slot 4. + ## ✅✅ SOLVED — the mission freeze was a modal sign-in dialog (2026-08-26) `XamShowSigninUI` opens a modal dialog and `xeXamDispatchDialog` blocks the diff --git a/docs/re/structures/isl-builtin-dispatch.md b/docs/re/structures/isl-builtin-dispatch.md index ae035728..132c2343 100644 --- a/docs/re/structures/isl-builtin-dispatch.md +++ b/docs/re/structures/isl-builtin-dispatch.md @@ -100,5 +100,47 @@ clear condition in the corpus. through the decode, which `isl.py` does not do. * **The 35 non-vtable built-ins**, and `op21`/`op22`'s generic deque helpers (`0x82175C20`, `0x82274BA0`), were not read — only their arguments. -* **The vtable's length.** `0x820A84BC` is the base and slot 404 is in use, so it - is at least 102 entries; I did not establish where it ends. +* ~~**The vtable's length.**~~ ✅ **SETTLED (2026-08-27): 113 slots (0…112).** + +## ✅ (2026-08-27) The class is `silph::SilphScriptPhase`, the vtable is 113 slots, and there is a SECOND one + +The MSVC RTTI locator sits at `vtable[-1]`. At **`0x820A84B8`** it is `0x8210DDC0`, +whose type descriptor `0x8289CD18` spells **`.?AVSilphScriptPhase@silph@@`** — so +the class is named, not inferred. + +The vtable **ends at `0x820A867C`, 113 entries**. The terminator is not "a +non-code word" — a scan on that rule runs straight past the end, which is why an +earlier pass reported "≥200 slots, no non-code word in the first 200". The real +terminator is **the next class's COL**: `0x820A8680` holds `0x8210DD74`, and a +second vtable begins at `0x820A8684`. + +**Cross-check, independent of the RTTI:** the 147 built-in stubs between them +reference **109 distinct slots, minimum 0, maximum 110** — every one inside +0…112. A wrong length would have stubs pointing past the end. + +### The second class: `silph::SilphScriptPhaseDemo` + +`0x8210DD74` → type descriptor `0x8289CCC0` = **`.?AVSilphScriptPhaseDemo@silph@@`**, +vtable at `0x820A8684`, **also 113 slots**. It **overrides 109 of the 113**, and +the overwhelming majority point at a single shared stub, `0x8226C160`. So the Demo +phase implements almost none of the script surface — it keeps a handful of real +methods (slot 15 → `sub_82391BA8`, slot 111 `Update` → `sub_82275800`, +slot 112 → `sub_8237EF08`) and stubs the rest. + +🔑 That explains a loose end from [isl-timers](isl-timers.md): the stopwatch +advance `sub_822710D0` has **two** callers, `sub_82263408` and `sub_82275800`. +They are the two classes' `Update` methods — same slot 111, base and derived. + +### And both "unread spawner callers" are placed + +`isl-coroutine-spawner.md` lists `sub_82273910` and `sub_82264058` as unread +callers of the spawner. They are a **matched pair**: + +| | slot | class | +|---|---|---| +| `sub_82264058` | **0** | `SilphScriptPhase` | +| `sub_82273910` | **0** | `SilphScriptPhaseDemo` | + +Slot 0 in this ABI is the scalar deleting destructor, so these are the two +classes' teardown paths — not two separate mysteries. 🟡 *Why* a destructor +reaches the spawner is not read here. diff --git a/docs/re/structures/isl-condition-builtins.md b/docs/re/structures/isl-condition-builtins.md index f3c72574..0934bdc5 100644 --- a/docs/re/structures/isl-condition-builtins.md +++ b/docs/re/structures/isl-condition-builtins.md @@ -215,9 +215,25 @@ Compare the controls honestly: | stage index (this) | **1** — Stage17 | 1 value | 1-of-1 | That is a much tighter fit, and it is *still* coincidence-shaped. **Not adopted.** -What would settle it: the `Stage%02d` construction site — the image has a generic -`'%s%02d'` formatter at `0x820A9C8C`, and whether it is fed `index` or -`index + 1` decides the question outright. Not found this pass. + +### 🔴 (2026-08-27) The test I proposed for it DOES NOT EXIST + +The plan was to find the `Stage%02d` construction site and see whether it is fed +`index` or `index + 1`. Two searches say there is no such site: + +* **No stage-shaped format string.** The `'%s%02d'` at `0x820A9C8C` has exactly + **one** xref, into `sub_822929E0`, a generic string helper. Listing *every* + short `%d`-bearing string in the image — 43 of them — turns up no + `Stage`/`_S%02d` pattern at all. +* **No precomputed name hash.** Using the corpus's own `name_hash`, the values for + `Stage_S00…30`, `Stage00…30`, `StageNN.ssb` and `UnitGroup_S00…30` appear + **nowhere in the image as a 4-byte word**. + +So the executable never builds a stage script's name; the mapping lives on the +data side. **The stage-index lead cannot be settled this way**, and the next pass +should not retry it. The one remaining static handle is another hop up: `X+12` +arrives as `[r21+8]` in `GamePart_MainGame` vtable slot 4, so it comes from +whatever the GamePartTask manager passes when it switches parts. 🟡 Its neighbours belong to the same cluster: `builtin103` reads `[phase+10156]` and `[phase+10152]` (9 and 7 writers), and a sibling vtable stub