You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Camera: a world larger than the frame, per-segment easing, handheld shake, camera-aware validation #428
User feedback from a real production (a 15 s one-take product film, 1920×1080, hand-written scenario). These are the blockers that made them keep an HTML/browser-based renderer for that kind of film.
To do
Free camera over a world larger than the frame. Today the scene camera only shows what is inside the 1920×1080 frame: panning to an element placed at x: 3000 shows an empty frame, and validate rejects the element as a viewport overflow. The world view steps from beat to beat with a crossfade, so an object cannot travel from one beat to the next. Wanted: a scene whose world extends beyond the frame, a camera that moves through it, and elements that travel continuously across it.
Expected at t=2: the pink square in the centre. Actual: empty frame, and validate reports a viewport overflow.
Camera-aware validation: an element outside the frame but brought into view by the camera is not an overflow.
Per-keyframe easing on camera keyframes, like component keyframes (today one easing per camera property for the whole scene).
Handheld shake: a wiggle/shake applicable to the camera.
Definition of done
A test that fails on current main is written first. For anything visual it is a render test (scenario → frame → pixel assertions); tests that build AnimatedProperties by hand are not enough — that is exactly how the regressions listed here slipped through.
Each item is verified end to end: scenario JSON, rustmotion validate, render, image inspected.
Vocabulary changes update crates/rustmotion/skills/SKILL.md, the relevant rules/ file and the exported schema. A field the validator accepts must have an effect, otherwise the validator rejects it or warns.
No comments in Rust code (repository rule). Commits follow <type>: <verb> <message>. No AI attribution in commits, PRs or issue comments.
Root cause of the first item, found while implementing #427 and verified at 1920×1080. I had filed it separately as #451 before noticing it is this item; closing that one as a duplicate and keeping the diagnosis here.
The viewport clip is issued after the camera transform is already on the canvas, in crates/rustmotion/src/engine/render/scene.rs (two sites, around lines 490 and 1518):
let camera_guard = matcheffective_camera(scene){Some(camera)if plane_cam.is_none() => {let g = super::CanvasGuard::new(canvas);apply_camera_transform(canvas, scene, camera, time asf32, w, h);Some(g)}
_ => None,};let clip_guard = super::CanvasGuard::new(canvas);
canvas.clip_rect(Rect::from_wh(config.widthasf32, config.heightasf32),ClipOp::Intersect,true);
Rect::from_wh(width, height) is in local coordinates, and the canvas matrix already carries the camera translation, so the clip rectangle travels with the camera instead of staying pinned to the surface.
Reproduction: 1920×1080, camera x: 0 → -3000 over 2 s, a red rectangle at local x: 200 and a green one at local x: -2800, so the green one lands at screen x: 200 when the pan completes. Counting sampled pixels of each colour in rustmotion still:
time
camera x
red
green
0.0
0
1900
0
1.0
-1500
1026
0
2.0
-3000
0
0
At time: 1.0 the clip sits at device [1500, 3420], so red — at screen 1700..2100 — survives only over 1700..1920, 55% of it; 1026/1900 is 54%. At time: 2.0 the clip is at [3000, 4920], entirely off a 1920-wide surface, and the frame is blank although the green rectangle is at screen 200..600.
So "panning to an element placed at x: 3000 shows an empty frame" is not a missing feature — the content is drawn and then clipped away. The clip is the surface, so it belongs before the camera transform, or expressed in device coordinates. That is separable from the other half of the item (validate rejecting the element as a viewport overflow, which is the third checkbox's camera-aware validation).
camera.motion_blur from #427 inherits the same ceiling, since each sub-frame exposure goes through the same path.
Context
User feedback from a real production (a 15 s one-take product film, 1920×1080, hand-written scenario). These are the blockers that made them keep an HTML/browser-based renderer for that kind of film.
To do
x: 3000shows an empty frame, andvalidaterejects the element as a viewport overflow. Theworldview steps from beat to beat with a crossfade, so an object cannot travel from one beat to the next. Wanted: a scene whose world extends beyond the frame, a camera that moves through it, and elements that travel continuously across it.{"version":"1.0","video":{"width":1920,"height":1080,"fps":30,"background":"#101018"},"scenes":[{"duration":2,"camera":{"keyframes":[{"property":"x","easing":"linear","values":[{"time":0,"value":0},{"time":2,"value":2100}]}]},"children":[{"type":"div","position":"absolute","x":3000,"y":440,"style":{"width":200,"height":200,"background":"#FF3366"}}]}]}validatereports a viewport overflow.wiggle/shakeapplicable to the camera.Definition of done
mainis written first. For anything visual it is a render test (scenario → frame → pixel assertions); tests that buildAnimatedPropertiesby hand are not enough — that is exactly how the regressions listed here slipped through.rustmotion validate, render, image inspected.cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspacepass.crates/rustmotion/skills/SKILL.md, the relevantrules/file and the exported schema. A field the validator accepts must have an effect, otherwise the validator rejects it or warns.<type>: <verb> <message>. No AI attribution in commits, PRs or issue comments.