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:
@@ -567,6 +567,40 @@ unknown, what evidence exists, and what the first step would be. Move an item in
|
||||
construction site is in `sub_8225EC78` — `sub_82260568(Y, r26, [X+12])` at
|
||||
`0x8225ED5C`. The kind goes **straight into the constructor as arg 3**.
|
||||
|
||||
* ✅✅ **(2026-08-27) SETTLED — `X+12` IS A 0-BASED STAGE INDEX. The gate is a
|
||||
bounds check on a 33-entry table, and `!= 16` is Stage17.
|
||||
[structures/isl-condition-builtins](structures/isl-condition-builtins.md).**
|
||||
Three iterations circled this. Following the *value* rather than the filename
|
||||
does 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` builds a
|
||||
string-pointer table on its stack and indexes it with exactly that field
|
||||
(`lwz r11, 152(r21)`; `rlwinm ×4`; `lwzx r4, r11, r10` where `r10 = r31+144`).
|
||||
The table is contiguous at **`0x820A8880`**, 20 bytes/entry:
|
||||
**index 0 = `STAGE01_UNIT_MAX` … index 16 = `STAGE17_UNIT_MAX` … index 32 =
|
||||
`STAGE33_UNIT_MAX`**, then `PLANE` at 33.
|
||||
⇒ **`X+12` = `[phase+152]` = a 0-based stage index, `N → STAGE(N+1)`** — and the
|
||||
gate is now explained rather than described: **`<= 32` is the array bound**
|
||||
(33 entries, 0…32) and **`!= 16` is STAGE17**, the one stage number with no
|
||||
`.ssb` on the disc.
|
||||
✅ **Retracts my own "not adopted".** Two iterations ago the stage-index reading
|
||||
scored 1-of-1 but was coincidence-shaped and deliberately not believed. It is
|
||||
now read off the game's own strings, and **0-based is PROVED by index 5 →
|
||||
`STAGE06`**, not assumed.
|
||||
⚠️ **False positive caught**: `0x82272D88 lwz r11, 152(r11)` in the built-in
|
||||
switch is not this field — `r11` had just been loaded from `0(r31)`, so it is
|
||||
the **vptr**, and the instruction is a virtual call to **slot 38**. Offset 152
|
||||
recurs; the base register decides.
|
||||
✅ **And a THIRD class** — [structures/isl-builtin-dispatch](structures/isl-builtin-dispatch.md).
|
||||
`sub_822700C0` stamps its own vptr `0x820A8E44` before `sub_8225FEF8` overwrites
|
||||
it: **`.?AVSilphScriptPhaseBase@silph@@`**, also 113 slots. So the hierarchy is
|
||||
`SilphScriptPhaseBase` ← `SilphScriptPhase` and ← `SilphScriptPhaseDemo`, and
|
||||
the Demo object is built by calling the **base** ctor then stamping the Demo
|
||||
vptr — which is why `sub_822700C0` shows up on both construction paths.
|
||||
🟡 Not settled: what `STAGENN_UNIT_MAX` resolves to (the lookup goes through
|
||||
`sub_824480D0` / `sub_82448AA0`), and whether `Stage30`–`Stage33` exist on the
|
||||
disc at all — the table has 33 slots but only 28 scripts were extracted.
|
||||
|
||||
## ✅✅ SOLVED — the mission freeze was a modal sign-in dialog (2026-08-26)
|
||||
|
||||
`XamShowSigninUI` opens a modal dialog and `xeXamDispatchDialog` blocks the
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -235,6 +235,61 @@ 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.
|
||||
|
||||
## ✅✅ (2026-08-27) SETTLED — `X+12` IS A 0-BASED STAGE INDEX, and the game's own strings prove it
|
||||
|
||||
Following the *value* instead of the filename does it in two hops.
|
||||
|
||||
**Hop 1 — where it is stored.** `sub_82260568` passes the kind as `r7` into the
|
||||
phase initialisers. `SilphScriptPhase`'s constructor `sub_8225FEF8` **never
|
||||
touches `r7`** (zero mentions in the whole function); the *base* constructor
|
||||
`sub_822700C0` does:
|
||||
|
||||
```
|
||||
822700F0 or r26, r7, r7
|
||||
82270188 stw r26, 152(r30) ; -> [phase+152]
|
||||
```
|
||||
|
||||
**Hop 2 — who reads it.** `sub_82261F70` builds a table of string pointers on its
|
||||
stack at `r31+144…` and indexes it with that field:
|
||||
|
||||
```
|
||||
8226230C lwz r11, 152(r21) ; the stored kind
|
||||
82262310 addi r10, r31, 144 ; the table
|
||||
82262318 rlwinm r11, r11, 2, 0, 29 ; kind * 4
|
||||
8226231C lwzx r4, r11, r10 ; table[kind] -> a STRING
|
||||
```
|
||||
|
||||
The table is contiguous at `0x820A8880`, 20 bytes per entry:
|
||||
|
||||
| index | string |
|
||||
|---|---|
|
||||
| 0 | `STAGE01_UNIT_MAX` |
|
||||
| 1 | `STAGE02_UNIT_MAX` |
|
||||
| … | … |
|
||||
| **16** | **`STAGE17_UNIT_MAX`** |
|
||||
| … | … |
|
||||
| **32** | **`STAGE33_UNIT_MAX`** |
|
||||
| 33 | `PLANE` — the table ends |
|
||||
|
||||
> ⇒ **`X+12` = `[phase+152]` = a 0-BASED STAGE INDEX**, `index N → STAGE(N+1)`.
|
||||
|
||||
The gate falls out of it exactly:
|
||||
|
||||
* **`kind <= 32`** is the **array bound** — the table has precisely 33 entries,
|
||||
0…32, with `PLANE` at 33 where it stops.
|
||||
* **`kind != 16`** is **`STAGE17`** — the one stage number with no `.ssb` on the
|
||||
disc (files run `Stage01`–`Stage16`, `Stage18`–`Stage29`).
|
||||
|
||||
✅ **This retracts the "not adopted" above.** The stage-index reading was recorded
|
||||
as 1-of-1 but coincidence-shaped and deliberately not believed; it is now read off
|
||||
the game's own string table, and **0-based is proved by `index 5 → STAGE06`**,
|
||||
not assumed.
|
||||
|
||||
⚠️ **A false positive caught on the way.** `0x82272D88 lwz r11, 152(r11)` inside
|
||||
the built-in switch looks like a read of this field. It is not: `r11` had just
|
||||
been loaded from `0(r31)`, so it is the **vptr** — that instruction is a virtual
|
||||
call to **slot 38**. Offset 152 recurs; the base register decides.
|
||||
|
||||
🟡 Its neighbours belong to the same cluster: `builtin103` reads
|
||||
`[phase+10156]` and `[phase+10152]` (9 and 7 writers), and a sibling vtable stub
|
||||
clears `[phase+10152]`. The shape is an engine→script status trio, but that is a
|
||||
|
||||
Reference in New Issue
Block a user