Rules
The game is a deterministic, fixed-step simulation: the same world and the same actions always
give the same result, bit for bit. There is no frame time. Everything happens in ticks, 10 per
second of game time (TICK_RATE). Rooms without spectators run faster than real time (SPEC §4);
a room runs in real time while someone watches it, like the showcase room on the home page (for at
most 10 minutes in a row; then another room takes over, and the first one rests for 20).
All numbers below are the defaults in
packages/engine/src/config.ts. Your bot should read the ones
it is given (info and state, see Bot API) instead of hard-coding them.
Sources: config.ts,
step.ts, growth.ts,
world.ts, food.ts,
math.ts, vision.ts.
Arena
- A square centred on (0, 0):
xandyboth lie in [−89, 89] (arenaHalfSize89, side 178, about the area of a disc of radius 100).xgrows east (right),ygrows north (up). - Angles are radians, counter-clockwise from +x:
0is east,π/2north. Headings are kept in (−π, π]. - A new snake spawns at a uniformly random spot of the centred square [−71.2, 71.2]²
(
arenaHalfSize × 0.8), trying up to 30 spots for one at least 10 units from every other snake's body, then taking the last one tried. It faces the centre, with its body laid out straight behind it.
Mass, size and body
A snake's mass (called length in the bot state) drives everything else:
| Quantity | Formula | At start (mass 10) |
|---|---|---|
| size (segment gap) | 1 + 4 × mass / 10000, capped at 5 |
1.004 |
| body radius | size / 2 |
0.502 |
| segments (with head) | floor(mass × 0.1 / size + 5), at least 1 |
5 |
- Size reaches its cap of 5 (radius 2.5) at mass 10 000.
- The body is a chain of points
sizeapart along the path the head travelled, head first. A growing snake unfolds from the tail. - Mass never drops below 0 (
minMass). A snake at mass 0 does not die of it; it just stays tiny.
Movement and turning
Every tick, in this order, each snake:
- Turns. The heading changes by
−turn × turnRate, withturnRate= 0.25 rad per tick (about 14°).turnis your action's value in [−1, 1]: negative turns left (counter-clockwise), positive turns right (clockwise). - Moves forward along the new heading by
speed: 1.5 units per tick, 3 while boosting.
So a snake can't stop or reverse. At full turn it describes a circle of radius
speed / turnRate = 6 units (12 while boosting), and a U-turn takes about 13 ticks.
Turn quantization (1/128)
The engine rounds turn to the nearest multiple of 1/128 before applying it, so replays can
store each turn in about a byte (quantizeTurn and TURN_STEPS in
step.ts):
- values are first clamped to [−1, 1];
NaNand non-numbers count as0; - then
Math.round(turn × 128) / 128, so0.3becomes0.296875. Very small turns go straight: anything above −1/256 and below +1/256 becomes0.Math.roundrounds halves up, so exactly +1/256 becomes +1/128 while −1/256 becomes0; - 0.5, 0.25 and other multiples of 1/128 stay exact.
Your bot sees the result only through its heading on the next tick. If you simulate your own movement ahead of time, quantize the same way or you will drift slightly.
Boost
- An action with
boost: trueboosts this tick if the snake's mass is at least 5 (boostMinMass). Below that, the request is ignored. - A boosted tick moves at double speed and burns 1 mass (
boostCost, never below 0). - All the burned mass is dropped as a food pellet at the tail (
boostDropRatio1), unless the arena already holds 3000 pellets.
Hunger
After moving (and boosting), every snake loses 0.002 + 0.0003 × mass mass per tick
(hungerFlat, hungerRate). At mass 100 that is 0.032 per tick; at mass 1000, about 0.3. Big
snakes have to keep eating to stay big, so circling in a corner doesn't pay.
Food
| Kind | Value | Pellet radius | Where it appears |
|---|---|---|---|
ambient |
1 | 0.5 | uniformly at random in [−87.2, 87.2]² |
boost |
the mass burned (1) | 0.5 | at a boosting snake's tail |
remains |
about 10 per pellet | 1.5 | along the body of a snake that died |
- Ambient food spawns, at most 4 pellets per tick, while the arena holds fewer than
150 + 40 × living snakespellets (at most 3000). Pellets of every kind count toward that target, so remains and boost drops hold ambient spawning back. A new world starts with 150. - Eating: a pellet is eaten when its distance from your head is less than
size × 3 +its radius and it lies in a forward cone within 60° of your heading (cos > 0.5). Food beside or behind your head is not eaten until you turn toward it. - A pellet two snakes could eat goes to the nearer head (the snake that spawned earlier on an exact tie).
- Eating happens after collisions, so a snake that dies this tick eats nothing.
- Bots only see the pellet's position and
value, not its kind. Remains are eaten from farther away (their radius is 1.5). Ambient and boost pellets are always worth 1 (boost needs mass 5, so it always burns a full 1), so avalueother than 1 means remains; remains worth exactly 1 are rare but possible.
Collisions and death
Collisions are resolved simultaneously, on the positions after every snake has moved, and continuously along each head's path during the tick. For each snake, the earliest thing its head touches during its move is what kills it.
- Body. You die when your head circle touches a body point of another snake (any segment after
its head), i.e. when the centre distance is at most
your radius + its radius. The kill goes to that snake. - Head-on. When two heads touch, the snake with less mass dies and the kill goes to the other. With equal mass both die, and each is credited with killing the other. A head-on contact between two snakes also cancels any body contact between the same two that tick, so the bigger snake doesn't die by brushing the smaller one's body as it eats it. Masses are compared after this tick's boost and hunger, so boosting into a head-on against an equal snake loses it.
- Wall. You die when your head point ends a tick outside the square, i.e. when
max(|x|, |y|) > 89andstate.wallDistancewould drop below 0. Your body radius doesn't count here. Corners are the dangerous spots: heading into one, a snake has two walls to turn away from. - Your own body never kills you.
- A snake that dies this tick still kills others: its body and head stay in place for this tick's collisions.
Causes of death
| Cause | What happened |
|---|---|
collision |
head touched another snake's body, or lost (or tied) a head-on |
wall |
head crossed the arena edge |
timeout |
the bot went over its per-tick budget 3 ticks in a row, or over its load budget (see Limits) |
error |
the bot threw 3 ticks in a row, failed to load (rejected source, an exception, top-level await), or ran out of memory |
retired |
the life reached the life cap (not a failure: the score is kept) |
timeout and error are decided by the sandbox before the tick is simulated: the snake dies
where it stands without moving.
When a snake dies for any reason, all its mass is dropped as remains pellets spread along its
body: ceil(mass / 10) pellets of equal value, but never more pellets than the body has segments,
each moved by up to half a segment gap in x and y (and kept within 87.2 of the centre on both
axes, so remains of a snake that died at the wall can still be eaten). So pellets are worth at most 10 for small snakes
and more for big ones (above mass ~380 the segment cap takes over: mass 1000 drops 76 pellets of
about 13).
Life cap
A life retires after 6000 ticks alive (lifeCap), 10 minutes of game time. The bot is
told this as info.maxLifeTicks. Retiring is the best possible end of a life: nothing is lost.
Score
A life's score is
score = 0.001 × (sum of your mass over every tick you were alive) + 50 × kills
(massScoreFactor 0.001, killBonus 50; lifeScore in
growth.ts). It is the area under your mass curve plus a
bonus per kill:
- staying at mass 100 for 1000 ticks earns 100 points, the same as 2 kills;
- a full life (6000 ticks) at mass 100 earns 600;
- dying early ends the sum, so survival matters as much as size.
Only the score of each life counts. Your rating is the average over many lives.
Order of one tick
From the comment on step() in step.ts:
- Sandbox faults (
timeout/error) kill their snakes where they stand. - Every other snake turns, boosts (burning mass and dropping food at its tail), moves, pays hunger and re-lays its body.
- Collisions are resolved on the new positions; the earliest contact of each head wins.
- Survivors eat food in their forward cone.
- Survivors age and score; those at the life cap retire.
- The dead are credited to their killers and drop remains; ambient food tops up.
Your bot is called before each tick with what it sees at that moment (see Bot API), and its action is applied in step 2.
Rooms
A room holds up to 20 snakes, at most one per author, and at least 5 different authors: house bots fill the gap when there aren't enough real bots. When a snake dies, the room takes the next life from the queue. This is the design in SPEC.md §4.
The queue takes turns: each bot has one life at a time, and when it ends the bot's next life goes
to the back of the queue, so every bot plays before any plays again. A bot whose author is already
in a room keeps its place for the next room with a free slot. New rooms open when lives pile up in
the queue; a room closes when only house bots are left in it and the queue is empty. A bot plays
up to 100 lives per UTC day (see Limits); after that it
waits for the next day. Rolling back, archiving, a ban or a quarantine take effect within seconds,
and lives of a version that can no longer play are retired (retired, not rated).