Skip to content

fix(animator): start the underdamped spring from rest - #285

Merged
LeadcodeDev merged 1 commit into
chantier/audit-2026-09from
fix/spring-step-response
Sep 22, 2026
Merged

LeadcodeDev merged 1 commit into
chantier/audit-2026-09from
fix/spring-step-response

Conversation

@LeadcodeDev

Copy link
Copy Markdown
Owner

Audit finding carried by this chantier. Refs #220 (RM-09).

The closed-form underdamped step response is `1 - e^(-zeta*omega*t) * [cos(omega_d*t) + (zeta*omega/omega_d)*sin(omega_d*t)]`. The code feeds `zeta*omega*t/omega_d` into `sin()` instead of `omega_d*t` — the `t` was multiplied into the coefficient instead of the phase. I verified this numerically against a 400k-step ODE integration of `m*x'' + c*x' + k*x = k`: the correct formula matches the integrator to 5 decimals, the repo formula does not. With the shipped defaults (damping 15, stiffness 100, mass 1, zeta = 0.75), at the first 60fps frame after the start (t = 0.0167 s) the solver reports 0.10416 of the travel where the physics gives 0.01282 — 8x. Initial velocity is 6.21/s instead of 0, so the element visibly snaps on frame 1 rather than easing out of rest, and the overshoot peak is wrong too (1.0194 vs 1.0284). This affects every spring path in the engine: `EasingType::Spring` keyframes, the `bounce_in` preset (kf_anim_spring, zeta = 0.6, animator.rs:2092), `elastic_in` (kf_anim_spring_underdamped, zeta = 0.274, animator.rs:2106), and every author-supplied `spring` override applied through `apply_spring_to_motion`. `spring_settle_time`/`spring_rest_time` (what `rustmotion info` reports and what the `spring.duration` remap divides by) are derived from the same wrong curve.

Refs #220
@LeadcodeDev LeadcodeDev added the bug Something isn't working label Sep 22, 2026
@LeadcodeDev LeadcodeDev self-assigned this Sep 22, 2026
@LeadcodeDev
LeadcodeDev merged commit 1bfa4bc into chantier/audit-2026-09 Sep 22, 2026
0 of 3 checks passed
LeadcodeDev added a commit that referenced this pull request Sep 22, 2026
The closed-form underdamped step response is `1 - e^(-zeta*omega*t) * [cos(omega_d*t) + (zeta*omega/omega_d)*sin(omega_d*t)]`. The code feeds `zeta*omega*t/omega_d` into `sin()` instead of `omega_d*t` — the `t` was multiplied into the coefficient instead of the phase. I verified this numerically against a 400k-step ODE integration of `m*x'' + c*x' + k*x = k`: the correct formula matches the integrator to 5 decimals, the repo formula does not. With the shipped defaults (damping 15, stiffness 100, mass 1, zeta = 0.75), at the first 60fps frame after the start (t = 0.0167 s) the solver reports 0.10416 of the travel where the physics gives 0.01282 — 8x. Initial velocity is 6.21/s instead of 0, so the element visibly snaps on frame 1 rather than easing out of rest, and the overshoot peak is wrong too (1.0194 vs 1.0284). This affects every spring path in the engine: `EasingType::Spring` keyframes, the `bounce_in` preset (kf_anim_spring, zeta = 0.6, animator.rs:2092), `elastic_in` (kf_anim_spring_underdamped, zeta = 0.274, animator.rs:2106), and every author-supplied `spring` override applied through `apply_spring_to_motion`. `spring_settle_time`/`spring_rest_time` (what `rustmotion info` reports and what the `spring.duration` remap divides by) are derived from the same wrong curve.

Refs #220
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant