fps-unlock-timing (2026-10-27)¶
Unlimited present rate WITHOUT speed-up. Before: fps_cap N made the virtual vsync fire at N Hz (and mtl_present.m hold frames for 1/N), so a higher cap ran the game faster.
What changed¶
runtime/src/timescale.[ch]:fps_mode(game pacing / match display / custom N / uncapped) as a HOST property (timescale_set_fps,timescale_present_period_ns),TS_GAME_VSYNC_NS, sharedtimescale_deadline_step,NAPPLE_FRAME_TRACECSV.runtime/src/services/vi.c: vsync period = 1/60 s / Speed only (no gfx rate read).gfx:napple_gfx_vsync_period_nsalways 1/60;mtl_present.m(pacing half only): guest-visible frame time isswap interval/60/speedin every mode, the present rate only decides which finished frames are shown (host_min);displaySyncEnabled = !uncapped && vsync(newhost_vsync_offsetting, wired from thevsyncsetting which previously did nothing).- Settings:
fps_mode+fps_cap(custom rate). Migration of old files: fps_cap 0 -> game, 1000 -> uncapped, N -> custom N and Speed N/60 when the file has no speed_percent (decision: keep the old feel explicit instead of silent). Shared menunapple_fps_choice*for the macOS overlay and the iOS panel source. app.m: envNAPPLE_FPS_MODE/NAPPLE_FPS_HZ/NAPPLE_VSYNC.NAPPLE_TURBOis now present-rate only (useNAPPLE_SPEED).tools/fps/frame_trace.py: title x mode matrix, windowed soak.- Docs: Timing section in
docs/architecture/runtime-services.md, Timing paragraph in eachdocs/titles/*.md.
Verified¶
make -C runtime test(test_timescale 55 checks incl. 10 simulated seconds at present rates 30/60/144/999/display/uncapped: identical vsync count and game clock, +-0.1 %),test-host,make -C gfx test,make -C frontend test(53 settings checks incl. migration).- Headless matrix (20 s, 4 titles x game/60/120/30/uncapped): game clock 1.000, guest vsync 60.00 Hz, presents never above vsync in all 20 runs, all exit 0 except one ACNH start (see below; reran twice OK).
- Windowed 60 s soaks: vsync drift <0.2 frame everywhere; p99/median present interval: SMO 1.01 (on and off), ACNH 1.25 / 1.01, LAS 2.1-2.2 and MK8D 4.8-5.0 FAIL the 1.5x bar. Those two present at 46-56 fps because of the known gfx throughput limit (GPU/CPU per frame, hitches at scene loads), not pacing; the pacing code is unchanged for them in game mode. Not fixed here (gfx performance).
Not verified / open¶
- Headless has no layer, so custom-30 does not drop frames there (trace shows 60 presents); the drop path (
host_min) and display-sync off are only exercised windowed and were not trace-measured at custom/uncapped modes. - No real extra game frames: per-title timestep patches are the later package; titles' vsync-dependence is inferred (docs/titles Timing).
- Intermittent ACNH headless start: guest Break (reason 0) at sdk+0xc80730 ~1 s in (1 of ~8 runs, exit 70), independent of fps mode; bug outside this area.
- HUD already showed
gamefps andshownfps separately (display.cuntouched to avoid a render-scale conflict); a rename to guest/present is a one-word follow-up after render-scale lands.