WIP: feat(input): opt-in Sprint on Shift or the left stick's press, without the roll #196

Draft
thegrandao wants to merge 11 commits from thegrandao/Longshot:feat/sprint into main
First-time contributor

Why

On keyboard and mouse there is no way to run without holding the game's A key, and pressing A while moving makes Link roll first. This adds an opt-in Sprint control: with the Controls tab's Sprint row on, hold Shift, or press the left stick once on a controller, and Link runs at once, without rolling and without talking to or picking up what is in front of him; tapping A mid-sprint still rolls.

What changed

  • input (host/nn_hid.c): a Sprint control, HOS_KEY_SPRINT after the ocarina note controls, with keys and two gamepad slots that trade inputs with the Switch buttons by the same slot rules. sprint_step holds A, the game's own run, only while the left stick or the movement keys are pushed. Shift runs while held; the left stick's press is a toggle that lasts until the stick returns to center.
  • A held, never pressed (nx2/oot_patches.py patch_sprint_press): the game's controllers work out "pressed this frame" as held now and not held before, in f_8038a220 (the four controllers gameplay reads) and f_834a3680 (the menus' one). The patch clears hos_sprint_press_mask from "pressed" there; sprint_step sets it to A's bit while it is Sprint that holds A. The game sees A held (its run) and never pressed: no roll, no talk, open or pick-up, and no pause before the run. report() warns if the patch is not found in both functions.
  • Rolling while sprinting (sprint_step): as the player's own A goes down mid-sprint, Sprint lets go of A for SPRINT_ROLL_GAP_MS (50 ms, a frame at 20 fps) and holds it again with the mask 0, so the game sees the player's press: a roll (or talk, open, pick up). A tap shorter than the gap still counts.
  • The ocarina (npad_states): while the ocarina waits for a note (hos_oc_wait 1), Sprint holds nothing and its toggle ends, as with the menu open: A is a note there and the stick bends its pitch.
  • menu (gpu/vk/vk_menu.cpp): the Sprint row on the Controls tab, off by default, with a warning in its text that it changes the controls. apply_sprint_layout runs when the row changes and after the Controls page's Reset while it is on: on, Sprint gets Shift (B moves to V, when no control has V) and the left stick's press; off, they go back. Only where they are still the defaults and Sprint has none of its own: controls the player rebound are left alone. The Controls page gets a Sprint row (pad_sprint / key_sprint lines).
  • API (gpu/vk/vk_api.h): MKWVK_API_VERSION 22 (binding/bind take gamepad inputs for HOS_KEY_SPRINT), HOS_OPT_SPRINT 54 (after the frame rate cap's 53, before Damage taken's 55).
  • docs: the README's option table and a Sprint paragraph.

Flags and settings

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
Controls tab > Sprint (HOS_OPT_SPRINT, 54) Sprint on Shift and the left stick's press; turning it on moves Shift from B to Sprint (B to V) and the stick's press from LS to Sprint, off puts them back Off Off by default: it changes the player's controls Keyboard players, and pads where holding A to run is tiring Players who already run with A Linux, Steam Deck
HOS_SPRINT_STILL_MS=<ms> Starts a sprint with the stick let go for that long: the older way round the roll, for a game built without the patch 0 (off) Leave off; a fallback only A game without the patch A game with it Most values; 120 checked in the tests

Measurements

Baseline: this PR @ 629c9d9a, Sprint off
main has no Sprint row, so the cost of the feature is measured as it is turned on: Sprint off against on in one session, on the same build (with the row off, sprint_step returns at once and the bindings are main's).
main: 0c74253d (2026-11-13) when measured; the PR is now on main d9648863
this PR: feat/sprint @ 629c9d9a (the game built from it), now 7e4f94b5
Why the numbers still hold: they compare Sprint on against off on one build, and what changed since is main's own work (in both columns alike) and, on Sprint's side, a test of hos_oc_wait per input read, a patch that reads less text at build time, a moved comment and test stubs: nothing that runs per frame beyond one compare.
Hardware: NVIDIA GeForce RTX 3060 (driver 32.0.16.2114), Intel Core i5-12400F, 32 GB RAM, Windows 11 Home (26200)
Scene: Hyrule Field, standing still; Ultra preset, output 2048x1152, Resolution 100%, Lanczos, no frame generation, no frame rate cap, V-Sync off
Method: four runs of 60 to 120 s, alternated (on, off, on, off), after a warm-up; the vi: fps lines of longshot_log.txt (one per 5 s), each tagged with the options file's sprint= as it was written, the samples within 10 s of a switch left out; the median of each run, and of each side
How to reproduce: this PR's game, a spot in Hyrule Field, the FPS counter on; switch the Controls tab's Sprint row between runs and read the vi: lines

this PR @ 629c9d9a, Sprint off this PR @ 629c9d9a, Sprint on
fps, run 1 / run 2 (median) 51.5 / 51.3 51.8 / 51.2
fps, median of all samples 51.5 (46 samples) 51.5 (29 samples)
frame time, median 19.44 ms 19.42 ms
(Sprint on, run 1)  [hos Presentation Thread] vi: 11373 frames presented (51.8 fps over the last 5.0 s, interval 1)
                    [hos Presentation Thread] vi: 11634 frames presented (52.0 fps over the last 5.0 s, interval 1)
(Sprint off, run 1) [hos Presentation Thread] vi: 15777 frames presented (51.5 fps over the last 5.0 s, interval 1)
                    [hos Presentation Thread] vi: 16029 frames presented (50.2 fps over the last 5.0 s, interval 1)
(Sprint on, run 2)  [hos Presentation Thread] vi: 22468 frames presented (50.2 fps over the last 5.0 s, interval 1)
                    [hos Presentation Thread] vi: 22721 frames presented (50.4 fps over the last 5.0 s, interval 1)
(Sprint off, run 2) [hos Presentation Thread] vi: 26557 frames presented (51.3 fps over the last 5.0 s, interval 1)
                    [hos Presentation Thread] vi: 26812 frames presented (50.8 fps over the last 5.0 s, interval 1)

The game-free checks, on this PC (llvm-mingw): main @ d9648863 452 passed, 8 failed, 73 skipped; this PR @ 7e4f94b5 453 passed, 8 failed, 73 skipped (test_sprint the extra pass). The same 8 fail on both: test_bake_portability, test_fpx_sticky, nx2/test_nup_paths, test_spawn_emit, test_check_third_party, test_lift_cache, test_menu_i18n, test_release.

Testing

Tested: tests/test_sprint.py: the host's binding table (C) linked with the menu's apply_sprint_layout (C++) against options files (a new install; on, on again, off; a binding swap; Reset; LS with two inputs; rebound controls kept; V taken; Sprint bound by the player left alone); sprint_step compiled alone (Shift held, the toggle and its end, nothing with the menu open, the press mask Sprint's only, the roll gap, a tap shorter than the gap, the player's A held before Sprint starts, HOS_SPRINT_STILL_MS); patch_sprint_press on lifted text (both functions, nowhere else, once, a drifted anchor left alone, a body that ends at its own brace); npad_states pausing Sprint during the ocarina's note wait. test_input_background, test_menu_env_overrides, test_rumble_menu, test_framegen_preset, test_readme_menu_rows and test_upscaler_round_trip (new on main: it cuts set_row) updated. In game, on an earlier build of this branch with the same Sprint code (Windows 11, RTX 3060, DualSense and keyboard), with the Sprint row on: Shift and the left stick's press run at once with no stutter and no roll; running past things Link could interact with leaves them alone; tapping A mid-sprint rolls, then Link keeps sprinting. The patch applied in both functions (oot_patches: Sprint's A without its press in code_003.c and code_040.c). On Longshot, this PR @ 629c9d9a (longshot v3.0.0-rc.3-6-g629c9d9a, Windows 11, RTX 3060): the build applied the patch in both functions again, the Sprint row and its warning, the Controls page's Sprint row (L3 and Shift, the left stick's press left at None) as in the screenshots, Sprint running in Hyrule Field with no roll or stutter at the start, tapping A mid-sprint rolling and Link sprinting on after it, and the fps runs above. On this PR @ 0046c6cf, before the rebase onto d9648863 that changed nothing of Sprint's (Windows 11, RTX 3060), Sprint on, held while doing each of these: the ocarina taken out mid-sprint (no note played by Sprint's A), swimming, riding Epona, pushing and pulling a block, carrying something: Sprint does nothing unwanted in any of them, and each works as without it.

Not tested: Linux (not built here: the Linux build and a run there are T-MKEL's).

Needs other testers:

  • Linux or Steam Deck, with a gamepad: Sprint on, the left stick's press runs without a roll, tapping A mid-sprint rolls, turning it off restores LS, expecting the same as on Windows (T-MKEL)

Screenshots

main has no Sprint row to compare with.

this PR @ 629c9d9a: the Controls tab's Sprint row, on, with its warning

this PR @ 629c9d9a: the Controls tab's Sprint row

this PR @ 629c9d9a: the Controls page with the row on: Sprint on L3 and Shift, the left stick's press left at None

this PR @ 629c9d9a: the Controls page's Sprint row

Risk and rollback

Off by default, so nothing changes for players who leave the row off: the bindings are the old ones and sprint_step returns at once. With it on, a game update that moves the controller code would leave the patch unapplied (a report() warning) and Sprint's A would roll again; turning the row off, or reverting the commits, undoes it all.

Reviewer notes

Start with sprint_step in host/nn_hid.c and patch_sprint_press in nx2/oot_patches.py.

Issues

None

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Flags noted, and the switch reference updated (docs/development.md, the README).
  • Screenshots for a change that shows on screen.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (built on Windows; Linux not built yet, T-MKEL covers it)
  • MKWVK_API_VERSION bumped (gpu/vk/vk_api.h's MkwVkHost or MkwVkRenderer changed).
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • A row in docs/tested-configurations.md for each measured run.
  • Docs updated.

🤖 Generated with Claude Code

## Why On keyboard and mouse there is no way to run without holding the game's A key, and pressing A while moving makes Link roll first. This adds an opt-in Sprint control: with the Controls tab's **Sprint** row on, hold Shift, or press the left stick once on a controller, and Link runs at once, without rolling and without talking to or picking up what is in front of him; tapping A mid-sprint still rolls. ## What changed - **input** (`host/nn_hid.c`): a Sprint control, `HOS_KEY_SPRINT` after the ocarina note controls, with keys and two gamepad slots that trade inputs with the Switch buttons by the same slot rules. `sprint_step` holds A, the game's own run, only while the left stick or the movement keys are pushed. **Shift** runs while held; **the left stick's press** is a toggle that lasts until the stick returns to center. - **A held, never pressed** (`nx2/oot_patches.py` `patch_sprint_press`): the game's controllers work out "pressed this frame" as held now and not held before, in `f_8038a220` (the four controllers gameplay reads) and `f_834a3680` (the menus' one). The patch clears `hos_sprint_press_mask` from "pressed" there; `sprint_step` sets it to A's bit while it is Sprint that holds A. The game sees A held (its run) and never pressed: no roll, no talk, open or pick-up, and no pause before the run. `report()` warns if the patch is not found in both functions. - **Rolling while sprinting** (`sprint_step`): as the player's own A goes down mid-sprint, Sprint lets go of A for `SPRINT_ROLL_GAP_MS` (50 ms, a frame at 20 fps) and holds it again with the mask 0, so the game sees the player's press: a roll (or talk, open, pick up). A tap shorter than the gap still counts. - **The ocarina** (`npad_states`): while the ocarina waits for a note (`hos_oc_wait` 1), Sprint holds nothing and its toggle ends, as with the menu open: A is a note there and the stick bends its pitch. - **menu** (`gpu/vk/vk_menu.cpp`): the **Sprint** row on the Controls tab, off by default, with a warning in its text that it changes the controls. `apply_sprint_layout` runs when the row changes and after the Controls page's Reset while it is on: on, Sprint gets Shift (B moves to V, when no control has V) and the left stick's press; off, they go back. Only where they are still the defaults and Sprint has none of its own: controls the player rebound are left alone. The Controls page gets a Sprint row (`pad_sprint` / `key_sprint` lines). - **API** (`gpu/vk/vk_api.h`): `MKWVK_API_VERSION` 22 (`binding`/`bind` take gamepad inputs for `HOS_KEY_SPRINT`), `HOS_OPT_SPRINT` 54 (after the frame rate cap's 53, before Damage taken's 55). - **docs**: the README's option table and a Sprint paragraph. ## Flags and settings | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | Controls tab > Sprint (`HOS_OPT_SPRINT`, 54) | Sprint on Shift and the left stick's press; turning it on moves Shift from B to Sprint (B to V) and the stick's press from LS to Sprint, off puts them back | Off | Off by default: it changes the player's controls | Keyboard players, and pads where holding A to run is tiring | Players who already run with A | Linux, Steam Deck | | `HOS_SPRINT_STILL_MS=<ms>` | Starts a sprint with the stick let go for that long: the older way round the roll, for a game built without the patch | 0 (off) | Leave off; a fallback only | A game without the patch | A game with it | Most values; 120 checked in the tests | ## Measurements Baseline: this PR @ 629c9d9a, Sprint off `main` has no Sprint row, so the cost of the feature is measured as it is turned on: Sprint off against on in one session, on the same build (with the row off, `sprint_step` returns at once and the bindings are main's). main: 0c74253d (2026-11-13) when measured; the PR is now on main d9648863 this PR: feat/sprint @ 629c9d9a (the game built from it), now 7e4f94b5 Why the numbers still hold: they compare Sprint on against off on one build, and what changed since is main's own work (in both columns alike) and, on Sprint's side, a test of `hos_oc_wait` per input read, a patch that reads less text at build time, a moved comment and test stubs: nothing that runs per frame beyond one compare. Hardware: NVIDIA GeForce RTX 3060 (driver 32.0.16.2114), Intel Core i5-12400F, 32 GB RAM, Windows 11 Home (26200) Scene: Hyrule Field, standing still; Ultra preset, output 2048x1152, Resolution 100%, Lanczos, no frame generation, no frame rate cap, V-Sync off Method: four runs of 60 to 120 s, alternated (on, off, on, off), after a warm-up; the `vi:` fps lines of `longshot_log.txt` (one per 5 s), each tagged with the options file's `sprint=` as it was written, the samples within 10 s of a switch left out; the median of each run, and of each side How to reproduce: this PR's game, a spot in Hyrule Field, the FPS counter on; switch the Controls tab's Sprint row between runs and read the `vi:` lines | | this PR @ 629c9d9a, Sprint off | this PR @ 629c9d9a, Sprint on | |---|---|---| | fps, run 1 / run 2 (median) | 51.5 / 51.3 | 51.8 / 51.2 | | fps, median of all samples | 51.5 (46 samples) | 51.5 (29 samples) | | frame time, median | 19.44 ms | 19.42 ms | ``` (Sprint on, run 1) [hos Presentation Thread] vi: 11373 frames presented (51.8 fps over the last 5.0 s, interval 1) [hos Presentation Thread] vi: 11634 frames presented (52.0 fps over the last 5.0 s, interval 1) (Sprint off, run 1) [hos Presentation Thread] vi: 15777 frames presented (51.5 fps over the last 5.0 s, interval 1) [hos Presentation Thread] vi: 16029 frames presented (50.2 fps over the last 5.0 s, interval 1) (Sprint on, run 2) [hos Presentation Thread] vi: 22468 frames presented (50.2 fps over the last 5.0 s, interval 1) [hos Presentation Thread] vi: 22721 frames presented (50.4 fps over the last 5.0 s, interval 1) (Sprint off, run 2) [hos Presentation Thread] vi: 26557 frames presented (51.3 fps over the last 5.0 s, interval 1) [hos Presentation Thread] vi: 26812 frames presented (50.8 fps over the last 5.0 s, interval 1) ``` The game-free checks, on this PC (llvm-mingw): main @ d9648863 452 passed, 8 failed, 73 skipped; this PR @ 7e4f94b5 453 passed, 8 failed, 73 skipped (`test_sprint` the extra pass). The same 8 fail on both: test_bake_portability, test_fpx_sticky, nx2/test_nup_paths, test_spawn_emit, test_check_third_party, test_lift_cache, test_menu_i18n, test_release. ## Testing **Tested:** `tests/test_sprint.py`: the host's binding table (C) linked with the menu's `apply_sprint_layout` (C++) against options files (a new install; on, on again, off; a binding swap; Reset; LS with two inputs; rebound controls kept; V taken; Sprint bound by the player left alone); `sprint_step` compiled alone (Shift held, the toggle and its end, nothing with the menu open, the press mask Sprint's only, the roll gap, a tap shorter than the gap, the player's A held before Sprint starts, `HOS_SPRINT_STILL_MS`); `patch_sprint_press` on lifted text (both functions, nowhere else, once, a drifted anchor left alone, a body that ends at its own brace); `npad_states` pausing Sprint during the ocarina's note wait. `test_input_background`, `test_menu_env_overrides`, `test_rumble_menu`, `test_framegen_preset`, `test_readme_menu_rows` and `test_upscaler_round_trip` (new on main: it cuts `set_row`) updated. In game, on an earlier build of this branch with the same Sprint code (Windows 11, RTX 3060, DualSense and keyboard), with the Sprint row on: Shift and the left stick's press run at once with no stutter and no roll; running past things Link could interact with leaves them alone; tapping A mid-sprint rolls, then Link keeps sprinting. The patch applied in both functions (`oot_patches: Sprint's A without its press` in code_003.c and code_040.c). On Longshot, this PR @ 629c9d9a (`longshot v3.0.0-rc.3-6-g629c9d9a`, Windows 11, RTX 3060): the build applied the patch in both functions again, the Sprint row and its warning, the Controls page's Sprint row (L3 and Shift, the left stick's press left at None) as in the screenshots, Sprint running in Hyrule Field with no roll or stutter at the start, tapping A mid-sprint rolling and Link sprinting on after it, and the fps runs above. On this PR @ 0046c6cf, before the rebase onto d9648863 that changed nothing of Sprint's (Windows 11, RTX 3060), Sprint on, held while doing each of these: the ocarina taken out mid-sprint (no note played by Sprint's A), swimming, riding Epona, pushing and pulling a block, carrying something: Sprint does nothing unwanted in any of them, and each works as without it. **Not tested:** Linux (not built here: the Linux build and a run there are T-MKEL's). **Needs other testers:** - Linux or Steam Deck, with a gamepad: Sprint on, the left stick's press runs without a roll, tapping A mid-sprint rolls, turning it off restores LS, expecting the same as on Windows (T-MKEL) ## Screenshots `main` has no Sprint row to compare with. this PR @ 629c9d9a: the Controls tab's Sprint row, on, with its warning ![this PR @ 629c9d9a: the Controls tab's Sprint row](https://git.magicbean.dev/attachments/0d0000f0-45e1-42fd-acf1-ca9bf0808e64) this PR @ 629c9d9a: the Controls page with the row on: Sprint on L3 and Shift, the left stick's press left at None ![this PR @ 629c9d9a: the Controls page's Sprint row](https://git.magicbean.dev/attachments/cb49d1f1-60c2-4142-b651-225e4f9e754e) ## Risk and rollback Off by default, so nothing changes for players who leave the row off: the bindings are the old ones and `sprint_step` returns at once. With it on, a game update that moves the controller code would leave the patch unapplied (a `report()` warning) and Sprint's A would roll again; turning the row off, or reverting the commits, undoes it all. ## Reviewer notes Start with `sprint_step` in `host/nn_hid.c` and `patch_sprint_press` in `nx2/oot_patches.py`. ## Issues None ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Flags noted, and the switch reference updated (docs/development.md, the README). - [x] Screenshots for a change that shows on screen. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (built on Windows; Linux not built yet, T-MKEL covers it) - [x] `MKWVK_API_VERSION` bumped (`gpu/vk/vk_api.h`'s `MkwVkHost` or `MkwVkRenderer` changed). - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] A row in `docs/tested-configurations.md` for each measured run. - [x] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
thegrandao changed title from WIP: feat(input): opt-in Sprint on Shift or the left stick's press, without the roll to feat(input): opt-in Sprint on Shift or the left stick's press, without the roll 2026-11-13 13:25:27 +00:00
thegrandao changed title from feat(input): opt-in Sprint on Shift or the left stick's press, without the roll to WIP: feat(input): opt-in Sprint on Shift or the left stick's press, without the roll 2026-11-13 13:33:34 +00:00
thegrandao force-pushed feat/sprint from c306f032f7
Some checks failed
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 10s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
to 629c9d9a9f
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 17s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
2026-11-13 13:35:52 +00:00
Compare
Author
First-time contributor

Rebased onto main @ 0c74253d (629c9d9a), replacing the merge commit, as CONTRIBUTING asks; no change to the Sprint code. Back to WIP: the fps with Sprint on against off, the screenshots and a run in the game on this base are still to come. The game-free checks: 442 passed, 8 failed here against 441 passed, 8 failed on main, the same 8.

Rebased onto main @ 0c74253d (629c9d9a), replacing the merge commit, as CONTRIBUTING asks; no change to the Sprint code. Back to WIP: the fps with Sprint on against off, the screenshots and a run in the game on this base are still to come. The game-free checks: 442 passed, 8 failed here against 441 passed, 8 failed on main, the same 8.
docs: Sprint's fps run, on against off, in tested configurations
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
0ff2619e06
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Author
First-time contributor

Measured on Longshot, this PR @ 629c9d9a (RTX 3060, Windows 11, Hyrule Field, standing still): Sprint off 51.5 and 51.3 fps, on 51.8 and 51.2 fps, a median of 51.5 fps both ways (19.4 ms): no cost. Screenshots of the Controls tab's and the Controls page's Sprint rows added, and a row in docs/tested-configurations.md (0ff2619e). Still to settle: the Linux build (T-MKEL).

Measured on Longshot, this PR @ 629c9d9a (RTX 3060, Windows 11, Hyrule Field, standing still): Sprint off 51.5 and 51.3 fps, on 51.8 and 51.2 fps, a median of 51.5 fps both ways (19.4 ms): no cost. Screenshots of the Controls tab's and the Controls page's Sprint rows added, and a row in docs/tested-configurations.md (0ff2619e). Still to settle: the Linux build (T-MKEL).
Author
First-time contributor

Checked in the game on this PR @ 629c9d9a too: tapping A mid-sprint rolls, then Link keeps sprinting.

Checked in the game on this PR @ 629c9d9a too: tapping A mid-sprint rolls, then Link keeps sprinting.
Merge branch 'main' into feat/sprint
Some checks failed
Website / Website on Cloudflare (pull_request_target) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 4s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
b38be49125
thegrandao changed title from WIP: feat(input): opt-in Sprint on Shift or the left stick's press, without the roll to feat(input): opt-in Sprint on Shift or the left stick's press, without the roll 2026-11-13 14:55:04 +00:00
HumanBean left a comment

The code reads well, and I found no correctness bug in it. The two things that hold it back are about the branch and its state, not the Sprint code.

Must change before merge

  1. Not rebased, and it conflicts with main. The head is a merge commit again (b38be491 "Merge branch 'main' into feat/sprint", made after the comment saying the merge had been replaced), so the "Rebased on current main" box doesn't hold. main has also moved past that merge with cd906265 (#197, occlusion queries) and fdc2840b (#147, dev packs). The only conflict is docs/tested-configurations.md, where both branches add a row at the top of Runs. docs/development.md and gpu/vk/vk_replay.cpp merge cleanly. Please rebase onto main (CONTRIBUTING step 7), drop the merge, keep both rows, and say in the comment why the fps numbers still hold.
  2. Out of WIP with a box still open. The Linux build box is unticked and waits on T-MKEL. AGENTS.md says a box that can't be true yet stays unticked with its reason and the PR stays WIP. Please pr wip it until the Linux build and run come back, or until someone builds it on Linux.

Checked and fine

  • patch_sprint_press: it matches the exact pair, needs exactly one match per function, is idempotent, and does nothing when it isn't found (with a report() warning). The mask is only ever 0 or A's bit, and it is only set while Sprint is the one holding A. When the row is off, sprint_step stores 0 every read, so nothing else's presses are cleared.
  • sprint_step: the gap always covers the read that saw the player's A go down, because the gap starts at that read. So the game sees A up for at least one read, even at 20 fps. The state stays sane when the row is off, the menu is open, the window is in the background (no keys, and pads only with the background option) and after the menu (pad_after_menu).
  • apply_sprint_layout: on, on again, off and Reset round-trip, and only the defaults are touched. The swap rules extend to Sprint with pad_swaps, and the face presets (apply_pad_layout) don't touch LS.
  • MKWVK_API_VERSION goes 21 → 22, and HOS_OPT_SPRINT 54 collides with no other id.
  • The source-reading checks still cut what they need: test_bound_keys and test_ocarina_notes take pad_swaps within their ranges. test_input_background's regex and fixtures were updated, not loosened, and the menu stubs gained apply_sprint_layout.
  • The README table and Controls text are updated, and HOS_SPRINT_STILL_MS is in docs/development.md. The measurements (on against off on one build) are what CONTRIBUTING asks of a feat.

There are three inline comments: one question about where held A goes besides the run, and two nits.

Code review only: I didn't build it or run the checks.

The code reads well, and I found no correctness bug in it. The two things that hold it back are about the branch and its state, not the Sprint code. **Must change before merge** 1. **Not rebased, and it conflicts with `main`.** The head is a merge commit again (b38be491 "Merge branch 'main' into feat/sprint", made after the comment saying the merge had been replaced), so the "Rebased on current `main`" box doesn't hold. `main` has also moved past that merge with cd906265 (#197, occlusion queries) and fdc2840b (#147, dev packs). The only conflict is `docs/tested-configurations.md`, where both branches add a row at the top of Runs. `docs/development.md` and `gpu/vk/vk_replay.cpp` merge cleanly. Please rebase onto `main` (CONTRIBUTING step 7), drop the merge, keep both rows, and say in the comment why the fps numbers still hold. 2. **Out of WIP with a box still open.** The Linux build box is unticked and waits on T-MKEL. AGENTS.md says a box that can't be true yet stays unticked with its reason and the PR stays WIP. Please `pr wip` it until the Linux build and run come back, or until someone builds it on Linux. **Checked and fine** - `patch_sprint_press`: it matches the exact pair, needs exactly one match per function, is idempotent, and does nothing when it isn't found (with a `report()` warning). The mask is only ever 0 or A's bit, and it is only set while Sprint is the one holding A. When the row is off, `sprint_step` stores 0 every read, so nothing else's presses are cleared. - `sprint_step`: the gap always covers the read that saw the player's A go down, because the gap starts at that read. So the game sees A up for at least one read, even at 20 fps. The state stays sane when the row is off, the menu is open, the window is in the background (no keys, and pads only with the background option) and after the menu (`pad_after_menu`). - `apply_sprint_layout`: on, on again, off and Reset round-trip, and only the defaults are touched. The swap rules extend to Sprint with `pad_swaps`, and the face presets (`apply_pad_layout`) don't touch LS. - `MKWVK_API_VERSION` goes 21 → 22, and `HOS_OPT_SPRINT` 54 collides with no other id. - The source-reading checks still cut what they need: `test_bound_keys` and `test_ocarina_notes` take `pad_swaps` within their ranges. `test_input_background`'s regex and fixtures were updated, not loosened, and the menu stubs gained `apply_sprint_layout`. - The README table and Controls text are updated, and `HOS_SPRINT_STILL_MS` is in docs/development.md. The measurements (on against off on one build) are what CONTRIBUTING asks of a `feat`. There are three inline comments: one question about where held A goes besides the run, and two nits. Code review only: I didn't build it or run the checks.
@ -14,6 +14,7 @@ otherwise.
| Date | Build | OS / desktop | GPU / driver | CPU / RAM | Settings | Where | Result |
|---|---|---|---|---|---|---|---|
| 2026-11-13 | v3.0.0-rc.3-6-g629c9d9a (source 9c216ab8): feat/sprint 629c9d9a, main 0c74253d + Sprint, built from source (llvm-mingw) | Windows 11 Home (26200), a window | RTX 3060, 32.0.16.2114 | Core i5-12400F, 32 GB | Ultra preset, output 2048x1152, Resolution 100%, Lanczos, no frame generation, no cap, V-Sync off; the Controls tab's Sprint row off and on, alternated (off, on, off, on: on, off, on, off as run) | Hyrule Field, standing still, ~60 to 120 s each, samples within 10 s of a switch left out | Sprint off: 51.5 and 51.3 fps (median 51.5, 19.4 ms); on: 51.8 and 51.2 fps (median 51.5, 19.4 ms): no cost. Sprint ran, the patch applied in both controller functions |
Owner

This row conflicts with main: cd906265 (#197) also adds a row at the top of Runs. Rebase onto main and keep both rows, rather than merging main in (b38be491).

This row conflicts with `main`: cd906265 (#197) also adds a row at the top of Runs. Rebase onto `main` and keep both rows, rather than merging `main` in (b38be491).
thegrandao marked this conversation as resolved
@ -2302,6 +2307,46 @@ void share_pipeline_list() {
/// A row's new value v, as the arrows set it (dir: 1 up, -1 down, for the rows that skip or wrap), saved unless !keep
/// (a slider's drag: saved once it is let go)
/// (longshot) The Controls tab's Sprint row turned on: Sprint gets Shift and the left stick's press where they are still
Owner

Nit: apply_sprint_layout was inserted between set_row's own doc comment ("A row's new value v, as the arrows set it...", two lines up) and set_row, so that comment now describes this function. Move the new function above that comment.

Nit: `apply_sprint_layout` was inserted between `set_row`'s own doc comment ("A row's new value v, as the arrows set it...", two lines up) and `set_row`, so that comment now describes this function. Move the new function above that comment.
thegrandao marked this conversation as resolved
host/nn_hid.c Outdated
@ -848,0 +889,4 @@
if (!steered || menu_open) latched = 0;
else if (pad && !was_pad) latched = 1; /* (pressed while he moves) */
was_pad = pad;
const int want = steered && !menu_open && (latched || (held & SPRINT_KEY));
Owner

Question: Sprint holds A whenever the stick is pushed, in every game state, not only while Link runs on foot. Did you check it while swimming, on Epona, pushing or pulling a block, carrying something, and during the ocarina's note wait while the stick bends the pitch (A is a note there, and oc_pad_word passes it through while the note rows are unset)? If any of those does something with A held, it may be worth gating on the player's state, or noting it in the README.

Question: Sprint holds A whenever the stick is pushed, in every game state, not only while Link runs on foot. Did you check it while swimming, on Epona, pushing or pulling a block, carrying something, and during the ocarina's note wait while the stick bends the pitch (A is a note there, and `oc_pad_word` passes it through while the note rows are unset)? If any of those does something with A held, it may be worth gating on the player's state, or noting it in the README.
thegrandao marked this conversation as resolved
@ -794,0 +813,4 @@
at = out.find(head)
if at < 0:
continue
end = out.find("\nvoid f_", at + len(head))
Owner

Nit: the body ends at the next \nvoid f_. If a lifted file ever puts something other than a void f_ function after one of these two, the body runs into the next function, and the count of one could match a pair there. Ending at the function's own closing \n}\n would be tighter. The current lifted files may well make this moot.

Nit: the body ends at the next `\nvoid f_`. If a lifted file ever puts something other than a `void f_` function after one of these two, the body runs into the next function, and the count of one could match a pair there. Ending at the function's own closing `\n}\n` would be tighter. The current lifted files may well make this moot.
thegrandao marked this conversation as resolved
thegrandao changed title from feat(input): opt-in Sprint on Shift or the left stick's press, without the roll to WIP: feat(input): opt-in Sprint on Shift or the left stick's press, without the roll 2026-11-13 18:16:04 +00:00
thegrandao force-pushed feat/sprint from b38be49125
Some checks failed
Website / Website on Cloudflare (pull_request_target) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 4s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
to 0046c6cf11
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 4s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
Website / Website on Cloudflare (pull_request) Has been cancelled
2026-11-13 18:18:30 +00:00
Compare
Author
First-time contributor

Rebased onto main @ fbd42024 (0046c6cf), the merge commit dropped; docs/tested-configurations.md keeps both rows. From HumanBean's review: patch_sprint_press now ends a body at the function's own closing brace (a check for it), set_row's comment is back above set_row, and Sprint holds nothing while the ocarina waits for a note (A is a note there; the stick's toggle ends). test_upscaler_round_trip, new on main, gets Sprint's stubs. The fps numbers compare Sprint on against off on one build; what changed since is main's own work and one compare per input read, so they still hold. Checks: main @ fbd42024 449 passed, 7 failed; this PR 450 passed, 7 failed, the same 7. Back to WIP: the Linux build (T-MKEL) and an in-game check of the ocarina, swimming, Epona, blocks and carrying are still to come.

Rebased onto main @ fbd42024 (0046c6cf), the merge commit dropped; docs/tested-configurations.md keeps both rows. From HumanBean's review: patch_sprint_press now ends a body at the function's own closing brace (a check for it), set_row's comment is back above set_row, and Sprint holds nothing while the ocarina waits for a note (A is a note there; the stick's toggle ends). test_upscaler_round_trip, new on main, gets Sprint's stubs. The fps numbers compare Sprint on against off on one build; what changed since is main's own work and one compare per input read, so they still hold. Checks: main @ fbd42024 449 passed, 7 failed; this PR 450 passed, 7 failed, the same 7. Back to WIP: the Linux build (T-MKEL) and an in-game check of the ocarina, swimming, Epona, blocks and carrying are still to come.
Author
First-time contributor

Thanks for the review. Everything in it is addressed, rebased onto main @ d9648863 (7e4f94b5), with no merge commit:

  1. Rebase: the merge is gone. docs/tested-configurations.md keeps both rows, and vk_api.h keeps HOS_OPT_SPRINT 54 beside Damage taken's 55. The fps numbers still hold: they compare Sprint on against off on one build, so main's own changes are in both columns. Sprint's only addition per input read is one compare (below). pr ready stays off until the Linux build box is settled.
  2. WIP: back to WIP until the Linux build and run (T-MKEL), or someone builds it on Linux.

Held A elsewhere (your question): checked in the game on 0046c6cf (Windows 11, RTX 3060), with Sprint held during each of these: swimming, riding Epona, pushing and pulling a block, carrying something, and the ocarina taken out mid-sprint. None does anything unwanted with Sprint's A, and each works as it does without Sprint. The ocarina did need a change, as you guessed. npad_states now pauses Sprint while the ocarina waits for a note (hos_oc_wait == 1), the same as with the menu open: Sprint holds nothing and the left stick's toggle ends. The README says so too.

Nits:

  • patch_sprint_press ends a body at the function's own \n}\n now. A new check puts a non-void f_ function with a pair after f_8038a220, and fails on the old code (it counted 2).
  • apply_sprint_layout sits above set_row's doc comment again.

Also, test_upscaler_round_trip (new on main) cuts set_row, so it needed Sprint's stubs. Checks: main @ d9648863 gives 452 passed, 8 failed; this PR gives 453 passed, 8 failed, and the same 8 fail on both.

Thanks for the review. Everything in it is addressed, rebased onto main @ d9648863 (7e4f94b5), with no merge commit: 1. **Rebase:** the merge is gone. `docs/tested-configurations.md` keeps both rows, and `vk_api.h` keeps `HOS_OPT_SPRINT` 54 beside Damage taken's 55. The fps numbers still hold: they compare Sprint on against off on one build, so main's own changes are in both columns. Sprint's only addition per input read is one compare (below). `pr ready` stays off until the Linux build box is settled. 2. **WIP:** back to WIP until the Linux build and run (T-MKEL), or someone builds it on Linux. **Held A elsewhere (your question):** checked in the game on 0046c6cf (Windows 11, RTX 3060), with Sprint held during each of these: swimming, riding Epona, pushing and pulling a block, carrying something, and the ocarina taken out mid-sprint. None does anything unwanted with Sprint's A, and each works as it does without Sprint. The ocarina did need a change, as you guessed. `npad_states` now pauses Sprint while the ocarina waits for a note (`hos_oc_wait == 1`), the same as with the menu open: Sprint holds nothing and the left stick's toggle ends. The README says so too. **Nits:** - `patch_sprint_press` ends a body at the function's own `\n}\n` now. A new check puts a non-`void f_` function with a pair after `f_8038a220`, and fails on the old code (it counted 2). - `apply_sprint_layout` sits above `set_row`'s doc comment again. Also, `test_upscaler_round_trip` (new on main) cuts `set_row`, so it needed Sprint's stubs. Checks: main @ d9648863 gives 452 passed, 8 failed; this PR gives 453 passed, 8 failed, and the same 8 fail on both.
thegrandao force-pushed feat/sprint from 0046c6cf11
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 4s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
Website / Website on Cloudflare (pull_request) Has been cancelled
to 7e4f94b5aa
Some checks failed
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Has been cancelled
2026-11-13 18:47:40 +00:00
Compare
Contributor

PR #137 (hold-to-toggle) also uses option id 54 (HOS_OPT_HOLD_TOGGLE), as does #152 (HOS_OPT_KEYBOARD). It has merged main f3fe1763; main's damage option is 55. Please renumber whichever of these lands second and rerun tests/test_option_ids.py; don't silently share 54. #137 uses the existing option callback and does not change MkwVkHost/MkwVkRenderer or bump the API version. #196 and #152 both propose API version 22, so their structure changes also need reconciliation at merge.

Sprint runs after the latch. A toggled A therefore looks like a held A to sprint_step. Please check the combined behavior: tap A to latch, physically release, then tap again to unlatch; confirm sprint/jump transitions still match the player's intent.

PR #137 (hold-to-toggle) also uses option id 54 (`HOS_OPT_HOLD_TOGGLE`), as does #152 (`HOS_OPT_KEYBOARD`). It has merged main f3fe1763; main's damage option is 55. Please renumber whichever of these lands second and rerun `tests/test_option_ids.py`; don't silently share 54. #137 uses the existing option callback and does not change `MkwVkHost`/`MkwVkRenderer` or bump the API version. #196 and #152 both propose API version 22, so their structure changes also need reconciliation at merge. Sprint runs after the latch. A toggled A therefore looks like a held A to `sprint_step`. Please check the combined behavior: tap A to latch, physically release, then tap again to unlatch; confirm sprint/jump transitions still match the player's intent.
Some checks failed
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Has been cancelled
This pull request is marked as a work in progress.
Some workflows are waiting to be reviewed.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'feat/sprint:thegrandao-feat/sprint'
git switch 'thegrandao-feat/sprint'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!196
No description provided.