forked from genewildish/Mainline
Major changes: - Pipeline architecture with capability-based dependency resolution - Effects plugin system with performance monitoring - Display abstraction with multiple backends (terminal, null, websocket) - Camera system for viewport scrolling - Sensor framework for real-time input - Command-and-control system via ntfy - WebSocket display backend for browser clients - Comprehensive test suite and documentation Issue #48: ADR for preset scripting language included This commit consolidates 110 individual commits into a single feature integration that can be reviewed and tested before further refinement.
114 lines
2.7 KiB
Markdown
114 lines
2.7 KiB
Markdown
---
|
|
name: mainline-effects
|
|
description: How to add new effect plugins to Mainline's effect system
|
|
compatibility: opencode
|
|
metadata:
|
|
audience: developers
|
|
source_type: codebase
|
|
---
|
|
|
|
## What This Skill Covers
|
|
|
|
This skill covers Mainline's effect plugin system - how to create, configure, and integrate visual effects into the pipeline.
|
|
|
|
## Key Concepts
|
|
|
|
### EffectPlugin ABC (engine/effects/types.py)
|
|
|
|
All effects must inherit from `EffectPlugin` and implement:
|
|
|
|
```python
|
|
class EffectPlugin(ABC):
|
|
name: str
|
|
config: EffectConfig
|
|
param_bindings: dict[str, dict[str, str | float]] = {}
|
|
supports_partial_updates: bool = False
|
|
|
|
@abstractmethod
|
|
def process(self, buf: list[str], ctx: EffectContext) -> list[str]:
|
|
"""Process buffer with effect applied"""
|
|
...
|
|
|
|
@abstractmethod
|
|
def configure(self, config: EffectConfig) -> None:
|
|
"""Configure the effect"""
|
|
...
|
|
```
|
|
|
|
### EffectContext
|
|
|
|
Passed to every effect's process method:
|
|
|
|
```python
|
|
@dataclass
|
|
class EffectContext:
|
|
terminal_width: int
|
|
terminal_height: int
|
|
scroll_cam: int
|
|
ticker_height: int
|
|
camera_x: int = 0
|
|
mic_excess: float = 0.0
|
|
grad_offset: float = 0.0
|
|
frame_number: int = 0
|
|
has_message: bool = False
|
|
items: list = field(default_factory=list)
|
|
_state: dict[str, Any] = field(default_factory=dict)
|
|
```
|
|
|
|
Access sensor values via `ctx.get_sensor_value("sensor_name")`.
|
|
|
|
### EffectConfig
|
|
|
|
Configuration dataclass:
|
|
|
|
```python
|
|
@dataclass
|
|
class EffectConfig:
|
|
enabled: bool = True
|
|
intensity: float = 1.0
|
|
params: dict[str, Any] = field(default_factory=dict)
|
|
```
|
|
|
|
### Partial Updates
|
|
|
|
For performance optimization, set `supports_partial_updates = True` and implement `process_partial`:
|
|
|
|
```python
|
|
class MyEffect(EffectPlugin):
|
|
supports_partial_updates = True
|
|
|
|
def process_partial(self, buf, ctx, partial: PartialUpdate) -> list[str]:
|
|
# Only process changed regions
|
|
...
|
|
```
|
|
|
|
## Adding a New Effect
|
|
|
|
1. Create file in `effects_plugins/my_effect.py`
|
|
2. Inherit from `EffectPlugin`
|
|
3. Implement `process()` and `configure()`
|
|
4. Add to `effects_plugins/__init__.py` (runtime discovery via issubclass checks)
|
|
|
|
## Param Bindings
|
|
|
|
Declarative sensor-to-param mappings:
|
|
|
|
```python
|
|
param_bindings = {
|
|
"intensity": {"sensor": "mic", "transform": "linear"},
|
|
"rate": {"sensor": "oscillator", "transform": "exponential"},
|
|
}
|
|
```
|
|
|
|
Transforms: `linear`, `exponential`, `threshold`
|
|
|
|
## Effect Chain
|
|
|
|
Effects are chained via `engine/effects/chain.py` - processes each effect in order, passing output to next.
|
|
|
|
## Existing Effects
|
|
|
|
See `effects_plugins/`:
|
|
- noise.py, fade.py, glitch.py, firehose.py
|
|
- border.py, crop.py, tint.py, hud.py
|