From a95b24a2463463795d21e75147756dfe32f92f91 Mon Sep 17 00:00:00 2001 From: David Gwilliam Date: Wed, 18 Mar 2026 12:19:26 -0700 Subject: [PATCH] feat(effects): add entropy parameter to effect plugins - Add entropy field to EffectConfig (0.0 = calm, 1.0 = chaotic) - Provide compute_entropy() method in EffectContext for dynamic scoring - Update Fade, Firehose, Glitch, Noise plugin defaults with entropy values - Enables finer control: intensity (strength) vs entropy (randomness) This separates deterministic effect strength from probabilistic chaos, allowing more expressive control in UI panel and presets. Fixes #32 --- engine/effects/plugins/fade.py | 2 +- engine/effects/plugins/firehose.py | 2 +- engine/effects/plugins/glitch.py | 2 +- engine/effects/plugins/noise.py | 2 +- engine/effects/types.py | 26 ++++++++++++++++++++++++++ 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/engine/effects/plugins/fade.py b/engine/effects/plugins/fade.py index be3c8d9..189b9f1 100644 --- a/engine/effects/plugins/fade.py +++ b/engine/effects/plugins/fade.py @@ -5,7 +5,7 @@ from engine.effects.types import EffectConfig, EffectContext, EffectPlugin class FadeEffect(EffectPlugin): name = "fade" - config = EffectConfig(enabled=True, intensity=1.0) + config = EffectConfig(enabled=True, intensity=1.0, entropy=0.1) def process(self, buf: list[str], ctx: EffectContext) -> list[str]: if not ctx.ticker_height: diff --git a/engine/effects/plugins/firehose.py b/engine/effects/plugins/firehose.py index a8b8239..0daf287 100644 --- a/engine/effects/plugins/firehose.py +++ b/engine/effects/plugins/firehose.py @@ -9,7 +9,7 @@ from engine.terminal import C_DIM, G_DIM, G_LO, RST, W_GHOST class FirehoseEffect(EffectPlugin): name = "firehose" - config = EffectConfig(enabled=True, intensity=1.0) + config = EffectConfig(enabled=True, intensity=1.0, entropy=0.9) def process(self, buf: list[str], ctx: EffectContext) -> list[str]: firehose_h = config.FIREHOSE_H if config.FIREHOSE else 0 diff --git a/engine/effects/plugins/glitch.py b/engine/effects/plugins/glitch.py index c4170e4..40eada2 100644 --- a/engine/effects/plugins/glitch.py +++ b/engine/effects/plugins/glitch.py @@ -6,7 +6,7 @@ from engine.terminal import DIM, G_LO, RST class GlitchEffect(EffectPlugin): name = "glitch" - config = EffectConfig(enabled=True, intensity=1.0) + config = EffectConfig(enabled=True, intensity=1.0, entropy=0.8) def process(self, buf: list[str], ctx: EffectContext) -> list[str]: if not buf: diff --git a/engine/effects/plugins/noise.py b/engine/effects/plugins/noise.py index ad28d8a..5892608 100644 --- a/engine/effects/plugins/noise.py +++ b/engine/effects/plugins/noise.py @@ -7,7 +7,7 @@ from engine.terminal import C_DIM, G_DIM, G_LO, RST, W_GHOST class NoiseEffect(EffectPlugin): name = "noise" - config = EffectConfig(enabled=True, intensity=0.15) + config = EffectConfig(enabled=True, intensity=0.15, entropy=0.4) def process(self, buf: list[str], ctx: EffectContext) -> list[str]: if not ctx.ticker_height: diff --git a/engine/effects/types.py b/engine/effects/types.py index 4486a5f..3f0c027 100644 --- a/engine/effects/types.py +++ b/engine/effects/types.py @@ -44,6 +44,11 @@ class PartialUpdate: @dataclass class EffectContext: + """Context passed to effect plugins during processing. + + Contains terminal dimensions, camera state, frame info, and real-time sensor values. + """ + terminal_width: int terminal_height: int scroll_cam: int @@ -56,6 +61,26 @@ class EffectContext: items: list = field(default_factory=list) _state: dict[str, Any] = field(default_factory=dict, repr=False) + def compute_entropy(self, effect_name: str, data: Any) -> float: + """Compute entropy score for an effect based on its output. + + Args: + effect_name: Name of the effect + data: Processed buffer or effect-specific data + + Returns: + Entropy score 0.0-1.0 representing visual chaos + """ + # Default implementation: use effect name as seed for deterministic randomness + # Better implementations can analyze actual buffer content + import hashlib + + data_str = str(data)[:100] if data else "" + hash_val = hashlib.md5(f"{effect_name}:{data_str}".encode()).hexdigest() + # Convert hash to float 0.0-1.0 + entropy = int(hash_val[:8], 16) / 0xFFFFFFFF + return min(max(entropy, 0.0), 1.0) + def get_sensor_value(self, sensor_name: str) -> float | None: """Get a sensor value from context state. @@ -80,6 +105,7 @@ class EffectContext: class EffectConfig: enabled: bool = True intensity: float = 1.0 + entropy: float = 0.0 # Visual chaos metric (0.0 = calm, 1.0 = chaotic) params: dict[str, Any] = field(default_factory=dict)