strat-gameplay-webapp/.claude/implementation/websocket-protocol.md
Cal Corum 5c75b935f0 CLAUDE: Initial project setup - documentation and infrastructure
Add comprehensive project documentation and Docker infrastructure for
Paper Dynasty Real-Time Game Engine - a web-based multiplayer baseball
simulation platform replacing the legacy Google Sheets system.

Documentation Added:
- Complete PRD (Product Requirements Document)
- Project README with dual development workflows
- Implementation guide with 5-phase roadmap
- Architecture docs (backend, frontend, database, WebSocket)
- CLAUDE.md context files for each major directory

Infrastructure Added:
- Root docker-compose.yml for full stack orchestration
- Dockerfiles for backend and both frontends (multi-stage builds)
- .dockerignore files for optimal build context
- .env.example with all required configuration
- Updated .gitignore for Python, Node, Nuxt, and Docker

Project Structure:
- backend/ - FastAPI + Socket.io game engine (Python 3.11+)
- frontend-sba/ - SBA League Nuxt 3 frontend
- frontend-pd/ - PD League Nuxt 3 frontend
- .claude/implementation/ - Detailed implementation guides

Supports two development workflows:
1. Local dev (recommended): Services run natively with hot-reload
2. Full Docker: One-command stack orchestration for testing/demos

Next: Phase 1 implementation (backend/frontend foundations)
2025-10-21 16:21:13 -05:00

668 lines
12 KiB
Markdown

# WebSocket Protocol Specification
## Overview
Real-time bidirectional communication protocol between game clients and backend server using Socket.io. All game state updates, player actions, and system events transmitted via WebSocket.
## Connection Lifecycle
### 1. Initial Connection
**Client → Server**
```typescript
import { io } from 'socket.io-client'
const socket = io('wss://api.paperdynasty.com', {
auth: {
token: 'jwt-token-here'
},
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5
})
```
**Server → Client**
```json
{
"event": "connected",
"data": {
"user_id": "123456789",
"connection_id": "abc123xyz"
}
}
```
### 2. Joining a Game
**Client → Server**
```json
{
"event": "join_game",
"data": {
"game_id": "550e8400-e29b-41d4-a716-446655440000",
"role": "player" // "player" or "spectator"
}
}
```
**Server → Client** (Success)
```json
{
"event": "game_joined",
"data": {
"game_id": "550e8400-e29b-41d4-a716-446655440000",
"role": "player",
"team_id": 42
}
}
```
**Server → All Participants** (User Joined Notification)
```json
{
"event": "user_connected",
"data": {
"user_id": "123456789",
"role": "player",
"team_id": 42,
"timestamp": "2025-10-21T19:45:23Z"
}
}
```
### 3. Receiving Game State
**Server → Client** (Full State on Join)
```json
{
"event": "game_state_update",
"data": {
"game_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "active",
"inning": 3,
"half": "top",
"outs": 2,
"balls": 2,
"strikes": 1,
"home_score": 2,
"away_score": 1,
"runners": {
"first": null,
"second": 12345,
"third": null
},
"current_batter": {
"card_id": 67890,
"player_id": 999,
"name": "Mike Trout",
"position": "CF",
"batting_avg": 0.305,
"image": "https://..."
},
"current_pitcher": {
"card_id": 11111,
"player_id": 888,
"name": "Sandy Alcantara",
"position": "P",
"era": 2.45,
"image": "https://..."
},
"decision_required": {
"type": "set_defense",
"team_id": 42,
"user_id": "123456789",
"timeout_seconds": 30
},
"play_history": [
{
"play_number": 44,
"inning": 3,
"description": "Groundout to second base",
"runs_scored": 0
}
]
}
}
```
### 4. Heartbeat
**Client → Server** (Every 30 seconds)
```json
{
"event": "heartbeat"
}
```
**Server → Client**
```json
{
"event": "heartbeat_ack",
"data": {
"timestamp": "2025-10-21T19:45:23Z"
}
}
```
### 5. Disconnection
**Client → Server**
```json
{
"event": "leave_game",
"data": {
"game_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
```
**Server → All Participants**
```json
{
"event": "user_disconnected",
"data": {
"user_id": "123456789",
"timestamp": "2025-10-21T19:46:00Z"
}
}
```
## Game Action Events
### 1. Set Defensive Positioning
**Client → Server**
```json
{
"event": "set_defense",
"data": {
"game_id": "550e8400-e29b-41d4-a716-446655440000",
"positioning": "standard" // "standard", "infield_in", "shift_left", "shift_right"
}
}
```
**Server → Client** (Acknowledgment)
```json
{
"event": "decision_recorded",
"data": {
"type": "set_defense",
"positioning": "standard",
"timestamp": "2025-10-21T19:45:25Z"
}
}
```
**Server → All Participants** (Next Decision Required)
```json
{
"event": "decision_required",
"data": {
"type": "set_stolen_base",
"team_id": 42,
"user_id": "123456789",
"runners": ["second"],
"timeout_seconds": 20
}
}
```
### 2. Stolen Base Attempt
**Client → Server**
```json
{
"event": "set_stolen_base",
"data": {
"game_id": "550e8400-e29b-41d4-a716-446655440000",
"attempts": {
"second": true, // Runner on second attempts
"third": false // No runner on third
}
}
}
```
### 3. Offensive Approach
**Client → Server**
```json
{
"event": "set_offensive_approach",
"data": {
"game_id": "550e8400-e29b-41d4-a716-446655440000",
"approach": "swing_away" // "swing_away", "bunt", "hit_and_run"
}
}
```
### 4. Dice Roll & Play Resolution
**Server → All Participants** (Dice Roll Animation)
```json
{
"event": "dice_rolled",
"data": {
"roll": 14,
"animation_duration": 2000,
"timestamp": "2025-10-21T19:45:30Z"
}
}
```
**Server → All Participants** (Play Outcome)
```json
{
"event": "play_completed",
"data": {
"play_number": 45,
"inning": 3,
"half": "top",
"dice_roll": 14,
"result_type": "single",
"hit_location": "left_field",
"description": "Mike Trout singles to left field. Runner advances to third.",
"batter": {
"card_id": 67890,
"name": "Mike Trout"
},
"pitcher": {
"card_id": 11111,
"name": "Sandy Alcantara"
},
"outs_before": 2,
"outs_recorded": 0,
"outs_after": 2,
"runners_before": {
"first": null,
"second": 12345,
"third": null
},
"runners_after": {
"first": 67890,
"second": null,
"third": 12345
},
"runs_scored": 0,
"home_score": 2,
"away_score": 1,
"timestamp": "2025-10-21T19:45:32Z"
}
}
```
### 5. Play Result Selection
When multiple outcomes possible (e.g., hit location choices):
**Server → Offensive Player**
```json
{
"event": "select_play_result",
"data": {
"play_number": 45,
"options": [
{
"value": "single_left",
"label": "Single to Left",
"description": "Runner advances to third"
},
{
"value": "single_center",
"label": "Single to Center",
"description": "Runner scores"
}
],
"timeout_seconds": 15
}
}
```
**Client → Server**
```json
{
"event": "select_play_result",
"data": {
"game_id": "550e8400-e29b-41d4-a716-446655440000",
"play_number": 45,
"selection": "single_center"
}
}
```
### 6. Substitution
**Client → Server**
```json
{
"event": "substitute_player",
"data": {
"game_id": "550e8400-e29b-41d4-a716-446655440000",
"card_out": 67890,
"card_in": 55555,
"position": "CF",
"batting_order": 3
}
}
```
**Server → All Participants**
```json
{
"event": "substitution_made",
"data": {
"team_id": 42,
"player_out": {
"card_id": 67890,
"name": "Mike Trout"
},
"player_in": {
"card_id": 55555,
"name": "Byron Buxton",
"position": "CF",
"batting_order": 3
},
"inning": 3,
"timestamp": "2025-10-21T19:46:00Z"
}
}
```
### 7. Pitching Change
**Client → Server**
```json
{
"event": "change_pitcher",
"data": {
"game_id": "550e8400-e29b-41d4-a716-446655440000",
"pitcher_out": 11111,
"pitcher_in": 22222
}
}
```
**Server → All Participants**
```json
{
"event": "pitcher_changed",
"data": {
"team_id": 42,
"pitcher_out": {
"card_id": 11111,
"name": "Sandy Alcantara",
"final_line": "6 IP, 4 H, 1 R, 1 ER, 2 BB, 8 K"
},
"pitcher_in": {
"card_id": 22222,
"name": "Edwin Diaz",
"position": "P"
},
"inning": 7,
"timestamp": "2025-10-21T19:47:00Z"
}
}
```
## System Events
### 1. Inning Change
**Server → All Participants**
```json
{
"event": "inning_change",
"data": {
"inning": 4,
"half": "top",
"home_score": 2,
"away_score": 1,
"timestamp": "2025-10-21T19:48:00Z"
}
}
```
### 2. Game Ended
**Server → All Participants**
```json
{
"event": "game_ended",
"data": {
"game_id": "550e8400-e29b-41d4-a716-446655440000",
"winner_team_id": 42,
"final_score": {
"home": 5,
"away": 3
},
"innings": 9,
"duration_minutes": 87,
"mvp": {
"card_id": 67890,
"name": "Mike Trout",
"stats": "3-4, 2 R, 2 RBI, HR"
},
"completed_at": "2025-10-21T20:15:00Z"
}
}
```
### 3. Decision Timeout Warning
**Server → User**
```json
{
"event": "decision_timeout_warning",
"data": {
"decision_type": "set_defense",
"seconds_remaining": 10,
"default_action": "standard"
}
}
```
### 4. Auto-Decision Made
**Server → All Participants**
```json
{
"event": "auto_decision",
"data": {
"decision_type": "set_defense",
"team_id": 42,
"action_taken": "standard",
"reason": "timeout",
"timestamp": "2025-10-21T19:45:55Z"
}
}
```
## Error Events
### 1. Invalid Action
**Server → Client**
```json
{
"event": "invalid_action",
"data": {
"action": "set_defense",
"reason": "Not your turn",
"current_decision": {
"type": "set_offense",
"team_id": 99
},
"timestamp": "2025-10-21T19:46:00Z"
}
}
```
### 2. Connection Error
**Server → Client**
```json
{
"event": "connection_error",
"data": {
"code": "AUTH_FAILED",
"message": "Invalid or expired token",
"reconnect": false
}
}
```
### 3. Game Error
**Server → All Participants**
```json
{
"event": "game_error",
"data": {
"code": "STATE_RECOVERY_FAILED",
"message": "Unable to recover game state",
"severity": "critical",
"recovery_options": [
"reload_game",
"contact_support"
],
"timestamp": "2025-10-21T19:46:30Z"
}
}
```
## Rate Limiting
### Per-User Limits
- **Actions**: 10 per second
- **Heartbeats**: 1 per 10 seconds minimum
- **Invalid actions**: 5 per minute (after that, temporary ban)
### Response on Rate Limit
```json
{
"event": "rate_limit_exceeded",
"data": {
"action": "set_defense",
"limit": 10,
"window": "1 second",
"retry_after": 1000,
"timestamp": "2025-10-21T19:46:35Z"
}
}
```
## Reconnection Protocol
### Automatic Reconnection
1. Client detects disconnect
2. Client attempts reconnect with backoff (1s, 2s, 4s, 8s, 16s)
3. On successful reconnect, client sends `join_game` event
4. Server checks if game state exists in memory
5. If not, server recovers state from database
6. Server sends full `game_state_update` to client
7. Client resumes from current state
### Client Implementation
```typescript
socket.on('disconnect', () => {
console.log('Disconnected, will attempt reconnect')
// Socket.io handles reconnection automatically
})
socket.on('connect', () => {
console.log('Reconnected')
// Rejoin game room
socket.emit('join_game', { game_id: currentGameId, role: 'player' })
})
```
## Testing WebSocket Events
### Using Python Client
```python
import socketio
sio = socketio.Client()
@sio.event
def connect():
print('Connected')
sio.emit('join_game', {'game_id': 'test-game', 'role': 'player'})
@sio.event
def game_state_update(data):
print(f'Game state: {data}')
sio.connect('http://localhost:8000', auth={'token': 'jwt-token'})
sio.wait()
```
### Using Browser Console
```javascript
const socket = io('http://localhost:8000', {
auth: { token: 'jwt-token' }
})
socket.on('connect', () => {
console.log('Connected')
socket.emit('join_game', { game_id: 'test-game', role: 'player' })
})
socket.on('game_state_update', (data) => {
console.log('Game state:', data)
})
```
## Event Flow Diagrams
### Typical At-Bat Flow
```
1. [Server → All] decision_required (set_defense)
2. [Client → Server] set_defense
3. [Server → All] decision_recorded
4. [Server → All] decision_required (set_stolen_base) [if runners on base]
5. [Client → Server] set_stolen_base
6. [Server → All] decision_recorded
7. [Server → All] decision_required (set_offensive_approach)
8. [Client → Server] set_offensive_approach
9. [Server → All] decision_recorded
10. [Server → All] dice_rolled
11. [Server → All] play_completed
12. [Server → All] game_state_update
13. Loop to step 1 for next at-bat
```
### Substitution Flow
```
1. [Client → Server] substitute_player
2. [Server validates]
3. [Server → All] substitution_made
4. [Server → All] game_state_update (with new lineup)
```
## Security Considerations
### Authentication
- JWT token required for initial connection
- Token verified on every connection attempt
- Token refresh handled by HTTP API, not WebSocket
### Authorization
- User can only perform actions for their team
- Spectators receive read-only events
- Server validates all actions against game rules
### Data Validation
- All incoming events validated against Pydantic schemas
- Invalid events logged and rejected
- Repeated invalid events result in disconnect
---
**Implementation**: See [backend-architecture.md](./backend-architecture.md) for connection manager implementation.