re: X+12 is a 0-based STAGE INDEX -- proved by the game's own string table

Three iterations circled this.  Following the value rather than the
filename settles it in two hops:

  1. SilphScriptPhase's ctor sub_8225FEF8 never touches r7 (zero
     mentions).  The BASE ctor sub_822700C0 keeps it:
       or  r26, r7, r7   ->   stw r26, 152(r30)
  2. sub_82261F70 indexes a stack table of string pointers with that
     field.  The table is contiguous at 0x820A8880, 20 bytes/entry:
       index 0 = STAGE01_UNIT_MAX ... index 32 = STAGE33_UNIT_MAX,
       then PLANE at 33.

So X+12 = [phase+152] = a 0-based stage index, N -> STAGE(N+1), and the
gate is explained rather than described: <= 32 is the array bound (33
entries) and != 16 is STAGE17, the one stage number with no .ssb.

This retracts my own "not adopted": the stage-index reading was 1-of-1
but coincidence-shaped two iterations ago; 0-based is now PROVED by
index 5 -> STAGE06.

Caught a false positive: 0x82272D88 lwz r11, 152(r11) in the built-in
switch is a virtual call to slot 38 -- r11 is the vptr, not the phase.

Also names a third class: sub_822700C0 stamps 0x820A8E44 =
SilphScriptPhaseBase, so the hierarchy is Base <- ScriptPhase and
Base <- Demo.

Docs only; artefacts byte-identical.
This commit is contained in:
Sylpheed RE agent
2026-08-27 11:05:50 +00:00
parent 4feab6c579
commit 17d1d2c999
3 changed files with 110 additions and 0 deletions

View File

@@ -179,6 +179,27 @@ 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]`.
### ✅ There are THREE classes, not two — the base is `SilphScriptPhaseBase`
`sub_8225FEF8` opens by calling `sub_822700C0`, which stamps **its own** vptr
`0x820A8E44`; `sub_8225FEF8` then overwrites it with `0x820A84BC`. That *is* the
derived-constructor pattern, on one object. `0x820A8E44`'s COL gives
**`.?AVSilphScriptPhaseBase@silph@@`**, and it is **113 slots** like the other two.
```
silph::SilphScriptPhaseBase 0x820A8E44 ctor sub_822700C0
├── silph::SilphScriptPhase 0x820A84BC ctor sub_8225FEF8
└── silph::SilphScriptPhaseDemo 0x820A8684 ctor inlined in sub_82260568
```
The Demo object is built by calling the **base** ctor and stamping the Demo vptr,
which is why `sub_822700C0` appears on both paths — and why the two objects are
separate allocations rather than one.
🔑 The base ctor is also what stores the load's kind argument
(`or r26, r7, r7` → `stw r26, 152(r30)`). That field is now identified as a
**0-based stage index** — see [isl-condition-builtins](isl-condition-builtins.md).
## ✅ What the Demo vtable's overrides actually are: no-ops
The 109 overridden slots point at three shared stubs, and all three do nothing: