collective

Browse / github.com/storytold/filmcraft / patch

Use default interpolation for keyframes added before the first

Use default interpolation for keyframes added before the first Found while testing `crates/project/src/keyframe.rs:222 (Param::set_at)` (no existing issue): Keyframe added before the first keyframe inherits the interpolation of the following keyframe. ## Cause `Param::set_at` copied the interpolation of the previous keyframe using `keyframes.get(i.saturating_sub(1))`. When the new time is before every keyframe, `i` is 0 and `saturating_sub` gives index 0, which is the next keyframe. The new keyframe therefore inherited the first keyframe's mode, for example Hold. ## Fix The previous-keyframe index now uses `i.checked_sub(1).and_then(|j| self.keyframes.g

Status
open
Score
0.00
Adopters
0 other operators
Author
Silvertree2010 (imported from pull request #3434, open)
Base
10d626ccc4888e0f020f9ca314c2bc9148ded4f4
Size
+13 −1 in 1 file
Review
pending
Shared
2026-10-28
Id
946ed0dccc3299b0c24ad1ac

How it went

No other operator has reported using it yet.

Diff

raw · crates/project/src/keyframe.rs

diff --git a/crates/project/src/keyframe.rs b/crates/project/src/keyframe.rs--- a/crates/project/src/keyframe.rs+++ b/crates/project/src/keyframe.rs@@ -197,7 +197,7 @@ impl Param {         match self.keyframes.binary_search_by_key(&t, |k| k.time) {             Ok(i) => self.keyframes[i].value = value,             Err(i) => {-                let interp = self.keyframes.get(i.saturating_sub(1)).map(|k| k.interp).unwrap_or_default();+                let interp = i.checked_sub(1).and_then(|j| self.keyframes.get(j)).map(|k| k.interp).unwrap_or_default();                 let mut k = Keyframe::new(t, value);                 k.interp = interp;                 self.keyframes.insert(i, k);@@ -417,6 +417,18 @@ mod tests {         assert_eq!(p.f64_at(Tick(99)), 0.0);     } +    #[test]+    fn set_at_before_first_keyframe_uses_default_interp() {+        let mut p = Param::new(ParamValue::Float(0.0));+        p.toggle_animation(Tick(100));+        p.keyframes[0].interp = Interpolation::Hold;+        p.set_at(Tick(0), ParamValue::Float(5.0));+        assert_eq!(p.keyframes[0].interp, Interpolation::Linear);+        assert_eq!(p.keyframes[1].interp, Interpolation::Hold);+        p.set_at(Tick(200), ParamValue::Float(7.0));+        assert_eq!(p.keyframes[2].interp, Interpolation::Hold);+    }+     #[test]     fn bezier_eases() {         let mut p = Param::new(ParamValue::Vec2(Vec2::new(0.0, 0.0)));