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.
This commit is contained in:
@@ -6632,3 +6632,34 @@ now a clear reason no amount of re-testing through the file driver will ever
|
||||
produce one.** The next thing that could is patching repeat support into the
|
||||
file driver (proposed twice, not yet built) and re-running this exact
|
||||
capture. `-1.0` stays authored, not guessed, either way.
|
||||
|
||||
## ✅ F1 — numbers, finally: 12 frames initial delay, 4 frames interval
|
||||
|
||||
[`docs/re/f1-repeat-measured-via-driver-patch.md`](../re/f1-repeat-measured-via-driver-patch.md).
|
||||
Built the fix the previous entry named: patched Canary's file driver to
|
||||
emit `REPEAT` keystrokes using the SDL driver's own constants (400 ms
|
||||
delay / 100 ms interval, guest time — opt-in, off by default, every other
|
||||
scripted script unaffected), rebuilt, re-ran the identical held-⬇ capture.
|
||||
|
||||
**The cursor moved continuously this time** — 19 distinct positions over
|
||||
one hold, cycling and wrapping through the whole 5-item list. At this run's
|
||||
achieved **29.87 fps** guest rate:
|
||||
|
||||
* **initial delay: 12 frames** (~402 ms) from the ordinary press-triggered
|
||||
step to the first repeat step;
|
||||
* **steady-state interval: 4 frames** (~133 ms) for 13 of 15 gaps, 3 frames
|
||||
(~100 ms) for the other 2.
|
||||
|
||||
**Use 12 and 4.** ⚠️ The interval is measurably slower than the raw 100 ms
|
||||
constant driving it (100 ms ÷ a ~33.5 ms frame is 2.99, not 4) — the game
|
||||
appears to consume repeat events at its own per-frame pace rather than
|
||||
instantly, and exactly why is not traced. The 4-frame figure is what
|
||||
actually matters: it's what the cursor visibly does, which is what a port
|
||||
needs to match.
|
||||
|
||||
⚠️ **What this is not**: proof of what a *real controller* produces — it's
|
||||
what the game does when fed `REPEAT` events shaped like the SDL driver's,
|
||||
the closest thing to that oracle available in a container with no physical
|
||||
pad. One run only; the corpus's two-run minimum isn't met, and the
|
||||
3-vs-4-frame split in the interval is itself worth a second look. Good
|
||||
enough to stop authoring `-1.0` against, not yet good enough to call final.
|
||||
|
||||
@@ -1108,6 +1108,13 @@ attached to a right direction; only the reason is refuted here.
|
||||
Still 🟡, not ❌: only the file-driver path is tested; the SDL-driver path
|
||||
(what a real controller and the human's play-test went through) is not.
|
||||
[`f1-held-down-measured-no-repeat-via-file-driver.md`](f1-held-down-measured-no-repeat-via-file-driver.md) ⟨capture⟩
|
||||
✅ **The other half done, same day — the Keystroke-`REPEAT` reading is
|
||||
confirmed, not just favoured.** Patched the file driver to emit `REPEAT`
|
||||
at the SDL driver's own constants (opt-in, off by default), rebuilt,
|
||||
re-ran the identical capture: the cursor that moved once and stopped now
|
||||
cycles continuously through the whole menu for as long as the button is
|
||||
held. Measured: 12 frames initial delay, 4 frames steady interval, at
|
||||
29.87 fps guest. [`f1-repeat-measured-via-driver-patch.md`](f1-repeat-measured-via-driver-patch.md) ⟨capture⟩
|
||||
* "F10-arming a UI draw capture is safe on any settled screen" — **implicit
|
||||
in every prior use of it, and false in a fresh container.** A `run-canary`
|
||||
launch with no signed-in profile (every container right after a restart —
|
||||
|
||||
23
docs/re/data/f1-repeat-cursor-transitions.tsv
Normal file
23
docs/re/data/f1-repeat-cursor-transitions.tsv
Normal file
@@ -0,0 +1,23 @@
|
||||
# frame gtick gfreq_hz cursor_quad_y_ndc frames_since_prev_transition
|
||||
# source: xenia_re_ui_draws_01.log, 2026-09-12, f1_hold_capture.py OUTDIR 2.5 repeat (run f1h)
|
||||
# --pad_file_repeat=true: file_input_driver.h emits Keystroke REPEAT at SDL-driver constants (400ms delay/100ms interval, guest time)
|
||||
# quad: page B5B1C73032BA3FA3, ~0.09x0.16 NDC (focus highlight)
|
||||
1 8088762299 50000000 0.05 0
|
||||
3 8092093499 50000000 -0.16 2
|
||||
15 8112191520 50000000 -0.385 12
|
||||
19 8118864353 50000000 0.5025 4
|
||||
23 8125673641 50000000 0.28 4
|
||||
27 8132277847 50000000 0.055 4
|
||||
31 8138935070 50000000 -0.1625 4
|
||||
35 8145620699 50000000 -0.385 4
|
||||
39 8152349650 50000000 0.505 4
|
||||
43 8159014126 50000000 0.285 4
|
||||
46 8164059013 50000000 0.05 3
|
||||
50 8170775552 50000000 -0.1675 4
|
||||
54 8177444513 50000000 -0.3875 4
|
||||
58 8184235878 50000000 0.5025 4
|
||||
62 8190930524 50000000 0.28 4
|
||||
66 8197605724 50000000 0.05 4
|
||||
70 8204312489 50000000 -0.1675 4
|
||||
73 8209322148 50000000 -0.39 3
|
||||
77 8216032739 50000000 0.5 4
|
||||
|
94
docs/re/f1-repeat-measured-via-driver-patch.md
Normal file
94
docs/re/f1-repeat-measured-via-driver-patch.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# 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`](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`](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.
|
||||
@@ -15,7 +15,13 @@ Reuses nav_repeat_and_b.py's proven boot-to-menu gate (glyph counting over a
|
||||
live x11grab pipe) verbatim in spirit -- that gate is the part of this
|
||||
apparatus already known to work -- and replaces its *measurement* half.
|
||||
|
||||
f1_hold_capture.py OUTDIR [hold_secs]
|
||||
f1_hold_capture.py OUTDIR [hold_secs] [repeat]
|
||||
|
||||
`repeat` passes --pad_file_repeat=true, which only exists on a Canary build
|
||||
carrying the patch described in docs/re/f1-repeat-measured-via-driver-patch.md
|
||||
(file_input_driver.h: opt-in Keystroke REPEAT at the SDL driver's own 400ms/
|
||||
100ms constants). Without that patch this flag is simply unrecognised --
|
||||
check `run-canary --help` output before relying on it after a rebuild.
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
@@ -26,6 +32,7 @@ import numpy as np
|
||||
|
||||
OUT = sys.argv[1]
|
||||
HOLD_S = float(sys.argv[2]) if len(sys.argv) > 2 else 2.5
|
||||
REPEAT = len(sys.argv) > 3 and sys.argv[3] in ("1", "true", "repeat")
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
SD = os.path.dirname(os.path.abspath(__file__))
|
||||
PAD = os.path.join(SD, "pad.py")
|
||||
@@ -123,10 +130,12 @@ def main():
|
||||
"run: run-canary --create_profile_if_none=Tag, wait ~5s, kill it",
|
||||
flush=True)
|
||||
return
|
||||
print(f"signing in profile {xuid}", flush=True)
|
||||
print(f"signing in profile {xuid}, pad_file_repeat={REPEAT}", flush=True)
|
||||
args = ["run-canary", f"--logged_profile_slot_0_xuid={xuid}"]
|
||||
if REPEAT:
|
||||
args.append("--pad_file_repeat=true")
|
||||
proc = subprocess.Popen(
|
||||
["run-canary", f"--logged_profile_slot_0_xuid={xuid}"],
|
||||
cwd=OUT, env=env, stdout=canary_log, stderr=subprocess.STDOUT)
|
||||
args, cwd=OUT, env=env, stdout=canary_log, stderr=subprocess.STDOUT)
|
||||
print(f"canary pid={proc.pid}, waiting for window", flush=True)
|
||||
|
||||
T0 = time.time()
|
||||
|
||||
Reference in New Issue
Block a user