iris: a fling starts at Compose's velocity, which is a curve fit and not an average
Iris, from the phone on the 4274b8b build: "flinging now actually works
but is slower than Compose's immediately after releasing the flick (the
slow down seems correct)." The spline was already AOSP's; the initial
velocity was not.
`VelocityTracker` held per-frame pan deltas and answered their sum over
the sample span -- an average, which cannot tell an accelerating flick
from a steady drag. Ported from the `-sources.jar` of
androidx.compose.ui:ui-android:1.12.0 and
androidx.compose.foundation:foundation-android:1.12.0 (the versions the
Compose app builds against) rather than from memory, and the reading
corrected the plan twice:
* The touch path is not `Strategy.Impulse`. `scrollable`/`draggable`
release through the 2D `VelocityTracker`, which on Android is two
`VelocityTracker1D(strategy = Lsq2)` over absolute positions -- a
degree-2 least-squares fit differentiated at the newest sample.
Impulse is reached only by `DifferentialVelocityTracker`, whose one
caller is `NonTouchScrollingLogic`: wheel and trackpad.
* There is no minimum fling velocity. `ViewConfiguration`'s 50dp/s is
used only by `NestedScrollInteropConnection`; `DefaultFlingBehavior`
skips `abs(v) <= 1f`, and says in its own comment that this is to
dodge a NaN out of the spline. So `List::fling` caps at 8000dp/s
against its own density and floors at 1px/s, and no threshold
Compose does not have was added.
So the tracker holds positions rather than deltas (Lsq2 refuses
differential data in Compose too), 20 of them, with Compose's 100ms
horizon and 40ms stopped-gap; `DragGesture` feeds the raw window
coordinate along the drag axis at the press and every `Pan` frame.
`iris/benches/velocity_reference.py` is the independent transcription
the checked-in numbers come from, as `fling_spline_reference.py` is for
the curve. On `flick-120hz.touch`: 11750px/s before, 15250px/s after. On
an accelerating flick -- the shape a real finger makes, which that 16ms
recording is too short to show -- 1080 before, 2445 after. An average
also flings from a standstill (2533px/s where Compose says 0) and flings
from two points that describe no curve.
Negative control: reverting `velocity` to `total / span` fails exactly
seven tests, all of them about the estimator, and leaves the steady
drag, the tap, the selection release, the sixteen arbiter tests and the
rest of phone_screen.rs passing.
`iris drag release:` keeps its info line and gains a debug
`iris drag release samples:` with every held sample as `t_ms:position`,
so a flick that felt wrong on a phone with no logcat can be replayed at
layer 1 or pasted into the reference script.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
452c44249f
commit
84a13e806b
7 files changed
+897
-150
No files matched your search
@@ -8,6 +8,41 @@ capability that moved. Small and trivial changes do not go here.
|
||||
An entry gives the date, what changed, why, and a short before/after where
|
||||
it helps judge the change without the session that made it. Newest first.
|
||||
|
||||
## 2026-09-07: `VelocityTracker` takes positions, not deltas
|
||||
|
||||
A flick released at the wrong speed because the tracker averaged. It now
|
||||
does what Compose's touch scrolling does, and that changes what a caller
|
||||
feeds it.
|
||||
|
||||
// before -- one frame's motion
|
||||
tracker.add_sample(dy, now);
|
||||
// after -- where the finger was
|
||||
tracker.add_position(pos.axis(axis), now);
|
||||
|
||||
`VelocityTracker::velocity` is a port of Compose's `VelocityTracker1D`
|
||||
with `Strategy.Lsq2`: a degree-2 least-squares fit through the last 20
|
||||
positions, differentiated at the newest sample, with Compose's 100ms
|
||||
horizon, 40ms stopped-gap and three-sample minimum. Positions rather than
|
||||
deltas because a fit needs points on a curve -- Compose itself throws on
|
||||
differential data for this strategy.
|
||||
|
||||
Three consequences a caller sees. **A gesture with fewer than three
|
||||
samples answers `0.0`**, where the average answered a number from two;
|
||||
that is Compose's answer too, and on the phone a 120Hz flick delivers
|
||||
four or five. **A finger that rests for more than 40ms before lifting
|
||||
answers `0.0`** rather than flinging at the speed it arrived with.
|
||||
**`add_position` must be called in time order** -- the same debug assert
|
||||
as before, now load-bearing for the fit's x-axis.
|
||||
|
||||
Also new: `VelocityTracker::samples_display` (the held samples as
|
||||
`t_ms:position`, printed by `DragGesture` at debug level so a flick
|
||||
reported from a phone can be replayed), `DragArbiter::axis`, and
|
||||
`sense::MAX_FLING_VELOCITY_DP_S` (8000, `ViewConfiguration`'s own).
|
||||
`List::fling` now applies that maximum against its own density and
|
||||
ignores anything at or under 1px/s, which is Compose's pair of thresholds
|
||||
exactly -- there is deliberately no 50dp/s minimum, because Compose's
|
||||
scrolling never consults the one in `ViewConfiguration`.
|
||||
|
||||
## 2026-09-07: `client-core` carries the app's own log
|
||||
|
||||
Not iris itself but the crate beside it, and it is a new public surface an
|
||||
|
||||
Reference in new issue
Block a user