Beyond Euler: Analytical Spring ODEs for Frame-Rate-Independent UI
Why step-based Euler integration breaks across variable refresh rate displays (60Hz to 120Hz ProMotion), and how Exhuma solves 2nd-order differential equations in closed form for zero-allocation, drift-free kinetic motion.
- 8 min read
- October 2026
- Authored by Fleect
- Kinetic Math
- Physics
- Performance
The Discretization Trap: Why Step-Based Physics Fails
Most web animation libraries simulate physical motion through numerical integration—specifically explicit forward Euler loops. Each frame tick, the engine measures the elapsed time delta (dt), computes the instantaneous spring force, and steps velocity and position sequentially: velocity += acceleration * dt; position += velocity * dt; On a uniform 60Hz display, this approximation feels passable. But the modern web runs across heterogeneous, variable refresh rate (VRR) displays ranging from 10Hz ambient lock-screens up to 120Hz ProMotion screens. When a heavy garbage collection pause or React hydration step delays a frame by even 40 milliseconds, the discretized dt term spikes. In an explicit Euler step, this sudden timestep expansion injects phantom kinetic energy, causing springs to violently overshoot, oscillate unpredictably, or fail to settle.
The Continuous Differential Model: Solving Harmonic Motion
Rather than approximating motion step-by-step, Exhuma models kinetic interactions as continuous solutions to the second-order damped harmonic oscillator differential equation: m * x″(t) + c * x′(t) + k * (x(t) - x_target) = 0 By normalizing with undamped angular frequency ω₀ = √(k / m) and damping ratio ζ = c / (2 * √(k * m)), the system’s behavior cleanly splits into three distinct analytical regimes: • Underdamped (ζ < 1): Controlled rhythmic oscillation with sinusoidal exponential decay. • Critically Damped (ζ = 1): The fastest non-oscillatory return to equilibrium with zero overshoot. • Overdamped (ζ > 1): Viscous, purely exponential return to rest.
Closed-Form Solutions & O(1) Random Access
By solving the characteristic quadratic equation r² + 2*ζ*ω₀*r + ω₀² = 0, position x(t) and velocity v(t) become pure, closed-form functions of total elapsed time t = t_now - t_start. For the underdamped regime (ζ < 1), with damped angular frequency ω_d = ω₀ * √(1 - ζ²): x(t) = x_target + e^(-ζ*ω₀*t) * (c₁ * cos(ω_d * t) + c₂ * sin(ω_d * t)) where c₁ = x₀ - x_target, and c₂ = (v₀ + ζ * ω₀ * c₁) / ω_d. This closed-form formulation delivers three decisive architectural advantages: 1. O(1) Random Access: You can evaluate the spring at any arbitrary millisecond without stepping through prior frames. If a background tab wakes up after 5 seconds, the state resolves in a single CPU cycle. 2. Zero Floating-Point Drift: Because t is measured against performance.now(), arithmetic rounding errors never compound across frames. 3. Zero Heap Allocations: All computations use primitive scalar math. Not a single object or array is allocated during animation frames.
// Exhuma Kinetic Methodology: Closed-Form Spring ODE Solver
export function solveAnalyticalSpring(
t: number, // elapsed time in seconds
x0: number, // initial displacement
v0: number, // initial velocity
target: number, // target equilibrium
stiffness: number, // spring constant k
damping: number, // damping coefficient c
mass: number = 1 // mass m
): { position: number; velocity: number } {
const w0 = Math.sqrt(stiffness / mass);
const zeta = damping / (2 * Math.sqrt(stiffness * mass));
const deltaX = x0 - target;
if (zeta < 1) {
// Underdamped regime: oscillatory decay
const wd = w0 * Math.sqrt(1 - zeta * zeta);
const c1 = deltaX;
const c2 = (v0 + zeta * w0 * deltaX) / wd;
const envelope = Math.exp(-zeta * w0 * t);
const cosTerm = Math.cos(wd * t);
const sinTerm = Math.sin(wd * t);
const position = target + envelope * (c1 * cosTerm + c2 * sinTerm);
const velocity = envelope * (
(-zeta * w0 * c1 + wd * c2) * cosTerm -
(zeta * w0 * c2 + wd * c1) * sinTerm
);
return { position, velocity };
} else if (zeta === 1) {
// Critically damped regime: fastest settling, zero overshoot
const envelope = Math.exp(-w0 * t);
const c1 = deltaX;
const c2 = v0 + w0 * deltaX;
const position = target + envelope * (c1 + c2 * t);
const velocity = envelope * (c2 - w0 * (c1 + c2 * t));
return { position, velocity };
} else {
// Overdamped regime: dual exponential decay
const r1 = -w0 * (zeta - Math.sqrt(zeta * zeta - 1));
const r2 = -w0 * (zeta + Math.sqrt(zeta * zeta - 1));
const c2 = (v0 - r1 * deltaX) / (r2 - r1);
const c1 = deltaX - c2;
const position = target + c1 * Math.exp(r1 * t) + c2 * Math.exp(r2 * t);
const velocity = c1 * r1 * Math.exp(r1 * t) + c2 * r2 * Math.exp(r2 * t);
return { position, velocity };
}
}Seamless Interruption & Momentum Transfer
The defining metric of authentic tactile feel is interruptibility. If a user grabs or redirects a moving card mid-flight, the interface must neither stutter nor discard stored kinetic energy. Because Exhuma’s closed-form equations yield both position x(t) and exact velocity v(t) analytically at any instant, interruption requires zero frame history: At touch interception t_intercept, we sample x(t) and v(t) in O(1), terminate the previous animation frame handle, and seed the incoming spring with initial conditions x₀ = x(t_intercept) and v₀ = v(t_intercept) + v_pointer. The transition is visually imperceptible and physically continuous—preserving momentum without a single dropped frame.