re: F1 -- traced C_PAD_RINGBUF; the queue naming inference was wrong
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:
@@ -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.
|
||||
|
||||
## 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 36–48 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 ~4–5 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
|
||||
|
||||
* Whether `C_PAD_DECODER` (which the human's play-test almost certainly went
|
||||
through, and which `pad-decoder-double-tap-not-key-repeat.md` already found
|
||||
has **no timer** on any direction path) reads its bits from the keystroke
|
||||
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.
|
||||
* The actual producer of `C_PAD_RINGBUF`'s contents, per the update above —
|
||||
narrowed to "somewhere reachable from the input-manager singleton," not
|
||||
found.
|
||||
* 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
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
`this+12` that `sub_8220B8C0` masks) back to either the keystroke ring or
|
||||
the polled state buffer. Settles the mechanism question with no emulator
|
||||
time, and turns the 400/100 ms prediction into a decoded fact or rules it
|
||||
out, in either direction.
|
||||
2. **Measure.** Add an opt-in repeat mode to `file_input_driver`'s
|
||||
`GetKeystroke()` (mirroring the SDL driver's `Waiting`/`Repeating` state
|
||||
machine and the same two constants, default OFF so every other scripted
|
||||
script keeps its current one-event-per-press behaviour), boot to the main
|
||||
menu via the established `boot_menu.sh`/`skip_intro.sh` gate, hold a
|
||||
direction, and read the cursor's position per frame off the draw log —
|
||||
the same instrument `f1-menu-repeat-harness-built-not-answered.md` already
|
||||
validated for this exact purpose. Report frame counts at the achieved
|
||||
present rate, per `TEMPORAL-VERIFICATION.md`, not a 400/100 ms guess
|
||||
dressed as measured.
|
||||
1. **Read, not run.** Finish tracing `C_PAD_RINGBUF`'s producer from
|
||||
`sub_821A9DC8`'s input-manager fetch forward, to confirm it copies from
|
||||
the polled `XINPUT_GAMEPAD` fields rather than the keystroke ring.
|
||||
Settles the mechanism with no emulator time. Given the axis-shaped fields
|
||||
already found, this is now confirmation work, not a coin flip.
|
||||
2. **Measure — and the existing driver may already be enough.** If the
|
||||
producer is polled-state as the update above now favours,
|
||||
`file_input_driver`'s `GetState()` needs **no modification**: it already
|
||||
holds a button continuously with no edge suppression. The likelier defect
|
||||
is the *detector*, not the driver — `nav_repeat_and_b.py`'s ~4–5 fps
|
||||
screen-diff undercounts a fast repeat. Re-run with the draw-log
|
||||
position-tracking instrument `f1-menu-repeat-harness-built-not-answered.md`
|
||||
already built and validated (per-frame cursor position, not a coarse
|
||||
pixel-diff), gated on the main menu via `boot_menu.sh`/`skip_intro.sh`
|
||||
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
|
||||
its own (a reversal of a standing claim) not to stack a build-and-boot unit
|
||||
|
||||
Reference in New Issue
Block a user