Haptics: stop the app claiming things the 5/MG buzz cannot do (#926) - #973
Merged
Conversation
From vishk23's finding, whose claims I verified against the tree — all of them
hold.
On a 5/MG the buzz payload is a hardcoded 12-byte literal whose byte[11]
(overallLoop) is 0, so the caller's repeat count never reaches the wire. All
four BuzzPattern choices produce the same buzz, and the picker offered them
ungated on BOTH platforms. A control that silently does nothing is worse than
an absent one: the user concludes the strap is broken rather than the feature
unsupported.
The picker keeps all four — the choice is stored per app and DOES apply to a
WHOOP 4.0, so hiding it would lose a real setting — and now says so, following
the honesty wording SmartAlarmView/SmartAlarmScreen already use for their own
5/MG-unconfirmed case.
Deleted AppModel.buzz(pattern:loops:): zero callers, and its docstring claimed
"used by the notification-pattern picker" when the picker calls buzz(loops:).
Corrected two claims that were becoming folklore:
- BLE_REVERSE_ENGINEERING's "7 presets" describes the STRAP's own table, read
via GET_ALL_HAPTICS_PATTERN (80) — a command neither platform has ever sent.
NOOP has never enumerated it. What the app exposes is four, all patternId 2.
- MaverickHaptics.notificationBuzz now says plainly that nothing ships it and
why it is kept: it is the only code that can build a variable-loop body, so
it is what a future overallLoop != 0 test would need.
The payload itself is deliberately untouched. The golden frame is hardware-
confirmed at 0, nobody has tried non-zero on 0x13, and the alarm path already
ships 7 — so the field is accepted non-zero, but proving it on the buzz wants a
staged probe, not a blanket change to a proven payload.
Re-review catch, and it was a parity bug I introduced. Android's `live.whoop5Detected` is reset to false on every scan and disconnect (WhoopBleClient.kt:2416,2468), so gating on it hid the note whenever the strap was offline — exactly when someone browses notification settings. Apple's `isWhoop5` reads `selectedModel`, which persists, so the two platforms would have disagreed about when to show the same sentence. Now matches the Settings #22 gate that SettingsScreen and TestCentreScreen:87 already use: the stored model OR a live-detected 5/MG this session. The `OR` keeps the case those two exist for — a 5/MG connected before its pref is written.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Items 1 and 2 from #926 — @vishk23's finding. I verified every claim in that issue against the tree
first; all of them hold, including the two corrections he made to his own earlier statements.
What users see today
On a 5/MG the buzz payload is a hardcoded 12-byte literal whose byte[11] (
overallLoop) is 0, so thecaller's repeat count never reaches the wire. All four
BuzzPatternchoices produce the same buzz — andthe picker offered them ungated on both platforms (
NotificationSettingsView.swift:180,NotificationsSettingsScreen.kt:773).That is the part worth fixing now: a settings control that does nothing is worse than an absent one,
because the user concludes their strap is broken rather than the feature unsupported.
What this does
Keeps the picker, adds the truth. The choice is stored per app and does apply to a WHOOP 4.0, so
hiding it would lose a real setting. The note follows the honesty wording
SmartAlarmView/SmartAlarmScreenalready use for their own 5/MG-unconfirmed case, rather than inventing a pattern.Translated into all six Android locales and all eight catalogue locales.
Deletes
AppModel.buzz(pattern:loops:)— zero callers, and its docstring claimed "used by thenotification-pattern picker" when the picker calls
buzz(loops:).Corrects two claims that were becoming folklore:
BLE_REVERSE_ENGINEERING.md's "7 presets" describes the strap's table, read viaGET_ALL_HAPTICS_PATTERN(80) — a command neither platform has ever sent, so NOOP has never enumeratedit. What the app exposes is four, all sharing patternId 2.
MaverickHaptics.notificationBuzznow says plainly that nothing ships it, and why it is kept: it is theonly code that can build a variable-loop body, which is exactly what a future
overallLoop != 0testwould need.
What this deliberately does NOT do
The payload is untouched. The golden frame is hardware-confirmed at
overallLoop = 0, nobody hastried non-zero on
0x13, and @vishk23 says so himself. The alarm path already ships7, so the field isaccepted non-zero by the strap — but proving it on the buzz path wants a staged probe (vary one byte, read
back, stop on refusal, as #932 does), not a blanket change to a proven payload. The
HELDreasoning inCHANGELOG.md:2218still stands.The live-session intensity levels are also no-ops on 5/MG, but they are internal — no control claims
otherwise — so they get a code note in the follow-up rather than UI.
One deliberate asymmetry in the gate
Android uses
stored model || live.whoop5Detected; Apple usesmodel.whoop5Detectedalone. That is notan oversight:
live.whoop5Detectedis reset tofalseon every scan and disconnect(
WhoopBleClient.kt:2416,2468), so gating on it alone would hide the note whenever the strap isoffline — which is exactly when someone browses notification settings. Hence the OR, matching the
Settings iOS: fix restore of WAL-header backups (SQLITE_CANTOPEN) — #18 #22 gate and
TestCentreScreen:87.isWhoop5readsselectedModel, which persists, so it needs no OR to survive disconnection.They agree in every settled state. They differ only in the transient first-pair window where a 5/MG is
connected before its pref is written: Android shows the note, Apple doesn't. Closing that would mean
plumbing a live-detection flag onto
LiveStatefor a moment when nobody is on this screen, so it isleft — recorded here rather than discovered later as a divergence.
Verification
i18n_audit --cianddoc_comment_lintclean. The Kotlin edit compiles: a partial compile of the touchedfile yields the same two pre-existing errors as
main(536 vs 544, the delta being unresolvedR.stringand
Metricsrefs a single-file compile can't see). Android CI and the catalogue jobs are the real gate.App-target Swift is not compiled by any CI job, so
NotificationSettingsView.swiftandAppModel.swiftrest on review — the change there is a
Textin an existingifand a deletion with no remainingreferences, which I checked.