Chapter 4 · verification

Verification — proving the work

Handicap scoring only means something if reps and effort are real. Lane 7 ships two verifiers in the first-party app today: camera rep counting (MoveNet pose detection, 100% on-device) and BLE heart-rate straps (any GATT strap, scored as Karvonen %HRR). Both feed fields the engine already scores.

Camera rep counting

live

Point the phone at yourself side-on, pick the exercise, do the set — the app counts reps live and logs the set as verified:true. No frame ever leaves the device.

Camera verification affordance in the app
app-camera-note.png · the camera-verify route offered at logging time

How it works — the pose layer. The verifier lazily injects TensorFlow.js + MoveNet Lightning (~1MB, UMD builds via CDN) the first time a verifier opens — never on app boot, never bundled. Every frame runs through MoveNet locally; the counter consumes only keypoints.

How it works — the counting state machine. Pure logic, unit-tested headlessly in count.ts: one tracked joint angle per exercise, two thresholds (hysteresis) plus a persistence window —

angle < downAngle enter "down" (timestamped)
angle > upAngle "up"; if down persisted ≥ 300ms1 rep
ExerciseTracked joint (vertex)down thresholdup threshold
Push-upsshoulder → elbow → wrist< 90°> 160°
Squatship → knee → ankle< 100°> 150°

Anti-jitter rules, all enforced in code: keypoint confidence floor 0.35 (both sides averaged when both clear it); the 300ms debounce rejects twitchy half-reps; a tracking dropout longer than the debounce invalidates the phase (the person may have moved anywhere while untracked) — brief dropouts are tolerated. The Good-GYM angle-threshold approach (MIT), kept DOM-free so it can be reused by future verifiers (watch, IMU).

Confirming the counted set logs it straight into the match; the leaderboard's verified % rises with it.

lives: apps/web/src/verify/camera.ts (sheet + MoveNet) · apps/web/src/verify/count.ts (pure logic)
proven by: counter unit tests + e2e camera mocks (--use-fake-device-for-media-stream) · figma-app surface in e2e.mjs

Heart-rate straps (%HRR)

live

Pair any standard chest/arm strap over Web Bluetooth; the app scores each set's effort as Karvonen %HRR and attaches avgHrrPct to the entry — the exact field the v2 handicap blend consumes.

First-party app match screen where verified entries land
webapp-match.png · the /app build where both verifiers live; verified % shows per player

How it works. Connects to the standard GATT Heart Rate Service (0x180D → measurement characteristic 0x2A37) — Polar H10, Garmin, Wahoo, anything that broadcasts it; no vendor API needed. Per-session it averages notifications into:

%HRR = (bpm restingHr) / (maxHr restingHr) × 100, maxHr = 220 age

Settings (resting HR default 60, age default 35 → max 185) persist in localStorage["rwf.hr.v1"] and are clamped to sane ranges. Graceful by design: unsupported browser (iOS Safari), user cancel, and mid-session disconnect (partial averages are kept) all resolve cleanly — verified reps never block a workout.

The engine side: an entry carrying avgHrrPct plus a learned player baseline switches that entry's multiplier to the 70/30 measured-vs-declared blend (see Game Rules → tier handicap), with baseline drift ≤10% per update as anti-sandbagging.

lives: apps/web/src/verify/hr.ts · engine blend in apps/figma-app/engine.js effortMultiplier · baseline logic in game-core/src/baseline.ts
proven by: Karvonen + settings tests; blend math covered in engine tests

The phased verification plan

P2/P3 parked
PhaseLaneStatus
P1In-browser MoveNet camera counting + Web Bluetooth HR strapsBUILT — live in the app, entries log verified:true / avgHrrPct
P2HealthKit / Health Connect history importphased (docs/05) — native wrapper required
P3WHOOP / Garmin cloud cross-checkphased (docs/05)
Custom ML rep countingdeliberately NOT a blocker — MoveNet covers MVP

Privacy stance: inference is 100% local, no frame ever leaves the device, and HR is averaged per-set rather than streamed anywhere. The wearable research trail (chest-strap accuracy, camera angles) is documented in docs/05_RESEARCH_WEARABLES.md.

plan: docs/05_RESEARCH_WEARABLES.md · agents/07-verification-wearables/BRIEF.md · docs/04_RESEARCH_OPENGYM.md (the counting approach's lineage)
← backBots