Balancing a tower defense game is a combinatorial problem. Nine warden types, each with upgrade tiers. Dozens of enemy compositions across 51 campaign configurations. A 29-talent progression tree that shifts player power at every stage. A gold economy that has to feel tight on the first playthrough and generous on the tenth. Testing all of that by hand means playing the same level over and over, tweaking one number, and playing it again.
So we built a system that does it in seconds.
Defense of Kyrath now has an automated headless test harness — it plays the game without a screen, at 200x real-time speed, and reports exactly what happened. Warden kills, gold flow, enemy leaks, placement timing, everything. We can sweep an entire difficulty curve before lunch.
Three Layers
The harness has three parts, each handling a different concern.
The headless engine. Godot’s --headless flag swaps in a dummy display server — all rendering becomes a no-op. Combined with --audio-driver Dummy and --disable-render-loop, the engine runs pure game logic: wardens fire, enemies walk, gold accumulates, waves spawn. The same code that runs during real gameplay, just invisible. We crank --fixed-fps to 600 and set a 20x time multiplier. The result: a 4-minute tutorial level completes in about 4 seconds.
An AI placement bot. This is the part that makes the simulations realistic. The bot analyzes path geometry to find kill zones — positions where bends and curves maximize firing time per enemy. It walks every path in 40-pixel steps, generates candidate positions on both sides, deduplicates them on a 20-pixel grid, and scores each one by measuring how much total path length falls within warden range. On multi-path levels, the score is the sum of coverage across all paths — positions near intersections where multiple paths converge naturally score highest, because they see enemies from two or three lanes at once. The bot clusters the top-scoring positions into kill zones spaced apart by 2.5× tower range, fills the best zone first with 2–3 wardens, then moves to the next. It defers upgrades until at least 3 wardens are placed (or the tower cap is hit), then upgrades the highest-level warden it can afford. It plays like a competent human — not optimal, but strategic.
A Python orchestrator. Generates test configurations, launches parallel Godot simulations, collects results, and produces comparison reports. The config structure separates two concerns: player_profile (what the simulated player owns — wardens, talents, tokens) and level_overrides (environment modifications — starting gold, allowed wardens, edicts, stat multipliers). This lets us sweep one axis while holding the other constant. We’ve built a canonical set of 15 tiered loadouts (T0 through T5) that represent realistic player progression stages — from “Sylvan only, no talents” to “full roster, all 29 talents.” Each tier has multiple variants: broad arsenal, deep specialist, economy-focused, fortitude-heavy. Running a level against the full loadout matrix tells you exactly where it sits in the difficulty curve.
Speed
| Scenario | Normal Play | Headless |
|---|---|---|
| One tutorial level | 4 minutes | ~4 seconds |
| 5-warden comparison sweep | 20 minutes | ~20 seconds |
| Economy sweep (10 gold values × 3 seeds) | 2 hours | ~2 minutes |
| Full regression suite (50 levels × 3 configs) | Days | ~25 minutes |
A complete balance analysis of a single level — warden count sweep, economy validation, difficulty curve check — takes under 60 seconds. We’ve now run 920 simulations across 50+ levels, including multi-path maps with up to 4 lanes. The cumulative numbers tell the story: 57 hours of simulated gameplay, compressed into 4 hours of wall-clock compute time. That’s 57 hours a human would spend playing the same levels over and over, tweaking one number, playing again — done in an afternoon.
Multi-Path and Intersections
The original algorithm worked on a single path. That was fine for the tutorial, but Defense of Kyrath has maps with 2, 3, and even 4 enemy paths. The placement bot needed to handle all of them.
The fix was elegant: instead of scoring each candidate against one path, score it against all paths and sum the coverage. A position near a bend on path A might score 300. A position near a bend on path B might score 280. But a position near the crossing where both paths converge scores 300 + 280 = 580. The algorithm naturally gravitates toward intersections without explicitly detecting them — it just follows the math.
This matters because intersections are exactly where a human player would build. One warden covering two lanes is worth more than two wardens covering one lane each. The bot’s intersection-first behavior means its placement decisions stay realistic even on complex maps.
The candidate deduplication is important here too. Walking 4 paths generates 4× the candidates, many of them at nearly identical positions. Snapping to a 20-pixel grid collapses overlapping candidates and keeps the scoring pass fast.
What Falls Out of the Data
Every simulation produces a results JSON with victory/defeat status, per-warden kill breakdowns, economy flow, and a full placement log with wave numbers and gold balances at each decision point.
This turns vague design questions into concrete answers:
- “Is this level winnable with only 2 Sylvan wardens?” — Yes. 97/99 enemies killed, 2 leaked, 8 lives remaining.
- “How many wardens does a new player need?” — One fails (66% killed). Two wins comfortably. Three is flawless.
- “When can the player afford their second warden?” — Wave 5, after earning 23 gold from kills.
- “Which warden in a group does the most work?” — Warden 1 at the bend: 47 kills. Warden 2 beside it: 18. Warden 3 nearby: 26.
- “On a 4-path map, does the bot spread too thin or focus correctly?” — It clusters at intersections where paths converge. But it still builds 6–9 wardens where a human would build 15–20. The gap is the signal.
That last one is particularly useful on multi-path levels. The bot’s intersection-scoring algorithm concentrates firepower at crossings, which is correct — but it can’t adapt mid-level the way a human does. A skilled player will raze underperforming wardens and relocate them as pressure shifts between lanes. That human-bot gap is a deliberate calibration tool: if the bot wins comfortably, the level is too easy for a human.
First Tuning Session: Haven Meadow
We pointed the harness at Haven Meadow, the tutorial level, and ran 11 simulations in about 60 seconds.
What the data showed:
The level was too short — 2 minutes against a 4–5 minute target. It was too easy with 2 wardens. But the core stat ratios felt right: a Grunt dies in 5 shots (2.0 seconds), which is satisfying without being instant. The economy pacing was healthy — the second warden becomes affordable at Wave 5, the third at Wave 8.
What we changed:
- Inter-wave delays doubled (3–7s to 10–15s) — this alone added 100 seconds
- Added 3 more waves (12 to 15) for a gentler ramp
- Total enemies: 67 to 99. Total HP: 665 to 1005
- Duration: ~2 minutes to ~4 minutes
What we confirmed should NOT change:
- Sylvan damage (3.0), fire rate (2.5/s), cost (20g)
- Grunt HP (15), Spriggan HP (5)
- Starting gold (30) — affords exactly 1 warden with 10g buffer
- Gold rewards (Grunt 2g, Spriggan 1g)
Those core stat ratios became the confirmed Tier 0 baseline. Every higher difficulty tier scales from there.
The distinction matters. Balance tuning isn’t just about finding what’s wrong — it’s about confirming what’s right with enough data to stop second-guessing it.
Six Lines of Code
The game-side changes were minimal: 6 early-return guards in the main game script. Skip tooltip updates, input handling, wave banners, and path glow effects when running headless. One new signal: simulation_complete. No game logic was modified. Wardens target, fire, and deal damage identically to normal play.
This is the part that makes the results trustworthy. The harness doesn’t approximate the game — it is the game, minus the pixels.
What’s Happened Since
What started as a proof-of-concept on one tutorial level has scaled to 920 simulations across the full campaign — 57 hours of simulated gameplay, compressed into 4 hours of compute. The placement algorithm now handles multi-path levels natively — scoring positions by summed coverage across all paths so that intersections emerge as the highest-value zones. We’ve built a tiered loadout matrix (15 configurations from “first warden only” to “full roster + all talents”) and run every level against multiple tiers to map the difficulty curve with data.
The biggest insight has been calibrating the gap between bot and human performance. On a 4-path Tier 5 level, the bot lost every run at max power — 0 wins out of 4, best kill rate 58%. A human player won the same level handily. The bot can’t distribute wardens across lanes adaptively, can’t raze and relocate underperformers, and can’t choose types based on upcoming wave composition. That gap is the calibration tool: we tune levels so the bot barely wins (or narrowly loses) at the intended tier, knowing a human will outperform it significantly.
What’s Next
The next step for the bot itself is an adaptive strategy layer — razing underperforming wardens mid-level and relocating them where pressure is highest. The game already supports razing with a 50% refund, so the mechanism is there. Teaching the bot to evaluate performance-per-warden and make reallocation decisions would close the gap between bot and human, giving us a tighter calibration baseline.
Beyond that: full campaign progression sweeps — simulating a player’s entire journey from tutorial through endgame, checking that the difficulty curve and economy hold across hours of play, not just minutes. And Folly validation across all 20 tiers, where the expected outcome is that only the best builds survive past tier 15.
The data-driven architecture we wrote about previously is what makes all of this possible. When every stat lives in a registry and every level is a JSON config, a headless simulation can modify anything without touching code. The test harness is a natural consequence of treating game design as data.