Skip to content

refactor(scene): paint decorative fullscreen through the box tree - #283

Merged
LeadcodeDev merged 1 commit into
chantier/audit-2026-09from
refactor/decorative-paint-path
Sep 22, 2026
Merged

LeadcodeDev merged 1 commit into
chantier/audit-2026-09from
refactor/decorative-paint-path

Conversation

@LeadcodeDev

Copy link
Copy Markdown
Owner

Severity Low, category correctness. Location: crates/rustmotion/src/engine/render/scene.rs:595

Impact

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.

Evidence the audit read

if let Some(e) = end_at {
            if time > e { return; }
        }
    }
    let props = match child.component.as_animatable() {
        Some(a) => {
            let effects = a.animation_effects();
            if effects.is_empty() { AnimatedProperties::default() }
            else { resolve_props_for_effects(effects, time, ctx.scene_duration) }

Stacked on fix/heropattern-rasterize-scale, which carries the previous finding of this workstream. GitHub shows only this finding's diff; merge in order.

Part of the September 2026 audit remediation chantier. Refs #220 (RM-36).

@LeadcodeDev LeadcodeDev added the bug Something isn't working label Sep 22, 2026
@LeadcodeDev LeadcodeDev self-assigned this Sep 22, 2026
@LeadcodeDev
LeadcodeDev changed the base branch from fix/heropattern-rasterize-scale to chantier/audit-2026-09 September 22, 2026 08:53
@LeadcodeDev
LeadcodeDev force-pushed the refactor/decorative-paint-path branch from e5699b7 to 01a14e0 Compare September 22, 2026 09:01
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.

Refs #220
@LeadcodeDev
LeadcodeDev force-pushed the refactor/decorative-paint-path branch from 01a14e0 to d199738 Compare September 22, 2026 09:05
@LeadcodeDev
LeadcodeDev merged commit 0a0d175 into chantier/audit-2026-09 Sep 22, 2026
LeadcodeDev added a commit that referenced this pull request Sep 22, 2026
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.

Refs #220
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.

1 participant