The week the ants got better
Visible brood carry, diagonal motion, real pixel-art terrain — plus a structured codebase review that found twenty latent bugs you'll never see fire.
Five days since the last post. Nineteen merged PRs in the game repo, somewhere north of fifty individual commits. A few of the changes are immediately visible the next time you load the demo. Most of them aren’t, and that’s a story too.
Things you can see
Nurses now carry brood. Eggs and larvae used to teleport from the Queen’s chamber into the Nursery — the simulation faked transport and the renderer faked the rest. As of this week, a nurse picks up a brood entity, walks it visibly across the colony, and deposits it in an Open tile inside a Nursery. It’s a real pickup-and-deposit state machine: pickup field seeded from claimable brood, deposit field seeded from Nursery tiles, the brood’s position syncs to the carrier each tick, and the renderer nudges the brood sprite a quarter-tile up so it sits in the carrier’s arms instead of stacked on her thorax. Dead carriers drop their brood in place; a maturing larva drops the carry mid-flight; another nurse can claim an orphaned brood from the death tile. All of that sounds obvious until you write it; six rounds of internal + Codex review caught about ten edge cases I’d missed on the first pass.
Ants move diagonally now. Movement was 4-connected (N/E/S/W only) since the first prototype. This week the simulation switched to 8-connected with a scurry-stop-scurry cadence: forager workers take a few steps, pause to “search,” then resume. The change is subtle in a screenshot and dramatic in motion — the colony reads as alive in a way it didn’t before. Fighters keep their direct line, because fighters chasing a target shouldn’t look hesitant.
The dirt is dirt. Until last week, the underground was solid brown. It now uses a procedurally-generated pixel-art terrain atlas, with quarter-tile autotiling on the walls so chamber edges, tunnel turns, and chamber corners all join correctly. The surface got ant-scale motifs — boulders, twigs, leaves the size of a worker — that block movement and give the world a sense of scale. After all that, I went back and fixed the rim-clip artifacts at the chamfered corners. The art will still get redrawn entirely in a later phase; the point of this pass was to make programmer-art look intentional rather than pre-final.
The AI got harder to push around
Two changes worth calling out:
The enemy AI was concentrating its colony into a tight pocket near the entrance — easy to invade, predictable to fight. New depth gate + spread bias + frontier-dig logic means the enemy now expands their colony over a wider area, with chambers placed reachable-but-not-adjacent and digging continued past initial chamber satisfaction. They feel like a colony making decisions instead of a script unrolling.
The AI’s chamber-placement BFS was running every tick — a ~4096-tile search per AI colony, sixty times a second. Strategic chamber decisions don’t need 60 Hz responsiveness; they’re bounded by how fast diggers can reach the marked tiles. Cadence is now once every eight seconds. That’s a 99% cost reduction on the BFS for no observable downside, and it matches how the rest of the simulation budgets work — fast loops on the cheap stuff, slow loops on the strategic stuff.
Things you can’t see
Now the bigger story: I did a structured review pass over the entire codebase and shipped roughly twenty fixes for things nobody had reported yet.
A few of the worse ones:
- Entity ID overflow.
allocateEntityIdwould let the counter walk pastMAX_ENTITIES = 8192and the next caller would write to TypedArray index 8192 — silent corruption. Fixed: counter freezes at the cap, callers receive a-1sentinel and gracefully bail. Egg-laying becomes a soft population cap instead of a crash. - NaN command coordinates. Every existing range check rejected
Infinity(Infinity >= maxis true) but notNaN(NaN >= maxis false). ANaNcoord slipped through every guard, persisted into colony state, and broke save replay determinism viaJSON.stringify(NaN) === 'null'. New sharedisTileCoordpredicate enforces integer-in-range at handler entry. - Save validation against tampering.
s.ants.count: 1e9used to allocate ~88 GB of TypedArrays before any sanity check. Now clamped.s.simVersionoutside the supported range used to silently flip every gate. Now throws. - Forward-compatible saves. A new
FutureSimVersionErrordistinguishes “save written by a newer build” from “save corrupted.” Future-build saves are now preserved acrossbootFresh+ autosave (the autosave gets suspended explicitly), so a player who downgrades the build can recover their world by reloading on a newer build. Definitively-corrupt saves get deleted normally. - Camera-pan freeze on HUD crossing. Drag-pan would jump every time the cursor crossed a HUD widget mid-drag. Combined with a related fix where keyboard pan no longer stacks on top of an active drag-pan, the camera input is finally consistent across every gesture I can think of.
- Pheromone leakage between zones. Underground carriers were writing phantom trails on the surface food-trail grid, corrupting surface forager behavior. Behind a sim-version gate so existing saves replay byte-identically.
The pattern across all of these is the same: each one was a real bug hiding behind an acceptable-looking range check or an unexamined invariant. None of them produced obvious symptoms in normal play. All of them would have produced very confusing symptoms eventually.
Why the simVersion gates matter
Every correctness fix in the simulation is gated behind a sim-version sentinel. New games run at the latest version. Saves written before the fix replay at their original version, byte-for-byte identical to what they would have produced before the fix existed. There are now twelve sim versions, each with a documented contract.
The cost is ugly conditionals at hot paths. The benefit is that a player’s seed-3201734851 mid-game save still replays today exactly as it would have in week one — and that I can fix sim correctness without breaking a single existing player’s save. That property is non-negotiable for a deterministic simulation that wants a multiplayer future.
Going forward
The rest of the week was infrastructure: command-coordinate validation, save-boundary hardening, code hygiene, defensive guards. None of it ships a feature. All of it makes the next feature safer to ship.
Next up: another pass at the player-facing UI, especially the underground view’s information density, and probably the start of a tutorial layer. Plus more art, because the procedural terrain atlas opened up the path to actually-good-looking tiles and I want to follow it.
If you find something, issue tracker. The bug-report template walks you through the F9 debug log, which gives me a reproducible replay instead of guesswork.
More soon.