Plan: adopt live config updates + spawn/edge/compression/browser decisions
- Tick rate default 10 TPS; live config via config_update, periodic resend - Spawn policy: prefer length 3 if a straight strip fits; else length 1; deny join if no free cell - Apples per snake: default 1; min 1, max 12; cap 255 total; live config - Wrap edges: live-configurable; head-only enforcement on transitions - Compression: DEFLATE is handshake-only (restart + reconnect) - Browser targets: latest Firefox required; latest Chrome desirable - Protocol: join_deny; config_update packet; input_broadcast includes base_tick + rel offsets and apply_at_tick
This commit is contained in:
@@ -33,15 +33,17 @@
|
||||
## 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 15-20 TPS; start with 15 for network headroom).
|
||||
- Model:
|
||||
- Authoritative simulation on the server with a fixed tick rate (default 10 TPS). Tick rate is reconfigurable at runtime by the server operator and changes are broadcast to clients.
|
||||
- Each tick: process inputs, update snakes, resolve collisions (blocked/shrink), manage apples, generate state deltas.
|
||||
- Entities:
|
||||
- Field: width, height, random seed, apple set.
|
||||
- Snake: id, name (<=16 chars), color id (0-31), 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).
|
||||
- Spawn:
|
||||
- New snake spawns at a random free cell with a random legal starting direction.
|
||||
- Initial length policy: try to allocate a 3-cell straight strip (head + two body cells) in a legal direction; if not possible, spawn with length 1.
|
||||
- If the field appears full, temporarily ignore apples as obstacles for the purpose of finding space. If no free single cell exists, deny the join and inform the client to wait.
|
||||
- On apple eat: grow by 1; spawn a replacement apple at a free cell.
|
||||
- Disconnect: Immediately remove snake and its length; apples persist. If all disconnect, ensure field contains 3 apples.
|
||||
- Rate limiting: Cap input datagrams per second and buffer growth per client.
|
||||
@@ -65,21 +67,26 @@ Constraints and goals:
|
||||
|
||||
Header (all packets):
|
||||
- ver (u8): protocol version.
|
||||
- type (u8): packet type (join, join_ack, input, input_broadcast, state_delta, state_full, part, ping, pong, error).
|
||||
- type (u8): packet type (join, join_ack, join_deny, input, input_broadcast, state_delta, state_full, part, config_update, ping, pong, error).
|
||||
- flags (u8): bit flags (compression, is_last_part, etc.).
|
||||
- seq (u16): sender's 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):
|
||||
Handshake (join -> join_ack | join_deny):
|
||||
- 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.
|
||||
- Server replies (join_ack): assigned player id (u8), color id (u8), field size (width u8, height u8), tick rate (u8), wrap_edges (bool), apples_per_snake (u8), apples_cap (u8 up to 255), compression_mode (enum: none|deflate), random seed (u32), palette, and initial full snapshot.
|
||||
- Server can reply (join_deny) with a reason (e.g., no free cell; please wait).
|
||||
|
||||
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.
|
||||
|
||||
Input broadcast (server -> clients):
|
||||
- Upon receiving a player's input, the server immediately relays those input events to all other clients as input_broadcast packets.
|
||||
- Contents: player_id (u8), the player's input_seq (u16), direction events, and apply_at_tick (u16) assigned by the server (typically current_tick+1) so clients can align predictions.
|
||||
- Contents:
|
||||
- player_id (u8), the player's input_seq (u16)
|
||||
- base_tick (u16): server tick for alignment (typically current_tick)
|
||||
- events: one or more direction changes, each with rel_tick_offset (u8/varint) from base_tick
|
||||
- apply_at_tick (u16): optional absolute apply tick for convenience (both provided; clients may use either)
|
||||
- Purpose: Enable client-side opponent prediction during late or lost state updates. Broadcasts are small; apply rate limits if needed.
|
||||
|
||||
State updates (server -> client):
|
||||
@@ -102,7 +109,8 @@ State updates (server -> client):
|
||||
- Client buffers chunks by `(update_id, snake_id)` and assembles when the full [0..len-2] range is present; then applies.
|
||||
|
||||
Compression:
|
||||
- Primary: domain-specific packing (bits + RLE for segments and apples), then optional DEFLATE flag (flags.compressed=1) if still large.
|
||||
- Primary: domain-specific packing (bits + RLE for segments and apples).
|
||||
- Optional global DEFLATE mode (server-configured): when enabled, server may set flags.compressed=1 and apply DEFLATE to payloads; this mode is negotiated in join_ack and is not changed on the fly (server restart required; clients must reconnect).
|
||||
- Client decompresses if set; otherwise reads packed structs directly.
|
||||
|
||||
Packet partitioning (if >1280 bytes after compression):
|
||||
@@ -122,6 +130,11 @@ Sequence wrap & ordering:
|
||||
- Clients ignore updates older-than last applied per-sender.
|
||||
- For input_broadcast, deduplicate per (player_id, input_seq) and apply in order per player; if apply_at_tick is in the past, apply immediately up to current tick.
|
||||
|
||||
Live config updates (server -> clients):
|
||||
- Packet type: config_update, sent on change and periodically (low frequency).
|
||||
- Fields: tick_rate (u8), wrap_edges (bool), apples_per_snake (u8), apples_cap (u8 up to 255).
|
||||
- Clients apply changes at the next tick boundary. Compression mode is not changed via config_update (handshake-only).
|
||||
|
||||
## Simulation Details
|
||||
- Tick order per tick:
|
||||
1) Incorporate at most one valid buffered input per snake (with 180-degree rule).
|
||||
@@ -129,7 +142,7 @@ Sequence wrap & ordering:
|
||||
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 tick's 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).
|
||||
- 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). Wrap-around rules are applied only to the head; if wrap_edges is turned off while a body segment is mid-edge traversal, allow the body to continue, but prevent the head from crossing walls thereafter.
|
||||
- 180-degree turn allowance when length == 1 only.
|
||||
|
||||
### Client-side Opponent Prediction Heuristics
|
||||
@@ -147,7 +160,11 @@ Sequence wrap & ordering:
|
||||
- Players: max 32 (ids 0..31); deny joins beyond capacity.
|
||||
- Names: UTF-8, truncate to 16 bytes; filter control chars.
|
||||
- Field: default 60x40; min 3x3; max 255x255; provided by server in join_ack.
|
||||
- Tick rate: default 15 TPS; configurable 10-30 TPS.
|
||||
- Tick rate: default 10 TPS; server-controlled, reconfigurable on the fly via config_update (reasonable range 5-30 TPS).
|
||||
- Apples per snake: default 1; server-controlled, reconfigurable on the fly; min 1/snake, max 12/snake; total apples capped at 255.
|
||||
- Wrap-around edges: default off (walls are blocking); server-controlled, reconfigurable on the fly; head obeys current rule; legacy body traversal allowed during transitions.
|
||||
- Compression (DEFLATE): global mode negotiated at join; requires server restart to change; clients must reconnect; when enabled server may set flags.compressed=1.
|
||||
- Browser targets: must work on latest Firefox; latest Chrome is optional but desirable; others optional.
|
||||
|
||||
## Error Handling & Resilience
|
||||
- Invalid packets: drop and optionally send error with code.
|
||||
@@ -172,13 +189,9 @@ Sequence wrap & ordering:
|
||||
8) 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).
|
||||
- Should input_broadcast include server-assigned apply_at_tick or relative tick offsets only?
|
||||
- Interpolation/smoothing specifics on client for variable tick rates.
|
||||
- Any additional anti-cheat measures for input_broadcast misuse.
|
||||
- Exact UI/UX for join denial when field is full (messaging/timing).
|
||||
|
||||
## Next Steps
|
||||
- Validate the protocol choices and mechanics with stakeholders.
|
||||
|
||||
Reference in New Issue
Block a user