docs: add a version inventory to TECH_STACK.md, refresh stale claims

TECH_STACK.md explained why each choice was made but never listed what is
actually pinned, so there was no single place to answer "what version of X do
we use". Adds a Version inventory section covering the shipped game, the Go
control plane's four direct dependencies, the datastore/platform versions and
the exactly-pinned training stack, plus a Verification toolchain subsection for
the Make/Docker/kind/Kustomize/Actions harness.

Also corrects two things the doc had outgrown: the allocation pipeline is now
wired end to end and gated in CI, so only the provider deployment remains; and
the Steam section covered only the GodotSteam client transport, omitting the
server-side Web API ticket verifier in server/steam.
This commit is contained in:
Josh Creek
2026-09-05 23:16:10 +01:00
parent 52cc478b38
commit fe453ab607
+113 -3
View File
@@ -105,6 +105,15 @@ port-forwarding requirement and to supply verified player identity —
direct-IP ENet's slot-reclaim logic is keyed by display name today, which is direct-IP ENet's slot-reclaim logic is keyed by display name today, which is
insecure against a public server (`multiplayer-next.md` §0, known defect C). insecure against a public server (`multiplayer-next.md` §0, known defect C).
There is a **second, independent** use of Steam that does not involve
GodotSteam at all: `server/steam/` verifies session tickets server-side against
Valve's `ISteamUserAuth/AuthenticateUserTicket` Web API over plain HTTP, which
is what turns a claimed identity into a trusted one for matchmaking and for
slot reclaim. It distinguishes "Valve rejected this ticket" (401) from "Valve
is unreachable" (503) so an outage cannot be mistaken for an authentication
failure, and refuses family-shared and banned accounts. It needs a **publisher
Web API key**, which is a server-side secret and must never reach a client.
## Dedicated server hosting: Docker (primary) or native systemd ## Dedicated server hosting: Docker (primary) or native systemd
The dedicated server is not a separately-written service — it's the same The dedicated server is not a separately-written service — it's the same
@@ -206,6 +215,81 @@ file and runs **inside the game** in pure GDScript — shipped bots need no
Python, no .NET, no network." Keeping the shipped game GDScript-only (no Python, no .NET, no network." Keeping the shipped game GDScript-only (no
.NET Godot build) is consistent with the rest of the stack. .NET Godot build) is consistent with the rest of the stack.
## Version inventory
Everything the project actually pins, in one place. The rest of this document
explains *why* these were chosen; this is *what* is in use. Versions here are
the source of truth's values at the time of writing — when they disagree with
the files named, the files win.
### Shipped game and dedicated server
| Thing | Version | Pinned in |
|---|---|---|
| Godot | 4.7.1 | `Dockerfile` (digest-pinned `barichello/godot-ci`) |
| Physics | Jolt | `Game/project.godot``3d/physics_engine="Jolt Physics"` |
| Runtime dependencies | none | pure GDScript; no .NET, no ONNX, no native extensions in the default build |
| GodotSteam | custom build, opt-in | `steam-dependencies.lock.json` |
The shipped client and server carry **no third-party runtime dependency at
all** in the default ENet build. That is a deliberate constraint, not an
accident of scope — see "What's deliberately absent".
### Matchmaking control plane (Go)
| Thing | Version | Notes |
|---|---|---|
| Go | 1.23 | `server/go.mod` |
| `jackc/pgx/v5` | 5.7.4 | PostgreSQL driver; used through `database/sql` for pooling, and directly for `LISTEN`/`NOTIFY`, which needs a dedicated session |
| `redis/go-redis/v9` | 9.7.0 | transient candidate index only; the durable queue is PostgreSQL |
| `alicebob/miniredis/v2` | 2.38.0 | test-only in-process Redis |
Four direct dependencies, three of them drivers. There is no web framework, no
ORM, no DI container and no code generation: HTTP is `net/http` with a hand-
written mux (`server/api/service.go`), SQL is hand-written, and migrations are
numbered `.sql` files under `server/migrations/` — each with a `down/`
counterpart — applied by the `cmd/migrate` binary. That is a deliberate choice
about a service whose whole job is a small number of carefully-fenced
transactions.
Rating maths is Glicko-2, implemented in `server/domain/rating.go` rather than
taken from a library.
### Datastores and platform
| Thing | Version | Pinned in |
|---|---|---|
| PostgreSQL | 17 (alpine) | `compose.*.yml`, `scripts/run_*_integration.sh` |
| Redis | 7 (alpine) | `compose.*.yml`, `scripts/run_redis_integration.sh` |
| Agones | 1.49.0 | `scripts/verify_kind_agones.sh` (`AGONES_VERSION`) |
| Kubernetes | 1.33 in CI | `kindest/node:v1.33.1` |
| Manifests | Kustomize | `deploy/k8s/base` + `overlays/{eu,na}` |
| Metrics | Prometheus | `deploy/observability/` — ServiceMonitors and PrometheusRules |
Container images are referenced by digest, never by tag; `scripts/verify_supply_chain.py`
fails the build on any mutable reference. All six digests under `deploy/` are
currently all-zero placeholders, and the `ghcr.io/cosmic-clash/*` registry
namespace does not exist yet — publishing the images is the open work tracked
in issue #31, and is the last thing standing between the manifests and a real
deployment.
### Training (out-of-process, not shipped)
| Thing | Version |
|---|---|
| Python | 3.12 |
| `godot-rl` | 0.8.2 |
| `stable-baselines3` | 2.4.0 |
| `torch` | 2.13.0 |
| `gymnasium` | 1.0.0 |
| `tensorboard` | 2.21.0 |
Pinned exactly, and `training/requirements.txt` explains why in unusual detail:
the curriculum depends on specific library *internals* rather than documented
public APIs, so an unpinned reinstall could silently change behaviour partway
through a 12-hour training stage. None of this ships — the game runs exported
policies through a pure-GDScript MLP.
## Tooling (not shipped with the game) ## Tooling (not shipped with the game)
- **`mcp/godot-mcp`** (git submodule, Node/TypeScript) — drives a live - **`mcp/godot-mcp`** (git submodule, Node/TypeScript) — drives a live
@@ -218,6 +302,29 @@ Python, no .NET, no network." Keeping the shipped game GDScript-only (no
(Blender's embedded Python, plus texture generators) used to produce the (Blender's embedded Python, plus texture generators) used to produce the
project's original meshes and textures. project's original meshes and textures.
### Verification toolchain
Everything is driven from `Makefile` targets so that CI and a local run are the
same command:
- **GNU Make** — the single entry point (`verify-phase6`,
`verify-enet-integration`, `verify-kind-agones`, `verify-supply-chain`, …).
- **Docker and Docker Compose** — the multi-process gates. The game gates use
a staged `Dockerfile`; the control-plane gates use `compose.*.yml` fixtures.
- **kind** (`kindest/node:v1.33.1`) **and Helm** — a throwaway Kubernetes
cluster with Agones installed, for the allocation gate.
- **Kustomize** — `deploy/k8s/base` plus `overlays/{eu,na}`, validated by
`kubectl kustomize` in CI rather than only at deploy time.
- **GitHub Actions** — eight workflows under `.github/workflows/`, each one a
thin wrapper around a Make target, with path filters so a docs-only change
doesn't spin up a Kubernetes cluster.
- **`scripts/verify_supply_chain.py`** — fails the build on any mutable image
reference, which is why every manifest pins by digest.
Godot itself has **no build step and no linter** — the project runs from
source, so "the tests pass" is the only mechanical check that exists on the
GDScript side.
## What's deliberately absent ## What's deliberately absent
- **No C# or .NET runtime anywhere in the shipped game or server.** The - **No C# or .NET runtime anywhere in the shipped game or server.** The
@@ -238,8 +345,11 @@ Python, no .NET, no network." Keeping the shipped game GDScript-only (no
- **The remaining Go matchmaking control-plane deployment** — independently - **The remaining Go matchmaking control-plane deployment** — independently
runnable matcher, allocator and maintenance roles backed by PostgreSQL and runnable matcher, allocator and maintenance roles backed by PostgreSQL and
Redis, deployed on provider-portable Kubernetes with Agones-managed game Redis, deployed on provider-portable Kubernetes with Agones-managed game
fleets. The authenticated API boundary exists; durable production wiring and fleets. The durable wiring now exists end to end — queue, latency probes,
provider deployment remain. The cloud provider remains deliberately proposal, allocation, signed assignment rosters and result submission — and
replaceable; the application stack is locked. is exercised by Compose and kind/Agones gates in CI. What remains is the
provider deployment itself: a registry to publish the images to, and a live
cluster. The cloud provider remains deliberately replaceable; the application
stack is locked.
This is a 1.0 launch blocker and the single largest departure from "one This is a 1.0 launch blocker and the single largest departure from "one
Godot project, no backend". See [`MATCHMAKING.md`](MATCHMAKING.md). Godot project, no backend". See [`MATCHMAKING.md`](MATCHMAKING.md).