By Rob, hero developer at Three Crown Interactive · 2 October 2026
In part 1 I showed how we took the worst hitch with 300 enemies from 340 ms down to 24 ms. We push the game this hard because we want a real horde on screen without stutter, and the benchmark is where the problems show up first. Numbers like that are only worth something if the test behind them measures the right thing. This morning ours didn't. This post is about how we rebuilt the test, and what it told us.
In short
- The test now starts where real waves are: 200 and 300 enemies instead of 10 to 100.
- The heroes in the test now fire their ultimates, and a Sword Soldier and a Maul Soldier play their full kit.
- A second test runs on a real level, Ravensklif, with 313 to 388 enemies alive.
- A release build runs about 2.5 times faster than the Unity Editor, so we judge with release builds.
- IL2CPP made the same code 17 to 25% faster, at the cost of much longer builds.
- In co-op, 300 enemies cost about 6.4 KB per network update: about 64 KB per second per player.
What was wrong with the old test
The old benchmark grew from 10 to 100 enemies, with a stress phase of 200. That made sense a few weeks ago. Now our endless waves grow to 300 enemies, so most of the test measured situations that no longer matter.
Worse, the heroes in the test never cast their ultimate. An ultimate costs 100 mana and the test heroes had 65, so the test quietly fell back to the next spell. The heaviest moments in the game, like the Phoenix, the Arctic Storm and Gaia, never happened. And there were only mages. The Soldier, who fights in the middle of the crowd with a sword or a maul, wasn't in it at all.
Three phases that match real waves
The new test has three phases of 30 seconds each:
- Stress: 200 enemies, 30 towers and three mages.
- Horde: 300 enemies, 30 towers and three mages.
- Heroes: 300 enemies, 30 towers and one of each: an Elementalist, a Sword Soldier and a Maul Soldier.
The mages now get their mana topped up just before each round, so the ultimates go off. For the Soldiers we wrote a small "driver" that plays their kit the way a player would: hitting the timing windows of Strike and Breaker Swing, holding Blade Rush over a few enemies, marking three targets for Skyfall, and aiming and confirming World Ender. Without those presses, the heaviest parts of their kits would never run.
Heroes phase, release build. The numbers top left are the test's own live counter.
A real level: Ravensklif
A flat test field is easy to repeat, but it isn't the game. So there's now a second test on Ravensklif, the level my teammate Tim is building: the real map at wave 20, where the horde gathers until 300 enemies are alive. Because the wave keeps spawning while we measure, there are 313 to 388 enemies on the field. They can't die and the keep is repaired every frame, so the load stays at its peak.
It runs four times, 30 seconds each: once from the builder's overview at the main gate, and once each from behind a Sword Soldier, a Maul Soldier and an Elementalist fighting just in front of that gate.
Screenshots from a later run of the same test.
Measured in a release build: 94 FPS in the overview, 120 behind the Sword Soldier, 116 behind the Maul Soldier and 112 behind the Elementalist. In none of the four views did a single frame take longer than 33 ms.
The test tells you what limits the game
After each run the game shows a report: frame times per phase, how busy the processor and graphics card were, and what holds the frame rate back.
A run in the Unity Editor, so the Editor's own work shows up as the largest cost.
The answer is the same every time: the game's main thread. In release builds it's busy 93 to 98% of every frame, while the game uses only 2 to 4 of the PC's 16 logical processors on average and the graphics card is idle for more than half of every frame. That's the big lever for later: spreading work over more cores.
Why we never judge in the Editor
During development you play inside the Unity Editor. It's convenient, but slow, because the Editor does a lot of its own work every frame.
The same code runs 2.4 to 2.5 times faster as a release build. So every number in these posts comes from release builds.
One more thing we learned the hard way: only compare runs made back to back. With a second Unity Editor and a few other programs open, this PC lost up to 10% FPS. So we build both versions first and then run them in turn: A, B, A, B.
IL2CPP: more speed, slower builds
Unity can run C# code in two ways. Mono, the default, compiles the code while the game runs. IL2CPP turns it into C++ ahead of time and compiles that into the game. We measured both with exactly the same code, each build run twice, in turn.
- IL2CPP Release: 17 to 22% more FPS.
- IL2CPP Master (more aggressive C++ optimisation): 19 to 25% more.
- 1% low with 200 enemies: 61–74 FPS with Mono, 94–97 with IL2CPP Release and 99–104 with Master.
- The price: building the test takes about 40 seconds with Mono, almost 4 minutes with IL2CPP Release and almost 16 minutes with Master.
We haven't switched yet. It's a team decision: everyone needs the C++ build tools installed, and longer builds slow down testing. For a release version it looks like an easy win.
Co-op: does everyone see the same thing?
The game is co-op: one host and up to three other players. The host simulates the fight and sends the result to everyone. So after all these changes we checked co-op again, with test scripts that start a host and two clients on one PC:
- Every player sees the same thing: the Ice mage's storm, the frozen enemies and the ice bursts, the Fire and Nature effects, and the Sword and Maul Soldiers with their weapons and moves.
- A player's hits, statuses and stuns are calculated by the host and show up on everyone's screen.
- With 300 enemies, one world update is about 6.4 KB, sent ten times per second: about 64 KB per second per player. Tim's network work for big hordes keeps that small. A player's own input is about 224 bytes.
- The host needed 4.5 ms per frame on average, a client 2.2 ms.
The check also caught two of our own test scripts out of date after balance changes: the Maul's World Ender now reaches 2.6 metres and one script stood just too far away, and the Arctic Storm now costs 100 mana, which the test hero didn't have. Both scripts are fixed. The game itself was fine.
What's next
- Spreading work over more CPU cores. That's the biggest remaining gain, and it touches more than the heroes, so it's a job for the whole team.
- Measuring on other PCs, including a typical mid-range PC. Everything here comes from one machine.
- A decision on IL2CPP.
- Co-op over the internet with four players. So far we tested on one PC over the local network.
How we measured
- PC: AMD Ryzen 7 7700, AMD Radeon RX 7800 XT, 32 GB RAM, Windows 11.
- Settings: 1920 × 1080 window, the game's current graphics preset, VSync and FPS limits off during the test.
- Builds: Unity 6 release builds with Mono unless IL2CPP is named; Editor numbers are marked as such.
- Tests: the built-in battle benchmark (fixed random seed, 4 seconds of warm-up left out, 30 seconds per phase) and the Ravensklif benchmark (wave 20, four views of 30 seconds). Comparisons only between builds run back to back.
- Co-op: a host and two clients on one PC over the local network. Not yet tested: the internet relay, four players under load, and two separate PCs.
- Limits: one PC; test battles, not real matches with players.
About me
I've been gaming for almost 35 years, and in daily life I work in coding and automation at enterprise level. Making a game with my friends is a dream I've had for a long time. Game development itself is a new world for me, but between game-dev communities and AI I can learn what I need, like the performance work in these two posts.
With the help of AI, I can now actually build my ideas. I work with an AI coding assistant (Claude) that writes and tests a large part of the code and runs measurements like these. That leaves me more time for concepts, concept art, trying ideas and tuning. The decisions are mine, and every number in this post comes from a measured run.
Previously: 300 enemies, part 1: hunting the stutter. On the heroes: Heroes, part 4: is it balanced?