docs: update README for figment mode
- Add figment mode to intro paragraph - New Figment Mode section: enabling, assets, trigger protocol, deps - Architecture table updated with engine/figment_render.py, figment_trigger.py, and effects_plugins/ directory breakdown - Dev setup: separate uv sync --extras entries (mic vs figment) - Testing section: mentions figment test coverage and Cairo skip - Roadmap: adds figment follow-up items (CLI flag, intensity wiring, ntfy trigger) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
150
README.md
150
README.md
@@ -2,7 +2,34 @@
|
|||||||
|
|
||||||
> *Digital consciousness stream. Matrix aesthetic · THX-1138 hue.*
|
> *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 --poetry # literary consciousness mode
|
||||||
python3 mainline.py -p # same
|
python3 mainline.py -p # same
|
||||||
python3 mainline.py --firehose # dense rapid-fire headline mode
|
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 websocket # web browser display only
|
||||||
python3 mainline.py --display both # terminal + web browser
|
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 --no-font-picker # skip interactive font picker
|
||||||
python3 mainline.py --font-file path.otf # use a specific font file
|
python3 mainline.py --font-file path.otf # use a specific font file
|
||||||
python3 mainline.py --font-dir ~/fonts # scan a different font folder
|
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) |
|
| `FRAME_DT` | `0.05` | Frame interval in seconds (20 FPS) |
|
||||||
| `FIREHOSE_H` | `12` | Firehose zone height (terminal rows) |
|
| `FIREHOSE_H` | `12` | Firehose zone height (terminal rows) |
|
||||||
| `GRAD_SPEED` | `0.08` | Gradient sweep speed |
|
| `GRAD_SPEED` | `0.08` | Gradient sweep speed |
|
||||||
|
| `FIGMENT_INTERVAL` | `60` | Seconds between figment appearances (set by `--figment-interval`) |
|
||||||
|
|
||||||
### Display Modes
|
### Display Modes
|
||||||
|
|
||||||
@@ -104,20 +135,56 @@ To push a message:
|
|||||||
curl -d "Body text" -H "Title: Alert title" https://ntfy.sh/your_topic
|
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
|
## Internals
|
||||||
|
|
||||||
### How it works
|
### How it works
|
||||||
|
|
||||||
- On launch, the font picker scans `fonts/` and presents a live-rendered TUI for face selection
|
- 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; results are cached for fast restarts
|
- 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
|
- 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
|
- 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 triggers Google Translate and font swap for non-Latin scripts
|
- 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 glitch probability
|
- The mic stream runs in a background thread, feeding RMS dB into the glitch probability calculation each frame
|
||||||
- The viewport scrolls through pre-rendered blocks with fade zones
|
- 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
|
- 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
|
### Architecture
|
||||||
|
|
||||||
@@ -138,32 +205,40 @@ engine/
|
|||||||
controller.py handles /effects commands
|
controller.py handles /effects commands
|
||||||
performance.py performance monitoring
|
performance.py performance monitoring
|
||||||
legacy.py legacy functional effects
|
legacy.py legacy functional effects
|
||||||
effects_plugins/ effect plugin implementations
|
fetch.py RSS/Gutenberg fetching + cache load/save
|
||||||
noise.py noise effect
|
|
||||||
fade.py fade effect
|
|
||||||
glitch.py glitch effect
|
|
||||||
firehose.py firehose effect
|
|
||||||
fetch.py RSS/Gutenberg fetching + cache
|
|
||||||
ntfy.py NtfyPoller — standalone, zero internal deps
|
ntfy.py NtfyPoller — standalone, zero internal deps
|
||||||
mic.py MicMonitor — standalone, graceful fallback
|
mic.py MicMonitor — standalone, graceful fallback
|
||||||
scroll.py stream() frame loop + message rendering
|
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
|
frame.py scroll step calculation, timing
|
||||||
layers.py ticker zone, firehose, message overlay
|
layers.py ticker zone, firehose, message + figment overlay rendering
|
||||||
eventbus.py thread-safe event publishing
|
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
|
events.py event types and definitions
|
||||||
controller.py coordinates ntfy/mic monitoring
|
controller.py coordinates ntfy/mic monitoring and event publishing
|
||||||
emitters.py background emitters
|
emitters.py background emitters for ntfy and mic
|
||||||
types.py type definitions
|
types.py type definitions and dataclasses
|
||||||
|
themes.py THEME_REGISTRY — gradient color definitions
|
||||||
display/ Display backend system
|
display/ Display backend system
|
||||||
__init__.py DisplayRegistry, get_monitor
|
__init__.py DisplayRegistry, get_monitor
|
||||||
backends/
|
backends/
|
||||||
terminal.py ANSI terminal display
|
terminal.py ANSI terminal display
|
||||||
websocket.py WebSocket server for browser clients
|
websocket.py WebSocket server for browser clients
|
||||||
sixel.py Sixel graphics (pure Python)
|
sixel.py Sixel graphics (pure Python)
|
||||||
null.py headless display for testing
|
null.py headless display for testing
|
||||||
multi.py forwards to multiple displays
|
multi.py forwards to multiple displays
|
||||||
benchmark.py performance benchmarking tool
|
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/).
|
Requires Python 3.10+ and [uv](https://docs.astral.sh/uv/).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv sync # minimal (no mic)
|
uv sync # minimal (no mic, no figment)
|
||||||
uv sync --all-extras # with mic support
|
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
|
uv sync --all-extras --group dev # full dev environment
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Figment mode also requires the Cairo C library: `brew install cairo` (macOS).
|
||||||
|
|
||||||
### Tasks
|
### Tasks
|
||||||
|
|
||||||
With [mise](https://mise.jdx.dev/):
|
With [mise](https://mise.jdx.dev/):
|
||||||
@@ -209,6 +288,8 @@ mise run topics-init # initialize ntfy topics
|
|||||||
|
|
||||||
### Testing
|
### 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
|
```bash
|
||||||
uv run pytest
|
uv run pytest
|
||||||
uv run pytest --cov=engine --cov-report=term-missing
|
uv run pytest --cov=engine --cov-report=term-missing
|
||||||
@@ -252,11 +333,18 @@ Pre-commit hooks run lint automatically via `hk`.
|
|||||||
- Parallax secondary column
|
- Parallax secondary column
|
||||||
|
|
||||||
### Cyberpunk Vibes
|
### Cyberpunk Vibes
|
||||||
- Keyword watch list with strobe effects
|
- **Figment intensity wiring** — `config.intensity` currently stored but not yet applied to reveal/dissolve speed or strobe frequency
|
||||||
- Breaking interrupt with synthesized audio
|
- **ntfy figment trigger** — built-in `NtfyFigmentTrigger` that listens on a dedicated topic to fire figments on demand
|
||||||
- Live data overlay (BTC, ISS position)
|
- **Keyword watch list** — highlight or strobe any headline matching tracked terms (names, topics, tickers)
|
||||||
- Theme switcher (amber, ice, red)
|
- **Breaking interrupt** — full-screen flash + synthesized blip when a high-priority keyword hits
|
||||||
- Persona modes (surveillance, oracle, underground)
|
- **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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user