diff --git a/CLAUDE.md b/CLAUDE.md index 8327e120..a0532eea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ Important rule: never create co-authored commits. Never mention Claude in commit ## Project overview -Cosmic Clash is an open-source, physics-based "vehicle soccer" game (a spiritual successor to Rocket League) built in Godot 4.7, using space ships instead of cars. It is GDScript/Godot only — the "C# backend" in README.md was never started and is not the plan; the dedicated server is an export of this same Godot project. README.md's "MVP is local-only against bots" section is historical: server-authoritative online multiplayer, a headless dedicated server, Docker/CI verification, and an optional Steam transport are all implemented (Phases 1–6). See `multiplayer-next.md` for what actually remains. +Cosmic Clash is an open-source, physics-based "vehicle soccer" game (a spiritual successor to Rocket League) built in Godot 4.7, using space ships instead of cars. It is GDScript/Godot only today — the "C# backend" in README.md was never started, and the dedicated server is an export of this same Godot project. A **separate backend service is now planned** (not started) for casual/ranked matchmaking, which is a 1.0 launch blocker; see `docs/MATCHMAKING.md`. README.md's "MVP is local-only against bots" section is historical: server-authoritative online multiplayer, a headless dedicated server, Docker/CI verification, and an optional Steam transport are all implemented (Phases 1–6). See `multiplayer-next.md` for what actually remains. Because the gameplay concept (vehicle soccer) can't be copyrighted but specific expression can, all code/art/assets must be original — this is why the project uses Godot instead of Unreal/Unity and ships instead of cars. Keep this in mind when writing code or pulling in assets: don't port or closely mirror Rocket League's actual implementation. @@ -20,6 +20,8 @@ The prose docs carry far more design rationale than the code comments, and sever - `SERVER.md` — dedicated-server build, config, systemd deploy, sizing. - `STEAM.md` — optional GodotSteam custom-build setup and the transport contract. - `FLIGHT_MANUAL.md` — the player-facing flight model. +- `docs/MATCHMAKING.md` — casual/ranked queue design. Not implemented; a 1.0 launch blocker, and the reason a backend service now exists in the plan. +- `docs/TECH_STACK.md` — what the project is built with and why. - `TODO.md` — deferred non-multiplayer work (audio is the big one: there is none at all). ## Godot MCP server diff --git a/README.md b/README.md index aa4a4b10..a1248ba7 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ The concept of 'vehicle soccer' cannot be copyrighted, but the original expressi ## Technical Information -Cosmic Clash is a single Godot 4.7 project, written entirely in GDScript — there is no separate C# backend. That same project exports both the interactive game and a headless dedicated server for online multiplayer. See [`docs/TECH_STACK.md`](docs/TECH_STACK.md) for the full stack and the reasoning behind each choice. +Cosmic Clash is a single Godot 4.7 project, written entirely in GDScript. That same project exports both the interactive game and a headless dedicated server for online multiplayer. See [`docs/TECH_STACK.md`](docs/TECH_STACK.md) for the full stack and the reasoning behind each choice. + +Online play with casual and ranked queues is a 1.0 requirement, and it needs a small backend service for identity, matchmaking and ratings — separate from the Godot project, and not yet built. See [`docs/MATCHMAKING.md`](docs/MATCHMAKING.md). ## Contributing diff --git a/docs/MATCHMAKING.md b/docs/MATCHMAKING.md new file mode 100644 index 00000000..5c1fbe96 --- /dev/null +++ b/docs/MATCHMAKING.md @@ -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. diff --git a/docs/TECH_STACK.md b/docs/TECH_STACK.md index e3aa79e2..84cc34fb 100644 --- a/docs/TECH_STACK.md +++ b/docs/TECH_STACK.md @@ -176,10 +176,21 @@ Python, no .NET, no network." Keeping the shipped game GDScript-only (no ## What's deliberately absent -- **No C# or .NET runtime anywhere in the shipped game or server**, despite - early project framing (see `README.md`'s history) once describing a - "C# backend" — that was never built and is not the current plan. +- **No C# or .NET runtime anywhere in the shipped game or server.** The + "C# backend" in `README.md`'s early framing was never built. A backend + service *is* now planned for matchmaking (see below), but nothing has + chosen C# for it — that framing predates every real decision here. - **No HTTP/WebSocket/gRPC layer** for multiplayer — ENet/Steam SDR over UDP - only, via Godot's own `MultiplayerAPI`. + only, via Godot's own `MultiplayerAPI`. Matchmaking will add the project's + first non-UDP network path, for backend traffic only; the simulation stays + on ENet/SDR. - **No ONNX or other ML runtime in the shipped game** — see "AI opponents" above. + +## Planned, not yet built + +- **A matchmaking backend service** — Steam auth ticket validation, casual + and ranked queues, a rating store, and per-match dedicated-server + allocation. Language and hosting are undecided. This is a 1.0 launch + blocker and the single largest departure from "one Godot project, no + backend". See [`MATCHMAKING.md`](MATCHMAKING.md). diff --git a/multiplayer-next.md b/multiplayer-next.md index 6ca2e709..e9c459d4 100644 --- a/multiplayer-next.md +++ b/multiplayer-next.md @@ -28,6 +28,29 @@ implementation evidence, and completed work stay in - [ ] 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`](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). + ## Known issues to resolve before public hosting - [ ] Slot reclaim is currently keyed by display name, so someone can take a