← All updates

23 July 2026

500 Rockets, 10 FPS, and the Hunt for the Missing Frames

Here's the dream: a screen so packed with rockets and explosions that you can barely find your own ship — running butter-smooth. Here's the reality we started the week with: fire a full rack of rocket pods and the framerate fell out of the sky.

Roughly 500 rockets on screen at ~100 FPS. A few days earlier, this exact scene crawled along in the teens.

So what happened in between? A hunt for the missing frames — and a couple of surprises worth writing down.

The dream, and the wall

Neon Armada is a reverse bullet-hell. The whole point is excess — a screen so full of ammo and fire that surviving it feels glorious. But when we actually stacked twelve rocket pods and let them rip, the game dropped to around 10 FPS at ~500 rockets. A slideshow. Not shippable, and definitely not the power fantasy we're here to sell.

Rule one: measure, don't guess

The tempting explanation was "too many rockets, obviously." But guessing is exactly how you burn a day optimising the wrong thing. So we put a stopwatch inside the frame and split it in two: the physics (all the game logic — where every rocket flies, what it hits) and the render (actually drawing it).

Physics was fine, around 10 milliseconds. The render was the wall. Good — now we knew which half of the frame to fight.

Layer 1: stop drawing rockets one at a time

Every rocket was its own little 3D object, handed to the graphics card individually. The fix is a technique called a MultiMesh: instead of "draw this rocket, now draw this rocket, now draw this rocket" five hundred times, you say "draw this shape, here are all five hundred positions" — once.

Here's surprise number one. We expected the draw-call count to collapse. It barely moved — the engine was already clever enough to batch identical objects together. The real cost wasn't the drawing; it was the bookkeeping: hundreds of separate objects, each one checked against the camera, re-positioned, and synced to the GPU every single frame. The MultiMesh throws all of that overhead away. Render time for 500 rockets dropped from 101 ms to 35 ms.

Better — but still only about 20 FPS. Something else was quietly eating the frame.

Layer 1b: the explosions were lying to us

This is where measuring paid for itself. We killed the explosions' lights — just the lights, keeping every fireball on screen — and the framerate jumped from 19 to 91. There it was.

Every explosion was spawning a dynamic light. With ten detonations a frame and each fireball glowing for over a second, that stacks up to roughly 750 live lights at once. And in a modern renderer, every dynamic light is expensive — for each pixel on screen the engine has to work out which lights touch it. Seven hundred and fifty of them brought a very capable GPU to its knees.

The fix is almost embarrassingly simple: put a cap on it. Only the first 32 explosions cast a real light; the rest render as pure fire. And because a light is handed back the instant its hot flash fades, a fresh blast grabs it immediately — so the flicker stays lively even mid-firehose. In a screen full of explosions, you genuinely cannot tell that only 32 of them are lit. Seven hundred and fifty lights, down to thirty-two.

The payoff

For ~500 rockets on screen:

  • Where we started: ~10–19 FPS
  • After Layer 1 (rocket bodies batched): ~20 FPS
  • After Layer 1b (explosion light cap): ~100 FPS

Render time went from 42 ms a frame to about 10. GPU load dropped by roughly 80%. And the part that matters most for the long game: the render cost no longer scales with how many rockets you fire. Throw a thousand at the screen and the picture holds.

Two things we're taking with us

  • Measure, don't guess. Our first instinct ("it's the rockets") was half right and half a red herring. The stopwatch pointed straight at the lights — and saved us a day chasing the wrong thing.
  • Dynamic lights are the hidden tax. A transparent fireball sprite is nearly free. A light attached to it is not. If an effect spawns a light per instance, cap it — the tenth one is doing real work you'll never see.

What's next

The rockets-and-explosions side is solved. For truly endless runs — the kind where a good pilot pushes deep and the swarm just keeps coming — the next wall is the enemies themselves: hundreds of ships each thinking for themselves every frame. The answer there is AI level-of-detail, letting far-away ships think a little less often. But that's a story for another post.

Fly safe. 🚀

Enjoying the ride? Add Neon Armada to your wishlist.

+ Wishlist on Steam
← All updates