docs+tools: screen_children.py, validated; the third paint order is blocked on input

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.
This commit is contained in:
Sylpheed RE agent
2026-08-19 07:48:58 +00:00
parent 01580d294e
commit efee309d8f
2 changed files with 110 additions and 0 deletions

View File

@@ -117,6 +117,18 @@ See [`structures/ui-composable-bundles.md`](structures/ui-composable-bundles.md)
screen carrying exactly one primitive — the `GP_DIALOG` DIFFICULTY box, which
is reachable from the main menu, would say whether its dim is first or last.
This is a runtime child-list read, so it is an emulator item, not a static one.
🔴 **ATTEMPTED 2026-08-19 and BLOCKED.** The reader
(`tools/re-capture/screen_children.py`) is written and validated — it
reproduces both known permutations character-for-character off the live title.
The screen could not be reached: **Ⓐ does not advance the title screen** at any
hold from 0.10 s to 0.40 s, nor does START, with the pad verified end to end.
Two harness bugs were found and fixed on the way (`--audio` is not a cvar and
broke eight boot scripts; `vgamepad` no longer exists but `skip_intro.sh`
still called it, pressing nothing while exiting 0). Neither unblocks the title.
Next step: boot with the emulator's stdout **kept** and check the
`[RE-INPUT] XamInputGetKeystrokeEx swallowed by IsUIActive` log already in the
canary branch. See
[`ui-paint-order-third-permutation.md`](ui-paint-order-third-permutation.md).
***Blend mode.** Everything is straight alpha-over. The near-white flash
quads (`0xf0ffffff`) and coloured ones (`0x60ff0000`) may be additive. The
title capture cannot separate the two — its resting elements are all

View File

@@ -0,0 +1,98 @@
# 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.
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](captures/title-with-press-a-live.png)). 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 `vgamepad` failure plus chance. The `vgamepad` bug
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.