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.
26 KiB
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, 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"
[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)
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 §3). Extracting all of
them gives 24 of the 29 ids bound to a C++ class; full list in
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), 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
3and4are the same class —GamePart_SaveLoadis registered twice. The id table's separateGP_LOAD/GP_SAVEnames 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), 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 |
| 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 §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
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, "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,
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
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.
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.
🟡 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 → 1vs0 → 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.
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
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 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, 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.