fix(fps): water ripples keep moving above 60 fps #2409

Merged
hckrman101 merged 1 commit from port/fritchii-timing into main 2026-11-11 18:02:07 +00:00
hckrman101 commented 2026-11-11 17:51:27 +00:00 (Migrated from github.com)

Title: fix(fps): water ripples keep moving above 60 fps

With the Frame rate cap above 60, the rings that Link and other actors make in water stop moving. The water's ripple simulation runs a whole number of steps per frame. Above 60 fps each frame's step is less than 1, so that number rounds down to 0 every frame.

What changes

  • The water renderer (lifted f_800672e0) runs its ripple simulation (int)step × substeps times a frame.
  • Under a cap above 60, the game's step is the frame's own time (#1612). At 144 fps that is about 0.42, which cuts to 0, so the ripples freeze.
  • This is the same pattern as the pick-up indicator (issue 1832). The cut now goes through hos_frame_whole (nx2/oot_patches.py WHOLE_SITES), with the fraction carried per water object (x21). Each surface then runs its 60 steps a second whatever the frame rate.
  • The fix also helps when frames are slower than 60 fps under a cap above 60: a step of 1.3 used to cut to 1.
  • At a cap of 60 or below the game's own step is used, as before, and nothing changes.
  • host/frame_whole.h keeps 64 remainders instead of 16. A scene's water surfaces each use one every frame, and if there were more surfaces than slots they would push each other out in turn and freeze again.

Ported from Fritchii. atk3001 found it for Lichii (zortos293/Lichii#1152 and #1180). Frizti ported the site and the per-object accumulator to Fritchii: d389685a4 and 063dbb741. Fritchii patches the game's code with an accumulator cave. Here it is one more entry in the existing whole-frames mechanism.

Verified

  • tests/test_frame_whole.py covers the new site (the patch replaces its one cut, once). It now also checks that 40 surfaces, each stepped once a frame at 144 fps, still get 59–60 steps a second, and that a reused slot starts again from 0.
  • Lifting the supported build applies the patch at both sites ("2 frame counters"), with no warning.
  • In game (RTX 3090, Linux, headless, my own dump): teleport to Lake Hylia, walk into the water, Frame rate cap 144.
    • This PC was too loaded for the game to go past 60 fps headless (20–50 fps). So, as a local test only, I scaled the step by 0.15, which is what a ~150–250 fps frame is to the game. I compared the water site cut as before against carried, in the same build.
    • Cut as before: 28 calls a second, 0 ripple steps a second (steps of 0.26–0.36).
    • This PR: about 10 steps a second, which is 60 × 0.15, the game's own rate.
    • Without the scale, at 20 fps with steps of about 1.3: 19.4 steps a second cut versus 24.0 carried (60 × 0.4). The cut also ran the ripples slow there.
    • Screenshots: the ripple rings around Link, cut (top) against this PR (bottom), each pair taken about 10 s apart. water
    • Lake Hylia, main against this PR at the real step: nothing else changes. lake
  • tests/run_all.py: 356 passed, 13 skipped. The 4 failures (test_wayland_libdecor_hint, test_nup_read_name, test_bake_portability, test_builder_log_read_only) also fail on main on this machine.

Not tested

  • A real run above 60 fps. The freeze was reproduced with a scaled step, not at a real high frame rate.
  • Windows.
  • Scenes with many water surfaces at once (Zora's Domain, Water Temple).
**Title:** fix(fps): water ripples keep moving above 60 fps With the Frame rate cap above 60, the rings that Link and other actors make in water stop moving. The water's ripple simulation runs a whole number of steps per frame. Above 60 fps each frame's step is less than 1, so that number rounds down to 0 every frame. ### What changes - The water renderer (lifted `f_800672e0`) runs its ripple simulation `(int)step × substeps` times a frame. - Under a cap above 60, the game's step is the frame's own time (#1612). At 144 fps that is about 0.42, which cuts to 0, so the ripples freeze. - This is the same pattern as the pick-up indicator (issue 1832). The cut now goes through `hos_frame_whole` (`nx2/oot_patches.py` `WHOLE_SITES`), with the fraction carried per water object (`x21`). Each surface then runs its 60 steps a second whatever the frame rate. - The fix also helps when frames are slower than 60 fps under a cap above 60: a step of 1.3 used to cut to 1. - At a cap of 60 or below the game's own step is used, as before, and nothing changes. - `host/frame_whole.h` keeps 64 remainders instead of 16. A scene's water surfaces each use one every frame, and if there were more surfaces than slots they would push each other out in turn and freeze again. Ported from Fritchii. atk3001 found it for Lichii (zortos293/Lichii#1152 and #1180). Frizti ported the site and the per-object accumulator to Fritchii: [d389685a4](https://github.com/Frizti/Fritchii/commit/d389685a4c019d1627d76369a5357b58b7c4a71e) and [063dbb741](https://github.com/Frizti/Fritchii/commit/063dbb741fc42a666bc658404b3ead3860b85b7b). Fritchii patches the game's code with an accumulator cave. Here it is one more entry in the existing whole-frames mechanism. ### Verified - `tests/test_frame_whole.py` covers the new site (the patch replaces its one cut, once). It now also checks that 40 surfaces, each stepped once a frame at 144 fps, still get 59–60 steps a second, and that a reused slot starts again from 0. - Lifting the supported build applies the patch at both sites ("2 frame counters"), with no warning. - In game (RTX 3090, Linux, headless, my own dump): teleport to Lake Hylia, walk into the water, Frame rate cap 144. - This PC was too loaded for the game to go past 60 fps headless (20–50 fps). So, as a local test only, I scaled the step by 0.15, which is what a ~150–250 fps frame is to the game. I compared the water site cut as before against carried, in the same build. - **Cut as before:** 28 calls a second, **0** ripple steps a second (steps of 0.26–0.36). - **This PR:** about 10 steps a second, which is 60 × 0.15, the game's own rate. - Without the scale, at 20 fps with steps of about 1.3: 19.4 steps a second cut versus 24.0 carried (60 × 0.4). The cut also ran the ripples slow there. - Screenshots: the ripple rings around Link, cut (top) against this PR (bottom), each pair taken about 10 s apart. ![water](https://raw.githubusercontent.com/hckrman101/Hookshot/e541336bd08b7f8f261b56436c914e7aacf42bcc/fritchii-timing/timing-water-ripples-cut-vs-carry.png) - Lake Hylia, main against this PR at the real step: nothing else changes. ![lake](https://raw.githubusercontent.com/hckrman101/Hookshot/e541336bd08b7f8f261b56436c914e7aacf42bcc/fritchii-timing/timing-lake-hylia-main-vs-pr.png) - `tests/run_all.py`: 356 passed, 13 skipped. The 4 failures (`test_wayland_libdecor_hint`, `test_nup_read_name`, `test_bake_portability`, `test_builder_log_read_only`) also fail on main on this machine. ### Not tested - A real run above 60 fps. The freeze was reproduced with a scaled step, not at a real high frame rate. - Windows. - Scenes with many water surfaces at once (Zora's Domain, Water Temple).
Commenting is not possible because the repository is archived.
No description provided.