Files
codexPySnake/CLAUDE.md
Vladyslav Doloman ed5cb14b30 WIP: Add input broadcasting and client-side prediction features
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>
2025-10-19 15:17:16 +03:00

242 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/)