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:
irisandClaude Fable 5.1 committed 2026-09-07 16:22:29 -04:00
1 parent 238057ad5e
commit 452c44249f
1 file changed
+132
+132
View File
@@ -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,