mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
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.
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user