Two things, both from reading the image.
The record layout is {id, name_ptr, handler}, not {handler, id, name_ptr} as first
published -- the same three fields shifted by one word, so every record was credited
with the previous record's handler. Caught by a control dump: under the old
alignment record 0 had a 'handler' of 0x10000000, not a code address. ids and names
are unaffected and DLG_SELECT_DIFFICULTY is still id 2000; only the attribution
moved. Corrected histogram over 70 records: 0x821D0808 x43, 0x821D05D8 x24,
0x821CFD80 x3.
And the id-to-pak-entry join is not reachable this way. All three handlers load the
same global at 0x828E2B14 and two take addresses at 0x828E45E0/4640/467C, and every
one of those sits inside a 364 601-byte contiguous zero run -- BSS, populated only
at runtime. Controlled: the dialog table itself reads non-zero through the same
arithmetic, so the addressing is right and the data is genuinely absent.
Reach stated: this closes one route, not the question. 'Not in the image' is not
established -- 'not reachable from the dialog handlers' is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
286 lines
16 KiB
Plaintext
286 lines
16 KiB
Plaintext
# Where does the DIFFICULTY screen live? ✅ DECODED 2026-08-31.
|
|
# It is a DIALOG -- `DLG_SELECT_DIFFICULTY`, GP_DIALOG.pak entries 2/3.
|
|
#
|
|
# CLOSES A NEGATIVE OF MINE. gp-title-holds-three-button-screens.txt recorded
|
|
# "not an 8-record btn-named build anywhere on the disc", with the failed
|
|
# assumption named as mine: I assumed DIFFICULTY's four items pair with `f` focus
|
|
# variants in an archive of its own, the way GP_TITLE's screens do. Both halves
|
|
# of that were wrong -- it has its own button prefix and it is not a GamePart
|
|
# screen at all.
|
|
#
|
|
################################################################################
|
|
# ROUTE 1 -- THE IMAGE. /image/sylpheed.pe, 3 occurrences of "DIFFICULTY":
|
|
# 0x820A2548 STAGE | DIFFICULTY | TITLE | FADE | BASE_EXTRA ...
|
|
# (a GamePartTask::RegisterToFactory name list)
|
|
# 0x820A3377 RECORD_DIFFICULTY (a results/record field)
|
|
# 0x820A41BB DLG_LEADERBOARD_MENU_NEXT | DLG_SYSTEM_PAUSE |
|
|
# **DLG_SELECT_DIFFICULTY** | DLG_MISSION_OBJECTIVE |
|
|
# DLG_MESSAGE_BOX | DLG_MESSAGE_BOX_YES_NO | ...
|
|
#
|
|
# ✅ The third is the answer: DIFFICULTY is one of the game's DLG_* dialogs.
|
|
# ⚠️ "GP_DIFFICULTY" appears 0 times in the image, which is consistent.
|
|
#
|
|
################################################################################
|
|
# ROUTE 2 -- THE DISC. GP_DIALOG.pak entries 2 and 3 are the only builds in that
|
|
# archive carrying `pcbtn00`..`pcbtn03` -- FOUR buttons, matching
|
|
# EASY / NORMAL / HARD / BACK. Design rows and spacing:
|
|
#
|
|
# entry 2 y 259 / 329 / 399 / 469 spacing 70, 70, 70
|
|
# entry 3 identical (⚠️ called "the EN/JP pair" here and
|
|
# WITHDRAWN 2026-08-31 -- see the section
|
|
# at the end; adjacent GP_DIALOG entries
|
|
# are generally unrelated dialogs)
|
|
#
|
|
# ✅ CONTROL: the same reader on GP_TITLE entry 5 returns 162/242/322/401/482,
|
|
# spacing 80 -- the rows that archive is independently known to place.
|
|
#
|
|
################################################################################
|
|
# ROUTE 3 -- THE ORACLE. My own capture of the running DIFFICULTY screen
|
|
# (captures/menu-nav/live-difficulty-opens-normal.png), with the disc-grounded
|
|
# calibration capture_y = 64.82 + 0.9919 * design_y:
|
|
#
|
|
# design row predicted measured in the capture residual
|
|
# 259 321.7 323.5 +1.8
|
|
# 329 391.2 394.0 +2.8
|
|
# 399 460.6 463.5 +2.9
|
|
# 469 530.0 533.5 +3.5
|
|
# measured spacing 70.5 / 69.5 / 70.0 against the disc's 70 / 70 / 70
|
|
#
|
|
# Four rows, all under 4 px, with the spacing agreeing exactly. The residual is a
|
|
# uniform ~+2.8 px offset, which is the calibration's own systematic and not a
|
|
# mismatch.
|
|
#
|
|
# => THREE INDEPENDENT ROUTES: the executable names it a dialog, the disc has a
|
|
# four-button dialog build, and the running game draws its rows where that
|
|
# build says they are.
|
|
#
|
|
# 📌 WHY IT MATTERS BEYOND THE LOCATION: DIFFICULTY being a DIALOG explains why
|
|
# NEW GAME's destination is not a screen in GP_TITLE, and it means the "four menu
|
|
# items load an external archive" reading of the event numbers
|
|
# (boot-config-and-gamepart-registry.md's count-match) is looser than it looked --
|
|
# NEW GAME opens a dialog from GP_DIALOG.pak, which is an external archive, but a
|
|
# dialog is not the same kind of thing as OPTIONS or TUTORIAL opening a GamePart.
|
|
# The count still matches; the categories are not uniform.
|
|
#
|
|
# ⚠️ REACH: entries 2/3 are identified by button count and geometry, not by a
|
|
# name binding DLG_SELECT_DIFFICULTY to a pak entry. No such binding was found --
|
|
# the DLG_* names live in a string list, and what maps a name to an archive entry
|
|
# is not decoded. Another 4-button dialog with the same rows would be
|
|
# indistinguishable by this evidence.
|
|
|
|
################################################################################
|
|
# ✅ DECODED 2026-08-31 (later): THE DIALOG TABLE. `DLG_SELECT_DIFFICULTY` = id 2000.
|
|
#
|
|
# Chasing the reach above -- "no binding from the DLG_ name to anything" -- found
|
|
# one. Every DLG_ string in the image is pointed at by exactly one aligned word,
|
|
# at a regular 12-byte stride. The table is:
|
|
#
|
|
# struct { u32 id; u32 name_ptr; u32 handler; } // 12 bytes, big-endian
|
|
#
|
|
# 🔴 CORRECTED 2026-08-31: this first read as `{handler, id, name_ptr}`, which is
|
|
# the same three fields SHIFTED BY ONE WORD, so every record was credited with the
|
|
# PREVIOUS record's handler. Caught by a control dump: under the old alignment
|
|
# record 0 (DLG_LET_SIGNIN) had a "handler" of 0x10000000, which is not a code
|
|
# address. ids and names are unaffected -- they are the same fields either way,
|
|
# and DLG_SELECT_DIFFICULTY is still id 2000. Only the handler attribution moved.
|
|
# Under the corrected alignment its handler is 0x821D0808, and the histogram over
|
|
# all 70 records is 0x821D0808 x43, 0x821D05D8 x24, 0x821CFD80 x3.
|
|
#
|
|
# spanning 0x820A0A2C .. 0x820A0D68, three distinct handler values
|
|
# (0x821CFD80, 0x821D05D8, 0x821D0808).
|
|
#
|
|
# ✅ COMPLETE: 70 DLG_ names in the image, 70 records, ZERO names without one.
|
|
# ids are banded and monotonic, with a single gap at 24:
|
|
# 0..23 sign-in, storage, save/load/replay, GO_TITLE, GO_NEXT
|
|
# 25..43 autosave, training, weapon develop, custom keys, menu jumps
|
|
# 98, 99 DLG_MESSAGE_BOX_YES_NO, DLG_MESSAGE_BOX
|
|
# 1000 DLG_MISSION_OBJECTIVE
|
|
# 2000 **DLG_SELECT_DIFFICULTY**
|
|
# 2001..3 SYSTEM_PAUSE, LEADERBOARD_MENU_JUMP, LEADERBOARD_MENU_NEXT
|
|
# 2100..15 DLG_STAGE_TITLE01..16
|
|
# 8000 DLG_RATING_ESRB
|
|
# 9000..2 trial dialogs
|
|
#
|
|
# Read from /image/sylpheed.pe directly -- these are the bytes the console ran,
|
|
# not a database row.
|
|
#
|
|
################################################################################
|
|
# ✅ REFUTATION ATTEMPT ON THE SHARED REACH -- IT SURVIVES, AND IS NOW BOUNDED.
|
|
#
|
|
# Both agents recorded: "another four-button dialog with the same rows would be
|
|
# indistinguishable by this evidence". Tested by scanning EVERY build in EVERY pak
|
|
# for four buttons within 6 px of rows 259/329/399/469
|
|
# (examples/four_button_row_rivals.rs):
|
|
#
|
|
# INCUMBENT GP_DIALOG.pak entry 2 rows [259, 329, 399, 469]
|
|
# INCUMBENT GP_DIALOG.pak entry 3 rows [259, 329, 399, 469]
|
|
# control: 2 incumbents found (want 2) -- PASSED
|
|
# RIVALS ELSEWHERE ON THE DISC: **0**
|
|
#
|
|
# So the hypothetical rival does not exist here. The geometric identification is
|
|
# UNIQUE DISC-WIDE, which is a stronger statement than the one either of us
|
|
# recorded, and it was cheap to get.
|
|
#
|
|
# ⚠️ WHAT IS STILL NOT BOUND: id 2000 -> a pak entry. The table gives name -> id
|
|
# and the disc gives a unique four-button build; nothing found so far connects the
|
|
# two. The tie is uniqueness of geometry plus the oracle capture, not a pointer.
|
|
|
|
################################################################################
|
|
# ❌ REFUTED: the id -> pak-entry join is NOT positional. 2026-08-31.
|
|
#
|
|
# GP_DIALOG.pak has exactly **140 entries** and the dialog table has exactly **70
|
|
# records** — a 2:1 ratio that looks like one EN/JP pair per dialog, which would
|
|
# make the unbound join an ordering question rather than a search. Tested, and it
|
|
# does not hold:
|
|
#
|
|
# ADJACENT pairing (2k, 2k+1) identical element-name sets: 2 of 65
|
|
# HALVES pairing (i, i+70) identical element-name sets: 0 of 65
|
|
# (5 pairs unreadable — entries that do not parse as builds)
|
|
#
|
|
# GP_TITLE's language pairs share their element sets exactly (except 4/7, the
|
|
# title art), so identical sets are the signature of a pair there. In GP_DIALOG
|
|
# almost nothing matches, so **the 2:1 ratio is not established as a language
|
|
# pairing** and no positional rule connects id 2000 to entries 2/3.
|
|
#
|
|
# 📌 RESIDUAL OBSERVATION, recorded because it is odd rather than because it is
|
|
# understood: the only two adjacent pairs with identical element sets are
|
|
# **entries 0/1 and 2/3** — and 2/3 is the DIFFICULTY build. Whatever makes those
|
|
# two structurally uniform in an archive where 63 of 65 pairs are not, this does
|
|
# not explain.
|
|
#
|
|
# ⚠️ A plausible reading I am NOT asserting: dialog text may be baked into
|
|
# language-specific sprites, which would make EN/JP entries differ in element
|
|
# names by construction. That is consistent with the 63, and it is one
|
|
# measurement away from being tested — but it is not tested here, and the 2 that
|
|
# DO match would then need their own explanation.
|
|
#
|
|
# => The join stays unbound. Table gives name -> id; disc gives a unique build;
|
|
# the tie is uniqueness plus the oracle capture. Positional ordering is now
|
|
# ruled out as the missing pointer, which narrows where to look next.
|
|
|
|
################################################################################
|
|
# 🔴 MY UNTESTED READING IS REFUTED, and it inverts the puzzle rather than
|
|
# solving it. 2026-08-31, sylpheed-port, re-derived here with my own reader.
|
|
#
|
|
# I offered: dialog text may be baked into language-specific sprites, so EN/JP
|
|
# entries differ in element names by construction -- which would explain the 63
|
|
# differing pairs and leave the 2 matching ones needing their own account.
|
|
#
|
|
# ❌ **26 of 65 adjacent pairs differ in BUTTON COUNT.** Two languages of one
|
|
# dialog cannot: a locale changes the glyphs on a button, not how many there are.
|
|
# Reproduced independently (examples/dialog_pair_button_counts.rs): 26 differing,
|
|
# 39 equal, 5 unreadable -- their count exactly.
|
|
#
|
|
# entries 6/7 2 vs 3 entries 44/45 2 vs 0
|
|
# entries 8/9 2 vs 0 entries 46/47 2 vs 0
|
|
#
|
|
# And the names say it once read rather than counted: 6/7 is ranking_NEXT against
|
|
# ranking_JUMP, 8/9 is py_ranking_* against pzeff*, 10/11 is pzstg10 against
|
|
# pzstg02 -- different subsystems, different stages.
|
|
#
|
|
# 📌 SO THE PUZZLE DISSOLVES INSTEAD OF DEEPENING. Adjacent GP_DIALOG entries are
|
|
# simply UNRELATED DIALOGS. The 63 never needed the language reading, so the 2
|
|
# that match need no special explanation either, and the 140:70 ratio is a
|
|
# **coincidence of counting, not a pairing** -- which is the same fact my own
|
|
# halves-pairing result of 0 was already showing from the other side.
|
|
#
|
|
# ⚠️ AND THE CAREFUL PART, which is theirs and which I am preserving: this does
|
|
# NOT establish that 0/1 and 2/3 ARE EN/JP pairs. Identical element sets is the
|
|
# signature in GP_TITLE; here it is equally consistent with a DUPLICATE. And 37
|
|
# of the 63 differ WITHOUT a button-count mismatch -- for those the language
|
|
# reading is UNSUPPORTED, not refuted. What is refuted is it as an explanation of
|
|
# the 63, which is what I offered it as.
|
|
#
|
|
# 🔴 CONSEQUENCE FOR A DELIVERED CLAIM: I described entries 2/3 as "an EN/JP pair"
|
|
# in HANDOFF.md and above. **Withdrawn.** The DIFFICULTY identification does not
|
|
# rest on the pairing -- it rests on the unique four-button geometry (0 rivals
|
|
# disc-wide) plus the oracle capture -- but the pairing was stated as fact and was
|
|
# not one.
|
|
|
|
################################################################################
|
|
# ❌ AND THE 37 ARE NOW REFUTED TOO, not merely unsupported. 2026-08-31.
|
|
#
|
|
# Both agents recorded that the language reading survived for the 37 pairs that
|
|
# differ WITHOUT a button-count mismatch, and both noted that nothing rewarded
|
|
# closing it. It cost one scan.
|
|
#
|
|
# STEP 1: all 39 equal-button-count pairs share their button NAMES AND ROWS
|
|
# exactly (examples/dialog_pair_37.rs; control: entries 2/3 counted as matching).
|
|
# ⚠️ That does NOT settle it -- two different dialogs sharing a button template,
|
|
# like two yes/no boxes, look identical by that test.
|
|
#
|
|
# STEP 2: look at what actually differs (examples/dialog_pair_diffs.rs):
|
|
#
|
|
# entries 10/11 pzstg10_01..20 + _eff vs pzstg02_01..16 + _eff
|
|
# entries 12/13 pzstg11_* vs pzstg03_*
|
|
# entries 14/15 pzstg12_* vs pzstg13_*
|
|
# entries 16/17 pzstg04_* vs pzstg14_*
|
|
# entries 18/19 pzstg05_* vs pzstg15_*
|
|
# entries 20/21 pzstg06_* vs pzstg16_*
|
|
#
|
|
# ✅ THESE ARE DIFFERENT STAGES. They are the sixteen DLG_STAGE_TITLE01..16
|
|
# dialogs from the table above, and an adjacent pair carries TWO DIFFERENT STAGES,
|
|
# not two languages of one. That settles it unaided.
|
|
#
|
|
# 🔴 A SECOND ARGUMENT I OFFERED HERE IS WITHDRAWN. I wrote: "the sprite COUNTS
|
|
# differ too (stg10 has 20 title sprites, stg02 has 16), which is a different
|
|
# amount of text, not a translation of the same text." **It does not hold
|
|
# uniformly.** sylpheed-port re-ran it and 12/13 comes out EQUAL; my own output
|
|
# above shows it -- pzstg11_01..13 against pzstg03_01..13, thirteen each. I
|
|
# generalised from the 10/11 example I had looked at.
|
|
# ⚠️ Their absolute figures also differ from mine (42 vs 34 where I said 20 vs 16)
|
|
# because we counted different things: they count every `.t32` element in the
|
|
# entry, I counted only the differing stage-title lines. Both are legitimate; I
|
|
# did not say which I meant.
|
|
# 📌 The conclusion is untouched -- the stage numbers carry it alone. What this
|
|
# is worth is the shape: a conclusion resting on two legs, one of which does not
|
|
# reproduce. **The leg carrying no weight is the one that went unchecked**, which
|
|
# is the `EN/JP pair` failure one step out, committed while writing up that very
|
|
# failure.
|
|
#
|
|
# => The language reading is refuted for the 37 as well. The whole 63 is now
|
|
# explained by one fact: **adjacent GP_DIALOG entries are unrelated dialogs.**
|
|
# No residue, no special case.
|
|
#
|
|
# 📌 THE 2 REMAINING PAIRS (0/1 and 2/3) are still the only ones with identical
|
|
# element sets, and still unexplained -- but they are no longer anomalous against
|
|
# a hypothesis, because the hypothesis is gone. Whether they are a language pair,
|
|
# a duplicate, or two dialogs sharing every element is untested.
|
|
#
|
|
# ⚠️ WORTH RECORDING ABOUT THE PROCESS, not the disc: this question was left open
|
|
# by both agents with the explicit observation that "nothing rewards closing it".
|
|
# It cost two scans and went AGAINST the reading I had offered. A bound nobody is
|
|
# incentivised to test is exactly where a convenient claim survives -- and the
|
|
# next reader cannot tell whether a bound was respected or merely never revisited.
|
|
|
|
|
|
################################################################################
|
|
# ❌ THE id -> PAK-ENTRY BINDING IS NOT IN THE IMAGE BY THIS ROUTE. 2026-08-31.
|
|
#
|
|
# Positional ordering was already ruled out. The next route was the dialog
|
|
# table's own handlers: if anything joins a dialog id to an archive entry, the
|
|
# code that builds a dialog should touch it.
|
|
#
|
|
# All three handlers were disassembled from the image. All three begin by loading
|
|
# the SAME global -- `lis r11,0x828E` then `lwz r3,0x2B14(r11)` -> 0x828E2B14 --
|
|
# and two take addresses at 0x828E45E0 / 0x828E4640 / 0x828E467C.
|
|
#
|
|
# ❌ EVERY ONE OF THOSE IS BSS. They sit inside a single contiguous ZERO RUN of
|
|
# 364 601 bytes (0x828A28C7 .. 0x828FB900) -- uninitialised data, zero in the
|
|
# image, populated only at runtime.
|
|
#
|
|
# ✅ CONTROL, because an all-zero read is also what a bad address gives: the
|
|
# dialog table itself at 0x820A0A2C reads non-zero through the same arithmetic
|
|
# (10000000 00000000 820a4598 821cfd80). The addressing is right; the data is
|
|
# genuinely absent.
|
|
#
|
|
# => The handlers operate on RUNTIME state. If the id -> entry join exists there,
|
|
# it is visible only in a running game, not in the image. That is a route
|
|
# closed rather than a question answered, and it says where the next attempt
|
|
# has to look: guest memory at 0x828E2B14 with the game up.
|
|
#
|
|
# ⚠️ REACH: one route (the table's own handlers). Not looked at: the archive
|
|
# loader, the GamePart that opens GP_DIALOG, or any table keyed by id elsewhere
|
|
# in the image. "Not in the image" is NOT established -- what is established is
|
|
# "not reachable from the dialog handlers, because they read BSS".
|