# Phase 4: GameEventColorStripStream **Status:** ✅ Complete **Parent plan:** [PLAN.md](./PLAN.md) **Domain:** backend ## Objective Create a new ColorStripSource/Stream type that renders LED effects in response to game events. Follows the NotificationColorStripStream pattern — event-driven with a 30 FPS render loop and double-buffered output. ## Tasks - [x] Task 1: Create `GameEventColorStripSource` dataclass in `storage/color_strip_source.py` - Fields: game_integration_id (str), idle_color (BindableColor), event_mappings (list of EventMapping dicts — override/supplement config-level mappings) - source_type = "game_event" - Implement to_dict/from_dict/create_from_kwargs/apply_update - sharable = False (each target gets its own stream) - [x] Task 2: Register "game_event" in `_SOURCE_TYPE_MAP` in color_strip_source.py - [x] Task 3: Create `GameEventColorStripStream` (`core/processing/game_event_stream.py`) - Constructor: parse event_mappings into lookup dict, subscribe to EventBus - `_on_game_event(event)` — callback from EventBus, enqueue effect (thread-safe via deque) - Effect types: flash, pulse, sweep, color_shift, breathing - Priority-based layering — higher priority effects override lower (same as notification) - 30 FPS background render thread with frame_time sleep - Double-buffered output under threading.Lock - Idle: output idle_color when no active effects - `get_latest_colors()` → returns current rendered frame (np.ndarray) - `start()` / `stop()` for lifecycle - Cleanup: unsubscribe from EventBus on stop - [x] Task 4: Register in `_SIMPLE_STREAM_MAP` in `color_strip_stream_manager.py` - [x] Task 5: Wire EventBus injection — stream needs access to the singleton EventBus - Added `game_event_bus` parameter to ColorStripStreamManager constructor - Injection via `set_event_bus()` using same hasattr pattern as asset_store - [x] Task 6: Write tests for GameEventColorStripSource (serialization, factory, update) - [x] Task 7: Write tests for GameEventColorStripStream (event → effect rendering, priority, idle state, lifecycle) ## Files to Modify/Create - `server/src/wled_controller/storage/color_strip_source.py` — add GameEventColorStripSource + register - `server/src/wled_controller/core/processing/game_event_stream.py` — new stream class - `server/src/wled_controller/core/processing/color_strip_stream_manager.py` — register in _SIMPLE_STREAM_MAP, inject EventBus - `server/src/wled_controller/core/processing/processor_manager.py` — pass EventBus to stream manager (if needed) - `server/tests/core/test_game_event_css.py` — source + stream tests ## Acceptance Criteria - "game_event" source type serializes/deserializes correctly - Stream subscribes to EventBus and renders effects when events arrive - Multiple simultaneous effects layer by priority - Idle state outputs the configured idle_color - Stream cleans up subscriptions on stop - All tests pass ## Notes - Reuse effect rendering logic from NotificationColorStripStream where possible (flash, pulse, sweep are identical) - Consider extracting shared effect rendering into a utility if duplication is significant - The stream needs the EventBus singleton — simplest injection is via the stream manager's dependencies - ⚠️ Big Bang: color_strip_source.py and stream_manager.py are shared files — coordinate with Phase 5 if running in parallel ## Review Checklist - [x] All tasks completed - [x] Code follows project conventions - [x] No unintended side effects - [x] Tests pass (28/28) ## Handoff to Next Phase ### What was built - `GameEventColorStripSource` dataclass in `storage/color_strip_source.py` with full serialization, factory, and update support - `GameEventColorStripStream` in `core/processing/game_event_stream.py` — event-driven stream with 5 effects (flash, pulse, sweep, color_shift, breathing), 30 FPS render loop, double-buffered output, priority-based effect layering - EventBus injection via `game_event_bus` parameter on `ColorStripStreamManager` constructor + `set_event_bus()` method on the stream ### Integration points for later phases - **Phase 7 (frontend)**: The `source_type = "game_event"` needs a UI editor. Fields: `game_integration_id` (EntitySelect), `idle_color` (BindableColor picker), `event_mappings` (list of EventMapping dicts with effect/color/duration/intensity/priority), `led_count` - **Phase 8 (wiring)**: `processor_manager.py` needs to pass the `GameEventBus` singleton to `ColorStripStreamManager` via the new `game_event_bus=` constructor parameter. The bus is created in Phase 1's `init_dependencies()`. ### Files modified - `server/src/wled_controller/storage/color_strip_source.py` — added `GameEventColorStripSource` class + registered in `_SOURCE_TYPE_MAP` - `server/src/wled_controller/core/processing/game_event_stream.py` — new file - `server/src/wled_controller/core/processing/color_strip_stream_manager.py` — added import, `_SIMPLE_STREAM_MAP` entry, `game_event_bus` constructor param, injection hook - `server/tests/core/test_game_event_css.py` — 28 tests covering source serialization, stream lifecycle, rendering, effects, auto-size, and hot-update