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
|
- [ ] Scroll clamped at both ends, and Compose's impulse velocity
|
||||||
estimator with min/max fling velocity (docs/IRIS_TODO.md, 2026-09-07
|
estimator with min/max fling velocity (docs/IRIS_TODO.md, 2026-09-07
|
||||||
later). After the culling fix lands (same file).
|
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
|
- [ ] Input-event and timing instrumentation into the log ring, copied
|
||||||
by the report button. After the logging route lands (same ring).
|
by the report button. After the logging route lands (same ring).
|
||||||
- [ ] Masks with a shape -- docs/LAYOUT.md "Masks with a shape (decided
|
- [ ] 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
|
different fault from either. For the fling, a flick should now visibly
|
||||||
slow before it stops rather than running out at speed.
|
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
|
### 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,
|
Iris's four items are listed in docs/IRIS_TODO.md's "From the phone,
|
||||||
|
|||||||
Reference in new issue
Block a user