← All updates

4 August 2026

Five Hundred Ships That Think

At the end of the rockets post we made a promise: "the next wall is the enemies themselves — hundreds of ships each thinking for themselves every frame. But that's a story for another post."

This is that post. When we started, the game held up to about a hundred enemies. My own save — the one that kicked this whole thing off — chugged along at 15–20 FPS when the swarm got serious. Two days of hunting later, that same save runs at 80–100, with five hundred ships on the field. Here's where the frames were hiding.

What does an enemy actually cost?

A rocket is cheap: it flies straight and explodes. An enemy is a different animal. Every single ship, sixty times a second, has to think (where am I going? do I shoot? do I dodge?), not crash into its four hundred and ninety-nine friends, check whether any of your bullets hit it, and then actually get drawn. Multiply each of those by five hundred and you find out very quickly which ones were never built for a crowd this size.

Same rule as last time: measure, don't guess. We put a stopwatch inside the frame and split the swarm's cost into pieces. Then we went down the list, biggest first.

Round 1: the crowd, in native code

The single nastiest job in a swarm is personal space. Every ship pushes away from every nearby ship — and done naively, "who is near me?" means comparing everyone against everyone: 500 × 500 = a quarter of a million checks, per frame. We already sorted ships into a grid so each one only looks at its own neighbourhood, but at this scale even that arithmetic became too heavy for the game's scripting language. So the crowd math — separation, collision lookups, and the routine "move everyone forward" bookkeeping — moved into fast native code. Same formulas, same behaviour, several times faster. The swarm still looks identical; a player watching the before and after confirmed it: "definitely still looks like a swarm."

Round 2: brains on a schedule

Here's a secret about game AI: nothing needs to decide sixty times a second. A ship picking "chase the player, weave a little" and then re-picking it 16 milliseconds later is wasted work. So decisions now happen thirty times a second, staggered so half the swarm decides on even frames and half on odd — while movement stays perfectly smooth at full rate, because the native code keeps flying the ship between decisions. You cannot see the difference. The frame can.

And inside each decision, we went hunting for waste. The AI was re-reading its own stat sheet — top speed, weave style, aggression — from a lookup table every single decision, fifteen thousand times a second across the swarm. Now each ship memorises its numbers once when it spawns. Same brain, same choices, a third less thinking time.

Round 3: the death spiral you've definitely played through

This one explains why games don't slow down gracefully — they fall off a cliff. The engine simulates the world in fixed steps to keep game-time honest. If drawing a frame takes too long, the engine runs extra simulation steps to catch up. But those extra steps make the next frame take even longer… which schedules more catch-up… That's the death spiral: the game isn't 10% over budget, it's drowning in its own lateness. It's why you so often see either "buttery" or "slideshow" and nothing in between.

The flip side: every millisecond you claw back near that cliff edge pays out double, because you're not just doing less work — you're cancelling catch-up steps. Several of our fixes looked modest on paper and then doubled the framerate in practice. That's the cliff releasing its grip.

Round 4: the GPU joins the party

Once the simulation got fast, the graphics card became the next suspect — especially with beam-heavy builds. Three fixes:

  • Beam trails, batched. Every beam shot leaves a glowing after-image. Each one was its own object with its own draw — hundreds of them mid-fight. They now all live in a single batched draw, the same trick that saved the rockets. The glow gradient survived the migration pixel-for-pixel.
  • Explosion size, gently capped. Stack area-of-effect upgrades and blasts get huge — and a twice-as-wide explosion costs four times the pixels. Past a threshold the visuals now grow gently while the damage radius stays fully honest (the shockwave ring still marks the true kill zone).
  • Dynamic resolution. The sneaky one. When the screen turns into pure light-show chaos, the game quietly renders slightly fewer pixels and scales up for a moment, then returns to full sharpness when things calm down. In a bullet storm you genuinely cannot tell — and it protects weaker GPUs automatically.

The scoreboard

  • Where it started: ~100 enemies comfortable; 500 = 15–20 FPS.
  • Where it landed: the same save, the same fight: 80–100 FPS with 500 ships.
  • The stress bench went further still — the swarm now scales linearly all the way past two thousand ships before it truly gives out.

Explain it like I'm fifteen

The whole campaign, in four ideas:

  • The classroom. Five hundred students each shouting a question at the teacher every second — that's a naive swarm. Put them in rows so they only talk to their neighbours (the grid), let them ask half as often (staggered decisions), and give the teacher an assistant who handles the routine paperwork at native speed. Nobody learns less. The room just stops being on fire.
  • The recipe. The old AI re-read the whole cookbook for every pancake. Now it memorises the recipe when it puts on the apron. Same pancakes.
  • The traffic jam. One slow car doesn't slow a motorway by one car's worth — it breeds a jam that feeds itself. Game engines do the same with missed frames (the catch-up spiral). Clearing the jam gives you back far more than the one car you removed.
  • The painter. A screen full of transparent glows means painting the same pixels over and over, layer on layer. The GPU fixes are all one idea: paint fewer layers, paint smaller layers, and when nobody can tell the difference anyway — paint a slightly smaller canvas.

Things that surprised us

  • The collision system was innocent. We assumed ten-way piercing bullets bouncing off walls would drown the hit detection. Measured: the entire collision chain is ~15% of the frame. What piercing bullets actually cost is success — more kills per second means more explosions, more loot, more respawns. You don't pay for the bullets; you pay for the victory.
  • Laptops lie. When a game runs terribly, the CPU is pinned and hot, and it clocks itself down — making everything measure worse. Fix something real and the chip gets breathing room, boosts up, and suddenly every measurement looks miraculous. Twice we nearly credited a fix with a miracle that was really the silicon exhaling.
  • Playtesting finds what benchmarks can't. "Why do they all spawn in the top-left corner?" turned out to be a two-layer bug in our own stress bench that no automated run had ever noticed — because the numbers didn't care where the ships came from. Eyes on the game still beat graphs.

What's next

There's one worst-case left on the board: diving straight into the spawn mouth of a 500-ship swarm, where everything is on screen and packed wing-to-wing, still dips hard. The cure for that one is known and big — turning the far swarm into pure batched geometry, no objects at all. Whether we need it depends on how deep the endless runs go. If we do, you'll read about it here.

Fly safe. 🚀

Enjoying the ride? Add Neon Armada to your wishlist.

+ Wishlist on Steam
← All updates