Fix scanlines going blank instead of regenerating; reduce count; animate sweep

Reduced visible scanlines from 8 to 6 per window (dropping the last two
of the 8 evenly-spaced SCANLINE_PERIOD positions, rather than changing
the period itself, so it keeps dividing evenly into ART_SLOT).

Also animated the scanlines to sweep downward (previously static),
matching SUB/WAVE's own continuously-animated scanlines. A first attempt
built a buffer twice the art slot's height, pre-shifted with a negative
render.Padding so the middle 32 rows lined up with the window at rest,
then swept the full ART_SLOT distance in one pass. That's what produced
"scanlines disappearing at the bottom without regenerating at the top":
empirically, negative Padding didn't behave like the (well-proven
elsewhere in this file) positive case, which reliably grows a widget's
size -- confirmed via an isolated test that it went blank partway
through a static sweep instead of revealing new rows.

Replaced with a repeating sawtooth instead of one big sweep: the buffer
is only one extra SCANLINE_PERIOD taller than the art slot (the same
small, already-proven-safe oversized margin used throughout this file,
e.g. the zoom's own ART_SIZE vs. ART_SLOT), and the animation ramps down
by exactly one period, snaps back to 0, and repeats -- since the pattern
repeats every period anyway, the snap is invisible (a standard infinite-
scrolling-texture trick), and the translate distance never leaves the
confirmed-safe small range.

Verified via an isolated test (translating a plain Column child by small
amounts, mirroring the technique already used for the zoom/jitter) before
building this in, and via the real render: no black border across all
125 frames of a real render, payload well under budget (193KB).
This commit is contained in:
poprhythm committed 2026-10-09 15:48:50 -04:00
1 parent 29b0505dc7
commit ef38fabebb
1 file changed
+53 -9
+53 -9
View File
@@ -522,20 +522,64 @@ def _glitch_jitter(zoom):
jittered = animation.Transformation(child = zoom, duration = total_ticks, keyframes = keyframes) jittered = animation.Transformation(child = zoom, duration = total_ticks, keyframes = keyframes)
return render.Box(width = ART_SLOT, height = ART_SLOT, child = jittered) return render.Box(width = ART_SLOT, height = ART_SLOT, child = jittered)
SCANLINE_PERIOD = 4 # 1px line, 3px gap
SCANLINE_SWEEP_DURATION = 160 # 8s total at the default 50ms/tick
SCANLINE_CYCLES = 8 # how many times the sawtooth below repeats over that
_SCANLINE_CYCLE_TICKS = SCANLINE_SWEEP_DURATION // SCANLINE_CYCLES
def _glitch_scanlines(): def _glitch_scanlines():
# render.Sequence doesn't loop (it just stops painting once its total # Modeled on SUB/WAVE's own scanlines, which sweep continuously via
# frame count is exhausted -- confirmed in pixlet's own source), so # an animated `background-position-y` rather than sitting still.
# the RGB-split jitter above plays once per show_static window and #
# then goes quiet; these persist for the whole window as a constant # An earlier version tried a single large sweep across a buffer built
# texture rather than a one-shot burst, closer to how SUB/WAVE's # twice as tall as the art slot, pre-shifted with a *negative*
# scanlines sweep continuously while its live class is applied. # render.Padding so the pattern's middle 32 rows lined up with the
# window at rest. That didn't work how a positive Padding (which
# reliably *grows* a widget's declared size -- used elsewhere in this
# file, e.g. _glitch_band) does; empirically it went blank partway
# through instead of revealing new rows at the top, which is what
# "scanlines disappearing at the bottom without regenerating at the
# top" looked like.
#
# This avoids all of that: the buffer is only ONE extra
# SCANLINE_PERIOD taller than the art slot (the same small, proven-
# safe oversized margin used everywhere else in this file -- e.g. the
# zoom's own ART_SIZE vs. ART_SLOT), and instead of one big sweep it's
# a repeating sawtooth -- ramp down by exactly one SCANLINE_PERIOD,
# then snap back to 0 and repeat. Since the pattern repeats every
# SCANLINE_PERIOD anyway, a one-period displacement looks identical to
# the starting position, so the snap is invisible -- a standard
# infinite-scrolling-texture trick -- while the translate distance
# never has to leave the small range already confirmed safe.
tall = ART_SLOT + SCANLINE_PERIOD
lines = [] lines = []
for y in range(ART_SLOT): for y in range(tall):
if y % 4 == 0: # SCANLINE_PERIOD=4 over ART_SLOT=32 gives 8 evenly-spaced lines
# per window; skipping 2 of those 8 positions (dropping the last
# two in each 32-row repeat, rather than changing the period
# itself) keeps the period dividing evenly into things cleanly.
if y % SCANLINE_PERIOD == 0 and (y % ART_SLOT) < ART_SLOT - 2 * SCANLINE_PERIOD:
lines.append(render.Box(width = ART_SLOT, height = 1, color = "#00000080")) lines.append(render.Box(width = ART_SLOT, height = 1, color = "#00000080"))
else: else:
lines.append(render.Box(width = ART_SLOT, height = 1, color = "#00000000")) lines.append(render.Box(width = ART_SLOT, height = 1, color = "#00000000"))
return render.Column(children = lines) pattern = render.Column(children = lines)
keyframes = []
for cycle in range(SCANLINE_CYCLES):
cycle_start = cycle * _SCANLINE_CYCLE_TICKS
ramp_end = cycle_start + _SCANLINE_CYCLE_TICKS - 1 # last tick of the cycle is the snap back to 0
keyframes.append(animation.Keyframe(
percentage = float(cycle_start) / SCANLINE_SWEEP_DURATION,
transforms = [animation.Translate(0, 0)],
))
keyframes.append(animation.Keyframe(
percentage = float(ramp_end) / SCANLINE_SWEEP_DURATION,
transforms = [animation.Translate(0, SCANLINE_PERIOD)],
curve = "linear",
))
sweeping = animation.Transformation(child = pattern, duration = SCANLINE_SWEEP_DURATION, keyframes = keyframes)
return render.Box(width = ART_SLOT, height = ART_SLOT, child = sweeping)
def _glitch_corner_ticks(color): def _glitch_corner_ticks(color):
tick_size = 5 tick_size = 5
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.