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
@@ -27,7 +27,7 @@
padding: 0 4px;
}
/* Automation condition pills — constrain to card width */
/* Automation rule pills — constrain to card width */
[data-automation-id] .card-meta {
display: flex;
flex-wrap: wrap;
@@ -41,8 +41,8 @@
white-space: nowrap;
}
/* Automation condition editor rows */
.automation-condition-row {
/* Automation rule editor rows */
.automation-rule-row {
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 10px;
@@ -50,19 +50,19 @@
background: var(--bg-secondary, var(--bg-color));
}
.condition-header {
.rule-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.condition-type-label {
.rule-type-label {
font-weight: 600;
font-size: 0.9rem;
}
.condition-type-select {
.rule-type-select {
font-weight: 600;
font-size: 0.9rem;
padding: 2px 6px;
@@ -72,13 +72,13 @@
color: var(--text-color);
}
.condition-always-desc {
.rule-hint-desc {
display: block;
color: var(--text-muted);
font-size: 0.85rem;
}
.btn-remove-condition {
.btn-remove-rule {
background: none;
border: none;
color: var(--danger-color, #dc3545);
@@ -88,22 +88,22 @@
transition: opacity 0.15s;
}
.btn-remove-condition:hover {
.btn-remove-rule:hover {
opacity: 1;
}
.btn-remove-condition .icon {
.btn-remove-rule .icon {
width: 16px;
height: 16px;
}
.condition-fields {
.rule-fields {
display: flex;
flex-direction: column;
gap: 8px;
}
.condition-field label {
.rule-field label {
display: block;
font-size: 0.85rem;
margin-bottom: 3px;
@@ -202,8 +202,8 @@
}
.condition-field select,
.condition-field textarea {
.rule-field select,
.rule-field textarea {
width: 100%;
padding: 6px 8px;
border: 1px solid var(--border-color);
@@ -214,12 +214,12 @@
font-family: inherit;
}
.condition-apps {
.rule-apps {
resize: vertical;
min-height: 60px;
}
.condition-apps-header {
.rule-apps-header {
display: flex;
justify-content: space-between;
align-items: center;