feat: Introduce visual effects module, enhance text rendering with SSAA, and add shifting gradient support.
This commit is contained in:
133
engine/effects.py
Normal file
133
engine/effects.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
Visual effects: noise, glitch, fade, ANSI-aware truncation, firehose, headline pool.
|
||||
Depends on: config, terminal, sources.
|
||||
"""
|
||||
|
||||
import random
|
||||
from datetime import datetime
|
||||
|
||||
from engine import config
|
||||
from engine.terminal import RST, DIM, G_LO, G_DIM, W_GHOST, C_DIM
|
||||
from engine.sources import FEEDS, POETRY_SOURCES
|
||||
|
||||
|
||||
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}"
|
||||
Reference in New Issue
Block a user