From 654f20e28f582052877041789e3af8c96b08c95a Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Sat, 5 Sep 2026 18:09:10 +0100 Subject: [PATCH] docs: record the backfill roster-delivery decision and remaining work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- docs/MATCHMAKING.md | 11 +++++++++++ multiplayer-next.md | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/MATCHMAKING.md b/docs/MATCHMAKING.md index eb990281..749b2dbe 100644 --- a/docs/MATCHMAKING.md +++ b/docs/MATCHMAKING.md @@ -259,6 +259,17 @@ only after the same transition commits. - An original casual participant gets 30 seconds to reconnect; leaving after that applies a 60-second queue cooldown. The match's ordinary hidden-rating result still applies, with no extra rating penalty. +- **Late roster delivery.** 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. The agreed + model is: the control plane marks the roster changed, the supervisor -- which + already holds an authenticated channel to the control plane and already owns + the roster file -- re-fetches and rewrites it, then signals the game process + to reload. This deliberately adds no inbound path into the game pod and no new + trust boundary; the roster stays an allowlist the server is told to expect, + rather than admitting anyone holding a valid signature. Signature + verification is unchanged and already covers match, server, slot and + generation. - An accepted casual initial-connect no-show gets the same 60-second cooldown. The match proceeds with a bot only if at least one human connected on each team; otherwise it cancels and restores every innocent ticket with original diff --git a/multiplayer-next.md b/multiplayer-next.md index 37baf3f2..af848683 100644 --- a/multiplayer-next.md +++ b/multiplayer-next.md @@ -249,7 +249,7 @@ are done; everything below is what's left on the tasks still open. | 8.16 `[D:8.14,8.15]` | Candidate/team formation, matcher worker | The matcher is a real long-running poll loop and now has casual and ranked Deployments in `deploy/k8s/base`; what remains is live soak against a cluster rather than the integration itself ([#17](https://github.com/jcreek/CosmicClash/issues/17)) | | 8.17 `[D:8.14,8.16]` | Proposal policy (response window, cooldowns, offender/innocent split) | Live PostgreSQL execution and allocation integration remain | | 8.18 `[D:8.5,8.14,8.17]` | Store layer (serializable retries, claim SQL, atomic promotion) | Allocation runtime integration remains | -| 8.19 `[D:8.18]` | Casual lineup (2–6 humans, bot backfill) | Casual lineup formation is built and wired (`domain.BuildCasualLineup`, called from `domain/formation.go` and `domain/noshow.go`), and all four penalty kinds are durably written (`PROPOSAL_DECLINED`, `PROPOSAL_TIMEOUT`, `INITIAL_CONNECT_NO_SHOW`, `MATCH_ABANDONED`). What genuinely remains is the opt-in 10 s **backfill proposal** path: `domain.CanCasualBackfill`/`CasualBackfillPenalty` exist as policy, but no matcher code path ever creates a proposal to fill a slot in an in-progress match. Live integration also remains | +| 8.19 `[D:8.18]` | Casual lineup (2–6 humans, bot backfill) | Candidate selection landed (`domain.SelectCasualBackfillCandidate`: oldest ordinary casual ticket meeting build/region/tolerance, ties by ticket ID, deterministic across replicas). Casual lineup formation was already built and wired, and all four penalty kinds are written durably. What remains is the backfill proposal itself, the matcher pass that finds vacated kickoff slots, the client offer UI, and **late roster delivery** — a backfilled player's authorisation is issued after their server started, and the supervisor fetches the roster once before launching the game child with no reload path. That delivery design is now decided (supervisor re-fetches and signals a reload; see `docs/MATCHMAKING.md` § Casual) and the remaining work is tracked in [#32](https://github.com/jcreek/CosmicClash/issues/32). End-to-end verification needs a live cluster ([#17](https://github.com/jcreek/CosmicClash/issues/17)) | | 8.20 `[D:8.18]` | Ranked admission (six unique verified humans) | Done. Allocation wiring was already complete end to end (allocator sets the `cosmic-clash.io/arena-path` annotation → `supervisor.withAllocatedCompatibility` maps it to `--arena-path` → `server_boot.gd` → `ServerMatchLoop.allocated_arena_path`), with coverage at each hop. `ArenaRegistry` integration is now a cross-language guard rather than a shared list: `server/domain/ranked.go` must keep its own ranked-eligible subset (the choice is server-authoritative and made before any Godot process exists), so `arena_registry_sync_test.go` parses `arena_registry.gd` and fails if the two disagree in either direction, if rotation order diverges, or if a ranked path has no scene behind it. Verified against four drift scenarios including promoting an elevated variant, which the registry's own comment anticipates. Live ranked admission against a real cluster remains ([#17](https://github.com/jcreek/CosmicClash/issues/17)) | | 8.21 `[D:8.5,8.20]` | Rating core (Glicko-2, weights, transactional updates) | Live maintenance/DB execution remains | | 8.22 `[D:8.21]` | Ranked profile (provisional games, tiers) | Persisted tier policy done: bands live in `tier_bands`, seeded with the exact compiled launch policy so storage changed without behaviour changing, loaded at startup with a malformed policy failing startup rather than silently mis-tiering, and an empty table falling back to the compiled default so an operator can truncate back to known-good. Retuning is now a rolling restart rather than a rebuilt image. `PROVISIONAL` is rejected as a durable band, being derived from game count rather than rating. Client UI was already built (`RankedProfileState.display_text()` renders tier, provisional status, ranked games and the season countdown). Reconnect transport is tracked by 8.42 and depends on live auth/backend events |