diff --git a/gateway/internal/transcode/encode.go b/gateway/internal/transcode/encode.go index eb0e7c4..ae99486 100644 --- a/gateway/internal/transcode/encode.go +++ b/gateway/internal/transcode/encode.go @@ -166,14 +166,17 @@ func toWireState(s backendclient.StateResp) wire.StateView { // encodeMatch builds a MatchResult payload. func encodeMatch(m backendclient.MatchResp) []byte { b := flatbuffers.NewBuilder(512) - matched := m.Matched && m.Game != nil + // Enqueue always lands the caller in a game; an open game awaiting an opponent reports + // matched=false but still carries it, so encode the game whenever it is present (else the + // client never receives it and cannot navigate in). + hasGame := m.Game != nil var game flatbuffers.UOffsetT - if matched { + if hasGame { game = buildGameView(b, *m.Game) } fb.MatchResultStart(b) - fb.MatchResultAddMatched(b, matched) - if matched { + fb.MatchResultAddMatched(b, m.Matched) + if hasGame { fb.MatchResultAddGame(b, game) } b.Finish(fb.MatchResultEnd(b)) diff --git a/gateway/internal/transcode/transcode_test.go b/gateway/internal/transcode/transcode_test.go index f66cb48..e96af40 100644 --- a/gateway/internal/transcode/transcode_test.go +++ b/gateway/internal/transcode/transcode_test.go @@ -114,6 +114,36 @@ func TestEnqueueRoundTripEncodesMatch(t *testing.T) { } } +func TestEnqueueEncodesOpenGameWhenNotMatched(t *testing.T) { + // An auto-match enqueue that opens a game awaiting an opponent returns matched=false but + // still carries the game; it must reach the client so it navigates into the game at once. + backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(`{"matched":false,"game":{"id":"g-open","variant":"scrabble_en","status":"open","players":2,"to_move":0,"seats":[]}}`)) + }) + defer cleanup() + + reg := transcode.NewRegistry(backend, nil) + op, _ := reg.Lookup(transcode.MsgLobbyEnqueue) + + b := flatbuffers.NewBuilder(32) + v := b.CreateString("scrabble_en") + fb.EnqueueRequestStart(b) + fb.EnqueueRequestAddVariant(b, v) + b.Finish(fb.EnqueueRequestEnd(b)) + + payload, err := op.Handler(context.Background(), transcode.Request{Payload: b.FinishedBytes(), UserID: "u-1"}) + if err != nil { + t.Fatalf("handler: %v", err) + } + m := fb.GetRootAsMatchResult(payload, 0) + if m.Matched() { + t.Fatal("an open game awaiting an opponent must report matched=false") + } + if g := m.Game(nil); g == nil || string(g.Id()) != "g-open" { + t.Fatalf("open game must be on the wire even when not matched: %+v", g) + } +} + func TestDomainErrorSurfacesBackendCode(t *testing.T) { backend, cleanup := fakeBackend(t, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusConflict)