fix(world): stop the camera teleporting and the frame budget losing frames - #152
Merged
LeadcodeDev merged 1 commit intoAug 8, 2026
Conversation
…rames Eight confirmed audit findings on scene transitions and the `world` view — the one mechanism meant to read as a single continuous shot, and the one where every defect below is plainly visible on screen. - A transition longer than the scene it leaves silently deleted frames from the *entering* scene. The outgoing scene cannot spend more frames than it has, but the budget was computed as if it could, and the overrun was taken out of the next scene. Now clamped to the outgoing scene's own frame count, with the accounting asserted frame by frame. - `camera_pan_duration` longer than a scene teleported the camera: measured jump of 245.33px between two consecutive frames, now 21.33px. - The outgoing scene's background held at full strength through the whole pan, then vanished in one frame — an avg-luma step of 109.0 between two frames, now at most 4.82. - The scene cross-fade drove the *whole* frame to 50% opacity at every pan midpoint (both scenes at 0.5, nothing behind them). Mid-pan opacity is now 0.670 instead of sitting on that floor. - `persist: true` snapped opacity back to 1.0 while the scene was still fading out — a 0.326 discontinuity, now 0.034. - A view-to-view transition duplicated the junction frame on both sides. `ViewTransition` progress is now an open interval, matching what `SlideTransition` already guaranteed. - `scene.freeze_at` was read by no world code path at all. - `scene.effects` (post-effects) were dropped on world frames and on view transition frames. Every figure above is a before/after measurement from the tests added here, not an estimate. Tests: 144 pass on this branch alone.
LeadcodeDev
added a commit
that referenced
this pull request
Aug 10, 2026
…rames (#152) Eight confirmed audit findings on scene transitions and the `world` view — the one mechanism meant to read as a single continuous shot, and the one where every defect below is plainly visible on screen. - A transition longer than the scene it leaves silently deleted frames from the *entering* scene. The outgoing scene cannot spend more frames than it has, but the budget was computed as if it could, and the overrun was taken out of the next scene. Now clamped to the outgoing scene's own frame count, with the accounting asserted frame by frame. - `camera_pan_duration` longer than a scene teleported the camera: measured jump of 245.33px between two consecutive frames, now 21.33px. - The outgoing scene's background held at full strength through the whole pan, then vanished in one frame — an avg-luma step of 109.0 between two frames, now at most 4.82. - The scene cross-fade drove the *whole* frame to 50% opacity at every pan midpoint (both scenes at 0.5, nothing behind them). Mid-pan opacity is now 0.670 instead of sitting on that floor. - `persist: true` snapped opacity back to 1.0 while the scene was still fading out — a 0.326 discontinuity, now 0.034. - A view-to-view transition duplicated the junction frame on both sides. `ViewTransition` progress is now an open interval, matching what `SlideTransition` already guaranteed. - `scene.freeze_at` was read by no world code path at all. - `scene.effects` (post-effects) were dropped on world frames and on view transition frames. Every figure above is a before/after measurement from the tests added here, not an estimate. Tests: 144 pass on this branch alone.
LeadcodeDev
added a commit
that referenced
this pull request
Aug 11, 2026
) Closes #164. PR #152 fixed a real bug — `scene.freeze_at` was read by no code path in the `world` view — by adding a fifth hand-written copy of the clamp "for parity with the other four". The cause it left untouched was that no single place computes a scene's render time, so every new render path has to remember to redo it. Writing the parameterized test first, before any fix, paid for itself twice. Four of the five paths agreed; the fifth did not, and the reason was not the cosmetic `.min()`-versus-`if` drift already noted on the issue: `render_world_frame_scaled` painted the active scene's animated background from the raw world clock, never clamped. PR #152's fifth copy was itself incomplete, and nothing had caught it. `RenderContext::time` is now a `SceneTime` rather than an `f64`. Its single field is private to a submodule and its only two constructors both apply the clamp, so it cannot be built anywhere else — verified by trying: error[E0423]: cannot initialize a tuple struct which contains private fields Since `render_with_new_pipeline` — the only function that paints a scene's component tree — requires a `&RenderContext`, a sixth render path is held to the clamp by the compiler rather than by discipline. That does not stop someone writing `frame_index as f64 / fps as f64` from scratch, but it removes the failure mode actually observed here: copying an existing path for parity. Every path left to copy now goes through `SceneTime`. Two findings worth recording rather than burying: - `time_scale`/`time_offset` were already available on `card`, `flex`, `grid`, `container` and `positioned` — PR #64 generalized them long before this chantier, with the affine composition, its tests and its rule documentation. Nothing here needed to change. - `freeze_at` does not need to enter that composition. Because `time_scale` is validated strictly positive, every composed remap is monotonic, so clamping the global time before composing is equivalent to clamping after, at any nesting depth. A test pins the brief's own example — a `card` at `time_scale: 2` over a `flex` at `time_offset: -1` — rather than leaving that as an argument. Not covered: a world crossfade with two scenes visible keeps the raw world clock for the background. Freezing it means deciding which scene wins during the transition, which is a design question, not an oversight; the comment says so where it applies.
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.
Round 3 du chantier d'audit — lot transitions & vue world, 8 constats vérifiés.
C'est le lot le plus visible à l'œil : la vue
worldest le seul mécanisme censé produire un plan continu, et chacun des défauts ci-dessous se voit à la lecture.camera_pan_durationsupérieur à une durée de scène téléporte la caméra.persist: truerepasse l'opacité à 1.0 alors que la scène est en plein fondu sortant.scene.freeze_atn'est lu par aucun chemin de code world.scene.effects(post-effets) est ignoré sur les frames world et sur les transitions de vues.Mesures avant / après
Chaque chiffre vient d'un test ajouté dans cette PR, pas d'une estimation.
persistNotes d'implémentation
actual_outgoing_transition), et la comptabilité est vérifiée frame par frame : chaque index local de la scène entrante doit être rendu exactement une fois.ViewTransitionutilise désormais une progression en intervalle ouvert, ce queSlideTransitiongarantissait déjà — d'où la disparition de la frame dupliquée.Vérification
cargo test -p rustmotionsur cette branche seule : 144 tests, 0 échec.