feat(effects): add plugin architecture for visual effects

- Extract effects as fully decoupled plugins in engine/effects/
- Add EffectConfig, EffectContext dataclasses and EffectPlugin protocol
- Add EffectRegistry for plugin discovery and management
- Add EffectChain for ordered pipeline execution
- Move built-in effects to effects_plugins/ directory
- Add interactive effects config picker during startup
- Add NTFY command handler for /effects commands
- Add tests for effects system (24 new tests)
- Update AGENTS.md with effects plugin documentation
- Add conventional commits section to AGENTS.md

chore: add coverage.xml to .gitignore
This commit is contained in:
2026-03-15 17:15:46 -07:00
parent 3551cc249f
commit 291e96d11e
17 changed files with 1069 additions and 3 deletions

View File

@@ -11,6 +11,7 @@ from engine import config
from engine.frame import calculate_scroll_step
from engine.layers import (
apply_glitch,
process_effects,
render_firehose,
render_message_overlay,
render_ticker_zone,
@@ -18,6 +19,8 @@ from engine.layers import (
from engine.terminal import CLR
from engine.viewport import th, tw
USE_EFFECT_CHAIN = True
def stream(items, ntfy_poller, mic_monitor):
"""Main render loop with four layers: message, ticker, scroll motion, firehose."""
@@ -42,6 +45,7 @@ def stream(items, ntfy_poller, mic_monitor):
noise_cache = {}
scroll_motion_accum = 0.0
msg_cache = (None, None)
frame_number = 0
while True:
if queued >= config.HEADLINE_LIMIT and not active:
@@ -93,10 +97,24 @@ def stream(items, ntfy_poller, mic_monitor):
buf.extend(ticker_buf)
mic_excess = mic_monitor.excess
buf = apply_glitch(buf, ticker_buf_start, mic_excess, w)
firehose_buf = render_firehose(items, w, fh, h)
buf.extend(firehose_buf)
if USE_EFFECT_CHAIN:
buf = process_effects(
buf,
w,
h,
scroll_cam,
ticker_h,
mic_excess,
grad_offset,
frame_number,
msg is not None,
items,
)
else:
buf = apply_glitch(buf, ticker_buf_start, mic_excess, w)
firehose_buf = render_firehose(items, w, fh, h)
buf.extend(firehose_buf)
if msg_overlay:
buf.extend(msg_overlay)
@@ -106,6 +124,7 @@ def stream(items, ntfy_poller, mic_monitor):
elapsed = time.monotonic() - t0
time.sleep(max(0, config.FRAME_DT - elapsed))
frame_number += 1
sys.stdout.write(CLR)
sys.stdout.flush()