This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/ui-paint-order-third-permutation.md
Sylpheed RE agent e3ad6ad28c tools: fix four boot-harness bugs; find the swallowed-keystroke cause and refute it
Chasing why (A) does not advance the title turned up four harness bugs, one real
root cause, and a refutation of that root cause as the explanation.

FIXED, each verified:
  * boot_menu.sh sent the emulator stdout to /dev/null, which is why none of the
    rest was visible. It now keeps a log and prints its path.
  * skip_intro.sh probed pixel (625,618) for the green (A) glyph - a 1280x720
    coordinate. screenshot returns the 1279x675 game surface, where that point
    is on the copyright line and reads (8,17,31). Symptom: a 600s TIMEOUT with
    the title on screen. Now classified with screen_id.py; next boot printed
    "TITLE at 239s -> A".
  * skip_intro.sh no longer taps through the movies. Making that press real (the
    vgamepad fix) was a REGRESSION: the boot then reached the title in 90s and
    that title accepted nothing. menu_draw_capture.sh records the same finding
    independently.
  * boot_menu.sh signs in whichever profile exists instead of a hard-coded XUID.

ROOT CAUSE FOUND: naming a XUID with no profile behind it opens a sign-in
dialog, xam_dialogs_shown_ goes to 1, and IsUIActive() then discards every
XamInputGetKeystrokeEx. Measured 8,388,601 swallowed calls in one boot with the
pad log showing presses arriving normally throughout. Only B13EBABEBABEBABE
exists on disc; the script asked for E0300000EFBEA3D4. Signing in the real
profile takes the swallow count 8,388,601 -> 0.

AND REFUTED: that is not why the title is stuck. With a valid profile and zero
swallowed keystrokes, a single (A) on the title that ends the boot still does
nothing. The dialog bug was real and worth fixing; it is not the explanation.

Next probe is specific: --log_mask=13 records no kernel calls, so it is not even
known whether the game polls at the title. A log on the SUCCESSFUL
XamInputGetKeystrokeEx return, mirroring the one already on the swallow path,
answers it in one boot.
2026-08-19 08:21:08 +00:00

170 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`](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`](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.
The boot log states it outright, which is the only reason it was caught:
```
movie (rmse 1662) at 434s -> skip A
tools/re-capture/skip_intro.sh: line 44: vgamepad: command not found
```
Every "-> skip A" and every "TITLE -> A" this harness has ever printed pressed
nothing. Note the timing too: the movie ran to **434 s** because it was never
skipped, which is most of why a boot takes seven minutes here.
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
log shows `[file-pad] keystroke vk=5800 down/up` for every press — the title
screen does not advance. Holds of 0.10 s, 0.25 s and 0.40 s, Ⓐ and START, on a
screen that unambiguously renders PRESS Ⓐ BUTTON
([capture](captures/title-with-press-a-live.png)).
### ✅ A root cause found, measured, and fixed — a sign-in dialog
Keeping the emulator's stdout (see below) produced the line immediately:
```
w> [RE-INPUT] XamInputGetKeystrokeEx swallowed by IsUIActive (dialogs=1 nui=0, 8388601 so far)
```
`xam_dialogs_shown_` is **1**, so `XamState::IsUIActive()` returns true and
`xam_input.cc` discards every keystroke — **8 388 601 of them in one boot**,
while the pad log shows presses arriving normally the whole time.
The dialog is a **sign-in UI opened because the requested profile does not
exist**. `boot_menu.sh` passed `--logged_profile_slot_0_xuid=E0300000EFBEA3D4`;
the only profile on disc is `B13EBABEBABEBABE`, and the boot log says so at line
309: `FindProfiles: Adding profile B13EBABEBABEBABE to profile list`,
`ProfileManager: Found 1 Profiles`. Naming a XUID with nothing behind it is not
a harmless no-op.
Fixed — `boot_menu.sh` now signs in whichever profile exists. **Swallowed
keystrokes: 8 388 601 → 0.**
### 🔴 Refuted: that was not sufficient
Fixing the swallow does **not** make the title accept Ⓐ. With a valid profile
signed in and **zero** swallowed calls, the title still does not advance. So the
dialog bug was real and worth fixing, and it is not the explanation.
That also means `menu_draw_capture.sh`'s note — "the title that ends the boot
sequence accepts a single (A) (2 of 2 runs)" — no longer reproduces. The run
here reached the title at 239 s, pressed once, and stayed.
## Three more harness bugs, all of which read as 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 — a hang deep in the emulator, to look
at. `run-canary`'s own header documents this trap; the scripts predate it.
**`vgamepad` no longer exists.** `skip_intro.sh` still called
`vgamepad tap A 250`, and without `set -e` the call failed silently: the branch
pressed **nothing** and still `exit 0`. The boot log states it outright —
```
movie (rmse 1662) at 434s -> skip A
tools/re-capture/skip_intro.sh: line 44: vgamepad: command not found
```
— so every "→ skip A" and "TITLE → A" this harness ever printed pressed nothing,
and the intro ran to 434 s because it was never skipped.
**Do not tap through the movies.** Making that press real for the first time was
a *regression*: the boot then reached the title in 90 s and that title accepted
nothing at all. `menu_draw_capture.sh` records the same thing independently —
"a run that tapped (A) every 4 s through the boot delivered 88 presses and ended
on a black screen that never came back", and "the title the attract loop returns
to accepts nothing at all, and 40 taps at 1/s do not change that". `skip_intro.sh`
now waits the intro out and spends its single press on the title that ends the
boot.
**The title probe sampled a dead pixel.** `skip_intro.sh` tested `p{625,618}` for
the green Ⓐ glyph — a coordinate from a 1280×720 grab. `screenshot` returns the
**1279×675 game surface**, where that point is on the copyright line and reads
`(8,17,31)`. The symptom was a 600 s `TIMEOUT` with the title on screen the whole
time. Now classified with `screen_id.py`, which called those same frames "title";
the next boot printed `TITLE at 239s -> A`.
**And `boot_menu.sh` sent the emulator's stdout to `/dev/null`** — which is why
none of this was visible. A boot harness that discards the emulator's own account
of itself can only ever report symptoms. It now keeps a log and prints its path.
## What is not settled
* 🔴 The third permutation. The reader is ready and validated; the screen is not
reachable.
* 🔴 **Why the title does not advance.** The sign-in dialog is excluded, the
`vgamepad` no-op is excluded, and the pad is confirmed delivering. The next
probe needs a log level that records kernel *calls* — at `--log_mask=13` the
only mentions of `XamInputGetKeystrokeEx` in an 84 KB log are import-table
entries, so it is not even known whether the game polls at the title. A
targeted log on the **successful** return path (mirroring the existing one on
the swallow path) would answer it in one boot.
* ❔ Only **one** screen object was resident when the harness believed the main
menu was up, where the title has seven.