Why Does My Game Speed Up Every Time I Restart It?

In Velocity Puck, restarting created a new setInterval(animate, 1000/60) loop while the old interval was not always cleared, so after a few restarts 3, 5, or even 10 loops could run at once and the game sped up. The fix stops any existing loop and nulls its handle before every start, disposes scene geometry, materials, textures, and renderer on restart, and removes the if (!gameStarted) return; guard that stopped logic but not the interval. No frame-rate number is claimed; the devlog gives none.

Why Does My Game Speed Up Every Time I Restart It?
Guide chapters5 chapters

Restarting a game should reset it, not make it faster. In Velocity Puck, an air-hockey game from Steel Cyclone Studios, players reported the opposite: after a restart, the frame rate would suddenly shoot way higher than normal, and gameplay felt unusually fast or inconsistent. The cause was not a faster computer or a changed setting. It was a second animation loop, then a third, then more, all running at once because restarting the game kept starting a new loop without reliably stopping the old one.

Velocity Puck art from the developer's itch.io page: a composite image with the character selection menu on the left, a custom avatar preview at top right, and a top-down air-hockey table at bottom right. This picture shows the game, not the animation-loop bug.

At a glance

  • The source: Velocity Puck Devlog #10, about the major frame-rate performance bug fix, posted on itch.io on November 29, 2025, by Steel Cyclone Studios. Every fact about the game below comes from that devlog.

  • The game: Velocity Puck, a fast-paced, physics-driven air-hockey game built with Three.js, with one-on-one matches against AI opponents or a second player offline.

  • The symptom: after restarting, the frame rate shot way higher than normal and gameplay felt unusually fast or inconsistent. After a few restarts, the devlog says the game might be running 3, 5, or even 10 animation loops at once.

  • Not claimed here: no numeric frame-rate result, before or after the fix, no Three.js version, browser, operating system, or test hardware, and no claim that the code shown in the devlog is a verbatim copy of the shipped game code. The devlog presents a simplified illustration of the start, end, and restart flow, and this article treats it that way.

  • Last verified: November 1, 2026.

One loop is a heartbeat. Ten loops are a stampede.

The bug was found because players spoke up, and the devlog says so plainly: the problem was only discovered because players reported unusual behavior. That matters, because this kind of bug does not announce itself with a crash or an error message. The game still runs. It just runs wrong, in a way that feels like speed rather than breakage. A player who restarts once may notice nothing. A player who restarts a few times gets a game that feels hyperactive, and the natural instinct is to blame the frame cap, the graphics settings, or the machine. In this case, none of those was the problem. The problem was quantity: too many loops doing the same job at the same time. That mismatch is also why the symptom is easy to misread. Speed feels like a performance win, so a player may first suspect a setting was changed or a limit was raised, when in fact the game is doing the same work several times over. Counting loops, rather than admiring the speed, is what turns the symptom back into a diagnosis.

To see how that happens, follow the loop the devlog describes. A function called animate() updates the 3D scene, moves objects, and checks collisions. The game runs it 60 times per second with setInterval(animate, 1000 / 60), and a function called startGame() creates that interval. A function called endGame() clears it. So far, so tidy. The trouble lives in the restart path. The flow runs from a finished match through endGame(), to a Game Over screen, to restartGame(), and then startGame() is called again, which creates a new animation loop. But sometimes, the devlog says, the old interval was not cleared in time or at all. Each restart can therefore leave a survivor behind. Two animate loops run at the same time, then three, then four, each one stacking more draw calls onto the browser, and the game speeds up like crazy. The devlog gives the range plainly: after a few restarts, the game might be running 3, 5, or even 10 loops at once. There is a second trap hiding in the same code. A guard line, if (!gameStarted) return;, sat inside animate(). It stopped the logic, but not the interval itself. A flag that says stop is not the same as a loop that has actually stopped, and this bug is what that difference looks like in a real game.

Stop first, then start: the three-part fix

The devlog presents three improvements, and the first one is the load-bearing change: always clear the animation loop before starting a new one. A new function, stopGameLoop(), clears the interval and sets the handle to null, and it is called before every start. In the final flow the devlog shows, startGame() calls stopGameLoop() first, then initializeScene(), then startGameLoop(), and startGameLoop() itself calls stopGameLoop() again before creating its interval. The order is the point. Starting a loop is no longer allowed to assume the previous loop is gone; it must prove it, by stopping whatever exists first. That is what guarantees only one active loop at all times, in the devlog's own words. If you remember one line from this case, remember that order: stop, then start, every time, without exception.

The second improvement cleans up the scene itself. Three.js stores objects in GPU memory, the devlog notes, and an old scene left in memory can slow things down. So before starting a new game, a function called cleanupScene() walks the scene with scene.traverse and disposes geometry, material, and material textures, then calls renderer.dispose(). The devlog's summary of that step is that nothing carries over to the next run. The third improvement removes the misleading guard: the if (!gameStarted) return; line comes out of animate(), and the loop now stops correctly by clearing the interval instead of by asking a flag to pretend the loop is off. The reported result is deliberately modest, and this article keeps it that way: the frame rate no longer skyrockets after restarting, and the game behaves consistently no matter how many times the player returns to the main menu or starts a new match. The devlog calls the result smooth, memory-safe, predictable, and FPS-stable, but it gives no numeric frame-rate measurement, so this article gives none either. Do not read a number into a claim the source never made.

What to reuse

Nothing below is presented as what the Velocity Puck developer did beyond the three improvements already described; the checklist generalizes those improvements for your own project, and it stays inside it. The reusable idea is loop ownership: whoever starts a loop must own stopping it, and the restart path is where ownership usually breaks, because the happy path is tested far more than the path that runs for the fifth time. A game that behaves on first launch and misbehaves on the fifth restart is telling you that its start and stop logic disagree. The fix is not a faster loop or a stricter frame cap. It is a single, enforced rule: no new loop may start until the old one is provably gone, and no old scene may survive into the new run. Test the restart the way a real player uses it, repeatedly, impatiently, and from every screen that can lead back into a match. That is the test the happy path never gives you, and it is the test that would have caught this bug before players had to report it.

What to do Monday morning

  • In your own project, restart the game five times in a row and watch for speed changes. A game that only misbehaves after several restarts is pointing at loop lifecycle, not at graphics settings.

  • List every place in your own code that starts an animation loop or timer. If more than one path can start one, that list is your suspect list.

  • In your own project, make stopping the first step of starting: clear the existing interval and null the handle before any new interval is created, on every start path, not just the main one.

  • In your own project, audit every flag that claims a loop is off. If the flag stops logic but the interval keeps firing, replace the flag check with a real stop, and retest the restart path.

  • In your own project, dispose scene resources on exit and restart, geometry, materials, and textures, so an old scene cannot carry over into the next run and quietly cost performance.

  • In your own project, test restarts from every screen that can lead back into a match, menu, Game Over screen, and pause screen included, not just the clean first launch.

Save this guide

Sign in to save guides and sync them across devices.

Sign in