Rebuild scanlines with mathematically even spacing

6 lines (8 candidate period-4 positions minus a 2-position skip) looked
uneven and glitched during the sweep -- root-caused via pixel-sampled
test renders to the skip breaking the pattern's true repeat period
(became the full 32px instead of 4px), so the small-sawtooth sweep's
shift-by-4-and-snap no longer reproduced an identical-looking frame.

Switched to 4 lines, spacing derived directly from the count
(ART_SLOT // SCANLINE_COUNT = 8px), which divides evenly and keeps the
sweep's shift distance equal to the pattern's actual period -- the only
way the loop stays seamless. Also confirmed (separate pixel-sampled
tests) that a single large full-height sweep isn't viable at all:
pixlet's Transformation+Translate never reveals content beyond what's
visible at rest, so a duplicated-buffer big-sweep approach just goes
blank past a point, same failure mode as the earlier negative-Padding
attempt.

Verified visually (frames crop to 4 evenly-spaced, full-height lines
starting at row 0) and payload stays under the 460KB device limit
(425KB typical, 451KB worst-case at strong zoom + random position --
inflated by the still-active TEMP DEV OVERRIDE forcing the glitch on
for the whole track instead of just the start/end window).
This commit is contained in:
poprhythm committed 2026-10-09 16:10:26 -04:00
1 parent c6b44af1e4
commit 90870f4a24
1 file changed
+30 -23
+30 -23
View File
@@ -553,7 +553,26 @@ 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 # Line count and spacing are derived from each other, not picked
# independently: the small-sawtooth sweep below only loops seamlessly
# when sweeping by exactly one full repeat-period of the line pattern,
# which only exists (as a *small* shift, not the full ART_SLOT) when
# SCANLINE_COUNT evenly divides ART_SLOT. 6 lines was tried first (an
# explicit skip-tuple dropping 2 of 8 period-4 positions) and looked
# uneven/inconsistent during the sweep -- traced to exactly this: with 2
# positions skipped, the pattern's true repeat period became the full 32
# (not 4), so shifting by 4 and snapping back no longer reproduced an
# identical-looking frame. 4 divides 32 cleanly (spacing/sweep = 8px);
# the only other clean option near the original density is 8 (spacing
# 4px, the original count). Confirmed separately (pixel-sampled test
# renders) that pixlet's Transformation+Translate can't reveal content
# beyond what's visible at rest -- shifting only moves already-visible
# pixels, it doesn't pull in new rows from an off-screen duplicate -- so
# a single large sweep (shifting by the full ART_SLOT, tried as an
# alternative to this small-sawtooth approach) isn't viable either;
# small periodic shifts are the only mechanism that actually works here.
SCANLINE_COUNT = 4
SCANLINE_SPACING = ART_SLOT // SCANLINE_COUNT
SCANLINE_SWEEP_DURATION = 160 # 8s total at the default 50ms/tick SCANLINE_SWEEP_DURATION = 160 # 8s total at the default 50ms/tick
SCANLINE_CYCLES = 8 # how many times the sawtooth below repeats over that SCANLINE_CYCLES = 8 # how many times the sawtooth below repeats over that
_SCANLINE_CYCLE_TICKS = SCANLINE_SWEEP_DURATION // SCANLINE_CYCLES _SCANLINE_CYCLE_TICKS = SCANLINE_SWEEP_DURATION // SCANLINE_CYCLES
@@ -573,32 +592,20 @@ def _glitch_scanlines():
# top" looked like. # top" looked like.
# #
# This avoids all of that: the buffer is only ONE extra # This avoids all of that: the buffer is only ONE extra
# SCANLINE_PERIOD taller than the art slot (the same small, proven- # SCANLINE_SPACING taller than the art slot (the same small, proven-
# safe oversized margin used everywhere else in this file -- e.g. the # 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 # 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, # a repeating sawtooth -- ramp down by exactly one SCANLINE_SPACING,
# then snap back to 0 and repeat. Since the pattern repeats every # then snap back to 0 and repeat. Since the pattern repeats every
# SCANLINE_PERIOD anyway, a one-period displacement looks identical to # SCANLINE_SPACING (see SCANLINE_COUNT above for why), a one-period
# the starting position, so the snap is invisible -- a standard # displacement looks identical to the starting position, so the snap
# infinite-scrolling-texture trick -- while the translate distance # is invisible -- a standard infinite-scrolling-texture trick -- while
# never has to leave the small range already confirmed safe. # the translate distance never has to leave the small range already
# SCANLINE_PERIOD=4 over ART_SLOT=32 gives 8 evenly-spaced candidate # confirmed safe.
# positions (0, 4, 8, ..., 28); skipping 2 of those 8 (rather than tall = ART_SLOT + SCANLINE_SPACING
# changing the period itself, which the sweep's periodicity depends
# on) to land on 6 visible lines. Skipping two *interspersed*
# positions instead of the last two in the run -- which left an 8px
# gap with nothing in it at the bottom of every cycle -- spreads the
# resulting two, slightly-larger gaps across the full height instead.
# Removing two slots exactly opposite each other (half the 8-slot
# cycle apart) is the most even spacing achievable while keeping every
# line on a multiple of SCANLINE_PERIOD: gaps come out 4,4,8,4,4,8 --
# symmetric, vs. e.g. removing two adjacent-ish slots which leaves an
# uneven 4,8,4,8,4,4 pattern.
SCANLINE_SKIP = (0, 16)
tall = ART_SLOT + SCANLINE_PERIOD
lines = [] lines = []
for y in range(tall): for y in range(tall):
if y % SCANLINE_PERIOD == 0 and (y % ART_SLOT) not in SCANLINE_SKIP: if y % SCANLINE_SPACING == 0:
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"))
@@ -614,7 +621,7 @@ def _glitch_scanlines():
)) ))
keyframes.append(animation.Keyframe( keyframes.append(animation.Keyframe(
percentage = float(ramp_end) / SCANLINE_SWEEP_DURATION, percentage = float(ramp_end) / SCANLINE_SWEEP_DURATION,
transforms = [animation.Translate(0, SCANLINE_PERIOD)], transforms = [animation.Translate(0, SCANLINE_SPACING)],
curve = "linear", curve = "linear",
)) ))
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.