Fix the periodic zoom pop at its root: disable Translate's pixel rounding

Root-caused the "pop" from the Scale+Translate centered zoom (reverted a
few commits back) to pixlet's own source: render/animation/translate.go
rounds a Translate's x/y to the nearest whole pixel every frame by
default (rounding = "round"), while animation/scale.go never rounds
Scale. A continuously-interpolating Scale combined with a rounded
Translate holds still for several frames, then jumps a whole pixel at
once -- a real, confirmed pixlet behavior, not a flaw in this app's
formula.

Transformation accepts a `rounding` parameter; passing "none" disables
the snapping. Confirmed via frame-by-frame pixel-delta analysis: the
per-frame change is now even throughout the animation (consistently
~900 AE out of 1024 possible, every frame) instead of long near-zero
stretches punctuated by sudden full-frame jumps.

Re-enables the centered zoom (Scale + Translate) that was reverted for
jerkiness, now smooth. ZOOM_LEVELS["strong"] stays capped at 1.45 --
that black-border ceiling is unrelated to rounding and still applies.
Verified against the live station: no pop, no black border at any zoom
level, payload ~370KB (up from ~220-320KB, since rounding="none" means
WebP can dedupe fewer near-identical frames) but still well under the
460KB device limit.
This commit is contained in:
poprhythm committed 2026-10-02 22:03:05 -04:00
1 parent 9f63adbfbf
commit 4a2b3caac8
3 files changed
+73 -47

No files matched your search

+24 -14
View File
@@ -46,20 +46,30 @@ app's config when adding it to a device.
attempts produced a real, on-device black border; see the fuller
history below.
`animation.Scale`'s pivot defaults to the top-left corner (confirmed
empirically — an explicit `origin` parameter has no effect at all), so
zooming in crops a bit more off the bottom/right than the top/left. A
centered version (pairing Scale with a computed `animation.Translate`)
was built and visually worked, but combining Scale with Translate turned
out to make pixlet periodically re-rasterize the whole image with a
visible "pop" every ~0.7-1s — confirmed with both WebP and GIF output,
independent of zoom amplitude, so it's a property of pixlet's animation
engine, not this app's math. Scale *alone* has none of that popping.
Given the choice between a subtle, constant crop asymmetry and a
periodic visible pop, this app accepts the asymmetry. (`dev/calibration_test.star`
has a working centered version if anyone wants to revisit this trade-off.)
Verified payload stays well under the 460KB device limit at every zoom
level, measured against the live station.
`animation.Scale`'s pivot defaults to the *center* of the widget per
pixlet's own docs, so Scale alone is already close to centered — just
slightly skewed toward the top-left as the zoom amount increases.
Paired with a computed `animation.Translate` to cancel out that residual
skew. Combining Scale with Translate at all was tried once before and
reverted after it produced a periodic rendering "pop" roughly every
0.7-1s — confirmed with both WebP and GIF output, independent of zoom
amplitude. Root-caused to pixlet's own source
(`render/animation/translate.go`): a Translate's x/y are rounded to the
nearest whole pixel every frame by default, while Scale is never
rounded, so a continuously-changing Scale combined with a rounded
Translate holds still for several frames and then jumps a whole pixel
at once. Passing `rounding = "none"` on the `Transformation` disables
that snapping and fixes it — confirmed via frame-by-frame pixel-delta
analysis (the amount of change is now even from frame to frame, instead
of long still stretches punctuated by sudden jumps). "strong" is capped
at 1.45 (not pushed further): above ~1.46 the centered crop starts
revealing a real black border in the bottom-right corner, verified with
the calibration tool — this is the safe ceiling, not an arbitrary
choice. Verified payload stays well under the 460KB device limit at
every zoom level, measured against the live station (though with
`rounding = "none"`, WebP can no longer dedupe as many near-identical
frames, so payloads run larger than the "pop" version did — ~370KB
vs. ~220-320KB — still comfortably within budget).
**Why a loop, not a one-shot reveal, and why it's short:** a one-way
version looked jumpy in practice — since the app re-renders on
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.