""" Visual effects: noise, glitch, fade, ANSI-aware truncation, firehose, headline pool. Depends on: config, terminal, sources. These are low-level functional implementations of visual effects. They are used internally by the EffectPlugin system (effects_plugins/*.py) and also directly by layers.py and scroll.py for rendering. The plugin system provides a higher-level OOP interface with configuration support, while these legacy functions provide direct functional access. Both systems coexist - there are no current plans to deprecate the legacy functions. """ import random from datetime import datetime from engine import config from engine.sources import FEEDS, POETRY_SOURCES from engine.terminal import C_DIM, DIM, G_DIM, G_LO, RST, W_GHOST def noise(w): d = random.choice([0.15, 0.25, 0.35, 0.12]) return "".join( f"{random.choice([G_LO, G_DIM, C_DIM, W_GHOST])}" f"{random.choice(config.GLITCH + config.KATA)}{RST}" if random.random() < d else " " for _ in range(w) ) def glitch_bar(w): c = random.choice(["░", "▒", "─", "╌"]) n = random.randint(3, w // 2) o = random.randint(0, w - n) return " " * o + f"{G_LO}{DIM}" + c * n + RST def fade_line(s, fade): """Dissolve a rendered line by probabilistically dropping characters.""" 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 vis_trunc(s, w): """Truncate string to visual width w, skipping ANSI escape codes.""" result = [] vw = 0 i = 0 while i < len(s): if vw >= w: break 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 else: result.append(s[i]) vw += 1 i += 1 return "".join(result) def next_headline(pool, items, seen): """Pull the next unique headline from pool, refilling as needed.""" while True: if not pool: pool.extend(items) random.shuffle(pool) seen.clear() title, src, ts = pool.pop() sig = title.lower().strip() if sig not in seen: seen.add(sig) return title, src, ts def firehose_line(items, w): """Generate one line of rapidly cycling firehose content.""" r = random.random() if r < 0.35: # Raw headline text title, src, ts = random.choice(items) text = title[: w - 1] color = random.choice([G_LO, G_DIM, W_GHOST, C_DIM]) return f"{color}{text}{RST}" elif r < 0.55: # Dense glitch noise d = random.choice([0.45, 0.55, 0.65, 0.75]) return "".join( f"{random.choice([G_LO, G_DIM, C_DIM, W_GHOST])}" f"{random.choice(config.GLITCH + config.KATA)}{RST}" if random.random() < d else " " for _ in range(w) ) elif r < 0.78: # Status / program output sources = FEEDS if config.MODE == "news" else POETRY_SOURCES src = random.choice(list(sources.keys())) msgs = [ f" SIGNAL :: {src} :: {datetime.now().strftime('%H:%M:%S.%f')[:-3]}", f" ░░ FEED ACTIVE :: {src}", f" >> DECODE 0x{random.randint(0x1000, 0xFFFF):04X} :: {src[:24]}", f" ▒▒ ACQUIRE :: {random.choice(['TCP', 'UDP', 'RSS', 'ATOM', 'XML'])} :: {src}", f" {''.join(random.choice(config.KATA) for _ in range(3))} STRM " f"{random.randint(0, 255):02X}:{random.randint(0, 255):02X}", ] text = random.choice(msgs)[: w - 1] color = random.choice([G_LO, G_DIM, W_GHOST]) return f"{color}{text}{RST}" else: # Headline fragment with glitch prefix title, _, _ = random.choice(items) start = random.randint(0, max(0, len(title) - 20)) frag = title[start : start + random.randint(10, 35)] pad = random.randint(0, max(0, w - len(frag) - 8)) gp = "".join(random.choice(config.GLITCH) for _ in range(random.randint(1, 3))) text = (" " * pad + gp + " " + frag)[: w - 1] color = random.choice([G_LO, C_DIM, W_GHOST]) return f"{color}{text}{RST}"