# 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. ## ✅ Resolved: Ⓐ was never the problem — the harness was Instrumenting the *other* side of the keystroke path settled it. The existing `[RE-INPUT]` line only fires when `IsUIActive()` swallows a keystroke, so its silence was ambiguous between "the game polls and gets nothing" and "the game does not poll". Two logs were added on the reaching path (canary `f9170fd3f`, corrected in `8d3299975`), and the boot said: ``` [RE-INPUT] XamInputGetKeystrokeEx -> user=0 vk=5800 flags=0001 (call flags 00000003) [RE-INPUT] XamInputGetKeystrokeEx -> user=0 vk=5800 flags=0002 (call flags 00000003) ``` Exactly two events — a KEYDOWN and a KEYUP for `0x5800` (`VK_PAD_A`) — out of ~6 500 polls. **The game polls constantly and receives precisely the press that was sent.** Input delivery was never broken. With all four harness bugs fixed the boot then went straight through: title at 289 s, one Ⓐ, **main menu** ([capture](captures/main-menu-oracle.png)). ### A slip in the instrumentation, kept because it nearly misled The first version logged on `XSUCCEEDED(result)`. `X_ERROR_EMPTY` is `0x10D2` and `XSUCCEEDED` is `((s & 0xC0000000) == 0)`, so an **empty poll passes it** — 6 499 lines of `vk=0000` buried the two that mattered, and for a few minutes the reading was "the driver is flooding the guest with junk keystrokes". Testing `result == X_ERROR_SUCCESS` exactly fixes it. (The pre-existing assignment above shares the quirk and writes `user_index_ptr` on an empty poll; left alone, since the struct is zeroed first.) ## ✅ And the third permutation was obtained — from the main menu Not from `GP_DIALOG`: pressing Ⓐ on NEW GAME leads to the standing black-screen hang, and no dialog object was ever constructed. But the **main menu itself** carries two primitives, which is what the question needed. Recorded in [`structures/ui-paint-order-key.md`](structures/ui-paint-order-key.md): ``` GP_TITLE.pak ratc-index 8, 16 elements paint order: 1 3 4 2 5 8 9 6 7 15 10 11 12 13 14 0 ``` `pteff02.prm` (the 25 % dim) paints 4th, beneath the UI; `pteff00.prm` (the transition fade) paints **last** — both exactly where they sit on the title screen. The composite scores **0.9591 at shift (0,0)** against the live capture. ## 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 * ❔ **`GP_DIALOG` DIFFICULTY is still unreached.** Ⓐ on NEW GAME leads to a black screen the run never comes back from — the standing post-load hang, now the only thing between here and a fourth permutation. * ❔ **The derivation for primitives.** Three measured permutations now, and the positions are stable *by role* (backdrop first, dim at slot 4, fade last), but nothing in the file predicts them. A primitive has no `T8aD` header, so any key must be in the 60-byte declaration entry; four of its words are still unread. * ❔ `ptframe1`/`ptframe2` rest at alpha 0 and so are not composited, but the capture shows the menu frame plainly. ## A fourth permutation: attempted, blocked on an intermittent boot (2026-08-19) The tie-break needs a fourth measured screen with a tied group ([`structures/ui-paint-order-key.md`](structures/ui-paint-order-key.md)), and OPTIONS is reachable from the main menu with three d-pad steps — avoiding the NEW GAME path that hangs. It was not reached. **Advancing past the title is intermittent: 1 success in 3 attempts today**, with the same binary, the same profile and the same procedure. * **Attempt 1** stalled before the title existed at all. The guest was *alive* — RSS steady at 598 MB, `[RE-INPUT] … reached the driver (15001 so far)`, i.e. the game polling input normally — but the screen was black and `screen_children.py` found **only the splash object** resident. So that failure is not a presentation problem: the game never constructed the title screen. Being able to tell those two apart is new, and it is what the reader is for. * **Attempt 2** reached the title at 265 s and then refused four presses — Ⓐ at 0.25 s, 0.25 s and 0.5 s, and START at 0.3 s. Every one was **delivered**: ``` [RE-INPUT] XamInputGetKeystrokeEx -> user=0 vk=5800 flags=0001 [RE-INPUT] XamInputGetKeystrokeEx -> user=0 vk=5800 flags=0002 [RE-INPUT] XamInputGetKeystrokeEx -> user=0 vk=5814 flags=0001 ``` ([log](captures/title-input-delivered-not-acted-on.log)). That is the third independent confirmation that input delivery is fine and the game simply does not act on the press. Waiting longer does not help: the presses were spread from 10 s to ~4 minutes after the title appeared, and the earlier successful run's press landed within seconds of it. No predictor has been found — which is the same conclusion the corpus reached before, now with the input path excluded rather than suspected. ## What is not settled * 🔴 **Why the title accepts Ⓐ only sometimes.** Input delivery is excluded three ways over. The next probe has to be on the guest side: the title's handler is reachable from the emulator-era notes (`sub_821C5690` and the item vtable `0x820b30b4`), so a watchpoint or a log on whatever consumes the keystroke would say whether the game reads it and discards it, or never reads it. * 🔴 A fourth measured paint order, and with it the tie-break. * ❔ Why a boot sometimes stalls at the splash with the guest still running.