- Add EffectPlugin ABC with @abstractmethod decorators for interface enforcement - Add runtime interface checking in discover_plugins() with issubclass() - Add EffectContext factory with sensible defaults - Standardize Display __init__ (remove redundant init in TerminalDisplay) - Document effect behavior when ticker_height=0 - Evaluate legacy effects: document coexistence, no deprecation needed - Research plugin patterns (VST, Python entry points) - Fix pysixel dependency (removed broken dependency) Test coverage improvements: - Add DisplayRegistry tests - Add MultiDisplay tests - Add SixelDisplay tests - Add controller._get_display tests - Add effects controller command handling tests - Add benchmark regression tests (@pytest.mark.benchmark) - Add pytest marker for benchmark tests in pyproject.toml Documentation updates: - Update AGENTS.md with 56% coverage stats and effect plugin docs - Update README.md with Sixel display mode and benchmark commands - Add new modules to architecture section
59 lines
1.8 KiB
Python
59 lines
1.8 KiB
Python
import random
|
|
|
|
from engine.effects.types import EffectConfig, EffectContext, EffectPlugin
|
|
|
|
|
|
class FadeEffect(EffectPlugin):
|
|
name = "fade"
|
|
config = EffectConfig(enabled=True, intensity=1.0)
|
|
|
|
def process(self, buf: list[str], ctx: EffectContext) -> list[str]:
|
|
if not ctx.ticker_height:
|
|
return buf
|
|
result = list(buf)
|
|
intensity = self.config.intensity
|
|
|
|
top_zone = max(1, int(ctx.ticker_height * 0.25))
|
|
bot_zone = max(1, int(ctx.ticker_height * 0.10))
|
|
|
|
for r in range(len(result)):
|
|
if r >= ctx.ticker_height:
|
|
continue
|
|
top_f = min(1.0, r / top_zone) if top_zone > 0 else 1.0
|
|
bot_f = (
|
|
min(1.0, (ctx.ticker_height - 1 - r) / bot_zone)
|
|
if bot_zone > 0
|
|
else 1.0
|
|
)
|
|
row_fade = min(top_f, bot_f) * intensity
|
|
|
|
if row_fade < 1.0 and result[r].strip():
|
|
result[r] = self._fade_line(result[r], row_fade)
|
|
|
|
return result
|
|
|
|
def _fade_line(self, s: str, fade: float) -> str:
|
|
if fade >= 1.0:
|
|
return s
|
|
if fade <= 0.0:
|
|
return ""
|
|
result = []
|
|
i = 0
|
|
while i < len(s):
|
|
if s[i] == "\033" and i + 1 < len(s) and s[i + 1] == "[":
|
|
j = i + 2
|
|
while j < len(s) and not s[j].isalpha():
|
|
j += 1
|
|
result.append(s[i : j + 1])
|
|
i = j + 1
|
|
elif s[i] == " ":
|
|
result.append(" ")
|
|
i += 1
|
|
else:
|
|
result.append(s[i] if random.random() < fade else " ")
|
|
i += 1
|
|
return "".join(result)
|
|
|
|
def configure(self, config: EffectConfig) -> None:
|
|
self.config = config
|