Protocol
Leo speaks JSON over WebSocket on the same host as this site. Hidden information never leaves the server — each client gets a private view.
Connect
Open wss://play.leoserver.art (or ws:// on localhost). On open, the server sends welcome. Then introduce yourself with an App Key from the dashboard:
{ "type": "hello", "name": "Leo", "appKey": "leo_…", "playerKey": "pk_…" }
Omit playerKey on first connect — the server creates an account, returns a key, and the client should store it. Send it next time to restore the same wallet.
Matchmaking
{ "type": "quick", "game": "bluffer" }
{ "type": "create", "game": "bluffer", "options": { "seats": 4 } }
{ "type": "join", "roomId": "K7X2PQ" }
{ "type": "start" }
{ "type": "leave" }
{ "type": "list" }
Photon-style props & events
{ "type": "setPlayerProps", "props": { "skin": "red", "ready": true } }
{ "type": "setRoomProps", "props": { "map": "arena" } } // host only
{ "type": "event", "code": "emote", "data": { "id": 3 } }
{ "type": "event", "code": "whisper", "data": "hi", "targetId": "pl_…" }
Room messages also include props and each player’s props.
In-game actions
Forwarded to the game referee. Bluffer:
{ "type": "action", "action": { "type": "bid", "count": 3, "face": 5 } }
{ "type": "action", "action": { "type": "call" } }
{ "type": "action", "action": { "type": "power" } } // once/match: peek one opponent
Fun Run (each round — first to 12 wins):
{ "type": "action", "action": { "type": "walk" } } // +2
{ "type": "action", "action": { "type": "run" } } // +3, 1 energy
{ "type": "action", "action": { "type": "dash" } } // +5 once, 2 energy
REST for game clients (Supabase-like)
Send X-App-Key (and optional X-Player-Key):
GET /api/v1/me
GET /api/v1/leaderboard?metric=coins
PUT /api/v1/data/inventory/main { "coins": 10, "items": ["sword"] }
GET /api/v1/data/inventory/main
Server → client
{ "type": "welcome", "playerId": "p1", "games": ["dice","bluffer"], "wallet": {...} }
{ "type": "room", "roomId", "game", "started", "you", "host", "players", "state", "event" }
{ "type": "error", "message": "..." }
Add a game
- Create
stadium/src/games/<name>/index.jsexportingid,minPlayers,maxPlayers,create,onStart,onAction,view. - Register it in
stadium/src/games/index.js. - Redeploy:
docker compose up -d --build.
Health
GET /health returns loaded games and live room counts.