mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
07071e334c
- Updated PresentSync to accurately measure present() block time, ensuring that the probe does not misclassify sync status due to FrameCap sleep artifacts. - Enhanced FixedStep to implement a hard ceiling on catch-up debt, preventing input starvation during high-speed scenarios. - Adjusted logic in Game and Game2 to utilize the new catchupLimit function for setting maxAccum, ensuring consistent behavior across speed multipliers. - Improved test coverage for PresentProbe and PresentSync to validate the new logic and edge cases.
118 lines
4.8 KiB
Lua
118 lines
4.8 KiB
Lua
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.modkit")
|
|
local FrameCap = require("src.core.FrameCap")
|
|
local RefreshRate = require("src.core.RefreshRate")
|
|
local VSync = require("src.core.VSync")
|
|
local PresentSync = require("src.core.PresentSync")
|
|
local FixedStep = require("src.core.FixedStep")
|
|
local PresentProbe = require("src.core.PresentProbe")
|
|
|
|
RefreshRate.reset()
|
|
VSync.reset()
|
|
PresentSync.reset()
|
|
PresentProbe.reset()
|
|
|
|
local function measure(hz)
|
|
RefreshRate.reset()
|
|
for _ = 1, 31 do RefreshRate.sample(1 / hz) end
|
|
end
|
|
|
|
measure(144)
|
|
T.eq(RefreshRate.mismatch(), 144, "144Hz is not a multiple of 60")
|
|
|
|
FrameCap.apply(FrameCap.DISPLAY)
|
|
VSync.reset()
|
|
love.window.getVSync = function() return 0 end
|
|
love.window.setVSync = function() end
|
|
VSync.apply("off")
|
|
T.eq(PresentSync.logicRefreshPeriod(), nil,
|
|
"DISPLAY with vsync off does not snap logic to the panel (#1958)")
|
|
|
|
VSync.reset()
|
|
love.window.getVSync = function() return 1 end
|
|
VSync.apply("on")
|
|
PresentProbe._testSetState({ osLinux = false, ready = true, gated = false,
|
|
needsSoftwareCap = true, nest = "windows" })
|
|
T.eq(PresentSync.logicRefreshPeriod(), nil,
|
|
"DISPLAY with broken sync falls back to software cap, not panel snap")
|
|
T.check(PresentSync.needsSoftwareCap(), "failed sync trips the FrameCap cascade")
|
|
T.check(not PresentSync.displaySyncConfirmed(), "and is not treated as confirmed")
|
|
|
|
PresentProbe._testSetState({ osLinux = false, ready = true, gated = true,
|
|
needsSoftwareCap = false, nest = "windows" })
|
|
T.eq(PresentSync.logicRefreshPeriod(), 1 / 144,
|
|
"DISPLAY with working sync tracks the panel")
|
|
T.check(PresentSync.displaySyncConfirmed(), "gated+clear softcap is confirmed sync")
|
|
|
|
FrameCap.apply(60)
|
|
T.eq(PresentSync.logicRefreshPeriod(), nil,
|
|
"a 60 cap on a 144Hz panel leaves refresh snapping off")
|
|
|
|
FrameCap.apply(144)
|
|
T.eq(PresentSync.logicRefreshPeriod(), 1 / 144,
|
|
"a 144 cap on a 144Hz panel may snap logic to the panel")
|
|
|
|
PresentSync.applyFixedStepPeriod()
|
|
T.eq(FixedStep.refreshPeriod, 1 / 144, "applyFixedStepPeriod writes the module field")
|
|
|
|
-- Probe isolation: during calibration we do NOT soft-cap (raw swapchain),
|
|
-- and we never snap logic until sync is confirmed.
|
|
FrameCap.apply(FrameCap.DISPLAY)
|
|
VSync.apply("on")
|
|
PresentProbe._testSetState({ osLinux = false, ready = true, clearGated = true,
|
|
needsSoftwareCap = false, nest = "windows" })
|
|
T.check(PresentSync.probingDisplaySync(), "DISPLAY+vsync probes before a verdict")
|
|
T.check(not PresentSync.needsSoftwareCap(),
|
|
"probe isolation: FrameCap is not forced during calibration")
|
|
T.check(not PresentSync.displaySyncConfirmed(), "unconfirmed while still probing")
|
|
T.eq(PresentSync.logicRefreshPeriod(), nil, "and does not snap logic during warmup")
|
|
|
|
PresentProbe._testSetState({ gated = true, needsSoftwareCap = false })
|
|
T.check(not PresentSync.probingDisplaySync(), "a finished probe clears warmup")
|
|
T.check(PresentSync.displaySyncConfirmed(), "gated verdict confirms display sync")
|
|
T.check(not PresentSync.needsSoftwareCap(), "so software cap stays off once sync is confirmed")
|
|
|
|
-- Broken sync after an honest ungated probe keeps the thermal net and no snap.
|
|
PresentProbe._testSetState({ gated = false, needsSoftwareCap = true })
|
|
T.check(PresentSync.needsSoftwareCap(), "ungated DISPLAY keeps FrameCap")
|
|
T.check(not PresentSync.displaySyncConfirmed(), "ungated is never confirmed")
|
|
T.eq(PresentSync.logicRefreshPeriod(), nil, "and never snaps logic to the panel")
|
|
|
|
VSync.apply("on")
|
|
PresentProbe._testSetState({ needsSoftwareCap = true, gated = false })
|
|
T.check(PresentSync.vsyncEnableBlocked(), "broken sync blocks enabling vsync")
|
|
T.check(PresentSync.vsyncStepAllowed("on", 1), "but one step to OFF is allowed")
|
|
T.check(not PresentSync.vsyncStepAllowed("on", -1),
|
|
"while a step that stays on/adaptive is not")
|
|
|
|
-- FixedStep snaps wall-clock dt, then applies speed (not the reverse).
|
|
FixedStep.refreshPeriod = 1 / 60
|
|
local steps = 0
|
|
FixedStep:init(function() steps = steps + 1 end)
|
|
FixedStep.maxAccum = FixedStep.catchupLimit(4)
|
|
FixedStep:update(1 / 60, 4)
|
|
T.eq(steps, 4, "4X on a snapped 60Hz frame runs four logic steps")
|
|
steps = 0
|
|
FixedStep.maxAccum = FixedStep.catchupLimit(2)
|
|
FixedStep:update(1 / 60, 2)
|
|
T.eq(steps, 2, "and 2X runs two")
|
|
|
|
-- Catch-up debt hard ceiling: high speed cannot raise maxAccum past MAX_ACCUM.
|
|
T.eq(FixedStep.catchupLimit(1), FixedStep.STEP * 2,
|
|
"1X catch-up floor is two steps")
|
|
T.eq(FixedStep.catchupLimit(4), 4 * FixedStep.STEP * 1.5,
|
|
"4X catch-up matches one frame of target work")
|
|
T.eq(FixedStep.catchupLimit(200), FixedStep.MAX_ACCUM,
|
|
"200X catch-up is hard-capped at MAX_ACCUM (no input-starving spiral)")
|
|
FixedStep.refreshPeriod = nil
|
|
|
|
love.window.getVSync, love.window.setVSync = nil, nil
|
|
VSync.reset()
|
|
PresentSync.reset()
|
|
PresentProbe.reset()
|
|
RefreshRate.reset()
|
|
FrameCap.apply(FrameCap.DEFAULT)
|
|
|
|
T.finish("present sync logic")
|