Files
codexPySnake/PROJECT_PLAN.md
Vladyslav Doloman 03969ebd99 Plan: initial multiplayer Snake design (server, client, protocol)
- Add PROJECT_PLAN.md outlining the core design:
  - Server: Python 3 asyncio, authoritative tick loop
  - Client: WebTransport (datagrams), canvas rendering, spectator overlay
  - Mechanics: blocked head + tail shrink, apples, colors, 60x40 field,
    continuous play, length as score, input buffer rules, 180° policy
  - Networking: datagrams, seq numbers + wraparound, delta/full updates,
    1280-byte budget, up to 32 players, names ≤16 bytes
  - Testing strategy and milestones

Rationale: establish a shared baseline for scope and interfaces.
2025-10-07 18:36:46 +03:00

157 lines
9.7 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.
# Multiplayer Snake (WebTransport) — Project Plan
## Overview
- Goal: Build a real-time, networked multiplayer Snake game with a Python 3 authoritative server and a web client using WebTransport datagrams for low-latency updates.
- Gameplay: Continuous, persistent world (no rounds). Players can join/leave anytime. Display each snakes current length; the longest snake is the momentary “leader”.
- Field: Default 60×40 grid; protocol supports 3×3 up to 255×255.
- Networking: Unreliable, unordered datagrams with sequence numbers and wraparound handling. Compression and packet partitioning to fit MTU (~12801500 bytes).
## Core Mechanics
- Collision handling: When the snake head would hit an obstacle (wall, self, other snake), the head stays in place (blocked) and the tail shrinks by 1 per tick until the player turns to a free direction.
- Self-collision: Temporary; tail shrink can clear the blocking segment.
- Other-snake collision: Clears when the other snake moves or shrinks away.
- Minimum length: A snake cannot be shorter than its head; minimum length is 1.
- Turning rules:
- Length > 1: 180° turns are invalid and ignored at consumption time.
- Length = 1 (only head left): 180° turns are valid.
- Apples and scoring:
- Replace score display with current snake length.
- Eating apple grows snake by 1 immediately.
- When 0 players remain connected, pre-populate field with exactly 3 apples.
- Colors: Assign a color when a client connects and keep it stable for that session.
- Spectator join: On initial connection, show current gameplay and overlay “press space to join”.
## Input Buffer Rules
- Maintain a small buffer of up to 3 upcoming direction changes.
- If the new input is directly opposite to the last buffered direction, replace the last buffered entry instead of appending.
- Drop repeats: Do not add consecutive duplicates to the buffer.
- Overflow policy: If buffer is full, replace the last element with the new input.
- At each tick, before moving:
- Consume at most one input from the buffer.
- If length > 1 and it is a 180° turn, ignore it and immediately try the next buffered input; if none valid, keep current direction.
## Server Architecture (Python 3)
- Runtime: Python 3 with asyncio.
- Transport: WebTransport (HTTP/3 datagrams). Candidate library: `aioquic` for server-side H3 and datagrams.
- Model:
- Authoritative simulation on the server with a fixed tick rate (target 1520 TPS; start with 15 for network headroom).
- Each tick: process inputs, update snakes, resolve collisions (blocked/shrink), move apples, generate state deltas.
- Entities:
- Field: width, height, random seed, apple set.
- Snake: id, name (≤16 chars), color id (031), deque of cells (head-first), current direction, input buffer, blocked flag, last-move tick, last-known client seq.
- Player session: transport bindings, last-received input seq per player, anti-spam counters.
- Spawn:
- New snake spawns at a random free cell with a random legal starting direction, length 3 by default (configurable).
- On apple eat: grow by 1; spawn a replacement apple at a free cell.
- Disconnect: Immediately remove snake and its score/length; apples persist. If all disconnect, ensure field contains 3 apples.
- Rate limiting: Cap input datagrams per second and buffer growth per client.
## Client Architecture (Web)
- Tech: Browser WebTransport (H3 datagrams), Canvas/WebGL rendering, vanilla or minimal framework for UI overlay.
- Modes:
- Spectator: Render world, show overlay “press space to join”.
- Player: Capture keyboard, build input buffer client-side, send inputs with sequence numbers; predict local movement for smoothness but reconcile to server.
- Rendering:
- Gridless pixel or cell-based canvas.
- Colors: 32-color predefined palette; deterministic mapping from player id to color id.
- HUD: Current length; leaderboard (top N by length).
## Networking & Protocol
Constraints and goals:
- Use datagrams (unreliable, unordered). Include a packet sequence number for late-packet discard with wraparound handling.
- Keep payloads ≤1280 bytes to avoid fragmentation; if larger, partition logically.
- Support up to 32 concurrent players; names limited to 16 bytes UTF8.
Header (all packets):
- `ver` (u8): protocol version.
- `type` (u8): packet type (join, join_ack, input, state_delta, state_full, part, ping, pong, error).
- `flags` (u8): bit flags (compression, is_last_part, etc.).
- `seq` (u16): senders monotonically incrementing sequence number (wraps at 65535). Newer-than logic uses half-range window.
- Optional `tick` (u16): simulation tick the packet refers to (on server updates).
Handshake (join → join_ack):
- Client sends: desired name (≤16), optional preferred color id.
- Server replies: assigned player id (u8), color id (u8), field size (width u8, height u8), tick rate (u8), random seed (u32), palette, and initial full snapshot.
Inputs (client → server):
- Packet `input` includes: last-acknowledged server seq (u16), local `input_seq` (u16), one or more direction events with timestamps or relative tick offsets. Client pre-filters per buffer rules.
State updates (server → client):
- Types:
- `state_full`: rare (on join or periodic recovery); includes all snakes and apples.
- `state_delta`: frequent; contains only changed snakes (head move, tail shrink/grow) and apple changes since last acked tick.
- Per-snake encoding (compressed):
- `id` (u8), `len` (u16), head `(x,y)` (u8,u8), direction (2 bits), and a compact body representation:
- Option A: Run-length of axis-aligned segments (dir, run u16) starting at head.
- Option B: Delta-coded cells with RLE for straight runs.
Compression:
- Primary: domain-specific packing (bits + RLE for segments and apples), then optional DEFLATE flag (`flags.compressed=1`) if still large.
- Client decompresses if set; otherwise reads packed structs directly.
Packet partitioning (if >1280 bytes):
- Split `state_full`/`state_delta` by snakes into multiple independent sub-updates.
- Each part has: `update_id` (u16), `part_index` (u8), `parts_total` (u8). Clients apply parts as they arrive; missing parts simply leave some snakes stale until the next update.
- Extremely long single-snake updates: split that snakes body into balanced segments by RLE chunks.
Sequence wrap & ordering:
- Define `is_newer(a, b)` using signed 16-bit difference: `((a - b) & 0xFFFF) < 0x8000`.
- Clients ignore updates older-than last applied per-sender.
## Simulation Details
- Tick order per tick:
1) Incorporate at most one valid buffered input per snake (with 180° rule).
2) Compute intended head step; if blocked, set `blocked=true` and keep head stationary.
3) If `blocked=true`: shrink tail by 1 (to min length 1). If the blocking cell becomes free (due to own shrink or others moving), the next ticks step in current direction proceeds.
4) If moving into an apple: grow by 1 (do not shrink tail that tick) and respawn an apple.
5) Emit per-snake delta (move, grow, or shrink) and global apple changes.
- Blocking detection: walls (0..width-1, 0..height-1), any occupied snake cell (including heads and tails that are not about to vacate this tick).
- 180° turn allowance when length==1 only.
## Data Model & Structures
- Field cells: Represent coordinates as `(x:u8, y:u8)`.
- Snakes: Deque of cells or segment list; store length as `deque.size()` and maintain head index.
- Apples: Hash set of cells for O(1) lookup; spawn on empty cells only.
- Occupancy: Sparse set/map from cell→(snake_id, index) for O(1) collision checks.
## Limits & Config
- Players: max 32 (ids 0..31); deny joins beyond capacity.
- Names: UTF8, truncate to 16 bytes; filter control chars.
- Field: default 60×40; min 3×3; max 255×255; provided by server in join_ack.
- Tick rate: default 15 TPS; configurable 1030 TPS.
## Error Handling & Resilience
- Invalid packets: drop and optionally send `error` with code.
- Flooding: server-side rate limits and strike-based disconnects.
- Out-of-order: discard using `is_newer` check.
- Desync recovery: server sends `state_full` periodically or when client reports large gaps.
## Testing Strategy
- Unit tests: input buffer rules, 180° logic, blocking/shrink, collision resolution, sequence wrap comparison.
- Property tests: snake movement invariants (no duplicates in body except expected at head on block), apple placement safety.
- Soak tests: bot clients sending random but rate-limited inputs; measure packet size and latency.
## Milestones
1) Protocol draft + wire structs (this plan).
2) Server skeleton: tick loop, entities, occupancy, apples, basic WebTransport datagrams.
3) Input handling: buffer logic and per-tick consumption; collision/blocking/shrink rules.
4) State encoding: deltas, compression, and partitioning; client renderer (spectator).
5) Player flow: join overlay, name/color, spawn, HUD and leaderboard.
6) Performance pass: packet size tuning, RLE, optional DEFLATE, rate limits.
7) Polishing: error messages, reconnect, telemetry/logging, Docker/dev scripts.
## Open Questions
- Exact tick rate target and interpolation strategy on client.
- Initial snake length on spawn (3 is suggested) and spawn retry rules in crowded fields.
- Apple count baseline when players are present (fixed count vs proportional to players vs area).
- Whether to allow wrap-around at field edges (currently: walls are blocking).
- Compression choice tradeoffs: custom bitpacking only vs optional DEFLATE.
- Minimum viable browser targets (WebTransport support varies).
## Next Steps
- Validate the protocol choices and mechanics with stakeholders.
- Decide on tick rate and initial lengths.
- Confirm library choices (`aioquic` server; client-side `WebTransport` API usage).
- Begin Milestone 2: implement server skeleton and simulation core.