Skip to content

perf: skip full decode of non-EVENT frames on the offload gesture path (#47 follow-up)#64

Merged
ryanbr merged 2 commits into
mainfrom
perf-offload-gesture-prefilter
Jul 8, 2026
Merged

perf: skip full decode of non-EVENT frames on the offload gesture path (#47 follow-up)#64
ryanbr merged 2 commits into
mainfrom
perf-offload-gesture-prefilter

Conversation

@ryanbr

@ryanbr ryanbr commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Follow-up to #47/#63. The bigger of the two decode redundancies I flagged.

Problem

FrameRouter.dispatchLiveGestureIfFresh fires for every frame on the historical-offload path — thousands of type-47 records over a multi-minute sync — purely to catch a rare EVENT gesture (double-tap / wrist). It fully parsed each one (CRC32 + FieldBuilder allocation) only to discard it at guard parsed.typeName == "EVENT". Offloads carry far more frames than the ~1 Hz live stream #47 targeted, so this is the larger win.

Fix

  • New WhoopProtocol.frameTypeName(_:family:): the packet type name only — no CRC verify, no FieldBuilder. Family-aware (inner type byte at [4] on WHOOP4, [8] on 5/MG, using the same lookup each parse path uses).
  • dispatchLiveGestureIfFresh now pre-checks frameTypeName(...) == "EVENT" and skips the full parse for the ~99% that aren't. Byte-identical: an EVENT frame still gets the full parse + CRC guard below; a non-EVENT frame was dropped at the typeName guard anyway.

Verification

  • Ran locally (WhoopProtocol is a pure package — no Compression wall): a new FastPathParityTests case proves frameTypeName == parseFrame(...).typeName across the whoop4 parity + historical corpora and the whoop5 hardware vectorspassing. So the pre-filter's == "EVENT" guard provably can't diverge from the full-parse guard.
  • CI compiles the app-target FrameRouter change.

Scope / caveat

Follow-up to #47.

ryanbr added 2 commits July 8, 2026 16:03
… path (#47 follow-up)

FrameRouter.dispatchLiveGestureIfFresh runs for EVERY frame on the historical-offload path
(thousands of type-47 records over a multi-minute sync) purely to catch a rare EVENT gesture
(double-tap / wrist). It fully parsed each one — CRC32 + FieldBuilder allocation — only to
discard it at `guard typeName == "EVENT"`. Offloads carry far more frames than the ~1Hz live
stream #47 targeted, so this is the bigger decode redundancy.

Add WhoopProtocol.frameTypeName(_:family:): the packet type NAME only, no CRC/FieldBuilder,
family-aware (inner type byte at [4] on WHOOP4, [8] on 5/MG, same lookup each parse uses).
dispatchLiveGestureIfFresh now pre-checks `frameTypeName == "EVENT"` and skips the full parse
for the ~99% that aren't. Byte-identical: an EVENT frame still gets the full parse + CRC guard;
a non-EVENT frame was dropped at the typeName guard anyway.

Verified LOCALLY (WhoopProtocol has no Compression wall): a new FastPathParityTests case proves
frameTypeName == parseFrame(...).typeName across the whoop4 parity + historical corpora and the
whoop5 hardware vectors — passing. So the pre-filter can't diverge from the full-parse guard.

iOS/macOS only — Android dispatches gestures INSIDE handleFrame (entangled with routing) and,
post-#47, already parses offload frames once, so there's no isolated pre-filter to add there.
NEEDS the same on-strap sanity check as #47 (gestures still fire mid-offload) before merge.
…er drops a gesture (#47 re-review)

The parity test asserted strict frameTypeName == parseFrame.typeName. That's right for
valid frames but would FALSE-FAIL on a malformed/short frame (frameTypeName returns nil;
parseFrame calls it 'INVALID/FRAGMENT') — and it left malformed frames untested, the exact
case where a false-negative would silently drop a gesture on real BLE (fragments/corruption).

Assert the two things that actually matter: exact typeName for VALID frames, and the EVENT
*decision* for ANY frame (the property the pre-filter's guard rests on). Add explicit empty /
short / wrong-SOF / short-5MG cases — all land on 'not EVENT' in both paths, so the pre-filter
skips exactly as the full parse would. Passes locally.
@ryanbr ryanbr merged commit cc9d94d into main Jul 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant