fix(matchmaking): re-enqueue opens a new game, not the caller's own
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 16s
CI / ui (pull_request) Has been skipped
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s

A second "random opponent" enqueue with the same variant and per-turn
rule, while the caller's first game was still open (awaiting an
opponent), returned that same open game, so a player could never start a
fresh random game while one was still searching.

Drop the own-open short-circuit (step 1) in store.OpenOrJoin: a
re-enqueue now joins another player's open game or opens a fresh one.
Accumulation stays bounded by MaxActiveQuickGames, which counts open
games. Update the matchmaker/service/store doc comments and
ARCHITECTURE.md, and flip the pinning test to assert the new behavior.
This commit is contained in:
Ilia Denisov
2026-06-18 10:18:44 +02:00
parent 8793bd34f2
commit 9d52885a6e
5 changed files with 39 additions and 40 deletions
+12 -5
View File
@@ -74,9 +74,11 @@ func TestMatchmakingOpensThenJoins(t *testing.T) {
}
}
// TestMatchmakingReEnqueueReturnsOwnOpenGame checks a re-enqueue is idempotent: the
// caller gets their existing open game rather than a second one.
func TestMatchmakingReEnqueueReturnsOwnOpenGame(t *testing.T) {
// TestMatchmakingReEnqueueOpensNewGame checks a re-enqueue opens a fresh open game
// rather than returning the caller's existing one: tapping "random opponent" again while
// still waiting starts a new search instead of bouncing the player back into the pending
// game. Both games stay open until an opponent (a human or the reaper's robot) joins each.
func TestMatchmakingReEnqueueOpensNewGame(t *testing.T) {
ctx := context.Background()
clearOpenGames(t)
mm := newMatchmaker(t, newRobotService(t, newGameService()), 90*time.Second, 90*time.Second)
@@ -90,8 +92,13 @@ func TestMatchmakingReEnqueueReturnsOwnOpenGame(t *testing.T) {
if err != nil {
t.Fatalf("re-enqueue: %v", err)
}
if r2.Game.ID != r1.Game.ID || r2.Matched {
t.Fatalf("re-enqueue = (game %s, matched %v), want the same open game %s unmatched", r2.Game.ID, r2.Matched, r1.Game.ID)
if r2.Matched || r2.Game.ID == r1.Game.ID {
t.Fatalf("re-enqueue = (game %s, matched %v), want a new open game distinct from %s, unmatched", r2.Game.ID, r2.Matched, r1.Game.ID)
}
for _, g := range []uuid.UUID{r1.Game.ID, r2.Game.ID} {
if _, _, status, err := newGameService().Participants(ctx, g); err != nil || status != "open" {
t.Fatalf("game %s status = %q err %v, want open", g, status, err)
}
}
}