From 7bfddca207bf1fbeb1f9b0670f85f0308b65c362 Mon Sep 17 00:00:00 2001 From: Luke Policinski Date: Sat, 19 Sep 2026 14:02:47 -0400 Subject: [PATCH] bug: keep a demo session on playing when the pod's live report lands second --- .../game-streamer.service.spec.ts | 38 +++++++++++++++++++ .../game-streamer/game-streamer.service.ts | 8 +++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/matches/game-streamer/game-streamer.service.spec.ts b/src/matches/game-streamer/game-streamer.service.spec.ts index 2855b057..ad34cd6c 100644 --- a/src/matches/game-streamer/game-streamer.service.spec.ts +++ b/src/matches/game-streamer/game-streamer.service.spec.ts @@ -101,6 +101,44 @@ describe("GameStreamerService", () => { expect(setOf().status).toBe("launching_cs2"); }); + it("keeps the row on playing when the pod's live report lands second", async () => { + hasura.query.mockResolvedValueOnce({ + match_demo_sessions_by_pk: { + status: "playing", + status_history: [ + { status: "connecting_to_game", at: "2026-01-01T00:00:00.000Z" }, + { status: "playing", at: "2026-01-01T00:01:00.000Z" }, + ], + }, + }); + hasura.mutation.mockResolvedValueOnce({}); + + await service.reportDemoStatus("session-1", { status: "live" }); + + const set = setOf(); + expect(set.status).toBeUndefined(); + expect(set.error_message).toBeUndefined(); + expect((set.status_history as any[]).map((e: any) => e.status)).toEqual([ + "connecting_to_game", + "playing", + "live", + ]); + }); + + it("still lets a rebooted pod move the row off playing", async () => { + hasura.query.mockResolvedValueOnce({ + match_demo_sessions_by_pk: { + status: "playing", + status_history: [{ status: "playing", at: "2026-01-01" }], + }, + }); + hasura.mutation.mockResolvedValueOnce({}); + + await service.reportDemoStatus("session-1", { status: "booting" }); + + expect(setOf().status).toBe("booting"); + }); + it("appends after an event instead of rewriting it", async () => { // The row still says downloading_cs2 while the newest history entry is // the demo_ready event — coalescing must compare against the entry. diff --git a/src/matches/game-streamer/game-streamer.service.ts b/src/matches/game-streamer/game-streamer.service.ts index 46d05733..37f3c319 100644 --- a/src/matches/game-streamer/game-streamer.service.ts +++ b/src/matches/game-streamer/game-streamer.service.ts @@ -1299,7 +1299,13 @@ export class GameStreamerService { // An `event` is a one-shot milestone (demo_ready) raised by a worker // running alongside the main boot — it marks a stage complete in the // history without yanking the row off whatever setup-steam is doing. - const isEvent = GameStreamerService.isTruthyFlag(body.event); + // + // A `live` landing after `playing` gets the same treatment. The pod sends + // `live` through a 2s-polled daemon but `playing` straight from the + // spec-server, exactly once, so the two can arrive swapped — and applying + // the late `live` strands the viewer on the boot screen for good. + const isLateLive = current.status === "playing" && status === "live"; + const isEvent = GameStreamerService.isTruthyFlag(body.event) || isLateLive; const statusChanged = current.status !== status; const set: Record = { status_history: nextHistory,