re: a script load builds TWO phase objects, main at Y+4 and demo at Y+8

sub_8225FEF8's only caller is sub_82260568, which looks like a derived
ctor calling its base -- i.e. every phase is a Demo, which would make
the built-in dispatch inert.  Reading the constructor refutes it: it
allocates 10216 bytes and runs SilphScriptPhase's ctor on object A,
then initialises a second object B and stamps the Demo vptr on it.
They land at Y+4 and Y+8, which is why the load path resolves the
ScriptPhase through Y+4.

The Demo class's 109 overrides all point at three no-op stubs; only
slot 15, slot 111 (Update = sub_82275800), slot 112 and the destructor
survive.  DEMO is the corpus's established cutscene text family, so the
cutscene-runner reading is recorded as a reading, not a result.

Also notes a new handle for the stalled X+12 question: the kind is
passed straight into sub_82260568 as its third argument.

Docs only; artefacts byte-identical.
This commit is contained in:
Sylpheed RE agent
2026-08-27 10:14:00 +00:00
parent 044b98dcc4
commit 4feab6c579
2 changed files with 95 additions and 0 deletions

View File

@@ -534,6 +534,39 @@ unknown, what evidence exists, and what the first step would be. Move an item in
🟡 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.
* ✅⚠️ **(2026-08-27) A SCRIPT LOAD BUILDS **TWO** PHASE OBJECTS — and the tidy
"every phase is a Demo" reading is killed by reading the constructor.
[structures/isl-builtin-dispatch](structures/isl-builtin-dispatch.md).**
Each vtable constant is materialised at exactly two sites (ctor + dtor), and
`vptr_writes` agrees: `SilphScriptPhase` ← `sub_8225FEF8` / `sub_82261B60`;
`SilphScriptPhaseDemo` ← `sub_82260568` / `sub_82260FF8`.
⚠️ **The wrong turn.** `sub_8225FEF8`'s *only* caller is `sub_82260568` — which
looks exactly like a derived ctor calling its base, i.e. **every phase is a
Demo**. That would make the whole built-in dispatch inert, since the Demo
vtable no-ops nearly everything. Reading the constructor refutes it:
`sub_82260568` **allocates and builds two SEPARATE objects** — `sub_82150EF8`
for **10216 bytes** then `sub_8225FEF8` on object A, and `sub_822700C0` +
`stw 0x820A8684, 0(r29)` on object B — stored at **`Y+4` (main)** and
**`Y+8` (demo)**. That is exactly why the load path resolves the ScriptPhase
through `Y+4`. 📏 10216 bytes is consistent with the highest field read here,
`[phase+10160]`. 🔑 **Method note: a constructor calling another class's
constructor is not proof of inheritance — check which object each vptr lands
on.**
✅ **The Demo overrides are no-ops.** All 109 point at three shared stubs, and
every one does nothing: `0x8226C160` (`special[0] = 0`), `0x822748B8` (same,
return 0), `0x822748A8` (`[phase+176] = 0.0`). Only four real methods survive:
slot 15 `sub_82391BA8`, **slot 111 `Update` `sub_82275800`**, slot 112
`sub_8237EF08`, and the destructor.
🟡 **What "Demo" means — a reading from the corpus's own vocabulary.** `DEMO` is
one of the eight **cutscene** text families already decoded here
(`MSG_DEMO_<id>_<page>_<line>`), so a second phase built from the same load with
every gameplay built-in stubbed and only `Update` alive reads as **the cutscene
script runner**. Nothing here shows it executing cutscene bytecode, so it stays
a reading.
🔑 **New handle on the stalled `X+12` domain**: the Demo class's single
construction site is in `sub_8225EC78` — `sub_82260568(Y, r26, [X+12])` at
`0x8225ED5C`. The kind goes **straight into the constructor as arg 3**.
## ✅✅ SOLVED — the mission freeze was a modal sign-in dialog (2026-08-26)
`XamShowSigninUI` opens a modal dialog and `xeXamDispatchDialog` blocks the