Repository navigation
Debug tab: a Movement speed choice for Link (1x to 3x) - #2020
Conversation
A debug tool beside flight and heart protection: Link walks, runs and swims faster while the player steers him. The forward speed the game sets on his character controller is scaled at the places it is set (nx2/oot_patches.py patch_cct_speed: his move f_809552f0 and the controller's setter), so the physics step still moves him with its collisions. Speeds over 15 m/s (a ledge climb's) and scene walks keep their pace. Off with the debug tools. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hckrman101
left a comment
There was a problem hiding this comment.
Nice, small and well contained. I went through the patch, the host side and the menu, and compared the hook places against a local lift.
What I checked:
- Off means off. At 1x, or with the debug tools off (the menu sends 100), the host stores no controller, so the hook returns on its first compare and the speed passes through untouched. An older renderer answers 0 for the new option, which the host clamps to 100, so not bumping
MKWVK_API_VERSIONis right: only an enum value was added, and neither interface struct changed. The hooks go in at lift time, in the supported build's lift only (like the other debug-tool patches). Whether to scale is decided every time the speed is set, so changing the row takes effect right away with no relift. The refactor of the controller walk inoot_update_player_cheatsbehaves the same as before (the vtable has to match before the+0xe8check, so using the constant is equivalent). - No double scaling. The two places in Link's move and the component helper each inline the setter only when the virtual setter is that same function. Otherwise they call through to another controller type, which can't be Link's. So a speed is scaled once.
- Collisions. Only the forward speed the physics step sweeps with is scaled, never a position, so walls and floors still stop him. The 15 m/s limit on the unscaled speed caps a frame's step at about 0.75 m even at 3x, and his 3x run is about 0.23 m a frame. I don't see a way through walls from this. Vertical motion (jumps, falls) isn't touched.
- Option id 36 is free on main (32 to 35 are the other cheats, 40 to 45 are HDR, Discord and camera centering, with 44 reserved) and in the open PRs (10, 44, and 48 in #1945). No collision.
- Not saved, not in a preset.
link_speedisn't inopts, so it isn't written to the ini, it's back to 1x at every launch, and the Quality preset can't touch it. Normal players never see it. - Tests.
test_movement_speed.pyis plain Python with no arguments, so run_all picks it up without aRUN_ALL_*declaration. Thetest_input_background.pychange makes that check stricter, not weaker: the cut-out state now has to include the new flag, which the harness needs so the cut-outnpad_statescompiles. Nothing it asserts was dropped.
Things to look at:
- The stick gate is "stick pushed", not "the player has control". A walk the game scripts is still scaled whenever the stick happens to be held. The common case is walking through a loading zone and keeping the stick forward into the arrival walk. The Lake Hylia test reads as if the stick was let go. Could you try it holding forward through the transition? If it overshoots, it's probably fine for a debug tool, but worth a line in the row's help.
- Untested movement types. Swimming is intended. Pushing or pulling a block, ladders and vines, crawlspaces, the hookshot pull and Epona aren't in the testing notes. Epona is probably a different controller, so unaffected. A block push at 3x is the one I'd check, since Link could get ahead of the block.
- CI. The Conventional Commit title check fails: the title needs a type prefix, e.g.
feat(debug): Movement speed choice for Link (1x to 3x). The Windows Python failures (test_log_batch.py, andtest_bake_skeleton.pytiming out) fail the same way on main, so they aren't from this PR. - Nit, inline: a comment moved to the wrong line.
Fine to merge once the title is fixed. Point 1 is a question, not a blocker.
| constexpr int link_speeds[] = {100, 125, 150, 200, 300}; | ||
| constexpr const char* link_speed_names[] = {"1x", "1.25x", "1.5x", "2x", "3x"}; | ||
| constexpr int kLinkSpeeds = sizeof link_speeds / sizeof link_speeds[0]; | ||
| std::atomic<int> link_speed{0}; // (the game side's report, OOT_CHEAT_AGE_ID: 0 child, 1 adult, -1 no save loaded) |
There was a problem hiding this comment.
Nit: this comment belongs to link_age two lines up (the age report from the game side), not to link_speed. It looks like the new block was inserted between the declaration and its comment. Move it back to link_age?
| if (!controller || controller != __atomic_load_n(&oot_speed_controller, __ATOMIC_RELAXED)) return; | ||
| const int percent = __atomic_load_n(&oot_speed_percent, __ATOMIC_RELAXED); | ||
| int hos_move_stick_held(void); /* host/nn_hid.c */ | ||
| if (percent <= 100 || percent > 400 || !hos_move_stick_held()) return; |
There was a problem hiding this comment.
This gate is "the stick is pushed", so a walk the game scripts while the player holds the stick (e.g. holding forward through a loading zone into the arrival walk) is still scaled. Probably fine for a debug tool. Worth a quick try, and a word in the row's help if it overshoots.
## Changes Two fixes to the Debug tab's flight, both from playing with it on. The first: an event that takes Link (a map change, the age toggle's reload, a cutscene) waits for him to stand, and the flight moves him to its own position every tick, so he never stood. Flying into an area's exit or toggling his age in the air left the screen black until the flight was switched off. A teleport sent from the Debug tab while flying could do the same, and a second request logged "teleport waits until the current event ends". While the event manager has an event playing, the flight now lets him go, so he lands and the event goes on. When it has ended the flight takes him again from where he stands. The second: a respawn after a fall, or a warp, keeps Link's actor, so the flight put him straight back where it had him, over the same drop. With the flight on at an owl cutscene he fell, respawned and fell again until it was switched off. When he is more than 2 units from where the flight last placed him (it moves him 4 a second), the game moved him, and the flight starts again from there. `tests/test_flight_yields_to_events.py` checks both pieces of the host code. ## Testing RTX 4070 Ti SUPER, driver 621.14, Windows 11. Build: the Windows game builds with these two commits on fc375e9 plus #2020 (the same `host/hos_main.c` as this branch apart from #2020's conflict, resolved by keeping both functions). `test_flight_yields_to_events`, `test_movement_speed` and `test_input_background` pass on Linux. Play, on that build: flying, teleported from the Debug tab to Sacred Forest Meadow as adult. Link landed, the map loaded and the picture was normal. On the build before, the same kind of map change while flying gave a black screen until the flight was switched off, several times in one session. Not tested in play yet: the respawn case (the owl), and flying into an exit or toggling age in the air on the fixed build.
…of-the-game, 1406 pipelines (#2064) More play added to my contributed list from #2029, mostly adult areas and dungeons I had not reached before. The data, plus one test change. The list file was re-made with the tool from #2040: ``` python -I nx2/pipeline_states.py add jarynclouatre-most-of-the-game <my lists> --build ... --settings ... --route ... ``` It keeps only pipelines no other contributed list has. Of 8,021 pipelines in my recordings, 1,406 are new, so my file goes from 7,251 to 8,657 pipelines and the merged list from 12,800 to 14,206. The `.md` note lists the new areas: Graveyard, Hyrule Field, Kokiri Forest, Lost Woods, Sacred Forest Meadow, Forest Temple, Death Mountain Trail and Crater, Goron City, Fire Temple and its boss, Zora's River, Domain and Fountain, and the Ice Cavern, all as adult. Recorded on Windows 11, RTX 4070 Ti SUPER (driver 621.14), no mods, 2560x1440 with dynamic resolution, on main with #1961 (some runs also with #2020 and #2050, which change no shaders). As before it holds only hashes, formats and draw state, no game code. **Checked:** - `nx2/pipeline_states.py check` passes. - `tests/test_pipeline_states.py`, `tests/test_ship_state_list.py` and `tests/test_pipeline_state_sources.py` pass. - Based on main at 6cecdd1; it merges cleanly with the tour list (#2065). `test_split_list_lost_nothing` pinned each migrated list's exact size, so growing a list failed it. It now takes each list's first n pipelines, since `add` keeps a list's own pipelines first, and still checks their count, that they don't overlap, and their hash. The same file grown by `add` passes, and main's form of the test fails on it. I have not timed the first start's shader preparation with this list yet.
Requested by Tommy · project thread
Getting around while testing takes a long time at Link's own pace. This adds a Movement speed choice to the Debug tab's tools: 1x, 1.25x, 1.5x, 2x or 3x.
What changed
gpu/vk/vk_menu.cpp): a "Movement speed" row in the Player group (Left/Right choose, stops at 1x and 3x). Only the Debug tab changes; the two-column boxed layout stays as it was. With the debug tools off, Link is back to his own speed. The row's percent goes to the game side as optionHOS_OPT_CHEAT_SPEED(36,gpu/vk/vk_api.h; an older renderer answers 0, which the host reads as 1x, soMKWVK_API_VERSIONis unchanged).nx2/oot_patches.pypatch_cct_speed,host/hos_main.chos_oot_cct_speed): the forward speed the game sets on a character controller goes through the host, which scales it for Link's controller only. It is hooked where the speed is set:f_809552f0, the move Link's walk, run and swim go through (at its two setter calls), plus the controller's setterf_80559df0andf_80964bf0, which inlines it. The physics step then moves him by that speed, so walls and floors still stop him. Like the other debug-tool patches, it is applied only to the supported build's lift.host/nn_hid.chos_move_stick_held), so walks the game scripts (the walk into a map after a map change) keep their pace.+476,OneShotPhysicsCctSetSpeedRate) first. The game sets it again before every step, so writing it had no effect.Testing
In the game (Windows, RTX, headless scripted runs with a throwaway save; Link's position logged once a second while the stick was held forward):
python -I -B tests/run_all.py: the same 62 checks fail with and without this change (they need gcc/g++, which this PC doesn't have). The checks that skip for want of gcc/g++, run with clang instead: no new failures oncetests/test_input_background.pywas updated for the new stick flag inhost/nn_hid.c. New:tests/test_movement_speed.py(the hooks, the host's limits, the menu's choices).Screenshots
The Debug tab at 2560x1440, menu size Small, Medium and Large (at this resolution Medium and Large already used one column before this change):
🤖 Generated with Claude Code