Changes include: - Client: INPUT_BROADCAST packet handling and opponent prediction rendering - Client: Protocol parsing for INPUT_BROADCAST packets - Server: Input broadcasting to all clients except sender 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
242 lines
11 KiB
Markdown
242 lines
11 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
## Project Overview
|
||
|
||
This is a real-time multiplayer Snake game using Python 3 (server) and vanilla JavaScript (web client), communicating via WebTransport datagrams (HTTP/3) for low-latency gameplay. The game features continuous persistent gameplay, unique collision mechanics (head-blocks-and-tail-shrinks instead of death), and sophisticated UDP packet handling with compression and partitioning.
|
||
|
||
## Running the Server
|
||
|
||
### Dependencies
|
||
```bash
|
||
# Create and activate virtual environment
|
||
python -m venv venv
|
||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||
|
||
# Install dependencies
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
### Server Modes
|
||
|
||
The server supports multiple transport modes via `MODE` environment variable:
|
||
|
||
```bash
|
||
# In-memory mode (testing, no network)
|
||
MODE=mem python run.py
|
||
|
||
# WebTransport mode (HTTP/3, default for production)
|
||
MODE=wt QUIC_CERT=cert.pem QUIC_KEY=key.pem python run.py
|
||
|
||
# QUIC datagram mode only
|
||
MODE=quic QUIC_CERT=cert.pem QUIC_KEY=key.pem python run.py
|
||
|
||
# Combined mode (both WebTransport and QUIC)
|
||
MODE=net WT_PORT=4433 QUIC_PORT=4443 QUIC_CERT=cert.pem QUIC_KEY=key.pem python run.py
|
||
```
|
||
|
||
### Environment Variables
|
||
|
||
- `MODE`: Transport mode (mem|wt|quic|net)
|
||
- `QUIC_CERT` / `QUIC_KEY`: TLS certificate and key paths (required for wt/quic/net)
|
||
- `WT_CERT` / `WT_KEY`: WebTransport-specific cert/key (falls back to QUIC_* if not set)
|
||
- `WT_HOST` / `WT_PORT`: WebTransport server host/port (default: 0.0.0.0:4433)
|
||
- `QUIC_HOST` / `QUIC_PORT`: QUIC server host/port (default: 0.0.0.0:4433, or 4443 in net mode)
|
||
- `STATIC`: Enable static HTTPS server for client (default: 1 in wt mode)
|
||
- `STATIC_HOST` / `STATIC_PORT` / `STATIC_ROOT`: Static server settings (default: same as WT host, port 8443, root "client")
|
||
- `LOG_LEVEL`: Logging level (DEBUG|INFO|WARNING|ERROR)
|
||
- `RUN_SECONDS`: Optional timeout for server shutdown (testing)
|
||
|
||
### Help
|
||
```bash
|
||
python run.py --help
|
||
```
|
||
|
||
## Architecture
|
||
|
||
### Server Architecture (Python 3 + asyncio)
|
||
|
||
**Core Components:**
|
||
|
||
1. **server/server.py** (`GameServer`): Authoritative game server
|
||
- Fixed tick rate simulation (default 10 TPS, configurable 5-30)
|
||
- Manages player sessions, snakes, apples, collision detection
|
||
- Handles join/spawn logic, input processing, state broadcasting
|
||
- Automatically partitions large updates across multiple datagrams (<1200 bytes each to avoid IP fragmentation)
|
||
|
||
2. **server/model.py**: Game state entities
|
||
- `GameState`: Field grid, snakes dict, apples list, occupancy map
|
||
- `Snake`: Deque-based body representation, input buffer (capacity 3), blocked flag
|
||
- `PlayerSession`: Per-player metadata (id, name, color, peer handle)
|
||
|
||
3. **server/protocol.py**: Wire protocol implementation
|
||
- Packet types: JOIN, JOIN_ACK, JOIN_DENY, INPUT, INPUT_BROADCAST, STATE_DELTA, STATE_FULL, PART, CONFIG_UPDATE
|
||
- TLV body encoding: 2-bit direction streams, RLE compression, chunked variants for oversized snakes
|
||
- QUIC varint encoding for efficient integer serialization
|
||
- Sequence number handling with wraparound logic (16-bit)
|
||
|
||
4. **server/config.py** (`ServerConfig`): Server configuration dataclass with validation
|
||
|
||
5. **Transport layer** (pluggable):
|
||
- `server/transport.py`: Abstract `DatagramServerTransport` interface
|
||
- `server/webtransport_server.py`: WebTransport (HTTP/3) implementation using aioquic
|
||
- `server/quic_transport.py`: Raw QUIC datagram implementation
|
||
- `server/multi_transport.py`: Multiplexer for running multiple transports concurrently
|
||
- `server/static_server.py`: Optional HTTPS static file server for serving client assets
|
||
|
||
### Client Architecture (Vanilla JavaScript)
|
||
|
||
**Files:**
|
||
- `client/index.html`: Main HTML page with canvas and UI controls
|
||
- `client/client.js`: Game client, WebTransport connection, input handling, rendering
|
||
- `client/protocol.js`: Wire protocol parsing/building (mirrors server protocol)
|
||
|
||
**Key Features:**
|
||
- WebTransport datagram API for unreliable, unordered messaging
|
||
- Canvas-based rendering with auto-scaling grid
|
||
- Input buffering (up to 3 direction changes) with 180° turn filtering
|
||
- State update handling: STATE_FULL (initial/recovery), STATE_DELTA (per-tick), PART (fragmented)
|
||
- Packet sequence tracking to discard late/out-of-order updates
|
||
|
||
### Game Mechanics (Unique Collision System)
|
||
|
||
**No Death on Collision:**
|
||
- When a snake's head hits an obstacle (wall, self, or another snake), the head stays in place (blocked state)
|
||
- While blocked, the tail shrinks by 1 cell per tick until the player turns to a free direction
|
||
- Minimum length is 1 (head only); snake cannot disappear while player is connected
|
||
|
||
**Input Buffer Rules:**
|
||
- Client-side buffer holds up to 3 upcoming direction changes
|
||
- Opposite direction to last buffered input replaces the last entry (not appended)
|
||
- Consecutive duplicates are dropped
|
||
- Overflow policy: replace last element
|
||
- Server consumption: At each tick, consume at most one input; skip 180° turns when length > 1
|
||
|
||
**Apples & Scoring:**
|
||
- Displayed as current snake length (not separate score)
|
||
- Eating an apple grows snake by 1 immediately
|
||
- Target apple count: max(3, min(apples_cap, connected_players × apples_per_snake))
|
||
- When 0 players remain, pre-populate field with exactly 3 apples
|
||
|
||
**Spawning:**
|
||
- Try to allocate a 3-cell straight strip in a free direction
|
||
- Fallback: spawn length 1 (head only) at any free cell
|
||
- Deny join if no free cells available
|
||
|
||
### Protocol Details
|
||
|
||
**Packet Structure:**
|
||
```
|
||
Header (all packets):
|
||
ver (u8) | type (u8) | flags (u8) | seq (u16 big-endian) | [tick (u16 optional)]
|
||
```
|
||
|
||
**Sequence Numbers:**
|
||
- 16-bit wraparound with half-range window comparison: `is_newer(a,b) = ((a-b) & 0xFFFF) < 0x8000`
|
||
- Clients discard packets with older seq than last applied per sender
|
||
|
||
**State Encoding:**
|
||
- **STATE_FULL**: Complete snapshot (on join, periodic recovery); includes all snakes + apples
|
||
- Per-snake: id (u8), len (u16), head (x,y), body TLV (BODY_2BIT or BODY_RLE)
|
||
- **STATE_DELTA**: Incremental update (per tick); includes only changed snakes + apple diffs
|
||
- Per-change: snake_id, flags (head_moved|tail_removed|grew|blocked), direction, new_head coords
|
||
- **PART**: Multi-part fragmentation for large updates (>1200 bytes)
|
||
- Fields: update_id (u16), part_index, parts_total, inner_type (STATE_FULL or STATE_DELTA), chunk_payload
|
||
|
||
**Body TLV Types:**
|
||
- `BODY_2BIT (0x00)`: 2-bit direction stream (up=00, right=01, down=10, left=11), packed LSB-first
|
||
- `BODY_RLE (0x01)`: Run-length encoding (dir u8, count QUIC varint) for straight segments
|
||
- `BODY_2BIT_CHUNK (0x10)` / `BODY_RLE_CHUNK (0x11)`: Chunked variants for splitting oversized snakes across parts
|
||
- Chunk header: start_index (u16), dirs_in_chunk (u16)
|
||
|
||
**Input Broadcasting:**
|
||
- Server relays client inputs immediately to all other clients as INPUT_BROADCAST packets
|
||
- Enables client-side opponent prediction during late/lost state updates
|
||
- Fields: player_id, input_seq, base_tick, events (rel_tick_offset + direction), optional apply_at_tick
|
||
|
||
**Partitioning Strategy:**
|
||
- Soft limit: 1200 bytes per datagram (avoid fragmentation; MTU ~1280-1500)
|
||
- Whole-snake-first: pack complete snakes greedily; if a single snake exceeds budget, split using chunked TLV
|
||
- Apples included only in first part; clients merge across parts using update_id
|
||
|
||
## Key Simulation Details
|
||
|
||
**Tick Order:**
|
||
1. Consume at most one valid input per snake (apply 180° rule for length > 1)
|
||
2. Compute intended head step; if blocked (wall/occupied), set `blocked=True` and keep head stationary
|
||
3. If blocked: shrink tail by 1 (min length 1)
|
||
4. If moving into apple: grow by 1 (don't shrink tail that tick) and respawn apple
|
||
5. Emit per-snake delta and global apple changes
|
||
|
||
**Collision Detection:**
|
||
- Occupancy map tracks all snake cells as (coord) -> (snake_id, index)
|
||
- Allow moving into own tail if it will vacate (i.e., not growing)
|
||
- Walls block only when `wrap_edges=False`; wrapped coordinates computed by `_step_from()`
|
||
|
||
**180° Turn Handling:**
|
||
- Checked at consumption time using XOR: `(int(new_dir) ^ int(current_dir)) == 2`
|
||
- Allowed only when snake length == 1 (head only)
|
||
|
||
## Development Notes
|
||
|
||
- **Testing**: No automated test suite currently exists. Manual testing via in-memory mode (`MODE=mem`) or live WebTransport server.
|
||
- **Logging**: Use `LOG_LEVEL=DEBUG` for verbose packet-level debugging.
|
||
- **Field Size**: Default 60×40; protocol supports 3×3 to 255×255 (negotiated in JOIN_ACK).
|
||
- **Players**: Max 32 concurrent (ids 0-31); names truncated to 16 UTF-8 bytes.
|
||
- **Colors**: 32-color palette; deterministic mapping from player_id to color_id (0-31).
|
||
- **Tick Rate Changes**: Server broadcasts CONFIG_UPDATE every 50 ticks; clients apply at next tick boundary.
|
||
- **Compression**: Optional global DEFLATE mode (handshake-only; requires server restart). Currently defaults to "none".
|
||
- **Browser Compatibility**: Requires WebTransport API support (latest Firefox/Chrome).
|
||
|
||
## Common Patterns
|
||
|
||
**Adding a New Packet Type:**
|
||
1. Add enum value to `PacketType` in server/protocol.py and client/protocol.js
|
||
2. Implement builder function in protocol (e.g., `build_foo()`) and parser (e.g., `parse_foo()`)
|
||
3. Handle in server's `on_datagram()` and client's `readLoop()` switch statements
|
||
4. Update header packing if tick field is required (pass tick to `pack_header()`)
|
||
|
||
**Modifying Simulation Logic:**
|
||
1. Edit `_simulate_tick()` in server/server.py for per-tick updates
|
||
2. Update `SnakeDelta` dataclass if new change types are needed
|
||
3. Adjust `build_state_delta_body()` if encoding changes
|
||
4. Mirror changes in client rendering logic (client/client.js)
|
||
|
||
**Changing Configuration:**
|
||
1. Update `ServerConfig` dataclass in server/config.py
|
||
2. Add runtime validation in `validate_runtime()`
|
||
3. If live-configurable: broadcast via CONFIG_UPDATE packet; otherwise require server restart
|
||
4. Update JOIN_ACK builder/parser if part of handshake
|
||
|
||
## File Organization
|
||
|
||
```
|
||
G:\Coding\code2\
|
||
├── run.py # Server entrypoint with mode switching
|
||
├── requirements.txt # Python dependencies (aioquic, cryptography)
|
||
├── PROJECT_PLAN.md # Detailed design spec (authoritative reference)
|
||
├── IDEAS.md # Original feature brainstorming
|
||
├── server/
|
||
│ ├── server.py # GameServer (tick loop, join/spawn, simulation)
|
||
│ ├── model.py # GameState, Snake, PlayerSession entities
|
||
│ ├── protocol.py # Wire protocol (packet builders/parsers, TLV encoding)
|
||
│ ├── config.py # ServerConfig dataclass
|
||
│ ├── transport.py # Abstract DatagramServerTransport interface
|
||
│ ├── webtransport_server.py # HTTP/3 WebTransport implementation
|
||
│ ├── quic_transport.py # QUIC datagram implementation
|
||
│ ├── multi_transport.py # Multi-transport multiplexer
|
||
│ ├── static_server.py # Optional HTTPS static file server
|
||
│ └── utils.py # Utility functions
|
||
└── client/
|
||
├── index.html # Main HTML page
|
||
├── client.js # Game client (WebTransport, input, rendering)
|
||
└── protocol.js # Wire protocol (mirrors server protocol.py)
|
||
```
|
||
|
||
## References
|
||
|
||
- **PROJECT_PLAN.md**: Comprehensive design document covering protocol, mechanics, limits, testing strategy, and milestones.
|
||
- **aioquic**: Python QUIC and HTTP/3 library (https://github.com/aiortc/aioquic)
|
||
- **WebTransport**: W3C spec for low-latency client-server messaging over QUIC (https://w3c.github.io/webtransport/)
|