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
room.startGame({ is: isBlob, gs: { bal: { 0: 0, 5: 1, 7: 2 } } });
room.stopGame();| Option | What it is |
|---|---|
is | The initial physics state (an LZ-String string). |
gs | Game 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:
import { getFootballDefaultBlob, getGamemodeDefaultBlob } from 'bonktools';
getFootballDefaultBlob(2); // 1v1
getFootballDefaultBlob(4); // 2v2
getGamemodeDefaultBlob('football', 2); // by game modeFor maps that vary per room (classic, arrows, etc.), use the per-map cache.
Per-map cache
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:
BONK_USERNAME=your_user BONK_PASSWORD=your_password npx bonktools-capture-is https://bonk.io/123456abcdeIt 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 }.
// 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:
| Situation | Packet sent |
|---|---|
| Room in the lobby | INFORM_IN_LOBBY |
| Match in progress | INFORM_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.