Skip to content

feat(input): Auto run, the sprint goes on after A is let go - #2747

Open
getzabelz wants to merge 3 commits into
NotRexed:mainfrom
getzabelz:feat/auto-run
Open

getzabelz wants to merge 3 commits into
NotRexed:mainfrom
getzabelz:feat/auto-run

Conversation

@getzabelz

@getzabelz getzabelz commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

What this is for: sprinting needs A held the whole time. Auto run (Controls tab, Off by default) lets the player hold A until Link sprints, let go, and keep sprinting while the stick stays pushed. What is tested: a Windows game run on an RX 6800 XT with an Xbox controller over XInput (Xbox / PC layout), including swimming, ladders and climbing walls, the keyboard alone (Shift as A, W to move), and a new game-free test. Not tested: Epona (not reached yet in the test save), Linux, other controllers.

Problem

In this game A is the run button: pressed while moving, Link rolls, then sprints for as long as A is held. Long runs across Hyrule Field mean holding the bottom face button down the whole time.

What changed

Auto run in the Insert menu

Testing

  • In game (Windows 11, RX 6800 XT, Xbox controller over XInput, Xbox / PC layout):

    • holding A until the sprint and letting go keeps Link sprinting;
    • letting go of the stick and tapping A stop it;
    • a short A press still only rolls;
    • in the water, on ladders and climbing walls it behaves as expected (nothing out of place);
    • the keyboard: A bound to Shift, first with the controller's stick, then moving with a key (W): kept and ended the same way as on the controller.

    The build tested in game had the same logic on an older main (8fcc8c2) under the id 52 and the names keep_running/run_lock.h. This branch is that change squashed on current main, renamed (auto_run) and moved to id 55; it was not rebuilt and run again after that.

  • tests/test_auto_run.py (new): plays presses through the header and checks that the row, its answer, the call in nn_hid.c and the readme are there. Cases:

    • kept after a long hold;
    • not kept after a short one;
    • A pressed again, Plus, an event;
    • the row off;
    • the stick under 30%, a diagonal;
    • A held while standing and then moving.
  • tests/test_input_background.py: its fixture gets the header and a hos_oot_player_free stub. Locally it fails to compile on main too (oc_consumed_pads set but not used with llvm-mingw's clang -Wunused-but-set-global), the same as tests/test_driver_cache_folder.py (mkdir). Both are unrelated to this change, so I'm leaving them to CI.

getzabelz and others added 3 commits November 12, 2026 11:44
A Controls row in the Insert menu (Off by default). In this game Link sprints while A is held (the run button:
a roll, then the sprint). With Auto run on, A held for 0.4 s while the left stick is pushed and then let go stays
held for the game, so the sprint goes on without the button held. A short press (a roll on its own) is never kept.
It ends when the stick comes back to the middle (Link stops), when A is pressed again (letting go of it stops; a
long hold keeps the sprint again), when Plus or Minus is pressed, or when Link is no longer the player's to move:
an event or a talk playing, the game paused, the Insert menu open. So A is never held into a menu or a text box.

The logic is host/auto_run.h, run from nn_hid.c's npad_states on each controller read, after the flight and the
free camera. host/hos_main.c notes on Link's update whether an event plays (hos_oot_player_free: updated within
250 ms, no event). Option id HOS_OPT_AUTO_RUN = 55 (52-54, 60 and 64 are taken by open PRs). The log says once
"hid: auto run: A kept held for the sprint" when it first keeps A.

tests/test_auto_run.py plays presses through the header (kept, short press, A again, Plus, events, the row off,
the stick under 30%) and checks the row, its answer, the call and the readme; tests/test_input_background.py's
fixture gets the header and a hos_oot_player_free stub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The row works the same with the keys (tested: A on Shift, moving with W), so the help says "A (or its key)" and
"the stick or the movement keys", as Camera speed's says "the camera keys too".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@getzabelz

Copy link
Copy Markdown
Contributor Author

CI note: after updating the branch, everything passes except Linux AppImage. It fails in the System packages step before anything is built: an ssl.SSLEOFError: UNEXPECTED_EOF_WHILE_READING while fetching packages, then a timeout (exit code 124), in run 43210240175. The same step failed the same way on the first run, and on other PRs in the same window (fix/prompts-live-hint-bars, feat/keyboard-ocarina-notes, feat/file-backed-pipeline-cache). I can't re-run jobs here; a re-run of the AppImage job should go through once the package mirror answers.

The earlier tests/test_release_scripts.py failure (Python source checks, ubuntu part 2) passed on this run; it is unrelated to the change.

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I merged this into current main (51da046). tests/test_auto_run.py and tests/test_input_background.py both pass on Linux with gcc. The appimage failure is the Launchpad SSL timeout in the System packages step, not this change. A re-run should clear it.

The state machine in host/auto_run.h reads right. A short press is never kept, a second A press hands control back, Plus/Minus and the Insert menu end it, and the row being off passes the buttons through untouched. Option id 55 is free on main (the highest taken there is 51) and MKWVK_API_VERSION doesn't need a bump, since only an enum is added.

One case I couldn't rule out from the code: the ocarina. oc_pad_word turns A into a note while the game waits for notes. If Link takes out the ocarina while the sprint is kept and the stick is still pushed, the release ends only if oot_event_playing() is true during ocarina play (host/hos_main.c:899). If it isn't, the game would see A held as a note. Could you check that once in game? Ending the kept A while the note wait is active would also cover it. Also, the in-game run was on the older id-52 build, so a quick boot of this exact head would be good before merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants