How It's Made · Step 16 of 27
Balancing 50 levels with bots that play them
tower-defensebalancebotstesting

The wave generator from last week wrote 1,910 waves. They were a curve, not a game: no human had played most of them. We needed to know which levels were impossible, which were trivial and which were a good evening. We did not want to play fifty levels fifty times to find out, so we built players.
Three bots
The TdBot is a test harness that plays a Tower Defense level headless through exactly the same commands a human issues: place a tower, buy a unit, upgrade, sell. It is forbidden from cheating, so no free credits, no kills and no damage. It has three profiles:
- BALANCED plays like a sensible player.
- POOR plays worse on purpose.
- GREEDY is the third spending style, there to see how much a lopsided strategy changes the result.
The idea is a bracket. A level that BALANCED cannot win is probably too hard for a normal player. A level that POOR wins is probably too easy. The result is a log line per wave and a final line per run: won, with how much of the base is left, or lost on which wave.
On 16 October the first read was grim: on the first ten generated levels, BALANCED won 4, POOR won 1 and GREEDY won none.
The tuning layer
Editing 50 levels by hand would break the generator's curve. So we added a tuning layer, scripts/td_tune.py, which sits on top of the generator's output and applies per-level numbers from one table. It is data-only, re-runnable, and can reset a level to its base. The knobs:
k: how much the last wave is scaled (split between unit count and hit points, so the total value is exact);mandwref: pull the biggest waves down so the last ordinary wave is worth a chosen multiple of an early one;air,sea,boss: multipliers for groups on air lanes, sea lanes and boss groups;yardandgap: more base hit points or longer gaps between waves for levels that need them.
Then a loop: run the bots on a level, read where it died, move one or two knobs, run again. We wrote a small script, td_why.py, to look at why a level was lost.
The effect on a typical level, from the log. Level 7 before: BALANCED lost at wave 13 of 30. After tuning (the last wave scaled to about a fifth): won with 95 per cent of the base left. Level 4, before: lost on the last wave of 25. After, with a slightly smaller final wave: won on all three seeds, with 52 to 82 per cent of the base left.

By 21 October (revision 41) BALANCED could hold 36 of the 50 levels. Seven more levels were turned in that single pass. Fourteen were still lost, and their death waves are written in one table so the next pass starts where the last stopped. The next day nine sea levels were turned.
What a bot cannot tell you
- Fun. Three skill levels measure difficulty, not whether a level is interesting. The owner plays the levels and writes notes, and that is the verdict.
- Things it cannot do. The bot builds no ships and one kind of coastal battery. So on levels with sea lanes it loses to a hull at anchor that nothing in its toolbox can hit. We added a way to measure with the sea groups dropped, to see what the land and air part can hold. It is a measurement and never a shipped setting.
- Rules runs are not balance runs. A scripted run with a very large starting purse once reached the final wave of level 10 and looked great. It was a test of the rules, not of the balance, and we marked it so.
- Its own gaps. Some early losses were the bot's, not the level's: it needed anti-air and pier handling added before its results meant anything.
What we would do differently
- Build the bots before the generator. We had a curve for two days before we had a ruler.
- Give the bot the player's real interface limits from the start: build speed, mouse reach, attention.
- Keep the seeds. Three runs per level with fixed seeds are comparable; one lucky run is not.
Try it yourself
If your game has a rule set, write a player that follows it and give it three personalities. You will find the impossible level and the trivial one in an afternoon, and you will have saved your own time for the part only you can judge.
Next: Seventy achievements, a fake Steam and the friend build (22 October 2026)
Written afterwards from our project notes and commit history; dated to when the work happened.


