docs/RUST.md: queue -- logging landed; iris app enrolment replaces the build-time log destination; build-apk.sh traps
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
238057ad5e
commit
452c44249f
1 file changed
+132
+132
@@ -487,6 +487,27 @@ closes it.
|
||||
- [ ] Scroll clamped at both ends, and Compose's impulse velocity
|
||||
estimator with min/max fling velocity (docs/IRIS_TODO.md, 2026-09-07
|
||||
later). After the culling fix lands (same file).
|
||||
- [ ] Iris app enrolment (decided 2026-09-07): the bench APK's log
|
||||
destination is baked from `AI_APP_LOG_*` at build time, which cannot
|
||||
work for Iris's phone -- the APK is built in the VM, whose CA and
|
||||
token are not the host's, and no secret may go in a repo or a
|
||||
delivered artifact. Replace with the mechanism the Compose app and
|
||||
`desktop-app` already use: the iris Android app registers the
|
||||
`aiapp://enroll?host&port&token` VIEW intent, stores the enrolment
|
||||
the way `client_core::config` does, pins the CA the same way (the
|
||||
Compose app reads it at build time from the *building* machine, which
|
||||
is the same problem -- so for the phone the CA has to arrive with the
|
||||
link or the build has to happen on the host; check how Dev Updater's
|
||||
Enroll button and `ai-server --enroll-link` present the link and
|
||||
whether the CA fingerprint can ride in it as a query parameter, which
|
||||
`wg-app-link` may already support). `log_upload` then takes its
|
||||
destination from the enrolment at runtime, `AI_APP_LOG_*` is removed,
|
||||
and Dev Updater's enrol screen offers the link as a tappable link so
|
||||
Android hands it to the iris app. Rejected: building the iris APK on
|
||||
the host (needs cargo-ndk and an NDK there, and still bakes a token).
|
||||
- [ ] `iris/android-app/build-apk.sh`: clear Gradle's merged-native-libs
|
||||
cache when the ABI changes (the x86_64 trap), and make the debug bench
|
||||
APK installable (648 MB) -- RUST.md's logging section names both.
|
||||
- [ ] Input-event and timing instrumentation into the log ring, copied
|
||||
by the report button. After the logging route lands (same ring).
|
||||
- [ ] Masks with a shape -- docs/LAYOUT.md "Masks with a shape (decided
|
||||
@@ -744,6 +765,117 @@ the targetSdk reading was wrong and the window is still being resized.
|
||||
different fault from either. For the fling, a flick should now visibly
|
||||
slow before it stops rather than running out at speed.
|
||||
|
||||
### The fling started too slow: Compose fits a curve, iris averaged (2026-09-07)
|
||||
|
||||
Iris 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 curve was right -- that was the previous
|
||||
fix -- so the wrong number was the *initial velocity*.
|
||||
|
||||
**What iris did.** `VelocityTracker` held the last 100ms of per-frame
|
||||
pan *deltas* and answered their sum over the span between the oldest and
|
||||
newest: an average. An average cannot tell an accelerating flick from a
|
||||
steady drag, and a flick is by definition accelerating, so every fling
|
||||
started at roughly the speed of the middle of the gesture rather than
|
||||
the speed at the release. Every test it had asserted the average's own
|
||||
definition back at it, which is the same shape of self-grading the
|
||||
spline shipped a straight line through.
|
||||
|
||||
**What Compose does, which is not what it is remembered as.** Read out
|
||||
of the `-sources.jar` of `androidx.compose.ui:ui-android:1.12.0` and
|
||||
`androidx.compose.foundation:foundation-android:1.12.0` on
|
||||
dl.google.com -- the versions `app/gradle/libs.versions.toml` builds the
|
||||
Compose app against, which is the app being compared with:
|
||||
|
||||
- `scrollable`/`draggable` release through `DragGestureNode.
|
||||
sendDragStopped`, which calls the **2D `VelocityTracker`**. On Android
|
||||
that delegates to `Lsq2VelocityTracker` -- two
|
||||
`VelocityTracker1D(strategy = Lsq2)` over **absolute positions**,
|
||||
fitting a degree-2 polynomial by least squares (`polyFitLeastSquares`,
|
||||
Gram-Schmidt QR) and taking its derivative at the newest sample.
|
||||
- **`Strategy.Impulse` is not on the touch path.** Its only route in is
|
||||
`DifferentialVelocityTracker`, whose only caller is
|
||||
`NonTouchScrollingLogic`: mouse wheel and trackpad.
|
||||
`AndroidComposeUiFlags.isFrameworkVelocityTrackerEnabled`, which would
|
||||
swap in the platform's own (impulse) tracker, defaults to `false`.
|
||||
This was the surprise of the port, and the reason to read rather than
|
||||
remember -- the plan for this task named Impulse.
|
||||
- Constants: `HistorySize = 20`, `HorizonMilliseconds = 100`,
|
||||
`AssumePointerMoveStoppedMilliseconds = 40`, `minSampleSize = 3` for
|
||||
Lsq2. The walk back from the newest sample stops at the first sample
|
||||
older than the horizon **or** separated from its neighbour by more
|
||||
than the stopped gap.
|
||||
- **Which samples.** `sendDragStart` adds the DOWN change; every
|
||||
subsequent MOVE, historical samples included, is added by
|
||||
`sendDragEvent`. The **UP position is never added** --
|
||||
`Lsq2VelocityTracker.addPointerInputChange` wraps its `addPosition`
|
||||
calls in `if (!event.changedToUpIgnoreConsumed())`, and the UP branch
|
||||
only resets the tracker when 40ms have passed since the last MOVE
|
||||
(b/238654963). So a finger that comes to rest before lifting reads as
|
||||
a stop, not as a decelerating tail.
|
||||
|
||||
**The clamps, both checked rather than assumed.** Maximum:
|
||||
`ViewConfiguration.getScaledMaximumFlingVelocity()`, 8000 dp/s, passed
|
||||
into `calculateVelocity(maximumVelocity)` at the release -- 20400px/s at
|
||||
the phone's density 2.55, which this flick does not reach. Minimum:
|
||||
**there is none on the fling path.** `ViewConfiguration.
|
||||
minimumFlingVelocity` (AOSP's 50 dp/s) exists in Compose's
|
||||
`ViewConfiguration` interface, but its only use in either artifact is
|
||||
`NestedScrollInteropConnection`, for View interop.
|
||||
`DefaultFlingBehavior.performFling` guards with
|
||||
`abs(initialVelocity) > 1f` and says why in its own comment ("we need it
|
||||
since spline curve gives us NaNs"). So iris applies 1px/s, not 50dp/s: a
|
||||
50dp/s floor would swallow slow deliberate releases that Compose flings.
|
||||
Both live in `List::fling`, which is the only place that knows the
|
||||
density the dp figure has to be multiplied by.
|
||||
|
||||
**What changed.** `VelocityTracker` holds **positions**, not deltas
|
||||
(Lsq2 refuses differential data in Compose too), capped at 20 samples;
|
||||
`add_sample(delta, at)` is now `add_position(position, at)`, and
|
||||
`DragGesture` feeds the raw window coordinate along the drag axis at the
|
||||
press and at every `Pan` frame -- the same set Compose feeds, minus the
|
||||
one MOVE that crosses the touch slop, which Compose drops only because
|
||||
`sendDragStart` happens to add just the DOWN. `poly_fit_least_squares`
|
||||
is Compose's `polyFitLeastSquares` on fixed-size arrays.
|
||||
|
||||
**`iris/benches/velocity_reference.py`** is the independent
|
||||
transcription, same role as `fling_spline_reference.py`, and prints
|
||||
every number the tests assert. On the phone's own recording
|
||||
(`transcript-fixture/touch/flick-120hz.touch`, five samples in 16ms):
|
||||
|
||||
| sample set | shipped (average) | Compose (Lsq2) |
|
||||
| --- | --- | --- |
|
||||
| `flick-120hz.touch` | 11750 px/s | **15250 px/s** |
|
||||
| steady drag, 5px/10ms | 500 px/s | 500 px/s |
|
||||
| accelerating flick, deltas doubling | 1080 px/s | **2445 px/s** |
|
||||
| old fast burst then 1px/10ms | 9182 px/s | 100 px/s |
|
||||
| stopped 48ms, then released | 2533 px/s | 0 px/s |
|
||||
| press + one move frame | 11750 px/s | 0 px/s |
|
||||
|
||||
The recording understates the change (1.30x) because it is only 16ms
|
||||
long; the accelerating set (2.26x) is the shape of a real finger flick
|
||||
and is what Iris was feeling. The last three rows are the cases an
|
||||
average gets not just low but *wrong*: it flings from a standstill, and
|
||||
it flings from two points that describe no curve.
|
||||
|
||||
**Tests.** Eight in `sense::velocity_tracker_tests`, two rewritten in
|
||||
`sense::drag_gesture_tests` (three samples is the fewest that can fling;
|
||||
one move frame answers 0, as Compose does), and `phone_screen.rs` now
|
||||
asserts -15250px/s rather than "more than 1000". **Negative control
|
||||
run**: with `velocity` reverted to `total / span`, exactly seven fail --
|
||||
the flick recording, the accelerating flick, the horizon, the stopped
|
||||
finger, the minimum sample count, both `drag_gesture` flick tests -- and
|
||||
the steady drag, the tap, the selection release, all sixteen arbiter
|
||||
tests and the whole of `phone_screen.rs` bar the flick pass unchanged.
|
||||
That is the half the change had no reason to touch.
|
||||
|
||||
**`iris drag release:` keeps its info line and gains a debug one**,
|
||||
`iris drag release samples:`, printing every held sample as
|
||||
`t_ms:position` relative to the first. Iris has no logcat, so that is
|
||||
the only way a flick that felt wrong on her screen becomes something
|
||||
replayable: paste it into a `touch/*.touch` file for layer 1, or
|
||||
straight into `velocity_reference.py`.
|
||||
|
||||
### The 22:16 phone report, worked 2026-09-06/07
|
||||
|
||||
Iris's four items are listed in docs/IRIS_TODO.md's "From the phone,
|
||||
|
||||
Reference in new issue
Block a user