From efee309d8f200647f7292e4949513f815f2fa858 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Wed, 19 Aug 2026 07:48:58 +0000 Subject: [PATCH] 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. --- docs/re/BACKLOG.md | 12 +++ docs/re/ui-paint-order-third-permutation.md | 98 +++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 docs/re/ui-paint-order-third-permutation.md diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index 706d03e..d1716e3 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -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 diff --git a/docs/re/ui-paint-order-third-permutation.md b/docs/re/ui-paint-order-third-permutation.md new file mode 100644 index 0000000..fcf2569 --- /dev/null +++ b/docs/re/ui-paint-order-third-permutation.md @@ -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.