Files
client/overview/tech.md
T
anekdotin edcda92812 Add ENet multiplayer (movement/combat sync) and matchmaking API
Networked play, end to end: connect, get matched, spawn, fight, respawn.

Godot side:
- Replace the single hardcoded Player with per-peer networked ships
  (MultiplayerSpawner + custom spawn_function), driven by client-side
  prediction with server reconciliation for movement.
- Server-authoritative combat: bullets, health, death/respawn all decided
  by the server and broadcast to every peer over RPC.
- Fix a Camera2D bug where Godot auto-promotes the first camera to ever
  enter the scene tree as the active one regardless of its own `current`
  value — with MultiplayerSpawner catch-up replication that was almost
  never the local player's own ship. `make_current()` on the owner's
  camera fixes it; property assignment doesn't reliably override the
  auto-claim.
- Fix two "late joiner never learns already-established state" gaps
  (health, visibility) by folding both into the existing per-tick
  position broadcast instead of relying on one-shot RPCs that only reach
  peers already connected when they fire.
- Make the 2 offered factions in team select match for every player in
  a match: the server decides once and clients either read it directly
  (server's own instance) or request it over RPC, rather than each
  client rolling its own random pair.
- New MatchmakingClient autoload wires the main menu's CASUAL/RANKED
  buttons to the matchmaking API instead of connecting directly.

New matchmaking-api/ (FastAPI + Postgres, Docker Compose):
- Queue/match/stats/ranks endpoints. In-memory matchmaking queue in a
  single API process — no Redis until there's an actual reason to shard
  the queue across instances.
- Background loop forms a match once enough players are queued for a
  mode, assigns the first available registered game server.
- No real server pool yet: one dev server is auto-seeded from
  DEV_SEED_SERVER_IP/PORT; POST /servers/register exists for real
  servers to self-register later but nothing calls it yet.

Docs: CLAUDE.md and overview/tech.md updated to match — networking
checklist mostly checked off, matchmaking backend section rewritten to
describe what was actually built vs. the original Go/Redis plan, and
Current Tasks reordered around what's actually left (bot fill, real
server pool, ranked/MMR refinement, lag compensation).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 22:37:59 -04:00

3.8 KiB
Raw Blame History

Tech Stack

Engine & Language

  • Engine: Godot 4.7 (Forward Plus renderer)
  • Language: GDScript
  • Project path: /mnt/code/spacewar/spacewar/ (inner folder is the Godot project root)
  • Platform target: Linux (Steam Deck native, 1280×800), Windows PC
  • Art style: Pixel art (placeholder sprites in use currently)

See structure.md for the full file tree and scene graph.


Multiplayer Architecture (Planned)

[Client] ──── [Matchmaking Server] ──── [Dedicated Game Server]
                      │
               [Backend API]
               (queue, ranks, accounts)

Authoritative Server Model

  • Dedicated game servers run the authoritative simulation (not player-hosted)
  • Clients send inputs (movement, fire, ability) — server validates and broadcasts results
  • No client-side cheating possible on critical state (position, HP, kills)
  • Player position simulated server-side; clients predict locally and reconcile

Godot Networking Layer

# Server: move a player
@rpc("authority", "call_local", "reliable")
func set_position(new_pos: Vector2) -> void:
    position = new_pos

# Client: send input to server
@rpc("any_peer", "call_remote", "unreliable")
func send_input(dir: Vector2, firing: bool) -> void:
    pass
  • Transport: ENet (UDP, built into Godot) for low-latency game data
  • Reliable channel: critical state (HP, death, respawn)
  • Unreliable channel: high-frequency position/velocity updates

Client-Side Prediction & Lag Compensation

  1. Client predicts own movement locally (feels instant)
  2. Server confirms or corrects (reconciliation)
  3. Other players interpolated between last two known positions
  4. Lag compensation: server rewinds state slightly to validate hitscan shots

Matchmaking Backend

Built — see matchmaking-api/README.md for how to run it. Actual stack diverged from the original plan below:

Component Planned Actual
API Server Go or Node.js FastAPI (Python) — I/O-bound queue/CRUD logic, not a latency-critical hot path, so dev velocity won over raw throughput
Queue / State Redis In-memory in the API process — no Redis; revisit only if the API needs to run as more than one instance
Database (accounts, ranks) PostgreSQL PostgreSQL, as planned
Hosting VPS (Hetzner / DigitalOcean) or self-hosted Not deployed — local Docker Compose only so far

Flow (as built):

  1. Client sends POST /matchmaking/queue/join with callsign + mode
  2. Backend queues the ticket; a background loop forms a match once enough players are queued for that mode
  3. Match forms → backend assigns the first available registered GameServer for that mode
  4. Client polls GET /matchmaking/queue/status/{ticket} until matched, then connects directly to the returned server IP/port

Not yet built: session tokens (server IP/port are handed back unauthenticated — fine for local dev, not for a real deployment), and a real server pool — only one dev server is registered right now (auto-seeded on API startup); POST /servers/register exists for real servers to self-register/heartbeat but nothing calls it yet.

Steam Integration

  • GodotSteam plugin (open source, wraps Steamworks SDK)
  • Handles: Steam auth, VAC anti-cheat, Steam lobbies, achievements
  • No separate account system needed at launch

Networking Checklist

  • Godot ENet server/client setup
  • Player input RPC structure
  • Position sync with interpolation
  • Ship class registration per peer
  • Server-authoritative health/death
  • Matchmaking API (queue + lobby assignment) — client wired end-to-end; real server pool and ranked MMR-window widening still open, see Current Tasks in CLAUDE.md
  • GodotSteam auth + VAC
  • Lag compensation (basic rewind)