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:
Sylpheed RE agent
2026-08-27 10:05:01 +00:00
parent 084ce60bf7
commit cbf54c9d21
3 changed files with 95 additions and 5 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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