Skip to content

fix(background): scroll only the presets that are periodic under it - #414

Merged
LeadcodeDev merged 1 commit into
mainfrom
fix/scroll-only-periodic-backgrounds
Sep 29, 2026
Merged

LeadcodeDev merged 1 commit into
mainfrom
fix/scroll-only-periodic-backgrounds

Conversation

@LeadcodeDev

Copy link
Copy Markdown
Owner

Closes #155.

⚠️ This is a visible rendering change

A scenario that declares direction on gradient_shift, concentric_circles or
halo renders differently after this. The motion being removed is the motion that
dragged the background off the frame, so removing it is the fix — but it is worth
saying out loud rather than letting it arrive inside a batch.

What was wrong

compute_scroll_offset translated every preset before dispatch. Three of the
seven are not periodic under translation, and already animate themselves from
speed:

Preset Its own motion Why translation breaks it
gradient_shift rotation sense (direction means cw/ccw here) the shader is painted over Rect::from_wh(width, height) with no margin, so any offset leaves an uncovered band
concentric_circles offset = (time * speed) % spacing the outer translation is a second animation, and translating a radial pattern moves its centre
halo its zones breathe —

PR #154 bounded the offset to one tile period, turning an unbounded drift into a
bounded periodic jump. It is now zero for those three.

The four that keep it

grid_dots, grid_lines, pixel_grid and heropattern are tiled patterns with
no motion of their own, drawn with a whole period of margin on each side. For them
the outer scroll is the only thing that can move anything.

pixel_grid was the exception: its cell loops started at index 0, so scrolling
right uncovered a band on the left. They start at -1 now — what the other three
already did.

pixel_grid's own motion field (twinkle/sweep) does not translate anything,
so it composes with the scroll rather than doubling it, and its default (none)
leaves it exactly where grid_dots is: a still texture.

Tests

The issue suggested comparing the avg_luma of a trailing band over time. That
turned out to measure the wrong thing — gradient_shift and halo vary that band
legitimately as they animate themselves, so the test failed on a correct
implementation. Two sharper statements replaced it:

Test
direction_is_inert_on_a_preset_that_cannot_be_translated frames with direction: right are byte-identical to frames with no direction at all
direction_still_moves_a_preset_that_has_no_motion_of_its_own and for the other four they must differ
a_scrolled_tile_never_uncovers_the_band_it_is_dragged_away_from over a magenta scene background, the left 60 px hold zero magenta pixels
a_preset_that_is_not_periodic_under_translation_is_never_translated the offset itself, at four instants

heropattern and concentric_circles are deliberately not in the coverage test:
both leave transparent gaps by design, so the scene colour showing through is not
a defect there.

Both halves of the fix bite on revert:

revert the early return    → direction_is_inert_on_a_preset_that_cannot_be_translated FAILED
revert pixel_grid's margin → 19200 magenta pixels of the scene's own background came through

Docs

rules/continuous-presets.md gains the table of which presets accept direction
and why the other three now ignore it.

Gate

cargo fmt --all --check clean · cargo clippy --workspace --all-targets --features rustmotion/studio -D warnings clean · cargo test --workspace 1782 passed.

compute_scroll_offset translated every preset before dispatch. Three of
the seven are not periodic under translation and already animate
themselves from speed:

- gradient_shift uses direction for the rotation sense, and paints its
  shader over the frame rect with no margin, so any translation left an
  uncovered band.
- concentric_circles computes its own offset = (time * speed) % spacing.
  The outer translation was a second animation on top, and translating a
  radial pattern moves its centre.
- halo animates its zones internally.

PR #154 bounded the offset to one tile period, which turned an unbounded
drift into a bounded periodic jump. It is now zero for those three:
declaring a direction on one of them is pixel-inert, verified frame by
frame against the same scenario with no direction at all.

This is a visible rendering change for an existing scenario that
declares one. The motion being removed is the motion that dragged the
background off the frame.

The four that keep it -- grid_dots, grid_lines, pixel_grid, heropattern
-- are tiled patterns with no motion of their own, drawn with a whole
period of margin on each side. pixel_grid was the exception: its cell
loops started at index 0, so scrolling right uncovered a band on the
left. They start at -1 now, which is what the other three already did.

pixel_grid's own `motion` field does not translate anything, so it
composes with the scroll rather than doubling it, and its default
(`none`) leaves it exactly in grid_dots's position: a still texture the
outer scroll is the only thing that can move.
@LeadcodeDev LeadcodeDev added the bug Something isn't working label Sep 29, 2026
@LeadcodeDev LeadcodeDev self-assigned this Sep 29, 2026
@LeadcodeDev
LeadcodeDev merged commit e62999a into main Sep 29, 2026
4 checks passed
@LeadcodeDev
LeadcodeDev deleted the fix/scroll-only-periodic-backgrounds branch September 29, 2026 08:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Background scroll applies to non-periodic presets that already animate themselves

1 participant