Chickens Must Die.Technical docs / Protocol

Authoritative Multiplayer Server Starter

Godot ↔ Server Protocol

The WebSocket contract between the Godot client and the Colyseus server: connection options, validated messages, synchronized state, and the compatibility rules to follow when extending the game.

Contract stability

Transport uses Colyseus over WebSocket and the my_room room. The Godot client never sends an authoritative position. Changing message names, field types, or the state schema is a compatibility change and must be coordinated with the client.

Core rule: The client sends input. The server determines authoritative positions and gameplay state.

Connection & join options

Authentication requires the following options:

{
  "nickname": "Player One"
}

When joining, the client may additionally provide a visual variant:

{
  "nickname": "Player One",
  "playerColor": "chicken_2"
}

Allowed player colors

Value Variant
chicken_1 White / default
chicken_2 Black
chicken_3 Dark brown
chicken_4 Light brown

Possible authentication rejection codes

nickname_requirednickname_too_shortnickname_too_longnickname_invalid_charactersnickname_takenrate_limited

Client → server

move

{
  "seq": 42,
  "clientTime": 1789051200123,
  "input": {
    "left": false,
    "right": true,
    "up": false,
    "down": false
  }
}

Validation rules:

  • seq: a safe, non-negative integer; begin with 1.
  • clientTime: a non-negative number.
  • All four direction flags are required and must be booleans.
  • Additional fields at either the message level or within input are rejected.
  • A message that exceeds the rate limit is ignored.
  • The server retains at most 60 recent inputs per player.

clientTime can be used by the client for diagnostics or prediction, but the server does not use it to calculate position.

chat_send

{
  "text": "Hello!"
}

The payload must contain a string. For normalization and length rules, see the Gameplay guide.

Server → client

game_config

Sent after joining:

{
  "playerCollider": {
    "type": "circle",
    "radius": 5
  },
  "playerGrowth": {
    "initialMass": 5,
    "colliderScaleExponent": 0.5
  },
  "seeds": {
    "radius": 2
  }
}

For a rectangular collider, playerCollider contains type, width, and height. The message includes neither server secrets nor player position.

tick_sync

The first message after joining may contain only the tick:

{
  "serverTick": 12
}

Later broadcasts also include the server time:

{
  "serverTick": 72,
  "serverTime": 1789051200123
}

chat_message

{
  "id": "f1bce155-bf16-49c8-8e6d-a75ea14a5bf4",
  "playerId": "colyseus-session-id",
  "nickname": "Player One",
  "text": "Hello!",
  "sentAt": 1789051200123
}

chat_error

{
  "code": "invalid_message"
}

code is either invalid_message or rate_limited.

player_eaten

Sent separately to the predator and the prey:

{
  "predatorSessionId": "predator-id",
  "preySessionId": "prey-id",
  "gainedMass": 8
}

gainedMass is the mass actually transferred from the prey.

Synchronized schema state

The root state contains two maps:

players: MapSchema<Player>
seeds:   MapSchema<Seed>

Player

Field Type Meaning
nickname string Normalized nickname
x, y number Authoritative center of the collider
left/right/up/down boolean Last processed input
mass number Current mass
lastProcessedSeq number Input ACK used for reconciliation
playerColor string Visual variant

Seed

Seed contains x and y. Keys follow the seed-N format within each room.

Recommendations for the Godot client

  1. Send a full snapshot of all four direction flags, not just the key that changed.
  2. Increase seq monotonically, starting at 1.
  3. Treat x, y, mass, and respawn in the schema state as authoritative.
  4. Reconcile local prediction against lastProcessedSeq and server state.
  5. Do not add x or y to a move payload: the strict schema rejects it.
  6. Handle Colyseus reconnection and rebuild local state after reconnecting.
  7. Do not assume that every input will be accepted; the rate limiter may drop it.

Adding messages without breaking the client

  • Add the name to the appropriate enum in MessageTypes.ts.
  • Add a Zod schema and payload type.
  • Create a handler and register it in RoomRuntimeBuilder.
  • Add tests for valid, invalid, and excessive payloads.
  • Only then update the Godot client.
  • Do not rename existing messages or fields without versioning the protocol.
Compatibility reminder: A change to a message name, payload, or synchronized schema can require a coordinated client release.

↑ Back to top