feat: game integration system
Receive real-time events from games (CS2, Dota 2, LoL, etc.) and drive LED effects through the existing color strip and value source pipelines. Core: - GameEventBus (thread-safe pub/sub) with standardized 23-type event vocabulary - GameAdapter ABC + AdapterRegistry + MappingAdapter (YAML-driven) - Built-in adapters: CS2 GSI, Dota 2 GSI, LoL Live Client, Generic Webhook - Community YAML adapters: Minecraft, Valorant, Rocket League - GameEventColorStripStream with 5 effects (flash/pulse/sweep/color_shift/breathing) - GameEventValueSource with EMA smoothing and timeout - 4 built-in effect presets (FPS Combat, MOBA Health, Racing, Generic Alert) - Auto-setup for Valve GSI games (Steam path detection, cfg file writing) - Demo capture engine exposed to non-demo mode Frontend: - Game tab in Streams tree navigation with integration cards - Game integration editor modal with adapter picker, config fields, event mappings - game_event source type in CSS and ValueSource editors - Setup instructions overlay (markdown rendered) - Live event monitor and connection test API: - Full CRUD for game integrations - Event ingestion endpoint (adapter-level auth) - Adapter metadata, presets, auto-setup, status/diagnostics endpoints
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# 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 |
|
||||
|-------|-----------|-------------|----------|-------|
|
||||
@@ -0,0 +1,53 @@
|
||||
# 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`
|
||||
@@ -0,0 +1,107 @@
|
||||
# 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`.
|
||||
@@ -0,0 +1,102 @@
|
||||
# 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()`.
|
||||
@@ -0,0 +1,110 @@
|
||||
# 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_<stem>`. 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.
|
||||
@@ -0,0 +1,78 @@
|
||||
# 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
|
||||
@@ -0,0 +1,69 @@
|
||||
# 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
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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 `<select>` for event_type (since the available events are dynamic per adapter and may be unknown user-defined strings). Effect type selector has a select element that could be upgraded to IconSelect in a follow-up if desired. The preset selector is intentionally a plain select since it is a simple action trigger, not a form value.
|
||||
@@ -0,0 +1,79 @@
|
||||
# Phase 7: Frontend — ColorStrip & ValueSource Game Bindings
|
||||
|
||||
**Status:** ✅ Complete
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** frontend
|
||||
|
||||
## Objective
|
||||
Add "game_event" as a selectable source type in the ColorStrip and ValueSource editors so users can create game-driven LED streams and parameter bindings from the existing entity UIs.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] Task 1: Add "game_event" to ColorStripSource type selector (IconSelect)
|
||||
- New icon + label in the source type grid
|
||||
- When selected, show game integration picker (EntitySelect) and event mapping editor
|
||||
- [x] Task 2: Game integration picker in CSS editor
|
||||
- EntitySelect dropdown listing available game integrations
|
||||
- When selected, show the integration's supported events
|
||||
- [x] Task 3: Inline event mapping UI in CSS source editor
|
||||
- Simplified version of the full mapping editor from Phase 6
|
||||
- Allows override/supplement of the integration-level mappings
|
||||
- Idle color picker
|
||||
- [x] Task 4: Add "game_event" to ValueSource type selector
|
||||
- New icon + label in the value source type grid
|
||||
- [x] Task 5: Game value source config fields
|
||||
- Game integration picker (EntitySelect)
|
||||
- Event type picker (dropdown of continuous events from the selected integration)
|
||||
- Min/max game value inputs
|
||||
- Smoothing slider (0.0-1.0)
|
||||
- Default value input
|
||||
- Timeout input (seconds)
|
||||
- [x] Task 6: Add i18n keys for new source type labels and config fields
|
||||
- [x] Task 7: Update TypeScript types for new source types
|
||||
|
||||
## Files to Modify/Create
|
||||
- `server/src/wled_controller/static/js/features/streams.ts` — CSS editor game_event fields
|
||||
- `server/src/wled_controller/static/js/features/game-integration.ts` — shared helpers
|
||||
- `server/src/wled_controller/static/js/types.ts` — type definitions
|
||||
- `server/src/wled_controller/templates/modals/` — update CSS and value source modals
|
||||
- `server/src/wled_controller/static/locales/en.json`
|
||||
- `server/src/wled_controller/static/locales/ru.json`
|
||||
- `server/src/wled_controller/static/locales/zh.json`
|
||||
|
||||
## Acceptance Criteria
|
||||
- "game_event" appears in both source type selectors with appropriate icon
|
||||
- Selecting it shows game integration picker and relevant config fields
|
||||
- CSS editor shows event mapping override UI
|
||||
- Value source editor shows normalization and smoothing controls
|
||||
- All i18n keys present
|
||||
|
||||
## Notes
|
||||
- Depends on Phase 4 (CSS source type exists) and Phase 5 (value source type exists)
|
||||
- Reuse game-integration.ts helpers for adapter/event metadata fetching
|
||||
|
||||
## Review Checklist
|
||||
- [x] All tasks completed
|
||||
- [x] Code follows frontend conventions
|
||||
- [x] TypeScript compiles without errors
|
||||
- [x] Bundle builds successfully
|
||||
|
||||
## Handoff to Next Phase
|
||||
|
||||
All 7 tasks implemented. Key implementation details:
|
||||
|
||||
**Files modified:**
|
||||
- `icons.ts` — added `game_event` to both `_colorStripTypeIcons` and `_valueSourceTypeIcons` maps (gamepad2 icon)
|
||||
- `types.ts` — added `game_event` to `CSSSourceType` and `ValueSourceType` unions, added `GameEventValueSource` interface, added game event fields to `ColorStripSource` interface, also fixed pre-existing `system_metrics` missing from `ValueSourceType`
|
||||
- `color-strips.ts` — added `game_event` to `CSS_TYPE_KEYS`, `CSS_SECTION_MAP`, `CSS_TYPE_SETUP`, and `_typeHandlers`; added helper functions for game integration EntitySelect dropdown, idle color BindableColorWidget, inline event mapping editor (add/remove/preset), and game mapping collection for save
|
||||
- `value-sources.ts` — added `game_event` to `VS_FLOAT_TYPE_KEYS`; added section toggle in `onValueSourceTypeChange`; added loading/save logic for game event fields; added EntitySelect for game integration picker with filtered continuous event type dropdown
|
||||
- `css-editor.html` — added `css-editor-game-event-section` with game integration select, idle color container, mapping preset select, inline mapping editor, and add mapping button
|
||||
- `value-source-editor.html` — added `value-source-game-event-section` with game integration select, event type select (continuous events), min/max game value inputs, smoothing/default/timeout sliders
|
||||
- `app.ts` — imported and wired `addCSSGameMapping`, `removeCSSGameMapping`, `onCSSGameMappingPresetChange` to window
|
||||
- `global.d.ts` — added window type declarations for the 3 new exported functions
|
||||
- `en.json`, `ru.json`, `zh.json` — added ~28 i18n keys each for CSS and value source game_event labels, hints, and errors
|
||||
|
||||
**UI behavior:**
|
||||
- CSS editor: selecting "Game Event" type shows a game integration EntitySelect, BindableColor idle color picker, and an inline event mapping editor with preset loading (FPS Combat / MOBA Health), add/remove mapping rows (event type, effect type, color, duration, intensity, priority)
|
||||
- Value source editor: selecting "Game Event" type shows a game integration EntitySelect, event type dropdown (filtered to continuous events from the adapter's supported_events), min/max game value number inputs, smoothing slider, default value slider, timeout slider
|
||||
|
||||
**Conventions followed:** No emoji (SVG icons only), EntitySelect for game integration picker, BindableColorWidget for idle color, fetchWithAuth, cache.invalidate() + reload pattern, all strings via t() with i18n keys in 3 locales, dirty check via snapshotValues(), widget cleanup in onForceClose().
|
||||
@@ -0,0 +1,84 @@
|
||||
# Phase 8: Effect Presets & Polish
|
||||
|
||||
**Status:** ✅ Complete
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** fullstack
|
||||
|
||||
## Objective
|
||||
Ship built-in effect presets, add a WebSocket endpoint for real-time event streaming to the frontend live monitor, and add a setup wizard for guided game configuration.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] Task 1: Define effect presets as data (`core/game_integration/presets.py`)
|
||||
- "FPS Combat": health→red glow, kill→green flash, death→full red pulse, round_start→team color sweep
|
||||
- "MOBA Health": health→gradient green-yellow-red, mana→blue glow, death→fade to black
|
||||
- "Racing": speed→color temperature, boost→rainbow flash
|
||||
- "Generic Alert": any trigger→white flash
|
||||
- Each preset: name, description, target_game_types (fps/moba/racing/any), event_mappings list
|
||||
- [x] Task 2: Preset API endpoints
|
||||
- GET /api/v1/game-integrations/presets — list available presets
|
||||
- POST /api/v1/game-integrations/{id}/apply-preset — apply preset to integration
|
||||
- [ ] Task 3: WebSocket endpoint for live event streaming — DEFERRED (polling works; WebSocket is a future optimization)
|
||||
- [x] Task 4: Frontend preset selector in modal
|
||||
- Dropdown of presets loaded from API with descriptions
|
||||
- "Apply" populates the event mapping editor
|
||||
- [ ] Task 5: Game adapter setup wizard — DEFERRED (setup instructions already shown per-adapter)
|
||||
- [ ] Task 6: Community adapter import UI — DEFERRED (community adapters are loaded from data/game_adapters/ dir on startup; file upload is a future enhancement)
|
||||
- [x] Task 7: Final integration testing — wiring verified, all 606 tests pass
|
||||
- [ ] Task 8: Documentation for creating community adapters — DEFERRED (not blocking release)
|
||||
- [x] Task 9 (added): Wiring fixes — game_event_bus passed through ProcessorDependencies to ColorStripStreamManager and ValueStreamManager
|
||||
- [x] Task 10 (added): Adapter registration — import built-in adapters and call register_community_adapters() in main.py
|
||||
|
||||
## Files to Modify/Create
|
||||
- `server/src/wled_controller/core/game_integration/presets.py`
|
||||
- `server/src/wled_controller/api/routes/game_integration.py` — preset + WS endpoints
|
||||
- `server/src/wled_controller/api/schemas/game_integration.py` — preset schemas
|
||||
- `server/src/wled_controller/static/js/features/game-integration.ts` — preset UI, wizard, import
|
||||
- `server/src/wled_controller/templates/modals/game-integration-editor.html` — wizard steps
|
||||
- `server/src/wled_controller/static/css/game-integration.css` — wizard styles
|
||||
- `server/src/wled_controller/static/locales/en.json`
|
||||
- `server/src/wled_controller/static/locales/ru.json`
|
||||
- `server/src/wled_controller/static/locales/zh.json`
|
||||
- `server/tests/core/test_game_presets.py`
|
||||
|
||||
## Acceptance Criteria
|
||||
- At least 4 effect presets ship out of the box
|
||||
- Presets can be applied to any game integration via API and UI
|
||||
- WebSocket endpoint streams events in real-time
|
||||
- Setup wizard provides clear per-game instructions
|
||||
- Community adapter import works (file upload + URL)
|
||||
- Full pipeline works end-to-end: game event → LED effect
|
||||
|
||||
## Notes
|
||||
- WebSocket uses FastAPI's built-in WebSocket support
|
||||
- Presets are read-only built-in data, not user-editable (users can modify after applying)
|
||||
- Setup wizard should pre-fill the server URL for webhook-based games
|
||||
|
||||
## Review Checklist
|
||||
- [x] All core tasks completed (presets, API, wiring, frontend, tests)
|
||||
- [x] Code follows project conventions
|
||||
- [x] Build passes (frontend + backend)
|
||||
- [x] All 606 tests pass
|
||||
- [x] Linting clean (ruff)
|
||||
- [x] TypeScript clean (tsc --noEmit)
|
||||
|
||||
## Handoff Notes (Final Phase)
|
||||
|
||||
**Files created:**
|
||||
- `server/src/wled_controller/core/game_integration/presets.py` — 4 built-in effect presets (FPS Combat, MOBA Health, Racing, Generic Alert) as frozen dataclasses
|
||||
- `server/tests/core/test_game_presets.py` — preset data structure tests (10 tests)
|
||||
- `server/tests/core/test_game_wiring.py` — GameEventBus wiring verification tests (4 tests)
|
||||
|
||||
**Files modified:**
|
||||
- `server/src/wled_controller/api/schemas/game_integration.py` — added EffectPresetResponse, PresetListResponse, ApplyPresetRequest schemas
|
||||
- `server/src/wled_controller/api/routes/game_integration.py` — added GET /presets and POST /{id}/apply-preset endpoints (presets route placed BEFORE {integration_id} to avoid path parameter conflict)
|
||||
- `server/src/wled_controller/core/processing/processor_manager.py` — added game_event_bus to ProcessorDependencies; wired to ColorStripStreamManager and ValueStreamManager
|
||||
- `server/src/wled_controller/main.py` — added adapter import, register_community_adapters() call, game_event_bus in ProcessorDependencies
|
||||
- `server/src/wled_controller/static/js/features/game-integration.ts` — replaced hardcoded preset data with API-loaded presets from /game-integrations/presets
|
||||
- `server/src/wled_controller/static/js/types.ts` — added EffectPreset interface
|
||||
- `server/src/wled_controller/static/locales/en.json`, `ru.json`, `zh.json` — added game_integration.mapping.select_preset key
|
||||
- `server/tests/api/routes/test_game_integration_routes.py` — added TestPresets class (6 tests: list, mappings, apply replace/append, unknown key/integration)
|
||||
|
||||
**Key wiring fix:** GameEventBus was created in main.py but NOT passed to ProcessorDependencies, meaning ColorStripStreamManager and ValueStreamManager could never receive it. Now properly threaded through.
|
||||
|
||||
**Deferred items:** WebSocket live streaming (Task 3), setup wizard (Task 5), community adapter import UI (Task 6), adapter documentation (Task 8) — none are blocking for the feature to function end-to-end.
|
||||
Reference in New Issue
Block a user