Each game has a Live tab with a player counter, a 24-hour peak, an all-time peak, a 5-minute concurrency chart, and a feed of joins, leaves, crashes, and feedback as they land. All of it runs on the heartbeat stream your SDK already sends. No extra integration, no separate dashboard service.
Every Playloop SDK sends a heartbeat every 60 seconds while a session is active. The Live tab reads from that stream: count the sessions whose most-recent heartbeat is fresh, you get the player count. Sample that count every 5 minutes and you get the chart. No new SDK calls, no opt-in, no extra config. If telemetry works, Live works.
A player counts as online if a heartbeat or any other telemetry event from their session arrived in the last 120 seconds. The SDK heartbeat interval is 60 seconds, so 120s leaves room for one missed beat (mobile network blip, brief stall, GC pause) without the counter flickering. A player who actually disconnects clears at most 2 minutes later.
The threshold is not configurable. Lower would produce a jumpy counter. Higher would let crashed players loiter for several minutes. 120 seconds is the same number SteamDB uses for the same reason.
When a player quits cleanly (closes the app, exits the demo, ends the session via your in-game UI), the SDK fires a final session_summary flush with sessionEnded: true. The server flips the session status immediately, and the next Live poll drops the player from the counter. No 2-minute wait.
When the game crashes, force-quits, or the device runs out of battery, the SDK never gets to send that flush. The session just goes silent. The Live counter drops the player when its last heartbeat ages past 120 seconds. Analysis of that abandoned session is handled separately by the 30-minute stale-session cleanup, covered in /docs/analysis.
Every 5 minutes we sample the player count for each active game and store one data point. The chart plots those samples directly. 48-hour, 1-week, and max range toggles all read the same series; the longer ranges just include more samples.
“24-hour peak” is the max sample value over the last 288 samples (24h times 12 per hour). “All-time peak” updates whenever a fresh sample exceeds the stored value. Both are exact, not estimates. The sample cadence sets the resolution but not the accuracy.
The page polls /api/games/[slug]/realtime every 10 seconds while the tab is visible, every 30 seconds when you switch to another tab. The server caches each response for 5 seconds per game, so several teammates watching the same dashboard share one query rather than fanning out. Even at hundreds of viewers, the database cost is one count plus one max plus one range scan every 5 seconds.
Worst-case latency between “an event happens” and “it shows in the UI” is about 15 seconds (5s cache plus 10s poll). The chart and all-time peak depend on that periodic refresh, so a fresh peak appears within 5 minutes of being hit.
A few intentional exclusions worth knowing about:
status: ‘failed’. The analyzer crashed on them. The player may or may not still be playing, but we do not have fresh telemetry to know.These docs are evolving. Playloop is in active development ahead of launch, so APIs and details may change as we polish.