Repository: flutter
Issue: FrictionSimulation decays faster than physics, and isn't ballistic
Labels: framework,platform-mac,a: fidelity,f: scrolling,a: desktop,has reproducible steps,P2,found in release: 3.7,found in release: 3.8,team-macos,triaged-macos

The simulation in [`FrictionSimulation`](https://api.flutter.dev/flutter/physics/FrictionSimulation-class.html) doesn't correctly model the physics it's described as modeling, when `constantDeceleration` is nonzero. This is the simulation used for fling-scrolling in `BouncingScrollPhysics` / `BouncingScrollSimulation`, which is used by default on macOS.

(On iOS we also use `BouncingScrollSimulation` by default, but with a zero `constantDeceleration`, which doesn't trigger this issue.)

This has two consequences:
* I suspect that the actual macOS behavior this simulation is intended to match does correctly model the physics. If so, then this is a fidelity issue.
* The physically-incorrect behavior is not ballistic/restartable. (For context on that invariant, see #120338.) The excess deceleration compared to the real physics starts at zero and grows over time, so restarting the simulation makes it decelerate less, and go farther in total.
  * This is the inverse of the problem seen with ClampingScrollSimulation / on Android and other non-Apple platforms, in that other issue.

  When the simulation is restarted very frequently, its behavior will closely approximate the real physics. If my suspicion above is correct, then this means frequent restarts actually give this scroll physics nearly the desired behavior, and it's when it *doesn't* get restarted that it behaves incorrectly.


## Steps to Reproduce

I haven't yet tested this out empirically or compared to the native macOS scroll behavior, because I wanted to get this issue written down and posted for comparison with the related issues.

Here's why I say it doesn't correctly model the physics it's described as modeling:

* This class models "a particle affected by fluid drag." If the particle's current velocity is $v$, then one way that could behave is that its acceleration is $-kv$, where $k$ is a drag coefficient.
  * (I think it's typically much steeper than that with real fluids, growing much faster than linearly in the velocity, like $- k v^4$. But let's leave that aside.)
* With `constantDeceleration: 0`, this is indeed what `FrictionSimulation` does:
  * `dx(time)` is `_v * math.pow(_drag, time)`, or $x'(t) = v_0 e^{-kt}$, where $v_0$ is the initial velocity.
  * Taking the derivative, we find $x''(t) = -k v_0 e^{-kt}$.
  * These satisfy $x''(t) = - k x'(t)$.
* With a positive `constantDeceleration`… well, this parameter isn't really documented. But presumably it should do what its name says: it adds a constant amount of deceleration.
* This means we should have $x''(t) = -k x'(t) - c$, where $c$ is `constantDeceleration`.
* But the actual deceleration is greater than that (meaning $x''(t)$ is less than that):
  * Now `dx(time)` is `_v * math.pow(_drag, time) - _constantDeceleration * time`, or $x'(t) = v_0 e^{-kt} - c t$.
  * So $x''(t) = - k v_0 e^{-kt} - c$.
  * But we were supposed to have an acceleration of $-k x'(t) - c$, which, calling it $x''_*$, would be
    
     $x''_*(t) = - k v_0 e^{-kt} + k c t - c$.
  * So the deceleration is too big, by $x''_*(t) - x''(t) = k c t$, i.e. `_constantDeceleration * (-_dragLog) * time`.

---

The crux of the bug is that we've made it so that the effect of `constantDeceleration` is to make the deceleration at time t be a constant amount more than it would otherwise have been *at time t*.

But the physics doesn't care how long the simulation has been running: from a physical perspective, adding a constant deceleration means the deceleration at time t should be a constant amount more than it would otherwise have been *at velocity dx(t)*. (And position x(t), but this simulation isn't affected by position.)

Because over time the velocity and deceleration both decrease, this means that at time t we're starting from the deceleration that a simulation with zero `constantDeceleration` would have had at time t… when that simulation would have been going faster than this one is at time t, and therefore decelerating harder than it will when it later gets down to this one's current speed… which means that we take too large a deceleration as the baseline, and add `constantDeceleration` to that.