Files
sideline/.opencode/skills/mainline-effects/SKILL.md
David Gwilliam b926b346ad fix: resolve terminal display wobble and effect dimension stability
- 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
2026-03-18 03:37:53 -07:00

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
audience source_type
developers 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:

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

  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:

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