← Devlog

I built a bot to play my game 140 times. It found a wall I couldn't see.

Four of my seven worlds were impossible to lose. I threw every balance knob I had at them and nothing moved — because the variable that mattered wasn't damage, it was damage-per-cost. Here's the mechanism, and the one cut that fixed six worlds.

June 23, 2026 devlogbalancesimulation

Overserved has 140 levels and it looked finished. The problem with a difficulty curve is that you can’t feel one across seven worlds and twenty levels each by playing them. You’d go insane, and you’d lie to yourself about how hard each level “felt” anyway.

So I built a bot to play them for me, and it told me something I’d never have noticed by hand.

A bot that never gets bored

The bot drives the game’s own update loop in a tight synchronous loop — no rendering, no waiting for real time — with a player that builds roughly optimally. At the end it reads one number: lives kept. 100% is a flawless walkover (boring). 0% means the bot lost (impossible). The “you just barely made it” band sits around 40–75%. One scalar per level, 140 levels, a few seconds. That’s the whole point of a headless sim: it turns a feel question into a number you can sweep.

The first sweep was humiliating. China: all twenty levels at 100%. Europe: walkovers through level 13. Africa, same. And every boss came in near 100% — titans I’d lovingly designed, melting in about two seconds. Four of my seven worlds were impossible to lose. I’d shipped a game you couldn’t fail.

Throwing the whole toolbox at it

China was the worst offender, so I attacked it — and the bot just sat at 100% and stared at me.

More enemies? 265 units on one level, still 100%. Tankier enemies didn’t get tense; they cliffed straight to 0%, because in this game a tower hit doesn’t deal damage so much as satisfy a customer, and a fully satisfied one converts into money. Tanky enemies never convert, so the economy starves and collapses. Six-times-normal speed: 100%. Less starting money: the cheap-tower snowball just refilled. Range cut in half: 100%. I packed 135 scenery props onto a 252-tile board to physically deny build spots — still left 75 valid spots near the path. Still 100%.

Every knob I had, dead.

The question I should’ve asked on day one

I stopped asking “how do I make this harder” and asked “why is a single shot enough to win.” That reframe cracked it.

The answer wasn’t damage. It was damage per unit of cost. The cheapest tower, the Chatbot, did about 3.2 satisfaction per coin spent; the premium towers, 0.3 to 0.5. Four to ten times more efficient. Raw damage tells you nothing here — a player spends a fixed budget, so the tower that buys the most kill per coin is the one a rational player spams, and that’s the only number that shapes a board. Build a wall of Chatbots and every enemy dies on contact. My worlds weren’t walls of coverage. They were walls of overkill.

That’s why every knob was inert. When damage dwarfs enemy health, every other lever gets absorbed into the surplus before it can do anything. Double the enemies — they still die on contact. Speed them up — they reach a tower one tick sooner and die there instead. The fight is decided at the first tile, so nothing downstream — placement, coverage, economy — ever gets a turn. I’d spent hours turning dials wired to nothing.

One cut that fixed six worlds

The obvious fix — nerf all damage — is the wrong one. It would gut the worlds that legitimately need the expensive towers while barely denting the cheap-tower worlds. So the cut was surgical: drop the cheap towers’ damage, leave the two genuine premiums untouched. Chatbot came down hard, a couple of others trimmed. Overkill fell toward roughly 1–2× health, and suddenly units survived long enough that where you build mattered again. Six of my seven worlds went from flat walls to real difficulty curves in one change.

China still resisted, because its national quirk makes every cheap tower an overkill engine — nerf one and the surplus just shifts to the next. Damage was never China’s lever; scarcity was. The fix was a mechanic that censors low-quality answers and forces players onto the expensive, compute-hungry towers, played against China’s deliberately starved compute budget. Choke the input instead of the output, and it finally got a curve.

The leftover walkovers went to a second tool: it adds one varied enemy at a time, re-runs the sim, and repeats until the bot drops into the target band — then emits the result as a designed, escalating wave. Run in reverse, it eases the brutal spikes. Twenty-two walkovers became real, mixed waves.

The bosses got a combat model instead of a hit-points number. They’d been single units that every tower focus-fired with uncapped damage — instant death, so their sabotage abilities never fired. I gave them a barrier: a cap on how much satisfaction they absorb per second, a leaky bucket that deflects the excess and recharges. Now a boss takes the whole crossing to bring down, and the bot can verify the new rule — a fresh save loses, an upgraded one wins.

What it actually taught me

Overkill is the master variable. When tower damage dwarfs enemy health, every other knob is dead weight — density, economy, geometry, speed all get swallowed by the surplus. Pull damage roughly in line with health first; only then do the rest of your levers come back to life. Everything I tried before that was noise.

The real dial is damage-per-cost, not raw damage. A player spends a fixed budget, so cost-efficiency decides what gets built. The tower that’s five times more efficient is the one warping your economy. Cut that one, not everything.

A sim that builds its own test waves must build them exactly like the shipping game. Mine interleaved unit types; the real game grouped them. Same units, but the spacing alone swung difficulty by about 20% — so I was tuning waves easier than anything a player would ever see. A generated test is only as honest as its fidelity to production.

The bot lies about bosses. For an optimal player a boss with a barrier is binary — the board absorbs anything short of impossible, so “tense” can’t be measured as a number. The sim can verify the rules (upgrade-gating, sustained pacing); the felt tension is a human-playtest problem, not a math one. Know which questions your metric can’t answer.

An optimal autoplayer is a brutal, honest reviewer. It found walls a human tester would’ve shrugged past as “eh, I won.” Just respect its blind spots — it doesn’t dodge abilities, it doesn’t make mistakes — and read its zeros and its hundreds, not its middle.

The bot didn’t make my game harder. It told me the truth: I’d been polishing a game nobody could lose.