gesturecore bench

cam off delegate – fps – detect – core –
Space pause R reset
Press Start camera and allow camera access
— or watch a demo with recorded hands.
measured charging active lost your limit raw / off rings: outer engage · inner pose · small pinch view mirrored

Every change goes straight to core.setConfig() and is saved in this browser only. ◆ marks values that differ from the defaults.

Gestures are plain data: poses are hand shapes, movements are how the hand travels. Both take effect on the next frame and are saved with the config.

Record a pose from your hand

Hold the hand shape. The recorder averages each finger's curl and makes a pose that matches your curls ± the tolerance. A new pose appears in Moves straight away, and fires a pose event once it beats every other pose.

Tick include thumb for any shape the thumb defines — a thumbs-up is a fist to every other finger, so without the thumb it is scored as fist and never wins.

Record a movement

Show it instead of describing it. Perform the movement a few times; the bench watches which way your hand actually travelled, how far and how long it took, and fits a movement that every take would have fired — then checks that against the takes before offering it.

Build a movement by hand
reversals

Poses (JSON)

Curl ranges are inclusive, 0 = straight, 1 = fully bent. Unlisted fingers are ignored.

Movements (JSON)

gesturecore-sound, an optional brick: gestures trigger short synthesized sounds, and a hand can play a sustained voice. The core works the same with this switched off. Browsers keep audio silent until you ask, so sound starts with the button.

off
Hear the sounds
Cues (JSON)

One-shot sounds for gesture events. on is an event type; hand, name and finger narrow it; pan can be "hand".

Controls (JSON)

Hand measurements steering a voice while a condition holds. Controls with the same voice steer the same voice.

gesturecore-head, an optional brick: it reads your face from the same camera — where your head points, nods and shakes, blinks and winks, raised brows, an open mouth, a smile. The hands work the same with it off. Switching it on downloads the face model (3.8 MB) and runs it on every frame.

off
Neutral

Angles start from facing the camera squarely. If your screen sits to one side, or you blink-read as half closed, sit as you normally do, look at the screen and press Set neutral: that pose becomes zero and those eyes count as open.

Settings, expressions and movements (JSON)

An expression is ranges on readings, all holding at once: degrees for yaw, pitch, roll; 0…1 for the rest, including any raw blendshape by name. A movement is strokes of one angle, like a hand movement but in degrees.

False activations per 10 minutes

This measures the core rather than adding a feature: while you use the computer normally, how often does gesturecore fire a gesture you did not mean? The target is fewer than 2 per 10 minutes. If it fails, the defaults need retuning (usually pinch.closed, hysteresis, dwellMs, engage.dwellMs).

  1. Start the camera and sit as you normally do, hands in view.
  2. Press Start below.
  3. Work normally for at least 10 minutes: type, reach for things, rest your hands, and gesture on purpose now and then.
  4. Every time the log shows a pinch or movement you did not intend, press F.
0:00
elapsed
0
activations
0
false (F)
–
false / 10 min

Everything gesturecore ships with. All of it is plain data — edit or replace any of it in Gestures. A row lights up while that gesture is happening.

Test captures: a recorded hand the automated tests replay, so a change in the maths shows up as a failing test instead of a surprise. Saving writes test/fixtures/<name>.json; run npm test afterwards.

This does not add a gesture. To teach the bench a new hand shape so it fires events, use Gestures → Record a pose from your hand. Capture it here as well only if you want the tests to guard it.

What to capture
Views

One snapshot proves nothing: hand tracking is weakest away from a square-on view, so the same shape is recorded from several angles and distances and the tests require all of them to read the same. Capture what you can — every view you add tightens the test.

Result
Camera
–
Orientation

mirror (header) flips the image like a mirror, so moving your hand right moves it right on screen. It also flips the x coordinates the core receives, which affects tilt sign and swipe direction.

swap L/R (header) only changes the Left/Right label. Raise only your right hand: it should say Right. The two settings are independent.

MediaPipe
Appearance
Overlay
tilt → field tiltopen / fist → wave heightfingers up → wave tightness palm → position and speedleft violet · right cyangestures ripple

gesturecore bench

A workbench for gesturecore, a small library that turns camera hand tracking into clean gesture events: pinches, poses, movements. The bench shows everything the library measures and decides, lets you tune every constant live, and lets you design new gestures without writing code.

1. Quick start

  1. Press Start camera (top right) and allow camera access.
  2. Raise only your right hand. Its label on the video should say Right. If it says Left, tick swap L/R in the header.
  3. Hold an open palm toward the camera. A ring fills around your palm; when it turns green the hand is engaged.
  4. Touch your thumb tip to a fingertip and hold. A small ring fills between them; when full you get pinch:start with the finger's name in the Events panel.
  5. Engaged and not pinching, make a quick sideways swipe: motion swipeRight appears in the log.
  6. Open Gestures to record a pose from your own hand, or build your own movement.
Nothing is sent anywhere. Tuning, gestures, layout and session history live in this browser only; see Files and saving.

2. How it works

Every camera frame goes through the same one-way pipeline:

Cameraa video frame
→
MediaPipefinds up to 2 hands, 21 points each, plus a Left/Right guess
→
Bench adaptermirrors the view, fixes the label, adds a timestamp
→
SmoothingOne Euro filter on every point
→
Featurescurls, pinch distances, openness, tilt, size…
→
Rules + timersposes, engage, pinch, movements, loss
→
Eventslisted in the Events panel

The outlined boxes are gesturecore itself. It has no idea there is a camera, a browser or a screen: it receives 21 points per hand and a time, and returns a list of events. That is what lets the same library drive a browser tool, a musical instrument, or run in tests with no camera at all. Everything else on this page (camera, drawing, panels, storage) is the bench, the first program using the library.

Gestures are geometry, not machine learning. MediaPipe does the hard part of finding the hand; gesturecore only measures angles and distances and applies thresholds and timers. Every decision is therefore explainable: the hand panels show the exact numbers each rule looks at.

Two ideas used everywhere

  • Hysteresis: an on/off decision uses two thresholds, one to switch on and a looser one to switch off. A value hovering near a single line would otherwise flicker on-off-on every frame.
  • Dwell: a state must hold continuously for some time (default 300 ms) before its event fires. Any frame that breaks it resets the timer. The filling rings on the video are these timers.

3. Colour key

Colour carries meaning here; it is never decoration. The same six colours mean the same thing on the video, in the bars, in the log and in these docs. Anything structural (frames, tabs, labels) stays neutral steel.

ColourMeaningWhere you see it
cyana live measurementfeature bars, smoothed skeleton, fps and timing readouts
ambercharging: a condition is met and a timer is running, nothing has fired yetfilling rings, dwell bars, the held pose, movement progress, a finger that is closed but not yet a pinch
greenactive or fired: the gesture is realengaged hand, pinching finger, fired pose and movement events, PASS
redlost, failed, unintendedlost events, errors, the false-activation count, FAIL
whitea limit or value you setthreshold ticks on bars, slider handles, ◆ next to changed settings
greyraw input, disabled, offunsmoothed skeleton, disabled fingers, absent hands, pinch:move spam

Left and right hands are told apart by their labels, never by colour, so colour can stay meaningful.

Glow and backdrop

Two colours belong to the interface rather than to the data, so they can never be confused with a measurement:

  • Cyan glow = you can click this. Buttons, links, fields, checkboxes and the tab close buttons light up quickly under the mouse.
  • Violet glow = a surface, not an action. Tabs, panels, tables and code blocks answer with a slower, softer glow that fades in and out.

The field is a set of slow waves that drift on their own and lean toward what your hands are doing:

palm tilttilts the whole field
open hand / fistwave height — a fist calms it flat
fingers extendedhow tight the waves are
palm positionwhere the waves sit, and how fast they travel
which handLeft pulls the colour violet, Right pulls it cyan
engaged / eventsbrightness, and a ripple on each pinch, pose or movement

Every influence is smoothed heavily, so it reads as atmosphere, never as a readout — for numbers, use the hand panels. It repaints about sixteen times a second on a small canvas, stops when the tab is in the background, and stays still for anyone whose system asks for reduced motion.

You choose where it lives, in Source → Appearance: as the page background, as a panel of its own (Field, where it is drawn sharper and can be watched properly), as both, or as neither. Panels keep a dark scrim over it either way, so text stays readable whatever the waves do.

4. Hand measurements

0 wrist1234 5678 9101112 13141516 17181920 span thumb↔index

MediaPipe gives 21 points per hand. Tips are 4 8 12 16 20 (thumb → pinky), knuckles (MCP) 1 5 9 13 17, middle joints (PIP) 2 6 10 14 18.

Span is the wrist-to-middle-knuckle length. Every distance is divided by the span, so a pinch or a swipe measures the same whether your hand is near the camera or far away. Distances in these docs are therefore in hand-size units: 1.0 = one span.

FeatureRangeWhat it is
curl (×5)0 … 1How bent each finger is: the angle at its middle joint, 0 = straight, 1 = fully bent. The thumb uses joint 3 and is noisier. The angle→0…1 mapping is set by the finger straight / bent sliders.
openness0 … 11 − the average curl of index…pinky. 0 = fist, 1 = all fingers straight.
pinchRaws0 … ~1.5Thumb-tip to each fingertip distance, in spans. About 0.1 when touching.
pinch0 … 1Thumb↔index distance rescaled between the closed and open sliders: 0 = touching, 1 = wide open.
tilt−180° … 180°Palm rotation on screen: the direction from wrist to middle knuckle. 0 = fingers up, positive = leaning right.
span~0.1 … 0.5Hand size on screen. Bigger = closer to the camera.
centroid0 … 1Palm centre (wrist + four knuckles) as a fraction of the frame. Used by movements.

MediaPipe's depth value z is noisy, so it is used only inside joint angles, never as a distance. Features are computed after smoothing.

5. Engagement

Engagement is a safety catch. A hand in view is not trusted to do anything until it shows the engage pose (default: open palm, held 500 ms). Until then:

  • pinch and movement events never fire;
  • pose events still fire, since they only describe the hand's shape.

Once engaged, the hand stays engaged whatever it does, until it leaves the camera view (see Losing a hand). This is what stops typing, scratching your face or reaching for a mug from firing gestures.

On screen the outer ring fills amber while you hold the engage pose and turns green when engaged; the skeleton turns green too. Change the pose, or switch the catch off, in Tuning → engage pose.

6. Pinch

For each enabled finger the library measures the thumb-tip distance in spans. With the default settings:

StepRule
closethe nearest enabled finger comes below 0.20 (closed + hysteresis). Its bar turns amber and the ring appears.
settlewhile the ring fills, the thumb may slide to a neighbouring finger; the nearest one is the candidate and the timer keeps running.
startclosed for 300 ms (dwellMs) and the hand is engaged → pinch:start with finger. That finger is now locked and turns green.
moveevery following frame → pinch:move with the same finger and a 0…1 value.
endthat finger's distance goes above 0.30 (closed + 3×hysteresis), the hand is lost, or the finger is disabled → pinch:end.
thumb ↔ finger distance time → release line 0.30 close line 0.20 closes start (+300 ms) end wobbling between the lines changes nothing

If the thumb opens again before the 300 ms are up, nothing fires and the timer resets. Enable or disable fingers in Tuning → pinch fingers.

7. Poses (hand shapes)

A pose is a list of allowed curl ranges per finger. Fingers not listed are ignored.

{ "name": "fist", "fingers": {
    "index":  { "curl": [0.75, 1] }, "middle": { "curl": [0.75, 1] },
    "ring":   { "curl": [0.75, 1] }, "pinky":  { "curl": [0.75, 1] } } }
  • Score: each listed finger scores 1 if its curl is inside the range, fading to 0 when it is pose falloff (0.15) outside. The pose score is the average, shown in the ≈ rows of the hand panel.
  • Held pose: the highest-scoring pose that reaches its minScore (default 0.8). Its name goes amber. Equal scores go to the more specific pose — the one constraining more fingers, then the one with narrower ranges — so a thumbs-up beats a fist, which says nothing about the thumb.
  • Event: when the held pose stays the same for dwellMs (300 ms), one pose event fires and the name turns green. It fires again only after the pose changes.

Built in: fist, openPalm, point. Make your own in Gestures → Record a pose, which reads your real finger curls over 15 frames and saves ranges of curl ± tolerance. A larger tolerance is more forgiving but may overlap other poses; include the thumb only when it matters, such as a thumbs-up.

8. Movements

A movement describes how the palm centre travels. It fires once when satisfied, and only while engaged and not pinching, so a pinch-drag never doubles as a swipe.

FieldMeaning
axisx sideways · y up/down · depth toward/away from the camera (the hand gets bigger or smaller) · tilt twisting the palm
direction1 = right / down / toward camera / clockwise on screen; -1 = the opposite. Left and right are as you see them with mirror on.
distancehow far one stroke must go: spans for x and y; relative size for depth (0.25 = 25 % bigger); radians for tilt (0.5 ≈ 29°)
withinMsthe whole movement must fit in this time, so slow drifting never counts
reversals0 = one stroke (swipe, push, twist). 3 = four strokes back and forth (a wave); direction is then ignored
poseoptional: only movement made while holding this pose counts
cooldownMsoptional: minimum time before the same movement can fire again (default 500)
{ "name": "swipeUp", "axis": "y", "direction": -1, "distance": 1.5, "withinMs": 400 }
{ "name": "wave", "axis": "x", "reversals": 3, "distance": 0.5, "withinMs": 1500, "pose": "openPalm" }
{ "name": "push", "axis": "depth", "direction": 1, "distance": 0.3, "withinMs": 500 }

Built in: swipeLeft, swipeRight (1.5 hand-lengths within 400 ms) and wave. The ↝ rows in the hand panel show each movement's progress from 0 to 1 in amber; watch them while you move to see how close you are, then adjust distance or time.

9. Losing a hand

If a hand disappears for more than 150 ms (lostAfterMs), the library closes everything in order: pinch:end if pinching, disengage if engaged, then lost, and forgets that hand. A shorter gap — a finger passing in front, a blurry frame — changes nothing, so a drag survives brief occlusion. Raise the value if tracking drops out often; lower it if a hand that has left the frame feels sticky.

10. The workspace

Every panel is a dockable window (built with dockview):

  • Drag a tab onto another panel's edge to split, or onto its tab strip to stack. Drag it out to float it.
  • Drag the divider between panels to resize. Double-click a tab to maximize its group.
  • Close a panel with the × on its tab; reopen it from the Panels menu in the header, which also has Reset layout.
  • The layout is remembered in this browser.

Header

camresolution in use, or off
delegatewhere MediaPipe runs: GPU (faster) or CPU. "CPU (GPU failed)" means the GPU could not be used.
fpscamera frames analysed per second
detecttime MediaPipe spends per frame
coretime gesturecore spends per frame, normally well under 1 ms
mirrorflip the image like a mirror, so moving your hand right moves it right on screen
swap L/Rswap only the Left/Right label. Independent of mirror; either one resets tracking.

Keys, ignored while typing in a field: Space pause or resume detection · R reset tracking · F mark a false activation during a reliability session.

11. The panels

Camera

  • Grey skeleton: the raw points from MediaPipe. Cyan skeleton: the same points after smoothing, turning green once the hand is engaged. The gap between grey and cyan is the smoothing lag; the grey jitter is what smoothing removes.
  • Outer ring: engage timer. Inner ring: pose timer. Small ring between thumb and finger: pinch timer. Each fills amber and turns green when it completes.
  • The label under the hand reads: hand · pose · engaged · PINCH finger.

Events

Newest first: time in seconds, hand, event. Activations are green, releases white, lost red. Consecutive pinch:move events are folded into the pinch:start line as "moves ×N"; tick each pinch:move to list them individually.

Hand · R and Hand · L

One panel per hand, dimmed when that hand is not visible. Badges show engaged, pinch (with finger) and the held pose — amber while held, green once its event has fired.

pinchthumb↔index openness, 0 touching … 1 open
thumb→i m r pfour bars: thumb distance to index, middle, ring, pinky. Ticks mark the close and release lines. A bar goes amber when it crosses the close line and green while that finger is pinched; disabled fingers are grey. The number is the nearest finger.
openness, curl ×5see Hand measurements
tiltgrows from the centre: right = leaning right
span, centroidhand size; palm position
engage / pinch / pose dwellthe three timers as bars, amber filling to green
≈ namescore of every pose; the held one is amber
↝ nameprogress of every movement, 1 = fires

Tuning

A control for every library setting; changes apply on the next frame. Reset to defaults, Copy config (to keep or share a tuning) and Import… (paste one back).

SettingDefaultEffect
minCutoff1.0 Hzsmoothing when the hand is still. Lower = steadier but laggier.
beta0.007how much smoothing relaxes during fast movement. Higher = less lag. The default is very low; 5–20 is worth trying.
dCutoff1.0 Hzsmoothing of the speed estimate used by beta. Rarely needs changing.
pinch fingersallwhich fingers can pinch
closed0.15thumb distance counted as touching. Close line = closed + h, release line = closed + 3h.
open0.75distance counted as fully open; only affects the 0…1 pinch value
hysteresis h0.05gap between close and release lines. Bigger = less flicker, harder to release.
engage poseopenPalmthe pose that engages a hand, or none
dwellMs300hold time for pinch start and pose events
engage dwellMs500hold time for engaging
lostAfterMs150how long a hand may vanish before it is dropped
finger / thumb straight, bent0.25 / 2.4, 0.2 / 1.4 radjoint angles counted as curl 0 and curl 1. If your straight fingers read 0.2, raise straight; if a tight fist reads 0.6, lower bent.
pose falloff0.15how fast a finger's score drops outside its range. Bigger = more forgiving poses.

Moves

The catalogue: every gesture the core currently knows — the engage gate, each pose, each pinch finger and each movement — with how to perform it and the numbers it is judged by. A row reads now while that gesture is happening, so it doubles as a cheat sheet you can practise against. It lists whatever is in the config, so gestures you add appear here too.

Gestures

Four ways in, and all of them end as the same plain data. A pose or movement whose name already exists is replaced, and Apply on the JSON boxes says exactly what is wrong if anything is.

  • Record a pose — hold the shape. The spread of your curls over the hold sets the tolerance, and a finger that wandered is left unconstrained rather than pinned to a meaningless range. Tick include thumb whenever the thumb is what makes the shape different.
  • Record a movement — perform it a few times instead of describing it. See below.
  • Build a movement by hand — fill in the fields yourself.
  • JSON — paste or edit either list directly.

Learning a movement by demonstration

Describing a movement in numbers is guesswork; showing it is not. Give it a name, choose how many takes, and perform the movement. From those takes the bench works out:

  • which axis carried it — sideways, up/down, depth or twist — judged in units of a typical movement on each axis, so a big twist is not mistaken for a small drift;
  • how many strokes it took, dropping the half-strokes at the start and end of the recording;
  • a distance comfortably under your smallest demonstration, and a time comfortably longer than your slowest.

It then replays your takes against the movement it just wrote and loosens it until every take would have fired, so a learnt movement always recognises the demonstrations it came from. The result is shown before anything is added: per-take scores, the axis comparison, and a warning when the axis was not clear-cut — that usually means a bigger, cleaner movement is needed.

Nothing is trained and nothing leaves the machine. The fitted movement is ordinary JSON you can read, tune and share, exactly like one written by hand. The same fitting is part of the library (fitMotion, fitPose), so an app can offer "record your own gesture" without the bench.

Sound

The gesturecore-sound brick. It only listens to what the core produces, so the core behaves identically with it on or off. Browsers keep audio silent until you ask, so nothing sounds until you press Turn sound on.

  • Cues — short synthesized sounds for events: a rising tone on engage, a blip per pinch finger (each finger its own note), a click on release, a chime for poses, a swoosh for swipes. Edit them as JSON; on is the event type, and hand, name and finger narrow it.
  • Theremin — while your right hand pinches, its height plays notes on a pentatonic scale (hard to make sound wrong), spreading the other fingers makes it louder, and moving sideways changes the tone.
  • Controls — the general form of the theremin: any hand measurement steering pitch, volume or brightness while a condition holds.
  • Hear the sounds — preview every built-in sound.

Every sound is synthesized on the spot; there are no audio files. The decisions — which sound, which note, how loud — are made by pure code that is tested without audio, and only a thin layer talks to the Web Audio API.

Head

The gesturecore-head brick: a head and face reader. It takes MediaPipe's Face Landmarker points from the same camera frames and turns them into events the same way the core treats hands: measurements, then held states with a dwell and hysteresis, then movements. It is off until you switch it on, because the face model is a download of its own and costs time on every frame.

  • yaw, pitch, roll — where the head points, in degrees: turning to your right, looking up, and tilting toward your right shoulder are positive. They come from fitting MediaPipe's canonical face to the forehead, nose, eye corners and sides of the face, so talking or smiling does not move them.
  • blink L / R — how closed each of your eyes is, from the eyelid points (0 open, 1 shut). Left and right are your own, whichever way the view is mirrored.
  • brows, frown, mouth, smile — from MediaPipe's expression scores (blendshapes), averaged over both sides.
  • ≈ expressions — held states: brows raised, frown, mouth open, smile, blink, wink left and right, turn, look up or down, tilt. Each fills amber while it holds and turns green when it starts; the log shows expression:start and expression:end with how long it lasted.
  • ↝ movements — nod (down and back up) and shake (side to side), found by the core's own stroke detector running on the head angles.
  • Set neutral — makes your usual pose zero and your resting eyes "open", for a camera that is off to one side or eyes that read half closed.

Everything — the settings, the expressions and the movements — is one JSON object in the panel, and the defaults are first guesses to be tuned against real faces. Like the core, the reader is pure code tested without a camera, on synthetic faces turned by exactly known angles.

Reliability

The definition-of-done measurement: fewer than 2 unintended activations per 10 minutes. Start a session, work normally for ten minutes, and press F whenever a pinch or movement fires that you did not mean. Finished sessions are listed underneath. This is a measurement of the core's defaults, not a feature.

Fixtures (test captures)

A fixture is a hand the automated tests replay, so a change in the maths shows up as a failing test rather than a surprise. It is not a picture and not a gesture definition — for the gestures themselves see Moves.

One snapshot is a weak test. Hand tracking is at its best square-on to the camera and degrades on angled and distant views, which is exactly where a gesture quietly stops being recognised. So a fixture holds several views of the same shape — front, turned left and right, tilted up and down, near and far — and the tests require every view to read as the same pose. Views you skip are simply not tested; each one you add tightens the net.

Press Capture every view and follow the prompts, or capture single views with their own buttons. Each view averages several frames. A view that disagrees with the others is flagged in amber before you save it — that disagreement is the finding, and usually means the pose's curl ranges are too tight rather than that the capture is bad. Save to test/fixtures writes the file; then run npm test.

The tests also check the same thing synthetically: each fixture is moved, resized, rotated and mirrored, and must still read as the same pose. That covers what maths can cover; real captures cover what it cannot, namely the tracker's own behaviour at awkward angles.

Source

Camera device and resolution (lower resolution can raise fps), GPU/CPU, and MediaPipe's own confidence thresholds: detection for finding a new hand, presence for deciding a tracked hand is still there, tracking for trusting frame-to-frame tracking. Raising them reduces phantom hands but drops real ones more often. The overlay toggles hide the raw skeleton, the smoothed skeleton or the image.

12. Tuning guide

SymptomTry
Skeleton jitters when stilllower minCutoff
Skeleton trails behind fast movementraise beta (try 5, then 20)
Pinch feels slow to startraise beta; lower dwellMs to about 200
Pinch does not trigger although fingers touchwatch the thumb→ bars: if they never cross the tick, raise closed
Accidental pincheslower closed, raise dwellMs, disable fingers you do not use, keep engagement on
Pinch drops during a dragraise hysteresis; raise lostAfterMs if the hand flickers out
Wrong pose, or flickering between posescheck the curl bars; adjust finger straight/bent, narrow the pose ranges, or raise its minScore
Pose never recognisedraise pose falloff, widen the ranges, or re-record with more tolerance
Swipe does not firewatch its ↝ bar: lower distance or raise withinMs; make sure you are engaged and not pinching
Swipe fires by accidentraise distance, lower withinMs, or require a pose
Left and right swipes reversedturn mirror on

Change one thing at a time and watch the numbers. When a tuning feels right, Copy config and keep it: those values are what should become the library defaults.

13. Using it in code

import { GestureCore } from 'gesturecore';

const core = new GestureCore({ aspect: 640 / 480, engage: { pose: 'openPalm', dwellMs: 500 } });

// every frame: hands from MediaPipe, time in milliseconds
for (const e of core.update(hands, performance.now())) {
  if (e.type === 'pinch:start') console.log(e.hand, 'pinched', e.finger);
  if (e.type === 'motion' && e.name === 'swipeLeft') history.back();
}

core.getFeatures('Right');   // latest measurements, or null
core.getHandState('Right');  // engaged, timers, pose scores, movement progress
core.setConfig({ dwellMs: 200 });   // live change, same as the sliders
EventExtra fields
engage / disengage–
pinch:start / pinch:endfinger
pinch:movefinger, value 0…1
posename
motionname
lost–

Every event carries type, hand ('Left' | 'Right') and t. Within one frame, hands come Left then Right. Every pinch:start gets exactly one pinch:end, and every engage one disengage. The config is plain JSON — exactly what Copy config gives you. More in the project README.

14. Troubleshooting

"Camera permission was denied"allow the camera from the icon in the address bar, then press Start camera again
Black video, or camera busyanother app may hold the camera; close it, or pick a different device in Source
Low fpsSource → resolution 320×240; close other tabs; check that delegate says GPU
Hand labelled the wrong sidetick swap L/R
Nothing engagesTuning → engage pose: if it says "(missing!)" that pose was deleted; pick another, or none
Hands flicker in and outbetter lighting, plainer background; lower the detection/presence confidence slightly; raise lostAfterMs
Could not start: model errorthe model file is missing: run npm run fetch-model, then reload
Everything feels off after experimentingTuning → Reset to defaults; Gestures → Reset; R resets tracking; Panels → Reset layout

15. Files and saving

  • Saved in this browser (localStorage): the config (settings, poses, movements), bench preferences, the panel layout and session history. Another browser starts from defaults; clearing site data resets everything.
  • Written to the project: only fixture captures, in test/fixtures/.
  • Project layout: src/ the library (no browser APIs allowed), test/ the automated tests, bench/ this page, README.md developer notes.
  • Commands: npm run bench starts this page, npm test runs the tests, npm run fetch-model downloads the hand model.

↑ back to top