Fixed Time Step Flutter

I've seen a lot of questions about this problem, but really no solution. Hopefully I can explain the issue well enough that I can get some sort of closure on this problem.

I'm using a Fixed Time Step based on the code found here : GAFFER ON GAMES / Fix Your Timestep! , and with that method I'm using an accumulator value to compensate for any slowdowns or speedups in the frame rate. Typically the time between frames is between 15.5 and 17.5 ms (with the fixed rate aiming for 1000/60 fps). What ends up happening is this:

That green chart is showing the accumulator. Which freaks out once the accumulator gets beyond 1000/60 fps. At this point you can see that because of the variance between refresh rates (15.5-17.5) sometimes the accumulator is above or below the threshold of 16.66666666... until it eventually gets to a point that the accumulator is too big to be negated by the next frame variance.

If I simply ignore the refresh rate and assume a constant 60fps the flutter never occurs, so its shouldn't be a vsync issue (right?), it's not the GC kicking in. It's an issue with this part of the code:

accumulator += frameTime;

while ( accumulator >= dt )
{
update_logic();
accumulator -= dt;
}


I realize the issue is when the accumulator hits ~0 it actually starts doing double updates and skipping updates for a while causing the flutter/stutter look.

It's very possible that I'm just not understanding how the accumulator is suppose to work, or perhaps the variance in screen refreshing is too much for this to work well.

Update

Here is a screen cap of an output log showing how the variance in frameTime is causing the update call to double up and skip.

The items in red are when the accumulator never gets to 16.66666666...

For the time being I'm 'fixing' the issue by never letting the accumulator to get below the frameTime variance, but this doesn't feel like a real answer, just a patch.

if(accumulator < 1){
accumulator = 1.5;
}


Perhaps this bit will help explain. If you start the accumulator at 0 and the first frame takes 15.89999ms, we don't update anything, and then if the next frame is 17.55555ms we now have (~33.44ms) so it updates twice. The more the variance in the frameTime the worse the problem.

I've noticed plenty of people with this issue, and typically the conversation ends with no posted solution, it either "goes away" or the thread dies.

• What about problems with frameTime precision? – wondra Jan 5 '16 at 20:41
• @wondra I believe that is the true problem. It's not reliable enough, and I get the impression that the frameTime if averaged out over a few frames is actually closer to 59.9 fps which is why slowly over 30 seconds or so the accumulator gets bigger and bigger. But how do I compensate for this variance? – ericjbasti Jan 5 '16 at 20:56
• Show more code, it can't be that the updates are skipped 'for a while' : if you just made 2 updates sure you can skip one after maaaaayybe 2, but if it's more than 2 skipped there's an error somewhere. – GameAlchemist Jan 6 '16 at 12:20
• @GameAlchemist you've actually described the issue. It's not a constant thing, but its very predictable (per device) which is why I think most people write it off as the GC. It's only once the accumulator gets to 0 that the issue occurs, because thats the only point > a constant of dt can flip flop if the frameTime has enough variance. These results change based on the device, with little variance you would probably never notice the issue, but with enough you see the variance at a fairly constant rate (in my case every ~20s). – ericjbasti Jan 6 '16 at 15:39
• I've tested with the most basic of code, just a RAF that prints out the frameTime (taking out any other logic or rendering that could cause a problem) and the variance remains. – ericjbasti Jan 6 '16 at 15:46

The solution provided in the Blog post you mentionned is not that good, even if you follow it to its end, that is to say if you interpolate.
Because you won't be able to escape the case when the error accumulates until there's no need for update, and then obviously there will be the need for two. It's just maths here.
The interpolation hides that issue since even with the 0+2 issue, the interpolation between two steps gives you almost the +1 status, but at the cost of duplicating all positions to be able to, well, interpolate, and you might even need to store speed or other things if your game depends on it (trail effect...). Ouch.

Now you can improve the algorithm and try to minimize the mean error by considering that if you the accumulated error is 'near enough' from the logic time step, you'll will step and substract the error made. So, if say you accumulated 15 ms and your step is 16, go for an update and consider you accumulated -1.

...But... more or less you have build, with this, just a complicated version of a system doing 1 fixed step update by render frame... Still it is better if you get far from your fixed logic time step ( ex : 20Hz or 120Hz).

Now there's a way that i think is most preferable : if you use an update/logic time step significantly (at least 2 times) smaller than the render time step, then it makes sense again to use the fixed time step principle. Why small time steps could be good ? stepping is a simple process, and limit the hard-to-handle cases of tunneling and 'simultaneous' collisions, i mean collisions occurring during a single step. Still the sad thing being you can't avoid to treat them, but now you might even afford to have an ugly non optimized algorithm given their unlikeliness.

For the win i did a little fiddle, you can play with it to see the various cases you want to explore.

• I need to take some time a truly digest what you're saying here. About the blog post (that everyone swears by) the funny thing is the interpolation doesn't even hide the issue, it only hides it going in one direction (when the screen refresh rate is higher than the dt) its the silly < 16.666666 frameTimes that cause the issues. Just as a side, if I set the desired rate to ~59.97fps the issue take much much longer to occur. – ericjbasti Jan 6 '16 at 17:03