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
2 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.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.