Files
CosmicClash/multiplayer-next.md
T
Josh Creek 4ffa1543cc docs: design per-match server autoscaling, with measured boot time
Casual/ranked queues need servers allocated per match and shut down
afterwards, so cost is incurred only while a match runs - while the
existing Docker and CI gates keep passing unchanged.

Measured against the repo's own cosmicclash-server image rather than
estimated: the runtime image is ~148 MB of content, and boot to the
server_started line is ~870 ms on the container's own clock. That was
taken under x86_64 emulation on an arm64 host, so it is a pessimistic
bound and is recorded as one - it needs re-measuring on native Linux
before it sets any timeout.

Two findings that would each break a naive implementation, both hit
while taking that measurement:

- Godot's stdout is block-buffered off a TTY. A detached container logs
  nothing at all - server_started does not appear even after 35s - so an
  orchestrator readiness probe that greps the log hangs forever. Probe
  the UDP socket or flush explicitly.
- --port defaults to 7777 and the Dockerfile hardcodes EXPOSE 7777/udp,
  so several matches cannot share a host without a port range or an
  address per match. Being UDP, L7 ingress routing does not apply.

Also records the honest tension in 'only pay during a match': a server
must listen before players connect, and image pull plus scheduling can
dwarf 870 ms, so the recommendation is match-level scale-to-zero over a
small warm node pool rather than node-level scale-to-zero.

The rule for keeping verify-phase6 and verify-enet-integration green:
every allocation feature is opt-in via a ServerConfig flag defaulting to
current behaviour, with a second Compose file rather than mutating
compose.phase6-smoke.yml.
2026-08-31 18:45:05 +01:00

4.7 KiB
Raw Blame History

Multiplayer — next work

Short, current checklist for online multiplayer. Historical design decisions, implementation evidence, and completed work stay in multiplayer-todo.md.

Release blockers

  • Phase 4 playtest: a human playtest at roughly 100 ms RTT. Confirm that ship and ball interaction feel local and contact corrections feel like bumps rather than glitches.
  • Phase 5 session: complete a real 3v3 match with a mid-match disconnect and late joiner.
  • Phase 6 external check: run the exported Docker server and clients from separate machines over the internet, then play a full match. Keep this controlled-only until Steam identity is complete.

Phase 7 — Steam, identity, discovery

  • Obtain the pinned GodotSteam client/server builds and Steamworks SDK access described in STEAM.md.
  • Run make verify-steam-templates with the custom executables and fix any custom-template failures.
  • Validate a two-account Steam SDR host/join using the existing explicit NetworkManager Steam transport. ENet direct-IP must keep passing its smoke test.
  • Build the Steam server browser: internet, LAN, favourites, and history.
  • Add Steam auth tickets, verified Steam identity in the roster, and a persistent ban list. This fixes the slot-reclaim security issue below.

Phase 8 — casual and ranked matchmaking (1.0 launch blocker)

Design and reasoning: docs/MATCHMAKING.md. This is a different server model from the community-server one that exists today — players queue, a matchmaker groups them, and a server is allocated per match. Phase 7's Steam auth tickets are a hard prerequisite: a rating attached to a spoofable identity is worse than no rating.

  • Decide the rating algorithm (Glicko-2 recommended over Elo for a small launch population) and how a team result distributes across individuals.
  • Choose the backend language and hosting, and cost out allocated servers per match at expected population.
  • Stand up the backend: Steam auth ticket validation via the Steamworks Web API, queue, rating store, server allocator.
  • Add an assigned-roster server mode so only matched SteamIDs may claim a slot, replacing the first-come model.
  • Add server-authoritative match result reporting to the backend over a channel a client cannot forge.
  • Client queue UI: playlist select, estimated wait, accept/decline, connect-on-assignment, post-match rating delta.
  • Casual and ranked playlist rulesets (backfill, bots, abandon penalties, arena restriction — see the comparison table in the design doc).

Server orchestration (same phase — the servers must autoscale and bill only for the duration of a match):

  • Choose the orchestrator (Agones on Kubernetes is the recommended default; it models allocation, readiness and per-match lifetime natively).
  • Fix readiness detection. Godot's stdout is block-buffered off a TTY, so server_started never appears in docker logs for a detached container — a log-grep readiness probe hangs forever. Probe the UDP socket, or flush.
  • Support more than one match per host: a per-container port from a range, or one address per match. --port defaults to 7777 and the Dockerfile hardcodes EXPOSE 7777/udp.
  • Add a no-show timeout so an allocated server that never fills abandons and exits instead of idling at cost.
  • Re-measure boot-to-listening on native x86_64 Linux. The repo's current figure is ~870 ms, measured under emulation on arm64 — a pessimistic bound.
  • Re-measure the SERVER.md sizing estimate (610 processes/core, 150250 MB RSS) under real concurrency before it sizes a bill.
  • Keep make verify-phase6 and make verify-enet-integration green: every allocation feature is opt-in via a ServerConfig flag defaulting to today's behaviour, with a second Compose file for the allocated path rather than mutating compose.phase6-smoke.yml.

Known issues to resolve before public hosting

  • Slot reclaim is currently keyed by display name, so someone can take a disconnected player's reserved slot. Do not expose public servers before verified Steam identity lands.
  • Investigate occasional input loss during a long server stall; the existing sequence resync recovers it, but transport delivery is variable.
  • Fix the remaining _broadcast_snapshot packet-send stderr race.

Decide after the latency playtest

  • Decide whether client-only, contact-cohort shadow physics is worthwhile for the remaining prediction weakness.

Explicitly deferred

120 Hz simulation, latency-gap measurement, audio hooks, and split-screen are not part of the current multiplayer release path.