edcda92812
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>
3.8 KiB
3.8 KiB
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
- Client predicts own movement locally (feels instant)
- Server confirms or corrects (reconciliation)
- Other players interpolated between last two known positions
- 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):
- Client sends
POST /matchmaking/queue/joinwith callsign + mode - Backend queues the ticket; a background loop forms a match once enough players are queued for that mode
- Match forms → backend assigns the first available registered
GameServerfor that mode - Client polls
GET /matchmaking/queue/status/{ticket}untilmatched, 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)