@clankagent/puck 0.1.0 → 0.3.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/AGENTS.md +14 -0
- package/CHANGELOG.md +57 -0
- package/CONTRIBUTING.md +20 -0
- package/DESIGN.md +42 -0
- package/README.md +36 -71
- package/VERIFICATION.md +92 -0
- package/dist/calibration.d.ts +49 -0
- package/dist/calibration.js +139 -0
- package/dist/gestures.d.ts +74 -0
- package/dist/gestures.js +230 -0
- package/dist/graph.d.ts +56 -0
- package/dist/graph.js +111 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +6 -0
- package/dist/press-tilt-calibration.d.ts +32 -0
- package/dist/press-tilt-calibration.js +88 -0
- package/dist/recording.d.ts +40 -0
- package/dist/recording.js +48 -0
- package/dist/tilt-calibration.d.ts +26 -0
- package/dist/tilt-calibration.js +46 -0
- package/dist/tune.d.ts +58 -0
- package/dist/tune.js +56 -0
- package/docs/agents.md +34 -0
- package/docs/api.md +93 -0
- package/docs/lab.md +62 -0
- package/docs/motion.md +20 -0
- package/docs/press-tilt.md +131 -0
- package/docs/quickstart.md +78 -0
- package/docs/roadmap.md +44 -0
- package/docs/troubleshooting.md +27 -0
- package/docs/tuning.md +146 -0
- package/docs/upgrading.md +59 -0
- package/examples/gesture-session.mjs +56 -0
- package/llms.txt +29 -0
- package/package.json +17 -3
package/docs/roadmap.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Toward Puck 1.0
|
|
2
|
+
|
|
3
|
+
[Documentation](../README.md) · [Changelog](../CHANGELOG.md)
|
|
4
|
+
|
|
5
|
+
1.0 means the public contracts and interaction behavior are dependable enough
|
|
6
|
+
for applications to adopt without chasing API changes. It does not mean every
|
|
7
|
+
SpaceMouse model or every possible gesture is supported.
|
|
8
|
+
|
|
9
|
+
## 0.3: tilt families, still experimental
|
|
10
|
+
|
|
11
|
+
0.3 adds push/pull + rx/ry tilt and standalone tilt singles/doubles, with
|
|
12
|
+
explicit arbitration and ordinary doubles preserved. Defaults use freeform
|
|
13
|
+
physical evidence, supplemented by synthetic boundary tests. This is enough
|
|
14
|
+
for an experimental release, not for 1.0: next evaluate unintended activations
|
|
15
|
+
during ordinary pan/zoom, repeat sessions, and real application adoption.
|
|
16
|
+
The contracts and calibration heuristics remain open to refinement.
|
|
17
|
+
|
|
18
|
+
## Requirements for 1.0
|
|
19
|
+
|
|
20
|
+
- **Stable contracts:** settle event shapes, press modes, tune editing and
|
|
21
|
+
persisted recording/tune formats. Define backward-compatible restoration and
|
|
22
|
+
errors, and publish a migration guide for any pre-1.0 changes.
|
|
23
|
+
- **Predictable interactions:** cover boundary timing, mixed gestures, diagonal
|
|
24
|
+
input, incidental cross-axis motion, holds, reversals, report silence,
|
|
25
|
+
cancellation and double presses. Identical reports must produce identical
|
|
26
|
+
events regardless of render cadence.
|
|
27
|
+
- **Physical evidence:** evaluate ordinary and combined gestures on real
|
|
28
|
+
recordings, including unintended activations during pan/zoom. Separate
|
|
29
|
+
inferred intent from known intended actions; publish the tested scope and
|
|
30
|
+
remaining uncertainty without shipping private captures.
|
|
31
|
+
- **Honest calibration:** provide useful coverage and ambiguity feedback for
|
|
32
|
+
every gesture family advertised as automatically tunable. Do not treat the
|
|
33
|
+
existing eight-action calibration as evidence for directional gestures.
|
|
34
|
+
- **Usable integration:** ship complete browser and transport-independent
|
|
35
|
+
examples, typed configuration, accessible recording graphs, lifecycle tests,
|
|
36
|
+
and clear guidance for keeping gesture actions and camera movement exclusive.
|
|
37
|
+
- **Release reliability:** verify CI, the packaged documentation and public
|
|
38
|
+
exports, then install and exercise the actual registry release. Keep changelog
|
|
39
|
+
entries linked to adoption documentation.
|
|
40
|
+
|
|
41
|
+
Additional device profiles, physical buttons, framework adapters and built-in
|
|
42
|
+
rendering are not prerequisites. Add them only for concrete, verified use cases.
|
|
43
|
+
Until these requirements are met, keep gesture APIs experimental and use 0.x
|
|
44
|
+
releases to refine them. There is no date-based promise for 1.0.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
[Documentation](../README.md) · [API reference](api.md)
|
|
4
|
+
|
|
5
|
+
| Symptom | Check / fix |
|
|
6
|
+
|---|---|
|
|
7
|
+
| Import or graph entry point missing | Gesture and graph APIs require 0.2.0 or later. Check your installed version and package exports; see the upgrade guide. |
|
|
8
|
+
| Bare import fails in browser | Resolve package imports with a bundler or import map. A browser cannot resolve npm package names by itself. |
|
|
9
|
+
| WebHID unavailable / chooser fails | Use a supporting browser and secure context; connect from a user click and display the caught error. Core processing works without WebHID. |
|
|
10
|
+
| Chooser cancelled | A null connection is expected; allow another click. |
|
|
11
|
+
| Device selected but no usable input | Verify the exact profile/report format. Matching vendor alone does not establish support. |
|
|
12
|
+
| First gesture ignored | Startup/reset is blocked until a fresh neutral input. Start recording before moving the cap. |
|
|
13
|
+
| Single feels delayed | Exclusive mode waits for the double window (default 400 ms). Immediate mode fires a single first and then an additive double; it does not undo the single. |
|
|
14
|
+
| Double becomes two singles | Inspect the completion interval and neutral valley. Check doubleMs and release/dwell settings. Do not blindly increase the activation threshold. |
|
|
15
|
+
| Stronger gesture rejected | Typical high is not a rejection cutoff. Check hold duration, competing axis dominance, and neutral return. |
|
|
16
|
+
| Push/pull harder than twist | Use the separate push/pull bands or calibrate; do not force identical thresholds on all axes. |
|
|
17
|
+
| Nothing fires during report silence | Keep calling advance using the same monotonic clock as update. |
|
|
18
|
+
| Clock throws RangeError | Use performance.now() in both report and frame callbacks for gestures. rAF's supplied timestamp can be older than a report already processed. |
|
|
19
|
+
| Actions fire when leaving the page | Treat the WebHID lifecycle sentinel as reset, not a physical release. See the complete session example. |
|
|
20
|
+
| Calibration incomplete | Show missing/counts; add at least three singles and three doubles per direction. Combining distinct captures is supported. |
|
|
21
|
+
| Calibration ambiguous | Close singles may resemble a double; runs of three or more fast pulses are ambiguous. Inspect raw graph and repeat only unclear actions with pauses between independent actions. |
|
|
22
|
+
| Saved tune has no methods | JSON stores data only. Call createGestureTune(JSON.parse(saved)). |
|
|
23
|
+
| Tune edit throws on assignment | Tunes and toJSON data are frozen. Copy nested objects before editing, or use the immutable methods. |
|
|
24
|
+
| Recorder stops adding entries | Inspect full; stop/save and start another capture. Defaults are two minutes / 50000 timeline entries. |
|
|
25
|
+
| Graph overlays wrong session | Pass the zero-based recordingIndex used in the combined calibration. |
|
|
26
|
+
|
|
27
|
+
When reporting a problem, include package version, runtime/browser, OS, device IDs and connection type, relevant configuration, expected/actual events and a minimal synthetic reproduction when possible. Remove personal information from any raw capture. See [contribution guidance](../CONTRIBUTING.md) for evidence needed for a new profile.
|
package/docs/tuning.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Tunes, recording and calibration
|
|
2
|
+
|
|
3
|
+
[Documentation](../README.md) · [Complete integration](quickstart.md) · [API reference](api.md)
|
|
4
|
+
|
|
5
|
+
The snippets below explain individual operations. Use the complete integration example for connection, capture start/stop and cleanup.
|
|
6
|
+
|
|
7
|
+
## Gesture tunes
|
|
8
|
+
|
|
9
|
+
The experimental gesture API uses an immutable tune object. The default profile
|
|
10
|
+
has a shared rotation center of .465, push .314, and pull .289. It uses rotation
|
|
11
|
+
activation/release .25/.15, push .20/.08, pull .12/.06, neutral dwell 25 ms, and a
|
|
12
|
+
400 ms double window. These are useful starting defaults, not universal hardware
|
|
13
|
+
calibration. Pan/zoom behavior is unchanged.
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import {
|
|
17
|
+
createGestures, createGestureTune, defaultGestureTune, gesturePresets,
|
|
18
|
+
} from '@clankagent/puck';
|
|
19
|
+
|
|
20
|
+
const tune = defaultGestureTune.soften(0.1).widen(0.15);
|
|
21
|
+
const gestures = createGestures(tune);
|
|
22
|
+
// createGestures() uses defaultGestureTune.
|
|
23
|
+
// Also available: gesturePresets.default, .soft (20% softer), .hard (20% harder).
|
|
24
|
+
|
|
25
|
+
const saved = JSON.stringify(tune);
|
|
26
|
+
const restored = createGestureTune(JSON.parse(saved));
|
|
27
|
+
const another = restored.harden(0.05).narrow(0.1);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Every edit returns a new deeply frozen object, including its nested bands.
|
|
31
|
+
Amounts are fractions: `.soften(.1)` lowers force levels by 10%; `.harden(.1)`
|
|
32
|
+
raises them by 10%. Levels saturate at normalized full scale. `.narrow(.1)`
|
|
33
|
+
reduces distances from the center by 10%, moving the activation gate toward the
|
|
34
|
+
center; `.widen(.1)` expands them and lowers the activation gate. Release remains
|
|
35
|
+
proportional to activation. These methods edit force only; timing remains unchanged.
|
|
36
|
+
Softening and narrowing require amounts in [0,1); hardening and widening accept
|
|
37
|
+
[0,10]. Edits are composable, not algebraic inverses: softening by 10% and then
|
|
38
|
+
hardening by 10% gives 99% of the original force.
|
|
39
|
+
|
|
40
|
+
Each `rotation`, `push`, and `pull` band contains `center`, `low`, `high`,
|
|
41
|
+
`activation`, and `release`. Low/high describe typical peak variation; they are
|
|
42
|
+
not rejection boundaries. Stronger-than-typical input still counts. Rotation
|
|
43
|
+
always shares a band between clockwise and counterclockwise. `toJSON()` returns
|
|
44
|
+
portable versioned data; `toOptions()` returns the low-level recognizer options.
|
|
45
|
+
For precise timing edits, copy both `toJSON()` and its nested `timing` object, then call
|
|
46
|
+
`createGestureTune(data)` to validate it.
|
|
47
|
+
|
|
48
|
+
The low-level `createGestures(options)` overload remains available. Missing
|
|
49
|
+
settings use the default tune; `createGestures({})` matches `createGestures()`.
|
|
50
|
+
Explicit signed-direction gates override axis gates, which override shared
|
|
51
|
+
activation/release values. Prefer a tune object for complete configuration.
|
|
52
|
+
## Recording and custom calibration
|
|
53
|
+
|
|
54
|
+
A complete calibration needs at least **three of each of eight actions**:
|
|
55
|
+
clockwise single/double, counterclockwise single/double, push single/double,
|
|
56
|
+
and pull single/double. Grouping examples is convenient but not required.
|
|
57
|
+
Use short pulses and leave a pause between separate actions. The parser uses
|
|
58
|
+
raw deflection and timing, not the order of action groups or recorded event labels.
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
import {
|
|
62
|
+
createGestureRecorder, createGestures, defaultGestureTune,
|
|
63
|
+
calibrateGestures, neutralInput,
|
|
64
|
+
} from '@clankagent/puck';
|
|
65
|
+
|
|
66
|
+
const gestures = createGestures(defaultGestureTune);
|
|
67
|
+
const recorder = createGestureRecorder({
|
|
68
|
+
startTimeMs: performance.now(), tune: defaultGestureTune, source: 'device',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Call for every report; don't discard reports between render frames.
|
|
72
|
+
function onInput(input) {
|
|
73
|
+
const now = performance.now();
|
|
74
|
+
if (input === neutralInput) { // WebHID lifecycle sentinel, not physical release
|
|
75
|
+
gestures.reset(); recorder.reset(now); return;
|
|
76
|
+
}
|
|
77
|
+
recorder.input(input, now);
|
|
78
|
+
const events = gestures.update(input, now);
|
|
79
|
+
recorder.events(events);
|
|
80
|
+
consume(events); // Your application handles recognized actions.
|
|
81
|
+
}
|
|
82
|
+
function frame() {
|
|
83
|
+
const now = performance.now(); // Same clock used by onInput.
|
|
84
|
+
recorder.advance(now);
|
|
85
|
+
const events = gestures.advance(now);
|
|
86
|
+
recorder.events(events);
|
|
87
|
+
consume(events);
|
|
88
|
+
requestAnimationFrame(frame);
|
|
89
|
+
}
|
|
90
|
+
// Wire WebHID onReset to reset both processors as well.
|
|
91
|
+
// Start your frame loop and pass onInput to your transport.
|
|
92
|
+
|
|
93
|
+
const recording = recorder.snapshot(performance.now()); // At the end of capture.
|
|
94
|
+
// Your app can download JSON.stringify(recording) or save it to its own backend.
|
|
95
|
+
const result = calibrateGestures(recording);
|
|
96
|
+
// Or combine sessions: calibrateGestures([recordingA, recordingB]).
|
|
97
|
+
if (result.tune !== null) {
|
|
98
|
+
const tunedGestures = createGestures(result.tune);
|
|
99
|
+
// Save JSON.stringify(result.tune) for the next session.
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The SDK owns no timers, listeners, storage or network access. The recorder copies
|
|
104
|
+
samples, has a default two-minute / 50,000-entry bound, reports `full`, and
|
|
105
|
+
returns detached snapshots. Apps own start/stop and storage. Raw records include
|
|
106
|
+
six axes, relative timestamps, frame ticks, resets, and optional recognized
|
|
107
|
+
events. The latest input remains held during report silence. Reset cancels
|
|
108
|
+
recognition and requires a fresh neutral sample. Use `performance.now()` in both
|
|
109
|
+
callbacks; rAF's supplied timestamp can precede a report already processed.
|
|
110
|
+
|
|
111
|
+
Calibration returns `status`, `tune`, `counts`, `missing`, `stats`, `pulses`,
|
|
112
|
+
`actions`, and `issues`. Incomplete or ambiguous input returns `tune: null`;
|
|
113
|
+
counts never claim labeled ground-truth accuracy. Quick same-direction singles
|
|
114
|
+
can be indistinguishable from an intended double. Runs of three or more closely
|
|
115
|
+
spaced pulses are flagged as ambiguous. Separate sessions and resets are never
|
|
116
|
+
paired. Long holds, unfinished excursions, and very short spikes are excluded.
|
|
117
|
+
|
|
118
|
+
The initial parser uses a normalized .025 detection floor, local peak/valley
|
|
119
|
+
shape and a 250 ms maximum inter-pulse gap for pairs. These can be configured
|
|
120
|
+
with `detectionFloor` and `pairGapMs`. `minimumPerAction` can raise the requirement
|
|
121
|
+
but cannot go below three. Candidate centers are per-direction mean peak force;
|
|
122
|
+
rotation uses the unweighted average of the two direction means. Typical bands
|
|
123
|
+
use interpolated 10th/90th percentiles. Gates and timing are derived conservatively
|
|
124
|
+
from observed weaker peaks, return valleys, pulse lengths and double intervals.
|
|
125
|
+
Calibration infers intended actions; it is not evidence of subjective feel or
|
|
126
|
+
new hardware compatibility.
|
|
127
|
+
|
|
128
|
+
## Graphs for consuming applications
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
import { createGestureGraph, renderGestureGraphSvg } from '@clankagent/puck/graph';
|
|
132
|
+
|
|
133
|
+
const model = createGestureGraph(recording, result.tune ?? defaultGestureTune, {
|
|
134
|
+
actions: result.actions,
|
|
135
|
+
// Optional start/end in milliseconds to inspect an individual action.
|
|
136
|
+
});
|
|
137
|
+
container.innerHTML = renderGestureGraphSvg(model, { width: 900 });
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`createGestureGraph` returns renderer-neutral data for four separate positive
|
|
141
|
+
magnitude lanes. The optional SVG renderer has no DOM dependencies: it labels
|
|
142
|
+
directions, typical peaks, activation and release, preserves held samples as
|
|
143
|
+
steps, and highlights inferred action intervals. Its text is escaped. For
|
|
144
|
+
combined calibrations, pass the corresponding recording and `recordingIndex`.
|
|
145
|
+
Apps can use the model with their own chart library instead. Only graph a tune
|
|
146
|
+
when available; incomplete calibration can be inspected with the default tune.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Upgrading and adopting updates
|
|
2
|
+
|
|
3
|
+
[Changelog](../CHANGELOG.md) · [Documentation](../README.md) · [API reference](api.md)
|
|
4
|
+
|
|
5
|
+
## Identify the version first
|
|
6
|
+
|
|
7
|
+
Read the changelog for the version you are adopting, then use docs from that same tag or installed package. Main can contain unreleased features. `pnpm list @clankagent/puck` shows your app's installed version; its exports and declarations determine which APIs are available.
|
|
8
|
+
|
|
9
|
+
Gesture, tune, recording, calibration and graph APIs are included in 0.2.0. In your consuming app:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add @clankagent/puck@0.3.0
|
|
13
|
+
pnpm list @clankagent/puck
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Commit your app's updated manifest and lockfile. Use the documentation shipped with that package or the v0.3.0 source tag. The new gesture APIs remain experimental; existing motion integrations retain their behavior.
|
|
17
|
+
|
|
18
|
+
To test future unreleased source, build a chosen revision with `pnpm install --frozen-lockfile`, `pnpm check` and `pnpm pack --pack-destination artifacts`, then install the generated tarball into your app with `pnpm add /path/to/package.tgz`. Record the source commit alongside it; a working-tree package version alone does not identify unreleased changes.
|
|
19
|
+
|
|
20
|
+
## From 0.2 to 0.3
|
|
21
|
+
|
|
22
|
+
No new gesture family is enabled automatically. Add `standaloneTilt:true` for
|
|
23
|
+
rx/ry singles and doubles; add `pressMode:'auto'` or `'tilt'` for push/pull +
|
|
24
|
+
tilt combinations. Keep `singleMode:'exclusive'` when combined modes are enabled.
|
|
25
|
+
See the [tilt integration guide](press-tilt.md) for event examples, default gates,
|
|
26
|
+
freeform calibration and graphs. Feed every physical report and advance the clock
|
|
27
|
+
as before; camera arbitration remains application-owned.
|
|
28
|
+
|
|
29
|
+
`GestureDirection` now also includes four rx/ry directions. TypeScript consumers
|
|
30
|
+
with exhaustive direction maps must extend them, even if the feature stays off.
|
|
31
|
+
Use exported `PulseDirection` for APIs intentionally limited to the original four.
|
|
32
|
+
Combined events add an optional `tilt`; route those before handling plain pressure.
|
|
33
|
+
Tune version stays 1 with optional new fields; old tune JSON is supported.
|
|
34
|
+
|
|
35
|
+
## Existing pan/zoom applications
|
|
36
|
+
|
|
37
|
+
No migration is required for the new opt-in features: decoding, `createPanZoom` and `/webhid` behavior and defaults are unchanged from the original baseline. Keep your existing camera, input ownership and render loop.
|
|
38
|
+
|
|
39
|
+
## Add only what your app needs
|
|
40
|
+
|
|
41
|
+
| Goal | Add | Application responsibility |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| React to single/double gestures | `createGestures(tune)` | Feed every report, advance the same clock, reset on lifecycle changes, consume events. [Complete integration](quickstart.md) |
|
|
44
|
+
| Offer softer/harder controls | Presets and immutable tune methods | Retain the returned tune and create a new recognizer to apply it. [Tune editing](tuning.md#gesture-tunes) |
|
|
45
|
+
| Save a personal tune | `createGestureTune` | Save JSON; restore through the constructor. [Tune API](api.md#tunes) |
|
|
46
|
+
| Learn a tune from normal input | Recorder plus `calibrateGestures` | Own capture/storage; show missing/ambiguous evidence and apply only a non-null tune. [Calibration](tuning.md#recording-and-custom-calibration) |
|
|
47
|
+
| Explain the learned result | Optional `/graph` imports | Show a text status/count summary and select the matching capture for overlays. [Graphs](api.md#graphs) |
|
|
48
|
+
|
|
49
|
+
To add gestures alongside motion, reuse the existing connection and frame loop. Fan physical reports out to both processors. Ignore the exact `neutralInput` lifecycle sentinel for gestures and reset both processors in `onReset`. Use `performance.now()` in both gesture report and frame callbacks. The [session example](../examples/gesture-session.mjs) demonstrates lifecycle handling; adapt it to your existing connection/loop rather than starting duplicate ones.
|
|
50
|
+
|
|
51
|
+
## Behavior to choose explicitly
|
|
52
|
+
|
|
53
|
+
- **Single versus double:** exclusive singles wait for the double window. Immediate mode is additive: a double also produces the earlier single. It does not undo an action.
|
|
54
|
+
- **Gesture versus camera:** both processors may react to the same movement. Your app decides whether a gesture tool pauses camera motion or permits both.
|
|
55
|
+
- **Applying a tune:** changing a variable does not retune an existing recognizer. Replace the recognizer or reconnect the example session with the new tune; pending actions are discarded and fresh neutral is required.
|
|
56
|
+
- **Calibration quality:** require three singles and three doubles in each direction. Group order does not matter. Closely spaced singles can look like a double; inferred counts are not labeled accuracy. Avoid duplicate captures as new evidence.
|
|
57
|
+
- **Data versions:** package versions are separate from tune/recording `version: 1`. Validate restored data through public APIs; do not manually change a format version to bypass validation.
|
|
58
|
+
|
|
59
|
+
Before shipping, check a single, double, held input, neutral rearming, blur/disconnect and teardown. Check JSON restoration and incomplete calibration if used. [Troubleshooting](troubleshooting.md) maps common symptoms to fixes.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createGestures, createGestureRecorder, defaultGestureTune, neutralInput } from '@clankagent/puck';
|
|
2
|
+
import { connectWebHid } from '@clankagent/puck/webhid';
|
|
3
|
+
|
|
4
|
+
/** Call from a click handler. Returns null when the chooser is cancelled. */
|
|
5
|
+
export async function connectGestureSession({ tune = defaultGestureTune, options = {}, onEvents = console.log, onDisconnect = () => {} } = {}) {
|
|
6
|
+
const configuration = {...tune.toOptions(),...options};
|
|
7
|
+
const gestures = createGestures(configuration);
|
|
8
|
+
let recorder = null;
|
|
9
|
+
let frameId;
|
|
10
|
+
let stopped = false;
|
|
11
|
+
function emit(events) { recorder?.events(events); if (events.length) onEvents(events); }
|
|
12
|
+
function reset() { gestures.reset(); recorder?.reset(performance.now()); }
|
|
13
|
+
const connection = await connectWebHid({
|
|
14
|
+
onInput(input) {
|
|
15
|
+
// Lifecycle sentinel is followed by onReset, not a physical release.
|
|
16
|
+
if (input === neutralInput) return;
|
|
17
|
+
const now = performance.now();
|
|
18
|
+
recorder?.input(input, now);
|
|
19
|
+
emit(gestures.update(input, now));
|
|
20
|
+
},
|
|
21
|
+
onReset: reset,
|
|
22
|
+
onDisconnect() { stopped = true; cancelAnimationFrame(frameId); onDisconnect(); },
|
|
23
|
+
});
|
|
24
|
+
if (!connection) return null;
|
|
25
|
+
function frame() {
|
|
26
|
+
if (stopped) return;
|
|
27
|
+
// Same current clock as reports, not rAF's earlier frame timestamp.
|
|
28
|
+
const now = performance.now();
|
|
29
|
+
recorder?.advance(now);
|
|
30
|
+
emit(gestures.advance(now));
|
|
31
|
+
frameId = requestAnimationFrame(frame);
|
|
32
|
+
}
|
|
33
|
+
frameId = requestAnimationFrame(frame);
|
|
34
|
+
return {
|
|
35
|
+
startRecording() {
|
|
36
|
+
if (stopped) throw new Error('Session has ended.');
|
|
37
|
+
if (recorder) throw new Error('Stop the current recording first.');
|
|
38
|
+
gestures.reset();
|
|
39
|
+
recorder = createGestureRecorder({ startTimeMs: performance.now(), options:configuration, source: 'device' });
|
|
40
|
+
},
|
|
41
|
+
stopRecording() {
|
|
42
|
+
if (!recorder) return null;
|
|
43
|
+
const recording = recorder.snapshot(performance.now());
|
|
44
|
+
recorder = null;
|
|
45
|
+
return recording;
|
|
46
|
+
},
|
|
47
|
+
get recordingFull() { return recorder?.full ?? false; },
|
|
48
|
+
pause: connection.pause,
|
|
49
|
+
resume: connection.resume,
|
|
50
|
+
async close() {
|
|
51
|
+
stopped = true;
|
|
52
|
+
cancelAnimationFrame(frameId);
|
|
53
|
+
await connection.close();
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Puck
|
|
2
|
+
|
|
3
|
+
> TypeScript/ESM six-axis input, pan/zoom, tunable cap gestures and freeform calibration. Zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
Documentation targets 0.3.0, including experimental gesture/tune/recording/calibration/graph APIs. Match documentation to your installed version.
|
|
6
|
+
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
- [Overview and version status](README.md)
|
|
10
|
+
- [Changelog and compatibility](CHANGELOG.md)
|
|
11
|
+
- [Upgrade and integration paths](docs/upgrading.md)
|
|
12
|
+
- [Complete integration](docs/quickstart.md)
|
|
13
|
+
- [API reference: imports, units, defaults, lifecycle](docs/api.md)
|
|
14
|
+
- [Tunes, recording and calibration](docs/tuning.md)
|
|
15
|
+
- [Motion and hardware scope](docs/motion.md)
|
|
16
|
+
- [Troubleshooting](docs/troubleshooting.md)
|
|
17
|
+
- [Agent integration invariants](docs/agents.md)
|
|
18
|
+
- [Runnable session wrapper](examples/gesture-session.mjs)
|
|
19
|
+
- [Camera arithmetic](examples/camera.mjs)
|
|
20
|
+
|
|
21
|
+
## Source development
|
|
22
|
+
|
|
23
|
+
- [Contribution workflow](CONTRIBUTING.md)
|
|
24
|
+
- [Repository agent instructions](AGENTS.md)
|
|
25
|
+
- [Local gesture lab](docs/lab.md)
|
|
26
|
+
- [Next release and 1.0 criteria](docs/roadmap.md)
|
|
27
|
+
- [Standalone and combined tilt API](docs/press-tilt.md)
|
|
28
|
+
|
|
29
|
+
Exact build signatures are in dist/index.d.ts, dist/webhid.d.ts and dist/graph.d.ts. Import from @clankagent/puck, @clankagent/puck/webhid and @clankagent/puck/graph only.
|
package/package.json
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clankagent/puck",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Frame-independent six-axis input
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Frame-independent six-axis input, pan/zoom and tunable gestures",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
9
|
"README.md",
|
|
10
|
-
"
|
|
10
|
+
"CHANGELOG.md",
|
|
11
|
+
"docs",
|
|
12
|
+
"llms.txt",
|
|
13
|
+
"examples/camera.mjs",
|
|
14
|
+
"examples/gesture-session.mjs",
|
|
15
|
+
"AGENTS.md",
|
|
16
|
+
"CONTRIBUTING.md",
|
|
17
|
+
"DESIGN.md",
|
|
18
|
+
"VERIFICATION.md"
|
|
11
19
|
],
|
|
12
20
|
"exports": {
|
|
21
|
+
"./graph": {
|
|
22
|
+
"types": "./dist/graph.d.ts",
|
|
23
|
+
"import": "./dist/graph.js"
|
|
24
|
+
},
|
|
13
25
|
".": {
|
|
14
26
|
"types": "./dist/index.d.ts",
|
|
15
27
|
"import": "./dist/index.js"
|
|
@@ -42,6 +54,8 @@
|
|
|
42
54
|
"input"
|
|
43
55
|
],
|
|
44
56
|
"scripts": {
|
|
57
|
+
"lab": "node examples/gestures/serve.mjs",
|
|
58
|
+
"lab:analyze": "node examples/gestures/analyze-recording.mjs",
|
|
45
59
|
"build": "tsc -p tsconfig.json",
|
|
46
60
|
"test": "node --test tests/*.test.mjs",
|
|
47
61
|
"check": "pnpm run build && pnpm test"
|