Files
CosmicClash/docs/MATCHMAKING.md
T
Josh Creek 3aa0f5b9c2 docs: scope casual and ranked matchmaking as a 1.0 launch blocker
Queued matchmaking had never been considered anywhere in the planning
docs - not as planned work, and not even on the explicitly-deferred
list. It is a launch requirement, so record the design before code.

Add docs/MATCHMAKING.md covering the model change (community-server ->
per-match allocation), the decision to use Steam for identity and a
project-owned backend for queue/rating/allocation, what the existing
server already provides (--max-matches=1 is the allocation primitive,
ServerConfig, the roster, MatchState), the casual/ranked ruleset split,
and the open questions - rating algorithm, team-to-individual rating,
and the server cost that allocated matches reintroduce.

Ranked is hard-blocked on Phase 7 Steam auth tickets: slot reclaim is
keyed by display name today, and a rating on a spoofable identity is
worse than no rating.

Add Phase 8 to multiplayer-next.md, and correct README/CLAUDE.md/
TECH_STACK.md, which asserted no backend exists or is planned - true
before this was scoped, wrong now.
2026-08-31 18:33:36 +01:00

7.4 KiB

Matchmaking — casual and ranked queues

Design scope for online casual and ranked play. This is a 1.0 launch blocker, not a post-launch addition.

Nothing described here is implemented yet. This doc exists to record the decisions and the reasoning before code is written; per-task implementation evidence belongs in multiplayer-todo.md once work starts, and the live checklist lives in multiplayer-next.md.

The model change

The multiplayer that exists today is a community-server model. A dedicated server runs forever: it waits for --min-players by roster, counts down --start-countdown, loads the next arena from the rotation, plays a match, returns to the lobby, and repeats (server_match_loop.gd). Players reach it by direct IP, and after Phase 7 by a Steam server browser. The server is the durable thing and players come and go around it.

Queued matchmaking inverts that. Players are the durable thing: they enter a queue, a matchmaker groups them by rating and region, and a server is allocated for that one match and torn down afterwards. Both models can coexist — community servers via the browser, queues via the matchmaker — and they should, because the server browser is already most of the way to done.

Hard prerequisite: verified identity

Ranked cannot ship before Phase 7's Steam auth tickets.

Slot reclaim is currently keyed by display name (see --slot-reservation-seconds, and the known-issues list in multiplayer-next.md). A rating attached to a spoofable identity is worse than no rating at all: it is trivially farmed, and it invites players to invest in a ladder that cannot be defended. "Ranked is critical" therefore raises the priority of Steam identity rather than routing around it.

Casual queueing has a weaker requirement — it still needs stable identity for abandon penalties and ban enforcement, but the cost of a compromise is lower.

Architecture

Decided: Steam for identity, a project-owned backend for everything else.

This reverses the "no backend" position stated in TECH_STACK.md and README.md, which described the state of the project before matchmaking was scoped. The dedicated server remains a Godot export; the new service is separate from it.

The alternative — Steam-native matchmaking (lobbies plus Leaderboards or User Stats as the rating store) — was rejected on two grounds. Steam lobby matchmaking has no real concept of a skill distribution to match against, and Leaderboards are a display surface rather than a rating store with the transactional guarantees a ladder needs. It would also permanently bind the game to Steam, foreclosing other platforms.

Components

Component Runs where Responsibility
Steam auth ticket validation backend Turn a client-supplied ticket into a verified SteamID via the Steamworks Web API. The only trusted source of identity.
Queue / matchmaker backend Hold queued players per playlist and region; form matches on rating proximity with a widening tolerance over wait time.
Rating store backend (DB) Per-identity, per-playlist rating and match history. Written only by the backend, never by a game client.
Server allocator backend Start a dedicated-server instance per formed match, hand its address to the matched clients, reclaim it on exit.
Dedicated server Godot export Unchanged simulation. Gains a mode where the roster is assigned rather than open, and reports a result at the end.
Game client Godot Queue UI, estimated wait, accept/decline, connect-on-assignment, post-match rating delta.

What already exists and gets reused

The server side needs less new work than it looks:

  • --max-matches=1 already makes the server drain and exit(0) after a single match. That is precisely the lifecycle a per-match allocator wants; it was built for CI, and it generalises for free.
  • ServerConfig is a single-source-of-truth flag table with strict validation — new allocation flags are declared in one place and are automatically parsed, type-checked, config-file-backed and documented.
  • --min-players / --start-countdown / --slot-reservation-seconds are the match-formation primitives, and they already count roster members rather than raw peers.
  • MatchNet's roster already survives the lobby→match transition, which is the structure an assigned roster slots into.
  • MatchState already has a legal-transition table with wire-stable integer values, so new lifecycle states append cleanly.

What is genuinely new

  • The backend service itself (process, deploy, DB, ops) — nothing like it exists in this repo today.
  • Server-authoritative match results: the dedicated server must report the outcome to the backend over a channel a client cannot forge. This is the first non-ENet/SDR network path in the project (see TECH_STACK's "no HTTP layer" note, which this supersedes).
  • An assigned-roster server mode: only the matched SteamIDs may take a slot, replacing the current first-come model.
  • Client-side queue UI and the accept/decline flow.

Casual vs ranked

They are different playlists, not a difficulty toggle, and their rules diverge in ways that affect the server:

Casual Ranked
Rating Hidden, used only for matching Visible, with tiers
Backfill on disconnect Yes — keep the match playable No — the match is rating-bearing and must not change shape mid-way
Bots filling empty slots Acceptable (--fill-bots exists) Never
Abandon penalty Light (short queue cooldown) Real (rating loss, escalating cooldown)
Arena selection Full rotation Restricted set, so a variant nobody has practised can't decide a ladder match
Party / premade Unrestricted Constrained by size and rating spread

Note the arena constraint interacts with an existing rule: elevated-goal variants are Free-Play-only until a checkpoint trained on training_elevated.tscn is promoted (arena_registry.gd). Ranked's arena set should be drawn from "random": true arenas only.

Open questions

  • Rating algorithm. Glicko-2 is the default recommendation over plain Elo: it models rating uncertainty, which matters enormously for a small launch population where most players have few games. Not yet decided.
  • Team rating from individual ratings. How a 3v3 match's outcome distributes across six players is a separate design problem from the rating system itself.
  • Server cost. Allocated servers cost real money per match, unlike community servers that players host themselves. README.md's original note about a subscription to fund servers is suddenly load-bearing again. Population size and match length set the bill; this needs a number before launch, not after.
  • Region / ping policy. How much rating tolerance to trade for latency, and whether cross-region is ever allowed at low population.
  • Placement matches and whether ranked has a soft reset per season.
  • Backend language and hosting. Not chosen. It does not have to be C# despite the original README framing — that framing was aspirational and predates every real decision in this project.

Explicitly out of scope

Tournaments, in-game leaderboards beyond a personal rank display, cross-platform play with non-Steam identity providers, and spectator/observer tooling for ranked matches. None are precluded by this design; none are launch scope.