re: the ScriptPhase vtable is 113 slots, and there is a SilphScriptPhaseDemo
RTTI at vtable[-1] names the class .?AVSilphScriptPhase@silph@@ and the next class's COL at 0x820A8680 terminates the table at 113 entries. The earlier ">=200 slots" reading used a bad terminator -- scanning for a non-code word runs straight into the next vtable. Cross-check: the 147 built-in stubs use 109 distinct slots, min 0 max 110. The next vtable is .?AVSilphScriptPhaseDemo@silph@@, also 113 slots, overriding 109 of them with mostly one shared stub -- a cut-down phase. That explains why sub_822710D0 has two callers: sub_82263408 and sub_82275800 are the two classes' Update, both slot 111. Both "unread spawner callers" are placed: sub_82264058 is slot 0 of SilphScriptPhase and sub_82273910 is slot 0 of the Demo class -- the two destructors. Also records a negative: the Stage%02d construction site I proposed last iteration does not exist (no stage format string among the 43 short %d strings; no precomputed name hash anywhere), so the stage-index lead cannot be settled that way. Docs only; artefacts byte-identical.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user