diff --git a/README.md b/README.md index cd549ba..0dcbd3f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,34 @@ > *Digital consciousness stream. Matrix aesthetic · THX-1138 hue.* -A full-screen terminal news ticker that renders live global headlines in large OTF-font block characters with a white-hot → deep green gradient. Headlines auto-translate into the native script of their subject region. Ambient mic input warps the glitch rate in real time. A `--poetry` mode replaces the feed with public-domain literary passages. Live messages can be pushed to the display over [ntfy.sh](https://ntfy.sh). +A full-screen terminal news ticker that renders live global headlines in large OTF-font block characters with selectable color gradients (Verdant Green, Molten Orange, or Violet Purple). Headlines auto-translate into the native script of their subject region. Ambient mic input warps the glitch rate in real time. A `--poetry` mode replaces the feed with public-domain literary passages. Live messages can be pushed to the display over [ntfy.sh](https://ntfy.sh). **Figment mode** overlays flickery, theme-colored SVG glyphs on the running stream at timed intervals — controllable from any input source via an extensible trigger protocol. + +--- + +## Contents + +- [Using](#using) + - [Run](#run) + - [Config](#config) + - [Display Modes](#display-modes) + - [Feeds](#feeds) + - [Fonts](#fonts) + - [ntfy.sh](#ntfysh) + - [Figment Mode](#figment-mode) + - [Command & Control](#command--control-cc) +- [Internals](#internals) + - [How it works](#how-it-works) + - [Architecture](#architecture) +- [Development](#development) + - [Setup](#setup) + - [Tasks](#tasks) + - [Testing](#testing) + - [Linting](#linting) +- [Roadmap](#roadmap) + - [Performance](#performance) + - [Graphics](#graphics) + - [Cyberpunk Vibes](#cyberpunk-vibes) + - [Extensibility](#extensibility) --- @@ -15,8 +42,11 @@ python3 mainline.py # news stream python3 mainline.py --poetry # literary consciousness mode python3 mainline.py -p # same python3 mainline.py --firehose # dense rapid-fire headline mode +python3 mainline.py --figment # enable periodic SVG glyph overlays +python3 mainline.py --figment-interval 30 # figment every 30 seconds (default: 60) python3 mainline.py --display websocket # web browser display only python3 mainline.py --display both # terminal + web browser +python3 mainline.py --refresh # force re-fetch (bypass cache) python3 mainline.py --no-font-picker # skip interactive font picker python3 mainline.py --font-file path.otf # use a specific font file python3 mainline.py --font-dir ~/fonts # scan a different font folder @@ -68,6 +98,7 @@ All constants live in `engine/config.py`: | `FRAME_DT` | `0.05` | Frame interval in seconds (20 FPS) | | `FIREHOSE_H` | `12` | Firehose zone height (terminal rows) | | `GRAD_SPEED` | `0.08` | Gradient sweep speed | +| `FIGMENT_INTERVAL` | `60` | Seconds between figment appearances (set by `--figment-interval`) | ### Display Modes @@ -104,20 +135,56 @@ To push a message: curl -d "Body text" -H "Title: Alert title" https://ntfy.sh/your_topic ``` +Update `NTFY_TOPIC` in `engine/config.py` to point at your own topic. + +### Figment Mode + +Figment mode periodically overlays a full-screen SVG glyph on the running ticker — flickering through a reveal → hold (strobe) → dissolve cycle, colored with a randomly selected theme gradient. + +**Enable it** with the `--figment` flag: + +```bash +uv run mainline.py --figment # glyph every 60 seconds (default) +uv run mainline.py --figment --figment-interval 30 # every 30 seconds +``` + +**Figment assets** live in `figments/` — drop any `.svg` file there and it will be picked up automatically. The bundled set contains Mayan and Aztec glyphs. Figments are selected randomly, avoiding immediate repeats, and rasterized into half-block terminal art at display time. + +**Triggering manually** — any object with a `poll() -> FigmentCommand | None` method satisfies the `FigmentTrigger` protocol and can be passed to the plugin: + +```python +from engine.figment_trigger import FigmentAction, FigmentCommand + +class MyTrigger: + def poll(self): + if some_condition: + return FigmentCommand(action=FigmentAction.TRIGGER) + return None +``` + +Built-in commands: `TRIGGER`, `SET_INTENSITY`, `SET_INTERVAL`, `SET_COLOR`, `STOP`. + +**System dependency:** Figment mode requires the Cairo C library (`brew install cairo` on macOS) in addition to the `figment` extras group: + +```bash +uv sync --extra figment # adds cairosvg +``` + --- ## Internals ### How it works -- On launch, the font picker scans `fonts/` and presents a live-rendered TUI for face selection -- Feeds are fetched and filtered on startup; results are cached for fast restarts -- Headlines are rasterized via Pillow with 4× SSAA into half-block characters -- The ticker uses a sweeping white-hot → deep green gradient -- Subject-region detection triggers Google Translate and font swap for non-Latin scripts -- The mic stream runs in a background thread, feeding RMS dB into glitch probability -- The viewport scrolls through pre-rendered blocks with fade zones -- An ntfy.sh SSE stream runs in a background thread for messages and C&C commands +- On launch, the font picker scans `fonts/` and presents a live-rendered TUI for face selection; `--no-font-picker` skips directly to stream +- Feeds are fetched and filtered on startup (sports and vapid content stripped); results are cached to `.mainline_cache_news.json` / `.mainline_cache_poetry.json` for fast restarts +- Headlines are rasterized via Pillow with 4× SSAA into half-block characters (`▀▄█ `) at the configured font size +- The ticker uses a sweeping white-hot → deep green gradient; ntfy messages use a complementary white-hot → magenta/maroon gradient to distinguish them visually +- Subject-region detection runs a regex pass on each headline; matches trigger a Google Translate call and font swap to the appropriate script (CJK, Arabic, Devanagari, etc.) using macOS system fonts +- The mic stream runs in a background thread, feeding RMS dB into the glitch probability calculation each frame +- The viewport scrolls through a virtual canvas of pre-rendered blocks; fade zones at top and bottom dissolve characters probabilistically +- An ntfy.sh SSE stream runs in a background thread for messages and C&C commands; incoming messages interrupt the scroll and render full-screen until dismissed or expired +- Figment mode rasterizes SVGs via cairosvg → PIL → greyscale → half-block encode, then overlays them with ANSI cursor-positioning commands between the effect chain and the ntfy message layer ### Architecture @@ -138,32 +205,40 @@ engine/ controller.py handles /effects commands performance.py performance monitoring legacy.py legacy functional effects - effects_plugins/ effect plugin implementations - noise.py noise effect - fade.py fade effect - glitch.py glitch effect - firehose.py firehose effect - fetch.py RSS/Gutenberg fetching + cache + fetch.py RSS/Gutenberg fetching + cache load/save ntfy.py NtfyPoller — standalone, zero internal deps mic.py MicMonitor — standalone, graceful fallback scroll.py stream() frame loop + message rendering - viewport.py terminal dimension tracking + viewport.py terminal dimension tracking (tw/th) frame.py scroll step calculation, timing - layers.py ticker zone, firehose, message overlay - eventbus.py thread-safe event publishing + layers.py ticker zone, firehose, message + figment overlay rendering + figment_render.py SVG → cairosvg → PIL → half-block rasterizer with cache + figment_trigger.py FigmentTrigger protocol, FigmentAction enum, FigmentCommand + eventbus.py thread-safe event publishing for decoupled communication events.py event types and definitions - controller.py coordinates ntfy/mic monitoring - emitters.py background emitters - types.py type definitions + controller.py coordinates ntfy/mic monitoring and event publishing + emitters.py background emitters for ntfy and mic + types.py type definitions and dataclasses + themes.py THEME_REGISTRY — gradient color definitions display/ Display backend system __init__.py DisplayRegistry, get_monitor backends/ terminal.py ANSI terminal display websocket.py WebSocket server for browser clients - sixel.py Sixel graphics (pure Python) - null.py headless display for testing - multi.py forwards to multiple displays + sixel.py Sixel graphics (pure Python) + null.py headless display for testing + multi.py forwards to multiple displays benchmark.py performance benchmarking tool + +effects_plugins/ + __init__.py plugin discovery (ABC issubclass scan) + noise.py NoiseEffect — random character noise + glitch.py GlitchEffect — horizontal glitch bars + fade.py FadeEffect — edge fade zones + firehose.py FirehoseEffect — dense bottom ticker strip + figment.py FigmentEffect — periodic SVG glyph overlay (state machine) + +figments/ SVG assets for figment mode ``` --- @@ -175,11 +250,15 @@ engine/ Requires Python 3.10+ and [uv](https://docs.astral.sh/uv/). ```bash -uv sync # minimal (no mic) -uv sync --all-extras # with mic support +uv sync # minimal (no mic, no figment) +uv sync --extra mic # with mic support (sounddevice + numpy) +uv sync --extra figment # with figment mode (cairosvg + system Cairo) +uv sync --all-extras # all optional features uv sync --all-extras --group dev # full dev environment ``` +Figment mode also requires the Cairo C library: `brew install cairo` (macOS). + ### Tasks With [mise](https://mise.jdx.dev/): @@ -209,6 +288,8 @@ mise run topics-init # initialize ntfy topics ### Testing +Tests live in `tests/` and cover `config`, `filter`, `mic`, `ntfy`, `sources`, `terminal`, and the full figment pipeline (`figment_render`, `figment_trigger`, `figment`, `figment_overlay`). Figment tests are automatically skipped if Cairo is not installed. + ```bash uv run pytest uv run pytest --cov=engine --cov-report=term-missing @@ -252,12 +333,19 @@ Pre-commit hooks run lint automatically via `hk`. - Parallax secondary column ### Cyberpunk Vibes -- Keyword watch list with strobe effects -- Breaking interrupt with synthesized audio -- Live data overlay (BTC, ISS position) -- Theme switcher (amber, ice, red) -- Persona modes (surveillance, oracle, underground) +- **Figment intensity wiring** — `config.intensity` currently stored but not yet applied to reveal/dissolve speed or strobe frequency +- **ntfy figment trigger** — built-in `NtfyFigmentTrigger` that listens on a dedicated topic to fire figments on demand +- **Keyword watch list** — highlight or strobe any headline matching tracked terms (names, topics, tickers) +- **Breaking interrupt** — full-screen flash + synthesized blip when a high-priority keyword hits +- **Live data overlay** — secondary ticker strip at screen edge: BTC price, ISS position, geomagnetic index +- **Theme switcher** — `--amber` (phosphor), `--ice` (electric cyan), `--red` (alert state) palette modes via CLI flag +- **Persona modes** — `--surveillance`, `--oracle`, `--underground` as feed presets with matching color themes and boot copy +- **Synthesized audio** — short static bursts tied to glitch events, independent of mic input + +### Extensibility +- **serve.py** — HTTP server that imports `engine.render` and `engine.fetch` directly to stream 1-bit bitmaps to an ESP32 display +- **Rust port** — `ntfy.py` and `render.py` are the natural first targets; clear module boundaries make incremental porting viable --- -*Python 3.10+. Primary display font is user-selectable via bundled `fonts/` picker.* \ No newline at end of file +*Python 3.10+. Primary display font is user-selectable via bundled `fonts/` picker.*