- Fix pre-existing lint errors in engine/ modules using ruff --unsafe-fixes - Add hk.pkl with pre-commit and pre-push hooks using ruff builtin - Configure hooks to use 'uv run' prefix for tool execution - Update mise.toml to include hk and pkl tools - All 73 tests pass
135 lines
4.3 KiB
Python
135 lines
4.3 KiB
Python
"""
|
|
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.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}"
|