""" Visual effects type definitions and base classes. EffectPlugin Architecture: - Uses ABC (Abstract Base Class) for interface enforcement - Runtime discovery via directory scanning (effects_plugins/) - Configuration via EffectConfig dataclass - Context passed through EffectContext dataclass Plugin System Research (see AGENTS.md for references): - VST: Standardized audio interfaces, chaining, presets (FXP/FXB) - Python Entry Points: Namespace packages, importlib.metadata discovery - Shadertoy: Shader-based with uniforms as context Current gaps vs industry patterns: - No preset save/load system - No external plugin distribution via entry points - No plugin metadata (version, author, description) """ from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import Any @dataclass class EffectContext: terminal_width: int terminal_height: int scroll_cam: int ticker_height: int mic_excess: float grad_offset: float frame_number: int has_message: bool items: list = field(default_factory=list) @dataclass class EffectConfig: enabled: bool = True intensity: float = 1.0 params: dict[str, Any] = field(default_factory=dict) class EffectPlugin(ABC): """Abstract base class for effect plugins. Subclasses must define: - name: str - unique identifier for the effect - config: EffectConfig - current configuration And implement: - process(buf, ctx) -> list[str] - configure(config) -> None Effect Behavior with ticker_height=0: - NoiseEffect: Returns buffer unchanged (no ticker to apply noise to) - FadeEffect: Returns buffer unchanged (no ticker to fade) - GlitchEffect: Processes normally (doesn't depend on ticker_height) - FirehoseEffect: Returns buffer unchanged if no items in context Effects should handle missing or zero context values gracefully by returning the input buffer unchanged rather than raising errors. """ name: str config: EffectConfig @abstractmethod def process(self, buf: list[str], ctx: EffectContext) -> list[str]: """Process the buffer with this effect applied. Args: buf: List of lines to process ctx: Effect context with terminal state Returns: Processed buffer (may be same object or new list) """ ... @abstractmethod def configure(self, config: EffectConfig) -> None: """Configure the effect with new settings. Args: config: New configuration to apply """ ... def create_effect_context( terminal_width: int = 80, terminal_height: int = 24, scroll_cam: int = 0, ticker_height: int = 0, mic_excess: float = 0.0, grad_offset: float = 0.0, frame_number: int = 0, has_message: bool = False, items: list | None = None, ) -> EffectContext: """Factory function to create EffectContext with sensible defaults.""" return EffectContext( terminal_width=terminal_width, terminal_height=terminal_height, scroll_cam=scroll_cam, ticker_height=ticker_height, mic_excess=mic_excess, grad_offset=grad_offset, frame_number=frame_number, has_message=has_message, items=items or [], ) @dataclass class PipelineConfig: order: list[str] = field(default_factory=list) effects: dict[str, EffectConfig] = field(default_factory=dict)