<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Subterrans devlog</title><description>Progress, decisions, and AI-agent notes from the making of Subterrans.</description><link>https://subterrans.com/</link><language>en-us</language><item><title>The ants learned to be afraid</title><link>https://subterrans.com/devlog/the-ants-learned-to-be-afraid/</link><guid isPermaLink="true">https://subterrans.com/devlog/the-ants-learned-to-be-afraid/</guid><description>A month of work in one post: seven sim versions of new ant behavior — fleeing, risk-aware foraging, battlefield scavenging — plus a full architecture review and the thirty-odd PRs it caused, touch groundwork, and a licensing decision that lets anyone ship Subterrans to an app store.</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;/devlog/getting-out-of-the-players-way&quot;&gt;The last post&lt;/a&gt; was about the controls — making the game answer to the player. That shipped in late June. The month since has gone in two very different directions at once.&lt;/p&gt;
&lt;p&gt;One is the interesting one: &lt;strong&gt;the ants got a relationship with danger.&lt;/strong&gt; They can be frightened now. They run from things, they route around things, and — as of this week — they eat the things that die. That is four sim versions of genuinely new behavior, and it changes how a round feels.&lt;/p&gt;
&lt;p&gt;The other is the unglamorous one: I had the codebase reviewed against where it is actually going, and that review turned into about thirty pull requests of structural work. Seven sim versions landed in total — V31 through V37 — along with the groundwork for touch, a real performance pass, and a licensing decision I want to explain properly.&lt;/p&gt;
&lt;h2&gt;The ants learned about danger&lt;/h2&gt;
&lt;p&gt;Before this month, an ant’s relationship with a threat was binary and late: it was fine, and then it was in a fight. There was one exception — the spider’s reticle made nearby workers scatter — and that was it. Danger existed as a pheromone on the grid, but almost nothing read it.&lt;/p&gt;
&lt;p&gt;Four changes fixed that, in order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fleeing and the idle reserve (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/285&quot;&gt;#285&lt;/a&gt;, V34).&lt;/strong&gt; This started as a bug report that the colony “looked hung”: when food storage filled up, surplus workers stopped having anything to do and froze in a motionless clump. They now &lt;strong&gt;mill around the entrance&lt;/strong&gt; in a deterministic wander so the clump breaks up, and — the real addition — when the danger pheromone above them spikes, they &lt;strong&gt;dash for shelter down the nearest open entrance&lt;/strong&gt;, hold at the shaft, and poke their heads back out once the danger overhead decays. There’s a nice detail in the safety logic: an ant only flees home &lt;em&gt;if home is safe&lt;/em&gt;, so a panicking worker never sprints toward a spider parked on its own doorstep. The same PR added a &lt;strong&gt;cross-colony alarm&lt;/strong&gt; — when an enemy kills one of your surface workers, that death seeds a danger mark on &lt;em&gt;your&lt;/em&gt; pheromone grid. Your colony finds out that somewhere is dangerous by losing someone there, which is exactly how it should work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Risk-aware foraging (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/291&quot;&gt;#291&lt;/a&gt;, V36).&lt;/strong&gt; Fleeing is a last-second reaction. This is the anticipatory version: a forager choosing its next step now weighs the &lt;strong&gt;food trail against the danger trail&lt;/strong&gt; at each candidate cell. A spider sitting on a supply line visibly bends traffic around itself instead of just generating a stream of panicked flees. Crucially it is a &lt;strong&gt;soft&lt;/strong&gt; bias rather than a wall — a strong enough food trail will still pull ants through mild danger. That trade-off is the whole point. Ants that refuse all risk starve.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Battlefield scavenging (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/292&quot;&gt;#292&lt;/a&gt;, V37).&lt;/strong&gt; Merged this week, and my favorite of the four because of what it does to the other three. Surface combat deaths now &lt;strong&gt;drop forageable food&lt;/strong&gt; where they fall. An enemy killing one of your ants leaves a pickup; killing the &lt;strong&gt;spider&lt;/strong&gt; leaves a hundred — a genuine windfall for finally bringing it down. Deaths underground, brood deaths, starvation, and anything the spider eats drop nothing.&lt;/p&gt;
&lt;p&gt;Put those together and you get an actual dilemma. A battle leaves food on the ground. The battlefield is also, thanks to V34, the most danger-marked patch of the map. So the pile your foragers most want is sitting exactly where V36 tells them not to go, and the value of going anyway scales with how big the fight was. The greed-versus-fear crossover is now a real thing you can watch happen, and it emerged out of three separately-designed mechanics rather than being scripted.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Underground idle wander (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/286&quot;&gt;#286&lt;/a&gt;, V35)&lt;/strong&gt; rounds it out — the same de-clumping for surplus workers below ground. That one has a story of its own.&lt;/p&gt;
&lt;h2&gt;The 44% regression nobody could see&lt;/h2&gt;
&lt;p&gt;The underground wander should have been trivial: it is the surface milling from V34, applied to the tunnels. The first implementation did exactly that, and it passed everything. Every unit test green. Determinism intact. Replays byte-identical. Code review clean.&lt;/p&gt;
&lt;p&gt;It also &lt;strong&gt;cut the AI colony’s economy by about 44%&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The cause is a rule that only bites underground: same-colony ants are one per tile. Idle workers wandering through tunnels were bumping productive foragers and diggers off their routes — thousands of tiny displacements a round, each individually invisible and collectively catastrophic. On one seed the AI colony went from 23 workers to 1.&lt;/p&gt;
&lt;p&gt;Nothing in the test suite could see it, because nothing was &lt;em&gt;wrong&lt;/em&gt;. The sim was correct, deterministic, and reproducible. It was just much worse. What caught it was a &lt;strong&gt;paired seed sweep&lt;/strong&gt; — run eight seeds on the old version and the new one, twelve thousand ticks each, and compare the colonies that come out the other end. Five experiments narrowed it down, and the fix was to &lt;strong&gt;confine the wander to chamber footprints&lt;/strong&gt;, which happen to be exempt from the one-ant-per-tile rule and are also exactly where the frozen clumps were in the first place. Post-fix the same sweep came back at a 1.05 worker ratio — slightly better than before, no colonies dead.&lt;/p&gt;
&lt;p&gt;I think this is the most useful thing I have learned in months of building this way. I have a lot of gates: type checking, lint, thousands of unit tests, determinism proofs, byte-identical replay, two external AI reviewers on every PR. Every single one of them passed a change that would have quietly ruined the game. &lt;strong&gt;Correctness gates cannot see badness.&lt;/strong&gt; The only thing that caught it was running the actual game a lot of times and looking at the outcome — and that is now a standing part of the process for anything that touches ant movement.&lt;/p&gt;
&lt;h2&gt;An architecture review, and the month it caused&lt;/h2&gt;
&lt;p&gt;At the start of July I stopped shipping features and had the whole codebase reviewed — not for bugs, but against where it is going next: bigger maps, mobile, and networked play. That produced about twenty tracked issues, shipped over three waves.&lt;/p&gt;
&lt;p&gt;The review found &lt;strong&gt;real bugs&lt;/strong&gt;, which is the part I did not expect. The spider was &lt;strong&gt;ignoring surface passability&lt;/strong&gt; and walking through terrain it should not have (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/248&quot;&gt;#248&lt;/a&gt;, V31). The AI’s operations were &lt;strong&gt;never validated&lt;/strong&gt;, so a malformed one could be issued and quietly do something incoherent (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/249&quot;&gt;#249&lt;/a&gt;, V32). The occupancy resolver &lt;strong&gt;left shifted ants slightly off tile center&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/251&quot;&gt;#251&lt;/a&gt;, V33). And at 2× and 4× speed, ants were &lt;strong&gt;snapping between positions&lt;/strong&gt; instead of interpolating, because the smoothing divided by a constant tick period rather than the live one (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/246&quot;&gt;#246&lt;/a&gt;) — a thing I had been looking at for weeks without registering as a bug.&lt;/p&gt;
&lt;p&gt;The rest was structural. Simulation scratch buffers that lived as &lt;strong&gt;process globals&lt;/strong&gt; became &lt;strong&gt;owned by the world&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/255&quot;&gt;#255&lt;/a&gt;), so two simulations in one process can no longer contaminate each other. The command pipeline got a &lt;strong&gt;single chokepoint&lt;/strong&gt; with provenance and overflow visibility (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/259&quot;&gt;#259&lt;/a&gt;). Three batches went into &lt;strong&gt;reaping dead compatibility gates&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/260&quot;&gt;#260&lt;/a&gt;, &lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/261&quot;&gt;#261&lt;/a&gt;, &lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/262&quot;&gt;#262&lt;/a&gt;) — every old sim version that no accepted save could still reach was branches the code no longer needed to carry. Saving got tougher too: an &lt;strong&gt;async storage seam&lt;/strong&gt;, a &lt;strong&gt;caption when an autosave fails&lt;/strong&gt; instead of failing silently, and &lt;strong&gt;validation of loaded data&lt;/strong&gt; rather than trusting it (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/264&quot;&gt;#264&lt;/a&gt;, &lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/266&quot;&gt;#266&lt;/a&gt;, &lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/267&quot;&gt;#267&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;And a proper performance pass, mostly on things that ran every tick without needing to: the per-tick brood-field rebuilds are now &lt;strong&gt;gated on whether the brood actually changed&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/257&quot;&gt;#257&lt;/a&gt;), the pheromone overlay is &lt;strong&gt;LOD-gated and cached&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/276&quot;&gt;#276&lt;/a&gt;), detailed-mode drawing &lt;strong&gt;reuses scratch buffers&lt;/strong&gt; instead of churning allocations (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/277&quot;&gt;#277&lt;/a&gt;), and the static minimap layer is &lt;strong&gt;baked once into a texture&lt;/strong&gt; rather than redrawn every frame (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/280&quot;&gt;#280&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;Two engines, one hash&lt;/h2&gt;
&lt;p&gt;The single result I am happiest with is small and hard to screenshot. Subterrans has always been deterministic — same inputs, same round — but that was only ever proven &lt;em&gt;within one engine&lt;/em&gt;. &lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/253&quot;&gt;#253&lt;/a&gt; runs an identical three-thousand-tick scenario in Node and in headless Chromium and requires the two worlds to &lt;strong&gt;hash to the same bytes&lt;/strong&gt;. &lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/283&quot;&gt;#283&lt;/a&gt; added WebKit as a third.&lt;/p&gt;
&lt;p&gt;That matters because everything interesting on the roadmap depends on it. Mobile means running the sim inside a WebView. Networked play means two machines stepping the same simulation and trusting they stay in agreement. Both assume the engine underneath does not change the answer. That was an assumption; now it is a test.&lt;/p&gt;
&lt;h2&gt;Getting ready for fingers&lt;/h2&gt;
&lt;p&gt;The controls post said touch was a later phase. Five PRs of groundwork landed anyway (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/271&quot;&gt;#271&lt;/a&gt;–&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/275&quot;&gt;#275&lt;/a&gt;): a &lt;strong&gt;two-pointer state machine&lt;/strong&gt; so a second finger is visible at all, &lt;strong&gt;pinch zoom&lt;/strong&gt; over the camera built last month, a &lt;strong&gt;drag threshold that scales with zoom&lt;/strong&gt;, &lt;strong&gt;long-press to open the chamber menu&lt;/strong&gt;, and a &lt;strong&gt;press-and-hold hover preview&lt;/strong&gt; — because touch has no hover, and the green/red placement cues from the controls rework are load-bearing enough that they needed a touch equivalent.&lt;/p&gt;
&lt;p&gt;None of it is switched on as a shipped mobile mode. But the input layer no longer assumes a mouse, which was the actual blocker.&lt;/p&gt;
&lt;h2&gt;Anyone can ship it to an app store now&lt;/h2&gt;
&lt;p&gt;One non-code decision worth explaining. Subterrans is AGPL-3.0, which is a strong copyleft license and stays that way. The problem is that AGPL’s “no further restrictions” clause &lt;strong&gt;conflicts with app store terms&lt;/strong&gt; — DRM, device limits, mandatory ToS — which is the old VLC-on-the-App-Store problem.&lt;/p&gt;
&lt;p&gt;The usual fix is a contributor license agreement: consolidate the copyright, then ship your own official binary under different terms. That works, and it means only the copyright holder can do it. I did not want that. So Subterrans now carries an &lt;strong&gt;AGPL section 7 additional permission granted to everyone&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/287&quot;&gt;#287&lt;/a&gt;) — anyone can build and distribute it through an app store, as long as the full source stays available under the AGPL. &lt;strong&gt;No CLA, no signing gate&lt;/strong&gt;, and because contributions come in under the same terms they go out under, external contributors get it automatically.&lt;/p&gt;
&lt;p&gt;The game stays copyleft. Nobody needs my permission to put it on a phone. That is the right shape for this project.&lt;/p&gt;
&lt;h2&gt;What’s next&lt;/h2&gt;
&lt;p&gt;The honest state of things: the simulation is in better structural condition than it has ever been, the ants are meaningfully smarter than they were a month ago, and there is now a real dilemma on the surface that I did not design directly — it fell out of three mechanics meeting.&lt;/p&gt;
&lt;p&gt;The next open question is a big one, and it is about map size. Several of the remaining architecture items are blocked on deciding how large a world Subterrans should support, because that decision changes how terrain is stored and streamed. Everything else is queued behind it.&lt;/p&gt;
&lt;p&gt;In the meantime, the corpse-food economy needs a tuning sweep — the same kind of paired seed run that caught the 44% regression, this time asking whether a defender reclaiming its own dead makes losing a fight too cheap. As ever: &lt;a href=&quot;/demo/play&quot;&gt;play it&lt;/a&gt;, lose a round, and the playtrace tells us both what happened.&lt;/p&gt;</content:encoded></item><item><title>Getting out of the player&apos;s way</title><link>https://subterrans.com/devlog/getting-out-of-the-players-way/</link><guid isPermaLink="true">https://subterrans.com/devlog/getting-out-of-the-players-way/</guid><description>The controls got a four-stage rework: a tool palette instead of overloaded clicks, a camera that actually zooms, a game that shows you your own queued commands, and controls that teach themselves — all of it render-only, with the simulation untouched.</description><pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;/devlog/sanding-down-phase-3&quot;&gt;The last post&lt;/a&gt; was about making Phase 3 correct. This one is about making it &lt;em&gt;playable&lt;/em&gt; — which turns out to be a different problem.&lt;/p&gt;
&lt;p&gt;The simulation was solid. The way you talked to it was not. The old control scheme had grown one mechanic at a time, and it showed: the left and right mouse buttons each did three or four different things depending on which view you were in and what was under the cursor. Panning was hold-&lt;code&gt;Space&lt;/code&gt;-and-drag. Game speed lived on the &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, and &lt;code&gt;4&lt;/code&gt; keys. Zoom did not really exist. None of it was written down anywhere in the game, and most of it you could only discover by accident. It worked fine for the one person who built it and was close to unlearnable for everyone else.&lt;/p&gt;
&lt;p&gt;So the controls got a rework — four stages of it, &lt;a href=&quot;https://github.com/LightAxe/subterrans/issues/18&quot;&gt;issue #18&lt;/a&gt;, shipped over about a week. The whole point was to get the interface out from between the player and the game.&lt;/p&gt;
&lt;h2&gt;Stage 1: a tool palette and one honest gesture&lt;/h2&gt;
&lt;p&gt;The root problem was overloading. A single click meant “give an order” or “pan the camera” or “dig a tile” or “open a chamber menu” depending on context, and the player had no way to know which without trying it.&lt;/p&gt;
&lt;p&gt;Stage 1 (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/210&quot;&gt;#210&lt;/a&gt;) split that apart into &lt;strong&gt;three explicit tools — Command, Dig, Chamber — on keys &lt;code&gt;1&lt;/code&gt;/&lt;code&gt;2&lt;/code&gt;/&lt;code&gt;3&lt;/code&gt;&lt;/strong&gt;, each with its own cursor and highlight, each resetting to a sensible default when you switch views (Command on the surface, Dig underground). Now the cursor tells you what a click will do &lt;em&gt;before&lt;/em&gt; you click.&lt;/p&gt;
&lt;p&gt;Underneath the tools is &lt;strong&gt;a single left-button gesture arbiter&lt;/strong&gt; with one rule that holds everywhere: &lt;strong&gt;tap to issue a context order, drag to pan&lt;/strong&gt; — the one exception being the Dig tool underground, where drag paints tiles. One code path decides tap-versus-drag, and one &lt;code&gt;cancelGesture()&lt;/code&gt; cleans it up on every tool switch, view switch, menu open, and phase change, so a half-finished drag can never leak into the next thing you do. Hold-&lt;code&gt;Space&lt;/code&gt;-to-pan is gone; &lt;strong&gt;&lt;code&gt;Space&lt;/code&gt; is now just pause/resume&lt;/strong&gt;, and game speed moved off the number keys onto a proper HUD widget (with &lt;code&gt;=&lt;/code&gt;/&lt;code&gt;-&lt;/code&gt; for the keyboard).&lt;/p&gt;
&lt;p&gt;The Command tool keeps the parts that were actually intuitive — tap a food pile to forage it, tap your nest to rally, tap an enemy to attack — but now they live behind a tool you chose on purpose, instead of being one of four things a click might have meant.&lt;/p&gt;
&lt;h2&gt;Stage 2: a camera that actually zooms&lt;/h2&gt;
&lt;p&gt;Stage 2 (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/215&quot;&gt;#215&lt;/a&gt;) added the thing every strategy game is expected to have and Subterrans did not: &lt;strong&gt;continuous, Google-Maps-style zoom and smooth pan&lt;/strong&gt;, in both the surface and underground views, with the zoom anchored to the cursor so the map grows toward whatever you are pointing at.&lt;/p&gt;
&lt;p&gt;Making that smooth meant changing how terrain is drawn. Re-rendering every tile every frame at every zoom level is wasteful, so terrain is now &lt;strong&gt;baked into a render texture once&lt;/strong&gt; and only the tiles that actually change — a freshly dug tunnel, a new entrance — get re-stamped, tracked by a dirty set. The camera just zooms and pans over the baked image. When you zoom all the way out to see the whole nest, individual ant sprites would be sub-pixel noise, so they collapse into a &lt;strong&gt;batched dot layer&lt;/strong&gt; with a little hysteresis so the swap does not flicker at the threshold.&lt;/p&gt;
&lt;p&gt;The felt result is that you can now read the whole battlefield at a glance and then dive into a single tunnel fight, without the camera fighting you on the way. The strategic view and the tactical view are the same view at different zooms.&lt;/p&gt;
&lt;h2&gt;Stage 3a: the game shows you your own commands&lt;/h2&gt;
&lt;p&gt;This is the stage I am happiest with, because the idea is simple and the effect is large: &lt;strong&gt;the game should show you your own commands.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Two things came out of that (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/217&quot;&gt;#217&lt;/a&gt;). The first is &lt;strong&gt;feedforward validity&lt;/strong&gt;: when you are aiming a placement tool — a chamber, a tunnel, an entrance — the target tints &lt;strong&gt;green&lt;/strong&gt; if the command is valid, &lt;strong&gt;red&lt;/strong&gt; if it is geometrically impossible, and &lt;strong&gt;grey&lt;/strong&gt; if your command queue is full. You know whether the click will work before you commit to it. The second is the &lt;strong&gt;paused-queue preview&lt;/strong&gt;: when you pause, the orders you have queued but that have not been applied yet are drawn as ghosts, so you can see what hitting resume will actually produce.&lt;/p&gt;
&lt;p&gt;The trick that makes this trustworthy is that both the cue and the real command are computed from the &lt;em&gt;same&lt;/em&gt; projected world — a clone of the game state with your entire pending queue folded through the real command handlers. The preview is not a separate “what I think will happen” guess that can drift from the truth; it is the truth, run one step ahead. &lt;strong&gt;Green never lies&lt;/strong&gt;, because the same code that paints it green is the code that would execute the order.&lt;/p&gt;
&lt;p&gt;Doing this honestly required one carefully bounded change inside the simulation: the block of code that applies queued commands during a tick was lifted out into its own function so the renderer could call it on a throwaway copy of the world. The behavior is byte-for-byte identical — proven by a golden test that captured the exact output before the refactor and replayed it after — so determinism and replay are untouched. (More on why that mattered below.)&lt;/p&gt;
&lt;h2&gt;Stage 3b: controls that teach themselves&lt;/h2&gt;
&lt;p&gt;The last stage (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/218&quot;&gt;#218&lt;/a&gt;) closed the loop the intro opened: a new player should discover pan, zoom, view-switching, and the tool palette without a manual or a tutorial wall.&lt;/p&gt;
&lt;p&gt;That’s three reactive surfaces, all drawn through &lt;strong&gt;one&lt;/strong&gt; device-glyph resolver so the hints can never disagree with the real bindings. There’s a &lt;strong&gt;hint strip&lt;/strong&gt; that composes its text from the actual key glyphs rather than hardcoded labels; &lt;strong&gt;one-time, cross-session first-use nudges&lt;/strong&gt; that fire on the &lt;em&gt;effect&lt;/em&gt; — the first real pan, the first accepted zoom, the first dig-drag that actually enqueues marks — plus a gentle &lt;code&gt;[Tab]&lt;/code&gt; prompt to discover the view toggle on your first input of a session; and &lt;strong&gt;hover tooltips&lt;/strong&gt; on every control widget, including the ones whose purpose isn’t obvious (the stats panel tooltip teaches that you can click it for ant activity). All of the captions route through a single queue with an explicit policy about what preempts what, so two hints never stomp on each other and a nudge that gets dropped under load un-marks itself and fires again later.&lt;/p&gt;
&lt;p&gt;The nice part is that none of it nags. Each hint shows once, when the moment is right, and then never again.&lt;/p&gt;
&lt;h2&gt;The discipline: the simulation never moved&lt;/h2&gt;
&lt;p&gt;The throughline across all four stages — and the constraint I am proudest of holding — is that &lt;strong&gt;this was render-only&lt;/strong&gt;. A complete overhaul of how the game looks and feels, and &lt;code&gt;git diff main -- src/sim&lt;/code&gt; is empty for three of the four PRs. The fourth moved exactly one block of code without changing what it does, with a golden test to prove it.&lt;/p&gt;
&lt;p&gt;That matters more than it sounds. Subterrans is deterministic: the same inputs always produce the same round, which is what makes saves, replays, and the playtrace data from the demo trustworthy. If the controls rework had reached into the simulation, every one of those guarantees would have needed re-checking. Keeping the line between “how the game is presented” and “how the game is computed” clean meant a week of UI work cost the sim exactly nothing. The tool palette, the camera, the previews, the teaching layer — all of it is a new lens on the same untouched machine.&lt;/p&gt;
&lt;h2&gt;What’s next&lt;/h2&gt;
&lt;p&gt;The controls were the last thing standing between the demo and a stranger’s hands. The simulation is correct, the round has shape, and now the interface explains itself instead of needing me next to you to translate. So the question from the last two posts still stands, just with the excuses removed: is it fun for someone who did not build it? &lt;a href=&quot;/demo/play&quot;&gt;Play it&lt;/a&gt; — and now you should not need me to tell you how.&lt;/p&gt;</content:encoded></item><item><title>Sanding down Phase 3</title><link>https://subterrans.com/devlog/sanding-down-phase-3/</link><guid isPermaLink="true">https://subterrans.com/devlog/sanding-down-phase-3/</guid><description>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.</description><pubDate>Sat, 06 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;/devlog/phase-3-shipped&quot;&gt;Phase 3 shipped ten days ago&lt;/a&gt;. 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.&lt;/p&gt;
&lt;p&gt;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.”&lt;/p&gt;
&lt;p&gt;Here is what landed.&lt;/p&gt;
&lt;h2&gt;The spider got meaner, and better-behaved&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;It now has a &lt;strong&gt;chase reflex&lt;/strong&gt; and the fighters around it have &lt;strong&gt;sight-based auto-aggression&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/172&quot;&gt;PR #172&lt;/a&gt;): the spider commits to a target instead of dithering, and fighters who can see it engage on their own. It got a &lt;strong&gt;health bar above the sprite&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/175&quot;&gt;#175&lt;/a&gt;) so that “is this fight worth it” is a question you can answer by looking, instead of guessing.&lt;/p&gt;
&lt;p&gt;Then the edge cases. The spider’s feed-retreat used to walk it off the map; now it &lt;strong&gt;reflects inward at the edges&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/176&quot;&gt;#176&lt;/a&gt;), and it &lt;strong&gt;keeps a margin from the boundary&lt;/strong&gt; so its sprite never slides half-off-screen (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/191&quot;&gt;#191&lt;/a&gt;, sim version V26). Its gate-hold behavior used to be able to pin the queen herself; that is now excluded, and there is a &lt;strong&gt;start grace period&lt;/strong&gt; so the spider does not ambush you in the opening seconds before you have any fighters (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/178&quot;&gt;#178&lt;/a&gt;). And the rampage notification, which used to fire once and then stay quiet through later rampages, now &lt;strong&gt;fires on every rampage&lt;/strong&gt; — and lost the leftover “tunnels” copy, since the spider never goes underground (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/202&quot;&gt;#202&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;The invasion finally paths correctly&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Invaders now &lt;strong&gt;route through bent underground tunnels via BFS&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/171&quot;&gt;#171&lt;/a&gt;), descent is &lt;strong&gt;gated on entrance blockers&lt;/strong&gt; so you cannot descend into a hole that is not actually open (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/169&quot;&gt;#169&lt;/a&gt;), and foreign-grid recall now &lt;strong&gt;keys on the rally point rather than the fight ratio&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/189&quot;&gt;#189&lt;/a&gt;, 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 &lt;strong&gt;scoped per-world&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/168&quot;&gt;#168&lt;/a&gt;) 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.&lt;/p&gt;
&lt;h2&gt;The starvation bug&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Dropping maturation to 1500 ticks (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/180&quot;&gt;#180&lt;/a&gt;) 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.&lt;/p&gt;
&lt;p&gt;Alongside it, the nursery deposit became &lt;strong&gt;capacity-aware&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/183&quot;&gt;#183&lt;/a&gt;, V24) so brood is not deposited into a nursery that is already full, which was the other half of why the pipeline stalled.&lt;/p&gt;
&lt;h2&gt;Performance, where it actually matters&lt;/h2&gt;
&lt;p&gt;Combat and the per-tick update are the hot paths — they run for every ant, every tick, in both simulations at once. They were &lt;strong&gt;allocating objects inside those loops&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/170&quot;&gt;#170&lt;/a&gt;), 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.&lt;/p&gt;
&lt;h2&gt;Tooling, tests, and save robustness&lt;/h2&gt;
&lt;p&gt;A lot of this stretch was paying down the kind of debt that does not show up in a screenshot.&lt;/p&gt;
&lt;p&gt;The codebase &lt;strong&gt;adopted Prettier&lt;/strong&gt; and normalized formatting repo-wide (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/184&quot;&gt;#184&lt;/a&gt;), then added a &lt;strong&gt;type-aware ESLint layer&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/185&quot;&gt;#185&lt;/a&gt;) that catches a class of bugs the basic linter cannot see. A &lt;strong&gt;verify + coverage workflow&lt;/strong&gt; went into CI (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/187&quot;&gt;#187&lt;/a&gt;), and after some honest accounting, coverage got recorded as &lt;strong&gt;intentionally local-only&lt;/strong&gt; rather than pretending it was a CI gate it was not (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/199&quot;&gt;#199&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The end-to-end suite had rotted during the v3.0 mechanics work. It got &lt;strong&gt;restored and gated in CI&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/195&quot;&gt;#195&lt;/a&gt;), then the flaky speed-cycle and pheromone tests got &lt;strong&gt;de-flaked and un-quarantined&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/200&quot;&gt;#200&lt;/a&gt;) so they are real signal again. The save-flow tests were &lt;strong&gt;reworked around real captured saves&lt;/strong&gt; instead of synthetic fixtures (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/197&quot;&gt;#197&lt;/a&gt;), and a genuine save bug got fixed along the way: &lt;strong&gt;future-build saves now survive a fresh boot&lt;/strong&gt; instead of being silently discarded (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/198&quot;&gt;#198&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The F9 debug snapshot got a &lt;strong&gt;self-describing guide block&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/182&quot;&gt;#182&lt;/a&gt;) 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 &lt;strong&gt;refresh for the post-Phase-3 state&lt;/strong&gt; (&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/201&quot;&gt;#201&lt;/a&gt;), because the map should match the territory after a phase that changed this much.&lt;/p&gt;
&lt;h2&gt;A note from the desk&lt;/h2&gt;
&lt;p&gt;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 &lt;em&gt;mine&lt;/em&gt; is the judgment — which is the part I wanted to be doing anyway.&lt;/p&gt;
&lt;p&gt;It also helps that, this week, the desk looks like this.&lt;/p&gt;
&lt;img src=&quot;/_astro/hawaii-bay-lanai.KxNbgCUc_ZCzm1U.jpg&quot; alt=&quot;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.&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;1200&quot; /&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;What’s next&lt;/h2&gt;
&lt;p&gt;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. &lt;a href=&quot;/demo/play&quot;&gt;Play it&lt;/a&gt;, lose a round, and the sim will tell both of us how.&lt;/p&gt;</content:encoded></item><item><title>Phase 3 shipped: it&apos;s a round now</title><link>https://subterrans.com/devlog/phase-3-shipped/</link><guid isPermaLink="true">https://subterrans.com/devlog/phase-3-shipped/</guid><description>Phase 3 is done. Seven mechanics, eight implementation stages, seven sim-version bumps. Here is what shipped and what it actually felt like to land.</description><pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Phase 3 is done. Two colonies, a neutral predator, a deterministic AI that invades on its own, real combat math, a reproduction lever, difficulty tiers, and a polish pass that makes the events legible. The &lt;a href=&quot;/devlog/phase-3-designing-the-first-real-round&quot;&gt;design post from ten days ago&lt;/a&gt; described what it was supposed to be. This is what it actually turned out to be.&lt;/p&gt;
&lt;p&gt;The short version: it is all there. Seven mechanics, eight implementation stages, seven sim-version bumps (V16 through V22). The game is now something that has a beginning, a middle, and an end. A round has shape. That is a different thing than what existed before.&lt;/p&gt;
&lt;h2&gt;What changed in combat&lt;/h2&gt;
&lt;p&gt;Before Phase 3, two ants meeting on the same tile resolved combat by coin flip: one died, one lived, repeat. Numbers barely mattered. Position did not matter. The home colony had no meaningful advantage.&lt;/p&gt;
&lt;p&gt;Phase 3 replaced this with a deterministic multi-tick model. Each ant has HP and a damage-per-strike, and home-ground ants get a 25% buff to both. A home defender at a tunnel entrance wins reliably against a single invader — not sometimes, always. Two invaders at the same chokepoint still beat one defender, because replacement depth means the second attacker inherits a wounded opponent. Three invaders beat the defender and barely lose anyone. The math is exact enough to plan around.&lt;/p&gt;
&lt;p&gt;The practical effect is that tunnel geometry matters now. A narrow underground passage is defensible in a way it was not before. The decision about where to put the queen chamber relative to your entrances is a real decision. Widening a tunnel to speed supply trades directly against its defensibility. This was theoretically true before; now the numbers support it.&lt;/p&gt;
&lt;p&gt;One thing that was not in the original plan: after combat shipped, a follow-on pass added sight-based fighter aggression. Fighters who see an enemy within range now initiate without being directed. The behavior ratio still governs how many fighters you have, but they are no longer passive — they engage what they find. This made the surface feel alive in a way the original plan underspecified.&lt;/p&gt;
&lt;h2&gt;What changed with the AI&lt;/h2&gt;
&lt;p&gt;The AI in Phase 2 foraged. It built its colony, occasionally its fighters ended up in the same space as your fighters, and things happened. It was never planning anything. The game outcome was whoever got unlucky on coin flips while running roughly parallel processes.&lt;/p&gt;
&lt;p&gt;Phase 3 gave the AI a named state machine: Peacetime, War Footing, Probing, Invading, Recovery. The transitions are driven by conditions in the sim — fighter count, food storage, round age, your colony size relative to theirs. The AI enters War Footing when it is ready and starts sending small probe raids — three fighters toward a surface food pile, not toward your queen. If the probe succeeds, it comes back at scale. If it fails, it recovers and tries again.&lt;/p&gt;
&lt;p&gt;The first time I saw a probe raid while the AI was in War Footing was the first time the game felt genuinely hostile. Not randomly hostile — intentionally hostile. There is now a reason to watch what the enemy colony is doing, not just your own.&lt;/p&gt;
&lt;p&gt;The loss-cause ledger was spec’d as a low-key requirement in the design plan. It ended up shipping as its own PR and becoming more central than expected. The game-over screen now tells you specifically how you lost — the enemy fighters broke through, the spider rampaged your nursery, your queen starved — using actual sim event data, not placeholder text. That one detail made losing feel informative instead of opaque. Players need to know what went wrong. Generic “you lost” is a dead end.&lt;/p&gt;
&lt;h2&gt;What changed with the spider&lt;/h2&gt;
&lt;p&gt;The spider is a neutral predator with a hunger clock. It wanders the surface on a ~60-second hunt cadence and places a red reticle on its target tile 6–8 seconds before striking. Workers self-scatter away from the reticle. If enough fighters arrive within engagement range, the spider retreats. If not enough arrive, they die.&lt;/p&gt;
&lt;p&gt;The design intent was that ignoring the spider is viable but costly: it picks off workers slowly, which compounds over a long round. Engaging it costs fighters and attention. The actual dynamic in play is that the reticle creates a recurring decision — every hunt cycle, you choose whether to respond. Most rounds you do not respond directly; you just route around it or accept the occasional worker loss. But at higher hunger levels the spider rampages into the nest, and that is immediately and visibly catastrophic. The hunger ring on the spider sprite (a color gradient that intensifies as hunger builds) is the only cue. If you are not watching it, you get surprised.&lt;/p&gt;
&lt;p&gt;The right-click command to designate the spider as a priority target shipped last, in its own cleanup stage. It was deferred from the spider’s initial implementation to keep that PR focused on the sim foundation. By the time it shipped the mechanic was solid enough that adding the input felt like filling in a blank rather than making a design decision.&lt;/p&gt;
&lt;h2&gt;Reproduction and difficulty&lt;/h2&gt;
&lt;p&gt;The reproduction lever ties food surplus to egg-laying speed. When your food storage is high relative to colony size, the queen lays faster. Nurses in nursery chambers accelerate larval maturation. Nursery capacity caps the throughput. The three levers interact: more surplus → more eggs → more demand for nurses → fewer foragers → slower surplus accumulation. The cycle regulates itself if you let it.&lt;/p&gt;
&lt;p&gt;This was the mechanic I was most uncertain about before Phase 3. In isolation, “food surplus accelerates growth” sounds like “just stockpile food and win.” In practice, the food-to-workers pipeline has enough latency and enough competing demands that it reads as a real trade-off, not a stat to maximize.&lt;/p&gt;
&lt;p&gt;Difficulty tiers shipped as Easy/Normal/Hard and apply two independent axes: asymmetric modifiers on both colonies’ economics and combat (AI gets buffed, player gets nerfed, or vice versa) and symmetric world-pressure changes (spider hunger interval, food pile respawn rate). Normal is balanced for a player who has played two or three rounds. Easy gives enough margin to learn the systems without constant punishment. Hard is hard.&lt;/p&gt;
&lt;h2&gt;What this unlocks&lt;/h2&gt;
&lt;p&gt;Phase 2 was a proof that the simulation worked. Phase 3 is a proof that the game loop has the right shape. A round now has pressure that escalates, decisions that compound, and outcomes you can explain after the fact.&lt;/p&gt;
&lt;p&gt;The honest next question is whether the game is fun in the hands of someone who did not build it. That is what the demo is for. &lt;a href=&quot;/demo/play&quot;&gt;Play it&lt;/a&gt; and use the feedback form at the end. The sim produces a playtrace for every session — loss reasons, decision points, where the round broke. That data is going to drive Phase 4 tuning.&lt;/p&gt;</content:encoded></item><item><title>A new roadmap</title><link>https://subterrans.com/devlog/a-new-roadmap/</link><guid isPermaLink="true">https://subterrans.com/devlog/a-new-roadmap/</guid><description>The roadmap has been reorganized into phases. Where we are, where v1.0 is, and what changed from the old structure.</description><pubDate>Sun, 17 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We have reorganized the roadmap. Versions are out; phases are in. Phases describe what we are building. Versions describe where we are. Those are different questions, and the old roadmap was trying to answer both with the same label.&lt;/p&gt;
&lt;h2&gt;Where we are&lt;/h2&gt;
&lt;p&gt;Subterrans is around v0.2. The simulation runs. Two colonies, pheromone trails, foraging, combat, a rule-based AI opponent. You can play a round end-to-end and lose to the enemy queen.&lt;/p&gt;
&lt;p&gt;What rounds do not yet have is shape. There is no middle. You dig, forage, and fight until one queen dies. That is a loop, but it is not yet a game — there is nothing that builds pressure, no moment where the match turns, no point at which the right decision earlier becomes visible as the right decision in retrospect.&lt;/p&gt;
&lt;p&gt;That is what Phase 3 is for. More on that in a &lt;a href=&quot;/devlog/phase-3-designing-the-first-real-round&quot;&gt;companion post&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Where v1.0 is&lt;/h2&gt;
&lt;p&gt;Version 1.0 is feature-complete and genuinely fun. Not a date. A destination. The phases between here and there are the things we have to build to know whether the game is actually fun, and to fix it if it is not.&lt;/p&gt;
&lt;p&gt;That framing matters. The old roadmap listed v1.0 as a milestone we had already shipped — the PRD and architecture phase. That was wrong in a way that mattered: it implied more progress than there was, and it made “v2.0” the new thing instead of acknowledging we are early in a long arc. The new numbering is honest. We are around v0.2. We know what we are building. The destination is clear.&lt;/p&gt;
&lt;h2&gt;The phases&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;/roadmap&quot;&gt;roadmap page&lt;/a&gt; has the full detail. Short version:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1 — PRDs &amp;amp; Architecture.&lt;/strong&gt; Shipped 2026-04-14. Requirements and architecture for the seven essential systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2 — Playable POC.&lt;/strong&gt; Shipped 2026-04-28. The simulation runs in a browser with two colonies and a functional game loop.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 3 — First Real Round.&lt;/strong&gt; In implementation now. Seven mechanics that give a match a beginning, middle, and end.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 4 — Yard Scale.&lt;/strong&gt; The world grows from one patch to a yard of connected territories with biome diversity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 5a — Sensory Pass.&lt;/strong&gt; Audio and pixel art. The game starts to feel like a finished product.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 5b — Variety Pass.&lt;/strong&gt; Additional predator types, multiple map seeds, expanded round configurations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 6 — Mobile.&lt;/strong&gt; Touch input and a performance budget for mid-range phones.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 7 — World &amp;amp; Social.&lt;/strong&gt; Castes, weather, day/night. Multiplayer is also here, framed as exploring — see below.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version 1.0.&lt;/strong&gt; Feature-complete, genuinely fun.&lt;/p&gt;
&lt;h2&gt;What changed from the old roadmap&lt;/h2&gt;
&lt;p&gt;Three things came off the list or moved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yellow Ant is removed.&lt;/strong&gt; Direct control of a single ant, SimAnt-style, was on the old roadmap as a planned depth feature. It is not in any current design. The game’s interesting design space is in colony management, not individual control. Removing it is not a concession — it is an honest accounting of what the game is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Castes, weather, and day/night have moved.&lt;/strong&gt; They were filed under “Beyond v2.0” with no home. They now belong to Phase 7 — World &amp;amp; Social — which gives them a place in the sequence without implying a timeline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multiplayer stays, framed correctly.&lt;/strong&gt; It is in Phase 7, labeled as “exploring.” The architecture preserves the option — deterministic simulation, replay logging, the whole foundation — but no design work has started, and a commitment to ship multiplayer before we know whether the single-player game is fun would be the wrong commitment to make. It is on the roadmap to anchor future architectural decisions. It is not a promise.&lt;/p&gt;
&lt;h2&gt;The point&lt;/h2&gt;
&lt;p&gt;Working in phases keeps us honest about scope. Each phase has a question it is answering. Phase 3 asks whether a round can have shape. Phase 4 asks whether the world can have scale. Phase 5 asks whether the game can be felt as well as played. Each question has a real answer, and if the answer is no, the phases after it are wrong.&lt;/p&gt;
&lt;p&gt;The roadmap is subject to change. Phases may be reordered, split, or dropped as we learn what makes the game fun.&lt;/p&gt;</content:encoded></item><item><title>Phase 3: designing the first real round</title><link>https://subterrans.com/devlog/phase-3-designing-the-first-real-round/</link><guid isPermaLink="true">https://subterrans.com/devlog/phase-3-designing-the-first-real-round/</guid><description>Phase 3 is the first version of Subterrans where a match has a beginning, middle, and end. Here is what it introduces and why.</description><pubDate>Sun, 17 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Phase 3 is the first version of Subterrans where a match has a beginning, middle, and end.&lt;/p&gt;
&lt;p&gt;Right now the simulation runs. Two colonies start on opposite sides of a patch, dig chambers, forage for food, grow workers, fight. One queen dies. The simulation is correct — deterministic, headlessly testable, well-specified. But a round does not have shape. There is no pressure that builds, no moment where the match turns. You play until one side wins, and the outcome feels roughly proportional to the decisions you made, but the decisions were not made &lt;em&gt;in response to anything&lt;/em&gt;. That is a loop. It is not yet a round.&lt;/p&gt;
&lt;p&gt;Phase 3 adds the mechanics that give it shape. Seven of them, in the order they layer in:&lt;/p&gt;
&lt;h2&gt;The spider&lt;/h2&gt;
&lt;p&gt;A neutral predator, not allied with either colony. The spider operates on a hunger clock independent of both players: it wanders, it attacks anything it finds, and when it is hungry enough it will engage ants it would otherwise avoid. It cannot be defeated by one colony alone at early-game population levels. Both colonies have to deal with it.&lt;/p&gt;
&lt;p&gt;This introduces the first thing that happens to you that you did not cause — and that your opponent did not cause either. The early game is no longer just a race to grow. It is a race to grow while something is reducing your workers.&lt;/p&gt;
&lt;h2&gt;The threat reticle&lt;/h2&gt;
&lt;p&gt;Six to eight seconds before a spider attack lands, a reticle appears over the target area. The warning is long enough to react to — pull workers back, adjust the behavior ratio, send fighters — but short enough that the reaction has to be fast and costs something. You cannot pre-position perfectly for a threat you have not seen yet.&lt;/p&gt;
&lt;p&gt;The reticle is a design principle as much as a mechanic. Danger that is invisible until it lands does not create decisions; it creates complaints. Telegraphed danger creates decisions. The question is whether you saw it, how fast you reacted, and whether the cost of reacting was worth paying.&lt;/p&gt;
&lt;h2&gt;The mark-priority command&lt;/h2&gt;
&lt;p&gt;A player command that designates a high-priority target — spider, enemy fighter, enemy entrance. Workers and fighters route toward it before taking other actions.&lt;/p&gt;
&lt;p&gt;This is the player’s most direct lever over the colony’s response to a threat. Before Phase 3, the behavior ratio (forage / defend / dig) was the only coarse-grained control over what the colony was doing. Mark-priority adds a fine-grained one: not “send more fighters” in general, but “send fighters &lt;em&gt;there&lt;/em&gt;, &lt;em&gt;now&lt;/em&gt;.” The distinction between the two is most visible under threat, which is exactly when the difference matters.&lt;/p&gt;
&lt;h2&gt;Worker self-scatter&lt;/h2&gt;
&lt;p&gt;When danger is close, workers in the affected area move away from it without a player command. They do not fight. They do not freeze. They scatter, and return when the threat passes.&lt;/p&gt;
&lt;p&gt;Self-scatter sounds passive, but it is the mechanic that makes the spider feel like a disruption rather than a damage event. A spider does not just remove workers — it scatters them, and scattering takes time to recover from. If workers were foraging a food pile near a spider patrol path, you lose not just the workers that got hit but the next twenty seconds of foraging efficiency while the rest return and re-establish trails. That is the middle of a round: a sequence of disruptions you manage better or worse than your opponent.&lt;/p&gt;
&lt;h2&gt;Danger pheromones&lt;/h2&gt;
&lt;p&gt;When ants encounter a threat — spider, enemy fighter, proximity to an enemy entrance — they emit danger pheromones. These propagate across the grid and decay over time, following the same two-grid pheromone system already used for food trails.&lt;/p&gt;
&lt;p&gt;The effect: the colony remembers where things went wrong, and ants route around those areas until the pheromone fades. A spider that killed workers in a corner of the map leaves a danger signature there. Workers will forage around it. Fighters will approach more cautiously.&lt;/p&gt;
&lt;p&gt;Danger pheromones do not require player input. They are emergent colony-level memory. But they are also visible — the pheromone overlay shows them — which means a player who reads them has information about where pressure has been and where it might return.&lt;/p&gt;
&lt;h2&gt;The reproduction lever&lt;/h2&gt;
&lt;p&gt;Food surplus above a threshold accelerates egg-laying rate and larva maturation speed. When the colony is well-fed, it grows faster. When food is contested or restricted, growth slows.&lt;/p&gt;
&lt;p&gt;This creates a mid-game dynamic that was absent before: food is not just a resource, it is the colony’s growth rate. A player who controls more food patches earlier builds population faster, which enables defending more food patches, which compounds. The reproduction lever is the mechanism that makes early-game foraging decisions visible as late-game population differences — the thing that makes a middle exist.&lt;/p&gt;
&lt;p&gt;It also makes the spider more interesting. A spider that disrupts foraging near a food-rich area does not just kill workers; it reduces the food flow that drives reproduction. Two disruptions, one event.&lt;/p&gt;
&lt;h2&gt;Difficulty tiers&lt;/h2&gt;
&lt;p&gt;Three settings that change how the AI opponent plays and how the world generates pressure. The differences are asymmetric: easier tiers reduce the AI’s resource efficiency and combat aggression, not just its health or numbers. Harder tiers give the AI better foraging routes, faster reaction to threat, and a world with more spider activity.&lt;/p&gt;
&lt;p&gt;Difficulty tiers exist because Phase 3 adds enough mechanistic depth that the same settings that make the game learnable for a new player make it trivial for an experienced one. The tiers are a tuning surface. What matters for Phase 3 is that they exist and that the hardest tier is genuinely hard — that requires the spider system and the reproduction lever to be working, which is why it gets built last.&lt;/p&gt;
&lt;h2&gt;The design weekend&lt;/h2&gt;
&lt;p&gt;Phase 3 was designed over one weekend, using a three-pass Codex review process and a dual-reviewer structure: each proposal was reviewed independently by two agents before being folded into the decision journal. The seven mechanics above came out of that process.&lt;/p&gt;
&lt;p&gt;The methodology matters only insofar as it produced a coherent phase. The seven mechanics reinforce each other — the spider creates disruption, the reticle makes disruption legible, scatter makes it expensive, danger pheromones make it persistent, the reproduction lever makes it consequential, mark-priority gives the player agency over it, and difficulty tiers calibrate how much of it there is. Designing them separately would have produced seven features. Designing them together produced a round.&lt;/p&gt;
&lt;h2&gt;What this unlocks&lt;/h2&gt;
&lt;p&gt;Once Phase 3 lands, the question shifts from “does the simulation run?” to “is the round fun?”&lt;/p&gt;
&lt;p&gt;That is the question Phase 1 — the original PRD, written in April — said we would answer first. We are finally there. The simulation is correct. The architecture is sound. The feedback tooling is in place. Phase 3 is the first version of Subterrans that can be evaluated as a game.&lt;/p&gt;
&lt;h2&gt;What comes next&lt;/h2&gt;
&lt;p&gt;Implementation starts now, stage by stage. The spider comes first; then the reticle and scatter; then pheromones and reproduction; then mark-priority; then difficulty tiers and tuning. Each stage ships as a PR, visible in the &lt;a href=&quot;https://github.com/LightAxe/subterrans&quot;&gt;game repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The devlog will be quieter while builds are happening. When Phase 3 is done, you will hear about it. In the meantime, the demo at &lt;a href=&quot;/demo/play&quot;&gt;/demo/play&lt;/a&gt; is the current state of the game, and the &lt;a href=&quot;/roadmap&quot;&gt;roadmap&lt;/a&gt; tracks where each phase sits. If you play it and something is confusing, broken, or just wrong — say so. That feedback matters more now than it will at any later point.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;This post is a companion to &lt;a href=&quot;/devlog/a-new-roadmap&quot;&gt;A New Roadmap&lt;/a&gt;, which covers the updated phase structure and what changed from the old roadmap.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>The week the ants got better</title><link>https://subterrans.com/devlog/the-week-the-ants-got-better/</link><guid isPermaLink="true">https://subterrans.com/devlog/the-week-the-ants-got-better/</guid><description>Visible brood carry, diagonal motion, real pixel-art terrain — plus a structured codebase review that found twenty latent bugs you&apos;ll never see fire.</description><pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Five days since &lt;a href=&quot;/devlog/first-players-first-bugs&quot;&gt;the last post&lt;/a&gt;. 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.&lt;/p&gt;
&lt;h2&gt;Things you can see&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Nurses now carry brood.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ants move diagonally now.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The dirt is dirt.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;h2&gt;The AI got harder to push around&lt;/h2&gt;
&lt;p&gt;Two changes worth calling out:&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Things you can’t see&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;A few of the worse ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Entity ID overflow.&lt;/strong&gt; &lt;code&gt;allocateEntityId&lt;/code&gt; would let the counter walk past &lt;code&gt;MAX_ENTITIES = 8192&lt;/code&gt; and the next caller would write to TypedArray index 8192 — silent corruption. Fixed: counter freezes at the cap, callers receive a &lt;code&gt;-1&lt;/code&gt; sentinel and gracefully bail. Egg-laying becomes a soft population cap instead of a crash.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NaN command coordinates.&lt;/strong&gt; Every existing range check rejected &lt;code&gt;Infinity&lt;/code&gt; (&lt;code&gt;Infinity &amp;gt;= max&lt;/code&gt; is true) but not &lt;code&gt;NaN&lt;/code&gt; (&lt;code&gt;NaN &amp;gt;= max&lt;/code&gt; is false). A &lt;code&gt;NaN&lt;/code&gt; coord slipped through every guard, persisted into colony state, and broke save replay determinism via &lt;code&gt;JSON.stringify(NaN) === &apos;null&apos;&lt;/code&gt;. New shared &lt;code&gt;isTileCoord&lt;/code&gt; predicate enforces integer-in-range at handler entry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save validation against tampering.&lt;/strong&gt; &lt;code&gt;s.ants.count: 1e9&lt;/code&gt; used to allocate ~88 GB of TypedArrays before any sanity check. Now clamped. &lt;code&gt;s.simVersion&lt;/code&gt; outside the supported range used to silently flip every gate. Now throws.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forward-compatible saves.&lt;/strong&gt; A new &lt;code&gt;FutureSimVersionError&lt;/code&gt; distinguishes “save written by a newer build” from “save corrupted.” Future-build saves are now preserved across &lt;code&gt;bootFresh&lt;/code&gt; + 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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Camera-pan freeze on HUD crossing.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pheromone leakage between zones.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Why the simVersion gates matter&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Going forward&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;If you find something, &lt;a href=&quot;https://github.com/LightAxe/subterrans/issues&quot;&gt;issue tracker&lt;/a&gt;. The bug-report template walks you through the F9 debug log, which gives me a reproducible replay instead of guesswork.&lt;/p&gt;
&lt;p&gt;More soon.&lt;/p&gt;</content:encoded></item><item><title>First players, first bugs</title><link>https://subterrans.com/devlog/first-players-first-bugs/</link><guid isPermaLink="true">https://subterrans.com/devlog/first-players-first-bugs/</guid><description>What changed in two weeks of going from private repo to embedded demo: real issue reports, the last Phase 1 gameplay seams, and one system that got smaller instead of bigger.</description><pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Two weeks ago the game lived in a private repo on my laptop. Today it’s embedded at &lt;a href=&quot;/demo/play&quot;&gt;/demo/play&lt;/a&gt;, the source code is on &lt;a href=&quot;https://github.com/LightAxe/subterrans&quot;&gt;GitHub&lt;/a&gt;, and the issues page has real bug reports from people who aren’t me. That shift — from building in isolation to building under observation — changed what I worked on this week.&lt;/p&gt;
&lt;p&gt;Here’s what landed.&lt;/p&gt;
&lt;h2&gt;Phase 1 is now actually playable end-to-end&lt;/h2&gt;
&lt;p&gt;The simulation has supported invading the enemy hive since Phase 09.1: a fighting ant standing on the enemy’s entrance descends into their grid, hunts the nearest hostile, and resolving combat against their queen triggers victory. That’s the win condition for Phase 1. But none of it was reachable from the UI — clicking on an enemy entrance did nothing visible, the keybind to swap underground views was undiscoverable, and there was no visual cue distinguishing “enemy entrance” from “any other surface tile.”&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/LightAxe/subterrans/pull/29&quot;&gt;PR #29&lt;/a&gt; wired up the four UI seams. Left-click on an enemy entrance now sets your rally point there. The X-keybind got a clickable button labeled “Your Colony / Enemy Colony.” Underground clicks on the spectator view became no-ops (instead of silently dispatching dig-marks against your own grid at the matching coordinates). And enemy entrances got a single-pixel red perimeter ring so they read as “rally here to invade” without any tutorial text.&lt;/p&gt;
&lt;p&gt;The simulation work was already done. The lesson was that “feature ships when sim supports it” is wrong; the feature ships when a player can find it without reading the source.&lt;/p&gt;
&lt;h2&gt;One system got smaller&lt;/h2&gt;
&lt;p&gt;Phase 10 was originally going to add a dig-priority slider to the existing forage/dig/fight ratio that players control. After playtest, the slider got cut entirely. The behavior ratio is now two fields — &lt;code&gt;forage&lt;/code&gt; and &lt;code&gt;fight&lt;/code&gt; — and digging is auto-assigned based on outstanding marked tiles. Less UI, less to explain, and the simulation does the bookkeeping that a human would have done badly anyway.&lt;/p&gt;
&lt;p&gt;This is the second time I’ve removed a control from the player surface in this project. The pattern: if a system has a single right answer most of the time, exposing it as a knob just lets the player make it worse. Auto-assign isn’t a smarter system; it’s a refusal to let the player accidentally starve the dig queue while staring at a UI element.&lt;/p&gt;
&lt;h2&gt;The first round of real issues&lt;/h2&gt;
&lt;p&gt;The issues page collected reports faster than I expected. The interesting ones, in rough order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/LightAxe/subterrans/issues/16&quot;&gt;&lt;strong&gt;#16 — queen wanders into the corner of her chamber and stays there.&lt;/strong&gt;&lt;/a&gt; Visually unsettling; functionally fine. Fix was a wander policy that keeps her inside the chamber bounds rather than against an arbitrary wall.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/LightAxe/subterrans/issues/15&quot;&gt;&lt;strong&gt;#15 — chamber food storage was render-authoritative.&lt;/strong&gt;&lt;/a&gt; Storage capacity was being computed by the rendering layer reading sprite occupancy, which is exactly the inversion the &lt;a href=&quot;/devlog/the-seven-principles&quot;&gt;seven principles&lt;/a&gt; say to never do. Made the chamber the authoritative source.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/LightAxe/subterrans/issues/27&quot;&gt;&lt;strong&gt;#27 — carriers oscillating between food piles when colony storage was full.&lt;/strong&gt;&lt;/a&gt; A worker would pick up food, walk halfway home, drop it because storage was saturated, walk back, repeat. Fix: bail out at pickup time, not at delivery.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/LightAxe/subterrans/issues/30&quot;&gt;&lt;strong&gt;#30 / #31 — invisible ceiling-row dig marks.&lt;/strong&gt;&lt;/a&gt; Clicking on the grass strip at the top of the underground view dispatched a real dig command against the tile underneath, but the renderer kept painting grass on top — zero feedback that anything happened, and the orphan marks were dragging idle workers into infinite loops. Fixed in three layers: input rejection, sim-handler rejection, and AI-controller pre-filter. Defense in depth, because the AI was creating ceiling-row marks too.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A pattern in this list: most of the bugs were &lt;em&gt;input-layer or rendering-layer&lt;/em&gt; bugs surfacing &lt;em&gt;simulation-layer&lt;/em&gt; symptoms. The sim itself, the part the seven principles protect, has been mostly fine. The friction is in the seams — the places where player intent translates into commands, or where world state translates into pixels.&lt;/p&gt;
&lt;h2&gt;How the game gets to the page&lt;/h2&gt;
&lt;p&gt;The library bundle work was the biggest mechanical chunk. The game builds itself as a single ESM module with a &lt;code&gt;mount()&lt;/code&gt; entry point that the website’s Astro page imports dynamically. Cache-busted hashed filenames + a manifest.json so the website’s deploy can reference the right URL. A &lt;code&gt;ready&lt;/code&gt; promise so the loading state knows when to clear. Source maps. A &lt;code&gt;.d.ts&lt;/code&gt; file generated directly from the source so the public API can’t drift from its declared types.&lt;/p&gt;
&lt;p&gt;It now takes a single push to game &lt;code&gt;main&lt;/code&gt; to redeploy the live demo. The game’s CI bumps the website’s submodule pointer, the website’s deploy picks that up, the bundle builds with a fresh hash, and CloudFront serves it. None of that is interesting in isolation. What’s interesting is that the friction-to-ship is now low enough that I’ll fix bugs in the morning instead of batching.&lt;/p&gt;
&lt;h2&gt;The dual-AI review pipeline went live&lt;/h2&gt;
&lt;p&gt;Every PR on the public repo now gets reviewed by Codex automatically (skipped on owner-authored PRs, since I’m reviewing my own work via Claude already). The bug-report template emphasizes the F9 debug log, which produces the input-log + seed needed to reproduce a deterministic replay. The branch-and-PR workflow is now the rule, not the convention.&lt;/p&gt;
&lt;p&gt;If you find something broken in the demo, I’d rather hear about it than not. The &lt;a href=&quot;https://github.com/LightAxe/subterrans/issues&quot;&gt;issue tracker&lt;/a&gt; is the place — the bug-report template walks you through the F9 debug log, which gives me a reproducible replay instead of guesswork.&lt;/p&gt;
&lt;p&gt;More soon.&lt;/p&gt;</content:encoded></item><item><title>The seven principles</title><link>https://subterrans.com/devlog/the-seven-principles/</link><guid isPermaLink="true">https://subterrans.com/devlog/the-seven-principles/</guid><description>Seven non-negotiable architectural rules for Subterrans, why each one matters, and what breaks if you skip it.</description><pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;(Editor’s note, 2026-05-17: Phase numbering has been updated since this post was published. See the &lt;a href=&quot;/roadmap&quot;&gt;current roadmap&lt;/a&gt; for the active phase structure.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Subterrans has seven architectural principles I won’t compromise on. They sound austere written down. They are. The reason they’re non-negotiable is that every one of them protects something I want — replay, future multiplayer, deterministic debugging, AI agents that can actually reason about the code — and the cost of relaxing any of them shows up later, when fixing it requires rewriting half the simulation.&lt;/p&gt;
&lt;p&gt;This post is the long version of &lt;a href=&quot;https://github.com/LightAxe/subterrans/blob/main/AGENTS.md&quot;&gt;AGENTS.md&lt;/a&gt;, with the reasoning expanded.&lt;/p&gt;
&lt;aside&gt; &lt;p&gt;If you&apos;re skimming&lt;/p&gt; &lt;div&gt;&lt;p&gt;Each principle has the same shape: a hard rule, what it protects, what breaks if you ignore it. The rules are boring. The consequences of ignoring them are not.&lt;/p&gt;&lt;/div&gt; &lt;/aside&gt;
&lt;h2&gt;1. Strict separation of simulation from rendering&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;src/sim/&lt;/code&gt; is pure TypeScript. Zero dependencies on Phaser, the DOM, the browser, or any rendering framework. The simulation takes inputs (player commands, time deltas) and produces state. The rendering layer reads that state and draws it. The simulation must be liftable into Node.js with no code changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What this protects:&lt;/strong&gt; headless tests, deterministic replay, future server-authoritative multiplayer, AI agents that can reason about the simulation in isolation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What breaks if you skip it:&lt;/strong&gt; every test that touches the simulation has to spin up Phaser. Every replay has to render. The “model” and the “view” tangle into one ball of mud, and a year from now any change to either one risks breaking the other in a way that wasn’t possible to predict.&lt;/p&gt;
&lt;p&gt;The lint config flags any &lt;code&gt;import&lt;/code&gt; from outside &lt;code&gt;src/sim/&lt;/code&gt; into &lt;code&gt;src/sim/&lt;/code&gt; as a hard violation. PR reviewers — both human and AI — block on it.&lt;/p&gt;
&lt;h2&gt;2. Fixed timestep at 20 Hz&lt;/h2&gt;
&lt;p&gt;The simulation advances exactly 50 milliseconds per tick. The renderer runs at the browser’s framerate (typically 60fps), interpolating between sim ticks for smooth visuals. Variable timestep is forbidden.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What this protects:&lt;/strong&gt; determinism. Same inputs from the same seed produce the same final state, tick for tick.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What breaks if you skip it:&lt;/strong&gt; SimAnt-era games could get away with frame-rate-tied physics because everyone’s hardware ran at roughly the same speed. In 2026, your laptop might render at 144fps and your friend’s tablet at 30fps, and a variable-step simulation produces different worlds on each. Goodbye replay, goodbye multiplayer, goodbye reproducible bugs.&lt;/p&gt;
&lt;p&gt;20 Hz is enough resolution for ant-scale movement and slow enough that the simulation has headroom to do interesting work each tick. The 60fps render layer covers any visual smoothness gap via interpolation.&lt;/p&gt;
&lt;h2&gt;3. Lightweight ECS-flavored architecture&lt;/h2&gt;
&lt;p&gt;Entities are integer IDs. Components are typed arrays (structure-of-arrays for hot data) or plain maps. Systems are pure functions that operate on entities matching certain components.&lt;/p&gt;
&lt;p&gt;No &lt;code&gt;class Ant&lt;/code&gt;. No &lt;code&gt;class Colony&lt;/code&gt;. Data is data; behavior is functions.&lt;/p&gt;
&lt;p&gt;No ECS library in Phase 1 — no &lt;code&gt;bitecs&lt;/code&gt;, no &lt;code&gt;miniplex&lt;/code&gt;. The lightweight approach is migration-compatible if I ever need a real ECS, and the absence of the library means there’s no framework to fight when the architecture wants to evolve.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What this protects:&lt;/strong&gt; cache-friendly hot loops, the ability to serialize the entire world cheaply, and a model that AI agents (and humans coming back to the code six months later) can hold in their heads.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What breaks if you skip it:&lt;/strong&gt; classes accrete behavior. An &lt;code&gt;Ant&lt;/code&gt; class quickly grows methods that touch the colony, the world, the renderer, and audio playback. By the time you notice, you can’t change one without breaking five others. ECS-shaped data avoids that by making the relationships explicit and the data flat.&lt;/p&gt;
&lt;h2&gt;4. Seeded deterministic random number generation&lt;/h2&gt;
&lt;p&gt;The simulation uses a single Mulberry32 PRNG instance, seeded at world creation. Every random decision in the entire game flows through this one instance. The seed is saved with the save file. &lt;code&gt;Math.random()&lt;/code&gt; is banned in &lt;code&gt;src/sim/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What this protects:&lt;/strong&gt; replay. Given a seed and an input log, the world plays back identically every time. That’s how I debug timing bugs, how I write determinism tests, and how multiplayer (Phase 4) becomes feasible at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What breaks if you skip it:&lt;/strong&gt; subsystems quietly create their own RNGs. A worker’s path-finding uses &lt;code&gt;Math.random&lt;/code&gt;. A larva’s hatching uses &lt;code&gt;Date.now() % 100&lt;/code&gt;. Over a 10-minute round, the divergence between two replays accumulates into completely different worlds. Determinism is binary — you either have it everywhere or you don’t have it at all.&lt;/p&gt;
&lt;h2&gt;5. No wall-clock time in the simulation&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;Date.now()&lt;/code&gt;, &lt;code&gt;performance.now()&lt;/code&gt;, and any other wall-clock APIs are banned in &lt;code&gt;src/sim/&lt;/code&gt;. The simulation knows only its own tick counter. Elapsed simulation time is &lt;code&gt;tickCount * msPerTick&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What this protects:&lt;/strong&gt; identical results regardless of when the simulation runs. A replay started at 3am produces the same world as one started at noon.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What breaks if you skip it:&lt;/strong&gt; subtle bugs that are unreproducible. “Sometimes the queen lays eggs at the wrong rate.” “Sometimes a worker behaves differently.” If wall-clock time is part of the simulation, every “sometimes” is unfixable without rebuilding from first principles.&lt;/p&gt;
&lt;p&gt;This rule is also what makes the game pause-able and step-able. You can call the tick function 100 times in a row in a test and the simulation can’t tell the difference between that and 5 seconds of real time.&lt;/p&gt;
&lt;h2&gt;6. Fixed-point integer math for all simulation quantities&lt;/h2&gt;
&lt;p&gt;Floats are banned in &lt;code&gt;src/sim/&lt;/code&gt;. Positions, velocities, food quantities, pheromone strengths — all integers, with an implicit scale factor (e.g., 1 unit = 1/256 of a tile). Floats are fine in the rendering layer for visual smoothness.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What this protects:&lt;/strong&gt; bit-identical results across platforms. IEEE 754 floating-point math is &lt;em&gt;almost&lt;/em&gt; deterministic across CPUs but not quite — different SIMD paths, different rounding modes, different math libraries on different OSes can produce off-by-one-ULP differences. Over a 10-minute simulation those differences accumulate into divergent worlds.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What breaks if you skip it:&lt;/strong&gt; my replay test passes on my Mac and fails on someone’s Linux box. The “deterministic” multiplayer desyncs after three minutes. The headless Node test runs different than the browser one.&lt;/p&gt;
&lt;p&gt;The cost is that I write &lt;code&gt;addFixed(x, y)&lt;/code&gt; instead of &lt;code&gt;x + y&lt;/code&gt; and the math is mildly less ergonomic. The benefit is that determinism is mechanically guaranteed instead of crossed-fingers hoped-for.&lt;/p&gt;
&lt;h2&gt;7. Snapshot saves with replay logging&lt;/h2&gt;
&lt;p&gt;Save files are JSON snapshots of the world state. In addition, every player input is logged alongside the seed, enabling deterministic replay for debugging and (later) multiplayer. Binary save format is deferred until JSON becomes a real bottleneck (likely never at our scale).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What this protects:&lt;/strong&gt; debugging power, replay-driven QA, and a foundation for multiplayer where the network layer just shuttles input logs around.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What breaks if you skip it:&lt;/strong&gt; debugging colony behavior is guesswork. “It happened around minute three” is the bug report; without replay you can’t reproduce it. With replay, you reload the seed plus input log and watch the same bug happen, every time.&lt;/p&gt;
&lt;p&gt;JSON also means saves are inspectable. I can grep them, diff two save states, and write tools against them without writing a binary parser first.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Why these are non-negotiable&lt;/h2&gt;
&lt;p&gt;Each of these rules is annoying at the moment you write the code. Fixed-point math is annoying. Banning &lt;code&gt;Date.now()&lt;/code&gt; is annoying. Refusing to import Phaser into &lt;code&gt;src/sim/&lt;/code&gt; is annoying. Each of them, individually, you could relax for “just this one case.”&lt;/p&gt;
&lt;p&gt;You can’t, though. Determinism is a system property, not a per-feature one. The day you let one float sneak into the sim, the property is gone, and you find out three months later when the multiplayer prototype desyncs and you can’t tell which subsystem to blame.&lt;/p&gt;
&lt;p&gt;The seven principles together are a contract. They’re enforced via lint rules, PR review (one human, two AI agents — that’s a &lt;a href=&quot;/experiment&quot;&gt;whole separate post&lt;/a&gt; about the dual-reviewer setup), and architectural tripwires that fail CI when they’re violated. The cost is paid up front, in friction. The benefit is paid out continuously, in things working the way you’d expect.&lt;/p&gt;
&lt;p&gt;If you’re building anything where determinism matters — a game, a simulation, a deterministic-replay debugger, a multiplayer system — the specific rules will look different than these, but the shape is the same. Pick the properties you can’t compromise on. Make them mechanically enforceable. Refuse to relax them when it’s convenient.&lt;/p&gt;
&lt;p&gt;That’s the whole thing.&lt;/p&gt;</content:encoded></item><item><title>Why Subterrans</title><link>https://subterrans.com/devlog/why-subterrans/</link><guid isPermaLink="true">https://subterrans.com/devlog/why-subterrans/</guid><description>What SimAnt was, what&apos;s missing in 2026, and what Subterrans actually commits to building.</description><pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;(Editor’s note, 2026-05-17: Phase numbering has been updated since this post was published. See the &lt;a href=&quot;/roadmap&quot;&gt;current roadmap&lt;/a&gt; for the active phase structure.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In 1991, Maxis released SimAnt. It put you in charge of a black ant colony in a suburban backyard, fighting a red colony for territory and eventually invading the house. You could zoom into a single ant and play it directly, or pull back and command the colony as a whole. Pheromone trails. Foraging. Larvae. A queen to protect.&lt;/p&gt;
&lt;p&gt;I played a lot of SimAnt. So did a lot of people. Then it disappeared into the shelf where Maxis kept the games that didn’t become franchises, and nothing has really filled that gap since.&lt;/p&gt;
&lt;p&gt;There are good ant-themed games — &lt;em&gt;Empires of the Undergrowth&lt;/em&gt; is excellent, &lt;em&gt;Ants Took My Eyeball&lt;/em&gt; is strange and great — but none of them are the SimAnt-shaped game. The thing where you’re managing a colony as a system, not commanding individual units, where pheromone fields propagate independently of the ants on them, where the enemy colony is running the same code you are.&lt;/p&gt;
&lt;p&gt;Subterrans is an attempt to build that game. It’s a spiritual successor, not a remake. Same shape, modern foundations.&lt;/p&gt;
&lt;h2&gt;What’s actually different&lt;/h2&gt;
&lt;p&gt;Three things, mostly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First, it runs in a browser.&lt;/strong&gt; SimAnt shipped on floppies. Subterrans Phase 1 is a single static page on subterrans.com. Native builds for iOS, Android, Steam, and desktop come later, but the web is the primary target. That’s a deliberate constraint — if I can’t get the loop fun in 2D inside a browser tab, I shouldn’t be building anything bigger.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second, the simulation is a first-class architectural object.&lt;/strong&gt; In SimAnt, the simulation and the rendering were inseparable — that was just how games were built in 1991. In Subterrans, &lt;code&gt;src/sim/&lt;/code&gt; is pure TypeScript. No DOM, no Phaser, no &lt;code&gt;Date.now()&lt;/code&gt;, no floats. Everything is integers, fixed timestep at 20 Hz, single seeded PRNG. The renderer reads state and draws it; the simulation doesn’t know the renderer exists.&lt;/p&gt;
&lt;p&gt;That’s not architectural showing-off. It’s the only way I get deterministic replay, future multiplayer, and AI agents that can actually reason about the code. More on that in &lt;a href=&quot;/devlog/the-seven-principles&quot;&gt;the seven principles post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Third, this is also an experiment in AI-agent development.&lt;/strong&gt; Most of the code is being written by Claude Code and Codex working from planning artifacts I write. The architectural rigor above isn’t separate from that — it’s how I get AI agents to produce code that doesn’t drift. Every PR gets reviewed by both agents independently. Lint rules enforce the architectural boundaries. The planning workflow is its own discipline.&lt;/p&gt;
&lt;p&gt;I’ll write more about that elsewhere. The short version: I want to know how much of a real game one person can ship if they treat AI agents as collaborators with very specific failure modes, and design the project around those failure modes rather than ignoring them.&lt;/p&gt;
&lt;h2&gt;What Phase 1 actually is&lt;/h2&gt;
&lt;p&gt;Phase 1 answers exactly one question: &lt;strong&gt;is the core colony management loop fun?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Not “is the art good.” Not “is there a tutorial.” Not “are there ten enemy types.” The minimum thing that’s recognizably &lt;em&gt;SimAnt-shaped&lt;/em&gt; is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your colony has a queen, workers, larvae, and food storage&lt;/li&gt;
&lt;li&gt;You designate tunnels to dig; workers dig them&lt;/li&gt;
&lt;li&gt;You assign chamber types (queen chamber, nursery, food storage); ants behave accordingly&lt;/li&gt;
&lt;li&gt;You toggle between surface view and underground cross-section&lt;/li&gt;
&lt;li&gt;Workers find food on the surface and bring it home&lt;/li&gt;
&lt;li&gt;Pheromone trails form and decay&lt;/li&gt;
&lt;li&gt;You set behavior priorities (forage / dig / defend / idle)&lt;/li&gt;
&lt;li&gt;One enemy AI colony runs the same simulation systems you do&lt;/li&gt;
&lt;li&gt;Win condition: kill the enemy queen. Lose condition: they kill yours.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s it. That’s the whole thing. A round takes 5 to 15 minutes. You play it, and either you want to play another round or you don’t.&lt;/p&gt;
&lt;p&gt;If the answer is “yes” — Phase 2 starts adding depth. Multiple castes. Real pixel art. Sound. The Yellow Ant, where you can drop into a single individual SimAnt-style. Multiple patches. A day/night cycle.&lt;/p&gt;
&lt;p&gt;If the answer is “no” — no amount of art, sound, tutorials, or content saves it. You go fix the core loop or you stop.&lt;/p&gt;
&lt;h2&gt;What I’m explicitly &lt;em&gt;not&lt;/em&gt; doing in Phase 1&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Pixel art. Phase 1 is programmer art — circles and rectangles. Real art begins after the loop is validated.&lt;/li&gt;
&lt;li&gt;Sound and music. Same reason.&lt;/li&gt;
&lt;li&gt;Tutorial system. There’s a one-page text explanation.&lt;/li&gt;
&lt;li&gt;Settings menus, save/load UI, main menu. The game boots straight into a hardcoded scenario and autosaves to localStorage.&lt;/li&gt;
&lt;li&gt;Multiple enemy types, multiple ant castes, weather, the Yellow Ant, the human invasion, mating flights. All deferred to later phases.&lt;/li&gt;
&lt;li&gt;Multiplayer. The architecture preserves the option (deterministic simulation, replay logging) but Phase 1 is single-player only.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these has a reason. The collective reason is the same: scope discipline. SimAnt was made by a bigger team than me, and they were doing it as their day job. I’m one person with AI collaborators. The way that math works is that I have to cut hard until the thing I’m building is small enough to be &lt;em&gt;good&lt;/em&gt;, then expand from there.&lt;/p&gt;
&lt;h2&gt;Where this is now&lt;/h2&gt;
&lt;p&gt;The site you’re reading just came online. The game itself has a working simulation skeleton — fixed timestep, deterministic RNG, the seven principles enforced via lint — and is currently mid-Phase 1 buildout. There’s no playable demo yet. There will be, behind a sign-up gate, and you’ll see it land in the &lt;a href=&quot;/roadmap&quot;&gt;roadmap&lt;/a&gt; when it does.&lt;/p&gt;
&lt;p&gt;I’ll keep posting here. No cadence promises — when something worth reading happens, you’ll see it.&lt;/p&gt;</content:encoded></item></channel></rss>