Gameplay messages carry gameType and tableId in the envelope and game-specific data in the payload. The core protocol routes these messages without inspecting the payload.
Turn-Based Messages
Used by sequential games (poker, blackjack) where one player acts at a time.
| Type | Direction | Description |
|---|
game_action_request | S → C (unicast) | It’s your turn; includes state + available actions |
submit_action | C → S | Client’s chosen action |
game_state_update | S → All | Game state changed (new phase, cards, etc.) |
player_action_broadcast | S → All | Another player’s action + resulting state |
round_result | S → All | Round ended; winners, amounts, rake, fairness proof |
game_error | S → C | Invalid action or game-level error |
game_action_request
Direction: Server → Client (unicast)
Sent when it’s this player’s turn. The client MUST respond with submit_action within timeoutSeconds.
{
type: "game_action_request",
gameType: string,
tableId: string,
messageId: string,
protocolVersion: "1.0",
timeoutSeconds: number, // Client MUST respond within this window
timestamp: number,
payload: {
// Game-specific state visible to this player
// + explicit availableActions array
}
}
Every game_action_request payload MUST include an availableActions field. This is an explicit enumeration of the valid actions the client may take in the current state, including the action type and any required/optional parameters.
// Example from Texas Hold'em
payload: {
holeCards: [...],
gameState: {...},
availableActions: [
{ type: "fold" },
{ type: "check" },
{ type: "call", callAmount: 50 },
{ type: "raise", minAmount: 100, maxAmount: 1000 },
{ type: "all_in" }
]
}
timeoutSeconds is enforced by the server’s clock. The client’s local clock is informational only. Budget at least 20% of the timeout for network round-trip.
If the client does not respond within timeoutSeconds, the server applies the default timeout action defined in the game specification (e.g., fold in poker, stand in blackjack).
submit_action
Direction: Client → Server
{
type: "submit_action",
gameType: string,
tableId: string,
messageId: string,
protocolVersion: "1.0",
timestamp: number,
payload: {
action: string, // Action type (e.g., "fold", "hit", "place_bet")
// ... action-specific parameters defined in game spec
}
}
Clients MUST NOT send submit_action for a turn-based game except in response to game_action_request. Servers MUST reject unsolicited actions with NOT_YOUR_TURN.
game_state_update
Direction: Server → All clients at the table
Broadcast when game state changes (cards dealt, community cards revealed, new phase, etc.).
{
type: "game_state_update",
gameType: string,
tableId: string,
payload: object, // Game-specific state
messageId: string,
timestamp: number
}
player_action_broadcast
Direction: Server → All clients at the table
Broadcast after a player’s action is processed.
{
type: "player_action_broadcast",
gameType: string,
tableId: string,
payload: {
playerId: string, // Address of acting player
action: string,
amount?: number,
resultingState: object // Updated game state
},
messageId: string,
timestamp: number
}
round_result
Direction: Server → All clients at the table
{
type: "round_result",
gameType: string,
tableId: string,
messageId: string,
timestamp: number,
payload: {
winners: [{
playerId: string,
grossAmount: number,
rake: number,
netAmount: number
}],
totalRake: number,
fairnessProof?: { // Present if server supports provably fair
serverSeed: string,
algorithm: string
}
// + game-specific result data
}
}
game_error
Direction: Server → Client
{
type: "game_error",
gameType: string,
tableId: string,
code: string,
message: string,
relatedMessageId?: string,
messageId: string,
timestamp: number
}
Phase-Based Messages
Used by simultaneous-action games (roulette, baccarat) where all players act within a time window.
| Type | Direction | Description |
|---|
betting_window_open | S → All | Betting phase started; includes available bets + time limit |
betting_window_closed | S → All | No more bets accepted |
submit_action | C → S | Same as turn-based (client places bet within window) |
Phase-based games reuse game_state_update, round_result, and game_error from the turn-based set.
betting_window_open
Direction: Server → All clients
{
type: "betting_window_open",
gameType: string,
tableId: string,
messageId: string,
protocolVersion: "1.0",
timeoutSeconds: number, // Window duration
timestamp: number,
payload: {
// Game-specific: available bets, limits, previous results
// MUST include availableActions field with valid bet types
}
}
Clients MAY submit multiple submit_action messages during an open window (e.g., placing multiple roulette bets). Servers MUST reject actions received after betting_window_closed.
betting_window_closed
Direction: Server → All clients
{
type: "betting_window_closed",
gameType: string,
tableId: string,
messageId: string,
timestamp: number
}
After the window closes, the server resolves the round and broadcasts round_result.
Client State Machine
For turn-based games:
IDLE → (receive game_action_request) → DECIDING → (send submit_action) → IDLE
For phase-based games:
IDLE → (receive betting_window_open) → BETTING → (send submit_action[s]) → BETTING
BETTING → (receive betting_window_closed) → IDLE
Multi-Table Play
When playing multiple concurrent games (requires capabilities.multiTable: true):
- Each incoming request includes
gameType and tableId
- The client correlates the request to the correct game context
- The client’s response MUST include matching
gameType and tableId
- Independent state machines are maintained per table
- Timeouts on one table are independent of actions on another