Library

Matches, IS blob and spectators

How to start matches with the correct initial state and sync late joiners.

Starting a match on bonk.io takes more than pressing "start": the host sends the initial physics state (the IS blob) and the game settings. The library handles the sending, but you have to provide the right blob.

Start and stop

ts
room.startGame({ is: isBlob, gs: { bal: { 0: 0, 5: 1, 7: 2 } } });
room.stopGame();
OptionWhat it is
isThe initial physics state (an LZ-String string).
gsGame settings overrides (Partial<GameSettings>), such as bal.

The server echoes the blob unchanged to every client. An empty blob makes the physics engine fail to start: players don't appear on the field and the game seems stuck loading.

The IS blob

The IS blob encodes spawn positions, rotations and which bodies exist in the match. It depends on two factors:

  • The map. A blob captured on one map does not work on another.
  • The number of active players. The 1v1 blob (2 players) does not work for 2v2 (4 players).

Default blobs

For the default football map and a few modes, the library ships ready-made blobs:

ts
import { getFootballDefaultBlob, getGamemodeDefaultBlob } from 'bonktools';

getFootballDefaultBlob(2);              // 1v1
getFootballDefaultBlob(4);              // 2v2
getGamemodeDefaultBlob('football', 2);  // by game mode

For maps that vary per room (classic, arrows, etc.), use the per-map cache.

Per-map cache

ts
import { MapBlobCache } from 'bonktools';

const cache = new MapBlobCache('./map-blob-cache.json');

const blob = cache.getForMap(room.currentMap, 4);   // this map's blob for 4 players
if (!blob) { /* capture um (veja abaixo) */ }
cache.setForMap(room.currentMap, 4, capturedBlob);

Capturing a blob

The reliable way is to capture it from a real session. The library includes a utility:

bash
BONK_USERNAME=your_user BONK_PASSWORD=your_password npx bonktools-capture-is https://bonk.io/123456abcde

It joins the room as a spectator and waits. When a real host starts a match in the browser, it prints the blob in the format BONK_INITIAL_STATE=..., ready to paste into your .env or the per-map cache. Capture one blob for each number of active players you plan to use.

The bal field

bal says which body each player controls, in the form { playerId: bodyIndex }.

ts
// bot (id 0) without a body; players 5 and 7 with bodies 1 and 2
room.startGame({ is, gs: { bal: { 0: 0, 5: 1, 7: 2 } } });

Always use the explicit mapping. With bal: [], bodies are assigned in ascending id order, and if players joined and left the ids have gaps: a blue-team player can spawn on the red side.

Players who join while a match is running

When a player joins, the host must hand them the initial data. There are two possible packets, and the client only accepts one:

SituationPacket sent
Room in the lobbyINFORM_IN_LOBBY
Match in progressINFORM_IN_GAME (carries the match state)

The library chooses on its own: if a match was started with startGame({ is }) and is still active, the new player receives INFORM_IN_GAME instead of INFORM_IN_LOBBY, and sees the match without restarting it for the others.

If your bot restarts the match right after someone joins (as the 24/7 room does to complete teams), everything works: the player receives the right packet and then the start of the new match.