Part of #326. Batch 1 — #329, #330, #331 and #334's second half all depend on the TimePoint type this workstream introduces.
build_frame_tasks builds a flat Vec<FrameTask> where the index is the frame number, and a transition's frames replace scene frames instead of overlapping them. Six scenes declaring 15.0 s therefore render 13.5 s, and every cut after the first transition drifts: -0.30 s, -0.60 s, -0.90 s, -1.15 s. At 115 BPM that last one is more than two beats. A rhythmic edit is currently impossible without compensating every duration by hand, and changing one transition re-times everything after it.
Owned files
crates/rustmotion-core/src/schema/time.rs (new — TimePoint)
crates/rustmotion-core/src/schema/scenario.rs (Scenario.bpm, beat_offset, duration, timing; Scene.at, Scene.tail)
crates/rustmotion/src/encode/video/tasks.rs (build_frame_tasks, build_slide_view_tasks)
Do NOT write: schema/animation.rs, engine/animator.rs (#330), schema/vars.rs (#329), any audio file (#331).
Deliverables
-
TimePoint — a value that is a bare number (seconds), or a string carrying a unit: "2.5s", "8b", "8b+120ms". Beat units require bpm and error with unresolved_beat_unit without it. The grid is beat_offset + n * 60/bpm; the reel being reproduced anchors its grid at 2.2 s, not at 0, so beat_offset is not decoration.
-
One rule for anchoring, stated once in the schema docs. at on a scene is absolute by definition. Everything inside a scene is relative to that scene. The prefix @ forces absolute: "@8b".
-
Scene.at, defaulting to "auto" — immediately after the previous scene's end, which is today's sequencing minus the subtraction.
-
Transitions become a declared overlap. Scene i occupies [at_i, at_i + duration_i). A transition of length d means scene i-1 is also rendered during [at_i, at_i + d), past its own end. Scene.tail defaults to "freeze" and reuses the freeze_at machinery; "continue" lets its animations run on. Total duration becomes at_last + duration_last, with no subtraction.
-
snap: "beat" at scenario level rounds every at, start_at and delay to the nearest beat. This is the cheapest line in the whole chantier for getting a rhythmic edit by default, without the author having to think about it.
-
off_grid_cut as a validation warning when bpm is set and a cut does not land on a beat.
-
The behaviour change sits behind "timing": "v2" at the scenario root. Absent means today's subtraction plus a deprecation warning from render, following the pattern the legacy top-level scenes warning already uses.
Acceptance
- Six scenes declaring 2.2 + 2.6 + 2.6 + 3.12 + 2.08 + 2.4 with five transitions render 15.0 s under
timing: v2
- The same file without
timing: v2 renders 13.5 s and warns
--frames a-b still addresses the same index space
Part of #326. Batch 1 — #329, #330, #331 and #334's second half all depend on the
TimePointtype this workstream introduces.build_frame_tasksbuilds a flatVec<FrameTask>where the index is the frame number, and a transition's frames replace scene frames instead of overlapping them. Six scenes declaring 15.0 s therefore render 13.5 s, and every cut after the first transition drifts: -0.30 s, -0.60 s, -0.90 s, -1.15 s. At 115 BPM that last one is more than two beats. A rhythmic edit is currently impossible without compensating every duration by hand, and changing one transition re-times everything after it.Owned files
crates/rustmotion-core/src/schema/time.rs(new —TimePoint)crates/rustmotion-core/src/schema/scenario.rs(Scenario.bpm,beat_offset,duration,timing;Scene.at,Scene.tail)crates/rustmotion/src/encode/video/tasks.rs(build_frame_tasks,build_slide_view_tasks)Do NOT write:
schema/animation.rs,engine/animator.rs(#330),schema/vars.rs(#329), any audio file (#331).Deliverables
TimePoint— a value that is a bare number (seconds), or a string carrying a unit:"2.5s","8b","8b+120ms". Beat units requirebpmand error withunresolved_beat_unitwithout it. The grid isbeat_offset + n * 60/bpm; the reel being reproduced anchors its grid at 2.2 s, not at 0, sobeat_offsetis not decoration.One rule for anchoring, stated once in the schema docs.
aton a scene is absolute by definition. Everything inside a scene is relative to that scene. The prefix@forces absolute:"@8b".Scene.at, defaulting to"auto"— immediately after the previous scene's end, which is today's sequencing minus the subtraction.Transitions become a declared overlap. Scene i occupies
[at_i, at_i + duration_i). A transition of length d means scene i-1 is also rendered during[at_i, at_i + d), past its own end.Scene.taildefaults to"freeze"and reuses thefreeze_atmachinery;"continue"lets its animations run on. Total duration becomesat_last + duration_last, with no subtraction.snap: "beat"at scenario level rounds everyat,start_atanddelayto the nearest beat. This is the cheapest line in the whole chantier for getting a rhythmic edit by default, without the author having to think about it.off_grid_cutas a validation warning whenbpmis set and a cut does not land on a beat.The behaviour change sits behind
"timing": "v2"at the scenario root. Absent means today's subtraction plus a deprecation warning fromrender, following the pattern the legacy top-levelsceneswarning already uses.Acceptance
timing: v2timing: v2renders 13.5 s and warns--frames a-bstill addresses the same index space