Skip to content

NOOP 10.5.0

Choose a tag to compare

@github-actions github-actions released this 20 Aug 10:09

NOOP v10.5.0

A release about seeing your training as a shape over time, getting numbers that used to need a tape
measure, and — most of all — your phone doing far less work to tell you the same things.

Training load

CTL, ATL and form (#1423, #1425). NOOP now models chronic and acute training load and the balance
between them, and draws all three on a Trends card. Fitness accumulates slowly, fatigue quickly, and the
gap between them is what tells you whether a hard block is building you up or digging a hole. Computed
on-device from the strain you already have; no new data is collected.

Sleep-versus-wake heart rate (#1424). The contrast between your sleeping and waking heart rate,
surfaced as its own signal rather than buried inside recovery.

A VO₂max you can actually get

Estimated without a waist measurement (#1391, #1406, #1393). The previous estimate needed a waist
circumference many people never enter. If NOOP knows your resting heart rate, it can now use the Uth
heart-rate-ratio method from age and sex alone, and it names which method produced the figure. An
estimate that says how it was reached is worth more than one that doesn't.

Battery

Stop re-scoring days that haven't changed (#1005, #1395, #1396, #1402). NOOP re-analysed a rolling
window every fifteen minutes, including days whose data had not moved since the last pass. A per-day
reuse cache now skips that work, on both WHOOP 4.0 and 5.0/MG. This was the largest background drain on
Android and it was reported, measured and chased down with logs by @bartmuskala.

Low refresh, and Power saving where you can find it (#1430, #1431). A sub-option syncs hourly at any
charge instead of every fifteen minutes — fewer reconnections is the biggest saving on a WHOOP 4.0 — and
Power saving moved into More, with the battery threshold now reaching 35%.

Fewer diagnostics running forever. Shadow instrumentation from the R-R over-count investigation was
doing six sorts of a night's beats per night, on every affected strap, four times an hour. Two
measurements that had finished their job were retired, and what replaced them costs one pass.

Apple Health, and your ring

Automatic Apple Health write-back (#1432, thanks @MikaSchultes). Workouts can flow back into Apple
Health without a manual export.

Hourly step import (#1429). iPhone steps arrive hour by hour, with a 90-day backfill, instead of one
lump per day.

More of the Oura ring decoded, and read honestly (thanks @pipiche38). The ring's own breath rate is
decoded and shown as instrumentation rather than scored (#1384, #1450); its step features are decoded as
Tier-B instrumentation (#1359); the skin-temperature "worn" gate now allows for a ring whose heart-rate
and temperature sensors run on independent clocks, which was blanking the card entirely (#1467); and the
Rhythm view says "no data" outright on hardware that cannot produce the reading, instead of showing an
empty chart (#1360).

Honesty and accuracy

A large part of this release is numbers that were quietly wrong, or quietly guessed:

  • A completed sync no longer reports itself as interrupted (#1466). A WHOOP 4.0 routinely ends a
    full night's offload on an idle timeout; NOOP called that "sync interrupted" even when the whole night
    had landed.
  • Diagnostic exports report the strap that actually connected (#1451, #1464). Two logs from WHOOP 5
    straps were headed "WHOOP 4.0", and on Apple the header had never reported a model at all.
  • Battery percent refreshes twice as fast while charging, because that is the one time the number
    moves while you are watching it.
  • The strap reconnect survives suspension (#1421), and a 5.0/MG that does serve history no longer
    thrashes its link every two minutes (#1414, thanks @justinjor-bit).
  • Re-scoring no longer silently skips Oura, watch and re-added straps (#1392, thanks @pipiche38).

Cross-platform correctness

@bhelm contributed roughly thirty fixes in this release, almost all of the same kind: places where
the Swift and Kotlin implementations of one calculation disagreed. Half-tie rounding in recovery traces,
FIT coordinates and connection durations; signed zero surviving a clamp; DST-spanning stress windows;
sleep-stage clipping; malformed UTF-8 in the Oura text decoders; JSON control characters in Android
backups; Unicode normalization of custom lab-marker keys. Individually small, collectively the reason
the two platforms produce the same numbers from the same bytes.

@vishk23 extended the golden decoder oracle past frame decode (#869) — and it immediately found four
places where a 32-bit narrowing on Kotlin would have silently discarded all history from 2038, or today
on a strap with a future-dated clock.

@terry-zeng1134 fixed the AltStore manifest's declared permissions (#1388), and @Duzafizzl built
out the Breathe protocol catalog with holds, session lengths and translations (#1390).

Thanks

To @bhelm, @pipiche38, @vishk23, @MikaSchultes, @Duzafizzl and @terry-zeng1134
for the code; and to @bartmuskala, @justinjor-bit and @pipiche38 for reports carrying logs,
screenshots and measurements — several of which found bugs no test was going to catch.

And a belated thank-you to @gdorgian (#1366), @riebschlaegerstar-sys (#1361) and
@pilleuspulcher-blip (#159), whose reports were fixed in v10.1.0 but went uncredited in that
release's notes.


What's Changed

  • feat(today): add VO₂max as an opt-in dashboard card (#1391) in #1393
  • fix(analyze): re-score gate must fingerprint HR across ALL devices, not "my-whoop" (#1392) in #1394
  • perf(analyzeRecent): per-day in-memory reuse cache — fix #1005 re-score battery drain in #1395
  • perf(analyzeRecent): extend the per-day reuse cache to WHOOP 5.0 / MG (#1005) in #1396
  • fix(steps): honest "connect a phone step source" prompt when a 4.0 has no step reference in #1397
  • fix(storage): count stepSample + ppgHrSample in the decoded-rows footprint in #1398
  • feat(breathe): ANS/Presence protocol catalog, holds, session length, i18n by @Duzafizzl in #1390
  • fix(altstore): declare healthkit.access, location, and motion permissions by @terry-zeng1134 in #1388
  • diag(hrv): size the cross-second R-R over-count with a windowed shadow candidate (#1331/#1118) in #1399
  • diag: capture the 4.0 GET_BATTERY_LEVEL frame in the strap log (#900) in #1400
  • Baseline-relative daytime stress (revives #463) — opt-in + full parity in #1401
  • perf(analyzeRecent): stop the #1005 cache churning on baseline drift during a backfill in #1402
  • diag: add the computed '-noop' spine to the Recent-Nd data line in #1403
  • fix(android): wire the VO₂max card's trend tap-through (#1391) in #1404
  • fix(android): route vo2max_est to its series trend — completes #1404 (#1391) in #1407
  • feat(fitness): offer a VO₂max without a waist via the Uth HR-ratio fallback (#1391) in #1406
  • #1391: settings VO₂max sub-text — note it builds from ~4 nights of HR in #1408
  • #1405: de-collide the Charge synthesis word from the readiness verdict in #1409
  • fix(ble): read the reboot-ack result at the family's offset (5/MG @12) in #1411
  • fix(live-session): guard the upsert so a late start-write can't erase the final row in #1412
  • fix(healthkit): commit the observer anchor only after ingestion, not before in #1415
  • fix(watch): tick the workout clock off the live builder, not sample callbacks in #1416
  • fix(ble): widen the 5/MG liveness fuse to the whole family (#1414) in #1418
  • feat(widget): keep showing the last heart rate through a quiet patch in #1419
  • fix(hrv): hold the spot capture to a real 60-second window by @bhelm in #1417
  • DRAFT: generation-side 0x49-onset keying for Oura sleep (#1284 residual 3) in #1383
  • fix(ble): standing-connect reconnect regime for WHOOP — survive suspension (#1413) in #1421
  • feat(backup): record the app build in exports + on version change (#1410 tiers 1-2) in #1422
  • feat(analytics): CTL/ATL/TSB training-load model (Swift + Kotlin) in #1423
  • Make the v20 optical byte map executable: name its eleven fields, CRC-gate the decoder by @vishk23 in #895
  • feat(analytics): sleep-vs-wake HR contrast (Swift + Kotlin) in #1424
  • feat(trends): CTL/ATL/TSB training-load card (iOS/macOS + Android) in #1425
  • refactor(sleep): split SleepScreen UI into sibling files (re-cut of #734) in #1426
  • fix(ble): pair WHOOP4 clock correlation with its capture wall, not now in #1427
  • fix(settings): waist row label vanished behind ~300dp of dead space (+ copy parity) in #1428
  • feat(apple): hourly step import — per-hour iPhone steps with 90-day backfill (re-cut of #369) in #1429
  • feat(power): "Low refresh" hourly sync, a sub-option of Power saving in #1430
  • feat(power): move Power saving into More (between Test Centre and Settings), offer 35% in #1431
  • diag(rr): pre-storage R-R census + tolerance sweep (#1008/#1118) in #1443
  • fix(hrv): align rolling rMSSD semantics across platforms by @bhelm in #1433
  • fix(stress): use calendar-day windows across DST by @bhelm in #1435
  • fix(breath): clamp negative stage durations by @bhelm in #1436
  • fix(recovery): align trace half-tie rounding by @bhelm in #1437
  • fix(sleep): trim newline wake labels consistently by @bhelm in #1438
  • fix(sleep): align reclip segment validation by @bhelm in #1439
  • fix(recovery): preserve signed zero in forecast clamp by @bhelm in #1442
  • Fix charge driver skin temperature formatting by @bhelm in #1440
  • fix(recovery): align charge driver half-ties by @bhelm in #1441
  • fix(workouts): preserve stored steps during backfill by @bhelm in #1434
  • fix(workouts): carry per-session steps through every row rebuild (#1444) in #1445
  • fix(recovery): keep the charge trace's negative zero, and correct the skin-temp comment in #1446
  • chore(workouts): require steps explicitly at every WorkoutRow construction (#1444) in #1447
  • Add automatic Apple Health write-back by @MikaSchultes in #1432
  • fix(hrv): make the windowed rMSSD gap-aware, like the nightly one already is (#1448) in #1449
  • diag(rr): log the record cadence and per-record fill the census could not show (#1451) in #1452
  • fix(oura): restore the ring's own breath rate as dailyMetric.respRateBpm by @pipiche38 in #1450
  • Fix sleep cache gravity fingerprint collisions by @bhelm in #1453
  • refactor(sleep): one gravity fold, not three copies of it (#1453 follow-up) in #1454
  • Fix Swift SleepStagerV2 shuffled-input clipping by @bhelm in #1456
  • diag(rr): print each sampled beat's ord, the last field that can localise the over-count (#1008) in #1459
  • Align Kotlin Rest sleep-need floor with Swift by @bhelm in #1460
  • fix(analytics): omit Rest for wake-only sessions by @bhelm in #1461
  • fix(coupled): carry through the shared selector on BOTH platforms, so the hero and its sheet agree (#1458) in #1462
  • Add per-chunk historical clock + R-R packing diagnostic (#1008) in #1476
  • Fix doubled and ungrammatical "ago" suffixes in the synced-at text (#1472) in #1479
  • Mark the orphaned 'now' catalogue key stale (#1472 follow-up) in #1480
  • Don't report a productive offload as an interrupted sync (#1466) in #1481
  • Fix Rest trace when analyzeDay has no Rest score by @bhelm in #1463
  • fix(steps): count usable calibration samples by @bhelm in #1469
  • fix(steps): distinguish missing day rows by @bhelm in #1470
  • fix(android): escape backup JSON controls by @bhelm in #1471
  • fix(connection): make duration rounding deterministic by @bhelm in #1473
  • fix(labs): normalize custom marker keys to NFC by @bhelm in #1474
  • Fix FIT coordinate half-tie rounding parity by @bhelm in #1475
  • Fix Swift activity summaries to include positive steps by @bhelm in #1477
  • Reject malformed UTF-8 in Kotlin Oura text decoders by @bhelm in #1478
  • fix(android): render Test Centre live readouts by @bhelm in #1468
  • fix(oura): widen the skin-temp worn-gate timestamp match for devices with independent HR/temp clocks (#1467) by @pipiche38 in #1467
  • doc(oura): correct §6.5 SpO2 overshoot stats - measured on collision-lossy data by @pipiche38 in #1465
  • Stop rebuilding the whole strap log on every Test Centre readout tick in #1482
  • Poll the strap battery every keep-alive tick while charging in #1483
  • test(protocol): extend the golden decoder oracle past frame decode — and fix the four u32 narrowings it found by @vishk23 in #869
  • Assert the widened unix as a Long (#869 follow-up) in #1484
  • Report the strap model that actually advertised, or say unknown in #1485
  • Count deliveries per stored second across a whole night (#1331/#1008) in #1486
  • Retire two superseded R-R shadow measurements (#1331/#1118) in #1487

New Contributors

Full Changelog: v10.1.0...v10.5.0


Downloads — NOOP Staging identity, installs beside the official app (separate data):

  • Android — NOOP-android-v10.5.0.apk (com.noop.whoop.staging)
  • macOS — NOOP-macos-v10.5.0.zip (unzip, then right-click → Open)
  • iOS — NOOP-ios-unsigned-v10.5.0.ipa (AltStore / Sideloadly)