Skip to content

spring returns NaN and breaks physics simulation when config has undefined properties #7607

Open

Description

@codeCraft-Ritik

Describe the bug

Describe the bug

When calling spring() with a config object containing undefined properties (common when passing optional React props: { damping: props.damping, stiffness: props.stiffness }), springCalculation merges the configuration with object spread:

In packages/core/src/spring/spring-utils.ts:

config: {
  ...defaultSpringConfig,
  ...config,
}

If stiffness or mass is undefined, it overrides defaultSpringConfig's value with undefined. In advance(), Math.sqrt(k / m) calculates Math.sqrt(undefined / 1) = NaN, causing spring() to return NaN for every frame.

If damping is undefined, zeta evaluates to NaN. Because NaN < 1 is false, it bypasses underDampedPosition and silently forces the spring into criticallyDampedPosition, breaking underdamped spring physics without error.

Steps To Reproduce

Steps To Reproduce

  1. Call spring with an optional property explicitly set to undefined:
import { spring } from 'remotion';

const value = spring({
  frame: 10,
  fps: 30,
  config: {
    damping: 10,
    stiffness: undefined,
  },
});

### Expected behavior

### Expected behavior
`springCalculation` should fall back to `defaultSpringConfig` values using nullish coalescing (`config.stiffness ?? defaultSpringConfig.stiffness`), gracefully handling partial and optional configuration objects.

### Suggested Fix
```diff
--- a/packages/core/src/spring/spring-utils.ts
+++ b/packages/core/src/spring/spring-utils.ts
@@ -14,7 +14,7 @@ export type SpringConfig = {
 	overshootClamping: boolean;
 };
 
-const defaultSpringConfig: SpringConfig = {
+export const defaultSpringConfig: SpringConfig = {
 	damping: 10,
 	mass: 1,
 	stiffness: 100,
@@ -118,12 +118,18 @@ export function springCalculation({
 	const from = 0;
 	const to = 1;
+	const resolvedConfig: SpringConfig = {
+		damping: config.damping ?? defaultSpringConfig.damping,
+		mass: config.mass ?? defaultSpringConfig.mass,
+		stiffness: config.stiffness ?? defaultSpringConfig.stiffness,
+		overshootClamping:
+			config.overshootClamping ?? defaultSpringConfig.overshootClamping,
+	};
 	const cacheKey = [
 		frame,
 		fps,
-		config.damping,
-		config.mass,
-		config.overshootClamping,
-		config.stiffness,
+		resolvedConfig.damping,
+		resolvedConfig.mass,
+		resolvedConfig.overshootClamping,
+		resolvedConfig.stiffness,
 	].join('-');
 	if (calculationCache[cacheKey]) {
 		return calculationCache[cacheKey];
@@ -144,10 +150,7 @@ export function springCalculation({
 		animation = advance({
 			animation,
 			now: time,
-			config: {
-				...defaultSpringConfig,
-				...config,
-			},
+			config: resolvedConfig,
 		});
 	}
 
@@ -156,10 +159,7 @@ export function springCalculation({
 		animation = advance({
 			animation,
 			now: (frameClamped / fps) * 1000,
-			config: {
-				...defaultSpringConfig,
-				...config,
-			},
+			config: resolvedConfig,
 		});
 	}

Packages

Packages

@remotion/core

Additional Context

No response

Activity

  1. harshvardhankhare commented on Sep 17, 2026

    @harshvardhankhare

    Hi! @codeCraft-Ritik I’d like to work on this issue. I’ll reproduce the undefined configuration behavior, implement the resolved spring config fallback, and add regression tests to cover optional/undefined configuration values.

  2. fulluhere commented on Sep 23, 2026

    @fulluhere
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions