fix(motion): let counters land, float_3d honour its timing, and pans dissolve - #141
Merged
Conversation
The ramp always spanned whatever remained of the scene, so a counter only reached `to` on its very last frame — the figure the card exists to show was still moving when the camera left, and was never read. `duration` sets the ramp length explicitly, measured from `start_at`, and the value holds once reached. Unset, behaviour is unchanged. A duration outlasting the scene is honoured rather than clamped: silently speeding up a count the author asked to be slow would be the greater surprise.
Its keyframes were pinned to 0.0/0.5/1.0 seconds, so every floating element shared one 1-second cycle in lockstep and both `delay` and `duration` were silently inert. A row of cards then bobs in unison, which reads as a dance rather than as depth. The cycle now spans delay..delay+duration, and a new `amplitude` on PresetConfig sets the travel (default 12px, the previous fixed value) with the tilt scaled to match. Varying the three across elements in one scene is what turns a shared bob into parallax.
The two scenes were composited fully opaque, so the outgoing one slid off as a solid slab while only the background had any differential motion. Each plane now fades as it leaves or arrives. Both curves are pinned at their own end — the outgoing plane is fully opaque at progress 0, the incoming one at progress 1 — because a transition frame sits directly against a normal frame at each junction, and any alpha short of 1 there is a visible step. Mirrored exponents keep both planes at 67% through the middle rather than 50%, so the frame never washes out half way across.
La dérive s'applique aussi aux `text`, mais plus discrète que sur les cards (2,5 → 5 px, cycles de 10 → 16 s) : la typographie est le plan le plus éloigné de l'objectif et un titre qui oscille visiblement se lit mal. Note aussi le piège de mesure : le pan caméra est un mode commun d'un ordre de grandeur supérieur à la dérive, donc corréler les positions absolues renvoie +0.99 même quand la parallaxe fonctionne. C'est l'écart entre deux éléments qu'il faut mesurer.
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.
Three motion defects that all read the same way on screen — movement that ignores what the scenario asked for.
Counters never landed. The ramp was hardcoded to stretch from
start_atto the end of the scene, so the figure reachedtoon the very last frame and the viewer never got to read the number they had just watched count up. New optionalcounter.duration(seconds, measured fromstart_at) makes the count finish early and hold. Unset, the old behaviour is preserved exactly. A duration outlasting the scene is honoured rather than clamped — the count simply never arrives, which is what was asked for; a zero or negative one falls back to the scene. Five unit tests pin the ramp, including the two edge cases.float_3dignored bothdelayandduration. Its keyframes were pinned to literal 0.0 / 0.5 / 1.0 seconds, so every floating element in a scene ran one shared 1-second cycle in lockstep no matter what the scenario declared. A row of cards bobbing in unison reads as a dance; the same cards on different phases read as depth, which is the entire point of the preset. The cycle now spansdelay..delay+duration, so duration sets the period and delay shifts the phase.Camera pans slid the foreground as a solid slab. The outgoing plane now dissolves and the incoming one materialises across the pan. Both curves are pinned at their own end —
fg_afully opaque at t=0,fg_bat t=1 — because a transition frame sits directly against a normal frame at each junction and any alpha short of 1 there is a visible step. Mirrored exponents rather than a plain crossfade keep both planes at 67% through the middle, so the frame never washes out half way. Two tests cover it: the junction no-op invariant, and the mid-pan floor.Known gap
PresetConfig.amplitudeis added and read by the preset expansion, but nothing can currently set it.AnimationTiming— the struct scenarios actually deserialize into — has noamplitudefield, andto_preset_config()hardcodesamplitude: None, which is the only construction site outsideDefault. Soconfig.amplitude.unwrap_or(12.0)always yields 12.0 and the knob is dead from a scenario author's point of view.delayanddurationdo land, since those come fromAnimationTiming. Wiring it through is a field onAnimationTimingplus one line into_preset_config; left out of this PR rather than smuggled in.Verification
cargo test --workspacegreen.cargo check --workspaceclean.