diff --git a/CLAUDE.md b/CLAUDE.md index 144ddf49..b5594191 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 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. +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. The game and the dedicated server are GDScript/Godot only — the "C# backend" an early README described was never started, and the dedicated server is an export of this same Godot project. There is one component outside the Godot project: a **Go matchmaking control plane** in `server/` for casual/ranked queues, ranked ratings and Agones-based server allocation. It is partially implemented and a 1.0 launch blocker — see `docs/MATCHMAKING.md` for the design, `multiplayer-next.md` §0 and §7 for what remains (a real deployment cannot complete a match end to end today), and `docs/TECH_STACK.md` for why the control plane is Go rather than C#, Rust or C++. 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. @@ -19,8 +19,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. +- `docs/MATCHMAKING.md` — casual/ranked queue design, and the locked constraints (Go/PostgreSQL/Redis/Agones) the `server/` module implements. Partially implemented; a 1.0 launch blocker, and the reason a backend service outside the Godot project exists at all. +- `docs/TECH_STACK.md` — what the project is built with and why, including the Go-vs-C#/Rust/C++ rationale for the matchmaking control plane. - `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 3f11c5f3..f2f17960 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The concept of 'vehicle soccer' cannot be copyrighted, but the original expressi 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). +Online play with casual and ranked queues is a 1.0 requirement, and it needs a backend service for identity, matchmaking and ratings — separate from the Godot project. That control plane is written in Go (with PostgreSQL, Redis and Agones on Kubernetes), chosen for the Agones/Kubernetes-native ecosystem rather than for raw speed: it never touches a simulation packet. See [`docs/MATCHMAKING.md`](docs/MATCHMAKING.md) for the design and [`docs/TECH_STACK.md`](docs/TECH_STACK.md) for why Go over C#, Rust or C++. ## Contributing diff --git a/docs/MATCHMAKING.md b/docs/MATCHMAKING.md index e1dc171c..c8a1d67b 100644 --- a/docs/MATCHMAKING.md +++ b/docs/MATCHMAKING.md @@ -29,7 +29,11 @@ Locked constraints: SDR. Direct ENet remains first-class for local development, CI, LAN, self-hosting, and community servers. - The control plane is Go, PostgreSQL, and Redis, deployed on Kubernetes. - Agones owns game-server allocation and lifecycle. + Agones owns game-server allocation and lifecycle. Go is chosen for the + Agones/Kubernetes-native client ecosystem and its concurrency model, not + for raw speed — the control plane never touches a simulation packet. See + "Matchmaking control plane" in [`TECH_STACK.md`](TECH_STACK.md) for the + full rationale and the alternatives weighed. - Infrastructure is provider-portable. Provider-specific cluster, network, DNS, and secret-store configuration lives behind isolated deployment overlays; application code never calls a provider allocation API. diff --git a/docs/TECH_STACK.md b/docs/TECH_STACK.md index edd0915b..6cf3c885 100644 --- a/docs/TECH_STACK.md +++ b/docs/TECH_STACK.md @@ -133,6 +133,50 @@ most familiar with. That matches what's independently visible in the repo — (`make verify-phase6`), while the systemd unit is native-deployment documentation only, with no automated verification of its own. +## Matchmaking control plane: Go, PostgreSQL, Redis, Agones + +The one part of the project that is *not* the Godot project. `server/` is a Go +module (~13k lines of non-test code across `matcher`, `allocator`, `api`, +`store`, `security`, `supervisor`, `agones`, `migrations`, `observability`) +implementing the casual/ranked queue design in +[`MATCHMAKING.md`](MATCHMAKING.md), plus a small PID-1 supervisor that exists +because Godot/GDScript cannot intercept `SIGTERM` and Agones needs a graceful +drain signal to land somewhere. + +**Why Go, and why "performance" is the wrong reason to give:** the control +plane is not in the simulation hot path. Physics, snapshots and 60 Hz input +all live in the Godot dedicated server over ENet/SDR (see the transport +sections above); Go never touches a game packet. Its actual workload is many +mostly-idle WebSocket connections, a matcher loop that runs on a sub-second +tick, and I/O against PostgreSQL, Redis and the Kubernetes API. That is +I/O- and concurrency-bound, not CPU-bound, so the raw single-thread speed a +systems language would buy is spent on work this service doesn't do. What +actually drove the choice: + +- **Agones and Kubernetes are Go-native.** Allocation, the GameServer SDK and + the k8s client are all first-party Go. Any other language means hand-rolling + REST against the Agones allocation service — see `server/agones/`, which uses + those clients directly. +- **Goroutines plus `context` are the right shape for the problem** — many + concurrent idle connections, a few periodic loops, and cancel-everything-on- + shutdown semantics that the PID-1 supervisor depends on. +- **The surrounding operational ecosystem is Go** — Prometheus instrumentation + (`server/observability/`), structured logging, migrations, and the + provider-portable deployment tooling. +- **Static binaries and slim containers**, which matters for the supervisor and + for keeping the allocated game-server image close to the existing one. + +**Alternatives, honestly weighed:** Rust or C++ would be the correct answer for +a custom UDP relay or the simulation server itself, and buy nothing measurable +for a queue-and-allocate service — while costing significantly in iteration +speed. C# is the only serious contender (ASP.NET Core is fast, its async model +is excellent, and Postgres/Redis/WebSocket support is mature); it loses on the +Agones/Kubernetes side, where the clients are community-maintained rather than +first-party, and on container weight. TypeScript or Python would prototype +faster but fit poorly for a service whose failure modes are almost entirely +races and timeouts. None of those gaps is large enough to justify rewriting the +Go that already exists. + ## AI opponents: reinforcement learning, trained out-of-process, run in pure GDScript Two entirely separate pieces, deliberately joined only at a JSON file: @@ -177,9 +221,11 @@ 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.** 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. + "C# backend" an early version of `README.md` described was never built — + that wording is long gone from the README itself. A backend + service *does* now exist for matchmaking, but it is Go, not C# — that + framing predates every real decision here. See "Matchmaking control plane" + above for why Go was chosen over C# and over Rust/C++. - **No HTTP/WebSocket/gRPC layer for simulation traffic** — the live game uses ENet/Steam SDR over UDP via Godot's own `MultiplayerAPI`. The matchmaking control plane now has an authenticated Go REST/WebSocket boundary for queue,