Repository navigation
Android: name all 58 strap events, reviving the dropped-RTC diagnostic (#791) - #793
Merged
Merged
Conversation
#791) The Android `EventNumber` enum listed 13 of the 58 events the shared schema names, so every other event reached an Android strap log as a bare `0x44(68)` while iOS named it. #791 was filed partly on the strength of an "uncatalogued" event that iOS already knew, and the events that would have explained that report's other two findings — RTC_LOST, BOOT, BLE_SYSTEM_RESET — all arrived anonymous. The cosmetic half is that an Android bug report cannot say what the strap is reporting. The real half is a behaviour bug: `DroppedRtcEvent.isRtcStateKind` matches the event LABEL by prefix, and the label is built from this enum. With 13 and 15/16/30 unnamed their labels were `0x0D(13)` and friends, which match nothing, so the #324 dropped-RTC diagnostic was dead code on Android while its Swift twin has captured it since #324. That is the diagnostic that explains a future-dated strap, which is exactly the symptom in #791 — the reporter saw the strap claim 2030-01-28 and no diagnostic recorded why. Names only, generated from whoop_protocol.json rather than transcribed. Event PAYLOAD decoding is untouched: it stays family-gated and deliberately conservative, because several of these payloads have no on-device 5.0 ground truth and are left raw rather than ported from 4.0 on faith. A name here does not imply a decoder, and a number absent from the schema still falls through to the hex label rather than being given an invented name — event 68 observed on real hardware in #791 is unknown to both platforms and stays unknown. No iOS change: iOS resolves names from the shared JSON at runtime and already had all 58. The lockstep test READS the schema rather than restating it. A first draft pinned the enum against a hand-transcribed copy of the catalogue, which would have caught a deletion or a renumber but sailed straight through a recurrence of this very bug — the schema growing while Android stands still. It follows DecoderOracleTest's existing convention for reaching the Swift tree: org.json (a real testImplementation dependency, since android.jar ships throwing stubs), the two-candidate user.dir locator, and an Assume-skip when that tree is absent. The targeted assertions for the #791 events and the revived RTC diagnostic never skip. FramingTest's event-29 expectation moves from `0x1D(29)` to `STRAP_CONDITION_REPORT(29)`; its comment called 29 uncatalogued, which the shared schema contradicts. Its payload assertion is left alone and is what now pins the name-without-decoder distinction.
ryanbr
force-pushed
the
fix/android-event-catalogue-791
branch
from
July 25, 2026 04:24
42a3bd4 to
a186e2f
Compare
ryanbr
added a commit
that referenced
this pull request
Jul 25, 2026
MARKETING_VERSION/versionName move together per the versioning rule; build numbers increment independently (207->208 Apple, 298->299 Android). The staging workflow reads MARKETING_VERSION for its title and asset names, so main has to carry 9.1.1 for the rolling testing-latest release to be labelled as such — the previous 9.1.1 staging build was cut from a commit that never landed on main. This is also the first compile of the app-target changes merged today (#783/#784/#785, #787, #788, #793, #795, #519/#798, #523, #778), which CI does not cover: app-build.yml and android.yml are disabled and only the Swift packages and the i18n gate run on PRs.
simoncad7
pushed a commit
to simoncad7/noop
that referenced
this pull request
Aug 17, 2026
ryanbr#791) (ryanbr#793) The Android EventNumber enum listed 13 of the 58 events the shared schema names, so every other event reached an Android strap log as a bare 0x44(68) while iOS named it. ryanbr#791 was filed partly on the strength of an "uncatalogued" event iOS already knew, and the events that would have explained that report's other two findings — RTC_LOST, BOOT, BLE_SYSTEM_RESET — all arrived anonymous. The cosmetic half is that an Android bug report cannot say what the strap is reporting. The real half is a behaviour bug: DroppedRtcEvent.isRtcStateKind matches the event LABEL by prefix, and the label is built from this enum. With 13 and 15/16/30 unnamed their labels matched nothing, so the ryanbr#324 dropped-RTC diagnostic was dead code on Android while its Swift twin has captured it since ryanbr#324. That is the diagnostic that explains a future-dated strap — the symptom in ryanbr#791, where the strap claimed 2030-01-28 and nothing recorded why. Names only. Event payload decoding is untouched: family-gated and deliberately conservative, because several payloads have no on-device 5.0 ground truth. A name does not imply a decoder, and a number absent from the schema still falls through to the hex label rather than being invented — event 68 stays unknown. No iOS change: iOS resolves names from the shared JSON and already had all 58. Does NOT resolve ryanbr#791. Battery percent is still unavailable on that firmware: nothing consumes EXTENDED_BATTERY_INFORMATION into batteryPct on either platform, so naming event 63 is log visibility only. This makes the evidence readable, nothing more.
simoncad7
pushed a commit
to simoncad7/noop
that referenced
this pull request
Aug 17, 2026
MARKETING_VERSION/versionName move together per the versioning rule; build numbers increment independently (207->208 Apple, 298->299 Android). The staging workflow reads MARKETING_VERSION for its title and asset names, so main has to carry 9.1.1 for the rolling testing-latest release to be labelled as such — the previous 9.1.1 staging build was cut from a commit that never landed on main. This is also the first compile of the app-target changes merged today (ryanbr#783/ryanbr#784/ryanbr#785, ryanbr#787, ryanbr#788, ryanbr#793, ryanbr#795, ryanbr#519/ryanbr#798, ryanbr#523, ryanbr#778), which CI does not cover: app-build.yml and android.yml are disabled and only the Swift packages and the i18n gate run on PRs.
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.
From reviewing #791. The reporter listed an "uncatalogued event 0x44=68" from an Android capture, which
prompted a look at what Android actually names.
The gap
The shared schema (
whoop_protocol.json, which iOS reads at runtime) names 58 strap events. Android'sEventNumberenum listed 13. Everything else reached an Android strap log as a bare0x44(68).The cosmetic consequence is that an Android bug report cannot say what the strap is reporting. The events
that would have explained #791's other two findings all arrived anonymous:
63 EXTENDED_BATTERY_INFORMATION13 RTC_LOST,16 SET_RTC15 BOOT,30 BOOT_REPORT,43 BLE_SYSTEM_RESET1 ERROR,2 CONSOLE_OUTPUTThe real half: a dead diagnostic
DroppedRtcEvent.isRtcStateKindmatches the event label by prefix (kind.startsWith("RTC_LOST")), andthe label is built from this enum as
"NAME(raw)". While 13/15/16/30 were unnamed their labels were0x0D(13)and friends, which match nothing — so the #324 dropped-RTC diagnostic was dead code onAndroid, though it mirrors Swift exactly (
Streams.swift:317) and iOS has captured it since #324(
HistoricalTimestampGateTestspins the string"RTC_LOST(13)").That is the diagnostic which explains a future-dated strap. #791 reports the strap claiming
2030-01-28with nothing recording why — on Android there could not have been.
droppedRtcEventsis documented "Diag only" and flows to the strap log viaBackfiller, so this restores adiagnostic rather than altering stored data or analytics.
Scope
Names only. Event payload decoding is untouched — it stays family-gated and deliberately conservative,
because several payloads have no on-device 5.0 ground truth and are left raw rather than ported from 4.0 on
faith (
Interpreter.swift:786). A name here does not imply a decoder. A number absent from the schema stillfalls through to the hex label and is never given an invented name: event 68, observed on real hardware
in #791, is unknown to both platforms and stays unknown.
No iOS change needed — iOS resolves names from the shared JSON at runtime and already had all 58.
Verification
Entries were generated from
whoop_protocol.json, not transcribed. Verified mechanically:pre-existing names already agreed with the schema so nothing is renamed
EventNumberusage — one label site and two== BATTERY_LEVELcomparisons, nonull-as-invalid checks, so
UNDEFINED(0)is safe to include for exact label parityFramingTest's event 29,updated here
org.jsonconfirmed as atestImplementationdependency; the locator resolves from both the module dirand the repo root
Tools/i18n_audit.py --ci origin/main→ exit 0Corrected during review
The first draft of the lockstep test pinned the enum against a hand-transcribed copy of the catalogue.
That would have caught a deletion or a renumber but sailed straight through a recurrence of this very bug —
the schema growing while Android stands still, which is exactly how the gap arose. The test now reads the
schema, following
DecoderOracleTest's existing convention for reaching the Swift tree:org.json, thetwo-candidate
user.dirlocator, and anAssume-skip when that tree is absent. The targeted assertions forthe #791 events and the revived RTC diagnostic never skip, so they hold either way.
Worth being precise about the guard's reach: the lockstep check is a drift guard for a full checkout, not a
hard gate in every context, because it skips when the Swift tree is missing. That is the repo's existing
convention rather than a new compromise.
Additional checks, no defects found
Recorded so a reviewer need not re-derive them:
BATTERY_LEVEL, so every ordinal moved. Nothingpersists this enum by
ordinal,name,valueOf, or a RoomTypeConverter— the only.ordinaluses inthe app belong to unrelated enums, and the full usage audit found one label site plus two
== BATTERY_LEVELcomparisons. Safe.decoder_oracle.jsonis asserted byte-identical across platforms and pinsexpected decodes for both. It contains no event labels at all (type-47 HISTORICAL_DATA frames, not EVENT
frames), and the two copies remain identical. Unaffected.
src/test,src/androidTestandsrc/main/assetsfor apinned hex label of a now-named event. Only
FramingTest's event 29, already updated.Strand/Collect/Backfiller.swift— same message, and uncapped on both sides by the same documentedrationale that these events are sparse rather than per-record. Reviving it introduces no divergence.
elsewhere in
com.noop.protocol.The one thing still unverified is compilation. The specific Kotlin surface used —
JSONObject.keys().asSequence(),Assume.assumeTrue(String, Boolean),Set<Int>difference,EventNumber.entries— wasreasoned through rather than compiled, which is not the same thing.
Not run: the JUnit suite. This machine has no JDK, so the assertions were verified by mechanically
simulating the same data and parsing against the real schema file rather than by execution. A real
gradlew testbefore merge is worth it —android.ymlis disabled, so CI will not do it either.