Skip to content
Subterrans

Sanding down Phase 3

Phase 3 shipped a game loop. The ten days since were spent making it correct, fast, and survivable — a meaner and better-behaved spider, fixed invasion pathing, real tooling, save robustness, and a starvation bug that needed finding.

Phase 3 shipped ten days ago. The game has a beginning, a middle, and an end now. That post was the exciting one — seven mechanics, a round with shape, the first time the AI felt intentionally hostile.

This is the other kind of post. The ten days since were not about adding anything. They were about making what shipped actually hold up: pathing that is correct instead of approximately correct, a spider that behaves the same at the map edge as it does in the middle, hot paths that do not allocate, tests that run in CI instead of rotting, and one balance bug that was quietly starving colonies. None of it is visible as a feature. All of it is the difference between “it works in the rounds I played” and “it works.”

Here is what landed.

The spider got meaner, and better-behaved

The spider was the headline mechanic of Phase 3, and it was the part most likely to embarrass me under observation. Most of this stretch went into it.

It now has a chase reflex and the fighters around it have sight-based auto-aggression (PR #172): the spider commits to a target instead of dithering, and fighters who can see it engage on their own. It got a health bar above the sprite (#175) so that “is this fight worth it” is a question you can answer by looking, instead of guessing.

Then the edge cases. The spider’s feed-retreat used to walk it off the map; now it reflects inward at the edges (#176), and it keeps a margin from the boundary so its sprite never slides half-off-screen (#191, sim version V26). Its gate-hold behavior used to be able to pin the queen herself; that is now excluded, and there is a start grace period so the spider does not ambush you in the opening seconds before you have any fighters (#178). And the rampage notification, which used to fire once and then stay quiet through later rampages, now fires on every rampage — and lost the leftover “tunnels” copy, since the spider never goes underground (#202).

That last one is a small thing that says something true about building under observation: the copy described behavior the spider does not have, and it sat there until it was read by someone who would notice.

The invasion finally paths correctly

Phase 3’s combat made tunnel geometry matter. The problem was that the invaders did not always respect the geometry they were supposed to be fighting through. A bent underground tunnel could leave an invader stuck against a wall, because the descent logic assumed a straighter path than the dig tools let you build.

Invaders now route through bent underground tunnels via BFS (#171), descent is gated on entrance blockers so you cannot descend into a hole that is not actually open (#169), and foreign-grid recall now keys on the rally point rather than the fight ratio (#189, V25) — which is to say, fighters come home based on where you told them to go, not on a number that happened to correlate with it most of the time. The flow-field caches that drive all of this got scoped per-world (#168) so that two simulations running in the same process cannot poison each other’s pathing — which matters for both the AI and the test suite.

The starvation bug

The one genuine balance bug in this stretch: colonies were starving for a reason that had nothing to do with the player’s decisions. Larva maturation was set to 2400 ticks, slow enough that the food-to-workers pipeline could not refill the workforce fast enough to keep the food coming in. The colony would tip into a spiral that looked like a strategy failure but was actually a constant.

Dropping maturation to 1500 ticks (#180) fixed it. The reproduction loop described in the last post — surplus feeds eggs, eggs demand nurses, nurses cost foragers — only reads as a real trade-off if the loop can actually close. At 2400 it could not. This is exactly what the playtraces are for: the loss-cause ledger kept reporting starvation in rounds where starvation should not have happened, and that pattern is what pointed at the constant.

Alongside it, the nursery deposit became capacity-aware (#183, V24) so brood is not deposited into a nursery that is already full, which was the other half of why the pipeline stalled.

Performance, where it actually matters

Combat and the per-tick update are the hot paths — they run for every ant, every tick, in both simulations at once. They were allocating objects inside those loops (#170), which is the kind of thing that is invisible until the colony gets large and then is the whole problem. Removing the per-tick allocations from the combat and tick hot paths is the unglamorous performance work that keeps late-game rounds smooth.

Tooling, tests, and save robustness

A lot of this stretch was paying down the kind of debt that does not show up in a screenshot.

The codebase adopted Prettier and normalized formatting repo-wide (#184), then added a type-aware ESLint layer (#185) that catches a class of bugs the basic linter cannot see. A verify + coverage workflow went into CI (#187), and after some honest accounting, coverage got recorded as intentionally local-only rather than pretending it was a CI gate it was not (#199).

The end-to-end suite had rotted during the v3.0 mechanics work. It got restored and gated in CI (#195), then the flaky speed-cycle and pheromone tests got de-flaked and un-quarantined (#200) so they are real signal again. The save-flow tests were reworked around real captured saves instead of synthetic fixtures (#197), and a genuine save bug got fixed along the way: future-build saves now survive a fresh boot instead of being silently discarded (#198).

The F9 debug snapshot got a self-describing guide block (#182) so a captured snapshot explains its own fields — useful when a player sends one in and I am reading it cold. And the architecture docs got a refresh for the post-Phase-3 state (#201), because the map should match the territory after a phase that changed this much.

A note from the desk

People ask what it is actually like to build a game this way — AI-driven development, one person, a stack of pull requests a day. The honest answer is that most of it is what you just read: small, correct, unglamorous changes, one after another, each one closing a gap between “works in the rounds I played” and “works.” Claude does a lot of that closing. I describe the gap, we argue about the fix, it writes most of the code, and I keep the taste and the no’s. It is the most productive I have ever been, and the part of the work that is most genuinely mine is the judgment — which is the part I wanted to be doing anyway.

It also helps that, this week, the desk looks like this.

A bay in Hawaii seen from a wooden lanai railing, with a papaya tree, palm fronds, and turquoise water curving around a sandy point under a bright, cloud-dappled sky.

I’m in Hawaii for the week — not where I normally work, but the laptop came along, and a stack of PRs doesn’t care what’s on the other side of the railing. A bay, a papaya tree leaning into frame, the water doing the thing the water does. It doesn’t make the starvation bug any easier to find. But it’s a genuinely good place to go looking for it.

What’s next

This was a hardening pass, not a phase. The next real question is still the one the last post ended on: is the round fun in the hands of someone who did not build it? The systems are correct enough now to trust the playtraces. Play it, lose a round, and the sim will tell both of us how.