Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/specs/AST-094-touch-presentations/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ affects_architecture:
affects_families: [family:buttons]
affects_contributing: []
affects_consumer_docs: []
verified_by:
[
packages/core/src/motion/__tests__/animatedLayoutBusy.interaction.browser.spec.ts,
]
---

# Touch presentations feel native system spec
Expand Down Expand Up @@ -1028,6 +1032,7 @@ Motion that FR45 moves, by component. Each component's record carries its own li
| FR4 | Role unit tests over every touch motion | each role at its table value; a theme that writes `motion` | a literal played in place of a role (other than a measured gesture-driven exit spring its component's motion table lists with its platform source), a value that differs from the table at `tempo` 1 and `'standard'`, or a theme that changes a role other than through its motion knobs fails |
| FR4 | Role unit tests over gesture-driven exits | a gesture-driven exit (a release that closes or dismisses) playing a spring measured from the platform reference with no named role; the same unnamed spring played by a program-started motion or by a gesture-driven motion that is not an exit, such as a settle | a gesture-driven exit playing a measured table spring passes; a program-started motion, or a non-exit gesture motion, playing an unnamed spring fails |
| FR5 | Browser proof: an animation keeps advancing frame by frame through a 1 s main-thread block, with no layout or paint per frame | Chromium phone emulation; WebKit | a script-driven or layout-animating implementation freezes or lays out |
| FR5 glide | Browser proof: the animated-layout glide keeps moving through a 700 ms main-thread block, on every frame a compositor control moves | the content after a Collapsible, a TreeList branch and a Table row; verified in Chromium; WebKit by an iOS Simulator spot-check (38/39 frames), not an automated run (evidence gap) | a glide stepped from the main thread freezes |
| FR6 | Unit and browser tests catching an animation mid-flight and retargeting it | catch at peak velocity; retarget with a new spring; a retarget before the browser reports the call's start | a layout read, a jump, or a lost velocity fails |
| FR6 pager | Unit and browser tests of a pager's settle start | releases just under and over 225 pt/s; a catch mid-settle; a programmatic page change mid-settle | a release under the floor whose settle starts moving, a release over it whose settle starts from rest, a catch that loses the on-screen velocity, or a programmatic page change mid-settle that starts from rest fails; a page change during a drag starts from rest |
| FR6 starved | Switch frame proofs on a starved main thread (4 workers on 2 cores); a unit test of a retarget on a settle tail | a second tap mid-travel, with frames read late or its lift handled 100 ms late; a first frame committed late; a retarget once the thumb is still | a jump out of a thumb that is still fails; a mid-travel step within the browser's commit delay is the known limit |
Expand Down
8 changes: 6 additions & 2 deletions internal/interaction-harness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ committed: a pull request links or attaches it.
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `scripts.ts` | Touch scripts as data: `tap`, `drag` (hold, rest), `flick` (exact final-move speed, velocity profiles), `holdThenDrag`, `pinch`, `rotate`, `path`, `merge` (a second finger mid-gesture), `releaseVelocity`, `toTouchEvents`. |
| `touch.ts` | `touchDriver(page).play(script)`: the script as real engine input. `installInputLog` / `readInputLog`: what the page received. |
| `frames.ts` | `startScreencast` (timestamped composited frames), `markElement` (a marker chip or outline), `trackMarker` (marker box per frame), `sampleRects` (per-rAF rects), `frameStats`. |
| `frames.ts` | `startScreencast` (timestamped composited frames), `markElement` (a marker chip or outline), `trackMarker` (marker box per frame), `trackMarkers` (several colors from one decode of each frame), `sampleRects` (per-rAF rects), `frameStats`. |
| `busy.ts` | `blockMainThread(page, ms)`, which returns the window it held and the span its loop measured, and `busyWindowProof(tracked, window)`: frames presented, frames that moved, longest stall, a verdict; `presented` adds the compositor's own frames. |
| `webInput.ts` | `webInputDriver(page)`: `wheel({x, y, dx, dy, ctrl})` and `pen(points)` as real engine input, for proofs across pointers (`spec:AST-100` FR4); `wheel` resolves once the page has the step; read back through the input log. |
| `trace.ts` | `traceRendering(page, body)`: main-thread counts of UpdateLayoutTree / Layout / PrePaint / Paint / Layerize / UpdateLayer / ScrollLayer / HitTest, `countBetween`, long tasks; `installLongTaskLog` / `readLongTasks`; `startCompositorTrace`: when the compositor presents. |
Expand Down Expand Up @@ -244,7 +244,11 @@ a dialog they still wait for the backdrop's. Track a top-layer control from
the same screencast with `trackControl(page, frames)` (`trackMarker` with
`CONTROL_RGB`, searching only the top strip it crosses). A control mounted
`within` a dialog moves with it, so track that one with `trackMarker(page,
frames, {rgb: CONTROL_RGB})`. Pass either as `control`: the component passes when it moved on every frame the control
frames, {rgb: CONTROL_RGB})`, or track the marker and the control together
with `trackMarkers(page, frames, {colors: [MARKER_RGB, CONTROL_RGB]})`, which
decodes each frame once. A proof reads frames up to
`BUSY_PROOF_LOOKAROUND_MS` (250 ms) around its judged part, so a spec may
decode only those. Pass either as `control`: the component passes when it moved on every frame the control
moved and its longest stall is no longer than the control's. Both are read from
the same screencast frames, so a compositor animation misses none. A window in
which the control moved on fewer than four frames fails, since the machine
Expand Down
23 changes: 20 additions & 3 deletions internal/interaction-harness/src/busy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* read from its frames, `pooledBusyProof`, which judges several short
* blocks as one, and `BusyWindowTooLateError`, for a block that began
* after the motion it was meant to catch had ended.
* `BUSY_PROOF_LOOKAROUND_MS` is how far around its judged part a proof
* reads frames.
* `trackControl` and `controlClock` read the control's box and its own
* clock from the screencast, a second control's too (`ahead`).
* @position The busy-main-thread proof: an animation that runs on the
Expand Down Expand Up @@ -864,6 +866,13 @@ function motionStepPx(values: readonly number[]): number {
* `maxMedianFrameMs`, when given, still applies; `minMovedFraction` and the
* absolute stall, frame and motion limits do not.
*/
/**
* How far around its judged part a busy proof reads frames, ms: the rest
* before it and the frame cadence on both sides. A proof that decodes only
* part of a cast keeps the frames this far after the window.
*/
export const BUSY_PROOF_LOOKAROUND_MS = 250;
Comment on lines +869 to +874

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miss (non-blocking): this constant now sits between busyWindowProof's JSDoc (lines 773–868) and the function at line 876. The constant carries the verdict's documentation and busyWindowProof has none. Moving the constant and its comment above line 773, after motionStepPx, keeps both documented.


export function busyWindowProof(
tracked: readonly TrackedFrame[],
window: BusyWindow,
Expand Down Expand Up @@ -1047,7 +1056,7 @@ export function busyWindowProof(
const prior = tracked.filter(f => f.box != null && f.t < inner.from);
let leadFrom = 0;
prior.forEach((f, i) => {
if (f.t < inner.from - 250) {
if (f.t < inner.from - BUSY_PROOF_LOOKAROUND_MS) {
leadFrom = i;
}
});
Expand Down Expand Up @@ -1104,7 +1113,11 @@ export function busyWindowProof(
const changes = inWindow.filter(t => t > judged.from || onScreenFrom == null);
const edges = [judged.from, ...changes, judged.to];
const around = tracked
.filter(f => f.t >= inner.from - 250 && f.t <= inner.to + 250)
.filter(
f =>
f.t >= inner.from - BUSY_PROOF_LOOKAROUND_MS &&
f.t <= inner.to + BUSY_PROOF_LOOKAROUND_MS,
)
.map(f => f.t);
const intervals = around
.slice(1)
Expand All @@ -1121,7 +1134,11 @@ export function busyWindowProof(
// The compositor's own frames, when recorded: its interval, from the frames
// around the judged part.
const draws = [...(presented ?? [])]
.filter(t => t >= judged.from - 250 && t <= judged.to + 250)
.filter(
t =>
t >= judged.from - BUSY_PROOF_LOOKAROUND_MS &&
t <= judged.to + BUSY_PROOF_LOOKAROUND_MS,
)
.sort((a, b) => a - b);
const drawIntervals = draws
.slice(1)
Expand Down
Loading
Loading