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
+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,