# 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`](../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`](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.