From 5d4b644865d78757565adbaa511991ee7cc68837 Mon Sep 17 00:00:00 2001 From: vangel76 Date: Sun, 11 Oct 2026 18:34:08 +0200 Subject: [PATCH] #880: motion paths with overflowing or NaN arc length evaluate without panicking Problem: spatial keys with huge coordinates (the Position field accepts any finite number) overflow the motion path's arc length to NaN. SpatialSeg::at_length only returned early for len <= 0.0, so NaN reached s.clamp(0.0, len), which panics. The viewer's motion-path overlay evaluates on the UI thread, so moving the current time over such keys crashed the app. Fix: at_length returns the segment start unless the length is positive and finite. A seeded fuzz of evaluate/velocity/spatial tangents with NaN, infinite and +-1.7e308 values (200k tracks) found no other panic or hang. Co-Authored-By: Claude Opus 5.5 --- crates/keyframe/src/lib.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/keyframe/src/lib.rs b/crates/keyframe/src/lib.rs index 787a0643..17d5cd3a 100644 --- a/crates/keyframe/src/lib.rs +++ b/crates/keyframe/src/lib.rs @@ -287,7 +287,9 @@ impl SpatialSeg { /// along curved paths instead of piecewise linear per cell (#456). fn at_length(&self, s: f64) -> [f64; 3] { let len = self.length(); - if len <= 0.0 { + // A path whose length overflowed (huge coordinates) or is NaN has no arc-length table to + // walk: `clamp` would panic on a NaN bound. + if !(len > 0.0 && len.is_finite()) { return self.p[0]; } let s = s.clamp(0.0, len); @@ -846,6 +848,30 @@ mod tests { assert_eq!(segment_value(&keys, 0, s(1.0), true), Value::Vec2([80.0, 50.0])); } + /// #880: a motion path whose arc length overflows (huge coordinates, as the Position field + /// accepts) or is NaN evaluates without panicking: scrubbing over it crashed the app. + #[test] + fn overflowing_or_nan_motion_paths_evaluate() { + let paths = [ + [[-1.7e308, 0.0], [1.7e308, 0.0], [-1.7e308, 5.0]], + [[0.0, 0.0], [f64::NAN, 10.0], [20.0, 0.0]], + [[f64::INFINITY, 0.0], [0.0, f64::NEG_INFINITY], [20.0, 0.0]], + ]; + for path in paths { + let mut keys: Vec = path.iter().enumerate().map(|(i, &v)| Keyframe::new(s(i as f64 * 2.0), Value::Vec2(v))).collect(); + for eased in [false, true] { + if eased { + keys = keys.into_iter().map(Keyframe::eased).collect(); + } + for f in 0..=40 { + let t = s(f as f64 * 0.1); + assert!(evaluate(&keys, t, true).is_some()); + let _ = speed(&keys, t, true); + } + } + } + } + #[test] fn spatial_auto_bezier_tangents_follow_after_effects() { // Measured on After Effects 26.3 with original paths (docs/fidelity/README.md): each