Skip to content

Characterful Scrolling (Class 1)

scrollkit.effects.scrolling provides scrolling content that feels alive instead of a constant 1 px/frame crawl. All three are async DisplayContent, run unchanged on the MatrixPortal S3 and the simulator, do no per-frame heap allocation, and pass the strict feasibility gate at 20 fps. Motion is driven by a fixed-point (1/16-px) accumulator and the integer easing tables, and text extent comes from display.measure_text (never len(text) * 6).

Best on scrolling text

KineticMarquee and WaveRider are scrolling presentations (PAIRS_WITH = ("scrolling",)); SplitFlap flips characters in place, so it reads as held static text (PAIRS_WITH = ("static",)). See pairing effects to content.

from scrollkit.effects.scrolling import KineticMarquee, WaveRider, SplitFlap

KineticMarquee

Scrolling text with mass: it eases in, dwells when a pause_chars character is centered, overshoots and springs back at each dwell, then scrolls off. The whole message is one reused Label whose .x moves per frame — the glyph bitmap is built once.

KineticMarquee("SCROLLKIT IN MOTION.", y=12, speed=34,
               pause_chars=".,!?;:", overshoot=True)

WaveRider

Characters ride a precomputed integer sine path as the message scrolls; only the visible window of single-char Labels is realized each frame (y = baseline + wave_table[(x // step + phase) & 255]).

WaveRider("RIDING THE WAVE", y=14, speed=30, amplitude=5, wavelength=16)

SplitFlap

A split-flap board: each cell flips through flip_steps (2–4) deterministic intermediate glyphs (a seeded LCG — no per-frame random allocation) before landing on its real character, staggered left-to-right.

SplitFlap("SPLIT FLAP", y=12, speed=30, flip_steps=3, seed=4)

Hardware budget

Each class exposes a FEASIBILITY dict (hardware_safe, allocates_per_frame, max_pixel_writes_per_frame, modeled_frame_ms). All three are strict-feasible at the ~50 ms (20 fps) bit_depth=4 device budget.

Effect Per-frame work max_pixel_writes_per_frame modeled_frame_ms
KineticMarquee one Label, .x only (built once) 0 (no painter writes) ~6
WaveRider only the visible-window chars; rebuilds as chars cross the edge 0 (no painter writes) ~16
SplitFlap a few cells rebuild while flipping, then stop 0 (no painter writes) ~18

max_pixel_writes_per_frame is 0 because these effects move/restyle Labels rather than painting pixels; their cost is the (bounded) glyph rebuilds the strict gate's median window tolerates.