Posted November 29, 2025 by SteelCycloneStudios
#air #hockey #airhockey #air-hockey #velocity #puck #frame-rate #frame #rate #performance #bug #fix
Hi everyone!
I wanted to share an important update about a performance bug in Velocity Puck that some players reported:
after restarting the game, the frame rate would suddenly shoot way higher than normal, making the gameplay feel unusually fast or inconsistent.
This turned out to be caused by something sneaky happening behind the scenes:
multiple animation loops were running at the same time.
Let me explain what was going on — in simple terms — and how I fixed it.
Velocity Puck uses a function called animate() that updates the 3D scene, moves objects, checks collisions, etc.
To run this function 60 times per second, the game used this setup:
animationInterval = setInterval(animate, 1000 / 60);
However… every time you restarted the game, a new interval was created.
The issue was:
So after a few restarts, instead of having one animate loop, the game might be running 3, 5, or even 10 loops at once.
That caused the game to speed up like crazy.
Inside your original index.html, the animation system worked like this:
let animationInterval = null;
function startGame() {
gameStarted = true;
animationInterval = setInterval(animate, 1000 / 60);
}
function endGame() {
gameStarted = false;
clearInterval(animationInterval);
}
At first glance, this looks fine.
But the restart flow looked like:
Player finishes a match โ endGame()
Game Over screen โ restartGame()
startGame() is called again โ new animation loop created
But sometimes the old interval was not cleared in time or at all
The result:
Two animate loops running at the same time… then three… then four…
Each one stacking more draw calls onto the browser.
To solve the issue, I implemented three key improvements:
Before starting the game, we now force-stop any existing interval:
function startGameLoop() {
if (animationInterval !== null) {
clearInterval(animationInterval);
animationInterval = null;
}
animationInterval = setInterval(animate, 1000 / 60);
}
This guarantees only one active loop at all times.
Three.js stores objects in GPU memory.
If an old scene stays in memory, it can slow things down.
Now, before starting a new game:
function cleanupScene(scene, renderer) {
scene.traverse(obj => {
if (obj.geometry) obj.geometry.dispose();
if (obj.material) {
if (obj.material.map) obj.material.map.dispose();
obj.material.dispose();
}
});
renderer.dispose();
}
This makes sure nothing carries over to the next run.
if (!gameStarted) return; from animate() and rely on proper loop controlThis line was causing confusion:
if (!gameStarted) return;
It stopped the logic, but not the interval itself.
Now the loop stops correctly by clearing the interval.
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.
Restarting is now:
Smooth
Memory-safe
Predictable
FPS-stable
And the Jungle Map clipping fix from the previous patch now looks great combined with this performance update.
Hereโs the improved, robust version now used in Velocity Puck:
let animationInterval = null;
function startGame() {
stopGameLoop(); // Make sure no loop exists
initializeScene(); // Load models, lighting, etc.
startGameLoop();
}
function startGameLoop() {
stopGameLoop();
animationInterval = setInterval(animate, 1000 / 60);
}
function stopGameLoop() {
if (animationInterval) {
clearInterval(animationInterval);
animationInterval = null;
}
}
function restartGame() {
cleanupScene(scene, renderer);
startGame();
}
This bug was only discovered because players reported unusual behavior — so thank you!
Your reports help make Velocity Puck better with each update.
More improvements are coming, and new maps + characters are currently in development ๐โก