Skip to content

Gen5 live R-R calculation discards packet boundaries and timestamps #286

Description

@DropTabl

Summary

The two live R-R consumers in Edge—guided-breathing coherence and the
pre/post breathing-window RMSSD—currently flatten every decoded interval into
one List<double>. In doing so they discard the type-40 packet timestamp and
make valid zero-count packets invisible.

Gen5 R-R emission is sparse. Intervals declared inside one packet are ordered
and consecutive, but a zero-count packet or timestamp discontinuity means the
consumer cannot assert continuity. Cross-packet beat adjacency is also not
currently proven merely because two packet timestamps are one second apart.

The current flat list can therefore:

  • add an RMSSD difference between intervals that were not successive; and
  • give cardiac coherence a cumulative sum(RR) clock with the real gaps
    removed.

This concerns how Edge consumes already-decoded Gen5 data. It is not a new or
unknown wire field, and it is not an attempt to reproduce a WHOOP calculation.

Current behavior

At Edge d4d4cdeb193154bd6de918007de8367c7876e6ab:

  • AppState._onLiveFrame() buffers type 0x28 and 0x2B frames during a
    breathing session/window;
  • _spotCheckCompute() and _breathingCoherenceCompute() call
    realtimeRr() for every buffered frame;
  • only non-null results survive;
  • every surviving interval is appended to one rrMs list;
  • RealtimeRrResult.ts is discarded; and
  • both paths call correctRr(rrMs) without rrTsMs.

Source:

At the protocol commit pinned by Edge,
realtimeRr() returns null when the declared interval count is zero. The
caller therefore cannot distinguish a valid empty type-40 packet from a frame
that contains no R-R record at all.

The pinned protocol source already documents the relevant consumer rule: the
series is gapped, intervals from non-adjacent seconds are not successive, and a
naive RMSSD must not bridge them.

The analytics implementation also documents that omitting rrTsMs constructs
a cumulative R-R clock and splices sensor dropouts out of the record:

Minimal example

timestamp 100: [800, 810]
timestamp 101: zero intervals
timestamp 102: [760, 770]

Edge currently builds:

[800, 810, 760, 770]

RMSSD then sees the differences +10, -50, +10. The -50 term treats
810 and 760 as successive even though the valid zero-count packet between
them explicitly prevents that claim.

The coherence path has a related problem: because the packet timestamps are
not passed through, it constructs its time axis by cumulatively summing the
reported intervals. Any unreported wall time disappears from the spectrum.

Affected features

  • Guided-breathing cardiac coherence.
  • Pre/post quiet-window RMSSD.
  • Persisted breathing_session.coherence, pre_rmssd and post_rmssd
    values derived from those live windows.

The historical sleep, recovery, nightly HRV, stress and respiration pipelines
are not affected. Those use the historical substrate and already retain R-R
timestamps.

Confirmed requirements

Any correction should:

  1. Preserve the timestamp and validity of a Gen5 type-40 packet even when its
    declared R-R count is zero.
  2. Treat zero-count packets, timestamp gaps, duplicate timestamps and
    out-of-order packets as boundaries.
  3. Never form an RMSSD successive-difference term across an unproven boundary.
  4. Never silently delete real wall-time gaps from the coherence time base.
  5. Return an unavailable/absent result when the available input cannot support
    the calculation. It must not substitute or fabricate a score.
  6. Stop routing Gen5 type-43 (0x2B, revision-21 IMU) through the breathing
    R-R buffer. It currently contributes no intervals, so this is cleanup and a
    future-regression guard rather than the source of the present corruption.
  7. Leave the existing historical pipelines and Gen4 R10 behavior unchanged.

Design decision needed

The input bug is confirmed, but the correct policy for sparse live Gen5 data
should be agreed before implementation. In particular, consecutive packet
timestamps alone do not prove that the final interval in one packet and the
first interval in the next packet represent successive detected beats.

Possible policies include:

  • calculate RMSSD only from differences whose within-packet adjacency is
    known, combining those squared differences only if the method is documented;
  • require a separately proven contiguous sequence and otherwise abstain; or
  • disable the live coherence score on Gen5 when a trustworthy continuous time
    series is unavailable, while retaining the breathing guide itself.

Coherence and RMSSD do not necessarily need the same policy: RMSSD can combine
known successive differences without inventing a cross-boundary term, whereas
a spectral coherence score makes a stronger claim about the time axis.

There is also a persistence question: breathing_session currently has no
method-version column, so pre-fix and post-fix values would be
indistinguishable. That should be decided as part of the implementation scope.
This change does not alter versioned day_result output, so a day-algorithm
version bump does not appear necessary.

Acceptance criteria

  • A valid zero-count Gen5 type-40 packet remains observable as a boundary.
  • Timestamp gaps, duplicates and out-of-order input cannot create false
    adjacency.
  • No RMSSD term crosses an unproven packet/series boundary.
  • Coherence uses a defensible real-time axis or returns unavailable.
  • Insufficient data produces NULL/unavailable, not a fallback number.
  • Gen5 type-43 IMU never enters R-R computation.
  • Historical HRV/sleep/recovery/respiration output and Gen4 R10 handling remain
    unchanged.
  • Regression tests use synthetic packets and contain no private health data or
    reverse-engineered source material.

Suggested regression tests

  1. Intervals within one valid packet retain their declared order.
  2. A valid zero-count packet creates a hard boundary.
  3. A missing timestamp creates a boundary.
  4. Duplicate and out-of-order packets cannot extend a series.
  5. The example above never creates the 810 -> 760 RMSSD term.
  6. Sparse input below the chosen policy's minimum returns unavailable.
  7. Coherence never receives a fabricated gap-free time axis.
  8. A Gen5 revision-21 type-43 frame contributes no R-R data.
  9. Existing historical and Gen4 fixtures remain unchanged.

Non-goals

  • Redesigning live HR/IMU ownership or BLE enable/disable sequencing.
  • Changing Gen5 optical opcode behavior.
  • Changing the realtime wearing-byte interpretation.
  • Modifying historical sleep, recovery, HRV, stress or respiration.
  • Claiming parity with an official WHOOP metric.
  • Publishing captures, decompiled application source, firmware, disassembly or
    personal health data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions