re: F1 -- traced C_PAD_RINGBUF; the queue naming inference was wrong
Some checks failed
CI / Native — linux (pull_request) Failing after 34m6s
CI / WASM — Web (pull_request) Successful in 31m37s
CI / Formatting (pull_request) Failing after 1m17s

Continuing the F1 investigation rather than starting a fresh one. Last
iteration left two competing hypotheses open (Keystroke-queue-driven vs
polled-state-driven repeat) and flagged C_PAD_RINGBUF's producer as the
cheapest thing to trace next -- named but not traced.

Traced it this time: C_PAD_DECODER's own constructor (sub_8220B610)
allocates C_PAD_RINGBUF (52-byte control struct, 1024-byte backing buffer,
confirmed against its own Shift-JIS trace strings -- "C_PAD_RINGBUF
initialization" and its allocation-error message). Its update function
(sub_8220B8C0) takes the input-manager singleton as a parameter and reads
the ring at offsets 12, 36, 40, 44 and 48 -- not just the one button word.

Offsets 36-48 are four consecutive fields read together through the same
int-to-double conversion an analog axis would use. XamInputGetKeystrokeEx
has no field for a stick position, so a structure carrying four axis-shaped
fields cannot be a keystroke queue -- it reads as a periodically-refreshed
polled-state snapshot. My own prior reading of the "ring buffer" name as
implying a queue was the wrong inference; refuted by tracing it, recorded
either way per adversarial duty.

This shifts the balance toward the second, previously-uncertain hypothesis:
the file driver's GetState() was always capable of showing real repeat (no
modification needed), and nav_repeat_and_b.py's null result is more likely
a sampling artifact of its ~4-5 fps screen-diff detector than a structural
driver limit. Revises "what would close it" accordingly -- re-run the
existing draw-log position-tracking instrument, gated on the menu properly,
before reaching for a driver change.

Not found: the actual producer that writes into C_PAD_RINGBUF each frame --
narrowed to "reachable from the input-manager singleton fetch in
sub_821A9DC8," not traced to completion. Still no number for issue #1;
this narrows the path to one, further than last iteration but not there.
This commit is contained in:
sylph-decoder
2026-09-12 11:05:22 +00:00
parent 9141741777
commit dfbefff512
2 changed files with 85 additions and 26 deletions

View File

@@ -6583,3 +6583,13 @@ and it competes with a second reading of the evidence that isn't resolved
either (see the page for both). **Keep authoring against `-1.0` until a either (see the page for both). **Keep authoring against `-1.0` until a
capture backs one of these**, same as before — this narrows why the number capture backs one of these**, same as before — this narrows why the number
is missing, it doesn't supply one. is missing, it doesn't supply one.
**2026-09-12 update, same page:** traced `C_PAD_RINGBUF` (the structure the
game's own pad decoder reads) and it carries four axis-shaped fields
alongside its button word — a shape only the **polled** controller state has
(analog sticks have no keystroke equivalent), not a keystroke queue as the
name alone had suggested. That favours the "the driver was always capable,
the coarse screen-diff detector undercounted" reading over the
Keystroke/400-100ms one, though neither is confirmed yet and the actual
producer still isn't found. Still no number — still don't take one from
here.

View File

@@ -108,44 +108,93 @@ longer hold through this same file driver and saw more than one step.
Both routes converge on the same next step below. Both routes converge on the same next step below.
## 2026-09-12 update — traced `C_PAD_RINGBUF`, and the naming inference was wrong
Last iteration's open question was whether `C_PAD_RINGBUF` is fed by the
keystroke ring or the polled state, and said its *name* suggested a queue.
Traced this time, ⟨image⟩, addresses re-verified against `/xenia-rs/sylpheed.db`
(not run against the raw `.pe` this pass — see reach below):
* `C_PAD_DECODER`'s own constructor (`sub_8220B610`) allocates
`C_PAD_RINGBUF` itself — a 52-byte control struct plus a 1024-byte backing
buffer (`リングバッファ確保エラー size=%d` / `C_PAD_RINGBUF 初期化`, both
read off the disc's own Shift-JIS strings) — and stores the pointer at
`this+76`.
* `C_PAD_DECODER`'s update (`sub_8220B8C0`) takes the **input-manager
singleton** (`sub_824574C0`, already named in
[`structures/title-a-press-fault.md`](structures/title-a-press-fault.md))
as its third argument, and reads `this+76`'s ring at offsets **12, 36, 40,
44 and 48** — not just the one button word `input-pad-read-path.md`'s
`sub_8220B8C0` excerpt showed. Offsets 3648 are four consecutive 32-bit
fields, read together, converted through the same int→double stack
round-trip a stick axis conversion would use.
**That is the tell.** `XamInputGetKeystrokeEx` reports discrete digital
button edges — it has no field for an analog stick position. A structure
that carries four axis-shaped fields alongside a button word cannot be a
keystroke queue; it reads as a **periodically-refreshed input snapshot**
(buttons and both sticks together), which only the **polled**
`XINPUT_GAMEPAD` path has to offer. The "ring buffer" name is the authors'
own choice of implementation (a reusable slot, not a growing queue), not
evidence of an event stream — my prior reading of the name was the wrong
inference, now corrected by tracing it.
**This favours the second hypothesis in the section above**: `pad.py`'s
"longer holds auto-repeat" caution is more likely the real mechanism, and
`nav_repeat_and_b.py`'s null result is more likely a **sampling artifact**
of its coarse ~45 fps screen-diff detector than a structural driver limit.
It does not flip the Keystroke/SDL-driver prediction to false outright —
a menu could still layer Keystroke-driven navigation on top of a
polled-state decoder for other purposes — but the balance of evidence moved.
**Not found this pass, and still open:** the actual *producer* that writes
into `C_PAD_RINGBUF`'s offsets 12/36-48 each frame. `sub_8220B8C0` only
*reads* them; nothing in its own 5 856 bytes stores through the chased
`this+76` pointer, so some other function holds a reference to the same
ring instance. `sub_821A9DC8` (one of `sub_8220B8C0`'s three callers) fetches
the input-manager singleton immediately before each call, which is the
strongest lead for where to look next, not yet followed to its own producer.
## What is NOT established, and is the reach ## What is NOT established, and is the reach
* Whether `C_PAD_DECODER` (which the human's play-test almost certainly went * The actual producer of `C_PAD_RINGBUF`'s contents, per the update above —
through, and which `pad-decoder-double-tap-not-key-repeat.md` already found narrowed to "somewhere reachable from the input-manager singleton," not
has **no timer** on any direction path) reads its bits from the keystroke found.
queue or from the polled `XINPUT_GAMEPAD` state. `input-pad-read-path.md`
names this exact gap: *"which of the two each menu action uses is not
decoded."* `C_PAD_RINGBUF`'s name suggests the former (a queue, matching
the keystroke ring's own `{ptr, count, capacity}` shape at `sub_82457038`
`r31+68`) but that is a naming inference, not a trace.
* Whether the menu's consumer of that ring treats a `REPEAT`-flagged * Whether the menu's consumer of that ring treats a `REPEAT`-flagged
keystroke identically to a fresh `KEYDOWN`, or filters it out. keystroke identically to a fresh `KEYDOWN` — now less likely to be the
relevant question at all, per the update above, but not ruled out.
* Which `--hid` driver `run-canary` uses by default, i.e. which of these two * Which `--hid` driver `run-canary` uses by default, i.e. which of these two
numbers (none, or 400/100) the human's own play-test actually went through. numbers (none, or 400/100) the human's own play-test actually went through.
Any of these could be wrong without changing the one thing this page does Any of these could be wrong without changing the one thing this page does
establish: **the previous "none" result cannot stand as evidence either establish: **the previous "none" result cannot stand as evidence either
way**, because its instrument was built to prevent repeat by design. way**, because its instrument was built to prevent Keystroke repeat by
design, and now more plausibly was never testing the path that matters.
## What would close it ## What would close it
Two options, cheapest first: Two options, cheapest first — and the 2026-09-12 update changes which one is
cheapest:
1. **Read, not run.** Trace `C_PAD_RINGBUF`'s producer (whatever writes 1. **Read, not run.** Finish tracing `C_PAD_RINGBUF`'s producer from
`this+12` that `sub_8220B8C0` masks) back to either the keystroke ring or `sub_821A9DC8`'s input-manager fetch forward, to confirm it copies from
the polled state buffer. Settles the mechanism question with no emulator the polled `XINPUT_GAMEPAD` fields rather than the keystroke ring.
time, and turns the 400/100 ms prediction into a decoded fact or rules it Settles the mechanism with no emulator time. Given the axis-shaped fields
out, in either direction. already found, this is now confirmation work, not a coin flip.
2. **Measure.** Add an opt-in repeat mode to `file_input_driver`'s 2. **Measure — and the existing driver may already be enough.** If the
`GetKeystroke()` (mirroring the SDL driver's `Waiting`/`Repeating` state producer is polled-state as the update above now favours,
machine and the same two constants, default OFF so every other scripted `file_input_driver`'s `GetState()` needs **no modification**: it already
script keeps its current one-event-per-press behaviour), boot to the main holds a button continuously with no edge suppression. The likelier defect
menu via the established `boot_menu.sh`/`skip_intro.sh` gate, hold a is the *detector*, not the driver — `nav_repeat_and_b.py`'s ~45 fps
direction, and read the cursor's position per frame off the draw log screen-diff undercounts a fast repeat. Re-run with the draw-log
the same instrument `f1-menu-repeat-harness-built-not-answered.md` already position-tracking instrument `f1-menu-repeat-harness-built-not-answered.md`
validated for this exact purpose. Report frame counts at the achieved already built and validated (per-frame cursor position, not a coarse
present rate, per `TEMPORAL-VERIFICATION.md`, not a 400/100 ms guess pixel-diff), gated on the main menu via `boot_menu.sh`/`skip_intro.sh`
dressed as measured. rather than blind sleeps, holding a direction through the plain
`--hid=file` driver as-is. Report frame counts at the achieved present
rate, per `TEMPORAL-VERIFICATION.md`. The opt-in Keystroke-repeat mode
this page originally proposed adding to the file driver is now a
fallback for if this comes back null again, not the first thing to try.
Not run this iteration — this is the static half, and it is large enough on Not run this iteration — this is the static half, and it is large enough on
its own (a reversal of a standing claim) not to stack a build-and-boot unit its own (a reversal of a standing claim) not to stack a build-and-boot unit