clearcote 0.48.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/isolated.js CHANGED
@@ -23,13 +23,43 @@ const resultValue = (r) => {
23
23
  /** @internal exported for tests. DOM.getNodeForLocation hit-tests in DOCUMENT coordinates (the
24
24
  * viewport point plus the scroll offset); Playwright's boxes, CDP's box model and the mouse are all
25
25
  * VIEWPORT coordinates. Measured on r37: a field scrolled 547 px down answered "No node found" at its
26
- * viewport centre and the input itself at centre + scroll. */
26
+ * viewport centre and the input itself at centre + scroll.
27
+ *
28
+ * No ignorePointerEventsNone: in CDP `true` means "hit-test pointer-events:none elements TOO", so a
29
+ * decorative layer with pointer-events:none over a control was returned instead of the control and
30
+ * every form route fell back to stock Playwright (SC 209: 6/6 untrusted change events). The default
31
+ * (false) skips such layers, exactly as a real press does. */
27
32
  export function nodeAtParams(x, y, scroll) {
28
33
  const [sx, sy] = scroll ?? [0, 0];
29
- return { x: roundHalfEven(x + sx), y: roundHalfEven(y + sy), includeUserAgentShadowDOM: false, ignorePointerEventsNone: true };
34
+ return { x: roundHalfEven(x + sx), y: roundHalfEven(y + sy), includeUserAgentShadowDOM: false };
35
+ }
36
+ /** @internal exported for tests. The frame directly under `main` that contains `frameId` (itself for a
37
+ * direct child), from a Page.getFrameTree answer; null when it is not below the main frame. */
38
+ export function topChildFrame(tree, frameId, main) {
39
+ const parents = new Map();
40
+ const walk = (t, parent) => {
41
+ const id = t?.frame?.id;
42
+ if (!id)
43
+ return;
44
+ parents.set(id, parent);
45
+ for (const c of t.childFrames ?? [])
46
+ walk(c, id);
47
+ };
48
+ walk(tree, null);
49
+ let fid = frameId;
50
+ for (let n = 0; n < 64; n++) {
51
+ const up = parents.get(fid);
52
+ if (up === undefined || up === null)
53
+ return null;
54
+ if (up === main)
55
+ return fid;
56
+ fid = up;
57
+ }
58
+ return null;
30
59
  }
31
60
  /** Python's round(): half to even (60.5 -> 60), so the hit test picks the same pixel in every SDK. */
32
- function roundHalfEven(v) {
61
+ /** @internal Python's round(): half to even (the pixel DOM.getNodeForLocation is asked about). */
62
+ export function roundHalfEven(v) {
33
63
  const f = Math.floor(v), d = v - f;
34
64
  if (d > 0.5)
35
65
  return f + 1;
@@ -111,8 +141,9 @@ export class IsolatedWorld {
111
141
  const s = await this.evaluate(ISO_SCROLL);
112
142
  return Array.isArray(s) && s.length === 2 ? [Number(s[0]), Number(s[1])] : [0, 0];
113
143
  }
114
- /** backendNodeId of the MAIN-FRAME element at viewport point (x, y), or undefined. `scroll`: the
115
- * page's [scrollX, scrollY] if the caller already has it (read here otherwise).
144
+ /** backendNodeId of the MAIN-FRAME element at viewport point (x, y) -- for a point inside a child
145
+ * frame, the <iframe> element that holds it --, or undefined. `scroll`: the page's [scrollX, scrollY]
146
+ * if the caller already has it (read here otherwise).
116
147
  *
117
148
  * CDP's hit test (DOM.getNodeForLocation) runs in the renderer with no script at all, so this is how
118
149
  * humanize finds the element a Playwright locator means -- getByRole, getByLabel, `>> nth=`,
@@ -129,9 +160,22 @@ export class IsolatedWorld {
129
160
  if (!hit || hit.backendNodeId === undefined || hit.backendNodeId === null)
130
161
  return undefined;
131
162
  if (hit.frameId && this.frame && hit.frameId !== this.frame)
132
- return undefined; // inside an iframe
163
+ return this.ownerInMain(hit.frameId);
133
164
  return hit.backendNodeId;
134
165
  }
166
+ /** A point inside a child frame: the <iframe> element in the MAIN document that holds it (through
167
+ * every nesting level), or undefined. To the main document a press there lands on that element: a
168
+ * control the iframe covers is covered, a target that is (or contains) the iframe is reached. This
169
+ * replaced the only Playwright trial humanize still ran (it moved the pointer and pressed, and page
170
+ * listeners registered first saw it). */
171
+ async ownerInMain(frameId) {
172
+ const tree = await this.send("Page.getFrameTree");
173
+ const top = this.frame ? topChildFrame(tree?.frameTree, frameId, this.frame) : null;
174
+ if (!top)
175
+ return undefined;
176
+ const own = await this.send("DOM.getFrameOwner", { frameId: top });
177
+ return own && typeof own.backendNodeId === "number" ? own.backendNodeId : undefined;
178
+ }
135
179
  /** Run `fnSource` -- a `function (arg) { ... }` whose `this` is the node -- in the isolated world and
136
180
  * resolve to its JSON value, or undefined. */
137
181
  async callOnNode(backendNodeId, fnSource, arg) {
@@ -175,10 +219,11 @@ export function worldFor(page) {
175
219
  }
176
220
  return w;
177
221
  }
178
- /** How long a trial action may look for a covered click point before the native path takes over. */
222
+ /** How long a covered click point is re-checked (an overlay fading out) before the native path takes over. */
179
223
  export const COVER_CHECK_MS = 400;
180
- /** The one longer look when that trial timed out only because the element was not "stable" yet (no
181
- * "intercepts pointer events" in Playwright's call log): slow first frames, not an overlay. */
224
+ /** The timeout of the utility-world value reads that prove a fill landed. (It was also the one longer
225
+ * look of the Playwright trial that checked a point inside a child frame; no Playwright trial runs in
226
+ * humanized mode any more: such a point is answered by its <iframe> element, see nodeAt.) */
182
227
  export const COVER_RETRY_MS = 2000;
183
228
  // The reads humanize makes, as functions of one JSON argument (run by IsolatedWorld.evaluate).
184
229
  export const ISO_VIEWPORT = "() => [innerWidth, innerHeight]";
@@ -230,20 +275,59 @@ export const ISO_DESCRIBE = `function (a) {
230
275
  return o; }`;
231
276
  // The controls a locator can mean for the form routes.
232
277
  export const ISO_FORM_CONTROL = "input, textarea, select, [contenteditable=''], [contenteditable='true'], [contenteditable='plaintext-only']";
233
- // Does the element the browser's hit test found at a click point belong to the element whose box the
234
- // caller holds -- the hit element itself, or an ancestor (across open shadow roots) that has exactly
235
- // that box? `this` = the hit node (DOM.getNodeForLocation, pointer-events:none skipped, as for a real
236
- // press). The covered-point check without a Playwright trial: a trial MOVES the real pointer onto the
237
- // element and back and performs a press whose events Playwright blocks, so a page saw a mousemove
238
- // inside the target, a jump back and a pointerdown/click before every humanized click (verifier B).
278
+ // Does a press at a click point reach the element the caller holds? `this` = the node the browser's
279
+ // hit test found there (DOM.getNodeForLocation, pointer-events:none skipped, as for a real press; for a
280
+ // point inside a child frame, the <iframe> element that holds it); `b` = the element's box, plus `sel`
281
+ // when the caller has a plain-CSS selector for it.
282
+ //
283
+ // With `sel` (its first match, which must still fill `b`), the answer is exact, as Playwright's own
284
+ // hit-target check decides it: the hit is the element or inside it (across open shadow roots), or inside
285
+ // a <label> whose control it is (the label passes the press on). Anything else on top -- an overlay
286
+ // with exactly the element's box, a badge -- covers it.
287
+ //
288
+ // Without one, by the box: the hit element itself, or an ancestor that has exactly that box, with no
289
+ // OTHER element of that same box beside the hit's path inside it that the press would miss. That twin
290
+ // is a wrapper's control under an overlay (<span class=wrap><input><span overlay></span></span>: the
291
+ // wrap has the input's box, the press lands on the overlay). A twin the press reaches anyway, or that no
292
+ // press could be meant for, does not count (SC 210 fix 2; counting every twin made a MUI-style button
293
+ // "covered" and sent its click to Playwright): one that takes no pointer events (pointer-events:none,
294
+ // not visibility:visible -- a ripple or focus-ring span, an icon layer), a control whose <label> holds
295
+ // the hit (a custom checkbox), and, inside an element that is itself a control, a decorative twin with
296
+ // no control in it (a background span under a button's text).
297
+ //
298
+ // The covered-point check without a Playwright trial: a trial MOVES the real pointer onto the element
299
+ // and back and performs a press whose events Playwright blocks, so a page saw a mousemove inside the
300
+ // target, a jump back and a pointerdown/click before every humanized click (verifier B).
239
301
  export const ISO_HIT_IN_BOX = `function (b) {
240
- let e = this.nodeType === 1 ? this : this.parentElement;
241
- for (let n = 0; e && n < 128; n++) {
242
- const r = e.getBoundingClientRect();
243
- if (Math.abs(r.x - b.x) <= 1.5 && Math.abs(r.y - b.y) <= 1.5 &&
244
- Math.abs(r.width - b.width) <= 1.5 && Math.abs(r.height - b.height) <= 1.5) return true;
245
- const p = e.parentNode;
246
- e = e.parentElement || (p && p.nodeType === 11 && p.host) || null;
302
+ const same = r => Math.abs(r.x - b.x) <= 1.5 && Math.abs(r.y - b.y) <= 1.5 &&
303
+ Math.abs(r.width - b.width) <= 1.5 && Math.abs(r.height - b.height) <= 1.5;
304
+ const up = n => { const p = n.parentNode; return n.parentElement || (p && p.nodeType === 11 && p.host) || null; };
305
+ const h = this.nodeType === 1 ? this : this.parentElement;
306
+ let t = null;
307
+ if (b.sel) { try { t = document.querySelector(b.sel); } catch (x) { t = null; } }
308
+ if (t && same(t.getBoundingClientRect())) {
309
+ for (let e = h, n = 0; e && n < 128; e = up(e), n++)
310
+ if (e === t || (e.tagName === 'LABEL' && e.control === t)) return true;
311
+ return false;
312
+ }
313
+ const CTL = 'a[href], area[href], button, input, select, textarea, label, summary, iframe, [contenteditable], ' +
314
+ '[tabindex], [role=button], [role=link], [role=checkbox], [role=radio], [role=switch], [role=tab], ' +
315
+ '[role=menuitem], [role=option], [role=combobox], [role=textbox]';
316
+ const takes = d => { const s = getComputedStyle(d); return s.pointerEvents !== 'none' && s.visibility === 'visible'; };
317
+ const path = new Set();
318
+ for (let e = h, n = 0; e && n < 128; e = up(e), n++) {
319
+ path.add(e);
320
+ if (same(e.getBoundingClientRect())) {
321
+ const all = e.getElementsByTagName('*'), ctl = e.matches(CTL);
322
+ if (all.length <= 4000)
323
+ for (const d of all) {
324
+ if (path.has(d) || !same(d.getBoundingClientRect()) || !takes(d)) continue;
325
+ if (d.labels && Array.prototype.some.call(d.labels, l => l.contains(h))) continue;
326
+ if (ctl && !d.matches(CTL) && !d.querySelector(CTL)) continue;
327
+ return false;
328
+ }
329
+ return true;
330
+ }
247
331
  }
248
332
  return false; }`;
249
333
  // A list box's scroll state and one row's box, from any node inside the <select> (`this`): where its
@@ -1 +1 @@
1
- {"version":3,"file":"isolated.js","sourceRoot":"","sources":["../src/isolated.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"isolated.js","sourceRoot":"","sources":["../src/isolated.ts"],"names":[],"mappings":"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"}
package/dist/motion.d.ts CHANGED
@@ -90,18 +90,59 @@ export interface PlanOpts {
90
90
  /** Target width/height (px) for the Fitts duration + click-point spread. Defaults ~24. */
91
91
  targetW?: number;
92
92
  targetH?: number;
93
- /** Append a tiny 1–2 px settling jiggle at the end (used for slider/drag seating). */
93
+ /** A drag that seats a handle: planned by planDrag, ending in a couple of sub-pixel seating jiggles. */
94
94
  settle?: boolean;
95
+ /** The button is held (a drag leg): planned by planDrag, landing exactly on `to`. */
96
+ held?: boolean;
95
97
  /** RNG for live micro-variation. Defaults to Math.random (non-reproducible). Pass a seeded Rng in tests. */
96
98
  rng?: Rng;
97
99
  }
98
100
  /** Minimum-jerk easing s(τ)=10τ³−15τ⁴+6τ⁵ (position fraction; speed 30τ²(1−τ)²). */
99
101
  export declare function minJerk(tau: number): number;
102
+ /** Per-identity path-shape traits, stable per seed (see shapeTraits). */
103
+ export interface ShapeTraits {
104
+ /** Share of aimed moves that pass the target and come back, 0.15–0.38. */
105
+ overshootRate: number;
106
+ /** Scale on the primary submovement's bow, 0.75–1.3. */
107
+ curve: number;
108
+ /** Scale on held-drag duration, 0.85–1.2. */
109
+ dragTempo: number;
110
+ }
111
+ /** The persona's path-shape traits, drawn from a salted stream of its seed.
112
+ *
113
+ * Measured on r38 / SDK 0.48.0 (SC 209): 1 of 36 planned approaches passed the target, against 15–40 %
114
+ * for real reaches and 14/36 for the engine's recorded trajectories, because the primary submovement
115
+ * always aimed at 84–94 % of the distance. Same values as Python `_motion.shape_traits` and .NET
116
+ * `Motion.ShapeTraitsOf`. */
117
+ export declare function shapeTraits(p: Persona): ShapeTraits;
100
118
  /**
101
119
  * Plan a free cursor move `from`→`to` as a sum of minimum-jerk submovements (primary + corrections)
102
120
  * with Fitts-scaled duration and colored noise. Returns the samples to dispatch.
121
+ *
122
+ * A move with a button held (`held`) or one that seats a handle (`settle`) is a drag, planned by
123
+ * {@link planDrag}.
124
+ *
125
+ * Shape (SC 209/210): the primary submovement arcs (peak sideways deviation ~6–18 % of the distance,
126
+ * per-persona curvature and handedness), reaches its speed peak before the middle of the move, and
127
+ * either stops short of the target (a short corrective submovement closes the gap) or, in
128
+ * `shapeTraits(p).overshootRate` of moves, passes it by 3–28 px and comes back. Straightness (distance /
129
+ * path length) of the planned points lands around 0.85–0.95, the band of real reaches.
103
130
  */
104
131
  export declare function planMove(from: Point, to: Point, p: Persona, opts?: PlanOpts): Step[];
132
+ /**
133
+ * Plan a held-button drag `from`→`to` (slider handle, drag-and-drop, range thumb).
134
+ *
135
+ * Measured on r38 / SDK 0.48.0 (SC 209): the held leg reused the aimed-move planner, whose arc put the
136
+ * hand up to 47 px off the line between grab and drop — off a 40 px slider track. A drag is a slower,
137
+ * flatter movement: one skewed min-jerk leg along the line with at most ~5 px of arc and ±6 px of total
138
+ * sideways error, 0.5–2 s for 300–400 px (`shapeTraits(p).dragTempo`), and now and then (about 0.6× the
139
+ * persona's overshoot rate) a 2.5–10 px overshoot and pull-back. `settle` adds a couple of sub-pixel
140
+ * seating jiggles before landing exactly on `to`.
141
+ */
142
+ export declare function planDrag(from: Point, to: Point, p: Persona, opts?: {
143
+ settle?: boolean;
144
+ rng?: Rng;
145
+ }): Step[];
105
146
  /** Grab/release dwell times (ms) for a held-button drag, drawn from the persona window. */
106
147
  export declare function dragDwell(p: Persona, rng?: Rng): {
107
148
  grabMs: number;
@@ -1 +1 @@
1
- {"version":3,"file":"motion.d.ts","sourceRoot":"","sources":["../src/motion.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"motion.d.ts","sourceRoot":"","sources":["../src/motion.ts"],"names":[],"mappings":"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"}
package/dist/motion.js CHANGED
@@ -152,28 +152,60 @@ export function minJerk(tau) {
152
152
  const t = clamp(tau, 0, 1);
153
153
  return t * t * t * (10 + t * (-15 + 6 * t));
154
154
  }
155
- /** Sample ONE min-jerk submovement a→b along a slightly bowed path, adding colored noise. Appends to
156
- * `out`. `t0` is the absolute clock (ms) at the start (for tremor phase continuity). Returns the
157
- * absolute clock at the end. `land` = put the final sample exactly on b with no noise. */
158
- function sampleSubmove(out, a, b, durMs, p, rng, t0, drift, jit, tremorPhase, land) {
155
+ // Salt for the path-shape stream. Like the rollover salt, a separate mulberry32(seed ^ salt) stream
156
+ // keeps makePersona's draw ORDER (and so every existing seed's motor identity) unchanged.
157
+ const SHAPE_SALT = 0x85ebca6b;
158
+ /** The persona's path-shape traits, drawn from a salted stream of its seed.
159
+ *
160
+ * Measured on r38 / SDK 0.48.0 (SC 209): 1 of 36 planned approaches passed the target, against 15–40 %
161
+ * for real reaches and 14/36 for the engine's recorded trajectories, because the primary submovement
162
+ * always aimed at 84–94 % of the distance. Same values as Python `_motion.shape_traits` and .NET
163
+ * `Motion.ShapeTraitsOf`. */
164
+ export function shapeTraits(p) {
165
+ const r = mulberry32((p.seed ^ SHAPE_SALT) >>> 0);
166
+ const overshootRate = lerp(0.15, 0.38, r());
167
+ const curve = lerp(0.75, 1.3, r());
168
+ const dragTempo = lerp(0.85, 1.2, r());
169
+ return { overshootRate, curve, dragTempo };
170
+ }
171
+ // Sub-pixel noise scale. The colored noise (OU drift + 8–12 Hz tremor + AR(1) jitter) stays, but smaller
172
+ // and carried mostly while the hand is moving: a mouse resting on the pad reports no counts, so a slow
173
+ // tail full of ±0.5 px wobble is what a quantizing page turns into 1 px zigzags.
174
+ const NOISE = 0.6;
175
+ /** floor(x + 0.5): the same rounding as Python `_js_round` and .NET `JsRound`. */
176
+ const jsRound = (x) => Math.floor(x + 0.5);
177
+ /** Time warp 1−(1−τ)^skew: skew > 1 moves the min-jerk speed peak before the midpoint. */
178
+ function skewed(tau, skew) {
179
+ const t = clamp(tau, 0, 1);
180
+ return skew !== 1 ? 1 - Math.pow(1 - t, skew) : t;
181
+ }
182
+ /** Sample ONE min-jerk submovement a→b along a bowed path, adding colored noise. Appends to `out`.
183
+ * `t0` is the absolute clock (ms) at the start (for tremor phase continuity). Returns the absolute
184
+ * clock at the end. `land` = put the final sample exactly on b with no noise. `bowFrac` = signed peak
185
+ * sideways deviation as a share of the submovement's length; `skew` = time warp (1 = symmetric). */
186
+ function sampleSubmove(out, a, b, durMs, p, rng, t0, drift, jit, tremorPhase, land, bowFrac, skew) {
159
187
  const dx = b.x - a.x, dy = b.y - a.y;
160
188
  const dist = Math.hypot(dx, dy) || 1e-6;
161
189
  const stepMs = 1000 / p.deviceHz;
162
190
  // Cap the sample count so a very long move can't emit thousands of steps (defends the Fitts cap).
163
- const n = Math.min(180, Math.max(4, Math.round(durMs / stepMs)));
164
- // perpendicular unit for the path bow; sign follows the persona's handedness bias
191
+ const n = Math.min(180, Math.max(4, jsRound(durMs / stepMs)));
165
192
  const nx = -dy / dist, ny = dx / dist;
166
- const bowSign = p.approachBias >= 0 ? 1 : -1;
167
- const bow = bowSign * (0.04 + Math.abs(gaussFrom(rng)) * 0.05) * Math.min(dist, 260) * (0.5 + Math.abs(p.approachBias));
168
- const c1x = a.x + dx * 0.33 + nx * bow, c1y = a.y + dy * 0.33 + ny * bow;
169
- const c2x = a.x + dx * 0.66 + nx * bow, c2y = a.y + dy * 0.66 + ny * bow;
193
+ // A cubic with both control points offset by c peaks at 0.75 c; the first control point carries more
194
+ // of it, so the widest point of the arc comes before the middle, as in a reach.
195
+ const c = bowFrac * dist / 0.75;
196
+ const c1x = a.x + dx * 0.3 + nx * c * 1.15, c1y = a.y + dy * 0.3 + ny * c * 1.15;
197
+ const c2x = a.x + dx * 0.7 + nx * c * 0.85, c2y = a.y + dy * 0.7 + ny * c * 0.85;
170
198
  let t = t0;
171
199
  for (let i = 1; i <= n; i++) {
172
- const e = minJerk(i / n);
200
+ const tau = i / n;
201
+ const e = minJerk(skewed(tau, skew));
173
202
  let px = bez(a.x, c1x, c2x, b.x, e);
174
203
  let py = bez(a.y, c1y, c2y, b.y, e);
175
204
  const last = i === n;
176
205
  if (!(last && land)) {
206
+ // noise follows the speed envelope (min-jerk speed 30τ²(1−τ)² peaks at 1.875)
207
+ const v = 30 * tau * tau * (1 - tau) * (1 - tau) / 1.875;
208
+ const g = NOISE * (0.35 + 0.65 * v);
177
209
  // Ornstein–Uhlenbeck drift (colored, low-frequency)
178
210
  const k = stepMs / p.driftTau;
179
211
  drift.x += -k * drift.x + Math.sqrt(2 * k) * p.driftAmp * gaussFrom(rng);
@@ -188,8 +220,8 @@ function sampleSubmove(out, a, b, durMs, p, rng, t0, drift, jit, tremorPhase, la
188
220
  // speed reversal every sample, manufacturing spurious "submovements" and a flat noise spectrum.
189
221
  jit.x = 0.7 * jit.x + gaussFrom(rng) * p.jitter * 0.22;
190
222
  jit.y = 0.7 * jit.y + gaussFrom(rng) * p.jitter * 0.22;
191
- px += drift.x + trX + jit.x;
192
- py += drift.y + trY + jit.y;
223
+ px += (drift.x + trX + jit.x) * g;
224
+ py += (drift.y + trY + jit.y) * g;
193
225
  }
194
226
  else {
195
227
  px = b.x;
@@ -206,8 +238,19 @@ function sampleSubmove(out, a, b, durMs, p, rng, t0, drift, jit, tremorPhase, la
206
238
  /**
207
239
  * Plan a free cursor move `from`→`to` as a sum of minimum-jerk submovements (primary + corrections)
208
240
  * with Fitts-scaled duration and colored noise. Returns the samples to dispatch.
241
+ *
242
+ * A move with a button held (`held`) or one that seats a handle (`settle`) is a drag, planned by
243
+ * {@link planDrag}.
244
+ *
245
+ * Shape (SC 209/210): the primary submovement arcs (peak sideways deviation ~6–18 % of the distance,
246
+ * per-persona curvature and handedness), reaches its speed peak before the middle of the move, and
247
+ * either stops short of the target (a short corrective submovement closes the gap) or, in
248
+ * `shapeTraits(p).overshootRate` of moves, passes it by 3–28 px and comes back. Straightness (distance /
249
+ * path length) of the planned points lands around 0.85–0.95, the band of real reaches.
209
250
  */
210
251
  export function planMove(from, to, p, opts = {}) {
252
+ if (opts.settle || opts.held)
253
+ return planDrag(from, to, p, { settle: opts.settle, rng: opts.rng });
211
254
  const rng = opts.rng ?? Math.random;
212
255
  const W = Math.max(6, opts.targetW ?? 24);
213
256
  const D = Math.hypot(to.x - from.x, to.y - from.y);
@@ -222,45 +265,49 @@ export function planMove(from, to, p, opts = {}) {
222
265
  const id = log2(D / W + 1);
223
266
  const mtLo = Math.min(1700, Math.max(70, 40 + D * 0.6));
224
267
  const mt = clamp((p.fittsA + p.fittsB * id) * (0.85 + 0.3 * rng()), mtLo, 1700);
225
- // Submovement targets: primary lands near `to` with a small over/undershoot; corrections close the
226
- // residual error, each covering ~55–80% of what's left.
268
+ const tr = shapeTraits(p);
269
+ const ux = (to.x - from.x) / D, uy = (to.y - from.y) / D;
270
+ // Submovement targets: the primary stops a little short of `to` or, in the persona's overshoot rate,
271
+ // passes it; one correction closes the residual (occasionally an intermediate one first).
227
272
  const targets = [];
273
+ let over = false;
228
274
  if (D >= 40) {
229
- const f = p.primaryFrac;
230
- const spread = Math.min(16, D * p.overshoot);
231
- targets.push({
232
- x: from.x + (to.x - from.x) * f + gaussFrom(rng) * spread,
233
- y: from.y + (to.y - from.y) * f + gaussFrom(rng) * spread,
234
- });
235
- // 1 correction usually, occasionally 2 (a 3rd is rare) — keeps the corrective-submovement count
236
- // in the human 1–3 band rather than over-generating homing bumps.
237
- const nCorr = rng() < 0.08 * p.maxCorrections ? 2 : 1;
238
- let cur = targets[0];
239
- for (let i = 0; i < nCorr; i++) {
275
+ over = rng() < tr.overshootRate;
276
+ const along = over
277
+ ? D + clamp(D * lerp(0.03, 0.08, rng()), 3, 28)
278
+ : D * clamp(p.primaryFrac + 0.05 + gaussFrom(rng) * 0.015, 0.88, 0.985);
279
+ const lat = gaussFrom(rng) * Math.min(6, D * 0.02);
280
+ targets.push({ x: from.x + ux * along - uy * lat, y: from.y + uy * along + ux * lat });
281
+ if (rng() < 0.08 * p.maxCorrections) {
282
+ const cur = targets[0];
240
283
  const close = lerp(0.55, 0.8, rng());
241
- const nx = { x: cur.x + (to.x - cur.x) * close, y: cur.y + (to.y - cur.y) * close };
242
- // tiny lateral error so corrections aren't perfectly collinear
243
- nx.x += gaussFrom(rng) * 1.2;
244
- nx.y += gaussFrom(rng) * 1.2;
245
- targets.push(nx);
246
- cur = nx;
284
+ const mx = cur.x + (to.x - cur.x) * close + gaussFrom(rng) * 1.0;
285
+ const my = cur.y + (to.y - cur.y) * close + gaussFrom(rng) * 1.0;
286
+ targets.push({ x: mx, y: my });
247
287
  }
248
288
  }
249
289
  targets.push({ x: to.x, y: to.y });
250
- // Allocate MT: primary ~62%, remaining split across corrections (each shorter).
251
290
  const segDur = [];
252
291
  if (targets.length === 1) {
253
292
  segDur.push(mt);
254
293
  }
255
294
  else {
256
- // Primary gets ~78% of the time (min-jerk peaks at its own mid-point ⇒ global peak lands ~0.35–0.4,
257
- // the human sweet spot slightly before the whole-gesture midpoint), corrections share the rest.
295
+ // Primary gets ~78% of the time; with its skewed min-jerk profile the global speed peak lands
296
+ // ~0.3 of the way through the move, before the midpoint as in a reach. Corrections share the rest.
258
297
  segDur.push(mt * 0.78);
259
298
  const rest = mt * 0.22;
260
299
  const nc = targets.length - 1;
261
300
  for (let i = 0; i < nc; i++)
262
301
  segDur.push((rest / nc) * lerp(0.8, 1.2, rng()));
263
302
  }
303
+ // The primary's arc: handedness sign from the persona (1 move in 5 bends the other way), size from
304
+ // the persona's curvature trait. Corrections are short and nearly straight.
305
+ let sign = p.approachBias >= 0 ? 1 : -1;
306
+ if (rng() < 0.2)
307
+ sign = -sign;
308
+ let bow = sign * Math.min(0.25, (0.08 + Math.abs(gaussFrom(rng)) * 0.11) * tr.curve * (0.75 + Math.abs(p.approachBias)));
309
+ if (D * Math.abs(bow) > 110)
310
+ bow = bow < 0 ? -110 / D : 110 / D;
264
311
  const drift = { x: 0, y: 0 };
265
312
  const jit = { x: 0, y: 0 };
266
313
  const tremorPhase = rng() * 2 * Math.PI;
@@ -268,18 +315,90 @@ export function planMove(from, to, p, opts = {}) {
268
315
  for (let s = 0; s < targets.length; s++) {
269
316
  const b = targets[s];
270
317
  const last = s === targets.length - 1;
271
- t = sampleSubmove(out, a, b, Math.max(20, segDur[s]), p, rng, t, drift, jit, tremorPhase, last && !opts.settle);
318
+ const first = s === 0;
319
+ t = sampleSubmove(out, a, b, Math.max(20, segDur[s]), p, rng, t, drift, jit, tremorPhase, last, first ? bow : bow * 0.15, first ? 1.25 : 1.0);
320
+ // motor re-planning gap; a reversal after passing the target comes back quicker
272
321
  if (!last && out.length)
273
- out[out.length - 1].sleepMs += lerp(40, 120, rng()); // motor re-planning gap
322
+ out[out.length - 1].sleepMs += over && first ? lerp(25, 90, rng()) : lerp(40, 120, rng());
274
323
  a = b;
275
324
  }
276
- if (opts.settle) {
325
+ return out;
326
+ }
327
+ /**
328
+ * Plan a held-button drag `from`→`to` (slider handle, drag-and-drop, range thumb).
329
+ *
330
+ * Measured on r38 / SDK 0.48.0 (SC 209): the held leg reused the aimed-move planner, whose arc put the
331
+ * hand up to 47 px off the line between grab and drop — off a 40 px slider track. A drag is a slower,
332
+ * flatter movement: one skewed min-jerk leg along the line with at most ~5 px of arc and ±6 px of total
333
+ * sideways error, 0.5–2 s for 300–400 px (`shapeTraits(p).dragTempo`), and now and then (about 0.6× the
334
+ * persona's overshoot rate) a 2.5–10 px overshoot and pull-back. `settle` adds a couple of sub-pixel
335
+ * seating jiggles before landing exactly on `to`.
336
+ */
337
+ export function planDrag(from, to, p, opts = {}) {
338
+ const rng = opts.rng ?? Math.random;
339
+ const settle = !!opts.settle;
340
+ const D = Math.hypot(to.x - from.x, to.y - from.y);
341
+ const out = [];
342
+ const stepMs = 1000 / p.deviceHz;
343
+ if (D < 1.5) {
344
+ out.push({ x: to.x, y: to.y, sleepMs: stepMs });
345
+ return out;
346
+ }
347
+ const tr = shapeTraits(p);
348
+ const ux = (to.x - from.x) / D, uy = (to.y - from.y) / D;
349
+ const mt = clamp((260 + 1.7 * D) * tr.dragTempo * lerp(0.9, 1.1, rng()), Math.min(500, 150 + 1.5 * D), 2000);
350
+ const over = D >= 60 && rng() < tr.overshootRate * 0.6;
351
+ const o = over ? clamp(D * lerp(0.01, 0.03, rng()), 2.5, 10) : 0;
352
+ const sign = p.approachBias >= 0 ? 1 : -1;
353
+ const h = sign * Math.min(5, D * 0.015) * lerp(0.3, 1.0, rng());
354
+ const tremorPhase = rng() * 2 * Math.PI;
355
+ const legs = [[0, D + o, mt * (over ? 0.86 : 1.0), h]];
356
+ if (over)
357
+ legs.push([D + o, D, mt * 0.14, 0]);
358
+ let drift = 0, jit = 0, t = 0;
359
+ for (let li = 0; li < legs.length; li++) {
360
+ const [s0, s1, dur, hh] = legs[li];
361
+ const n = Math.min(360, Math.max(4, jsRound(dur / stepMs)));
362
+ const dt = dur / n;
363
+ const lastLeg = li === legs.length - 1;
364
+ for (let i = 1; i <= n; i++) {
365
+ const tau = i / n;
366
+ const e = minJerk(skewed(tau, 1.2));
367
+ let along = s0 + (s1 - s0) * e;
368
+ const v = 30 * tau * tau * (1 - tau) * (1 - tau) / 1.875;
369
+ const k = dt / p.driftTau;
370
+ drift += -k * drift + Math.sqrt(2 * k) * p.driftAmp * 0.8 * gaussFrom(rng);
371
+ jit = 0.7 * jit + gaussFrom(rng) * p.jitter * 0.12;
372
+ const tAmp = p.tremorAmp * (0.7 + 0.3 * Math.sin((2 * Math.PI * t) / 850 + tremorPhase));
373
+ const tremor = tAmp * Math.sin(tremorPhase + (2 * Math.PI * p.tremorHz * t) / 1000);
374
+ along += tAmp * 0.6 * Math.sin(tremorPhase + 1.0 + (2 * Math.PI * (p.tremorHz * 0.93) * t) / 1000) * v;
375
+ let side = hh * Math.sin(Math.PI * e) + (drift + tremor + jit) * (0.6 + 0.4 * v);
376
+ side = clamp(side, -6, 6);
377
+ let px, py;
378
+ if (lastLeg && i === n && !settle) {
379
+ px = to.x;
380
+ py = to.y;
381
+ }
382
+ else {
383
+ px = from.x + ux * along - uy * side;
384
+ py = from.y + uy * along + ux * side;
385
+ }
386
+ const sl = dt * lerp(0.88, 1.12, rng());
387
+ out.push({ x: px, y: py, sleepMs: sl });
388
+ t += sl;
389
+ }
390
+ if (!lastLeg && out.length)
391
+ out[out.length - 1].sleepMs += lerp(30, 80, rng()); // notices, stops, pulls back
392
+ }
393
+ if (settle) {
277
394
  // A couple of tiny sub-pixel seating jiggles, then land exactly (slider handle "clunk" into place).
278
395
  const jig = 1 + Math.floor(rng() * 2);
279
396
  for (let i = 0; i < jig; i++) {
280
- out.push({ x: to.x + gaussFrom(rng) * 1.4, y: to.y + gaussFrom(rng) * 1.4, sleepMs: lerp(30, 90, rng()) });
397
+ const jx = to.x + clamp(gaussFrom(rng) * 0.8, -1.5, 1.5);
398
+ const jy = to.y + clamp(gaussFrom(rng) * 0.8, -1.5, 1.5);
399
+ out.push({ x: jx, y: jy, sleepMs: lerp(30, 90, rng()) });
281
400
  }
282
- out.push({ x: to.x, y: to.y, sleepMs: 1000 / p.deviceHz });
401
+ out.push({ x: to.x, y: to.y, sleepMs: stepMs });
283
402
  }
284
403
  return out;
285
404
  }