WIP: fix(vi): pace the 30 FPS cap in the host #609
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!609
Loading…
Reference in a new issue
No description provided.
Delete branch "TheDevil/Longshot:fix/30fps-host-pacing"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type: fix · Risk: medium · Tested on: Windows, llvm-mingw game-free fixtures · Needs: in-game Windows and Linux pacing validation
Why
Players cannot select a host-paced 30 FPS limit:
HOS_FPS_MAX=30is raised to 60 on main. This extracts and corrects the frame-cap portion of #173; it does not include input, wheels, NR or the patched-runtime bridge. See #306 for the related request for lower frame limits; this change limits game frames, not additional generated frames.How to reproduce
Seen on: production host code exercised by Windows llvm-mingw fixtures; an in-game reproduction for this revision is still pending.
Settings and switches:
HOS_FPS_MAX=30; alternatively the new menu choice, with Game speed set to Per frame.Steps: run
tests/test_frame_rate_cap_30.py --cc clangandtests/test_frame_rate_cap_step.py --cc clang.Expected: the host, game limiter and vsync period target 30; elapsed game time follows wall time; only one pacing mechanism is used.
Actual: main clamps the environment override to 60 and does not recognize the menu's new -1 choice. The combined PR's separate renderer sleep masked this rather than fixing the host.
What changed
Thirty FPS is now a choice of the existing host pacer. The engine limiter, display period and time-step hooks agree on that rate; the renderer needs no second limiter.
host/nn_vi.c: accept 30 from the menu and environment, keep initialization separate from the valid -1 menu value, keep interval 1 for the 30 Hz period, override Per frame at 30, and restore the normal limiter/step when returning to 60.gpu/vk/fps_stats.hand the menu: matching 30 FPS graph target, existing 0–6 saved cap values unchanged, explicit help text for the real-time override, translated notices.getenvorsleep_untilis added to the renderer.Measurements
Verdict: game-free regression checks pass; current-revision in-game A/B measurements are not yet available. The previous combined PR's renderer-sleep measurements are not reused as evidence for this host fix.
Before: main @
2b1d38c682After: this PR @
74f1da1924Hardware: Windows x64, i7-14700F, 16 GB RAM; GPU/driver not exercised by the game-free fixtures.
Scene: authored option/clock/input fixtures; no gameplay rendered for this revision.
How to reproduce: run
python -I -B tests/run_all.pywith the official llvm-mingw compiler on PATH; focused commands are listed under Testing.Method: extracted production functions compiled with llvm-mingw, fake options/clock/guest state, no game or driver calls.
2b1d38c674f1da19HOS_FPS_MAX=30host ratefps_of)Flags and settings
The menu adds 30 without changing its default of 60. Existing options-file cap indices remain compatible.
Testing
Tested: Windows game-free full suite on both named revisions; focused cap, time-step, graph, environment and translation checks pass.
git diff --checkpasses. Compiler: the installed official nightly llvm-mingw toolchain.The identical baseline failures are:
tests/test_appimage_player_build.py,tests/test_cpm_discovery.py,tests/test_crash_dialog.py,tests/test_env_on.py,tests/test_finish_output.py,tests/test_lift_cleanup_error.py,tests/test_linux_preflight.py,tests/test_make_game_cache.py,tests/test_oot_state_tap.py,tests/test_readback_late.py,tests/test_tested_configurations.py,tests/test_vk_pgo.py,tests/test_vram_budget_row.py,tests/test_walk_key.py. The before/after logs are retained locally; no game files or local paths are uploaded.Not tested: this revision in a running game, Linux build/runtime, physical displays at different refresh rates, cutscene/audio timing and generated-frame output. WIP intentionally records these gaps rather than reusing older combined-feature results.
Must be tested:
HOS_FPS_MAX=30; alternate 30/60 twice, verify roughly 33.3/16.7 ms presents and normal game/audio time, including saved Per frame. Record two interleaved runs each at these commits, expecting correct timing/input behavior without regressions (by hand).Risks
What could break: switching the game's limiter and nominal/timed step at 30 could expose game-specific frame-count assumptions, display pacing or cutscene timing problems.
HOS_GAME_INTERVAL, no-pacing and frame-generation combinations still need live runs.Turning it off: choose an existing cap (60/default) and unset
HOS_FPS_MAX; reverting this commit restores the prior selection range.Compatibility: no save changes, no shader/cache changes, no vk ABI change. Default and saved cap indices 0–6 retain their meanings. New -1 selects 30; older builds fall back to 60 for it. The saved Game speed preference is retained and used again at 60.
Checklist
Everyone
type(scope): description) of the status line's type.main.python -I -B tests/run_all.pypasses (any failure that also fails onmainis named).third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:trailers.docs/tested/fragment: pending live validation; WIP.Upstream refresh
Current head:
a9c0551156ae2043f441b4cfee05f1784fd58ed5. Main snapshot6c9edffeis incorporated by merge, preserving published history. 5 focused checks passed after the merge; zero failures/skips. Earlier full-suite measurements remain tied to their named feature commits above. The literal rebase checklist remains unticked because history was merged, not rewritten. Fresh game/Linux checks remain WIP.Aligned the description with the current template and named fixture results. Pending in-game/Linux validation remains explicit and the PR stays WIP.
Updated the branch to include main @
6c9edffeby non-rewriting merge. Current heada9c0551156; 5 focused post-merge checks passed, 0 failed/skipped. Description preserves commit-specific full-suite results and remaining live/Linux validation. Still WIP.Tested on RTX 5080, Windows 11: pass. 30 holds steadily, game time stays real, and switching between 30 and 60 in play works.
Builds. After: this PR @
a9c05511, built withmake_game.py(DLSS, shaders prepared). Before: main @25726217(host/nn_vi.candgpu/vk/fps_stats.hhave not changed between it and6c9edffe). Same saves and options on both.Method. Runs were headless with audio off and real pacing (no
HOS_NO_PACING, noHOS_REALTIME_STEP), withHOS_FPS_LOG=1andRX_VK_FRAME_LOG. I stood still in Kokiri Forest after a teleport and changed the Game frame rate cap through the control bridge (option set frame_rate_cap) every 600 presents: saved value → 30 → 60 → 30 → 60, then Game speed Per frame → 30 → 60. Main and the PR were run alternately, 3 runs each. The saved cap was 5 (240) at the start. Frame times are present intervals from the frame log, and the medians are per 600-present segment.25726217a9c05511option set-1 is cut to 0)vi: game clockspeed 1.000 (real time), and Per frame still saved when back at 60HOS_FPS_MAX=30(2 runs each)vi: frame rate cap 30from the startvi: game clockspeed at 30frame_rate_cap=5at startvi: frame rate cap 240)frame_rate_cap=-1at startEach switch logs one
vi: frame rate cap 30: pacing, the game's limiter and its time step follow itline, orframe rate cap 60: the game's own limiter and step. The host is the only limiter, and nothing in the renderer sleeps. Saves and options were restored after every run and checked withdiff -rq.One PR run (run 2) had a stretch of about half a minute where the game's CPU side slowed down: the game's own frame rate fell to 21, GPU time was 6.3 ms a frame, and speed was 0.76 for one 5 s window. It started in the middle of a 30 segment and went on through the next 60 one, so it doesn't follow a cap switch. It didn't come back: run 3, with the same steps, was clean, so it was most likely other load on the PC. Run 2's other segments were 33.3 and 16.7 ms.
Our review on #173 (comment 20679), point by point:
present_intervalis 1 at 30, so the console's interval 2 doesn't halve it to 15. There is no renderer sleep, andtest_frame_rate_cap_30.pychecks that none is added.HOS_FPS_MAX=30still giving 60: fixed in bothfps_ofandfps_stats::cap_rate, and seen in game.HOS_FPS_MAXread every frame: fixed. The host reads it once (thereadflag also fixes main'sstart = -1sentinel, which clashed with -1), and the renderer reads it once (static).Nit (not blocking): the options file loads a value under -1 as -1, since it is clamped to the row's
lo, so a hand-editedframe_rate_cap=-2gives 30 once the renderer is up. The fixture's(-2, unset) → 60case doesn't include that clamp. No build writes such a value.Game-free suite (
python -I -B tests/run_all.py, llvm-mingw): this PR 523 pass / 16 fail / 42 skip; main6c9edffe522 / 16 / 42 with the same 16 failures (appimage_player_build, bake_portability, cpm_discovery, crash_dialog, env_on, finish_output, lift_cache, lift_cleanup_error, linux_preflight, make_game_cache, oot_state_tap, readback_late, tested_configurations, vk_pgo, vram_budget_row, walk_key). The extra pass is the newtest_frame_rate_cap_30.py.Not tested here: a cutscene, audio (it was off), a real display with V-Sync (the runs were headless), frame generation,
HOS_GAME_INTERVAL=1, and Linux.This pull request now conflicts with
main: host/nn_vi.c, which #488 changed (the movie pacing). Could you rebase it onto the currentmain? Thank you.View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.