Inspiration

Most tower defense games make you read a stat sheet. You compare damage numbers, pick the better tower, and the map is just a track the enemies run along.

I wanted the opposite: a map where where you build matters more than what you build. So I built the game around a single geometric rule — two lanes spaced so that towers on the outer edges can never reach the far lane, no matter how much you upgrade them, while a small number of squares between the lanes can cover both. Plentiful cheap coverage, or scarce dual coverage. That one constraint generates the whole strategy.

What it does

Artificial Towers is a portrait-orientation tower defense prototype. Enemies march down two lanes that merge into a single exit. You place automatic towers on fixed squares, upgrade or sell them between waves, and try to survive ten waves on twenty lives.

Three towers with distinct roles: Gunner (cheap, fast), Cannon (slow, heavy, longest reach), and Frost (barely damages anything, but slows enemies so everything else gets extra shots).

The map splits into three tiers of build square:

Tier Count Reaches the far lane?
Outer 18 Never, at any upgrade level
Inner 10 Gunner and Cannon immediately, Frost after one upgrade
Exit corridor 4 Everything

Runs are scored on waves cleared, lives kept and cash unspent, and the top three are saved on your device.

How we built it

Entirely by prompting. Sessions 1–4 used DeepSeek Chat to get the core loop standing up; sessions 5–8 used Claude to redesign the map, tune it, and package it. Every session is recorded in the build log.

The most useful thing that came out of the AI workflow wasn't game code. When I asked for the "outer towers can't reach the far lane" rule, the numbers turned out to be tightly coupled — lane separation, node offset, three tower ranges and the +15%-per-level upgrade step all constrain each other, and I couldn't eyeball it. So Claude wrote a solver that searched the parameter space and reported the safety margin for every configuration, and an audit harness that runs the real node-generation code and asserts three invariants:

  1. No build square touches the path, at any segment angle.
  2. No outer square can reach the opposite lane, even fully upgraded.
  3. Every square fits the narrowest supported phone screen.

That harness stayed in the repo and caught real bugs repeatedly.

All sound effects are synthesised at runtime with the Web Audio API — sixteen distinct sounds built from enveloped oscillators and a single reused noise buffer. Zero bytes in the zip, no asset pipeline. Only the background music is a file.

Challenges we ran into

The audit failed the first time I ran it. Two build squares on the converging diagonals could be reached across the lanes by a maxed Cannon, because those squares drift inward and downward at the same time. Widening the lanes would have pushed the inner squares out of Cannon's base range and collapsed the interesting tier, so I made the diagonals a no-build zone instead. It reads better too — a clear gap marks where the lanes become one.

A bug that was invisible on desktop. The old code culled build squares to the live viewport. On a 390×844 phone that silently deleted every outer-lane square, so the left and right lanes only had buildable ground on their inner side. It had been in the build for three sessions and looked completely fine in a desktop browser. The fix was to fit the camera to a fixed design area so the layout is identical on every device.

The prototype would have shown a judge a black screen. Over file:// a browser treats the page as an opaque origin, and <script type="module"> is subject to CORS while a classic <script src> is not — so every import fails. Unzip and double-click would have produced nothing. I wrote a packaging script that emits a build with no ES modules anywhere: game code inlined into index.html as a classic script, Three.js bundled into vendor/ as a global.

Three.js r185 nearly broke that fix. I assumed I could copy three.module.js into vendor/. On r185 that ships a broken build — the file is only 0.62 MB because it re-exports from a separate 1.38 MB three.core.js. Caught it by testing against the real release instead of trusting the assumption.

Accomplishments that we're proud of

Full game in one day

What we learned

Make numeric design rules machine-checkable. The interesting rule in this game is a relationship between six constants, and every time I changed one by hand I broke it. Once there was a solver and an audit, tuning became safe.

And test the path your player actually takes. "It works on my machine" and "it works the way a judge opens it" turned out to be different claims, and the gap between them was total failure, not a rough edge.

What's next for Artificial Towers

More maps whose lane spacing changes which squares are dual-coverage, deeper upgrade branches, enemy abilities, and progression that carries between runs.

Built With

Share this project:

Updates

Submission history