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:
@@ -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
|
||||
|
||||
@@ -144,3 +144,65 @@ callers of the spawner. They are a **matched pair**:
|
||||
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.
|
||||
|
||||
## ✅ (2026-08-27) A script load builds TWO phase objects, not one
|
||||
|
||||
Each vtable constant is materialised at exactly **two** sites — its constructor
|
||||
and its destructor — and `vptr_writes` agrees:
|
||||
|
||||
| class | ctor | dtor |
|
||||
|---|---|---|
|
||||
| `SilphScriptPhase` | `sub_8225FEF8` (`0x8225FF28`) | `sub_82261B60` |
|
||||
| `SilphScriptPhaseDemo` | `sub_82260568` (`0x82260660`) | `sub_82260FF8` |
|
||||
|
||||
⚠️ **A wrong turn worth recording.** `sub_8225FEF8`'s *only* caller is
|
||||
`sub_82260568`, which looks exactly like "base ctor called from derived ctor" —
|
||||
i.e. **every phase is a Demo**. That would make the built-in dispatch inert,
|
||||
since the Demo vtable no-ops almost everything. Reading the constructor kills it:
|
||||
`sub_82260568` **allocates and builds two separate objects.**
|
||||
|
||||
```
|
||||
822605C8 bl 0x82150EF8 ; allocate 10216 bytes <- object A
|
||||
822605EC bl 0x8225FEF8 ; SilphScriptPhase ctor on A
|
||||
…
|
||||
82260658 bl 0x822700C0 ; the sibling initialiser on B
|
||||
82260660 addi r11, r11, -31100 ; 0x820A8684
|
||||
82260664 stw r11, 0(r29) ; Demo vptr onto B
|
||||
82260690 stw r29, 8(r30)
|
||||
```
|
||||
|
||||
Both get the same four arguments. They land at **`Y+4` (the main phase)** and
|
||||
**`Y+8` (the demo phase)** — which is why the load path read earlier resolves the
|
||||
ScriptPhase through `Y+4`. *A constructor calling another class's constructor is
|
||||
not proof of inheritance; check which object each vptr lands on.*
|
||||
|
||||
📏 The allocation is **10216 bytes**, consistent with the highest field this
|
||||
corpus has read, `[phase+10160]`.
|
||||
|
||||
## ✅ What the Demo vtable's overrides actually are: no-ops
|
||||
|
||||
The 109 overridden slots point at three shared stubs, and all three do nothing:
|
||||
|
||||
```
|
||||
8226C160 stw r11(=0), 164(r3) ; special[0] = 0 ; return
|
||||
822748B8 stw r10(=0), 164(r11) ; r3 = 0 ; same, and return code 0
|
||||
822748A8 lfd f0, 25600(0x820B) ; stfd f0, 176 ; [phase+176] = 0.0
|
||||
```
|
||||
|
||||
It keeps only four real methods: slot 15 → `sub_82391BA8`, **slot 111 `Update` →
|
||||
`sub_82275800`**, slot 112 → `sub_8237EF08`, and its destructor.
|
||||
|
||||
🟡 **What a "Demo" phase is — 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>` — see
|
||||
[isl-message-dialogue-link](isl-message-dialogue-link.md) and the movie↔subtitle
|
||||
work), so "demo" means *cutscene* in this codebase. A second phase object,
|
||||
built from the same script load, with every gameplay built-in stubbed to a no-op
|
||||
and only `Update` alive, reads as **the cutscene script runner**. Stated as a
|
||||
reading: nothing here shows it executing cutscene bytecode.
|
||||
|
||||
🔑 **A new handle on the stalled `X+12` question.** The Demo class's single
|
||||
construction site is inside `sub_8225EC78` (the script load):
|
||||
`sub_82260568(Y, r26, [X+12])` at `0x8225ED5C` — the kind is passed **straight
|
||||
into the constructor as its third argument**, alongside `[r30+12]` again at
|
||||
`0x822605E8` and `0x82260654`.
|
||||
|
||||
Reference in New Issue
Block a user