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/README.md +1 -1
- package/dist/forms.d.ts +35 -0
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +104 -0
- package/dist/forms.js.map +1 -1
- package/dist/humanize.d.ts +29 -3
- package/dist/humanize.d.ts.map +1 -1
- package/dist/humanize.js +418 -140
- package/dist/humanize.js.map +1 -1
- package/dist/isolated.d.ts +26 -7
- package/dist/isolated.d.ts.map +1 -1
- package/dist/isolated.js +106 -22
- package/dist/isolated.js.map +1 -1
- package/dist/motion.d.ts +42 -1
- package/dist/motion.d.ts.map +1 -1
- package/dist/motion.js +159 -40
- package/dist/motion.js.map +1 -1
- package/dist/pacing.d.ts +56 -0
- package/dist/pacing.d.ts.map +1 -0
- package/dist/pacing.js +203 -0
- package/dist/pacing.js.map +1 -0
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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)
|
|
115
|
-
*
|
|
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
|
|
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
|
|
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
|
|
181
|
-
*
|
|
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
|
|
234
|
-
//
|
|
235
|
-
// that
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
package/dist/isolated.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isolated.js","sourceRoot":"","sources":["../src/isolated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,MAAM,WAAW,GAAG,CAAI,CAAM,EAAiB,EAAE;IAC/C,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,gBAAgB;QAAE,OAAO,SAAS,CAAC;IACxE,OAAO,CAAC,CAAC,MAAM,EAAE,KAAU,CAAC;AAC9B,CAAC,CAAC;AAEF
|
|
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
|
-
/**
|
|
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;
|
package/dist/motion.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"motion.d.ts","sourceRoot":"","sources":["../src/motion.ts"],"names":[],"mappings":"AA6BA;iGACiG;AACjG,MAAM,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC;AAE/B,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAQ/C;AAED,+FAA+F;AAC/F,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAUlE;AAED,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAK1C;AAED,sGAAsG;AACtG,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,+FAA+F;IAC/F,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,cAAc,EAAE,MAAM,CAAC;IACvB,oFAAoF;IACpF,YAAY,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,wGAAwG;IACxG,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAID,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CA0B3D;AAED;oDACoD;AACpD,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,GAAE,GAAiB,GAAG,MAAM,CAEpE;AAED;6EAC6E;AAC7E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,GAAE,GAAiB,GAAG,MAAM,CAEnE;AAMD;;;;;6DAK6D;AAC7D,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAE/C;AAED,kFAAkF;AAClF,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;AAEtE;;;;;;yDAMyD;AACzD,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAyD,CAAC;AAE7H;;8FAE8F;AAC9F,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAGnE;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,aAAa,GAAG,IAAI,CAQ5F;AAED,MAAM,WAAW,KAAK;IAAG,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;CAAE;AAChD,6FAA6F;AAC7F,MAAM,WAAW,IAAI;IAAG,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;CAAE;AAEhE,MAAM,WAAW,QAAQ;IACvB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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,
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
const
|
|
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
|
|
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
|
-
|
|
226
|
-
|
|
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
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
|
|
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
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
257
|
-
// the
|
|
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
|
-
|
|
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(
|
|
322
|
+
out[out.length - 1].sleepMs += over && first ? lerp(25, 90, rng()) : lerp(40, 120, rng());
|
|
274
323
|
a = b;
|
|
275
324
|
}
|
|
276
|
-
|
|
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
|
-
|
|
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:
|
|
401
|
+
out.push({ x: to.x, y: to.y, sleepMs: stepMs });
|
|
283
402
|
}
|
|
284
403
|
return out;
|
|
285
404
|
}
|