Skip to content

Pin the first real 5/MG FAILURE response, from the #891 wrist pair - #906

Merged
ryanbr merged 1 commit into
mainfrom
test/real-mg-select-wrist-fixtures
Jul 27, 2026
Merged

Pin the first real 5/MG FAILURE response, from the #891 wrist pair#906
ryanbr merged 1 commit into
mainfrom
test/real-mg-select-wrist-fixtures

Conversation

@ryanbr

@ryanbr ryanbr commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Fixtures and rationale only — no production code changes.

The gap

No real WHOOP 5/MG frame in the tree carries result = FAILURE(0). The only non-SUCCESS fixtures on that family are the two synthetic GET_HELLO frames, so the FAILURE branch of the result decode added in #899 has never been exercised by bytes a strap actually sent.

#891's latest hardware runs supply one. The same MG accepted SELECT_WRIST with arg=1 and refused it with arg=0, minutes apart in one session:

frame resp_cmd resp_seq result
aa010c…d77b81010100007ce76722 SELECT_WRIST(123) 129 SUCCESS(1)
aa010c…217bcc000100000213163d SELECT_WRIST(123) 204 FAILURE(0)

That is a success/failure pair on one opcode — the shape that made the 4.0 result mapping credible once GET_EXTENDED_BATTERY_INFO was seen answering both ways, and which 5/MG had no equivalent of. It is also the first fixture for opcode 123 on either family.

Verification

Both frames re-decoded before committing — CRC16-Modbus header, CRC32 tail, declared length, and every field checked against the decoder rather than by hand. Provenance stated at the fixture per #903: WHOOP 5 MG WS50_r03, build 215, posted with the raw bytes in #891. Payloads are inert (01 01 00 00 and 00 01 00 00) — no name, serial or token. Swift and Kotlin assert the identical bytes.

What is deliberately not decided

The refusal is not interpreted. The strap ran the same opcode successfully minutes earlier so it is not "unknown command", but whether it refuses a real mutation or whether 0 is simply not a valid argument is open — docs/PROTOCOL.md flags right=0/left=1 as inferred from the vendor client's enum order, never attested. The test pins the decode of both outcomes and leaves the meaning to #891.

Also sharpens why byte 13 stays undecoded. The new run sent arg=0 and got byte 13 = 1, which refutes the echo reading outright. It does not establish read-back: across all eight MG command responses now on record — four opcodes, both result codes — byte 13 has taken exactly one value, so a constant fits every observation as well as stored state does and is simpler. Naming the slot would encode the unproven half of a result that only refuted the alternative.

docs/PROTOCOL.md is deliberately untouched: #896 is revising that same ECG section, and the hardware finding is already recorded in #891 and now at the fixture. Happy to add a paragraph there once #896 lands.

Until now no real WHOOP 5/MG frame in the tree carried `result = FAILURE(0)`.
The only non-SUCCESS fixtures on that family are the two synthetic GET_HELLO
frames, so the FAILURE branch of the result decode added in #899 had never been
exercised by bytes a strap actually sent.

#891's latest hardware runs supply one. The same MG accepted `SELECT_WRIST`
with arg=1 and refused it with arg=0 minutes apart in one session, which gives
a success/failure pair on a single opcode — the shape that made the 4.0 result
mapping credible when GET_EXTENDED_BATTERY_INFO was seen answering both ways,
and which 5/MG had no equivalent of. It is also the first fixture for opcode 123
on either family.

Both frames re-verified before committing: CRC16-Modbus header, CRC32 tail,
declared length, and every field against the decoder rather than by hand.
Provenance stated at the fixture per #903 — WHOOP 5 MG WS50_r03 build 215,
posted with the raw bytes in #891 — and the payloads are inert (`01 01 00 00`
and `00 01 00 00`): no name, serial or token. Swift and Kotlin assert the
identical bytes.

The refusal is deliberately not interpreted. The strap ran the same opcode
successfully minutes earlier, so it is not "unknown command", but whether it
refuses a mutation or whether 0 is simply not a valid argument is open — the
right=0/left=1 mapping is inferred from the vendor client's enum order and has
never been attested. The test pins the decode of both outcomes and leaves the
meaning to #891.

Also sharpens why byte 13 stays undecoded. The new run sent arg=0 and got byte
13 = 1 back, which refutes the echo reading outright. It does not establish the
read-back reading: across all eight MG command responses now on record, over
four opcodes and both result codes, byte 13 has taken exactly one value, so a
constant fits every observation as well as stored state does and is simpler.
Naming the slot would encode the unproven half of a result that only refuted
the alternative.

No production code changes — fixtures and rationale only.
@ryanbr
ryanbr merged commit 07d9c1a into main Jul 27, 2026
11 checks passed
@ryanbr
ryanbr deleted the test/real-mg-select-wrist-fixtures branch July 27, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant