--- phase: 05-live-show-execution plan: 01 subsystem: api tags: [websocket, udp, cue-scheduler, animation, fastapi, lightsync] # Dependency graph requires: - phase: 04-timeline-editor provides: Show model with tracks/cues and encode_animation_cmd UDP encoder provides: - Server-side cue scheduler in websocket tick handler that fires UDP animation commands at correct timestamps - Per-connection show state (show, fired_ids, is_playing) scoped to each WebSocket connection - preview_update broadcast messages sent to all clients on each tick - Seek-safe fired_ids rebuild preventing double-fire or missed cues after seeking affects: - 05-02 (live preview UI consumes preview_update messages) - 05-03 (keyboard shortcuts drive play/pause/seek which gates cue scheduler) # Tech tracking tech-stack: added: [] patterns: - Per-connection mutable state pattern for WebSocket handlers (show, fired_ids, is_playing) - 60ms LOOKAHEAD window for cue scheduling with fire-and-forget UDP dispatch - Seek-safe fired_ids set comprehension rebuild from show cues before seek position - active_cue tracking per track for preview state (not just newly-fired cues) - Batch preview_update broadcast after each tick's cue scheduling pass key-files: created: [] modified: - lightsync/api/ws.py key-decisions: - "LOOKAHEAD = 0.060 (60ms) defined inline per tick — not a module constant — matches D-02 spec" - "is_playing gate on cue scheduler prevents any cue firing during pause or seek drag" - "Past cues (>100ms behind position) silently marked fired without UDP dispatch — prevents late blasts on seek" - "active_cue tracking includes already-fired cues still within their duration window — preview stays lit for duration of block" - "import lightsync.main as _main inside load handler — deferred import avoids circular import at module level" patterns-established: - "Pattern: Per-connection WebSocket state — mutable variables scoped inside async handler function" - "Pattern: Seek rebuild — set comprehension over all tracks/cues to mark past cues fired" - "Pattern: Preview batching — collect preview_updates list then broadcast after cue loop" requirements-completed: - SHW-03 # Metrics duration: 8min completed: 2026-04-07 --- # Phase 5 Plan 01: Live Show Execution — Cue Scheduler Summary **WebSocket tick handler extended with 60ms look-ahead cue scheduler that fires UDP animation commands at exact timestamps, with seek-safe fired_ids rebuild and preview_update broadcast on every tick** ## Performance - **Duration:** 8 min - **Started:** 2026-04-07T10:09:19Z - **Completed:** 2026-04-07T10:17:00Z - **Tasks:** 2 - **Files modified:** 1 ## Accomplishments - Per-connection show state (show, fired_ids, is_playing) integrated into WebSocket handler - Load message extended to load show from show_store by show_id, resetting scheduler state - Play/pause handlers gate is_playing flag (pause never touches fired_ids per spec) - Seek handler rebuilds fired_ids via set comprehension over all cues before seek position - 60ms LOOKAHEAD cue scheduler fires UDP animation commands during active playback - Past cues (>100ms behind) silently skipped — no late UDP bursts after seek - active_cue tracking covers both newly-fired and already-active cues for preview continuity - preview_update messages broadcast to all connected clients on each tick tick (active and clear states) ## Task Commits Each task was committed atomically: 1. **Task 1: Add per-connection show state and load handler to ws.py** - `3d36cc5` (feat) 2. **Task 2: Add cue scheduling loop and preview_update broadcast to tick handler** - `64c49c9` (feat) ## Files Created/Modified - `lightsync/api/ws.py` - Extended with cue scheduler, show loading, play/pause/seek handlers, preview_update broadcast ## Decisions Made - `LOOKAHEAD = 0.060` defined inline inside the tick branch (not as module constant) — keeps it local to the scheduler logic - Deferred import of `lightsync.main as _main` inside the load handler body to avoid circular import at module level - `active_cue` tracks already-fired cues still within their duration window so preview stays lit for the full block duration, not just at fire time - Past-cue threshold set at 100ms (not 60ms) to give a small recovery window without causing stale fires ## Deviations from Plan None - plan executed exactly as written. ## Issues Encountered None. The Python runtime was invoked via `uv run python` (no `python`/`python3` on PATH in this environment). ## Next Phase Readiness - WebSocket cue scheduler is fully functional — ready for 05-02 (live preview UI consuming preview_update messages) - preview_update messages broadcast device_id, animation name, and color on each tick — frontend can render device state - Seek and pause behavior are correct per spec — no double-fire, no late blasts --- *Phase: 05-live-show-execution* *Completed: 2026-04-07*