Initial commit with project plans.

This commit is contained in:
Vladyslav Doloman
2025-10-07 15:26:57 +03:00
commit 28ddf4f455
2 changed files with 183 additions and 0 deletions

27
IDEAS.md Normal file
View File

@@ -0,0 +1,27 @@
This project will be a network mutiplayer Snake game.
python 3 server and a web client that uses webtransport datagrams.
the logic of the game: When the snake hits something, it doesn't die, the head stays in place, but the tail shortens by 1 every tick the head is turned in the direction of the obstacle. The player can change the direction the head is turned and the snake continues to move in new direction. The snake can not be shorter than its head, so the head always lives, even when the tail is all gone.
consider the possibility of stucking on anothers player snake (head or tail)? when the other player's snake have passed by and no more considered the obstacle, the current player's snake should continue moving.
self-collision is not permanent, because the tail keeps shrinking, there will be a moment, when the segment of a tail that was an obstacle will cease to exist
other snake collision can be auto-cleared when the other snake is moved away or shrinks to the point when it is no longer an obstacle
if a player is disconnected - his snake and score dissapears from the game, so there is no game-over state. the server runs constantly, without "rounds" of gameplay. new players connect directly into the game. There is no end game to detect winner or loser, there is continuous gameplay and in each moment there is a length of each snake. The longest snake is the "winner" at each moment (considering it can shorten or be outrunned by another player and lose its winning position).
keep the snake color the same for the whole duration of client connection
instead of a score based on eaten apples display the snakes current lengths
when only the head is left - it can turn 180 degrees as well as 90
make a small input buffer to buffer 3 direction changes for next ticks. if the new direction the user is pressing is directly opposite to the last in the buffer - replace the last in the buffer (do not add it as a new step).
instead of ignoring overflowinf inputs - replace the last one.
do not add to the buffer repeating inputs. before calculating next position when consuming 1 direction from buffer check if it is 180 turn when snake length>1, if so - ignore this input and consume the next one.
when connected to server - show the current gameplay on the background with the text overlay "press space to join"
when 0 players left - populate the field with 3 apples
field size 60 by 40 (by default). allow room in the protocol it to be changed between 3x3 up to 255x255
when using webtransport datagrams (UDP) for the game protocol, add packet number into the message to ignore late packets. make possible to wrap numbering from the beginnig in a case of integer overflow. use compression for the data in packets, so the full field update can be transmitted as one UDP datagram (typically up to 1500 bytes)
in UDP (webtransport datagrams) Protocol Design - allow room for lost packets before/after wrapping;
make room for up to 32 simultaneous players with different colors; limit player name length to 16 characters
if UDP packet size exceeds 1280 bytes - split the update in several parts that do not exceed this size by updating different snakes info in different independant packets, so if one of them is lost - some of the information still reaches the recipient. If a snake is so long that its update doesn't fit into one packet by itself - then find a way to split it into several updates, preferably of the similar size (split the snake into the equal size parts)

156
PROJECT_PLAN.md Normal file
View File

@@ -0,0 +1,156 @@
# 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.