diff --git a/docs/TECH_STACK.md b/docs/TECH_STACK.md index 2d867b26..9b6c7879 100644 --- a/docs/TECH_STACK.md +++ b/docs/TECH_STACK.md @@ -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 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 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 .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) - **`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 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 - **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 runnable matcher, allocator and maintenance roles backed by PostgreSQL and Redis, deployed on provider-portable Kubernetes with Agones-managed game - fleets. The authenticated API boundary exists; durable production wiring and - provider deployment remain. The cloud provider remains deliberately - replaceable; the application stack is locked. + fleets. The durable wiring now exists end to end — queue, latency probes, + proposal, allocation, signed assignment rosters and result submission — and + 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 Godot project, no backend". See [`MATCHMAKING.md`](MATCHMAKING.md).