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/canary-scripted-input-traps.md
Sylpheed RE agent 9e16331155 docs/re: a second screen captured, and the two title states pinned down
Two results, one of which closes the navigation problem the last three
iterations kept hitting.

**The title states.** The hypothesis was that the attract-loop title is a
non-interactive presentation that omits the PRESS (A) plate. Half right:

* the state distinction is REAL — a single (A) on the title that ends the boot
  opens the main menu, 2 of 2 in independent runs, one of which never pressed
  F10; the title the attract loop returns to accepts nothing, not (A), START, B,
  BACK, X or Y, across dozens of delivered presses;
* the proposed tell is REFUTED — capturing the draws in both states in one run
  gives 13 quads at identical rects, ptbtn00 and ptbtn00f included. The two are
  identical to the renderer and different only to the guest.

So there is now a reliable route to the menu: first title after boot, one tap.

**The main menu's paint order**, captured with it. Its sprites are GP_TITLE
build 5's, and ptframe1/ptframe2/ptbtn01f land within 4 px of their declared
resting placements — an independent placement check on an untouched bundle.

It does NOT settle the ordering question, and the entry says so: build 5 lists
its background at indices 1-2, so declaration order and "background first"
predict the same sequence here. Same failure mode as GP_READY_ROOM/GP_OPTIONS.
What it does establish is that the title's disagreement is not a decode
artefact — same pak, same engine, one build that follows its table and one that
does not.
2026-08-18 23:10:04 +00:00

161 lines
9.1 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.
# Getting past the title screen in the container — three traps and one blocker
**Status:** ✅ `CONFIRMED` for the three traps (each reproduced, and two of them
fixed in the toolkit). 🔴 **blocked** on reaching the main menu: the guest reads
Ⓐ at the title, and the step *behind* Ⓐ either crashes it or stalls it.
Written down because each of these presents as "the pad is dead" or "the game
ignores input", and each has a completely different cause.
## 1. F10 opens the emulator's menu bar, and that swallows every guest keystroke
F10 is the RE hotkey (ship capture, and now `log_ui_draws`). It is *also* the
toolkit key that opens the application menu bar, and it does both. With that menu
open, `XamState::IsUIActive()` is true, so `XamInputGetKeystrokeEx` returns
`X_ERROR_SUCCESS` with a **zeroed** keystroke before the driver is ever asked
(`xam_input.cc`). The guest polls, gets an empty event, and does nothing —
forever, and with no error anywhere.
This cost most of an iteration. Symptom: a run presses F10 to arm a capture,
then every later Ⓐ does nothing; the title sits there while the pad log happily
prints `[file-pad] keystroke vk=5800 down`. Screenshot
[`captures/title-with-emulator-menu-open.png`](captures/title-with-emulator-menu-open.png)
shows the giveaway — `Open… / Open Recent / Install Content… / Exit` over the
title art. It is easy to miss because the menu is at the far left of the window
and the game is still animating behind it.
**Fix:** after any F10, click once on the game surface to dismiss the menu.
`Escape` does *not* close it. `ui_draw_capture.sh` now does this.
## 2. The pad is fine — the driver was never the problem
Worth stating because the file-pad's own history (`--hid=file`, `GetKeystroke`
implemented specifically so "PRESS Ⓐ BUTTON" would work) makes it the natural
suspect. It delivers: the log shows `vk=5800 down`/`up` per tap, and Ⓐ during a
movie skips the movie, every time. When the title ignores Ⓐ, the pad is not why.
## 3. Ⓐ at the title needs a signed-in profile — and this container cannot type one in
With no profile, Ⓐ **is** handled: the guest calls `XamShowSigninUI` and Xenia
pops its Sign In dialog ([`captures/title-signin-dialog.png`](captures/title-signin-dialog.png)),
`ProfileManager: Found 0 Profiles`. That dialog is the proof the guest reads the
button.
The dialog cannot be completed from here: "Create Profile" opens an **ImGui text
field** for a gamertag, and synthetic X key events do not reach it. Tried, all
with the window focused (`xdotool getwindowfocus` = the game window):
`xdotool type --window`, `xdotool type` via XTEST, single `xdotool key` presses,
clicking the field first. The field stays empty; `Create` with an empty gamertag
is refused. Mouse clicks *do* work (they opened the dialog and the sub-panel), so
it is text entry specifically.
Xenia's own **Profile ▸ Show Profile Menu** panel does have a working `Create
Profile` button, and mouse clicks reach it — but the gamertag it then asks for is
the same ImGui field, so the UI route dead-ends in the same place.
**Fix, landed in Canary** (`auto/re-ui-draw-order`): a `--create_profile_if_none=<gamertag>`
cvar that calls `ProfileManager::CreateProfile(tag, autologin, default_xuid)` at
startup when no profile exists. `default_xuid` is fixed at `B13EBABEBABEBABE`, so
a script can then pass `--logged_profile_slot_0_xuid=B13EBABEBABEBABE` and be
deterministic. Verified: `ProfileManager: RE bootstrap profile 'SylphRE' ->
created`, then on the next boot `Found 1 Profiles` / `Loaded SylphRE (GUID:
B13EBABEBABEBABE) to slot 0`, and the Profiles Menu panel shows the user
([`captures/xenia-profile-panel-signed-in.png`](captures/xenia-profile-panel-signed-in.png)).
The profile persists on disk, so later runs need only
`--logged_profile_slot_0_xuid=B13EBABEBABEBABE`.
## Withdrawn: Ⓐ is not blocked — it works, intermittently
**The main menu has been reached**
([`captures/main-menu-reached.png`](captures/main-menu-reached.png) — NEW GAME /
LOAD GAME / TUTORIAL / OPTIONS / EXTRAS). So the previous version of this entry,
which called the title's Ⓐ a hard blocker, is **withdrawn**. What is true is
narrower and more annoying: **Ⓐ advances the title only sometimes** — one success
in roughly four attempts, with the press verifiably delivered every time
(`[file-pad] keystroke vk=5800 down/up` in the log) and no Xenia UI active.
The successful press landed on the **first** title after boot, before any attract
cycle; the failures were on a title that had come round again. That is a
hypothesis, not a rule — a later attempt on a first title also failed (the screen
flickered to something else for one sample and came back), so timing is involved
and the window has not been pinned down.
## What was eliminated on the way, so nobody re-walks it
* **`IsUIActive()` is now observable.** `XamInputGetKeystrokeEx` returns
`X_ERROR_SUCCESS` with a zeroed keystroke *before consulting any driver* when a
Xenia dialog is up — an invisible input blackout. Canary now logs
`[RE-INPUT] XamInputGetKeystrokeEx swallowed by IsUIActive (dialogs=N …)` when
that happens. On the runs where Ⓐ failed, **it never fires**, so the blackout
is not the cause there. (It *is* the cause after F10 — trap 1 above.)
* **The driver filter is fine.** The game polls with `flags = 3`, and
`InputSystem::FilterDrivers` keeps a driver when `flags & InputType` is
non-zero; the file pad reports `Controller = 1`, so it is selected.
* **The game does not query sign-in on the press.** Tracing every `Xam*` call
around a press shows only `XamInputGetCapabilities`, `XamInputGetKeystrokeEx`
and `XamInputGetState` — no content, user or signin call at all.
### Two traps in the measuring rig itself, which cost more than the bug
* **A FIFO trace consumer that exits stalls the emulator.** `--phase_a_event_log_path`
writes 1.5 GB a minute, so it was pointed at a named pipe with a `grep` on the
other end. When that reader goes away, the emulator blocks on the write — and
the symptom is *exactly* "the pad is dead": the guest stops polling, the driver
never refreshes, nothing is logged. Two runs' worth of "the driver never
reacts" was this, not the game.
* **The trace's `return_value` is a placeholder.** `phase_a_bridge::EmitReturn`
is called as `EmitReturn(name, 0)` — literally zero — so every `kernel.return`
event reads `"status": "0x00000000"`. "Every keystroke call returns SUCCESS"
looked like hard evidence for the `IsUIActive` theory and means nothing. The
theory had to be tested with a real log line instead.
## The scripted reproduction was tried, and it failed
"Tap Ⓐ once per second and see whether the success rate goes to 1" was the next
step this entry named. It was run (`tools/re-capture/menu_draw_capture.sh`), and
the answer is **no**:
| run | what was pressed | result |
|---|---|---|
| E | **one** Ⓐ on the first title after boot | **main menu** |
| F | **40** × Ⓐ at 1/s on the title, then one each of START, B, BACK, X, Y | title, unchanged |
| G | Ⓐ every 4 s through the whole boot (88 presses) | permanent **black screen**, no crash, never recovers |
In run F every press was delivered — the driver logs `keystroke vk=5800 down/up`
for each — and the new `[RE-INPUT]` line never fired, so nothing was swallowed.
The guest received them and ignored them. **No button does anything** on that
title: START, B, BACK, X and Y are as inert as Ⓐ
([`captures/title-unresponsive-attract.png`](captures/title-unresponsive-attract.png)).
Run G is worth its own warning: hammering Ⓐ through the boot is not merely
useless, it **breaks the run** — 88 presses left a black screen that never came
back (no crash, no throw, the emulator healthy). The script no longer taps
during boot; the intro takes ~3.5 minutes and gets to the title by itself.
**So the difference is not the number of presses or the button — it is which
title you are looking at**, and that is now measured rather than guessed:
* the title that **ends the boot sequence** accepts a single Ⓐ and opens the main
menu — **2 of 2**, in independent runs, one of which never pressed F10;
* the title the **attract loop returns to** accepts nothing — Ⓐ, START, B, BACK,
X, Y, dozens of presses, every one delivered, none swallowed.
**The recipe, then:** launch, wait for the first `title` classification, tap Ⓐ
once. Do not tap during the boot, and do not wait for a second title.
The tell is *not* visible in what the game draws — a `log_ui_draws` capture in
each state gives 13 quads at identical rects, `ptbtn00` (the `PRESS Ⓐ BUTTON`
plate) included in both. See
[`ui-title-paint-order-capture.md`](ui-title-paint-order-capture.md).
## Still open
**Why** the attract title stops accepting input, given that it draws exactly the
same thing. Nothing in the composition, the input path or the sign-in state
differs; the difference is in the guest's own state machine, and reading it means
reading that code.
Not open any more, and worth stating because it was the thing blocking the RE:
reaching the main menu is now routine (first title, one Ⓐ), and the second
screen's paint order has been captured with it.