Skip to content

feat(fps): add a 30 FPS cap that keeps real time - #2582

Open
monsterlag wants to merge 3 commits into
NotRexed:mainfrom
monsterlag:feat/30-fps-cap-2570
Open

monsterlag wants to merge 3 commits into
NotRexed:mainfrom
monsterlag:feat/30-fps-cap-2570

Conversation

@monsterlag

@monsterlag monsterlag commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Handhelds and slower PCs currently cannot select a 30 FPS cap. This adds that option while keeping normal game speed, including when an existing settings file selects Per frame.

  • Append 30 at saved index 7, after Uncapped, preserving all existing 0–6 settings and the 60 FPS default; also accept HOS_FPS_MAX=30. The ordering is intentional for saved-setting compatibility.
  • Apply it to native pacing and the guest limiter. Use the existing measured-frame hook so game time, sound, movie cues and counters advance by two 60ths per 30 FPS frame.
  • Support the initial nominal-60 fallback and use a 33.33 ms FPS-graph target. The menu explains the required restart; the complete cap hint is updated in all 13 translation tables.

Existing caps above 60 also change when Game speed is set to Per frame: hos_vi_native_interval now uses one host display interval instead of the game's requested multiple (2–4). For example, at a cap of 120 a requested interval of 2 previously paced at 60; it can now reach 120 when the hardware and display settings allow it. This matches realtime_step, which already forces real-time game speed above 60. Per frame retains its requested-interval behavior at a cap of 60. The explicit HOS_GAME_INTERVAL=1 diagnostic override still honors the requested interval at every cap.

Review updates:

  • Updated against main e0e60920 and resolved the test-results table conflict while preserving all upstream records and the original 30 FPS record.
  • Removed the tracked game capture and its link from docs/tested-configurations.md. The screenshot remains below in the PR description; no screenshot is included in the PR's file changes.
  • Synchronized the translated help keys and added the 30 FPS explanation in every existing menu language. Counts and integrity assertions are retained.

Validation:

  • CI passed on 19866037: both Ubuntu and both Windows source-test shards, Windows Vulkan, Linux AppImage, Builder package/setup, SPIR-V fixtures, workflow lint and title checks are green. Release-publishing jobs are intentionally skipped for pull requests.
  • 26 focused menu/timing/switch checks passed on the refreshed branch, with no skips, using native Windows Python and LLVM-MinGW. Translation integrity passed again after matching the translated setting labels. This includes test_env_on.py; the previous upstream lint failure is fixed on current main.
  • Coverage includes saved indices, overrides, interval requests at 30/60/120 with both Game speed modes and the diagnostic override, missing/disabled hooks, elapsed time, movie cues, sound, solver steps and counters. Both new 30 FPS cases reject the original source.
  • Live acceptance was performed on runtime commit b555f82a: a fresh matched API18 Windows build passed; the menu saved 30, closed normally and retained it after restart with Per frame still selected. Windows 11 Pro 26300, RTX 5090 / 617.42, Ryzen 7 9800X3D, 96 GB; 1280x720 window, 2048x1152 output, Docked, game upscaler/Auto render resolution, FG Off. A warmed stationary Kokiri Forest scene from a copied 11/6 12:58 a.m. autosave produced 2,175 native presents over 72.5003 s = 30.000 FPS, median 33.28 ms, p95 35.05 ms, longest 57.54 ms; two intervals exceeded 40 ms. Eight game-clock samples reported 59.99–60.01 units/s, speed 1.000. These are native present-call return times, not GPU/display completion times. Normal exit; canonical saves/settings and installed players unchanged. The run is recorded in docs/tested-configurations.md under the build actually tested.

Unverified: a new gameplay run after integration with current main, handheld battery/performance, live audio/cutscene synchronization, movement/controllers, moving platforms and frame generation. This is a cap/clock acceptance check, not a handheld speedup claim.

30 FPS active after restart, with frame generation Off (from the recorded test build):

30 FPS cap after restart

Fixes #2570
See #1007

Add a selectable 30 FPS cap without changing existing saved option indices.
Use measured frame time for game, sound, movie and counter updates, keep
Per frame from halving the cap, and accept the nominal-60 fallback.

Fixes NotRexed#2570

Assisted-by: OpenAI Codex
Record native present timing, game-clock speed, restart persistence,
hardware and remaining validation gaps. Include the observed menu image.

Assisted-by: OpenAI Codex
@monsterlag
monsterlag marked this pull request as ready for review November 12, 2026 00:53

@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. It conflicts only in docs/tested-configurations.md, and I took your side to test. tests/test_frame_rate_cap_step.py, test_frame_whole.py, test_high_fps_fixes.py, test_movie_frame_count.py, test_fps_stats.py and test_env_on.py pass. The Kokiri run with the game-clock speed is the right measurement.

What has to change:

  • docs/images/30-fps-cap.jpg is a capture of the game, with Kokiri Forest and the minimap behind the menu. CONTRIBUTING doesn't allow anything made from the game files in the repo, and screenshots in docs/ have been turned down before. Please remove the file and the link to it in docs/tested-configurations.md, and keep the picture in the PR description only.
  • Rebase on main for the tested-configurations.md conflict.

For the maintainers to decide: 30 sits after Uncapped (index 7), so the row reads …240, Uncapped, 30: old saves stay valid, but it looks odd.

Also, host/nn_vi.c:150 now paces to interval 1 at every cap other than 60 when Per frame is selected. Before, caps above 60 with Per frame paced to the game's interval. That is probably a fix, but it changes things for players with a cap above 60, so please mention it in the description.

Merge current main, retaining upstream test records and the measured
30 FPS run. Remove the tracked game screenshot and its docs link.
Synchronize all 13 cap-help translations with the changed menu text.

Validation: 26 focused menu/timing/switch checks passed, with no skips;
translation integrity rechecked after matching the localized labels.

Assisted-by: OpenAI Codex
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.

30 FPS Option

2 participants