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.
A backfilled player's join authorisation is issued after their server
started, but the supervisor fetches the roster once before launching the
game child and the game process has no reload path, so backfill cannot
work end to end regardless of how good the selection rule is.
Decided: the control plane marks the roster changed, the supervisor
re-fetches and rewrites the roster file, then signals the game process to
reload. Chosen because it reuses the authenticated channel and roster
endpoint that already exist -- no inbound path into the game pod, no new
trust boundary -- and keeps the roster an allowlist the server was told
to expect rather than admitting anyone holding a valid signature.
Signature verification is untouched and already binds match, server, slot
and generation.
Recorded in docs/MATCHMAKING.md, which the repo treats as the design
source of truth, so the decision is not re-litigated from a task row.
Remaining implementation is tracked in #32 and summarised in §7 8.19.
The Steam adapter took --steam-publisher-key/--steam-app-id and the
matching env vars, but no manifest supplied them, so a deployed control
plane would have kept sign-in returning 503 even once the App ID from
#15 arrived -- that issue would have unblocked nothing on landing.
Mount them from a new cosmic-clash-steam Secret, into the control-plane
Deployment alone: the publisher key is issued to us, never to a client,
and no other workload (least of all a game server) has any use for it. A
manifest test asserts both the wiring and that the Secret appears in no
other manifest; verified it fails in both directions.
Both keys are optional, so the Deployment still rolls out before the App
ID exists and sign-in simply stays 503.
Also correct task rows this branch made stale: 7.4 (durable ban storage
landed), 8.7 (adapter, bans and secret store landed), 8.39 (cross-replica
fan-out landed), and 8.5's migration range, which stopped at 0013.
Move the branch review into docs/ with a header marking it a point-in-time
artefact -- all thirteen findings are addressed, and its present tense
would otherwise read as current behaviour.
Record gotcha 52: the integration scripts use `docker run --rm`, which
reclaims the container but not its anonymous volume. Sixty-four of them,
~4 GB, accumulated during this session until PostgreSQL stopped starting
-- surfacing only as the script's own readiness timeout, not as a disk
error. That is the real cause behind the "Docker storage exhausted
locally" notes those rows carried.
docs/MATCHMAKING.md is the stated source of truth for this design, so it
changes first: the probe challenge endpoint and why probing gates
matching rather than merely improving it, and the key-ID rotation
procedure that makes overlapping-key rotation concrete.
multiplayer-next.md §0's root blocker is closed rather than deleted --
what it was, why it blocked everything, and how it was resolved, since
the reasoning is what a future reader needs. Tasks 7.6, 8.15 and 8.31
updated to what actually remains, which in every case is now external
rather than unbuilt.
TODO.md #14 asked for a join-signing design decision; that decision is
recorded with its rationale. CLAUDE.md no longer says a real deployment
cannot complete a match end to end.
multiplayer-next.md was a 1662-line mix of standing architecture spec
and task-completion tracking, most of which was dense per-task DONE
evidence for finished Phases 0-6. Split it:
- MULTIPLAYER_SPEC.md (new): the locked architecture decisions, wire
format, server-side input handling, prediction/reconciliation,
latency/frame-rate budget, and match lifecycle state machine -
standing design reference, not task-tracked.
- multiplayer-next.md (trimmed 1662 -> ~370 lines): only outstanding
work remains - §0 status, §7 Phase 7/8 task tables condensed to
"what's left" per task, §8-11 reference material (refactoring notes,
gotchas, testing, flagged items). Phases 0-6 collapsed to a pointer
at git history instead of ~500 lines of DONE evidence.
Also:
- Repointed every `multiplayer-next.md §N` code comment (N 1-6) across
Game/scripts, Game/tools and Game/tests to MULTIPLAYER_SPEC.md, since
those sections moved. Task-number references (`task N.N`, §7-11)
correctly still point at multiplayer-next.md.
- Updated CLAUDE.md's doc index and docs/TECH_STACK.md's spec-section
citations to match.
- TODO.md: added a "what's left to actually finish multiplayer
(human-actionable)" checklist pulled from multiplayer-next.md §0 and
docs/MATCHMAKING.md - things that need a person (hardware, a design
decision, a Steam App ID, hands on a controller), not more agent code.
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.
multiplayer-todo.md and multiplayer-next.md tracked overlapping
information in two places. Fold everything into multiplayer-next.md
(architecture decisions, wire format, task breakdown with checkboxes,
gotchas list, testing notes) and delete multiplayer-todo.md. Section
numbers are unchanged, so existing code comments citing them by
section/task number still resolve; update every such reference to
point at the new filename.
Casual/ranked queues need servers allocated per match and shut down
afterwards, so cost is incurred only while a match runs - while the
existing Docker and CI gates keep passing unchanged.
Measured against the repo's own cosmicclash-server image rather than
estimated: the runtime image is ~148 MB of content, and boot to the
server_started line is ~870 ms on the container's own clock. That was
taken under x86_64 emulation on an arm64 host, so it is a pessimistic
bound and is recorded as one - it needs re-measuring on native Linux
before it sets any timeout.
Two findings that would each break a naive implementation, both hit
while taking that measurement:
- Godot's stdout is block-buffered off a TTY. A detached container logs
nothing at all - server_started does not appear even after 35s - so an
orchestrator readiness probe that greps the log hangs forever. Probe
the UDP socket or flush explicitly.
- --port defaults to 7777 and the Dockerfile hardcodes EXPOSE 7777/udp,
so several matches cannot share a host without a port range or an
address per match. Being UDP, L7 ingress routing does not apply.
Also records the honest tension in 'only pay during a match': a server
must listen before players connect, and image pull plus scheduling can
dwarf 870 ms, so the recommendation is match-level scale-to-zero over a
small warm node pool rather than node-level scale-to-zero.
The rule for keeping verify-phase6 and verify-enet-integration green:
every allocation feature is opt-in via a ServerConfig flag defaulting to
current behaviour, with a second Compose file rather than mutating
compose.phase6-smoke.yml.
Queued matchmaking had never been considered anywhere in the planning
docs - not as planned work, and not even on the explicitly-deferred
list. It is a launch requirement, so record the design before code.
Add docs/MATCHMAKING.md covering the model change (community-server ->
per-match allocation), the decision to use Steam for identity and a
project-owned backend for queue/rating/allocation, what the existing
server already provides (--max-matches=1 is the allocation primitive,
ServerConfig, the roster, MatchState), the casual/ranked ruleset split,
and the open questions - rating algorithm, team-to-individual rating,
and the server cost that allocated matches reintroduce.
Ranked is hard-blocked on Phase 7 Steam auth tickets: slot reclaim is
keyed by display name today, and a rating on a spoofable identity is
worse than no rating.
Add Phase 8 to multiplayer-next.md, and correct README/CLAUDE.md/
TECH_STACK.md, which asserted no backend exists or is planned - true
before this was scoped, wrong now.
CLAUDE.md and README.md described the pre-multiplayer state (local-only
MVP, planned C# backend) even though server-authoritative multiplayer,
the dedicated server, Docker/CI verification, and Steam transport have
since shipped (Phases 1-6). Update both to reflect reality and add a
docs index in CLAUDE.md pointing at multiplayer-next.md as the current
checklist.
- Add docs/TECH_STACK.md, linked from README, explaining the stack and
why it's a single GDScript project with no separate backend.
- Add one TODO item for the video settings menu (missing presets/vsync/
resolution scaling), blocked on the same profiling gate as the
multiplayer 0.17 tasks.
- Pick up editor-generated .gd.uid sidecars and minor project.godot
formatting noise from opening the project in Godot 4.7.