Run the sprint patch set only while a sprint button is chosen, and never switch it on over a start that kept it off.

This commit is contained in:
kendo
2026-11-12 20:56:54 +00:00
committed by chrg6
parent 6faf4bd7f5
commit 0a4fe144c9
6 changed files with 47 additions and 13 deletions

View File

@@ -486,7 +486,8 @@ A change to the sprint button (`runtime/sprint.c`, the set `sprint` of `recomp/p
`runtime/ov_input.c` `npad_states`) passes `python tests\sprint_check.py`, which builds `sprint.c` with the rest of the
runtime stubbed and plays frames against a stand-in Link: toggle and hold on each kind of button, the states that keep
and end a sprint, ZL, the button kept from the game only during play (the bottom button only while Link moves) and a
Link the player does not control. No game files.
Link the player does not control, and the set switched off with the button off but never over a start that kept it
off. No game files.
A change to the mouse camera (`runtime/mouse_direct.c`, the mouse path of `runtime/input.c`, the set `mouse_direct` of
`recomp/patches-v111.txt` or `patches-v143.txt`) passes `python tests\mouse_direct_check.py --cc <clang> --deps

View File

@@ -151,7 +151,7 @@ The log says which it is when a gamepad is taken: `input: HD rumble available fo
## Sprint button
On 1.1.1, `NX2NAVI_SPRINT_BUTTON` (`sprint_button`, the *Sprint button* row on the Controls tab, live) gives a button that makes Link sprint without rolling first: `b` (the bottom face button), `l`, `r`, `l3` or `r3`, or `off` (the default). `NX2NAVI_SPRINT_MODE` (`sprint_mode`, the *Sprint mode* row) is `toggle` (the default: a press starts the sprint, the next press ends it) or `hold` (Link sprints while the button is held). Link sprints while his input flags hold the dash input; the set `sprint` of `recomp/patches-v111.txt` calls `runtime/sprint.c` after Link's movement update and the stance update that follows load those flags, and the host functions add the dash input while the sprint is on, so the game's own sprint runs with its speed, animation and camera. A toggled sprint goes on through jumps, slides and landings and ends when Link's state is anything else (an attack, a roll, an item, swimming, climbing, a cutscene), when ZL is held, or after four frames standing still (ninety after a press made while standing, before Link has moved). During play (Link's update ran within the last eight controller polls) the button is kept from the game, so it does not also fire its own action; the bottom button is kept only while Link moves, so it attacks as usual otherwise. In the settings menu, a menu of the game or a cutscene every button keeps its role. `NX2NAVI_SPRINT_PATCH=0` turns the patch set off and hides the rows.
On 1.1.1, `NX2NAVI_SPRINT_BUTTON` (`sprint_button`, the *Sprint button* row on the Controls tab, live) gives a button that makes Link sprint without rolling first: `b` (the bottom face button), `l`, `r`, `l3` or `r3`, or `off` (the default). `NX2NAVI_SPRINT_MODE` (`sprint_mode`, the *Sprint mode* row) is `toggle` (the default: a press starts the sprint, the next press ends it) or `hold` (Link sprints while the button is held). Link sprints while his input flags hold the dash input; the set `sprint` of `recomp/patches-v111.txt` calls `runtime/sprint.c` after Link's movement update and the stance update that follows load those flags, and the host functions add the dash input while the sprint is on, so the game's own sprint runs with its speed, animation and camera. A toggled sprint goes on through jumps, slides and landings and ends when Link's state is anything else (an attack, a roll, an item, swimming, climbing, a cutscene), when ZL is held, or after four frames standing still (ninety after a press made while standing, before Link has moved). During play (Link's update ran within the last eight controller polls) the button is kept from the game, so it does not also fire its own action; the bottom button is kept only while Link moves, so it attacks as usual otherwise. In the settings menu, a menu of the game or a cutscene every button keeps its role. The set runs only while a button is chosen, so with the sprint button off its sites cost nothing. `NX2NAVI_SPRINT_PATCH=0` (or a mod whose code patches the same sites) keeps the patch set off and hides the rows.
## Nintendo Switch 2 controllers

View File

@@ -98,10 +98,24 @@ static int state_kind(uint64_t link) {
return STOP;
}
int sprint_available(void) { return rt_patch_enabled(rt_patch_set("sprint")); }
// The set runs only while a button is chosen, so with the sprint button off its sites cost nothing. Whether it may run
// at all is read once, after the start: NX2NAVI_SPRINT_PATCH=0 or a mod whose code patches the same sites (mods.c
// mods_exefs_switch) leaves it off then, and switching it on later must not undo that.
static _Atomic int allowed = -1;
static int sprint_set(void) { return rt_patch_set("sprint"); }
int sprint_available(void) {
int a = atomic_load(&allowed);
if (a < 0) {
a = rt_patch_enabled(sprint_set());
atomic_store(&allowed, a);
}
return a;
}
void sprint_pad(uint64_t *buttons) {
int b = setting_button();
if (!sprint_available()) return;
int b = setting_button(), set = sprint_set();
if (rt_patch_enabled(set) != (b != BTN_OFF)) rt_patch_switch(set, b != BTN_OFF);
if (b == BTN_OFF) return;
static uint64_t last;
uint64_t bit = button_bits[b], now = *buttons;

View File

@@ -7,5 +7,6 @@
void sprint_pad(uint64_t *buttons);
// One update of Link's sprint: kind is how his state treats it (sprint.c), button and hold the settings. 1 while on.
int sprint_step(int kind, int button, int hold);
// 1 when the game version has the patch set sprint and it is on: the Controls tab shows the sprint rows then.
// 1 when the game version has the patch set sprint and nothing keeps it off (NX2NAVI_SPRINT_PATCH=0, a mod patching
// its sites): the Controls tab shows the sprint rows then. The set itself runs only while a button is chosen.
int sprint_available(void);

View File

@@ -19,8 +19,10 @@ const char *cfg_value(const char *key) {
if (!strcmp(key, "NX2NAVI_SPRINT_MODE")) return mode;
return "";
}
static int set_on = 1; // the patch set sprint, on at the start as its default
int rt_patch_set(const char *name) { return strcmp(name, "sprint") ? -1 : 3; }
int rt_patch_enabled(int set) { return set == 3; }
int rt_patch_enabled(int set) { return set == 3 && set_on; }
void rt_patch_switch(int set, int on) { assert(set == 3); set_on = on != 0; }
void rt_register_host(uint64_t addr, GuestFn fn) {
if (addr == RT_PATCH_PAGE + 0xf40) link_fn = fn;
else if (addr == RT_PATCH_PAGE + 0xf50) stance_fn = fn;
@@ -43,10 +45,10 @@ static uint64_t frame(uint64_t buttons, int32_t state, uint64_t *who) {
memset(&c, 0, sizeof c);
c.x[19] = (uintptr_t)who;
c.x[10] = 0x40; // other flag bits stay
link_fn(&c);
if (set_on) link_fn(&c); // the sites call in only while the set is on
flags_x10 = c.x[10];
c.x[8] = 0x01;
stance_fn(&c);
if (set_on) stance_fn(&c);
flags_w8 = c.x[8];
return buttons;
}
@@ -64,11 +66,23 @@ static void reset(const char *btn, const char *md) {
assert(!dashing());
}
int main(void) {
int main(int argc, char **argv) {
char *img = calloc(1, 0x4c80000);
assert(img);
test_main_base = (uintptr_t)img;
assert(link_fn && stance_fn && sprint_available());
assert(link_fn && stance_fn);
if (argc > 1 && !strcmp(argv[1], "kept-off")) {
// off at the start (NX2NAVI_SPRINT_PATCH=0, or a mod patching its sites): no rows, never switched on, and the
// button always reaches the game
set_on = 0, button = "l3";
assert(!sprint_available());
uint64_t bt = L3;
for (int i = 0; i < 20; i++) bt = L3, sprint_pad(&bt);
assert(!set_on && bt == L3);
puts("sprint_check kept-off: ok");
return 0;
}
assert(sprint_available());
link[0] = test_main_base + 0x4b4dfc0, link[3] = (uintptr_t)owner;
other[0] = test_main_base + 0x4b4dfc0, other[3] = (uintptr_t)a; // Link's class, another owner
owner[0x270 / 8] = (uintptr_t)a, a[0x188 / 8] = (uintptr_t)b, b[0x1c0 / 8] = (uintptr_t)state_obj;
@@ -79,9 +93,12 @@ int main(void) {
manager[0] = test_main_base + 0x4bdfe68, manager[0x470 / 8] = (uintptr_t)controlled;
controlled[0] = test_main_base + 0x4b49698, controlled[3] = (uintptr_t)owner;
// off: nothing changes, the button reaches the game
// off: nothing changes, the button reaches the game, and the set's sites stop calling in
reset("off", "toggle");
assert(frame(L3, WALK, link) == L3 && !dashing());
assert(frame(L3, WALK, link) == L3 && !dashing() && !set_on);
button = "l3";
frame(0, WALK, link);
assert(set_on); // a button chosen: the set runs again
// toggle on L3: a press while walking starts it; the button never reaches the game during play
reset("l3", "toggle");

View File

@@ -46,7 +46,8 @@ def main():
subprocess.check_call([compiler(), '-O1', '-Wall', '-Wno-unused-function', '-fno-strict-aliasing',
'-I', str(work), '-I', str(ROOT / 'runtime'),
str(ROOT / 'tests' / 'sprint_check.c'), '-o', str(exe)], env=env)
return subprocess.run([str(exe)], env=env).returncode
rc = subprocess.run([str(exe)], env=env).returncode
return subprocess.run([str(exe), 'kept-off'], env=env).returncode or rc
if __name__ == '__main__':