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

101 lines
3.8 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.
# 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
```gdscript
# 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
- [x] Godot ENet server/client setup
- [x] Player input RPC structure
- [x] Position sync with interpolation
- [x] Ship class registration per peer
- [x] Server-authoritative health/death
- [x] 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)