One Frame Behind Forever — Java Bug Hunt

Inspired by the fixed-timestep game loops described in every "Fix Your Timestep!" postmortem: the accumulator pattern must run whole simulation frames and…

  • Language: Java
  • Layer: Frontend
  • Difficulty: Hard
  • Concepts: Game Loops, Rounding
  • Modelled on: Game engines
  • Visible tests: underfunded frames do not run; full frames run with exact remainder; exact multiples leave zero remainder
  • Reward: 50 XP for a complete fix

Briefing

Inspired by the fixed-timestep game loops described in every "Fix Your Timestep!" postmortem: the accumulator pattern must run whole simulation frames and carry the exact remainder. Round instead of floor, and the simulation runs frames it hasn't earned — leaving a negative time debt that compounds into stutter.

GameLoop.java computes frames-to-run from the accumulator.

Bug report

BUG-TIMESTEP · Reported by: engine team

framesToRun(accumulatorMs, frameMs) -> int[]{frames, remainderMs}:

  • frames = floor(accumulator / frame) — only FULLY funded frames
  • remainder = accumulator - frames * frame (always 0 <= r < frame)

Observed: 15ms of accumulated time runs a 16ms frame — remainder -1ms. The debt snowballs and the camera micro-stutters forever.

Logs

[loop] acc=15 frame=16 -> ran 1 frame, remainder -1

The code as shipped

GameLoop.java (editable)

class GameLoop {
    static int[] framesToRun(int accumulatorMs, int frameMs) {
        int frames = Math.round((float) accumulatorMs / frameMs);
        int remainder = accumulatorMs - frames * frameMs;
        return new int[] { frames, remainder };
    }
}

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More Java bug hunts.