UnderTheC

0. Overview

A warning, the dragons game, why it reminded us of NYPC, and a map of this postmortem series.

Warning. The entire project behind these posts was vibe coded. LLM agents wrote almost all of the code: the bots, the simulators, the league, the arena, the scalers and the scripts. The posts are vibe worded too. An LLM drafted every one of them from our repositories and notes, and a human read and corrected each one before it went up. Where a number appears, it came from a commit, a log or a database; where we didn’t record something, we say so. Dates and times are UTC+9 unless marked Z (UTC).

The game

We are UnderTheC (syeyoung, bu-gye-jeong, equinox and chansolpark7), and we entered UNSW Battlecode 2026. The game ran on the competition server at game.battlecode.au, with the official toolkit shipped as the unswbc pip package.

Each team controls dragons, snake-like chains of segments, on a torus: walk off the right edge and you come back on the left. Boards on the contest maps were 11 to 64 tiles a side. Between tiles there can be kelp, a wall that kills any dragon that steps into it, or a portal, an edge paired with another edge somewhere else on the board that you step through and come out of still heading the same way.

A dragon crossing the east borderA dragon going through a portal into kelp
Off the east edge and back on the west, then through a portal and straight into kelp. Animations from the unswbc docs.

Some tiles are pearl beds. A bed grows a pearl after a random countdown; a dragon that eats one grows by a segment. A dragon that dies drops a pearl on every second segment of its body, so fights feed whoever cleans up. A dragon dies by stepping into kelp, into its own body, or into another dragon’s body. Two heads meeting kill both.

A dragon eating a pearlA dead dragon dropping pearls
A pearl adds a segment, and a dead dragon leaves a pearl on every second segment. Animations from the unswbc docs.

A turn is one command. MOVE takes one or more steps. Since rules 1.2.3, a dragon of length L gets its first ceil(L/4) steps free and pays a segment for each step after that, so a sprint burns length. SPLIT n cuts the last n segments off as a new dragon, reversed, so the old tail becomes the new head. A team may have up to 64 dragons alive. Dragons act one at a time in id order within a round, and later movers see what earlier ones did.

A dragon sprintingA dragon splitting
A sprint (north, north and east in one turn), and a length-6 dragon splitting: the child leaves head first from the old tail. Animations from the unswbc docs.

Every dragon is its own process. The judge forks one per dragon, memory is private, and a child created by a split starts as a brand-new process with no memory of its parent. Each dragon sees only a 7x7 window around its head (it can see through kelp). The only way to talk to teammates is sonar: a 64-bit word sent in a straight line, which is delivered to the first living dragon it reaches. That can be an enemy, or the sender itself. A dragon can ping once in each direction per turn. In the judge’s sandbox each turn gets a budget of 100M wasm CPU points.

A dragon and its vision windowSonar intercepted by an enemy
A dragon’s 7x7 vision as it moves across a portal and the border, and sonar stopping at the first body it meets: an enemy in the way hears everything after. Animations from the unswbc docs.

A match lasts at most 500 rounds. A team with no dragons left loses. The rules changed on 2026-10-01 (unswbc 1.2.3, “Slay Queen”): dragons 0 and 1 became their teams’ queens, and a queen stays the queen through splits because the parent keeps its id. At round 500 the longer queen wins, and a dead queen counts as length 0. Ties fall back to the longest living dragon, then total length. Before that change the longest dragon decided round 500, and every bot we had was built for that.

The full rules, as the official engine implements them, are in docs/game.md.

It looked like NYPC

Our first impression was that we had seen this kind of contest before. NYPC is Nexon’s programming competition, and its Master Track gives “AI battle-type problems”: each team writes code that plays a given game, and the bots play other teams’ bots. Battlecode is the same shape. So we started from what we knew about NYPC.

Two things stood out. The NYPC winner had used reinforcement learning, so RL became our first plan. And NYPC’s rules, which otherwise let contestants use LLMs freely, prohibit “calling LLMs en masse in an automated manner or using multiple LLMs to screen code”. If a competition bothers to ban that, it probably works. We also didn’t want to spend much of our own time on the contest, so after RL we wrote a loop that generated bots with LLM agents and let it run: mass code generation.

The series

  1. This post.
  2. RL: the first plan, reinforcement learning, and why a scripted bot ended it.
  3. Mass code generation: generating many bot variants with LLM agents and selecting, instead of training one bot.
  4. The arena and vast.ai workers: tokenz-arena, our team arena, and the machines that ran it.
  5. Metrics engine and probability analysis: reading a game from its replay before it ends.
  6. The LLM loop up close: orchestrator, strategists, analysts and workers, and the rules we added as they went wrong.
  7. What the factory made: the bot lines and which ideas moved the server rating.
  8. Postmortem and results: the ladder, the sprint tournament, and what we’d do differently.