Files
Sylpheed/docs/re/f1-repeat-measured-via-driver-patch.md
sylph-decoder ed9a7d702d re: F1 answered -- 12 frames initial delay, 4 frames interval, by patching in the repeat the driver lacked
Direct follow-through on this session's own named next step. The prior
result (f1-held-down-measured-no-repeat-via-file-driver.md) concluded the
file driver cannot show menu repeat because its GetKeystroke() never emits
a REPEAT-flagged event, and that the menu's repeat is very likely driven by
that flag rather than raw polled state. Testable, so tested: patched
/canary/src/xenia/hid/file/file_input_driver.h to add opt-in repeat behind
a new --pad_file_repeat cvar (off by default, every other scripted script
unaffected), using the SDL driver's own constants verbatim
(HID_SDL_REPEAT_DELAY/_RATE = 400/100, guest-time ms via
Clock::QueryGuestUptimeMillis) rather than re-deriving them. Incremental
rebuild, ~1 minute (only xenia_main.cc needed recompiling).

Control: the driver's own log confirms repeated keystroke events fire as
designed, zero crashes.

Result: re-ran the identical held-DOWN capture. The cursor that moved once
and stopped in the null result now cycles continuously through the whole
5-item menu, wrapping, for as long as the button is held -- the null result
was real for that driver path, and giving the driver the one thing it
lacked reverses it completely.

Measured at this run's achieved 29.87 fps guest rate: 12 frames (~402ms)
initial delay from the press-triggered step to the first repeat step; 4
frames (~133ms) steady-state interval for 13 of 15 gaps, 3 frames (~100ms)
for the other 2 -- slower than the raw 100ms constant driving it, which
this page flags but does not trace further (most likely the game batches
drained keystrokes per its own frame tick rather than reacting to each one
instantly). The 4-frame figure is what matters for the port: it's what the
cursor visibly does.

Honestly scoped: this measures what the game does when FED repeat events
shaped like the SDL driver's, not a capture through an actual physical
controller (none exists in this container) -- classified measured, not
decoded, for exactly that reason. One run only; the corpus's two-run
minimum isn't met, flagged rather than overclaimed.

f1_hold_capture.py gains an optional `repeat` argument. The Canary source
patch itself lives in /canary, outside this repo (Canary source, not
sylpheed-formats) -- fully described inline in the finding doc so it can be
reapplied if that tree doesn't persist across a container reset.

Reference data: docs/re/data/f1-repeat-cursor-transitions.tsv -- every
transition's frame, guest tick and Y position, not the raw draw log.
2026-09-12 12:31:05 +00:00

5.2 KiB
Raw Permalink Blame History

F1 — measured: ~12 frames initial delay, ~4 frames steady interval, by giving the driver the repeat it lacked

Status: measured (⟨capture⟩), via a purpose-built instrument, not the game's own emulated hardware path. This is the direct follow-through on f1-held-down-measured-no-repeat-via-file-driver.md's named next step. 2026-09-12, same session.

What changed

The prior page's conclusion: the file driver cannot show menu repeat because its GetKeystroke() never emits a REPEAT-flagged event, by design, and the menu's repeat is very likely driven by that flag rather than by polling raw held state. That is a testable claim, so it was tested: patched /canary/src/xenia/hid/file/file_input_driver.h to add opt-in repeat, gated behind a new --pad_file_repeat cvar (off by default — every existing scripted script keeps its one-event-per-press behaviour unchanged), using the exact same constants as the SDL driver (HID_SDL_REPEAT_DELAY/_RATE = 400/100, guest-time milliseconds via Clock::QueryGuestUptimeMillis()) rather than re-deriving them. Rebuilt Canary (build-canary Release, incremental, ~1 minute — only xenia_main.cc and the header needed recompiling). Full patch in /canary; not yet upstreamed into this repo's own tooling copy, since it's Canary source, not sylpheed-formats.

Control: the driver's own log confirms the mechanism fires as designed — a held ⬇ produced repeated [file-pad] keystroke vk=5811 repeat lines at the driver level, and zero crashes.

The result — the game DOES react to REPEAT, decisively

Re-ran the exact same capture as the null result (boot to menu, hold ⬇ for 2.5 s wall-clock, draw-log per frame), this time with --pad_file_repeat=true. Tracking the same focus-highlight quad (page B5B1C73032BA3FA3) that showed exactly one move and then nothing in the prior run: 19 distinct positions across the capture, spanning nearly the full NDC range — the cursor visibly cycled through the whole 5-item list multiple times, wrapping, for as long as the button was held. The null result was real for that driver path; giving the driver the one thing it lacked reverses it completely.

Reference data: data/f1-repeat-cursor-transitions.tsv — every transition's frame, guest tick and Y position, derived from the draw log (log itself not committed, per the corpus's game-content rule).

The numbers, in frames at this run's achieved guest rate

Achieved: 29.87 fps (432 frames / 14.46 s guest time) — reported before the numbers below, per TEMPORAL-VERIFICATION.md.

frame since previous
initial position 1
press-triggered step (not a repeat — the ordinary KEYDOWN edge) 3 2 frames
first repeat-driven step 15 12 frames (402 ms)
every step after, ×15 19, 23, 27, 31, 35, 39, 43, 50, 54, 58, 62, 66, 70, 73, 77 4 frames ×13, 3 frames ×2

Initial delay: 12 frames (~402 ms guest) from the press-triggered step to the first repeat. Strikingly close to the SDL driver's own 400 ms constant — expected, since that constant is what armed the timer, and the closeness is a sanity check on the measurement more than a new fact.

Steady-state interval: predominantly 4 frames (~133 ms), with 2 of 16 gaps at 3 frames (~100 ms). This is not the same as the 100 ms constant that drives the underlying REPEAT emission — it is measurably slower and shows the frame-vs-100ms aliasing pattern you'd expect if the game consumes repeat events at its own per-frame pace rather than reacting to every one instantly (100 ms ÷ 33.5 ms/frame = 2.99, not 4). The 4-frame figure is the one that matters for the port: it is what the cursor visibly does, regardless of how the underlying keystroke stream is paced. The mismatch against the raw driver constant is noted, not resolved — tracing exactly where the extra ~30 ms per step goes (game-side frame batching of drained keystrokes, most likely, given sub_82457038 drains up to three XamInputGetKeystrokeEx calls per poll) is future work, not needed to answer what's asked here.

What this is not

Not proof this is what a real controller produces. It is proof of what the game does when fed REPEAT events at the same rate and shape the SDL driver would produce — the closest thing to that oracle this container can exercise, since no physical controller exists here. If a future session can compare against an actual SDL-driver capture, this is the number to check it against. Classified measured, not decoded, for exactly that reason: nothing about this rate is decoded from the disc, and the delay/interval constants were chosen by us (borrowed from Canary's own upstream driver, not the game).

Reach

One run. The corpus's own two-run minimum for reproducibility is not met — flagging rather than overclaiming. The steady-state interval's 3-vs-4-frame split (14:2 across 16 gaps) is itself worth a second run to see if that ratio holds or was a one-off aliasing artefact of this particular boot's exact frame phase. The initial-delay figure rests on a single transition and would benefit from the same repeat.