/**
 * Critically damped spring step, parameterized by half-life rather than
 * angular frequency. A critically damped spring reaches its goal as fast as
 * possible without oscillation — ideal for FOV, crouch height, and lean.
 *
 * Form follows Daniel Holden, "Spring-It-On" — `theorangeduck.com/page/spring-roll-call`.
 *
 *   y  = ln(2) / halfLife
 *   eyt = exp(-y * dt)
 *   j1 = v + (x - target) * y
 *   x' = target + (x - target + j1 * dt) * eyt
 *   v' = (v - j1 * y * dt) * eyt
 *
 * @param {{value:number, velocity:number}} state mutated in place
 * @param {number} target  goal value
 * @param {number} halfLife seconds for the gap to halve (smaller = snappier)
 * @param {number} dt
 */
export function criticallyDampedSpringStep(state, target, halfLife, dt) {
    if (halfLife <= 0) {
        state.value = target;
        state.velocity = 0;
        return;
    }

    const y = 0.6931471805599453 / halfLife; // ln(2) / halfLife
    const eyt = Math.exp(-y * dt);

    const delta = state.value - target;
    const j1 = state.velocity + delta * y;

    state.value = target + (delta + j1 * dt) * eyt;
    state.velocity = (state.velocity - j1 * y * dt) * eyt;
}
