screen_children.py walks every resident screen object (vtable 0x820b30b4), reads the element array at +0x08 and the child array at +0x30, and prints the paint permutation with each pivot. Validated in one run off the live title: it reproduces BOTH previously measured orders character-for-character - the splash [0,2,4,6,1,3,5] and the 24-element title permutation - so the next screen it is pointed at can be trusted. The screen that matters was not reached. GP_DIALOG DIFFICULTY carries exactly one primitive and would say whether the game paints it first or last, which is the bit that decides where primitives belong. But (A) does not advance the title screen at holds of 0.10, 0.25 or 0.40s, and neither does START, with the pad verified end to end: pad.py writes /tmp/xenia_pad.txt, the emulator runs with --hid=file --pad_file pointing at it, and the mtime updates on every press. The title is unambiguously the interactive one - PRESS (A) BUTTON is rendered. Recorded as a blocker rather than worked around. Next step is specific: boot with the emulator stdout kept and read the [RE-INPUT] IsUIActive log already present on the canary branch, which would say whether the keystroke is being swallowed emulator-side.
4.9 KiB
A third measured paint order — tool built and validated, screen not reached
Status: ✅ the reader works and is CONFIRMED against both previously
measured screens. 🔴 BLOCKED on the emulator: Ⓐ does not advance the title
screen, so GP_DIALOG's DIFFICULTY box — the screen that would answer the
question — was never reached. Two harness bugs were found and fixed on the way.
Why a third permutation
Drawing the .prm primitives is implemented and measurably right on the title,
but it is off by default because nothing derives where a primitive paints
(structures/ui-prm-primitives.md). A
primitive has no T8aD header, hence no layer key, and the two screens read off
the running game disagree about the answer: the splash paints its primitive
first, the title paints one at slot 4 and another last.
GP_DIALOG's DIFFICULTY box carries exactly one primitive, pceff00.prm, a
50 % black dim. Whether the game paints it first (dimming what is behind the
dialog) or last (dimming the dialog too) is a single bit that would discriminate
between the candidate rules. It is reachable from the main menu: NEW GAME.
The reader
tools/re-capture/screen_children.py walks every resident screen object
(vtable 0x820b30b4), reads the element array at +0x08 (48-byte records,
declaration order) and the child array at +0x30 (pointers to those records,
paint order), and prints the permutation with each element's pivot — which is
what identifies the build in the file.
Validated against both known-good screens in one run, off the live title:
== object 0xbcd25288: 7 elements, 7 children
pivots: 0:(640,360) 1:(250,36) 2:(260,46) 3:(120,44) 4:(130,55) 5:(194,68) 6:(204,78)
paint order: [0, 2, 4, 6, 1, 3, 5]
== object 0xbcd25488: 24 elements, 24 children
paint order: [9, 11, 12, 10, 13, 6, 20, 19, 14, 15, 18, 16, 17, 0, 2, 4, 7, 1, 3, 5, 22, 23, 21, 8]
Both are character-for-character what
structures/ui-screen-runtime.md recorded
from the hand-driven read. So the next screen this is pointed at can be trusted.
Two harness bugs found on the way
Both fail in ways that look like the game misbehaving.
--audio is not a cvar, and eight boot scripts passed it. Xenia calls
ShowSimpleMessageBox from ParseLaunchArguments, before logging is
initialised, so the symptom is a 10×10 window, an empty log, no guest memory, and
a dialog blocking on XIfEvent forever — which reads as a hang deep in the
emulator. run-canary's own header documents this trap; the scripts predate it.
Fixed in all eight, and the boot now reaches the title.
vgamepad no longer exists. The uinput pad was replaced by the --hid=file
driver plus pad.py, but skip_intro.sh still called vgamepad tap A 250. The
script runs without set -e, so the call failed silently and the title branch
pressed nothing while still exit 0 — reporting "TITLE → A" with the game
sitting on the title. Now presses through pad.py and exits 6 if that fails.
That second bug looked like it would explain the standing "Ⓐ at the title works about half the time" note. It does not. See below.
🔴 The blocker: Ⓐ does not advance the title
With the pad verified end to end — pad.py writes /tmp/xenia_pad.txt, the
emulator was launched with --hid=file --pad_file=/tmp/xenia_pad.txt, and the
file's mtime updates on every press — the title screen does not advance:
| press | hold | result |
|---|---|---|
| Ⓐ ×2 | 0.10 s | still title |
| Ⓐ | 0.25 s | still title |
| Ⓐ ×5 | 0.40 s | still title |
| START | 0.30 s | still title |
The screen is unambiguously the interactive one — PRESS Ⓐ BUTTON is rendered (capture). So the input path is wired and the game is waiting for a press it never sees.
The most likely suspect is already instrumented but was not captured this run:
the [RE-INPUT] XamInputGetKeystrokeEx swallowed by IsUIActive log added to
xam_input.cc on the canary branch. boot_menu.sh sends the emulator's stdout
to /dev/null, so nothing was recorded. That is the first step next time:
boot with stdout kept and check whether the keystroke is being swallowed, which
would make this an emulator-side bug rather than a game-side one.
What is not settled
- 🔴 The third permutation itself. The tool is ready; the screen is not reachable.
- ❔ Whether Ⓐ ever worked at this title screen, or whether the "half the time"
note was always the silent
vgamepadfailure plus chance. Thevgamepadbug is now excluded, and the title still does not advance, so something else is wrong. - ❔ Only one screen object was resident at the moment the main menu was believed to be up (the splash), where the title has seven. Unexplained, and possibly a symptom of the same thing — the menu may never have loaded.