diff --git a/plans/demo-mode/CONTEXT.md b/plans/demo-mode/CONTEXT.md deleted file mode 100644 index 8ab097e..0000000 --- a/plans/demo-mode/CONTEXT.md +++ /dev/null @@ -1,30 +0,0 @@ -# Feature Context: Demo Mode - -## Current State -Starting implementation. No changes made yet. - -## Key Architecture Notes -- `EngineRegistry` (class-level dict) holds capture engines, auto-registered in `capture_engines/__init__.py` -- `AudioEngineRegistry` (class-level dict) holds audio engines, auto-registered in `audio/__init__.py` -- `LEDDeviceProvider` instances registered via `register_provider()` in `led_client.py` -- Already has `MockDeviceProvider` + `MockClient` (device type "mock") for testing -- Config is `pydantic_settings.BaseSettings` in `config.py`, loaded from YAML + env vars -- Frontend header in `templates/index.html` line 27-31: title + version badge -- Frontend bundle: `cd server && npm run build` (esbuild) -- Data stored as JSON in `data/` directory, paths configured via `StorageConfig` - -## Temporary Workarounds -- None yet - -## Cross-Phase Dependencies -- Phase 1 (config flag) is foundational — all other phases depend on `is_demo_mode()` -- Phase 2 & 3 (engines) can be done independently of each other -- Phase 4 (seed data) depends on knowing what entities to create, which is informed by phases 2-3 -- Phase 5 (frontend) depends on the system info API field from phase 1 -- Phase 6 (engine resolution) depends on engines existing from phases 2-3 - -## Implementation Notes -- Demo mode activated via `WLED_DEMO=true` env var or `demo: true` in YAML config -- Isolated data directory `data/demo/` keeps demo entities separate from real config -- Demo engines use `ENGINE_TYPE = "demo"` and are always registered but return `is_available() = True` only in demo mode -- The existing `MockDeviceProvider`/`MockClient` can be reused or extended for demo device output diff --git a/plans/demo-mode/PLAN.md b/plans/demo-mode/PLAN.md deleted file mode 100644 index 8970e38..0000000 --- a/plans/demo-mode/PLAN.md +++ /dev/null @@ -1,44 +0,0 @@ -# Feature: Demo Mode - -**Branch:** `feature/demo-mode` -**Base branch:** `master` -**Created:** 2026-03-20 -**Status:** 🟡 In Progress -**Strategy:** Big Bang -**Mode:** Automated -**Execution:** Orchestrator - -## Summary -Add a demo mode that allows users to explore and test the app without real hardware. Virtual capture engines, audio engines, and device providers replace real hardware. An isolated data directory with seed data provides a fully populated sandbox. A visual indicator in the UI makes it clear the app is running in demo mode. - -## Build & Test Commands -- **Build (frontend):** `cd server && npm run build` -- **Typecheck (frontend):** `cd server && npm run typecheck` -- **Test (backend):** `cd server && python -m pytest ../tests/ -x` -- **Server start:** `cd server && python -m wled_controller.main` - -## Phases - -- [x] Phase 1: Demo Mode Config & Flag [domain: backend] → [subplan](./phase-1-config-flag.md) -- [x] Phase 2: Virtual Capture Engine [domain: backend] → [subplan](./phase-2-virtual-capture-engine.md) -- [x] Phase 3: Virtual Audio Engine [domain: backend] → [subplan](./phase-3-virtual-audio-engine.md) -- [x] Phase 4: Demo Device Provider & Seed Data [domain: backend] → [subplan](./phase-4-demo-device-seed-data.md) -- [x] Phase 5: Frontend Demo Indicator & Sandbox UX [domain: fullstack] → [subplan](./phase-5-frontend-demo-ux.md) -- [x] Phase 6: Demo-only Engine Resolution [domain: backend] → [subplan](./phase-6-engine-resolution.md) - -## Phase Progress Log - -| Phase | Domain | Status | Review | Build | Committed | -|-------|--------|--------|--------|-------|-----------| -| Phase 1: Config & Flag | backend | ✅ Done | ✅ | ✅ | ⬜ | -| Phase 2: Virtual Capture Engine | backend | ✅ Done | ✅ | ✅ | ⬜ | -| Phase 3: Virtual Audio Engine | backend | ✅ Done | ✅ | ✅ | ⬜ | -| Phase 4: Demo Device & Seed Data | backend | ✅ Done | ✅ | ✅ | ⬜ | -| Phase 5: Frontend Demo UX | fullstack | ✅ Done | ✅ | ✅ | ⬜ | -| Phase 6: Engine Resolution | backend | ✅ Done | ✅ | ✅ | ⬜ | - -## Final Review -- [ ] Comprehensive code review -- [ ] Full build passes -- [ ] Full test suite passes -- [ ] Merged to `master` diff --git a/plans/demo-mode/phase-1-config-flag.md b/plans/demo-mode/phase-1-config-flag.md deleted file mode 100644 index 2869e24..0000000 --- a/plans/demo-mode/phase-1-config-flag.md +++ /dev/null @@ -1,42 +0,0 @@ -# Phase 1: Demo Mode Config & Flag - -**Status:** ⬜ Not Started -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Add a `demo` boolean flag to the application configuration and expose it to the frontend via the system info API. When demo mode is active, the server uses an isolated data directory so demo entities don't pollute real user data. - -## Tasks - -- [ ] Task 1: Add `demo: bool = False` field to `Config` class in `config.py` -- [ ] Task 2: Add a module-level helper `is_demo_mode() -> bool` in `config.py` for easy import -- [ ] Task 3: Modify `StorageConfig` path resolution: when `demo=True`, prefix all storage paths with `data/demo/` instead of `data/` -- [ ] Task 4: Expose `demo_mode: bool` in the existing `GET /api/v1/system/info` endpoint response -- [ ] Task 5: Add `WLED_DEMO=true` env var support (already handled by pydantic-settings env prefix `WLED_`) - -## Files to Modify/Create -- `server/src/wled_controller/config.py` — Add `demo` field, `is_demo_mode()` helper, storage path override -- `server/src/wled_controller/api/routes/system.py` — Add `demo_mode` to system info response -- `server/src/wled_controller/api/schemas/system.py` — Add `demo_mode` field to response schema - -## Acceptance Criteria -- `Config(demo=True)` is accepted; default is `False` -- `WLED_DEMO=true` activates demo mode -- `is_demo_mode()` returns the correct value -- When demo mode is on, all storage files resolve under `data/demo/` -- `GET /api/v1/system/info` includes `demo_mode: true/false` - -## Notes -- The env var will be `WLED_DEMO` because of `env_prefix="WLED_"` in pydantic-settings -- Storage path override should happen at `Config` construction time, not lazily - -## Review Checklist -- [ ] All tasks completed -- [ ] Code follows project conventions -- [ ] No unintended side effects -- [ ] Build passes -- [ ] Tests pass (new + existing) - -## Handoff to Next Phase - diff --git a/plans/demo-mode/phase-2-virtual-capture-engine.md b/plans/demo-mode/phase-2-virtual-capture-engine.md deleted file mode 100644 index ac8f79f..0000000 --- a/plans/demo-mode/phase-2-virtual-capture-engine.md +++ /dev/null @@ -1,48 +0,0 @@ -# Phase 2: Virtual Capture Engine - -**Status:** ⬜ Not Started -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Create a `DemoCaptureEngine` that provides virtual displays and produces animated test pattern frames, allowing screen capture workflows to function in demo mode without real monitors. - -## Tasks - -- [ ] Task 1: Create `server/src/wled_controller/core/capture_engines/demo_engine.py` with `DemoCaptureEngine` and `DemoCaptureStream` -- [ ] Task 2: `DemoCaptureEngine.ENGINE_TYPE = "demo"`, `ENGINE_PRIORITY = 1000` (highest in demo mode) -- [ ] Task 3: `is_available()` returns `True` only when `is_demo_mode()` is True -- [ ] Task 4: `get_available_displays()` returns 3 virtual displays: - - "Demo Display 1080p" (1920×1080) - - "Demo Ultrawide" (3440×1440) - - "Demo Portrait" (1080×1920) -- [ ] Task 5: `DemoCaptureStream.capture_frame()` produces animated test patterns: - - Horizontally scrolling rainbow gradient (simple, visually clear) - - Uses `time.time()` for animation so frames change over time - - Returns proper `ScreenCapture` with RGB numpy array -- [ ] Task 6: Register `DemoCaptureEngine` in `capture_engines/__init__.py` - -## Files to Modify/Create -- `server/src/wled_controller/core/capture_engines/demo_engine.py` — New file: DemoCaptureEngine + DemoCaptureStream -- `server/src/wled_controller/core/capture_engines/__init__.py` — Register DemoCaptureEngine - -## Acceptance Criteria -- `DemoCaptureEngine.is_available()` is True only in demo mode -- Virtual displays appear in the display list API when in demo mode -- `capture_frame()` returns valid RGB frames that change over time -- Engine is properly registered in EngineRegistry - -## Notes -- Test patterns should be computationally cheap (no heavy image processing) -- Use numpy operations for pattern generation (vectorized, fast) -- Frame dimensions must match the virtual display dimensions - -## Review Checklist -- [ ] All tasks completed -- [ ] Code follows project conventions -- [ ] No unintended side effects -- [ ] Build passes -- [ ] Tests pass (new + existing) - -## Handoff to Next Phase - diff --git a/plans/demo-mode/phase-3-virtual-audio-engine.md b/plans/demo-mode/phase-3-virtual-audio-engine.md deleted file mode 100644 index 6081f01..0000000 --- a/plans/demo-mode/phase-3-virtual-audio-engine.md +++ /dev/null @@ -1,47 +0,0 @@ -# Phase 3: Virtual Audio Engine - -**Status:** ⬜ Not Started -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Create a `DemoAudioEngine` that provides virtual audio devices and produces synthetic audio data, enabling audio-reactive visualizations in demo mode. - -## Tasks - -- [ ] Task 1: Create `server/src/wled_controller/core/audio/demo_engine.py` with `DemoAudioEngine` and `DemoAudioCaptureStream` -- [ ] Task 2: `DemoAudioEngine.ENGINE_TYPE = "demo"`, `ENGINE_PRIORITY = 1000` -- [ ] Task 3: `is_available()` returns `True` only when `is_demo_mode()` is True -- [ ] Task 4: `enumerate_devices()` returns 2 virtual devices: - - "Demo Microphone" (input, not loopback) - - "Demo System Audio" (loopback) -- [ ] Task 5: `DemoAudioCaptureStream` implements: - - `channels = 2`, `sample_rate = 44100`, `chunk_size = 1024` - - `read_chunk()` produces synthetic audio: a mix of sine waves with slowly varying frequencies to simulate music-like beat patterns - - Returns proper float32 ndarray -- [ ] Task 6: Register `DemoAudioEngine` in `audio/__init__.py` - -## Files to Modify/Create -- `server/src/wled_controller/core/audio/demo_engine.py` — New file: DemoAudioEngine + DemoAudioCaptureStream -- `server/src/wled_controller/core/audio/__init__.py` — Register DemoAudioEngine - -## Acceptance Criteria -- `DemoAudioEngine.is_available()` is True only in demo mode -- Virtual audio devices appear in audio device enumeration when in demo mode -- `read_chunk()` returns valid float32 audio data that varies over time -- Audio analyzer produces non-trivial frequency band data from the synthetic signal - -## Notes -- Synthetic audio should produce interesting FFT results (multiple frequencies, amplitude modulation) -- Keep it computationally lightweight -- Must conform to `AudioCaptureStreamBase` interface exactly - -## Review Checklist -- [ ] All tasks completed -- [ ] Code follows project conventions -- [ ] No unintended side effects -- [ ] Build passes -- [ ] Tests pass (new + existing) - -## Handoff to Next Phase - diff --git a/plans/demo-mode/phase-4-demo-device-seed-data.md b/plans/demo-mode/phase-4-demo-device-seed-data.md deleted file mode 100644 index b4da3f5..0000000 --- a/plans/demo-mode/phase-4-demo-device-seed-data.md +++ /dev/null @@ -1,54 +0,0 @@ -# Phase 4: Demo Device Provider & Seed Data - -**Status:** ⬜ Not Started -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Create a demo device provider that exposes discoverable virtual LED devices, and build a seed data generator that populates the demo data directory with sample entities on first run. - -## Tasks - -- [ ] Task 1: Create `server/src/wled_controller/core/devices/demo_provider.py` — `DemoDeviceProvider` extending `LEDDeviceProvider`: - - `device_type = "demo"` - - `capabilities = {"manual_led_count", "power_control", "brightness_control", "static_color"}` - - `create_client()` returns a `MockClient` (reuse existing) - - `discover()` returns 3 pre-defined virtual devices: - - "Demo LED Strip" (60 LEDs, ip="demo-strip") - - "Demo LED Matrix" (256 LEDs / 16×16, ip="demo-matrix") - - "Demo LED Ring" (24 LEDs, ip="demo-ring") - - `check_health()` always returns online with simulated ~2ms latency - - `validate_device()` returns `{"led_count": }` -- [ ] Task 2: Register `DemoDeviceProvider` in `led_client.py` `_register_builtin_providers()` -- [ ] Task 3: Create `server/src/wled_controller/core/demo_seed.py` — seed data generator: - - Function `seed_demo_data(storage_config: StorageConfig)` that checks if demo data dir is empty and populates it - - Seed entities: 3 devices (matching discover results), 2 output targets, 2 picture sources (using demo engine), 2 CSS sources (gradient + color_cycle), 1 audio source (using demo engine), 1 scene preset, 1 automation - - Use proper ID formats matching existing conventions (e.g., `dev_`, `tgt_`, etc.) -- [ ] Task 4: Call `seed_demo_data()` during server startup in `main.py` when demo mode is active (before stores are loaded) - -## Files to Modify/Create -- `server/src/wled_controller/core/devices/demo_provider.py` — New: DemoDeviceProvider -- `server/src/wled_controller/core/devices/led_client.py` — Register DemoDeviceProvider -- `server/src/wled_controller/core/demo_seed.py` — New: seed data generator -- `server/src/wled_controller/main.py` — Call seed on demo startup - -## Acceptance Criteria -- Demo devices appear in discovery results when in demo mode -- Seed data populates `data/demo/` with valid JSON files on first demo run -- Subsequent demo runs don't overwrite existing demo data -- All seeded entities load correctly in stores - -## Notes -- Seed data must match the exact schema expected by each store (look at existing JSON files for format) -- Use the entity dataclass `to_dict()` / store patterns to generate valid data -- Demo discovery should NOT appear when not in demo mode - -## Review Checklist -- [ ] All tasks completed -- [ ] Code follows project conventions -- [ ] No unintended side effects -- [ ] Build passes -- [ ] Tests pass (new + existing) - -## Handoff to Next Phase - diff --git a/plans/demo-mode/phase-5-frontend-demo-ux.md b/plans/demo-mode/phase-5-frontend-demo-ux.md deleted file mode 100644 index 72a2314..0000000 --- a/plans/demo-mode/phase-5-frontend-demo-ux.md +++ /dev/null @@ -1,50 +0,0 @@ -# Phase 5: Frontend Demo Indicator & Sandbox UX - -**Status:** ⬜ Not Started -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** fullstack - -## Objective -Add visual indicators in the frontend that clearly communicate demo mode status to the user, including a badge, dismissible banner, and engine labeling. - -## Tasks - -- [ ] Task 1: Add `demo_mode` field to system info API response schema (if not already done in Phase 1) -- [ ] Task 2: In frontend initialization (`app.ts` or `state.ts`), fetch system info and store `demoMode` in app state -- [ ] Task 3: Add `` next to app title in `index.html` header -- [ ] Task 4: CSS for `.demo-badge`: amber/yellow pill shape, subtle pulse animation, clearly visible but not distracting -- [ ] Task 5: On app load, if `demoMode` is true: show badge, set `document.body.dataset.demo = 'true'` -- [ ] Task 6: Add a dismissible demo banner at the top of the page: "You're in demo mode — all devices and data are virtual. No real hardware is used." with a dismiss (×) button. Store dismissal in localStorage. -- [ ] Task 7: Add i18n keys for demo badge and banner text in `en.json`, `ru.json`, `zh.json` -- [ ] Task 8: In engine/display dropdowns, demo engines should display with "Demo: " prefix for clarity - -## Files to Modify/Create -- `server/src/wled_controller/templates/index.html` — Demo badge + banner HTML -- `server/src/wled_controller/static/css/app.css` — Demo badge + banner styles -- `server/src/wled_controller/static/js/app.ts` — Demo mode detection and UI toggle -- `server/src/wled_controller/static/js/core/state.ts` — Store demo mode flag -- `server/src/wled_controller/static/locales/en.json` — i18n keys -- `server/src/wled_controller/static/locales/ru.json` — i18n keys -- `server/src/wled_controller/static/locales/zh.json` — i18n keys - -## Acceptance Criteria -- Demo badge visible next to "LED Grab" title when in demo mode -- Demo badge hidden when not in demo mode -- Banner appears on first demo visit, can be dismissed, stays dismissed across refreshes -- Engine dropdowns clearly label demo engines -- All text is localized - -## Notes -- Badge should use `--warning-color` or a custom amber for the pill -- Banner should be a thin strip, not intrusive -- `localStorage` key: `demo-banner-dismissed` - -## Review Checklist -- [ ] All tasks completed -- [ ] Code follows project conventions -- [ ] No unintended side effects -- [ ] Build passes -- [ ] Tests pass (new + existing) - -## Handoff to Next Phase - diff --git a/plans/demo-mode/phase-6-engine-resolution.md b/plans/demo-mode/phase-6-engine-resolution.md deleted file mode 100644 index 9c7a299..0000000 --- a/plans/demo-mode/phase-6-engine-resolution.md +++ /dev/null @@ -1,46 +0,0 @@ -# Phase 6: Demo-only Engine Resolution - -**Status:** ⬜ Not Started -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Ensure demo engines are the primary/preferred engines in demo mode, and are hidden when not in demo mode. This makes demo mode act as a "virtual platform" where only demo engines resolve. - -## Tasks - -- [ ] Task 1: Modify `EngineRegistry.get_available_engines()` to filter out engines with `ENGINE_TYPE == "demo"` when not in demo mode (they report `is_available()=False` anyway, but belt-and-suspenders) -- [ ] Task 2: Modify `AudioEngineRegistry.get_available_engines()` similarly -- [ ] Task 3: In demo mode, `get_best_available_engine()` should return the demo engine (already handled by priority=1000, but verify) -- [ ] Task 4: Modify the `GET /api/v1/config/displays` endpoint: in demo mode, default to demo engine displays if no engine_type specified -- [ ] Task 5: Modify the audio engine listing endpoint similarly -- [ ] Task 6: Ensure `DemoDeviceProvider.discover()` only returns devices when in demo mode -- [ ] Task 7: End-to-end verification: start server in demo mode, verify only demo engines/devices appear in API responses - -## Files to Modify/Create -- `server/src/wled_controller/core/capture_engines/factory.py` — Filter demo engines -- `server/src/wled_controller/core/audio/factory.py` — Filter demo engines -- `server/src/wled_controller/api/routes/system.py` — Display endpoint defaults -- `server/src/wled_controller/api/routes/audio_templates.py` — Audio engine listing -- `server/src/wled_controller/core/devices/demo_provider.py` — Guard discover() - -## Acceptance Criteria -- In demo mode: demo engines are primary, real engines may also be listed but demo is default -- Not in demo mode: demo engines are completely hidden from all API responses -- Display list defaults to demo displays in demo mode -- Audio device list defaults to demo devices in demo mode - -## Notes -- This is the "demo OS identifier" concept — demo mode acts as a virtual platform -- Be careful not to break existing behavior when demo=False (default) -- The demo engines already have `is_available() = is_demo_mode()`, so the main concern is UI defaults - -## Review Checklist -- [ ] All tasks completed -- [ ] Code follows project conventions -- [ ] No unintended side effects -- [ ] Build passes -- [ ] Tests pass (new + existing) - -## Handoff to Next Phase - diff --git a/plans/game-integration/CONTEXT.md b/plans/game-integration/CONTEXT.md deleted file mode 100644 index 551eb18..0000000 --- a/plans/game-integration/CONTEXT.md +++ /dev/null @@ -1,85 +0,0 @@ -# Feature Context: Game Integration - -## Configuration -- **Development mode:** Automated -- **Execution mode:** Orchestrator -- **Strategy:** Big Bang -- **Build (frontend):** `cd server && npm run build` -- **Test:** `cd server && py -3.13 -m pytest tests/ --no-cov -q` -- **Lint:** `cd server && ruff check src/ tests/ --fix` -- **TypeScript:** `cd server && npx tsc --noEmit` - -## Architecture Decisions - -### Not a Capture Engine -Game events are discrete data points, not pixel buffers. The integration lives as a separate `core/game_integration/` module with two output channels into existing pipelines: -1. `GameEventColorStripSource/Stream` — renders LED effects on game events -2. `GameEventValueSource` — exposes game metrics as 0.0-1.0 scalars for parameter binding - -### Standardized Event Vocabulary -All adapters map game-specific data into universal event categories (health, kill, death, round_start, etc.). Users configure effects against categories, not game-specific IDs. This means a "health < 30% → flash red" config works across CS2, LoL, and any future game. - -### Three Adapter Tiers -1. **Built-in** — CS2 GSI, LoL Live Client, Dota 2 GSI, generic webhook (ship with app) -2. **Community YAML** — declarative adapter files mapping JSON paths to standard events (no code) -3. **Generic webhook** — fallback with UI-guided JSON path mapping - -### Event Types -- **Continuous** (health, armor, mana, ammo) → best for ValueSource (drive brightness/speed/color) -- **Trigger** (kill, death, round_start, bomb_planted) → best for ColorStripStream (fire effects) - -## Tech Stack Context - -### Backend Patterns (from codebase analysis) -- **Entity pattern**: dataclass model (`storage/`) + JSON/SQLite store (`storage/*_store.py`) + Pydantic schemas (`api/schemas/`) + routes (`api/routes/`) -- **Store base**: `BaseSqliteStore[T]` with write-through cache, RLock, `_check_name_unique()` -- **ID generation**: `f"{prefix}_{uuid.uuid4().hex[:8]}"` -- **Events**: `fire_entity_event(entity_type, action, entity_id)` for UI invalidation -- **ColorStripSource**: inheritance with `_SOURCE_TYPE_MAP` registry, `to_dict()`/`from_dict()`/`create_from_kwargs()`/`apply_update()` -- **ColorStripStream**: `_SIMPLE_STREAM_MAP` in `color_strip_stream_manager.py` -- **ValueSource**: similar inheritance with `_VALUE_SOURCE_MAP` registry -- **NotificationColorStripStream**: closest analog — event-driven, deque + lock, 30 FPS render loop, double-buffered output -- **Bindable**: `BindableFloat`/`BindableColor` for value source parameter binding -- **Dependencies**: `init_dependencies()` in `dependencies.py`, FastAPI `Depends()` -- **STORE_MAP**: in `api/routes/system.py` for backup/restore - -### Frontend Patterns -- Vanilla TypeScript modules, no framework -- `CardSection` for entity lists with reconciliation -- `Modal` base class with `snapshotValues()` for dirty check -- `TreeNav` for sidebar navigation in Streams tab -- `DataCache` for state management -- `fetchWithAuth()` for all API calls -- `t('key')` for i18n, keys in en.json/ru.json/zh.json -- `fire_entity_event` → frontend listens for cache invalidation -- Icons from `core/icons.ts` (SVG paths), NEVER emoji -- `IconSelect` for predefined item grids, `EntitySelect` for entity references - -## Current State -Feature not yet started. Branch created, plan files written. - -## Temporary Workarounds -None yet. - -## Cross-Phase Dependencies -- Phase 2 depends on Phase 1 (EventBus, AdapterRegistry) -- Phase 3 depends on Phase 1 (GameAdapter ABC, MappingAdapter) -- Phase 4 depends on Phase 1 (EventBus) and Phase 2 (GameIntegrationStore for config) -- Phase 5 depends on Phase 1 (EventBus) and Phase 2 (GameIntegrationStore) -- Phase 6 depends on Phase 2 (API endpoints) and Phase 3 (adapter metadata) -- Phase 7 depends on Phase 4 (CSS source type) and Phase 5 (value source type) -- Phase 8 depends on all prior phases -- **Phases 4 and 5 are independent** — can run in parallel - -## Deferred Work -None yet. - -## Failed Approaches -None yet. - -## Review Findings Log -None yet. - -## Phase Execution Log -| Phase | Agent Used | Test Writer | Parallel | Notes | -|-------|-----------|-------------|----------|-------| diff --git a/plans/game-integration/PLAN.md b/plans/game-integration/PLAN.md deleted file mode 100644 index f59f42e..0000000 --- a/plans/game-integration/PLAN.md +++ /dev/null @@ -1,53 +0,0 @@ -# Feature: Game Integration - -**Branch:** `feature/game-integration` -**Base branch:** `master` -**Created:** 2026-03-30 -**Status:** 🟡 In Progress -**Strategy:** Big Bang -**Mode:** Automated -**Execution:** Orchestrator - -## Summary - -A system that receives real-time events from games (CS2, LoL, Dota 2, etc.) and drives LED effects through the existing color strip and value source pipelines. Uses a standardized event vocabulary so users configure effects against universal categories (health, kill, death) rather than game-specific IDs. Supports built-in adapters, community YAML adapter files, and a generic webhook fallback. - -## Build & Test Commands -- **Build (frontend):** `cd server && npm run build` -- **Test:** `cd server && py -3.13 -m pytest tests/ --no-cov -q` -- **Lint:** `cd server && ruff check src/ tests/ --fix` -- **TypeScript:** `cd server && npx tsc --noEmit` - -## Phases - -- [x] Phase 1: Core Event Bus & Adapter Framework [backend] → [subplan](./phase-1-event-bus.md) -- [x] Phase 2: Storage & API — Game Integration Configs [backend] → [subplan](./phase-2-storage-api.md) -- [x] Phase 3: Built-in Game Adapters [backend] → [subplan](./phase-3-adapters.md) -- [x] Phase 4: GameEventColorStripStream [backend] → [subplan](./phase-4-css-stream.md) -- [x] Phase 5: GameEventValueSource [backend] → [subplan](./phase-5-value-source.md) -- [x] Phase 6: Frontend — Game Integration Management UI [frontend] → [subplan](./phase-6-frontend-management.md) -- [x] Phase 7: Frontend — ColorStrip & ValueSource Game Bindings [frontend] → [subplan](./phase-7-frontend-bindings.md) -- [x] Phase 8: Effect Presets & Polish [fullstack] → [subplan](./phase-8-presets-polish.md) - -## Phase Progress Log - -| Phase | Domain | Status | Review | Build | Committed | -|-------|--------|--------|--------|-------|-----------| -| Phase 1: Event Bus & Adapter Framework | backend | ✅ Done | ✅ | ⬜ | ⬜ | -| Phase 2: Storage & API | backend | ✅ Done | ✅ | ⬜ | ⬜ | -| Phase 3: Built-in Adapters | backend | ✅ Done | ✅ | ⬜ | ⬜ | -| Phase 4: CSS Stream | backend | ✅ Done | ✅ | ⬜ | ⬜ | -| Phase 5: Value Source | backend | ✅ Done | ✅ | ⬜ | ⬜ | -| Phase 6: Frontend Management UI | frontend | ✅ Done | ✅ | ⬜ | ⬜ | -| Phase 7: Frontend Bindings | frontend | ✅ Done | ✅ | ⬜ | ⬜ | -| Phase 8: Presets & Polish | fullstack | ✅ Done | ✅ | ✅ | ⬜ | - -## Parallel Phases - -Phases 4 and 5 are independent (no shared files) — can run in parallel. - -## Final Review -- [ ] Comprehensive code review -- [ ] Full build passes -- [ ] Full test suite passes -- [ ] Merged to `master` diff --git a/plans/game-integration/phase-1-event-bus.md b/plans/game-integration/phase-1-event-bus.md deleted file mode 100644 index 598fbf5..0000000 --- a/plans/game-integration/phase-1-event-bus.md +++ /dev/null @@ -1,107 +0,0 @@ -# Phase 1: Core Event Bus & Adapter Framework - -**Status:** ✅ Complete -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Build the foundational event infrastructure: the standardized event model, in-process pub/sub bus, adapter ABC, adapter registry, and the YAML-driven mapping adapter. This phase creates the core abstractions that all subsequent phases depend on. - -## Tasks - -- [x] Task 1: Create `core/game_integration/__init__.py` package -- [x] Task 2: Define standardized event vocabulary as an enum/constant module (`core/game_integration/events.py`) - - Categories: health, armor, shield, mana, energy, ammo, gold, fuel, speed (continuous) - - Categories: kill, death, assist, damage_taken, damage_dealt (combat triggers) - - Categories: match_start, match_end, round_start, round_end (match flow triggers) - - Categories: objective_captured, objective_lost, objective_progress (objective) - - Categories: stunned, blinded, buffed, debuffed (status effects) - - Categories: team_a, team_b (team affiliation) - - Each event type has metadata: display name, category, value_type (continuous/trigger), default_range (min, max) -- [x] Task 3: Create `GameEvent` frozen dataclass (`core/game_integration/events.py`) - - Fields: `adapter_id`, `event_type` (str from vocabulary), `value` (float 0.0-1.0), `raw_data` (dict), `timestamp` (float, monotonic) -- [x] Task 4: Create `GameEventBus` (`core/game_integration/event_bus.py`) - - Thread-safe pub/sub with `threading.Lock` - - `publish(event: GameEvent)` — dispatches to matching subscribers - - `subscribe(event_type: str, callback)` → returns subscription ID - - `subscribe_all(callback)` → receives all events (for diagnostics/live monitor) - - `unsubscribe(subscription_id)` - - `get_recent_events(limit=50)` → returns recent events deque for diagnostics - - `get_stats()` → event counts per type, last event timestamp - - Use `collections.deque(maxlen=100)` for recent events -- [x] Task 5: Create `GameAdapter` ABC (`core/game_integration/base_adapter.py`) - - Class attributes: `ADAPTER_TYPE`, `DISPLAY_NAME`, `GAME_NAME`, `SUPPORTED_EVENTS` (list of event type strings) - - `parse_payload(cls, payload: dict, adapter_config: dict, prev_state: dict) -> tuple[list[GameEvent], dict]` — returns events + updated state (for diff detection) - - `validate_auth(cls, headers: dict, payload: dict, adapter_config: dict) -> bool` - - `get_config_schema(cls) -> dict` — returns JSON schema for adapter-specific config fields (for UI auto-generation) - - `get_setup_instructions(cls) -> str` — returns markdown setup guide for the game -- [x] Task 6: Create `AdapterRegistry` (`core/game_integration/adapter_registry.py`) - - `register(adapter_class)`, `get_adapter(adapter_type)`, `get_all_adapters()`, `get_available_adapters()` - - Follow `EngineRegistry` pattern -- [x] Task 7: Create `MappingAdapter` (`core/game_integration/mapping_adapter.py`) - - Parses community YAML adapter files into a generic adapter instance - - YAML schema: adapter name, game name, protocol (webhook/poll), mappings list - - Each mapping: `source_path` (JSONPath-like), `event` (standard type), `min`/`max` (for continuous), `trigger` mode (on_change/on_increase/on_decrease/on_value) - - `load_adapter_from_yaml(path) -> GameAdapter` — factory function - - `validate_adapter_yaml(data: dict) -> list[str]` — returns validation errors -- [x] Task 8: Create `core/game_integration/adapters/__init__.py` package (empty, for Phase 3) -- [x] Task 9: Write unit tests for GameEventBus (publish, subscribe, unsubscribe, thread safety) -- [x] Task 10: Write unit tests for AdapterRegistry (register, get, duplicates) -- [x] Task 11: Write unit tests for MappingAdapter (YAML parsing, payload translation, validation) - -## Files to Modify/Create -- `server/src/wled_controller/core/game_integration/__init__.py` — package init, re-exports -- `server/src/wled_controller/core/game_integration/events.py` — GameEvent dataclass + event vocabulary -- `server/src/wled_controller/core/game_integration/event_bus.py` — GameEventBus -- `server/src/wled_controller/core/game_integration/base_adapter.py` — GameAdapter ABC -- `server/src/wled_controller/core/game_integration/adapter_registry.py` — AdapterRegistry -- `server/src/wled_controller/core/game_integration/mapping_adapter.py` — MappingAdapter + YAML loader -- `server/src/wled_controller/core/game_integration/adapters/__init__.py` — empty package -- `server/tests/core/test_game_event_bus.py` — EventBus tests -- `server/tests/core/test_adapter_registry.py` — Registry tests -- `server/tests/core/test_mapping_adapter.py` — MappingAdapter tests - -## Acceptance Criteria -- GameEvent is frozen/immutable with all required fields -- Event vocabulary covers all standard categories with metadata -- EventBus correctly dispatches events to type-specific and wildcard subscribers -- EventBus is thread-safe (concurrent publish/subscribe doesn't crash) -- AdapterRegistry follows EngineRegistry pattern faithfully -- MappingAdapter can load a YAML file and translate a JSON payload into GameEvents -- MappingAdapter validates YAML schema and reports errors clearly -- All tests pass - -## Notes -- Use `time.monotonic()` for timestamps (not `time.time()`) — monotonic is immune to clock adjustments -- Keep the event vocabulary extensible — new types should just be new entries, no code changes -- The MappingAdapter is key for scaling to many games without writing Python code per game -- `prev_state` parameter on `parse_payload` enables diff-based trigger detection (e.g. CS2 kills counter) - -## Review Checklist -- [x] All tasks completed -- [x] Code follows project conventions (PEP 8, type annotations, immutability) -- [x] No unintended side effects -- [x] Tests pass (new + existing) - -## Handoff to Next Phase - -**Completed:** All 11 tasks implemented and tested. 55 tests pass, 0 failures. Ruff clean. - -### What was built -- `events.py` — `GameEvent` frozen dataclass + 23-type event vocabulary with `EventTypeMetadata` (category, value_type, default_range). Helper functions: `get_event_vocabulary()`, `get_event_metadata()`, `is_known_event_type()`. -- `event_bus.py` — `GameEventBus` with thread-safe pub/sub. Type-specific and wildcard subscriptions, `deque(maxlen=100)` for recent events, per-type event counts. Callbacks invoked outside lock to prevent deadlocks. -- `base_adapter.py` — `GameAdapter` ABC with `ADAPTER_TYPE`/`DISPLAY_NAME`/`GAME_NAME`/`SUPPORTED_EVENTS` class vars. Abstract methods: `parse_payload()`, `validate_auth()`. Default implementations: `get_config_schema()`, `get_setup_instructions()`. -- `adapter_registry.py` — `AdapterRegistry` following `EngineRegistry` pattern (class-level dict, register/get/list/clear). -- `mapping_adapter.py` — `MappingAdapter` (concrete, instance-based) + `load_adapter_from_yaml()` factory + `validate_adapter_yaml()` validator. Supports dot-notation JSON paths, 4 trigger modes (on_change/on_increase/on_decrease/on_value), value normalization to 0.0-1.0, header-based auth. -- `adapters/__init__.py` — empty package ready for Phase 3 built-in adapters. -- `__init__.py` — re-exports all public API. - -### Key design decisions -- `MappingAdapter` is instance-based (not classmethod-based like built-in adapters) because each YAML file creates a unique adapter with its own mappings. The `parse_payload`/`validate_auth` methods use `# type: ignore[override]` for the instance vs classmethod difference. -- `prev_state` dict keys are `source_path` strings, values are the last numeric value seen. This enables diff-based trigger detection (e.g., CS2 kill counter increments). -- Non-numeric values in payloads are treated as trigger events with value=1.0. - -### What Phase 2 needs -- `GameEventBus` instance should be created as a singleton in `dependencies.py` and injected via FastAPI `Depends()`. -- `AdapterRegistry` is ready for built-in adapters to call `AdapterRegistry.register(MyAdapter)` at import time. -- The `GameIntegrationStore` (Phase 2) will need to store adapter configs that include `adapter_id` fields matching what `parse_payload` expects in `adapter_config`. diff --git a/plans/game-integration/phase-2-storage-api.md b/plans/game-integration/phase-2-storage-api.md deleted file mode 100644 index af1a790..0000000 --- a/plans/game-integration/phase-2-storage-api.md +++ /dev/null @@ -1,102 +0,0 @@ -# Phase 2: Storage & API — Game Integration Configs - -**Status:** ✅ Complete -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Create the persistence layer and REST API for managing game integration configurations. Users create/edit/delete game integration configs, and games POST events to an ingestion endpoint. - -## Tasks - -- [x] Task 1: Create `GameIntegrationConfig` dataclass (`storage/game_integration.py`) - - Fields: id (gi_<8hex>), name, adapter_type, enabled, adapter_config (dict), event_mappings (list of EventMapping dicts), created_at, updated_at, description, tags - - `EventMapping` dataclass: event_type, effect, color ([R,G,B]), duration_ms, intensity, priority - - Standard to_dict/from_dict/create_from_kwargs/apply_update methods -- [x] Task 2: Create `GameIntegrationStore` (`storage/game_integration_store.py`) - - Extend BaseSqliteStore[GameIntegrationConfig] - - CRUD methods: create_integration, update_integration, delete (inherited) - - Name uniqueness validation - - get_references() for cascade prevention -- [x] Task 3: Create Pydantic schemas (`api/schemas/game_integration.py`) - - GameIntegrationCreate, GameIntegrationUpdate, GameIntegrationResponse, GameIntegrationListResponse - - EventMappingSchema - - GameEventPayload (for ingestion endpoint) - - AdapterInfoResponse, AdapterListResponse (for adapter metadata endpoint) - - GameIntegrationStatusResponse (for diagnostics) -- [x] Task 4: Create API routes (`api/routes/game_integration.py`) - - GET /api/v1/game-integrations — list all configs - - POST /api/v1/game-integrations — create config - - GET /api/v1/game-integrations/{id} — get config - - PUT /api/v1/game-integrations/{id} — update config - - DELETE /api/v1/game-integrations/{id} — delete config - - POST /api/v1/game-integrations/{id}/event — receive game event payload (ingestion) - - GET /api/v1/game-integrations/{id}/status — connection status, last event, event count - - GET /api/v1/game-integrations/{id}/events — recent events for debugging - - GET /api/v1/game-adapters — list available adapter types + supported events + config schema -- [x] Task 5: Wire into main.py — create store, register in dependencies, include router -- [x] Task 6: Add to STORE_MAP in api/routes/system.py for backup/restore - - N/A — backups are SQLite-level (full database snapshot). Added `game_integrations` to `_ENTITY_TABLES` in `database.py` which automatically includes it in backup/restore. -- [x] Task 7: Add game_integrations_file to StorageConfig in config.py (if JSON-based) or ensure DB table - - Added `"game_integrations"` to `_ENTITY_TABLES` in `database.py` — table auto-created on startup. -- [x] Task 8: Event ingestion logic — parse payload through adapter, publish to EventBus, track per-integration state (for diff detection) -- [x] Task 9: Write tests for store (CRUD, validation, uniqueness) -- [x] Task 10: Write tests for API routes (create, list, update, delete, event ingestion) - -## Files to Modify/Create -- `server/src/wled_controller/storage/game_integration.py` — dataclass models -- `server/src/wled_controller/storage/game_integration_store.py` — SQLite store -- `server/src/wled_controller/api/schemas/game_integration.py` — Pydantic schemas -- `server/src/wled_controller/api/routes/game_integration.py` — REST endpoints -- `server/src/wled_controller/main.py` — wire store + router -- `server/src/wled_controller/api/dependencies.py` — add get_game_integration_store -- `server/src/wled_controller/api/routes/system.py` — add to STORE_MAP -- `server/src/wled_controller/config.py` — add storage config (if needed) -- `server/tests/storage/test_game_integration_store.py` — store tests -- `server/tests/api/test_game_integration_routes.py` — route tests - -## Acceptance Criteria -- Full CRUD for game integration configs via REST API -- Event ingestion endpoint parses payload through the correct adapter and publishes to EventBus -- Adapter metadata endpoint returns available adapters with supported events and config schemas -- Status endpoint shows last event time and event counts -- Store validates name uniqueness -- Included in backup/restore via STORE_MAP -- All tests pass - -## Notes -- The ingestion endpoint must be low-latency — games send at tick rate (16-64 Hz) -- Store per-integration prev_state dict in memory (not persisted) for diff-based trigger detection -- Auth for ingestion endpoint: adapter-level auth (e.g. CS2 token) checked before standard API auth - -## Review Checklist -- [x] All tasks completed -- [x] Code follows project conventions -- [x] No unintended side effects -- [x] Tests pass - -## Handoff to Next Phase - -**Completed:** All 10 tasks implemented and tested. 48 tests pass, 0 failures. Ruff clean. - -### What was built -- `storage/game_integration.py` — `EventMapping` and `GameIntegrationConfig` dataclasses with full `to_dict()`/`from_dict()`/`create_from_kwargs()`/`apply_update()` methods. `apply_update()` returns a new instance (immutable pattern). -- `storage/game_integration_store.py` — `GameIntegrationStore` extending `BaseSqliteStore[GameIntegrationConfig]`. CRUD with name uniqueness, write-through caching. Aliases: `get_all_integrations`, `get_integration`, `delete_integration`. -- `api/schemas/game_integration.py` — Full Pydantic schema suite: `EventMappingSchema`, `GameIntegrationCreate/Update/Response/ListResponse`, `GameEventPayload`, `AdapterInfoResponse/AdapterListResponse`, `GameIntegrationStatusResponse`, `GameEventResponse/RecentEventsResponse`. -- `api/routes/game_integration.py` — 9 endpoints: full CRUD + event ingestion + status + recent events + adapter listing. Ingestion endpoint skips `AuthRequired` (uses adapter-level auth via `validate_auth()`). Per-integration runtime state tracked in-memory (`_prev_states`, `_integration_stats`) with thread-safe access. -- `database.py` — Added `"game_integrations"` to `_ENTITY_TABLES` so the table is auto-created and included in database backups. -- `api/dependencies.py` — Added `get_game_integration_store()` and `get_game_event_bus()` getters + `init_dependencies()` params. -- `api/__init__.py` — Registered `game_integration_router`. -- `main.py` — Creates `GameIntegrationStore(db)` and `GameEventBus()`, passes both to `init_dependencies()`. - -### Key design decisions -- **Ingestion auth**: The `/event` endpoint does NOT use `AuthRequired`. Instead, it calls `adapter_cls.validate_auth()` with request headers and adapter config. This allows game clients (CS2 GSI, etc.) to authenticate with their own token scheme without needing the app's API key. -- **Runtime state**: Per-integration `prev_state` (for diff detection) and event stats are stored in module-level dicts with a threading lock. Not persisted — resets on server restart. -- **Connected heuristic**: An integration is "connected" if it received an event within the last 30 seconds (based on monotonic time). -- **Immutable updates**: `GameIntegrationConfig.apply_update()` returns a new instance; the store replaces the cache entry. - -### What Phase 3+ needs -- `GameEventBus` singleton is now available via `get_game_event_bus()` dependency — Phase 4/5 streams can subscribe to it. -- `GameIntegrationStore` is available via `get_game_integration_store()` — Phase 6 frontend can call the CRUD API. -- Built-in adapters (Phase 3) should call `AdapterRegistry.register(MyAdapter)` at import time to appear in `GET /api/v1/game-adapters`. -- The `adapter_config` field on each integration stores adapter-specific secrets (e.g. CS2 auth token). Phase 3 adapters define their config schema via `get_config_schema()`. diff --git a/plans/game-integration/phase-3-adapters.md b/plans/game-integration/phase-3-adapters.md deleted file mode 100644 index 6638fff..0000000 --- a/plans/game-integration/phase-3-adapters.md +++ /dev/null @@ -1,110 +0,0 @@ -# Phase 3: Built-in Game Adapters - -**Status:** ✅ Complete -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Implement built-in adapters for popular games and ship example community adapter YAML files. Each adapter translates a game's native data format into standardized GameEvents. - -## Tasks - -- [x] Task 1: CS2 Game State Integration adapter (`core/game_integration/adapters/cs2_adapter.py`) - - Parse CS2 GSI JSON payload (player.state, round, map sections) - - Events: player_health, player_armor, player_ammo, player_money, kill, death, round_start, round_end, bomb_planted, bomb_defused, flashbang, team - - Auth: validate payload["auth"]["token"] against adapter_config["auth_token"] - - Diff-based detection for kills (compare match_stats.kills with prev_state) - - Setup instructions: how to create gamestate_integration_*.cfg in CS2 - - Config schema: auth_token (string) -- [x] Task 2: Dota 2 Game State Integration adapter (`core/game_integration/adapters/dota2_adapter.py`) - - Similar to CS2 GSI format but different payload structure - - Events: player_health, player_mana, kill, death, match_start, match_end, gold - - Auth: same pattern as CS2 - - Config schema: auth_token -- [x] Task 3: League of Legends Live Client Data API adapter (`core/game_integration/adapters/lol_adapter.py`) - - Poll-based: fetches from https://127.0.0.1:2999/liveclientdata/allgamedata - - Events: player_health, player_mana, player_level, death, respawn, gold, game_time - - Adapter manages its own polling thread (started/stopped with integration enable/disable) - - Config schema: poll_interval_ms (int, default 500) - - Note: LoL uses self-signed SSL cert — needs verify=False or custom cert handling -- [x] Task 4: Generic webhook adapter (`core/game_integration/adapters/generic_webhook_adapter.py`) - - User-defined JSON path mappings (configured in adapter_config) - - Config schema: mappings list (same format as MappingAdapter YAML) - - Effectively a MappingAdapter configured via API rather than YAML file -- [x] Task 5: Register all built-in adapters in `core/game_integration/adapters/__init__.py` -- [x] Task 6: Create example community adapter YAML files - - `server/src/wled_controller/data/game_adapters/minecraft.yaml` — via webhook mod - - `server/src/wled_controller/data/game_adapters/valorant.yaml` — via Overwolf/Insights API - - `server/src/wled_controller/data/game_adapters/rocket_league.yaml` — via SOS plugin -- [x] Task 7: Community adapter loader — scan data/game_adapters/ on startup, register as available -- [x] Task 8: Write tests for CS2 adapter (payload parsing, auth validation, diff detection) -- [x] Task 9: Write tests for Dota 2 adapter -- [x] Task 10: Write tests for LoL adapter (mock HTTP responses) -- [x] Task 11: Write tests for generic webhook adapter -- [x] Task 12: Write tests for community YAML adapter loading - -## Files to Modify/Create -- `server/src/wled_controller/core/game_integration/adapters/cs2_adapter.py` -- `server/src/wled_controller/core/game_integration/adapters/dota2_adapter.py` -- `server/src/wled_controller/core/game_integration/adapters/lol_adapter.py` -- `server/src/wled_controller/core/game_integration/adapters/generic_webhook_adapter.py` -- `server/src/wled_controller/core/game_integration/adapters/__init__.py` — register all -- `server/src/wled_controller/data/game_adapters/minecraft.yaml` -- `server/src/wled_controller/data/game_adapters/valorant.yaml` -- `server/src/wled_controller/data/game_adapters/rocket_league.yaml` -- `server/tests/core/test_cs2_adapter.py` -- `server/tests/core/test_dota2_adapter.py` -- `server/tests/core/test_lol_adapter.py` -- `server/tests/core/test_generic_webhook_adapter.py` -- `server/tests/core/test_community_adapter_loader.py` - -## Acceptance Criteria -- CS2 adapter correctly parses real GSI payloads into standardized events -- Dota 2 adapter handles its GSI format -- LoL adapter can poll (mocked) and produce events -- Generic webhook adapter translates arbitrary JSON using user-defined mappings -- Community YAML files load and register correctly -- Auth validation works per adapter -- All tests pass with realistic payload samples - -## Notes -- Use real CS2/Dota2 GSI payload samples from documentation for tests -- LoL polling thread must be stoppable (daemon thread or event flag) -- Community adapter directory should be configurable (default: data/game_adapters/) -- Generic webhook adapter reuses MappingAdapter logic from Phase 1 - -## Review Checklist -- [x] All tasks completed -- [x] Code follows project conventions -- [x] No unintended side effects -- [x] Tests pass - -## Handoff to Next Phase - -**Completed:** All 12 tasks implemented and tested. 92 tests pass, 0 failures. Ruff clean. - -### What was built - -- `adapters/cs2_adapter.py` — `CS2Adapter` parsing CS2 GSI payloads. Continuous events: health, armor, money (gold), ammo. Diff-based triggers: kill, death. Phase triggers: round_start, round_end, bomb_planted (objective_captured), bomb_defused (objective_lost). Flash detection (blinded). Team affiliation (team_a/team_b). Auth via `payload["auth"]["token"]`. -- `adapters/dota2_adapter.py` — `Dota2Adapter` parsing Dota 2 GSI payloads. Continuous: health (hp/max_hp ratio), mana (mp/max_mp ratio), gold (configurable max). Diff-based: kill, death. Match flow: match_start (PRE_GAME/GAME_IN_PROGRESS), match_end (POST_GAME/DISCONNECT). Auth via `payload["auth"]["token"]`. -- `adapters/lol_adapter.py` — `LoLAdapter` parsing LoL Live Client Data payloads + `LoLPoller` daemon thread for polling `https://127.0.0.1:2999/liveclientdata/allgamedata`. Continuous: health, mana, level (speed), gold. Triggers: death (health drops to 0), respawn (objective_progress). No auth (local-only API). Poller uses `threading.Event` for clean stop, `ssl.CERT_NONE` for self-signed cert. -- `adapters/generic_webhook_adapter.py` — `GenericWebhookAdapter` delegating to `MappingAdapter` internally. User defines mappings in `adapter_config["mappings"]`. Auth via configurable header (default: Authorization with Bearer prefix support). -- `adapters/__init__.py` — Registers all 4 built-in adapters with `AdapterRegistry` on import. -- `community_loader.py` — Scans `data/game_adapters/` for `.yaml`/`.yml` files, loads them as `MappingAdapter` instances keyed as `community_`. Module-level registry with `register_community_adapters()`, `get_community_adapter()`, `get_community_adapter_info()`. -- `data/game_adapters/minecraft.yaml` — Webhook-based, maps health/armor/food/XP/kills/deaths. -- `data/game_adapters/valorant.yaml` — Webhook-based via Overwolf, maps health/shield/money/kills/deaths/round/spike. -- `data/game_adapters/rocket_league.yaml` — Webhook-based via SOS plugin bridge, maps boost/speed/goals/time/teams. - -### Key design decisions - -- **CS2/Dota2 auth** uses `payload["auth"]["token"]` (not HTTP headers) — matches how Valve's GSI actually sends the token. -- **LoL polling** is opt-in via `LoLPoller` class, not auto-started by the adapter. The integration manager (Phase 4+) should instantiate and manage poller lifecycle. -- **Generic webhook** creates a transient `MappingAdapter` per `parse_payload` call. This is simple and stateless — the adapter_config is the source of truth. For high-frequency usage, caching the MappingAdapter instance could be a future optimization. -- **Community adapters** are separate from `AdapterRegistry` (which holds class-based adapters). They live in `community_loader._community_adapters` since they're instance-based MappingAdapters. - -### What Phase 4+ needs - -- Import `wled_controller.core.game_integration.adapters` in `main.py` to trigger built-in adapter registration. -- Call `register_community_adapters()` from `community_loader` during app startup. -- The adapter listing endpoint (`GET /api/v1/game-adapters`) should also include `get_community_adapter_info()` results. -- LoL polling needs lifecycle management — start `LoLPoller` when a LoL integration is enabled, stop when disabled. diff --git a/plans/game-integration/phase-4-css-stream.md b/plans/game-integration/phase-4-css-stream.md deleted file mode 100644 index aa08907..0000000 --- a/plans/game-integration/phase-4-css-stream.md +++ /dev/null @@ -1,78 +0,0 @@ -# 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 diff --git a/plans/game-integration/phase-5-value-source.md b/plans/game-integration/phase-5-value-source.md deleted file mode 100644 index e6a2614..0000000 --- a/plans/game-integration/phase-5-value-source.md +++ /dev/null @@ -1,69 +0,0 @@ -# Phase 5: GameEventValueSource - -**Status:** ✅ Complete -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** backend - -## Objective -Create a new ValueSource type that exposes game metrics (health, ammo, mana, etc.) as 0.0-1.0 scalar values. These can be bound to any existing effect parameter (brightness, speed, color position) via the BindableFloat/BindableColor system. - -## Tasks - -- [x] Task 1: Create `GameEventValueSource` dataclass in `storage/value_source.py` - - Fields: game_integration_id (str), event_type (str — from standard vocabulary), min_game_value (float, default 0.0), max_game_value (float, default 100.0), smoothing (float, 0.0-1.0, EMA alpha), default_value (float, 0.5), timeout (float, seconds before reverting to default) - - source_type = "game_event" - - Implement to_dict/from_dict -- [x] Task 2: Register "game_event" in `_VALUE_SOURCE_MAP` in value_source.py -- [x] Task 3: Create `GameEventValueStream` runtime resolver (`core/value_sources/game_event_value_source.py`) - - Subscribe to EventBus for the configured event_type - - Normalize incoming value using min/max mapping → 0.0-1.0 - - Apply EMA smoothing if configured - - Track last_event_time for timeout detection - - `get_value()` → returns current normalized value (or default if timed out) - - `get_color()` → returns None (game event value source only provides scalars) - - Thread-safe (EventBus callback + get_value from render thread) - - Cleanup: unsubscribe from EventBus on release -- [x] Task 4: Register in value source manager / factory - - Add case for "game_event" source_type in the value stream creation logic - - Inject EventBus reference -- [x] Task 5: Write tests for GameEventValueSource (serialization, from_dict, defaults) -- [x] Task 6: Write tests for GameEventValueStream (normalization, smoothing, timeout, thread safety) - -## Files to Modify/Create -- `server/src/wled_controller/storage/value_source.py` — add GameEventValueSource + register -- `server/src/wled_controller/core/value_sources/game_event_value_source.py` — runtime resolver -- `server/src/wled_controller/core/processing/value_stream.py` — register factory case + event_bus param -- `server/tests/core/test_game_event_value_source.py` — tests - -## Acceptance Criteria -- "game_event" value source type serializes/deserializes correctly -- Runtime resolver normalizes game values to 0.0-1.0 using min/max -- EMA smoothing works correctly (smooth transitions, not jumpy) -- Timeout reverts to default_value when no events received -- Can be bound to BindableFloat properties on any ColorStripSource -- All tests pass - -## Notes -- EMA formula: `smoothed = alpha * new_value + (1 - alpha) * smoothed` where alpha = 1 - smoothing -- Timeout uses monotonic clock — compare current time vs last_event_time -- Best suited for continuous event types (health, mana, ammo) — triggers (kill, death) are less useful here -- ⚠️ Big Bang: value_source.py is a shared file — coordinate with Phase 4 if running in parallel - -## Review Checklist -- [x] All tasks completed -- [x] Code follows project conventions -- [x] No unintended side effects -- [x] Tests pass (24/24) - -## Handoff to Next Phase - -### What was implemented -- **GameEventValueSource** dataclass in `storage/value_source.py` with all required fields (game_integration_id, event_type, min/max mapping, smoothing, default_value, timeout) -- **GameEventValueStream** in new module `core/value_sources/game_event_value_source.py` — subscribes to GameEventBus, normalizes values, applies EMA smoothing, handles timeout with monotonic clock, thread-safe via threading.Lock -- **ValueStreamManager** updated with `event_bus` parameter and factory case for `GameEventValueSource` in `core/processing/value_stream.py` -- **24 tests** covering serialization, normalization (7 cases), smoothing (3 cases), timeout (4 cases), lifecycle (4 cases), thread safety, and hot-update - -### Integration notes for downstream phases -- `ValueStreamManager.__init__` now accepts an optional `event_bus: GameEventBus` parameter — Phase 8 (wiring) needs to pass the EventBus instance when constructing ValueStreamManager in `dependencies.py` -- New module `core/value_sources/` created — contains `__init__.py` and `game_event_value_source.py` -- No changes to API schemas or routes — Phase 7 (frontend) will need to add "game_event" to the value source editor UI diff --git a/plans/game-integration/phase-6-frontend-management.md b/plans/game-integration/phase-6-frontend-management.md deleted file mode 100644 index 6f624c6..0000000 --- a/plans/game-integration/phase-6-frontend-management.md +++ /dev/null @@ -1,123 +0,0 @@ -# Phase 6: Frontend — Game Integration Management UI - -**Status:** ✅ Complete -**Parent plan:** [PLAN.md](./PLAN.md) -**Domain:** frontend - -## Objective -Build the UI for creating, configuring, and monitoring game integrations. Users pick a game from a visual grid, configure adapter settings with guided instructions, set up event-to-effect mappings visually, and monitor live events. - -## Tasks - -- [x] Task 1: Add "Game" tab/group to Streams tree navigation in streams.ts - - New tree group with game controller icon - - Children: game integrations list, game adapters info -- [x] Task 2: Create game integration cards in the Streams tab - - CardSection instance for game integrations - - Card shows: game name/icon, adapter type badge, status indicator (connected/waiting/error), last event timestamp, event count - - Status indicator: green dot = events received recently, yellow = waiting, red = error/timeout -- [x] Task 3: Create game integration editor modal (`templates/modals/game-integration-editor.html`) - - Step 1: Game picker — searchable grid of available adapters with game icons (IconSelect pattern) - - Step 2: Adapter config — auto-generated fields from adapter's config_schema (text inputs for auth tokens, number inputs for intervals) - - Step 3: Setup instructions — per-game markdown instructions (e.g. CS2 cfg file content) - - Step 4: Event mapping editor — visual grid of standard event categories - - Each mapping row: event type (dropdown), effect type (IconSelect: flash/pulse/sweep/color_shift/breathing), color picker, duration slider, intensity slider, priority - - Effect preset selector at top of mapping editor (dropdown: "FPS Combat", "MOBA Health", etc.) - - Name, description, tags fields -- [x] Task 4: Create TypeScript module `static/js/features/game-integration.ts` - - CRUD functions using fetchWithAuth - - Cache for game integrations data - - Cache for game adapters metadata - - Card rendering functions - - Modal open/save/delete handlers - - Event mapping editor logic (add/remove/reorder mappings) -- [x] Task 5: Game adapter icons — add SVG icons for supported games in `core/icons.ts` - - Generic gamepad icon for unknown games - - Stylized icons for CS2, LoL, Dota 2, Minecraft, Valorant, Rocket League -- [x] Task 6: Live event monitor panel - - Expandable panel on the game integration card (or modal tab) - - Shows real-time feed of incoming events: timestamp, event_type, value, color-coded by category - - Fetches from GET /api/v1/game-integrations/{id}/events (polling every 2s or WebSocket later) - - Useful for debugging: "is my game sending data?" -- [x] Task 7: Connection test button - - Button in modal that opens a test panel showing "Waiting for events..." - - When first event arrives, shows success with event details - - Helps users verify their game config is correct -- [x] Task 8: Add i18n keys for all new strings (en.json, ru.json, zh.json) - - Game integration section titles, modal labels, status messages, event category names, effect type names, error messages -- [x] Task 9: CSS styles for game integration cards and modal (`static/css/game-integration.css`) - - Game picker grid layout - - Event mapping editor rows - - Status indicators (colored dots) - - Live event monitor feed -- [x] Task 10: Wire into app.ts — import module, add to window exports for onclick handlers -- [x] Task 11: Wire into streams.ts — add cache, load function, CardSection, tree nav integration - -## Files to Modify/Create -- `server/src/wled_controller/static/js/features/game-integration.ts` — main module -- `server/src/wled_controller/static/js/features/streams.ts` — add game tab, cache, card section -- `server/src/wled_controller/static/js/app.ts` — import and window exports -- `server/src/wled_controller/static/js/core/icons.ts` — game icons -- `server/src/wled_controller/static/js/types.ts` — TypeScript types for game integration -- `server/src/wled_controller/static/css/game-integration.css` — styles -- `server/src/wled_controller/templates/modals/game-integration-editor.html` — modal template -- `server/src/wled_controller/templates/index.html` — include modal template -- `server/src/wled_controller/static/locales/en.json` — i18n keys -- `server/src/wled_controller/static/locales/ru.json` — i18n keys -- `server/src/wled_controller/static/locales/zh.json` — i18n keys - -## Acceptance Criteria -- Game integration tab appears in Streams tree navigation -- Cards display with correct status indicators and game icons -- Modal wizard guides user through game selection → config → mapping -- Adapter-specific config fields are auto-generated from schema -- Setup instructions display per game -- Event mapping editor allows visual configuration with effect previews -- Effect presets populate mapping editor with sensible defaults -- Live event monitor shows incoming events -- Connection test provides clear feedback -- All i18n keys present in all 3 languages -- UI follows existing project conventions (no emoji, SVG icons, IconSelect/EntitySelect) - -## Notes -- Follow existing modal patterns: Modal base class, snapshotValues() for dirty check -- Follow CardSection pattern for entity list with reconciliation -- Use fetchWithAuth for ALL API calls -- Icons must be SVG paths in icons.ts — NEVER emoji -- Use IconSelect for game picker and effect type selector -- Use EntitySelect for game_integration_id references -- The event mapping editor is the most complex UI piece — consider a sub-component approach - -## Review Checklist -- [x] All tasks completed -- [x] Code follows frontend conventions -- [x] No unintended side effects -- [x] TypeScript compiles without errors (only pre-existing SystemMetricsValueSource error remains) -- [x] Bundle builds successfully - -## Handoff to Next Phase - -All 11 tasks implemented. Key implementation details: - -**Files created:** -- `server/src/wled_controller/static/js/features/game-integration.ts` — main module (CRUD, cards, modal handlers, event monitor, connection test) -- `server/src/wled_controller/static/css/game-integration.css` — styles for mapping editor, event feed, status indicators, connection test panel -- `server/src/wled_controller/templates/modals/game-integration-editor.html` — modal with adapter picker, config fields, mapping editor, live events, connection test - -**Files modified:** -- `icon-paths.ts` — added gamepad2, crosshair, swords, shield, pickaxe, rocketIcon, circleDot -- `icons.ts` — added ICON_GAMEPAD, ICON_CROSSHAIR, ICON_SWORDS, ICON_SHIELD, ICON_PICKAXE, ICON_ROCKET_ICON, ICON_CIRCLE_DOT, getGameAdapterIcon() -- `types.ts` — added GameIntegration, GameAdapterInfo, GameEventMapping, GameEventRecord, GameIntegrationStatus -- `state.ts` — added gameIntegrationsCache, gameAdaptersCache, _cachedGameIntegrations, _cachedGameAdapters -- `streams.ts` — added game tab to tree nav (under Integrations group), CardSection, cache fetching, reconciliation -- `app.ts` — imported and wired all game integration functions to window -- `global.d.ts` — added window type declarations for game integration functions -- `index.html` — included game-integration-editor.html modal -- `all.css` — imported game-integration.css -- `en.json`, `ru.json`, `zh.json` — added ~50 i18n keys each - -**API endpoints consumed:** GET/POST/PUT/DELETE /game-integrations, GET /game-adapters, GET /game-integrations/{id}/events, GET /game-integrations/{id}/status - -**Conventions followed:** No emoji (SVG icons only), fetchWithAuth for all API calls, IconSelect for adapter picker, Modal subclass with snapshotValues() dirty check, TagInput for tags, CardSection with reconciliation, cache.invalidate() before reload, all strings via t() with i18n keys in 3 locales. - -**Note:** The mapping editor uses plain `` — use project custom selectors (CRITICAL project rule) +- NEVER use emoji — use SVG icons from `core/icons.ts` +- Use fetchWithAuth for ALL API calls (project rule) +- Call cache.invalidate() before load functions in save/delete handlers + +## Review Checklist +- [ ] All tasks completed +- [ ] Code follows project conventions +- [ ] No unintended side effects +- [ ] Build passes +- [ ] Tests pass (new + existing) + +## Handoff to Next Phase + diff --git a/plans/processed-audio-sources/phase-6-frontend-source-types.md b/plans/processed-audio-sources/phase-6-frontend-source-types.md new file mode 100644 index 0000000..3b6fb29 --- /dev/null +++ b/plans/processed-audio-sources/phase-6-frontend-source-types.md @@ -0,0 +1,67 @@ +# Phase 6: Frontend — Source Types + +**Status:** ⬜ Not Started +**Parent plan:** [PLAN.md](./PLAN.md) +**Domain:** frontend + +## Objective +Update the audio source UI to support the new `ProcessedAudioSource` and `CaptureAudioSource` types, and remove the old `MonoAudioSource` and `BandExtractAudioSource` UI. + +## Tasks + +- [ ] Task 1: Update audio source TypeScript types/interfaces for new source types + - Add `ProcessedAudioSource` type with `audio_source_id` + `audio_processing_template_id` + - Rename `MultichannelAudioSource` type to `CaptureAudioSource` (source_type: "capture") + - Remove `MonoAudioSource` and `BandExtractAudioSource` types +- [ ] Task 2: Create `ProcessedAudioSource` card component + - EntitySelect for input audio source (any audio source) + - EntitySelect for audio processing template + - Show resolved chain info (which capture source at the end) + - Create/Edit/Delete actions +- [ ] Task 3: Update `CaptureAudioSource` card (relabeled from Multichannel) + - Same fields (device selector, loopback toggle, template selector) + - Updated label/icon to say "Capture Audio Source" +- [ ] Task 4: Remove `MonoAudioSource` card component/rendering +- [ ] Task 5: Remove `BandExtractAudioSource` card component/rendering +- [ ] Task 6: Update audio source creation dialog/flow + - Source type picker now shows: Capture, Processed (instead of Multichannel, Mono, Band Extract) + - Type-specific form fields +- [ ] Task 7: Update EntitySelect dropdowns that list audio sources + - Show type badges (Capture vs Processed) for clarity + - Audio source selectors in CSS editor, value source editor, etc. +- [ ] Task 8: Update i18n keys for renamed/new source types +- [ ] Task 9: Update any inline onclick handlers or window exports in app.js + +## Files to Modify/Create +- `static/js/features/audio-sources.ts` — **modify** — new types, remove old types +- `static/js/features/audio-source-modal.ts` (or equivalent) — **modify** — updated editor +- `static/css/dashboard.css` — **modify** — any style updates +- `static/js/app.js` — **modify** — update exports if needed +- `static/js/core/i18n/en.json` — **modify** — updated keys +- `static/js/core/i18n/ru.json` — **modify** — updated keys +- `static/js/core/i18n/zh.json` — **modify** — updated keys + +## Acceptance Criteria +- ProcessedAudioSource can be created/edited/deleted from the UI +- CaptureAudioSource shows correctly with updated label +- MonoAudioSource and BandExtractAudioSource UI is completely removed +- EntitySelect for audio sources shows type badges +- Source type picker shows only Capture and Processed +- All strings are internationalized + +## Notes +- NEVER use plain HTML `