docs: explain why the matchmaking control plane is Go, fix stale status

Add a Go-vs-C#/Rust/C++ rationale for the matchmaking control plane to
TECH_STACK.md, and point at it from MATCHMAKING.md and README.md.

Also correct CLAUDE.md and README.md, which still described the backend
as unstarted/not built even though server/ has ~13k lines of Go across
matcher, allocator, api, store, security, supervisor and agones.
This commit is contained in:
Josh Creek
2026-09-04 19:10:09 +01:00
parent ad9289fb21
commit de263f30e8
4 changed files with 58 additions and 8 deletions
+3 -3
View File
@@ -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 16). 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 16). 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
+1 -1
View File
@@ -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
+5 -1
View File
@@ -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.
+49 -3
View File
@@ -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,