fix(display): end the scroll state at scroller-to-static handovers (#716)

A static plugin screen that follows a scroller no longer starts with the ticker's lagging rows on scan-compensated panels, and the 1 Hz loop's second frame is no longer recorded as a ~1 s mid-scroll freeze / Render stall. The display controller calls DisplayManager.end_scroll_for_static_screen() before a static screen's first display() (clears the scan history; _scan_segments passes its frames through in one swap) and set_scrolling_state(False) after it; the scroller's hold stays until then, so late-frame counts are unchanged. A screen's first frame is tagged 'handover': gaps of 250 ms or more before it go to the additive handover_freezes (frame_soak prints 'Handover gaps'), not freezes. The display thread is named display-<plugin id>. The WiFi notice and the schedule-off blank are not covered yet (docs list them as a follow-up).

ledpi A B B A soak (20 min each, --preview): main 0.118% / 0.113% late with 6 / 3 freezes; with this and #717 0.107% / 0.104% late, 0 freezes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-10-01 20:13:57 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 41db488c73
commit 34be83d595
12 changed files with 1041 additions and 38 deletions
+31
View File
@@ -122,6 +122,37 @@ policies are unchanged.
until the next minute, because the once-a-minute schedule check had
already run that minute and the session had overridden its answer.
### Scroller-to-static handovers
- A static plugin screen that follows a scroller no longer starts with the
scroller's leftovers. Nothing ended the scroll state at a handover; it
expired 2 s after the last scroll frame. So on a panel with scan-order
compensation the static screen's first frame went out with the lagging
rows (the bottom half on a 96x48 panel) taken from the ticker's last
frame: for the whole second it stays up after a scroll at one frame per
refresh, and for its first refresh after a slower, held one. The display
controller now calls the new
`DisplayManager.end_scroll_for_static_screen()` just before such a
screen's first `display()`, so the frames that call draws go out as
drawn, in one swap each, and `set_scrolling_state(False)` once it
returns. The scroll state and its frame hold stay until then, so the
handover is still timed, against the scroller's own pacing: late-frame
counts are unchanged.
- The phantom ~1 s freeze when a static plugin screen follows a scroller is
no longer recorded: the 1 Hz loop's second frame was timed as a frame of the old
scroll, in the soak's freezes and as a `Render stall` in the log. On ledpi
that was 17 of 31 `Render stall over` lines (2026-09-15 to 10-01).
- A screen's first frame is tagged `handover` in the frame stats, every
turn's, also when the rotation comes back to the same mode. A gap of
250 ms or more before it is counted in the new `handover_freezes`
(additive; the schema version is unchanged), not in `freezes` /
`freeze_by`, and `frame_soak.py` prints it as "Handover gaps": a
scroller rebuilding its content at the start of a turn shows up there.
**Freeze counts from soaks before and after this change are not
comparable.** A stall dump taken while that first `display()` is still
drawing says `in a handover gap` instead of `mid-scroll`, and the call
runs on a thread named `display-<plugin id>`.
## 3.8.0
Live Vegas elements: plugin content that keeps changing while it scrolls