refactor(scene): paint decorative fullscreen through the box tree - #249
Closed
LeadcodeDev wants to merge 2 commits into
Closed
LeadcodeDev wants to merge 2 commits into
LeadcodeDev wants to merge 2 commits into
Conversation
53 tasks
LeadcodeDev
force-pushed
the
fix/heropattern-scroll-period
branch
from
September 22, 2026 06:10
4c0b4ae to
f067906
Compare
LeadcodeDev
force-pushed
the
refactor/decorative-paint-path
branch
from
September 22, 2026 06:10
3dcfc14 to
afbca7b
Compare
LeadcodeDev
force-pushed
the
fix/heropattern-scroll-period
branch
from
September 22, 2026 08:35
f067906 to
67be435
Compare
LeadcodeDev
force-pushed
the
refactor/decorative-paint-path
branch
from
September 22, 2026 08:35
afbca7b to
6a2f28e
Compare
LeadcodeDev
force-pushed
the
fix/heropattern-scroll-period
branch
from
September 22, 2026 08:44
67be435 to
5a7f871
Compare
tile_w = def.width * cfg.scale is computed at line 581 and used only for the overscan margin; the pixmap at 605-606 is sized from the unscaled def.width/def.height and resvg renders with Transform::default() (identity). The magnification then happens on the raster, via the shader matrix, with SamplingOptions::default() — which I read in skia- safe-0.99.0/src/core/sampling_options.rs:70-80: filter: FilterMode::Nearest, mipmap: MipmapMode::None. So heropattern with scale: 4 on a 1080p frame draws hard 4x4 pixel blocks from a 20x20 source raster, when the source is vector art that resvg could have rendered crisply at any size. The whole point of shipping 87 SVG patterns is lost above scale 1. It is also wasteful below 1: a scale: 0.25 pattern is rasterized at full size and then point- sampled down, aliasing badly. Fix: Size the pixmap at the scaled tile ((def.width * cfg.scale).ceil() x (def.height * cfg.scale).ceil(), clamped to a sane maximum) and pass tiny_skia::Transform::from_scale(cfg.scale, cfg.scale) to resvg::render, then drop the shader matrix so the tile is used 1:1. At minimum, pass SamplingOptions::new(FilterMode::Linear, MipmapMode::Linear) instead of the nearest-neighbour default. Refs #220
Every non-decorative node is timed by PaintWindow::contains — self.end.is_none_or(|e| t < e) (rustmotion-core/src/engine/box_tree.rs:43), a half-open window — and gets its effects from box_builder::effective_effects (legacy_dispatch.rs:110), which folds in timeline steps and style.transition keyframes. This second path uses an inclusive time > e and calls a.animation_effects() raw. Particle declares timeline: Vec<TimelineStep> (particle.rs:57), so a particle's timeline steps and transition keyframes are silently dropped, and its end_at keeps it visible for one extra frame — but only in a world view, since this is the sole call site (scene.rs:1130). The same particle in a slide view behaves correctly. A component whose behaviour depends on which view type contains it is invisible to tests written against either one. Fix: Give decorative children a real BoxNode with position: absolute; width/height: 100% and let them flow through paint_tree like everything else, deleting paint_decorative_fullscreen. If the bypass must stay, have it call effective_effects and PaintWindow::contains rather than re-deriving both. Refs #220
LeadcodeDev
force-pushed
the
refactor/decorative-paint-path
branch
from
September 22, 2026 08:45
6a2f28e to
e5699b7
Compare
Owner
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Severity Low, category correctness. Location:
crates/rustmotion/src/engine/render/scene.rs:595Impact
Every non-decorative node is timed by
PaintWindow::contains—self.end.is_none_or(|e| t < e)(rustmotion-core/src/engine/box_tree.rs:43), a half-open window — and gets its effects frombox_builder::effective_effects(legacy_dispatch.rs:110), which folds intimelinesteps andstyle.transitionkeyframes. This second path uses an inclusivetime > eand callsa.animation_effects()raw.Particledeclarestimeline: Vec<TimelineStep>(particle.rs:57), so a particle's timeline steps and transition keyframes are silently dropped, and itsend_atkeeps it visible for one extra frame — but only in aworldview, since this is the sole call site (scene.rs:1130). The same particle in aslideview behaves correctly. A component whose behaviour depends on which view type contains it is invisible to tests written against either one.Fix
Give decorative children a real
BoxNodewithposition: absolute; width/height: 100%and let them flow throughpaint_treelike everything else, deletingpaint_decorative_fullscreen. If the bypass must stay, have it calleffective_effectsandPaintWindow::containsrather than re-deriving both.Evidence the audit read
Part of the September 2026 audit remediation chantier. Refs #220 (RM-36).