- Fix TerminalDisplay: add screen clear each frame (cursor home + erase down) - Fix CameraStage: use set_canvas_size instead of read-only viewport properties - Fix Glitch effect: preserve visible line lengths, remove cursor positioning - Fix Fade effect: return original line when fade=0 instead of empty string - Fix Noise effect: use input line length instead of terminal_width - Remove HUD effect from all presets (redundant with border FPS display) - Add regression tests for effect dimension stability - Add docs/ARCHITECTURE.md with Mermaid diagrams - Add mise tasks: diagram-ascii, diagram-validate, diagram-check - Move markdown docs to docs/ (ARCHITECTURE, Refactor, hardware specs) - Remove redundant requirements files (use pyproject.toml) - Add *.dot and *.png to .gitignore Closes #25
2.7 KiB
2.7 KiB
name, description, compatibility, metadata
| name | description | compatibility | metadata | ||||
|---|---|---|---|---|---|---|---|
| mainline-effects | How to add new effect plugins to Mainline's effect system | opencode |
|
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:
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:
@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:
@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:
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
- Create file in
effects_plugins/my_effect.py - Inherit from
EffectPlugin - Implement
process()andconfigure() - Add to
effects_plugins/__init__.py(runtime discovery via issubclass checks)
Param Bindings
Declarative sensor-to-param mappings:
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