docs(offscreen): redraw on real updates with a 10s floor; sync by operation replay

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-24 09:01:06 -04:00
co-authored by Claude Opus 5.5
parent 1af5d5fe45
commit 430e2312f8
+42 -13
View File
@@ -176,9 +176,16 @@ the segment keeps its snapshot until it has scrolled off.
`clock-simple` and `ledmatrix-music` are named on every 4-second tick. A `clock-simple` and `ledmatrix-music` are named on every 4-second tick. A
redraw whose pixels hash the same as the segment's is discarded without a redraw whose pixels hash the same as the segment's is discarded without a
swap. swap.
- **Rate limit.** A plugin is redrawn at most once per - **Redraw on real updates only.** Vegas makes no API calls. Each plugin
`vegas_scroll.refresh_min_interval` (proposed 5 s), and never while its fetches on its own schedule, and a redraw is triggered only when the
previous redraw is still running. plugin's `update()` has run since its segment was drawn. On hdpi live
football, baseball and hockey poll every 30 s (live odds every 60 s,
everything else hourly), so a live sports card is redrawn once per poll.
- **Floor.** A plugin is redrawn at most once per
`vegas_scroll.refresh_min_interval` (proposed 10 s), and never while its
previous redraw is still running. The floor never holds back a sports card
polling every 30 s. It exists for chatty plugins: `clock-simple` updates
every second and `ledmatrix-music` polls every 2 s.
- **One worker.** Redraws go through the same background worker as prefetch, - **One worker.** Redraws go through the same background worker as prefetch,
one plugin at a time at `nice 10`, under the plugin's lock. one plugin at a time at `nice 10`, under the plugin's lock.
@@ -210,12 +217,33 @@ half-changed.
### Multi-display sync ### Multi-display sync
The follower renders from its own copy of the strip, which the leader sends The follower renders from its own copy of the strip, offset from the leader's
whole at a new cycle (`send_scroll_image`), plus the scroll position every scroll position. Today the leader sends that copy whole, and only in
frame. Replacements and patches would need a new message `start_new_cycle()` (`send_scroll_image`), plus the scroll position every
(`send_segment_patch(offset, image)`) so the follower shows the same pixels. frame. Continuous scroll, the default, extends and trims the strip without
Until that exists, fresh-content updates are disabled while sync is active, starting a new cycle, and nothing sends those changes. From reading the code,
and the follower keeps today's behaviour. the follower therefore probably falls out of step after the first extension
already, before any of this design. That is untested; it needs a two-Pi rig.
With a segment strip, keeping the follower identical becomes **replaying the
leader's operations**:
- Every strip mutation (append, trim, replace, patch) is one operation in
strip coordinates. The leader applies it and sends the same operation to the
follower over the existing TCP channel. Segments are small: a card is ~29 KB
raw and compresses well.
- Operations on off-screen segments apply on arrival. A patch to a segment
that is on either panel carries an *apply at scroll position X* stamp a
couple of hundred milliseconds ahead. Both sides apply it when their scroll
position passes X, so both panels change on the same frame, within the
existing position-sync jitter.
- Each operation carries a sequence number. A follower that sees a gap (a
reconnect, a dropped message) asks for a full snapshot, which is today's
`send_scroll_image` path.
That also fixes the probable continuous-mode gap as a side effect, since
appends and trims become operations too. Until it is in place, fresh-content
updates are disabled while sync is active.
## Risks, and what was checked ## Risks, and what was checked
@@ -303,7 +331,8 @@ deferred path when `false`, and `display.vegas_scroll.live_refresh` (default
1. Keep the kill switch, or ship without one? 1. Keep the kill switch, or ship without one?
2. Plugin lock timeout: skip the plugin and keep its cached segment (proposed), 2. Plugin lock timeout: skip the plugin and keep its cached segment (proposed),
or wait longer? or wait longer?
3. `refresh_min_interval`: 5 s proposed. Sports plugins that poll every 15–30 s 3. `refresh_min_interval`: 10 s proposed. It only limits chatty plugins;
would mostly be redrawn once per fetch anyway. live sports are redrawn once per 30 s poll regardless.
4. Multi-display sync: ship fresh content with it disabled under sync 4. Multi-display sync: is there a two-Pi rig to test on? Operation replay is
(proposed), or build the follower patch message first? proposed as part of the segment strip (step 2), with fresh content
disabled under sync until it has been verified on real hardware.