Fix jitter invisible in pixlet serve by restoring the _glitch_hold pattern
Found by comparing against an earlier commit (4983e81) where jitter was
last confirmed visible, before this file's jitter architecture was
rewritten twice since (once for a live-zoom desync bug, once for a
Translate black-border bug). The shift mechanism (_glitch_shift,
Padding-based) was identical in both versions -- the actual difference
was how a state gets "held" for multiple ticks in the render.Sequence.
The rewritten version held a state by appending the same widget
reference to the Sequence's children list GLITCH_HOLD_TICKS times in a
row. That produced a correctly-encoded, pixel-diffable-as-different file
via plain `pixlet render`, but was reported as showing no jitter at all
through pixlet serve's live preview -- repeated-identical-children
apparently doesn't advance playback the same way every Sequence consumer
expects.
Restored _glitch_hold(child, duration): a no-op animation.Transformation
(empty keyframe transforms) wrapping each state once, pinning its own
FrameCount to `duration` so Sequence holds on a single child instead of
relying on duplicate entries. This is the original, proven-working
pattern from before the rewrites -- unchanged other than operating on
the now-static _glitch_rest_art instead of the live zoom.
Verified 0 black-corner hits across 194 frames, 185KB worst-case payload
(strong zoom + random position), both comfortably within budget.
This commit is contained in:
1 parent
60feeaec2c
commit
385e20785c
1 file changed
+48
-32
+48
-32
@@ -495,19 +495,46 @@ def _glitch_base_jitter(zoom, shift_x):
|
|||||||
# crop, so it reads as the full image nudging rather than a band.
|
# crop, so it reads as the full image nudging rather than a band.
|
||||||
return render.Box(width = ART_SLOT, height = ART_SLOT, child = _glitch_shift(zoom, shift_x))
|
return render.Box(width = ART_SLOT, height = ART_SLOT, child = _glitch_shift(zoom, shift_x))
|
||||||
|
|
||||||
|
def _glitch_hold(child, duration):
|
||||||
|
# A no-op Transformation purely to pin a Sequence child's FrameCount
|
||||||
|
# to `duration` ticks, so render.Sequence holds on it for that long
|
||||||
|
# before advancing to the next child -- Sequence has no other way to
|
||||||
|
# control how long a given child plays.
|
||||||
|
#
|
||||||
|
# This matters more than it looks: an earlier version of this
|
||||||
|
# function "held" a state by appending the same widget reference to
|
||||||
|
# the children list GLITCH_HOLD_TICKS times in a row instead of
|
||||||
|
# wrapping it once in a duration-N _glitch_hold. That rendered fine
|
||||||
|
# via plain `pixlet render` to a file (confirmed, pixel-diffed, a
|
||||||
|
# clean alternating shift) but was reported as showing no jitter at
|
||||||
|
# all in pixlet serve's live preview -- repeated-identical-children
|
||||||
|
# apparently doesn't advance the same way every consumer of a
|
||||||
|
# Sequence expects. Wrapping each state in its own _glitch_hold is
|
||||||
|
# the original, proven-working pattern this app used before that
|
||||||
|
# rewrite.
|
||||||
|
return animation.Transformation(
|
||||||
|
child = child,
|
||||||
|
duration = duration,
|
||||||
|
width = ART_SLOT,
|
||||||
|
height = ART_SLOT,
|
||||||
|
keyframes = [
|
||||||
|
animation.Keyframe(percentage = 0.0, transforms = []),
|
||||||
|
animation.Keyframe(percentage = 1.0, transforms = []),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def _glitch_jitter(zoom):
|
def _glitch_jitter(zoom):
|
||||||
# Built as a render.Sequence of discrete shifted states, each using
|
# Built as a render.Sequence of discrete shifted states, each using
|
||||||
# _glitch_shift (Padding-based), not animation.Transformation +
|
# _glitch_shift (Padding-based), not animation.Transformation +
|
||||||
# Translate (the previous approach). That seemed reasonable but
|
# Translate (an approach tried in between). Translate seemed
|
||||||
# produced a real, confirmed-on-device black border that no amount of
|
# reasonable but produced a real, confirmed-on-device black border
|
||||||
# scale adjustment on `zoom` could fix -- root-caused via isolated,
|
# that no amount of scale adjustment on `zoom` could fix --
|
||||||
# pixel-sampled test renders to a more fundamental pixlet limitation:
|
# root-caused via isolated, pixel-sampled test renders to a more
|
||||||
# Transformation + Translate can't reveal content beyond what's
|
# fundamental pixlet limitation: Transformation + Translate can't
|
||||||
# visible at rest, it only moves already-visible pixels around.
|
# reveal content beyond what's visible at rest, it only moves
|
||||||
# Padding genuinely grows the declared canvas instead, which is
|
# already-visible pixels around. Padding genuinely grows the declared
|
||||||
# exactly the mechanism _glitch_shift already uses and had already
|
# canvas instead, which is exactly the mechanism _glitch_shift
|
||||||
# validated as safe ("no black border at +-2px, at both rest and peak
|
# already uses.
|
||||||
# zoom") before this ever used Translate.
|
|
||||||
#
|
#
|
||||||
# A much earlier version of this file *did* use render.Sequence for
|
# A much earlier version of this file *did* use render.Sequence for
|
||||||
# the jitter and was abandoned for a real bug -- but that bug was
|
# the jitter and was abandoned for a real bug -- but that bug was
|
||||||
@@ -517,6 +544,8 @@ def _glitch_jitter(zoom):
|
|||||||
# frame-dependent zoom that version wrapped. That no longer applies:
|
# frame-dependent zoom that version wrapped. That no longer applies:
|
||||||
# `zoom` here is _glitch_rest_art's static, non-animated image, so
|
# `zoom` here is _glitch_rest_art's static, non-animated image, so
|
||||||
# there's nothing frame-dependent left for Sequence to desync.
|
# there's nothing frame-dependent left for Sequence to desync.
|
||||||
|
pause = _glitch_hold(render.Box(width = ART_SLOT, height = ART_SLOT, child = zoom), GLITCH_PAUSE_TICKS)
|
||||||
|
|
||||||
children = []
|
children = []
|
||||||
for cycle_index in range(GLITCH_REPEATS):
|
for cycle_index in range(GLITCH_REPEATS):
|
||||||
for _ in range(GLITCH_STATES_PER_CYCLE):
|
for _ in range(GLITCH_STATES_PER_CYCLE):
|
||||||
@@ -528,30 +557,17 @@ def _glitch_jitter(zoom):
|
|||||||
# cancel out, e.g. -2 and 2, which silently zeroed out the
|
# cancel out, e.g. -2 and 2, which silently zeroed out the
|
||||||
# jitter in about half the states when this was tried before).
|
# jitter in about half the states when this was tried before).
|
||||||
#
|
#
|
||||||
# Two attempts to make this more visible were each tried and
|
# A filter.Hue tint was also tried, to make this more visible
|
||||||
# reverted: +-2px reopened the black border (10 corner hits
|
# without a bigger (riskier) shift -- reverted, it tinted
|
||||||
# across 130 frames, vs 0 at +-1px -- _glitch_shift's margin
|
# _glitch_shift's own blank margin along with the real art in
|
||||||
# genuinely isn't big enough for +-2px in every
|
# both orderings tried, either as a visible color strip on
|
||||||
# zoom_position/origin combination). A filter.Hue tint on
|
# whichever edge got padded, or (once the margin was kept
|
||||||
# each state was tried next, in both orderings relative to
|
# genuinely untinted) as a real black border. Plain shift,
|
||||||
# _glitch_shift's Padding -- tinting the already-padded result
|
# no color tint -- confirmed 0 black-corner hits.
|
||||||
# tinted the shift's own blank margin too (a visible teal
|
|
||||||
# strip on whichever edge got padded, reported as "a
|
|
||||||
# gradient" on the art), and tinting first, before padding
|
|
||||||
# (to keep the margin neutral), instead exposed that the
|
|
||||||
# margin's true underlying fill is dark enough to read as a
|
|
||||||
# real black border (34 hits across 200 frames). Both
|
|
||||||
# reverted; the shift stays plain, capped at +-1px, with no
|
|
||||||
# color tint -- confirmed 0 black-corner hits at this
|
|
||||||
# configuration, which matters more here than making the
|
|
||||||
# burst louder.
|
|
||||||
shift = random.number(0, 2) - 1 # random.number requires min >= 0
|
shift = random.number(0, 2) - 1 # random.number requires min >= 0
|
||||||
shifted = render.Box(width = ART_SLOT, height = ART_SLOT, child = _glitch_shift(zoom, shift))
|
shifted = render.Box(width = ART_SLOT, height = ART_SLOT, child = _glitch_shift(zoom, shift))
|
||||||
for _ in range(GLITCH_HOLD_TICKS):
|
children.append(_glitch_hold(shifted, GLITCH_HOLD_TICKS))
|
||||||
children.append(shifted)
|
children.append(pause)
|
||||||
rest = render.Box(width = ART_SLOT, height = ART_SLOT, child = zoom)
|
|
||||||
for _ in range(GLITCH_PAUSE_TICKS):
|
|
||||||
children.append(rest)
|
|
||||||
|
|
||||||
return render.Sequence(children = children)
|
return render.Sequence(children = children)
|
||||||
|
|
||||||
|
|||||||