Life has slowed my progress way down, but I'm still noodling around with Chipmunk2D physics, trying to gracefully expose the library to Lua and see how much perf I can squeeze out of it on device. It's going pretty well, even if the device's CPU limitations definitely come into play. The thing I wanted to share early, though, was this nugget that was linked in one of the Chipmunk SDK samples:
Gaffer on Games: Fix Your Timestep!
So as @matt has demonstrated, unlocking the Playdate's refresh rate with setRefreshRate(0) adds a little homework but unlocks some tremendous super powers in terms of smoothness and responsiveness. The Playdate's display is an absolute champion of variable refresh rate. So simple, just unlock your frame rate, find out how much time elapsed since the last frame, advance the engine however much time passed, and render the next frame! If you're going too fast, you can always set a frame rate limit and skip the update loop if not enough time has passed (which is what I think is all pd.display.setRefreshRate() is doing).
Trouble is, your game might not be built to handle arbitrary spans of elapsed time that change every frame. Physics engines, as I found out the hard way, really do not like this, but I'm sure there are plenty of game engines that would be significantly harder to write if you had to make them able to advance arbitrary amounts of time every frame. If your engine is easier to write where it's based on a nominal world update speed (I'm using 100Hz now), just call that fixed step however many times you need to to fill the time that's elapsed since the last frame. Throw the rounding error in an accumulator variable so that partial steps get used up as needed. You're at risk of ugly judder (temporal quantization artifacts!) if the steps length is just the wrong distance from your display refresh rate, but I haven't detected any visible judder running a 100Hz physics refresh with a display going usually 40-80Hz. Fiedler suggests using position interpolation if judder is an issue, which is a pretty baller move, but I haven't needed to.
Here's what it looks like in my Chipmunk prototype:
FixedStepMs = 10 --100fps/10ms physics
StepAccumulator = 0
MaxStepsPerFrame = 7 --allow slowdown if it frame time is over 70ms - 15fps may be tolerable
LastUpdate = playdate.getCurrentTimeMilliseconds()
function playdate.update()
fixedRefresh()
end
function fixedRefresh --derived from https://gafferongames.com/post/fix_your_timestep/
local now = playdate.getCurrentTimeMilliseconds()
if now < LastUpdate + fixedStepMs then return end -- simple rate limiter
updateInputs()
local frameTime = now - LastUpdate
StepAccumulator += frameTime
LastUpdate = now
local steps = 0
while StepAccumulator >= FixedStepMs and steps < MaxStepsPerFrame do
steps = steps + 1
StepAccumulator -= FixedStepMs
end
for i=1, steps do
updateWorld(fixedStepMs)
end
updateGraphics()
end