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:
2026-03-31 13:17:52 +03:00
parent b6713be390
commit 492bdb95e3
87 changed files with 12170 additions and 912 deletions
@@ -24,7 +24,7 @@
<label data-i18n="automations.enabled">Enabled:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="automations.enabled.hint">Disabled automations won't activate even when conditions are met</small>
<small class="input-hint" style="display:none" data-i18n="automations.enabled.hint">Disabled automations won't activate even when rules are met</small>
<label class="settings-toggle">
<input type="checkbox" id="automation-editor-enabled" checked>
<span class="settings-toggle-slider"></span>
@@ -33,25 +33,25 @@
<div class="form-group">
<div class="label-row">
<label for="automation-editor-logic" data-i18n="automations.condition_logic">Condition Logic:</label>
<label for="automation-editor-logic" data-i18n="automations.rule_logic">Rule Logic:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="automations.condition_logic.hint">How multiple conditions are combined: ANY (OR) or ALL (AND)</small>
<small class="input-hint" style="display:none" data-i18n="automations.rule_logic.hint">How multiple rules are combined: ANY (OR) or ALL (AND)</small>
<select id="automation-editor-logic">
<option value="or" data-i18n="automations.condition_logic.or">Any condition (OR)</option>
<option value="and" data-i18n="automations.condition_logic.and">All conditions (AND)</option>
<option value="or" data-i18n="automations.rule_logic.or">Any rule (OR)</option>
<option value="and" data-i18n="automations.rule_logic.and">All rules (AND)</option>
</select>
</div>
<div class="form-group">
<div class="label-row">
<label data-i18n="automations.conditions">Conditions:</label>
<label data-i18n="automations.rules">Rules:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="automations.conditions.hint">Rules that determine when this automation activates</small>
<div id="automation-conditions-list"></div>
<button type="button" class="btn btn-secondary btn-sm" onclick="addAutomationCondition()" style="margin-top: 6px;">
+ <span data-i18n="automations.conditions.add">Add Condition</span>
<small class="input-hint" style="display:none" data-i18n="automations.rules.hint">Rules that determine when this automation activates</small>
<div id="automation-rules-list"></div>
<button type="button" class="btn btn-secondary btn-sm" onclick="addAutomationRule()" style="margin-top: 6px;">
+ <span data-i18n="automations.rules.add">Add Rule</span>
</button>
</div>
@@ -60,7 +60,7 @@
<label for="automation-scene-id" data-i18n="automations.scene">Scene:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="automations.scene.hint">Scene preset to activate when conditions are met</small>
<small class="input-hint" style="display:none" data-i18n="automations.scene.hint">Scene preset to activate when rules are met</small>
<select id="automation-scene-id"></select>
</div>
@@ -69,7 +69,7 @@
<label for="automation-deactivation-mode" data-i18n="automations.deactivation_mode">Deactivation:</label>
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
</div>
<small class="input-hint" style="display:none" data-i18n="automations.deactivation_mode.hint">What happens when conditions stop matching</small>
<small class="input-hint" style="display:none" data-i18n="automations.deactivation_mode.hint">What happens when rules stop matching</small>
<select id="automation-deactivation-mode">
<option value="none" data-i18n="automations.deactivation_mode.none">None — keep current state</option>
<option value="revert" data-i18n="automations.deactivation_mode.revert">Revert to previous state</option>