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