feat(reactor): persistent ambient particle field + external event feed#9
feat(reactor): persistent ambient particle field + external event feed#9k8rito01 wants to merge 3 commits into
Conversation
Two opt-in enhancements to the Event Reactor (bar anim mode 7): 1. Ambient particle field. Instead of being empty at rest, the reactor keeps a single pool of particles drifting through the gaps. On an event, these SAME particles reorganise to spell the text (reusing the swarm condensation) and then disperse back to drifting. Non-recruited particles dim while a message is held. Gated by `ambientField7` (default on here). When on, the paint loop no longer sleeps: ~15 fps drift in the calm, ~4-5% CPU at rest (set `ambientField7: false` to restore the pure zero-idle reactor). 2. External event feed. A tool can push a message to the reactor by writing one line "<epoch_ms>\tLEFT\tRIGHT" to ~/.cache/qs-reactor-event. First load only baselines, so old content is not replayed on start. Enables things like a "CLAUDE CODE / FABLE 5" swarm when an AI CLI is invoked, driven by that tool's own hook/wrapper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Long messages (e.g. a track title) have more glyph dots than the pool, and subsampling them dropped dots so letters became unreadable. Render every glyph dot instead: the first N9 start from the visible pool particles, the extra ones emerge to complete the word. The name/message always reads in full. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two fixes to the mode-7 reactor, both surfaced by web players (YouTube /
YouTube Music) that publish MPRIS metadata in stages as the next track
buffers.
Track-change restart
These players emit a transient generic/channel title first, then the
real one ~0.5s later. onPsTrackChanged fired on each stage, so the
swarm formed once, then a second event restarted it mid-flight — a
visible reset on every song change. Now debounced (800ms) with a
last-announced guard: one swarm per track, always the settled title.
Stall-proof, more organic ambient drift
- Drift is driven by a clamped clock (fieldT) instead of Date.now():
if the paint loop stalls a moment (metadata burst, media relayout)
the field micro-pauses instead of JUMPING on the next frame.
- driftPos is now anchored in absolute bar coords (bf*width) rather
than gap-relative, so a relayout no longer remaps the pool.
- Two harmonics per axis + size "breathing" for a more alive field.
- Idle drift 15fps -> ~33fps for smoother motion (reaction unchanged
at 60fps; still auto-sleeps when nothing is happening).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed one more commit ( Track-change restart (the main one): web players (YouTube / YT Music) publish MPRIS metadata in stages while the next track buffers — a transient channel/generic title, then the real title ~0.5s later. Stall-proof / more organic drift: drift now runs off a per-paint clamped clock ( qmllint clean. Ported cleanly on top of the subsample fix. |
Two opt-in enhancements to the Event Reactor (bar anim mode 7), in
modules/ParticleStream.qml.1. Persistent ambient particle field
Today mode 7 is empty at rest and only spawns particles on an event. This keeps one pool of particles always visible, drifting through the bar gaps. When an event fires, those same particles reorganise to condense the text (reusing the existing swarm interpolation), then disperse back to drifting — it reads as the message forming out of the ambient dust rather than a separate swarm flying in. Non-recruited particles dim while a message is held so the word stands out.
property bool ambientField7(defaults on in this PR).tick7 = 64). Measured ~4–5% CPU at rest, ~7% during a message.ambientField7: falserestores the original zero-idle reactor (empty at rest, ~0% CPU) — the text swarm keeps its original fly-in in that path.2. External event feed
A tiny hook so any tool can push a message to the reactor: write one line
to
~/.cache/qs-reactor-event(truncate to one line). AFileViewwatches it and callspushText(LEFT, RIGHT). First load only baselines, so old content isn't replayed on start.Example — announce an AI CLI invocation as a swarm (
CLAUDE CODE / FABLE 5) from a Claude CodeSessionStarthook:The tool-side glue (hooks/wrappers for claude/codex/opencode) lives in user config, not here.
Notes
main.qmllintclean. I couldn't run the upstream variant standalone, so a quick smoke-test on your side is worth it.🤖 Generated with Claude Code