Files
Wizard/docs/05-protocol.md
marvinandClaude Opus 5.5 9ffee8b952 Document limits, load control and performance
- New docs/09-limits-and-performance.md: principles, all protection
  layers, what players see, configuration reference, measurements,
  tuning guide, monitoring and how to test
- Server chapter summarises the limits and links to chapter 9; circuit
  breaker description and AI_MAX_INFLIGHT default corrected
- Architecture: new files in the layout, design decisions for load
  handling; docs index lists chapter 9
- Protocol, AI and operations chapters link to chapter 9; test counts
- Root and component READMEs describe the limits and settings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:04:21 +02:00

8.2 KiB
Raw Permalink Blame History

5. Protocol reference

All messages are JSON objects with a type field, sent as websocket text frames. There are two protocols:

  • Client protocol — browser ↔ server on /ws (public URL wss://game.marvinkrausser.com/wizard/api/ws). Schema: wizard_server/app/protocol.py (Python) and wizard_website/src/api/types.ts (TypeScript). Keep both in sync.
  • AI protocol — AI service ↔ server on /internal/ai. Implemented in wizard_server/app/ai_bridge.py and wizard_ai/wizard_ai/service/client.py.

Game objects inside messages (cards, actions, observations, events) use the engine's JSON format described in the engine chapter.


Client protocol

Browser → server

type Fields Allowed when Effect
hello token: string | null always; must be the first message starts or resumes a session
create_lobby name after hello creates a lobby and joins it as host
join_lobby code, name after hello joins an existing lobby
leave_lobby – in a lobby leaves (during a game an AI takes the seat)
add_ai – host, lobby waiting, < 6 seats adds an AI seat
remove_player player_id host, lobby waiting removes a seat (human or AI)
set_options hidden_bids: bool host, lobby waiting changes game options
start_game – host, lobby waiting, 3–6 seats starts the game
action action: {"type":"bid","value":n} or {"type":"play","card":id} your turn in a running game performs a move
return_to_lobby – host, game finished back to the waiting lobby

Validation (protocol.py):

  • name: whitespace stripped, 1–20 characters.
  • code: whitespace stripped, upper-cased, 4–12 characters.
  • Unknown type or wrong field types → error message, connection stays open.

Server → browser

welcome

Sent after every hello.

{"type": "welcome", "player_id": "3f9c0a1b2d4e", "token": "Zq…", "name": "Marvin"}

The browser stores token (in sessionStorage) and sends it in the next hello to resume the same session. player_id identifies you in lobby.players and lobby.host_id.

state

Sent after hello and whenever anything in your lobby changes. It always contains the complete current state.

{
  "type": "state",
  "lobby": {
    "code": "7C7TAJ",
    "status": "playing",
    "host_id": "3f9c0a1b2d4e",
    "options": {"hidden_bids": false},
    "players": [
      {"id": "3f9c0a1b2d4e", "name": "Marvin", "is_ai": false, "connected": true},
      {"id": "ai-1a2b3c4d", "name": "Merlin", "is_ai": true, "connected": true},
      {"id": "ai-5e6f7a8b", "name": "Morgana", "is_ai": true, "connected": true}
    ],
    "min_players": 3,
    "max_players": 6,
    "ai_available": true
  },
  "game": {
    "you": 0,
    "observation": { "…": "the personal Observation of seat 0" }
  },
  "events": [
    {"type": "trick_completed", "trick": {"…": "…"}}
  ]
}
  • lobby: null, game: null → you are not in a lobby (start page).
  • game: null → lobby is waiting (no game running).
  • players[i] is seat i in the game (observation uses the same indices).
  • events lists what the last action caused: trick_completed, round_completed, round_started, game_finished. Usually empty.
  • ai_available is false when no AI service is connected; AI seats then play random moves.

server_status

Sent right after welcome and to every connected client whenever the server's load level or capacity changes.

{"type": "server_status", "load": "overloaded", "accepting_games": false}
  • load: normal, busy (informational) or overloaded.
  • accepting_games: false while the server refuses create_lobby and start_game (overloaded, or MAX_ACTIVE_GAMES games running). Everything else, including running games, keeps working.

error

{"type": "error", "message": "Only the host can do that"}
{"type": "error", "message": "The server is busy right now, …", "code": "busy"}

code is present for errors the client may react to:

code Meaning
busy no capacity: connection limit reached (followed by close 1013), or create_lobby / start_game refused because the server does not accept new games
rate_limited too many messages on this connection, or too many lobbies created from this IP

Sent only to the client that caused it. Typical messages: Send 'hello' first, Invalid message: … (followed by the pydantic reason), Lobby ABC123 does not exist, The game in this lobby has already started, This lobby is full, Only the host can do that, Wizard needs 3 to 6 players, Illegal action Bid(value=2) for player 0, It is not player 1's turn, You were removed from the lobby, This session was opened in another window, Too many messages, slow down., You are creating lobbies too quickly, please wait a moment.

Close codes

Code Meaning Frontend reaction
(handshake rejected) origin not in ALLOWED_ORIGINS; the server closes with 1008 before accepting, so the browser sees a failed connection reconnects with backoff
1008 message flood (too many dropped messages) reconnects with backoff
1009 message larger than 64 KB (uvicorn --ws-max-size) reconnects with backoff
1013 "Try Again Later": connection limit reached (preceded by an error with code busy) banner "The server is full", retries slowly (15 s, 30 s, 60 s …)
4000 the session was opened in another tab stops reconnecting, shows a banner
other network problem, server restart reconnects (1 s, 2 s, 4 s … max 10 s)

Example conversation

sequenceDiagram
    participant H as Host browser
    participant S as Server
    participant G as Guest browser
    H->>S: hello {token:null}
    S-->>H: welcome, state {lobby:null}
    H->>S: create_lobby {name:"Ann"}
    S-->>H: state {lobby: code "K7M2QX", players:[Ann]}
    G->>S: hello {token:null}
    S-->>G: welcome, state {lobby:null}
    G->>S: join_lobby {code:"k7m2qx", name:"Ben"}
    S-->>H: state {players:[Ann, Ben]}
    S-->>G: state {players:[Ann, Ben]}
    H->>S: add_ai
    S-->>H: state {players:[Ann, Ben, Merlin]}
    S-->>G: state {…}
    H->>S: start_game
    S-->>H: state {game:{you:0, observation{phase:"bidding", current_player:1}}}
    S-->>G: state {game:{you:1, …}}
    G->>S: action {type:"bid", value:0}
    S-->>H: state {…}
    S-->>G: state {…}

AI protocol

The AI service connects to ws://wizard_server:8083/internal/ai with the header Authorization: Bearer <AI_SHARED_SECRET> and without forwarding headers (see "Routes" in the server chapter).

Direction Message Meaning
AI → server {"type":"hello","agent":"heuristic"} informational, logged
server → AI {"type":"decide","request_id":"<hex>","timeout":5.0,"observation":{…}} choose an action for observation.player; the server waits at most timeout seconds
AI → server {"type":"action","request_id":"<same>","action":{"type":"play","card":17},"agent":"heuristic"} the chosen action; agent names the agent that decided (logged)
AI → server {"type":"error","request_id":"<same>","message":"…"} the AI failed or refused (overloaded, expired); the server plays a random move

Rules:

  • Requests are independent; the AI service may answer them in any order (matched by request_id) and may handle several concurrently.
  • The answer must be one of observation.legal_actions; otherwise the server plays a random legal move.
  • No answer within AI_DECISION_TIMEOUT (5 s) → random move. The AI service drops requests whose timeout passed before it could start them (expired).
  • The server stops asking an AI connection that already has AI_MAX_INFLIGHT unanswered requests, and pauses the AI after repeated real failures (circuit breaker). overloaded / expired answers are load shedding, not failures, and do not trip the breaker. See "Layer 4" in chapter 9.
  • Several AI service instances may connect; the server distributes requests round robin.