TinyGames · how it works
A tower defence. Creeps walk a fixed road; you build alongside it and stop them. Three towers, three levels each, twenty lives.
Open index.html. No build step, no dependencies, no images — the map is drawn.
palisade/
index.html markup
style.css the frame around the canvas
js/engine.js copied unchanged from Overdrive - the eighth game on it
js/td.js the road, the tower table and the arithmetic
- no pixels, no engine, no DOM
js/palisade.js the playable layer
test-td.js node test-td.js
Not the towers. Creeps have to walk it at a genuinely constant speed, and the obvious way to write that is wrong:
t += speed * dt; // t runs 0..1 along THIS segment
pos = lerp(waypoints[i], waypoints[i + 1], t);
That advances the same fraction of every segment per second, so a creep crosses a two-cell corner at the same rate as a nine-cell straight — visibly sprinting down the short legs and crawling along the long ones. Every tower's damage-per-creep then depends on which leg it happens to overlook, and the game becomes unbalanceable for a reason nobody watching it can name.
So the road is measured once and addressed by distance, not by segment. The test walks the whole path in equal 3-pixel steps and measures how far the point actually moved each time:
$ node test-td.js
71 passed, 0 failed
Every step covers equal ground, corners included. A companion test builds the naive per-segment version on the same road and confirms it varies by more than 8× between its shortest and longest step — the bug is real, not hypothetical, and the test says so out loud.
The clearest measurement in this project. Two bots, the same money, the same towers, the same upgrade policy — differing only in where they put things:
| waves held | |
|---|---|
| builds on the squares nearest the road (which clusters them all in one corner) | 4 |
| spreads the same towers evenly along the road | 21 |
Doing nothing at all reaches wave 3. So a clustered defence is worth almost exactly one wave more than no defence; a spread one is worth eighteen. That is the whole game, and it fell straight out of a bot that was originally written badly.
It is also the reason buildability is measured against the road itself rather than a list of "path cells" — the clearance is then the same on a diagonal as on a straight, and the buildable area looks deliberate instead of blocky. 75% of the map takes a tower.
| Bolt | 60 | Fast single shots. Cheap, and the backbone of any line. |
| Mortar | 105 | Slow, heavy, hits everything near where it lands. |
| Frost | 80 | Barely scratches anything. Makes everything else land twice. |
Each has three levels. The tests hold the table to properties rather than to numbers: damage and range never fall on an upgrade, total spend only climbs, a tower never sells for what it cost, and — the one that actually matters — a level 3 tower must buy more damage per second than it costs, or upgrading is a trap dressed up as a choice.
Frost's slow deepens with each level but is asserted to stay below 1, because a tower that can stop a creep dead is a tower that ends the game.
Each tower is a different shape as well as a different colour, and that was verified by reading the rendered pixels back off the canvas.
You start with 190 — three Bolts, and deliberately not four. Wave 1 pays 42, which is less than a Bolt costs, so the opening is spent from the stake rather than from income and the first placements are the decision the rest of the map is built around.
From wave 2 on, every wave is asserted to fund at least one more tower, and twenty waves of income is asserted to fund a real defence. Sending a wave early pays 3 per second of the break you skip, which is the only way to get ahead of the curve.
A tower shoots the creep furthest along the road that is in range — not the nearest. Furthest-along concentrates fire on whatever is closest to escaping, so a creep that gets past a tower is not then saved by a fresh one wandering into range behind it. That is checked against an independent search over 2,000 random arrangements.
A shot chases the creep it was fired at. If that creep dies on the way, the shot carries on to where it last was and still splashes — so a mortar shell aimed at something a Bolt happens to finish first is not simply deleted in mid-air.
| Tap a tower button, then tap the map | build |
| Tap a tower | select it, then Upgrade or Sell |
| 1 2 3 | pick a tower to build |
| U | upgrade the selected tower |
| Space | send the next wave early |
| Esc | deselect |
| R | new game |
While a tower is selected or a build is pending, its range is drawn — and every square is shaded green or red so nobody has to guess where a tower will go.
Twenty-five towers and twenty-eight creeps: 0.005 ms a tick, 0.22 ms a frame to draw.
ROUTE is ten pairs of cell coordinates at the top of palisade.js; the
rules do not care how long or how twisted it is.Advertisement