Developer guide

BOT
API

Build your own independently controlled Babo. External bots use the regular game protocol, identify their software during connection, and remain fully responsible for sensing, decisions, movement, combat, and respawning.

01

Connect and identify

Open a binary WebSocket to the clean bot endpoint. Identity travels inside the initial binary handshake, never in the URL.

wss://nc-ctf.baboviolent.net/bot/ws

Handshake variables

nameBot implementation or agent name. 1 to 64 UTF-8 bytes.
versionYour release or model version. 1 to 32 UTF-8 bytes.
authorPerson, team, or organization responsible. 1 to 64 UTF-8 bytes.
playerNameIndependent in-game name, up to 96 input bytes before server normalization. Supports ^1 through ^9 colors and Babo Alt-code glyphs.

Server guarantees

The server publishes implementation identity under scoreboard[].botIdentity. It normalizes the independent player name to the native 31-byte field and always appends exactly one [bot].

02

Frame format

Every WebSocket message contains one or more BaboNet frames. Buffer partial input and drain complete frames in order.

uint16 payloadSizeLittle-endian payload byte count.
uint16 typeIdLittle-endian message identifier.
byte[payloadSize]Packet-specific payload.
function encodeFrame(typeId, payload = new Uint8Array()) {
  const frame = new Uint8Array(4 + payload.length);
  const view = new DataView(frame.buffer);
  view.setUint16(0, payload.length, true);
  view.setUint16(2, typeId, true);
  frame.set(payload, 4);
  return frame;
}
03

Handshake and lifecycle

Do not send gameplay packets before authentication. The complete connection sequence is:

  1. Receive SVCL_NEWPLAYER (101). Its first payload byte is your assigned player ID.
  2. Receive SVCL_GAMEVERSION (116).
  3. Within 10 seconds, send CLSV_GAMEVERSION_ACCEPTED (4). Keep the native player ID and 16-byte password prefix, then append ASCII BOT1.
  4. After BOT1, send implementation name, version, author, and player name in that order. Each field is one unsigned length byte followed by UTF-8 bytes.
  5. Receive server info, round state, flag state, projectiles, and player snapshots.
  6. Send CLSV_SVCL_TEAM_REQUEST (203) with player ID and team: 255 spectator, 0 blue, or 1 red.
  7. Send CLSV_SPAWN_REQUEST (2) with player ID, weapon, melee, skin, and decal colors.
  8. Answer every SVCL_PING (106) with CLSV_PONG (1). A missed pong disconnects the bot.
04

State to observe

A useful bot keeps an authoritative world model and updates it from server packets. Never infer a successful action only from what you sent.

Players and match

102 SERVER_INFOMap, game type, and team score.
105 PLAYER_ENUM_STATERoster, teams, status, loadout, stats, and life.
108 PLAYER_SPAWNAuthoritative spawn and loadout.
117 SYNCHRONIZE_TIMERGame and round clocks.
121 GAME_STATERound lifecycle changes.
128 AUTOBALANCEWarning or authoritative team move.

World and objectives

204 COORD_FRAMEPlayer ID, frame number, position, velocity, aim, and connection ID.
110 PLAYER_SHOOTAccepted server shot.
112 PROJECTILE_COORD_FRAMEAuthoritative projectile movement.
114 PLAYER_HITAccepted damage event.
118 CHANGE_FLAG_STATETake, return, or capture transition.
119 DROP_FLAGFlag dropped at a position.
120 FLAG_ENUMFull flag snapshot and revision.
123 MAP_CHANGEDiscard old world state and load the new map.
05

Actions and limits

Movement and actions use the same packets as a human client. The server remains authoritative and may reject invalid, stale, occluded, too-fast, unbalanced, or unavailable actions.

204 COORD_FRAMESend current frame ID, position, velocity, and aim. The gateway accepts about 60 movement updates per second with a burst allowance of 12, coalesces excess updates, and echoes collision-corrected frames to external bots for authoritative navigation.
3 PLAYER_SHOOTRequest a hitscan shot using the equipped weapon.
206 PLAYER_PROJECTILERequest rocket, grenade, Molotov, or other supported projectile creation.
207 PLAYER_SHOOT_MELEERequest melee or secondary use.
5 PICKUP_REQUESTRequest a nearby item pickup. The server also handles authoritative pickups.
202 CHATSend chat sparingly. Application requests are rate limited.

External bots cannot create votes or cast votes. They occupy a player slot and replace one built-in server bot when the configured roster is full.

06

Minimal JavaScript connection

This starter sends implementation identity and an independent colored player name in the binary handshake.

const bot = { name: "ExampleBot", version: "0.1.0", author: "Your Name", playerName: "^4Ruby ^1Runner" };
const ws = new WebSocket("wss://nc-ctf.baboviolent.net/bot/ws");
ws.binaryType = "arraybuffer";

let playerId;
ws.onmessage = ({ data }) => {
  const bytes = new Uint8Array(data);
  for (let offset = 0; offset + 4 <= bytes.length;) {
    const view = new DataView(bytes.buffer, bytes.byteOffset + offset);
    const size = view.getUint16(0, true);
    const type = view.getUint16(2, true);
    if (offset + 4 + size > bytes.length) throw new Error("buffer partial frames");
    const payload = bytes.subarray(offset + 4, offset + 4 + size);
    if (type === 101) playerId = payload[0];
    if (type === 116 && playerId !== undefined) {
      const fields = [bot.name, bot.version, bot.author, bot.playerName].map(value => new TextEncoder().encode(value));
      const accepted = new Uint8Array(21 + fields.reduce((sum, field) => sum + 1 + field.length, 0));
      accepted[0] = playerId;
      accepted.set(new TextEncoder().encode("BOT1"), 17);
      let cursor = 21;
      for (const field of fields) { accepted[cursor++] = field.length; accepted.set(field, cursor); cursor += field.length; }
      ws.send(encodeFrame(4, accepted));
    }
    console.log({ type, payload });
    offset += 4 + size;
  }
};
Build responsibly

Make a Babo with a point of view.

Give your bot a real identity, respect server limits, and keep its behavior fun for the humans and other bots sharing the arena.