docs/re: the main menu HAS been reached — the Ⓐ blocker is withdrawn

The previous entry called the title's Ⓐ a hard blocker. It is not: the main menu
is reached and screenshotted (NEW GAME / LOAD GAME / TUTORIAL / OPTIONS /
EXTRAS). What is true is narrower — Ⓐ advances the title only intermittently,
about one attempt in four, with the press verifiably delivered every time and no
Xenia UI active.

Three candidate causes were eliminated with measurements rather than argument:

* IsUIActive is now observable (Canary logs when it swallows a keystroke) and it
  never fires on the failing runs;
* the driver filter is fine — the game polls with flags=3 and the file pad
  reports Controller=1, so FilterDrivers keeps it;
* the game makes no content/user/signin call on the press at all — tracing every
  Xam call around it shows only input polling.

And two traps in my own measuring rig, which cost more than the bug and are
written down so nobody repeats them: a FIFO trace consumer that exits STALLS the
emulator (the guest stops polling — indistinguishable from a dead pad, and it
produced two runs of false evidence), and phase-A's kernel.return events carry a
placeholder return_value of literally 0, so "every keystroke call returns
SUCCESS" was an artifact of the logger, not a finding.
This commit is contained in:
Sylpheed RE agent
2026-08-18 22:18:51 +00:00
parent 44ff5454f9
commit f5603aa107
4 changed files with 54 additions and 54 deletions

View File

@@ -64,63 +64,55 @@ B13EBABEBABEBABE) to slot 0`, and the Profiles Menu panel shows the user
The profile persists on disk, so later runs need only
`--logged_profile_slot_0_xuid=B13EBABEBABEBABE`.
## The blocker: what happens *after* Ⓐ
## Withdrawn: Ⓐ is not blocked — it works, intermittently
With the profile created and signed in — Xenia's own **Profile ▸ Show Profile
Menu** confirms it, `User: SylphRE / XUID: B13EBABEBABEBABE / Assigned to slot: 1`
(the panel counts from 1; the log says slot 0) — Ⓐ at the title **still does not
advance the screen**. Measured across four boots, including one that was allowed
to play the whole intro untouched, with holds of 10 s and bursts of five taps.
**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.
What is *not* the explanation, each checked:
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.
* the emulator menu bar being open (trap 1) — the grabs are clean;
* the pad (trap 2) — Ⓐ skips movies in the same runs;
* the profile being absent (trap 3) — it is present, signed in, and the Sign In
dialog no longer appears;
* an ImGui window being open — but note this generalises trap 1: **any** Xenia UI
window makes `IsUIActive()` true, the Profiles Menu included, so a panel left
open silently disables the guest's input for as long as it is up.
## What was eliminated on the way, so nobody re-walks it
**One observation that did not reproduce, recorded rather than dropped.** In the
run where the Profiles Menu had just been closed, a single Ⓐ **crashed the
guest** — PC `0x824578A0`, guest thread 6, distinct from the boot-path crash
below. Two later attempts to reproduce it (open the panel, close it, press Ⓐ)
produced neither a crash nor a transition. So it is one data point that Ⓐ *can*
reach code the title does not otherwise run, and nothing more.
* **`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.
**The reproducible crash is the cache flush, and it is not what it looked like.**
With the default `--mem_watch=true`, in two runs, the guest died with an access
violation at PC `0x82307128` right after `RtlRaiseException(E06D7363)`. Chasing
that produced a correction and a cheap trigger, both in
[`title-crash-stl-tree.md`](title-crash-stl-tree.md):
### Two traps in the measuring rig itself, which cost more than the bug
* the access violation is a **consequence** of the guest's own `throw` returning
(this build does not unwind guest EH), so the event to study is the throw;
* the throw is `std::out_of_range` from the game's **cache-manager flush**, and
an **incomplete on-disc cache** triggers it ~100 s into a boot;
* the observation that started this — "crash with `--mem_watch=true`, none with
`--mem_watch=false`" — was **confounded** by cache warmth, and is withdrawn:
cold cache with the probe off throws just the same.
* **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 first of those next steps is done**, and it moves the crash out of this
entry into its own: `0x82307128` is inside an MSVC `std::map`/`set` **erase** —
the function carries the string `'invalid map/set<T> iterator'` and the node
offsets to prove it — which makes this a 40-second reproduction of the crash the
Canary handoff blames on the `mem_watch` probe. See
[`title-crash-stl-tree.md`](title-crash-stl-tree.md).
## Still open
**Next step, for whoever picks this up.** What is left:
1. dump the tree nodes from guest memory at the throw, to separate "the guest's
tree is corrupt" from "the emulator left a stale high word in a register" —
the fault address `0x1_0000000C` is consistent with either;
2. the content exports this title imports — `XamContentCreateEnumerator`,
`XamContentGetDeviceState`, `XamUserReadProfileSettings` — logged around the
press, to see whether the guest is handed a bad enumerator or a device state
it does not accept. `\aab216c3\5\…` is the content directory in question.
**What this blocks:** every dynamic experiment past the title — a second UI
screen's paint order, the main menu, mission captures. Everything up to and
including the title screen is unaffected, which is why the paint-order result
above could still be measured.
Why the acceptance is intermittent. The next step is not another guess: capture
the *first* title's Ⓐ under `--cache_throw_diag`-style logging of the guest's own
state machine, or simply script "tap Ⓐ once per second for 30 s on the first
title" and see whether the success rate goes to 1 — a cheap, honest experiment
that also gives a reliable route to the main menu for the UI paint-order work.