Skip to content

Capture wanted: paired v18 + deep-buffer overnight, and a timed strap-loosening run, to settle the v18 optical/quality fields #858

Open

Description

@vishk23

Splitting this out of #845 so it doesn't get lost in that thread, per @ryanbr's suggestion.

The v18 census in #845 established a coherent per-second signal-quality group — @33 flags, @36 bit 7 validity, @40 (a saturating 0–255 confidence score), @108/@109 amps with a record-level 128 sentinel, and @113 (a graded metric, r = −0.80 with @40). The 128 sentinel predicts the band's own beat-detection failure at 4.09×, and holds at 4.11× within near-zero-motion strata, so it is not merely restating motion.

But that whole result rests on one subject, one night, 5 h 10 m, 99.48 % asleep, near-motionless — and it isn't even the same wrist as the person analysing it (the corpus is @digitalerdude's public PacketLogger capture, gratefully used). Two specific captures would settle what passive re-analysis of that corpus cannot.

Capture A — paired v18 + deep-buffer over the same hours

What: one overnight where the v18 offload frames and the deep-buffer capture are both retained for the same wall-clock hours.

Why: every attempt so far to correlate the v18 optical/quality fields against real motion has failed for a boring reason — the only 100 Hz IMU corpus available (29,204 v21 buffers) is from a different subject four days apart from the v18 corpus. With both streams over the same seconds you can:

  • regress motion out of the optical quality flags properly, instead of relying on the record-internal dynamic_acceleration@41 as its own control;
  • settle dynamic_acceleration@41's scale by paired regression against within-second IMU AC-RMS (if it is in g, the slope should be ≈1.0 with ≈0 intercept; a slope far off suggests it is a filtered activity count, not an instantaneous magnitude);
  • test whether the @82 duty-cycle window (30 s every 20 min, phase-locked at unix mod 1200 == 337 — see WHOOP 5.0 raw HCI capture — where to submit? (docs reference #174, which doesn't exist) #103) is sleep-only or continuous, which needs a full 24 h.

Also needed regardless: a capture that spans a morning wake and daytime. The existing corpus has essentially no wake and zero sleep_state == still, so wake_quality@81 bits 2–3 read zero in all 18,650 records and motion_wear_quality/activity_class@63 never takes value 2. Neither can be characterised at all without awake data.

Capture B — timed strap-loosening

What: wear normally, then in timed 2-minute windows loosen the strap / slide it up the forearm and back, writing down the transition times to the second.

Why: this is the one structural question passive data cannot answer. optical_baseline_106/@107 are two independent u8 channels (#845) whose meaning is unresolved. If they are AGC / LED-drive registers they should step as the sensor loses coupling; if they are detector DC they should drift smoothly. Nothing in a passive night distinguishes those.

A shorter variant of the same idea also resolves onwrist@81 bit 0: take the strap off for 15+ minutes. The current reading is that bit 0 is a ~2-second motion-artifact blip (401 runs, median 2 s, 11× lift for high motion), not a sustained wear state — a long contiguous bit0 = 1 while genuinely off-wrist would overturn that.

Notes for whoever runs it

  • Deep-buffer capture is default-off and costs roughly 28× the offload throughput (124 B/s → 3508 B/s), so this is an opt-in research night, not something to leave on.
  • PuffinDeepBufferLog excludes v18 by size (minBufferBytes = 1000), so retaining v18 alongside needs PuffinFrameRecorder (which keeps everything) or an equivalent.
  • No health values need to be shared to answer the structural questions — the useful output is offsets, transitions and timings.

I can do the analysis on any capture that lands here, and I'm happy to write up the exact recorder settings if that helps. I can't run Capture B usefully myself in isolation — a second strap/wrist is exactly what the single-subject caveat needs.

Related: #845 (the census), #103 (@82 duty cycle), #423 (deep buffers), #520.

Activity

  1. ryanbr commented on Aug 2, 2026

    @ryanbr
    Owner

    Good ask, and the setup notes check out. I verified the code-level claims so whoever runs this does not lose a night to a wrong setting.

    Confirmed against the tree:

    • PuffinDeepBufferLog really does exclude v18 by size — minBufferBytes = 1000, and the guard is frame.count >= minBufferBytes. Same constant, same filter, on both platforms.
    • Capture is default OFF on both, so this is opt-in as you say.
    • The frame recorder really does keep everything.

    Two things that make it simpler than the notes imply.

    One toggle, not two. PuffinDeepBufferLog gates on PuffinFrameRecorder.enabledKey — the same switch. So "retaining v18 alongside" needs no second step: turning on raw capture gives you the all-frames log and the deep-buffer log over the same hours, which is exactly Capture A's requirement. Worth stating plainly, because the current phrasing ("needs PuffinFrameRecorder … or an equivalent") reads as though a volunteer has to go find something else.

    Capture A is runnable on Android too. I nearly told you it was not: there is no PuffinFrameRecorder.kt, so a volunteer searching for that class comes up empty. But the equivalent is there under a different shape — the isCaptureEnabled writer in WhoopBleClient, which "records every frame of a 5/MG backfill session as one JSONL line (parsed fields + raw hex)", and PuffinDeepBufferLog.kt carries the identical minBufferBytes = 1000. So both streams are available on Android; only the class name differs. Given you are asking for a second subject and a second wrist, not ruling out half the potential volunteers matters.

    What I did not check: the 28× throughput figure, unix mod 1200 == 337, and the #845 statistics. Those come from capture analysis rather than the code, and the issue is already explicit that they rest on one subject, one night, someone else's wrist.

    On the design itself — Capture B is the strong one. "AGC/LED-drive registers should step when coupling is lost; detector DC should drift" is a real discriminating test, and you are right that no amount of passive night distinguishes them. The 15-minute off-wrist variant for onwrist@81 bit 0 is the same shape: a sustained bit0=1 would overturn the current 2-second-artifact reading, and nothing in the existing corpus can.

    The wake/daytime gap is the part I would put first if a volunteer only has one night in them. wake_quality@81 bits 2–3 reading zero across all 18,650 records is not a finding, it is an absence of data, and the same for activity_class@63 never taking value 2. A single capture spanning a morning would move three fields from uncharacterised to characterised, which is more per hour of someone's sleep than the paired-IMU regression buys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions