because the test probed the wrong state Nine stores to the phase field this+132, control-gated on the 136 query returning at least the 18 known ones. Attributed to the handler each lives in, they give the phase graph: entry -> 2, then 2 -> 0 (splash), 2 -> 3, 3 -> 4, and 4 -> 2 on event 0. That last edge is the problem with what I did last iteration. The event-0 block sets TWO fields one instruction apart -- stw r28,136(r30) for state 0 and stw r11,132(r30) with r11 = 2 for phase 2. So B from the menu lands in PHASE 2, the same phase as the boot title, and the test I ran believing it probed phase 4 state 0 probed nothing of the kind. So the refutation is withdrawn. Worse for me and better for the idea: A working on the B-returned title is exactly what the hypothesis predicts, since phase 2 is the phase that references BUTTON. The hypothesis is back to untested, now consistent with two observations rather than one, and what it still needs is the attract-returned title's phase -- which no test so far has read. The measurement from that iteration stands, because it does not depend on the phase: the B-returned title accepts A, so only the ATTRACT-returned title is inert. I have said so explicitly in the page rather than letting the withdrawal take the good half down with it. METHOD gets the lesson: before testing "state X behaves like this", check your route actually reaches state X. When a transition writes more than one field, read the whole block and not just the store you were looking for.
584 lines
26 KiB
Markdown
584 lines
26 KiB
Markdown
# What the game reads at boot — `config.ini`, and which GameParts actually exist
|
||
|
||
**Status:** ✅ `CONFIRMED` and **decoded** for the language selection. ❔ a bounded
|
||
**negative** for the boot *order*. 🟡 for what the registration strings imply
|
||
about the attract loop.
|
||
|
||
Contributes to [MISSION Q6](../port/MISSION.md), and firms up Q4's ids.
|
||
|
||
## ✅ `config.ini` — the disc's only config, and it selects the language
|
||
|
||
`config.ini` sits at the **disc root**, is 400 bytes, and is the **only** `.ini`,
|
||
`.cfg` or `.txt` anywhere on the disc. Its own header comment (Shift-JIS) names
|
||
it:
|
||
|
||
> `アプリケーション/ゲームパート初期設定テーブル`
|
||
> — *"Application / GamePart initial settings table"*
|
||
>
|
||
> `SYSTEM セクションには、ゲーム及び各ゲームパートで共通にアクセスする情報を記述する`
|
||
> — *"the SYSTEM section describes information accessed in common by the game and
|
||
> each game part"*
|
||
|
||
```ini
|
||
[SYSTEM]
|
||
|
||
[LANGUAGE]
|
||
= eng ; default
|
||
#0x01 = eng ; XC_LANGUAGE_ENGLISH
|
||
#0x02 = jpn ; XC_LANGUAGE_JAPANESE
|
||
#0x03 = deu ; XC_LANGUAGE_GERMAN
|
||
#0x04 = fra ; XC_LANGUAGE_FRENCH
|
||
#0x05 = esp ; XC_LANGUAGE_SPANISH
|
||
#0x06 = ita ; XC_LANGUAGE_ITALIAN
|
||
```
|
||
|
||
**This is the mechanism behind the EN/JP screen pairs.** The console's
|
||
`XC_LANGUAGE_*` setting selects a three-letter code, and that code is what picks
|
||
`GP_TITLE`'s English or Japanese build ([`ui-title-build-map.md`](ui-title-build-map.md))
|
||
and the `<lang>.pak` families. Default is `eng`, keyed by the empty line — an
|
||
unlisted language falls back to English. ✅ decoded. The executable does read the
|
||
file: the string `config.ini` is at `.rdata 0x82062b44`.
|
||
|
||
## ❔ But the boot ORDER is not in it — and this is the whole search space
|
||
|
||
`[SYSTEM]` — the section the file's own comment says holds what the game and every
|
||
game part share — is **empty**. So the file the game itself calls the *GamePart
|
||
initial settings table* says nothing about which part runs first or what follows
|
||
what.
|
||
|
||
**Reach of the negative:** this is the only config file on the disc (one `find`
|
||
over the whole extract). The boot order is therefore not in disc-side
|
||
configuration at all; it is in code, or in a table inside the executable that has
|
||
not been located.
|
||
|
||
## 🟡 `GP_ADVERTISE_DEMO` is never registered — the attract loop is not its own part
|
||
|
||
The executable carries one diagnostic string per GamePart registration site
|
||
(`silph::GamePartTask::RegisterToFactory<N, class silph::GamePart_X>::RegisterToFactory is failed!`)
|
||
— the same evidence the corpus used to pin the id table
|
||
([`challenge-mission-gate.md`](challenge-mission-gate.md) §3). Extracting all of
|
||
them gives **24 of the 29 ids bound to a C++ class**; full list in
|
||
[`data/gamepart-class-ids.txt`](data/gamepart-class-ids.txt).
|
||
|
||
**The five ids with no registration site are `1`, `2`, `16`, `18`, `28`** — in the
|
||
id table's naming, `GP_ADVERTISE_DEMO`, `GP_SELECT_STORAGE`, `GP_DEMO`,
|
||
`GP_SELECTOR`, `GP_TEST`.
|
||
|
||
That `GP_ADVERTISE_DEMO` (1) is among them matters for Q6, and it agrees with
|
||
what was measured: the attract loop is **the title screen replaying `ADV.wmv`**
|
||
([`movie-binding.md`](movie-binding.md)), not a transition into a separate
|
||
advertise part. The id table names a part the shipped build never registers.
|
||
|
||
🟡 **not ✅**, because this is an argument from a *diagnostic string*: no error
|
||
message for id 1 implies no registration site for id 1. That is how the corpus
|
||
already reads these strings, and it is sound, but it is not the code.
|
||
|
||
### Two bonuses for Q4
|
||
|
||
* **`3` and `4` are the same class** — `GamePart_SaveLoad` is registered twice.
|
||
The id table's separate `GP_LOAD` / `GP_SAVE` names are two ids on one part.
|
||
* The ids behind the menu buttons now match the executable's own **class** names,
|
||
not just a list of table names: `GamePart_Options` = 8, `GamePart_Tutorial` =
|
||
25, `GamePart_Extras` = 5, `GamePart_MissionSelect` = 7,
|
||
`GamePart_MovieTheater` = 6, `GamePart_Title` = 0. Still a **name match** —
|
||
screen title ↔ class name — but anchored one level closer to the code.
|
||
|
||
## What is still missing for Q6
|
||
|
||
The *transitions*. Nothing found so far says "title, then movie, then title" — the
|
||
manifest gives the boot-side **assets** in play order
|
||
([`movie-binding.md`](movie-binding.md)), `config.ini` gives the **language**, and
|
||
the registry gives **which parts exist**. What decides to advance is in
|
||
`GamePart_Title`'s own code, and reading it is a static PPC job that has not been
|
||
started.
|
||
|
||
## ❔ The transitions are code, not data — the reach of that negative
|
||
|
||
Q6's remaining half asked what the game *reads* to decide the boot order. The
|
||
answer is: **nothing. It is not data-driven.** Four independent places were
|
||
checked, and the sequence is in none of them.
|
||
|
||
| looked in | result |
|
||
|---|---|
|
||
| **disc configuration** | `config.ini` is the only config file on the disc, its `[SYSTEM]` section is empty (above) |
|
||
| **the movie manifest** | carries the boot-side *assets* in play order, and no transitions — [`movie-binding.md`](movie-binding.md) |
|
||
| **a persistent part-id field** | already refuted: the requested GamePart id exists **only as a stack argument in flight**, with no literal store anywhere — [`challenge-mission-gate.md`](challenge-mission-gate.md) §5 |
|
||
| **the id table's attract entry** | the string `GP_ADVERTISE_DEMO` at `0x820a1fe8` has **zero xrefs of any kind**; nothing in the code reads it |
|
||
|
||
So a transition is a **call with an id argument**, chosen by code. There is no
|
||
table to read and nothing to poke.
|
||
|
||
**Where that code is, as far as it was traced.** The `RegisterToFactory<0, class
|
||
silph::GamePart_Title>` diagnostic string at `0x820a3d60` is referenced from
|
||
exactly one place, `sub_8280E148` — the registration site — which also takes the
|
||
address of **`sub_821C7D98`** (`addi`), the position a factory template puts its
|
||
creator. 🟡 That identification is by position and convention, not proven;
|
||
`sub_821C7D98` itself has **0 `.rdata` references**, consistent with a small
|
||
`new`+ctor thunk rather than the state machine. The substantial function in the
|
||
same class neighbourhood is `sub_821C6458` (4 460 bytes, has EH, 15 `.rdata`
|
||
refs), and **reading it has not been attempted**.
|
||
|
||
### What this means for the port
|
||
|
||
The boot sequence is **authored**, not transcribed — and that is fine, because the
|
||
sequence itself is measured end to end:
|
||
|
||
```
|
||
developer splash (a RATC screen, not a video)
|
||
→ ADV.wmv
|
||
→ title + PRESS Ⓐ ──idle ~8–10 s──> fade to black → ADV.wmv in full → title
|
||
→ Ⓐ → main menu
|
||
```
|
||
|
||
with the fade-through-black timings in [`screen-transitions.md`](screen-transitions.md)
|
||
and Ⓑ from the main menu returning to the title.
|
||
|
||
|
||
## 🟡 `sub_821C6458` read — the title's states are NAMED in the executable
|
||
|
||
The page above left this function as the named next step: "the substantial
|
||
function in `GamePart_Title`'s neighbourhood, and **reading it has not been
|
||
attempted**". It has now been looked at — not disassembled line by line, but
|
||
characterised, which is enough to sharpen Q6.
|
||
|
||
**It is the title part's screen-state function.** 4 460 bytes, has EH, and called
|
||
from **exactly one** place — `sub_821C7850`, which sits in the same neighbourhood
|
||
as the creator the registration site points at (`sub_821C7D98`). It makes 33
|
||
distinct calls.
|
||
|
||
**What it names.** Its `.rdata` string references are:
|
||
|
||
| string | at |
|
||
|---|---|
|
||
| **`TITLE_SCREEN`** | `0x820a3d3c` |
|
||
| **`TITLE_MENU`** | `0x820a3d30` |
|
||
| **`LOADING`** | `0x820a214c` |
|
||
| `BASE_INFO` (×2) | `0x820a20ec` |
|
||
|
||
Those are the states measured off the running game, in the game's own words: the
|
||
title carrying `PRESS Ⓐ BUTTON`, the five-button menu, and a loading state. And
|
||
`BASE_INFO` is this corpus's own marker for a **screen-config lookup** rather than
|
||
a table read ([`REFUTED.md`](REFUTED.md), "`BASE_INFO` discriminates
|
||
screen-config from table-read, 9/9 vs 10/10").
|
||
|
||
### The sharper negative
|
||
|
||
So the title part **does ask for configuration keyed by `TITLE_SCREEN` and
|
||
`TITLE_MENU`** — and [`config.ini`](#-configini--the-discs-only-config-and-it-selects-the-language),
|
||
the disc's only config file, contains **no such sections**. Its only sections are
|
||
an empty `[SYSTEM]` and `[LANGUAGE]`. The lookups find nothing, and the values are
|
||
whatever the code defaults to.
|
||
|
||
That is a better answer than "the order is not in config": the game *asks the
|
||
question*, the shipped disc *does not answer it*, and the defaults live in code.
|
||
For the port it means the state **names** are transcribable even though their
|
||
contents are not.
|
||
|
||
🟡 **Not ✅.** This is a characterisation from string references and call counts,
|
||
not a read of the control flow. Two self-references (`0x821c6498`, `0x821c6b7c`)
|
||
inside the function look like jump tables — a switch, which is what a state
|
||
machine compiles to — but that was **not confirmed**, and nothing here shows which
|
||
state leads to which.
|
||
|
||
### 🔴 Refuted on the way, because it looked like a find
|
||
|
||
The words at `0x820a3b48`… resolve as neat `{func, func, ptr}` triples and read
|
||
convincingly as a state/handler table. **They are not.** The bytes immediately
|
||
before them are the tail of
|
||
`…SaveLoad>::RegisterToFactory is failed!`, and the `0x8210c1xx` targets are
|
||
zero-filled descriptors — static-initialiser records trailing the registration
|
||
strings, not a dispatch table. A plausible-looking array of function pointers next
|
||
to relevant strings is not evidence of anything until its neighbours are read.
|
||
|
||
|
||
## 🟡 The title's transition is a screen lookup BY NAME — `sub_821CC860`
|
||
|
||
Reading the code around each of the three state names shows the **same four
|
||
instructions** at all three sites:
|
||
|
||
```
|
||
lwz r29, 20(r30) ; an object off the part
|
||
lwz r3, 4(...) ; ...
|
||
bl 0x822F2328
|
||
lis r11, 0x820A
|
||
li r6, 0
|
||
lwz r4, 24(r30)
|
||
addi r5, r11, 15676 ; "TITLE_SCREEN" (15664 = "TITLE_MENU",
|
||
lwz r3, 88(r30) ; 8524 = "LOADING")
|
||
bl 0x821CC860 ; <- lookup(this+88, this+24, NAME, 0)
|
||
mr r4, r3
|
||
bl 0x82187B78 ; <- install the result
|
||
```
|
||
|
||
So a title-side transition is **`sub_821CC860(…, "<NAME>", 0)` followed by
|
||
`sub_82187B78(result)`** — a *string-keyed* screen lookup, then an install. Not a
|
||
numeric id, not a table index. That also fits `GP_ADVERTISE_DEMO` having zero
|
||
xrefs: at this level the screen graph is keyed by **name**, not by GamePart id.
|
||
|
||
### The candidate name vocabulary
|
||
|
||
`sub_821CC860` is called from **28 distinct functions**. Collecting the
|
||
upper-case identifier strings those callers reference gives 35 names, and they
|
||
split into two obvious families:
|
||
|
||
* **screen/state names** — `TITLE_SCREEN`, `TITLE_MENU`, `LOADING`,
|
||
**`DIFFICULTY`**, `EXTRA_MENU`, `TUTORIAL_MENU`, `STANDARD_MENU`, `DEBRIEFING`,
|
||
`CHALLENGE`, `MISSIONS`, `LIVE_BOARD`, `LOCAL_BOARD`, `EXTRA_MENU`;
|
||
* **config keys** — `TEXT_FONT`, `TEXT_HEIGHT`, `LINE_SPACE`, `GAMMA_RGB`,
|
||
`GAMMA_WB`, `TEXT_SPEED_PER_LETTER`, `EXIT_VALUE`, `INPUT_DIR`,
|
||
`MENU_ENABLE_SKIP`/`_DISABLE_SKIP`, `PAUSE_SE`, `JINGLE`, …
|
||
|
||
plus `BASE_INFO` in **19 of the 28** callers, which is this corpus's existing
|
||
marker for a screen-config function.
|
||
|
||
**Three of the screen names are independently corroborated by measurement**:
|
||
`DIFFICULTY` is exactly the screen `NEW GAME` opens, `EXTRA_MENU` matches the
|
||
`EXTRAS` submenu and `TUTORIAL_MENU` the lesson list — all three measured off the
|
||
running game in [`menu-navigation-semantics.md`](menu-navigation-semantics.md)
|
||
before this function was ever looked at.
|
||
|
||
⚠️ **Why this is 🟡 and not ✅.** The 35 strings are what those callers
|
||
*reference*, **not** proven arguments to `sub_821CC860` — the list plainly mixes
|
||
screen names with config keys, so it is a **candidate vocabulary**, not a decoded
|
||
one. Confirming it means checking, per call site, which string actually lands in
|
||
`r5`. That was not done.
|
||
|
||
### What is still unread
|
||
|
||
Which state leads to which. The three lookups sit in different branches of one
|
||
function and at least one (`TITLE_MENU`) is guarded by a `cmplwi`/`bne`, but the
|
||
branch structure was not traced, so the *order* still comes from measurement, not
|
||
from the code.
|
||
|
||
|
||
## ✅ The argument is now decoded — and it refutes my own corroboration
|
||
|
||
The section above listed a **candidate** vocabulary from "upper-case strings the
|
||
callers reference", flagged 🟡 because those are not proven arguments, and named
|
||
the check: *confirm per call site which string actually lands in `r5`.* That check
|
||
has been run, and it fired.
|
||
|
||
**Method.** Forward register simulation over the 100 instructions before each of
|
||
the **48** call sites of `sub_821CC860`, tracking `lis` / `addi` / `subi` / `mr`.
|
||
Full table in [`data/name-lookup-callsites.txt`](data/name-lookup-callsites.txt).
|
||
|
||
**Gated on a control, and the first version failed it.** A backward scan for
|
||
`addi r5, …` recovered only 7/48 and **missed both title sites I had read by
|
||
eye** — because there the name goes into `r27` first and reaches `r5` via a later
|
||
`mr`. The rewritten forward simulation reproduces all three known sites
|
||
(`TITLE_SCREEN`, `TITLE_MENU`, `LOADING`) and then recovers **46 of 48**.
|
||
|
||
### 🔴 `DIFFICULTY` and `EXTRA_MENU` are *not* arguments
|
||
|
||
Last section I wrote that three names were "independently corroborated by
|
||
measurement": `DIFFICULTY`, `EXTRA_MENU`, `TUTORIAL_MENU`. **Only
|
||
`TUTORIAL_MENU` survives.** `DIFFICULTY` and `EXTRA_MENU` never appear in `r5` at
|
||
any of the 48 sites — they are strings that merely live in the same functions.
|
||
The corroboration I claimed was an artifact of the loose filter, and the 🟡 I put
|
||
on it is exactly what caught it.
|
||
|
||
### The 28 names that ARE passed
|
||
|
||
```
|
||
BASE BASE(x3) BG(x3) BLACK(x2) BUTTON DEBRIEFING DETAIL EXTRA FADE(x3)
|
||
FILE(x5) GAME GAMMA_RGB GAMMA_TITLE INFO KEY LIVE_BOARD(x2) LOADING(x2)
|
||
LOCAL_BOARD(x2) MENU(x2) MENU_DISABLE_SKIP MESSAGE NEW_ITEM(x2) PAD SOUND
|
||
TITLE(x2) TITLE_MENU TITLE_SCREEN(x2) TUTORIAL_MENU WINDOW
|
||
```
|
||
|
||
### 🔴 So `sub_821CC860` is not a screen factory
|
||
|
||
The previous section called it one. The real argument list says otherwise:
|
||
`BG`, `BLACK`, `FADE`, `FILE`, `KEY`, `PAD`, `SOUND`, `WINDOW`, `GAMMA_RGB`,
|
||
`MENU_DISABLE_SKIP` are **not screens**. It is a **generic name-keyed lookup** —
|
||
a named-entry getter used for config and resources throughout the executable, of
|
||
which the title part happens to call it with its three state names.
|
||
|
||
That weakens nothing about the title finding itself — `sub_821C6458` really does
|
||
transition by calling this with `TITLE_SCREEN` / `TITLE_MENU` / `LOADING` — but it
|
||
removes the inference that the 28 names are a screen vocabulary. They are a
|
||
**lookup-key vocabulary**, mostly config.
|
||
|
||
|
||
## ✅ The title's state machine — decoded
|
||
|
||
The last open piece of Q6 was *which state leads to which*. It is a plain
|
||
`switch` and it is now read.
|
||
|
||
```
|
||
821c6474 lwz r11, 136(r30) ; state = this+0x88
|
||
821c6478 cmplwi cr6, r11, 0x9 ; ten states, 0..9
|
||
821c647c bgt cr6, <default>
|
||
821c6480 lis/addi r12, 0x821C6498 ; jump table
|
||
821c648c lwzx r0, r12, r0
|
||
821c6494 bctr
|
||
```
|
||
|
||
⚠️ The table at `0x821C6498` disassembles as ten `lwz r16, N(r28)` instructions.
|
||
**It is data.** That is the "self-reference" this page flagged two sections ago as
|
||
*looking* like a jump table — it is one, and the disassembler was decoding its
|
||
words as code.
|
||
|
||
**Three of the ten states install a named screen**: **0 → `TITLE_SCREEN`**,
|
||
**2 → `TITLE_MENU`**, **8 → `LOADING`**.
|
||
|
||
**Eighteen transitions**, every one a literal `li rX, N ; stw rX, 136(r30)`:
|
||
|
||
```
|
||
0 → 1, 2 2 → 4 4 → 0, 5, 8×4 6 → 7, 9, 2 8 → 2
|
||
1 → 2, 2 3 → 4 5 → 6 7 → 9 9 → (none)
|
||
```
|
||
|
||
Full table with addresses in
|
||
[`data/title-state-machine.txt`](data/title-state-machine.txt).
|
||
|
||
### 🟡 It lines up with what was measured — read as corroboration, not proof
|
||
|
||
The boot reaches the title (state 0) and Ⓐ opens the main menu (state 2); the
|
||
graph has `0 → 2` directly and `0 → 1 → 2`. Ⓑ at the main menu returns to the
|
||
title, and `4 → 0` is the **only** edge back to state 0, reached from `2 → 4`.
|
||
Entering a submenu goes through `LOADING` and comes back, and `4 → 8` (four
|
||
separate sites) then `8 → 2` is exactly that shape.
|
||
|
||
Those readings are **mine, matching a graph to observed behaviour** — the
|
||
conditions on the edges are not decoded, so nothing here proves which input picks
|
||
which branch.
|
||
|
||
### What is still not decoded
|
||
|
||
* **the condition on each edge** — which input or event selects it;
|
||
* **states 1, 3, 5, 6, 7, 9** install no named screen, so what they do is unknown;
|
||
* **state 3 is never a destination** in this function, so something outside sets
|
||
it.
|
||
|
||
|
||
## ✅ The edge conditions, for the state that has them — an EVENT CODE
|
||
|
||
The previous section left "the condition on each edge" open. For the state that
|
||
matters it is now read.
|
||
|
||
**`sub_821C6458`'s third argument is an event code.** State 4 — reached from
|
||
`2 → 4`, i.e. immediately after the main menu is installed — is the
|
||
**input-waiting state**, and it dispatches on that argument:
|
||
|
||
```
|
||
821c6b5c cmplwi cr6, r27, 0x19 ; 26 events, 0..25
|
||
821c6b60 bgt cr6, <default>
|
||
821c6b68 addi r12, r12, 27516 ; second jump table, at 0x821c6b7c
|
||
821c6b78 bctr
|
||
```
|
||
|
||
**Six of the 26 are handled**; the other twenty fall through and change nothing.
|
||
|
||
| event | → state | |
|
||
|---|---|---|
|
||
| **0** | **0** | `TITLE_SCREEN` — back to the title |
|
||
| 3 | 8 | `LOADING` |
|
||
| 5 | 8 | `LOADING` |
|
||
| 8 | 8 | `LOADING` |
|
||
| 10 | 5 | — |
|
||
| 25 | 8 | `LOADING` |
|
||
|
||
So last section's edges `4 → 0, 5, 8×4` are each now attributed to a specific
|
||
event, and the shape of the state graph is complete for the input state.
|
||
|
||
### 🟡 A correspondence worth noticing, and not more than that
|
||
|
||
The main menu has five items, Ⓑ returns to the title, and this table has **one
|
||
event to state 0, four to `LOADING`, one elsewhere**. It is tempting to read that
|
||
as *Ⓑ = event 0, four items load an external archive, `EXTRAS` stays inside
|
||
`GP_TITLE`* — which would match everything measured.
|
||
|
||
**That is a count-match, not a decode.** The event numbers are not named, nothing
|
||
here shows event 3 is a particular menu row, and state 5 installs no named screen,
|
||
so the `EXTRAS` half of the story has no support at all. Recorded as an
|
||
observation so the next person sees the shape; it is **not** a button→event map.
|
||
|
||
### What is still not decoded
|
||
|
||
* **what the event numbers mean** — button id, menu-item index, or message id;
|
||
* **conditions on edges out of the other states** (`0 → 1` vs `0 → 2`, `6 → 7/9/2`);
|
||
* states 1, 3, 5, 6, 7, 9, which install no named screen.
|
||
|
||
|
||
## 🔴 Correcting my own framing: `sub_821C6458` is ONE PHASE, not the whole part
|
||
|
||
Chasing where the event code comes from turned up the level above, and it revises
|
||
what the previous three sections called "the title part's state machine".
|
||
|
||
`sub_821C6458` has exactly one caller, `sub_821C7850`, and that caller is itself a
|
||
dispatcher — on a **second, outer state field**:
|
||
|
||
```
|
||
821c786c lwz r11, 16(r30) ; only runs at all when this+16 == 3
|
||
821c7874 bne cr6, <exit>
|
||
821c787c lwz r11, 132(r30) ; phase = this+0x84
|
||
821c7880 cmplwi cr6, r11, 0x4 ; FIVE phases, 0..4
|
||
821c789c bctr ; table at 0x821C78A0
|
||
```
|
||
|
||
| phase | handler | |
|
||
|---|---|---|
|
||
| 0 | `sub_821C5690` | **the splash** — independently identified as the splash mechanics in the `iterate3E` notes, before any of this |
|
||
| 1 | inline at `0x821c790c` | |
|
||
| 2 | `sub_821C5818` | |
|
||
| 3 | `sub_821C5EC0` | |
|
||
| **4** | **`sub_821C6458`** | the title/menu machine — `TITLE_SCREEN` / `TITLE_MENU` / `LOADING` |
|
||
|
||
So `GamePart_Title` has **two nested state fields**: a phase at `this+132` (five
|
||
values) selecting which sub-machine runs, and the state at `this+136` (ten values)
|
||
*inside phase 4*. Everything the previous sections decoded — the ten states, the
|
||
eighteen edges, the event dispatch — is **phase 4 only**. Phases 0–3 are
|
||
untouched, and one of them is the developer splash.
|
||
|
||
That phase 0 lands on the function the corpus had already fingered as the splash,
|
||
from a completely different direction, is the useful check here.
|
||
|
||
### And the event code is forwarded, not created
|
||
|
||
All five phase handlers are called as `(this, r29, r28)` with the *same*
|
||
arguments, and `r28` is `sub_821C7850`'s own third argument, passed through
|
||
untouched. So the event vocabulary is defined **at least one level above** this
|
||
function. Finding what `3`, `5`, `8`, `10`, `25` mean means going up again, and
|
||
that was not done.
|
||
|
||
|
||
## Going up one more level — three of four callers pass a constant event
|
||
|
||
`sub_821C7850` has **four** direct callers. Recovering the `r5` argument at each:
|
||
|
||
| caller | event passed |
|
||
|---|---|
|
||
| `sub_821C7CB8` | **0** (→ back to the title) |
|
||
| `sub_821C7BA0` | **0** |
|
||
| `sub_821C47A0` | **5** (→ `LOADING`) |
|
||
| `sub_821C5580` | **not constant** — `lwz r5, 4(r27)`, read out of a structure |
|
||
|
||
So the vocabulary is still not enumerable from here: the interesting caller reads
|
||
its event from a field. ❔ What `3`, `8`, `10` and `25` mean remains open.
|
||
|
||
**`sub_821C5580` is also where the outer gate is set:**
|
||
|
||
```
|
||
821c5640 li r11, 3
|
||
821c5644 stw r11, 16(r28) ; this+16 = 3 — the exact value sub_821C7850 tests
|
||
821c5650 lwz r5, 4(r27) ; event from a struct field
|
||
821c5658 bl 0x821C7850
|
||
```
|
||
|
||
That answers the "what does `this+16 == 3` gate on" question from the previous
|
||
section: this function arms it.
|
||
|
||
### 🎁 And it names the title's music
|
||
|
||
The same function, a few instructions earlier:
|
||
|
||
```
|
||
821c560c li r5, 1103
|
||
821c5610 li r4, 4
|
||
821c561c lwz r3, 0(r29)
|
||
821c5620 bl 0x8217ACF8 ; a sound-play call
|
||
```
|
||
|
||
**1103 is a BGM cue id** — `BGM_103`. That closes an open residual from Q8/Q10
|
||
("which bank is the menu's music is not on the disc"), and it checks out three
|
||
ways; see [`structures/bgm-two-stems.md`](structures/bgm-two-stems.md).
|
||
|
||
|
||
## The other phases, characterised — and phase 0 confirms the splash twice over
|
||
|
||
Same treatment for the phase handlers that were still unread. Strings each one
|
||
references, and whether it carries a jump table of its own:
|
||
|
||
| phase | handler | size | strings referenced | own switch |
|
||
|---|---|---|---|---|
|
||
| 0 | `sub_821C5690` | 380 | **`LOGO`** | none |
|
||
| 2 | `sub_821C5818` | 1 576 | `BASE_INFO`, **`BUTTON`**, **`TITLE_SCREEN`** | none |
|
||
| 3 | `sub_821C5EC0` | 1 220 | *(none)* | one, `bctr` at `0x821c5ef8` |
|
||
| 4 | `sub_821C6458` | 4 460 | `BASE_INFO`, `LOADING`, `TITLE_MENU`, `TITLE_SCREEN` | two (known) |
|
||
|
||
**Phase 0 references `LOGO`** — a second, independent confirmation that it is the
|
||
developer splash. The `iterate3E` notes reached `sub_821C5690` from the guest side
|
||
and named the splash's `LOGO` items; this reaches the same function from the
|
||
registration site and finds the same string.
|
||
|
||
**Phase 2 draws the title *with* the `PRESS Ⓐ BUTTON` plate** — it references
|
||
`TITLE_SCREEN` and `BUTTON`, and [`ui-title-build-map.md`](ui-title-build-map.md)
|
||
established from the archive that the plate is its own build (2/3), composited
|
||
over the title art.
|
||
|
||
### 🟡 A hypothesis for a puzzle this corpus has had open for months
|
||
|
||
**The title is installed from two different places**: phase 2, and phase 4's
|
||
state 0. Same screen, different code.
|
||
|
||
[`canary-scripted-input-traps.md`](canary-scripted-input-traps.md) has long
|
||
recorded, and never explained, that *the title which ends the boot accepts Ⓐ while
|
||
the title the attract loop returns to accepts nothing* — with the giveaway that a
|
||
draw capture in each state is identical, 13 quads at the same rects. Two code
|
||
paths installing the same screen is exactly the shape that would produce that.
|
||
|
||
**Candidate:** the boot title is **phase 2**; the attract-returned title is
|
||
**phase 4, state 0**. Only phase 2 wires up the Ⓐ handling.
|
||
|
||
⚠️ **Tested, and dead.** Ⓑ from the main menu is the decoded edge `4 → 0`, so the
|
||
Ⓑ-returned title *is* phase 4 state 0 — and **Ⓐ works there**, opening the main
|
||
menu, with Ⓐ on the boot title as the control in the same run. Phase 4 state 0
|
||
handles Ⓐ, so the two-code-paths explanation is refuted. See
|
||
[`canary-scripted-input-traps.md`](canary-scripted-input-traps.md), which the
|
||
test did narrow usefully: only the **attract**-returned title is inert, not every
|
||
title after the first.
|
||
|
||
### ⚠️ One query in this section failed its own control
|
||
|
||
Counting `stw rX, 136(r30)` per phase returned **0 for phase 4**, which is known
|
||
to have 18 — the operand text is `r11, 136(r30)` and the pattern did not allow the
|
||
space. The per-phase store counts were discarded. The `bctr` half *does* pass its
|
||
control (phase 4 shows both known tables) and is reported above.
|
||
|
||
|
||
## ✅ The phase transitions — and they invalidate yesterday's test
|
||
|
||
Nine stores to the phase field `this+132` across the `GamePart_Title`
|
||
neighbourhood, attributed to the handler each lives in (control: the same query
|
||
shape finds 30 stores to `this+136`, ≥ the 18 known in phase 4):
|
||
|
||
| store | sets phase | lives in |
|
||
|---|---|---|
|
||
| `0x821c4fbc` | 2 | outside the handlers (entry/setup) |
|
||
| `0x821c4fe0`, `0x821c4fec` | 4 | outside the handlers |
|
||
| `0x821c5010` | *(not an immediate)* | outside the handlers |
|
||
| `0x821c592c` | **0** | phase 2 |
|
||
| `0x821c5b00` | **3** | phase 2 |
|
||
| `0x821c6370` | **4** | phase 3 |
|
||
| **`0x821c6e04`** | **2** | **phase 4, in the event-0 block** |
|
||
| `0x821c7950` | 2 | the dispatcher |
|
||
|
||
```
|
||
entry → 2 2 → 0 (splash) 2 → 3 3 → 4 4 → 2 (event 0)
|
||
```
|
||
|
||
### 🔴 Withdrawing the refutation from the previous iteration
|
||
|
||
The event-0 block sets **both** fields, one instruction apart:
|
||
|
||
```
|
||
821c6df8 li r11, 2
|
||
821c6e00 stw r28, 136(r30) ; state = 0
|
||
821c6e04 stw r11, 132(r30) ; phase = 2
|
||
```
|
||
|
||
So **Ⓑ from the menu lands in phase 2**, not phase 4 state 0. The previous section
|
||
tested "Ⓐ on the Ⓑ-returned title" believing it was probing phase 4 state 0 —
|
||
**it was probing phase 2**, the same phase as the boot title.
|
||
|
||
The test therefore says nothing about phase 4 state 0, and the refutation it
|
||
produced is **withdrawn**. Worse for me and better for the idea: Ⓐ working there
|
||
is exactly what the hypothesis predicts, since phase 2 is the phase that
|
||
references `BUTTON`.
|
||
|
||
**Status of the hypothesis: untested, and now consistent with two observations**
|
||
(Ⓐ works on the boot title and on the Ⓑ-returned title, both phase 2). What it
|
||
still needs is the **attract**-returned title's phase — the one thing no test so
|
||
far has read.
|