# Overlay Manager & Player List Design **Date:** 2026-03-20 **Status:** Approved ## Problem The OBS overlay (`optimized-controls.html`) has two issues after the upstream Game Picker API migrated from Puppeteer to an ecast shard monitor: 1. **Audio doesn't restart on new room codes.** The overlay updates the room code text when `game.added` fires, but audio playback fails to reinitialize because `hideDisplay()` (triggered by `game.started` or `audience.joined`) leaves the audio element in a state that `startAnimation()` doesn't fully recover from. There is no centralized lifecycle management — show/hide logic is scattered across event handlers. 2. **No player list.** The new shard-based API pushes real-time player names and join events (`room.connected`, `lobby.player-joined`), but the overlay doesn't consume them. Players joining the lobby should be visible to viewers. Additionally, several new WebSocket events (`room.connected`, `lobby.player-joined`, `lobby.updated`, `game.ended`, `room.disconnected`) are not handled, and the removed `audience.joined` event is still referenced. ## Design Decisions - **Event-driven state machine** over reactive flags or per-component lifecycle management. A central `OverlayManager` coordinates all components through explicit state transitions, preventing the ad-hoc state bugs that cause the current audio issue. - **ES module split** (no build step) over single-file or bundled architecture. Keeps OBS browser source simplicity while improving maintainability. - **WebSocket-only player data** — no REST polling fallback for player lists. - **Overlay visible only during lobby state** — room code, audio, and player list all share the same lifecycle. ## Architecture ### State Machine ``` idle → lobby → playing → ended → idle ↑ | └─────────────────────────┘ (new game.added) Any state → disconnected → idle (on reconnect, no active lobby) → lobby (on reconnect, active lobby) ``` | State | Entry Trigger | Visible Components | |-------|--------------|-------------------| | `idle` | Initial load, `session.ended`, `game.ended`, `room.disconnected` | None | | `lobby` | `game.added`, `room.connected` (lobby state) | Room code + audio + player list | | `playing` | `game.started` | None | | `ended` | `game.ended` | None (transitions to `idle` after brief delay) | | `disconnected` | WebSocket close/error | None (reconnect logic runs) | Key transition: `game.added` while already in `lobby` triggers a **full reset** — deactivate all components, update context, reactivate. This fixes the audio restart bug by design. ### Component Interface Every component implements: - `activate(context)` — enter active state with room/game/player context - `deactivate()` — fully clean up (stop audio, clear timers, hide elements) - `update(context)` — handle in-state updates (new player joined, etc.) - `getStatus()` — return current status for the debug panel ### File Layout ``` OBS-stuff/ ├── optimized-controls.html # DOM + CSS + bootstrap script ├── js/ │ ├── state-manager.js # OverlayManager: state machine, component registry │ ├── websocket-client.js # Auth, connect, reconnect, event routing │ ├── room-code-display.js # Room code animation component │ ├── audio-controller.js # Audio playback lifecycle │ ├── player-list.js # Player slot list component │ └── controls.js # Settings panel + debug dashboard ``` Loaded via `