WIP: feat(input): opt-in Sprint on Shift or the left stick's press, without the roll #196
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!196
Loading…
Reference in a new issue
No description provided.
Delete branch "thegrandao/Longshot:feat/sprint"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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
host/nn_hid.c): a Sprint control,HOS_KEY_SPRINTafter the ocarina note controls, with keys and two gamepad slots that trade inputs with the Switch buttons by the same slot rules.sprint_stepholds 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.nx2/oot_patches.pypatch_sprint_press): the game's controllers work out "pressed this frame" as held now and not held before, inf_8038a220(the four controllers gameplay reads) andf_834a3680(the menus' one). The patch clearshos_sprint_press_maskfrom "pressed" there;sprint_stepsets 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.sprint_step): as the player's own A goes down mid-sprint, Sprint lets go of A forSPRINT_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.npad_states): while the ocarina waits for a note (hos_oc_wait1), Sprint holds nothing and its toggle ends, as with the menu open: A is a note there and the stick bends its pitch.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_layoutruns 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_sprintlines).gpu/vk/vk_api.h):MKWVK_API_VERSION22 (binding/bindtake gamepad inputs forHOS_KEY_SPRINT),HOS_OPT_SPRINT54 (after the frame rate cap's 53, before Damage taken's 55).Flags and settings
HOS_OPT_SPRINT, 54)HOS_SPRINT_STILL_MS=<ms>Measurements
Baseline: this PR @
629c9d9a, Sprint offmainhas 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_stepreturns at once and the bindings are main's).main:
0c74253d(2026-11-13) when measured; the PR is now on maind9648863this PR: feat/sprint @
629c9d9a(the game built from it), now7e4f94b5Why 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_waitper 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 oflongshot_log.txt(one per 5 s), each tagged with the options file'ssprint=as it was written, the samples within 10 s of a switch left out; the median of each run, and of each sideHow 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:lines629c9d9a, Sprint off629c9d9a, Sprint onThe game-free checks, on this PC (llvm-mingw): main @
d9648863452 passed, 8 failed, 73 skipped; this PR @7e4f94b5453 passed, 8 failed, 73 skipped (test_sprintthe 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'sapply_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_stepcompiled 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_presson lifted text (both functions, nowhere else, once, a drifted anchor left alone, a body that ends at its own brace);npad_statespausing Sprint during the ocarina's note wait.test_input_background,test_menu_env_overrides,test_rumble_menu,test_framegen_preset,test_readme_menu_rowsandtest_upscaler_round_trip(new on main: it cutsset_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 pressin 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 ontod9648863that 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:
Screenshots
mainhas no Sprint row to compare with.this PR @
629c9d9a: the Controls tab's Sprint row, on, with its warningthis PR @
629c9d9a: the Controls page with the row on: Sprint on L3 and Shift, the left stick's press left at NoneRisk and rollback
Off by default, so nothing changes for players who leave the row off: the bindings are the old ones and
sprint_stepreturns at once. With it on, a game update that moves the controller code would leave the patch unapplied (areport()warning) and Sprint's A would roll again; turning the row off, or reverting the commits, undoes it all.Reviewer notes
Start with
sprint_stepinhost/nn_hid.candpatch_sprint_pressinnx2/oot_patches.py.Issues
None
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.MKWVK_API_VERSIONbumped (gpu/vk/vk_api.h'sMkwVkHostorMkwVkRendererchanged).tests/that reads changed source updated in this PR, not weakened.docs/tested-configurations.mdfor each measured run.🤖 Generated with Claude Code
WIP: feat(input): opt-in Sprint on Shift or the left stick's press, without the rollto feat(input): opt-in Sprint on Shift or the left stick's press, without the rollfeat(input): opt-in Sprint on Shift or the left stick's press, without the rollto WIP: feat(input): opt-in Sprint on Shift or the left stick's press, without the rollc306f032f7629c9d9a9fRebased 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.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).Checked in the game on this PR @
629c9d9atoo: tapping A mid-sprint rolls, then Link keeps sprinting.WIP: feat(input): opt-in Sprint on Shift or the left stick's press, without the rollto feat(input): opt-in Sprint on Shift or the left stick's press, without the rollThe 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
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 currentmain" box doesn't hold.mainhas also moved past that merge withcd906265(#197, occlusion queries) andfdc2840b(#147, dev packs). The only conflict isdocs/tested-configurations.md, where both branches add a row at the top of Runs.docs/development.mdandgpu/vk/vk_replay.cppmerge cleanly. Please rebase ontomain(CONTRIBUTING step 7), drop the merge, keep both rows, and say in the comment why the fps numbers still hold.pr wipit 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 areport()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_stepstores 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 withpad_swaps, and the face presets (apply_pad_layout) don't touch LS.MKWVK_API_VERSIONgoes 21 → 22, andHOS_OPT_SPRINT54 collides with no other id.test_bound_keysandtest_ocarina_notestakepad_swapswithin their ranges.test_input_background's regex and fixtures were updated, not loosened, and the menu stubs gainedapply_sprint_layout.HOS_SPRINT_STILL_MSis in docs/development.md. The measurements (on against off on one build) are what CONTRIBUTING asks of afeat.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 |This row conflicts with
main:cd906265(#197) also adds a row at the top of Runs. Rebase ontomainand keep both rows, rather than mergingmainin (b38be491).@ -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 stillNit:
apply_sprint_layoutwas inserted betweenset_row's own doc comment ("A row's new value v, as the arrows set it...", two lines up) andset_row, so that comment now describes this function. Move the new function above that comment.@ -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));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_wordpasses 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.@ -794,0 +813,4 @@at = out.find(head)if at < 0:continueend = out.find("\nvoid f_", at + len(head))Nit: the body ends at the next
\nvoid f_. If a lifted file ever puts something other than avoid 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}\nwould be tighter. The current lifted files may well make this moot.feat(input): opt-in Sprint on Shift or the left stick's press, without the rollto WIP: feat(input): opt-in Sprint on Shift or the left stick's press, without the rollb38be491250046c6cf11Rebased 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 @fbd42024449 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.Thanks for the review. Everything in it is addressed, rebased onto main @
d9648863(7e4f94b5), with no merge commit:docs/tested-configurations.mdkeeps both rows, andvk_api.hkeepsHOS_OPT_SPRINT54 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 readystays off until the Linux build box is settled.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_statesnow 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_pressends a body at the function's own\n}\nnow. A new check puts a non-void f_function with a pair afterf_8038a220, and fails on the old code (it counted 2).apply_sprint_layoutsits aboveset_row's doc comment again.Also,
test_upscaler_round_trip(new on main) cutsset_row, so it needed Sprint's stubs. Checks: main @d9648863gives 452 passed, 8 failed; this PR gives 453 passed, 8 failed, and the same 8 fail on both.0046c6cf117e4f94b5aaView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.