diff --git a/.github/workflows/lint-docs.yml b/.github/workflows/lint-docs.yml index b3e6dc39..d355520a 100644 --- a/.github/workflows/lint-docs.yml +++ b/.github/workflows/lint-docs.yml @@ -20,6 +20,10 @@ on: - "id_registry.csv" - "scripts/lint_docs.py" +# Least privilege for the job's GITHUB_TOKEN (added 2026-10-15, ai-sessions/0094 — ai-sessions/0093 A93-APP-09): it only reads the repository. +permissions: + contents: read + jobs: lint-docs: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 8a4087b3..519a4473 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ **/.claude/ # Android / Gradle build output (once app development starts — see ARCHITECTURE.md §14) +# IDE (Eclipse/VS Code Java) output: copies of the Kotlin sources under a module's bin/ — never project files +# (added 2026-10-15, ai-sessions/0094 — ai-sessions/0093 A93-HK-07) +android/**/bin/ build/ .gradle/ local.properties diff --git a/AGENTS.md b/AGENTS.md index f34f5a8a..438d4f95 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -238,6 +238,9 @@ order: cross-validation pass (`WORKSTATION_PREPARATIONS.md`) shows agreement between two models: model agreement raises confidence, it does not constitute the human review this rule requires. + - **Note (2026-10-15, maintainer-approved in chat, `ai-sessions/0094` — `ai-sessions/0093` A93-SES-08, D5):** a draft ADR may stand in + `DECISIONS.md` only with the maintainer's consent in chat, headed "(DRAFT — proposed, not accepted)" and registered `proposed` in `id_registry.csv`; it + decides nothing until the maintainer accepts it (as ADR-060). ## 7. Device Discovery & MAC Randomization diff --git a/AI_SESSION_LOG_PROCEDURE.md b/AI_SESSION_LOG_PROCEDURE.md index fb6686e9..47d05aeb 100644 --- a/AI_SESSION_LOG_PROCEDURE.md +++ b/AI_SESSION_LOG_PROCEDURE.md @@ -121,6 +121,12 @@ Before a RESULT file's `Status` is set to `complete`: (`complete (closed 2026-10-03 by …)`). 4. **In an audit that uses sub-reviews,** each finding says whether the main session re-derived it or took it from a sub-review (the M/S marks of `ai-sessions/0068`). A sub-review claim that was not re-derived is an open question with its check, not a fact. +5. **The INDEX row agrees with the RESULT** (added 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-HK-05/A93-SES-07): its status cell starts with + the same value as the RESULT's `Status`, and the next session that touches the record also updates the cell — `scripts/lint_docs.py` checks both, and an + unfilled `| (this commit) |` row in any record older than the newest session. +6. **Records are checked, not written from memory.** A compliance line quotes the command and its output (e.g. the merged manifest's permissions); a + count names the command that produced it; every document the prompt names as changed is re-read after the change. Stale "Next:" and "pull request + opened" lines in Summary and Progress are closed when the work they announce is done. ## 5. Multi-part results diff --git a/APK_REVERSE_ENGINEERING_PROCEDURE.md b/APK_REVERSE_ENGINEERING_PROCEDURE.md index aacd9ace..7347070c 100644 --- a/APK_REVERSE_ENGINEERING_PROCEDURE.md +++ b/APK_REVERSE_ENGINEERING_PROCEDURE.md @@ -7,7 +7,8 @@ the *what we found*, and `reverse-engineering/APK_VERSIONS.md` is the *which APK looked at*. This procedure follows `AGENTS.md` §6/`DECISIONS.md` ADR-017's AI-assistance boundary throughout: -an AI session may run the mechanical steps below (pulling, hashing, decompiling, running `pbtk`, +an AI session may run the mechanical steps below (pulling, hashing, decompiling, running the schema decoder — `pbtk` does not apply to this +APK, ADR-041 — keyword/string searching, explaining already-surfaced code or disassembly — including native `.so` disassembly output, per ADR-017 §4), but never decides which candidate is relevant to the protocol and never decides that something becomes a recorded HYPOTHESIS in `REVERSE_ENGINEERING.md`. Both of @@ -257,7 +258,8 @@ Account-Linking/Non-Owner traffic in captures) and move on — do not follow the ## 7. Checklist for a new APK version or a new Buds firmware (added 2026-10-03, `ai-sessions/0069`) -1. Pull and store the APK (§2); add its row to `reverse-engineering/APK_VERSIONS.md` with the tool versions. +1. Pull and store the APK (§2); add its row to `reverse-engineering/APK_VERSIONS.md` **the day it is pulled** (files and SHA-256 at once; the tool + versions when it is decompiled) — 1.0.990706425 was pulled on 2026-10-05 and registered only on 2026-10-15 (`ai-sessions/0093` A93-HK-03). 2. Decompile (§3). Re-run the schema decoder on the settings message (`qhr` in `v1.0.955078536`; the class name changes with every build — find it by its field count and by the string literals of its callers, `REVERSE_ENGINEERING.md`). 3. Diff against the previous version (§2.1): the settings fields (numbers and types), the pw_rpc service and method name literals, the Fast Pair diff --git a/APP_TESTPLAN.md b/APP_TESTPLAN.md index a2af1b1b..0ca92d6d 100644 --- a/APP_TESTPLAN.md +++ b/APP_TESTPLAN.md @@ -24,7 +24,7 @@ and on a pull (O8); A5 corrected (`CAP-067` §7). **Updated 2026-10-08 for the 1 toolchain of `ai-sessions/0078` — no new function; **section U**, a regression pass over what the new toolchain could change). **Updated 2026-10-09 for the 1.2.0 build** (`ai-sessions/0082`, `DECISIONS.md` ADR-058/ADR-059): "Changed by the Buds" in the ANC (i); the serial numbers on gear → Info; the "probably worn" line on the battery card; the **Case sounds** switches moved from Controls to gear → **Settings** (T5, T6 note; N1); **Conversation detection** moved from Sound to -**Controls** (M5 → N12; M1); thirteen MAESTRO requests at Connect (one `GetHardwareInfo` after the subscription; O8); **section V**. **Updated 2026-10-10 for +**Controls** (M5 → N12; M1); fifteen MAESTRO requests at Connect (one `GetHardwareInfo` after the subscription; O8); **section V**. **Updated 2026-10-10 for *(Counts corrected 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-CAP-01: earlier text said "thirteen"; a 1.2.x Connect sends fifteen MAESTRO requests — thirteen `ReadSetting`s (the EQ's `4:16` and twelve settings), `SubscribeRuntimeInfo`, `GetHardwareInfo` — `CAP-073`, 11 of 11 sessions; 1.3.0 sends seventeen.)* the rebuilt 1.2.0** (`ai-sessions/0084`, `DECISIONS.md` ADR-061): the Message Stream claim is held while the noise-control tab is on screen (F-rows: a tap there reuses the held channel; V10 can pass); the balance slider shows the finger's value while dragging and is finer near the centre — the ±3 snap of M3 is gone; **section W**. **Updated 2026-10-12 for 1.2.1** (`ai-sessions/0087`, `DECISIONS.md` ADR-062): the Settings tab's "Noise-control channel" choice — in a user with @@ -34,7 +34,7 @@ Google Play services Play's Fast Pair takes the held channel back ≈ 5 s later options that say whether the phone has Google Play services and a line whether Android finds Play services; in the Play option the re-claim comes 3 s after every cut, up to 20 times, then the hint with "Take it back again"; a mode the app's own `Get` finds changed gets "Changed since the app last saw it" in the (i); **section Y** (both users). **Updated 2026-10-14 for 1.3.0** (`ai-sessions/0091`, `DECISIONS.md` ADR-064 and ADR-065): the app subscribes to the Buds' settings -stream and reads the noise-control mode on its own channel at every Connect and re-open (fifteen MAESTRO requests: O8), so a press-and-hold shows on any tab +stream and reads the noise-control mode on its own channel at every Connect and re-open (seventeen MAESTRO requests: O8 plus the two new requests), so a press-and-hold shows on any tab without a claim of the shared channel; the hold, the re-claim, the "Noise control with Google Play services" card and the Play line are gone (the F-rows no longer hold the channel; the X/Y rows of 1.2.1/1.2.2 are history); the (i) texts of the noise-control card; **section Y rewritten** for the 1.3.0 run (both users). This is a *user-level* @@ -368,9 +368,9 @@ from frames the app already received; the moved switches send the same bytes as | V13 | Gear → Settings → **Case sounds**: Earbuds replaced off (a bud into the case and out; say whether the case sounded), on (the same); Other alerts off, on; the (i). | Each after its OK; "changed HH:MM:SS" | `4:{28:0}`, `4:{28:1}`, `4:{27:0}`, `4:{27:1}` = `CAP-070`/`CAP-071`'s requests | | BH-14 (T5, T6) | | V14 | Controls → **Conversation detection** off, on; speak 5 s. | Each after its OK; the subtitle kept | `4:{22:0}` = `CAP-019` 1720, `4:{22:1}` = 1808 | | BH-15 (N12) | | V15 | Sound: Balanced, a slider, *Read EQ again*, Right 4, Centre, mono, Volume EQ; Controls: touch, press and hold, the mode list without Off, head gestures, Multipoint — say what you hear. | Each after its OK; no conversation-detection row on Sound | each request = its reference (`CAP-072-EVENT-NOTES.md`'s table) | | BH-16, BH-17 (H5 ★, the "Right 4" write ★) | -| V16 | Disconnect, Connect; Sound, Controls, Settings, Info. | Every value read back; the **same three serials** with a new time; the Worn line "—" then "Probably worn" | the thirteen requests; one `GetHardwareInfo`, the same answer bytes | | BH-18 | +| V16 | Disconnect, Connect; Sound, Controls, Settings, Info. | Every value read back; the **same three serials** with a new time; the Worn line "—" then "Probably worn" | the fifteen requests; one `GetHardwareInfo`, the same answer bytes | | BH-18 | | V17 | Find: Ring Left, Stop, Ring Right, Stop. | The ring follows each tap | `04 01 00 01 02` / `00` / `01` / `00` → ACK | | BH-19 | -| V18 | The case (lid open), Home 2 min, Export + Bluetooth off/on; grep every export for a serial (first 4 + last 2) and the MAC. | The 1.0.1 cause texts; re-open by itself; "Bluetooth is disabled."; **0 hits** in the exports (positive control: the "Control channel" line) | ADR-044's `SABM` ≈ 1.5 s after the link; the thirteen requests after each re-open | | BH-20…BH-22 (C8, C9, J4, K1) | +| V18 | The case (lid open), Home 2 min, Export + Bluetooth off/on; grep every export for a serial (first 4 + last 2) and the MAC. | The 1.0.1 cause texts; re-open by itself; "Bluetooth is disabled."; **0 hits** in the exports (positive control: the "Control channel" line) | ADR-044's `SABM` ≈ 1.5 s after the link; the fifteen requests after each re-open | | BH-20…BH-22 (C8, C9, J4, K1) | | V19 | Film 2: C12 (the early tap), S12 (the export across a rotation), T11 (three `uiautomator` dumps: Controls, Sound, **Settings**). | "The setting was not changed: …"; "Debug log saved (N lines)."; every "—" node's `content-desc` = `Not read from the Buds yet` (Controls seven, Sound eight, Settings two) | no `WriteSetting` after the early tap | | BH-23…BH-26 | ## W. The rebuilt 1.2.0 (`ai-sessions/0084`: the Message Stream held on the noise-control tab — ADR-061 — and the finer balance slider) @@ -381,8 +381,8 @@ S12, H5, T7, V8) are the earlier IDs, run again. | ID | Steps | Expected on screen | Expected on the wire | Result | Notes | |---|---|---|---|---|---| -| W1 | Update over the `ec6d163` 1.2.0 (Dark mode and Debug mode set); open the app; Info. | Dark at once; Debug mode on; "App: 1.2.0, build ", no "-dirty"; firmware ×3; the serials | the thirteen requests, one `GetHardwareInfo` = `CAP-072` A 7593 (21) / A 8616 (19) | ✅ | BI-1, BI-2 | -| W2 | Disconnect, Connect; Bluetooth off/on; Export. | ready; "Bluetooth is disabled.", then ready by itself; "Debug log saved (N lines)." | the thirteen requests after each open | ⚠️ Bluetooth off/on not run | BI-3 … BI-5 | +| W1 | Update over the `ec6d163` 1.2.0 (Dark mode and Debug mode set); open the app; Info. | Dark at once; Debug mode on; "App: 1.2.0, build ", no "-dirty"; firmware ×3; the serials | the fifteen requests, one `GetHardwareInfo` = `CAP-072` A 7593 (21) / A 8616 (19) | ✅ | BI-1, BI-2 | +| W2 | Disconnect, Connect; Bluetooth off/on; Export. | ready; "Bluetooth is disabled.", then ready by itself; "Debug log saved (N lines)." | the fifteen requests after each open | ⚠️ Bluetooth off/on not run | BI-3 … BI-5 | | W3 | (the fixture-independent check) The requests of W1/W2 against `CAP-072`'s frames. | — | byte-identical to `CAP-072` (the unit tests' fixtures since `ai-sessions/0084` are `CAP-072`'s own frames) | ✅ | analysis | | W4 | Both buds worn: open the **ANC** tab, wait 30 s. | the mode; the (i) ends "While this tab is open the app keeps the Buds' noise-control channel open, …" | one `SABM` DLCI 0x04 + `08 11`; **no `DISC`** for 30 s | ⚠️ (i) not opened | BI-6 (ADR-061) | | W5 | **Press and hold** the Left bud, then the Right bud; the (i) after each. | the mode changes without a tap; "Changed by the Buds at HH:MM:SS (a press-and-hold on a bud, or the Buds' own change)."; the time moves | an unprovoked `08 13 00 04 01 e8 e8 xx` per hold, nothing from the app | ⚠️ (i) line not checked | BI-7, BI-8 (`TOUCH-007`) | @@ -403,21 +403,24 @@ measured 2026-10-12): recovery 5.2–5.7 s after it lost the channel, retry 2.2 | ID | Steps | Expected on screen | Expected on the wire | Result | Notes | |---|---|---|---|---|---| -| X1 | **User 0 (Play).** Update over 1.2.0 (Debug mode on); open the app; Info; gear → Settings: the "Noise-control channel" card and its (i). | "App: 1.2.1, build ", no "-dirty"; the card between Dark mode and Case sounds, "Show a hint, do not take it back" selected; the (i)'s four lines | the thirteen requests + `GetHardwareInfo`; the snapshot claim's attempt 1 fails (Play), attempt 2 connects | ⚠️ | BK-1, BK-2 — set 1: the fresh update showed the card with "Show a hint" (BK-15); set 2: build ✓, the card showed "Take it back" (pre-set before the film), the (i) opened later; the hash `31ee183` in both users | -| X2 | Disconnect, Connect; the ANC tab at once. | ready; the mode; the hold | the thirteen requests; the snapshot claim kept as the hold | ✅ | BK-3, BK-4 — 06:32:38 Connect → the ANC tab within 1.5 s → the snapshot claim held (also 06:19:02); Disconnect/Connect ×3 | +| X1 | **User 0 (Play).** Update over 1.2.0 (Debug mode on); open the app; Info; gear → Settings: the "Noise-control channel" card and its (i). | "App: 1.2.1, build ", no "-dirty"; the card between Dark mode and Case sounds, "Show a hint, do not take it back" selected; the (i)'s four lines | the fifteen requests (incl. `GetHardwareInfo`); the snapshot claim's attempt 1 fails (Play), attempt 2 connects | ⚠️ | BK-1, BK-2 — set 1: the fresh update showed the card with "Show a hint" (BK-15); set 2: build ✓, the card showed "Take it back" (pre-set before the film), the (i) opened later; the hash `31ee183` in both users | +| X2 | Disconnect, Connect; the ANC tab at once. | ready; the mode; the hold | the fifteen requests; the snapshot claim kept as the hold | ✅ | BK-3, BK-4 — 06:32:38 Connect → the ANC tab within 1.5 s → the snapshot claim held (also 06:19:02); Disconnect/Connect ×3 | | X3 | **Hint only:** press and hold a bud every ≈ 5 s for 30 s; touch nothing; pull the system log within a minute of the first cut. | the first hold shown; then the hint line "The noise-control channel was taken by another app at HH:MM:SS (for example Google Play services' Fast Pair). A change made on a bud is not shown until you tap a mode, Refresh, or pull down."; the later holds not shown | the cut ≈ 4.7–5.1 s after the `SABM` (the phone's `DISC`, "closed … read return: -1", "hold ended: the channel was closed, not by this app", "the hint is shown, no re-claim"); **no app `SABM` afterwards**; Play's `SABM` 2.2–2.6 s after the cut; the holds' `Notify` on Play's claim | ⚠️ | BK-5 (`TOUCH-007`) — hint only selected 06:31:20–06:32:26: the hint with the cut's time ✓ (06:31:33, 06:32:02), no re-claim ✓, the stack lines ✓ (the log viewer); the first hold not shown — the claims lived 0.42 / 1.2 s (Play's timer, not the app's `SABM`); 0 of 9 holds | | X4 | Open the ANC (i); tap a mode; later pull down. | the (i) has the hint line and the corrected last sentence ("… may take it back about 5 seconds later; what the app does then is set on the Settings tab under Noise-control channel."); the tap and the pull clear the hint; ≈ 5 s later it is back | the tap's claim (attempt 1 fails, attempt 2 connects), `08 11` → `Notify` → `Set` → ACK; the cut; the pull's claim; the cut | ⚠️ | BK-6, BK-7 (`ANC-001`…`004`) — the (i) with the hint and the corrected sentence ✓ (s265–269); the hint cleared by a Connect and by Refresh ✓; no mode tap in the run (0 `Set`) | | X5 | gear → Settings → **"Take it back while the noise-control tab is open (may fight that app)"**; back to ANC. | the row selected; a new claim on return | the `DISC` ≈ 1.5 s after the gear; a new `SABM` + `08 11` on return | ✅ | BK-8 — 06:32:26.5; a new claim on return (#19) | | X6 | **Take it back:** press and hold a bud every ≈ 4 s for 60 s; touch nothing; count the holds shown. | the holds inside the app's windows shown (≈ 6 of 10); after the third re-claim's cut the hint "…; the app took it back 3 times and stopped. …"; nothing more on screen | the cut ≈ 5 s after the `SABM`; the re-claim `SABM` + `08 11` **3.0 s** after the cut (attempt 1 fails, attempt 2 ≈ 0.4 s later); the cut; the re-claim **6.0 s** after; the cut; **12.0 s**; the cut; **no fourth**; export "re-claim 1/3 … 3000 ms", "2/3 … 6000 ms", "3/3 … 12000 ms", "stopped after 3 re-claims" | ✅ | BK-9 (`TOUCH-007`) — three chains: re-claims at 3 000 / 6 000 / 12 000 ms ✓ (on the wire 4.0–4.8 s after the cut: attempt 1 refused), the stop and the hint with "3 times" ✓ ×3; shown 8 of 19 holds (fewer than the ≈ 6 of 10 expected: the back-off lengthens the cycles to 12 and 18 s) | | X7 | Tap a mode; two holds; wait. | the hint gone; the chain restarted | the tap's claim; the cut; the re-claim 3 s after it | ✅ | BK-10 — Refresh (06:33:37) instead of a mode tap: the hint gone, chain 3 followed | | X8 | Within 2 s of a cut: the Sound tab (then back); Home (then return); the switch to "Show a hint" (then back). | no re-claim in any of the three; a new claim on each return | "Message Stream re-claim cancelled (ADR-062)" ×3; no `SABM` 3 s after those cuts; the returns' claims | not run | BK-11 … BK-13 — the one "re-claim cancelled" is pre-film (06:19:04) | -| X9 | Hint only: Disconnect, Connect on the ANC tab; Export. | the hint gone at Connect, back ≈ 5 s later; "Debug log saved (N lines)." | the thirteen requests; the snapshot claim held; the cut | ⚠️ | BK-14, BK-end1 (`PAIR-003`) — Disconnect/Connect done on the Connection tab (06:31:51/55): the hint gone at Connect ✓, the snapshot released; the ANC tab at 06:32:00 → the cut 1.2 s later → the hint; Export ✓ (513 lines) | +| X9 | Hint only: Disconnect, Connect on the ANC tab; Export. | the hint gone at Connect, back ≈ 5 s later; "Debug log saved (N lines)." | the fifteen requests; the snapshot claim held; the cut | ⚠️ | BK-14, BK-end1 (`PAIR-003`) — Disconnect/Connect done on the Connection tab (06:31:51/55): the hint gone at Connect ✓, the snapshot released; the ANC tab at 06:32:00 → the cut 1.2 s later → the hint; Export ✓ (513 lines) | | X10 | **User 10 (no Play).** Open the app; Info; Settings: the card (hint only). | the build; the card | the Connect sequence; the snapshot claim released ≈ 1.5 s after its answer | ✅ | BK-15 — film 1 s168–189: the re-open after the update, "App: 1.2.1, build 31ee183", the card with "Show a hint" | | X11 | Hint only: the ANC tab; holds every ≈ 5 s for 30 s. Then "Take it back"; back to ANC; holds every ≈ 5 s for 30 s. | every hold shown in both positions; no hint | one `SABM` + `08 11` per tab entry; **no `DISC` while the tab is shown**; no re-claim line; 0 Play-services claims | ✅ | BK-16, BK-17 (`TOUCH-007`) — H2/H3/H5 (hint only) and H4/H6 (take it back): 41 of 41 shown, no `DISC` while the tab was shown, 0 re-claim lines, 0 Play claims | | X12 | Leave the tab (Controls); optional: the tile on the tab, Home/return, both buds into the case and out with the tab shown; back to "Show a hint"; Export. | Controls; (optional branches as in W6–W8) | the `DISC` ≈ 1.5 s after leaving; (optional: no second `SABM` for the tile; the `DISC` after Home; the re-open's snapshot kept) | ⚠️ | BK-18 … BK-21, BK-end2 — the `DISC` 1.50–1.51 s after leaving the tab ×6 ✓; the optional branches not run; the switch **not** set back (set 1 ends on "Take it back"); Export ✓ (228 lines) | ## Y. 1.3.0 (`ai-sessions/0091`: the settings stream on the app's own channel — ADR-064, ADR-065 — in both users) +*(Columns repaired 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-CAP-04: the `CAP-076` results had been written into "Expected on the wire"; +the expected wire behaviour is restored from `8a72572` (counts corrected to seventeen) and each result sits in "Result".)* + In `CAP-076` as BL-1 … BL-25 (captures/CAP-076-2026-10-15_06-35-26_07-08-20-Group_BL/CAP-076-EVENT-NOTES.md, which has the reference frames per request). Two new requests on the wire at every Connect and re-open — `SubscribeToSettingsChanges` and `ReadSetting 4:13`, the official app's bytes (`CAP-051` 1177/1178, `CAP-056` 1451/1450); the Buds then push every change of the mode (and of every other setting the app reads) as a stream packet on the app's own channel, on any @@ -427,28 +430,45 @@ or a Connect's snapshot; the own tap's stream packet ≈ 0.3 s after the `Set`. | ID | Steps | Expected on screen | Expected on the wire | Result | Notes | |---|---|---|---|---|---| -| Y1 | **User 0 (Play).** Update over 1.2.2 (Debug mode on; the 1.2.2 card's choice on film first); open the app; Info; gear → Settings. | "App: 1.3.0, build ", no "-dirty"; Settings = Dark mode, Case sounds, Use different Buds — **no "Noise control with Google Play services" card, no "Google Play services: …" line** | ✅ | | BL-1, BL-2 (`PAIR-003`, `BATT-004`, `FW-003`) — film 1: 1.2.2 card on film (s59), the update (s92–93), "App: 1.3.0, build ff27ee1" and Settings without the card or the Play line (s100–107); the LINK_BACK re-open with the seventeen requests (S1) | -| Y2 | Disconnect, Connect. | ready | ✅ | | BL-3 (`PAIR-003`) — 06:39:41/44 — S2: the subscription and the read again (H1 2838/2839); seventeen requests per full session, not fifteen (`CAP-076-FINDINGS.md` §2) | -| Y3 | **Connection tab**, both worn: 10 press-and-holds ≈ 6 s apart; touch nothing; then open ANC. | the mode follows every hold (the notification's line; ANC shows the last) | ⚠️ | | BL-4 (`TOUCH-007`, `ANC-001`…`004`) — done differently: 13 changes on the Connection tab 06:40:47–06:41:51 (holds and wear changes during podcast playback, three with the Buds' AVRCP PAUSE/PLAY) — each pushed (twice: ch 21 + 19), no app `SABM` except the re-open's snapshot; **that tab shows no mode** (the worn line's time follows); ANC opened later | -| Y4 | **ANC tab**: 10 press-and-holds ≈ 6 s apart; the (i) after one. | **10 of 10** shown within a second; the (i) "Changed by the Buds at HH:MM:SS: the Buds reported a new mode — a press-and-hold on a bud, or a change they made themselves."; the last sentence "The Buds report every change of the mode on the app's own channel. … that part is a hypothesis." | ✅ | | BL-5, BL-6 (`TOUCH-007`, `ANC-001`…`004`) — **17 of 17 within 0.1 s** (06:43:14–06:44:11, Play holding DLCI 0x04 throughout); the (i) "Changed by the Buds at 06:44:11 …" and the stream sentence (s536) | -| Y5 | Tap a mode; the (i). The ANC tile once. | the mode at once; no "Changed by the Buds" line for the own tap; the tile steps one mode and the app follows | ⚠️ | | BL-7, BL-8 (`ANC-001`…`004`) — 4 taps + 9 tile taps: 11 `Set`s ACKed, the stream 154–246 ms after, classed as the app's (no cause line), the `DISC` 1.507–1.516 s after the ACK; **one tile tap (06:46:08.96) cut by Play after the `Get`'s answer: no `Set`, no message** (`CAP-076-FINDINGS.md` §5.3) | -| Y6 | One bud out (both worn), 10 s, back in, 10 s; read the mode after each. | either the loss and the re-open by itself with the mode read ("Changed since the app last saw it (read at …)" if it differs), or the Buds' own switch as "Changed by the Buds" (🟡) | ⚠️ | | BL-9 (`PAIR-003`, `TOUCH-007`) — six Buds-side `DISC`s (06:39:53 … 06:47:44), every re-open 1.50–1.51 s later subscribed and read again; **the two differing reads (S4, S7) shown as "read" — the snapshot's "changed since" overwritten by the equal `4:13` answer** (§6.2) | -| Y7 | Bluetooth off, 5 s, on; wait. | "Bluetooth was switched off"; ready again by itself | not run | | BL-10 (`PAIR-003`) — BL-10 — no Bluetooth off/on with 1.3.0 in this user (the one on film was before the 1.2.2 session) | -| Y8 | Taps while Play holds the channel; tap again after a failed one. | the mode after at most two taps; **no hint, no re-claim, no "Take it back again"** | ⚠️ | | BL-11 (`ANC-001`…`004`) — 13 taps with Play present in 12 claims: 8 refused at attempt 1 and done at attempt 2 (0.87–1.11 s later), 2 cut in their linger, 1 cut before the `Get`'s answer (toast), 1 cut after it (silent); nothing re-claimed (§5.2) | -| Y9 | 5 more holds on the ANC tab. | 5 of 5 shown | ✅ | | BL-12 (`TOUCH-007`) — 12 holds (06:48:44–06:49:33), **12 of 12 within 0.1 s**; no app `SABM` | -| Y10 | (optional) The official app on a second phone: an EQ slider; In-ear detection off/on. | the equalizer and the switch follow in OpenControl ("read HH:MM:SS", not "changed by this app") | not run | | BL-13 — BL-13 — no second phone | -| Y11 | Export; the system log; switch to user 10. | "Debug log saved (N lines)." | ✅ | | BL-end1 — "Debug log saved (1062 lines)."; the app logcat and the System log saved (s946–976) | -| Y12 | **User 10 (no Play).** Open the app; Info; Settings. | the build; no card, no Play line | ✅ | | BL-14 (`PAIR-003`, `FW-003`, `BATT-004`) — BL-14 — S10–S12; Info "App: 1.3.0, build ff27ee1", Settings without the card (film 2 s73–86) | -| Y13 | Connection tab: 10 holds; then ANC: 10 holds; the (i). | every hold followed / shown; "Changed by the Buds at …" | ⚠️ | | BL-15, BL-16 (`TOUCH-007`, `ANC-001`…`004`) — BL-15 not run; BL-16: **12 of 12 within 0.1 s** (07:05:24–07:06:38), the (i) "Changed by the Buds at …" five times; 0 Play claims (positive control part 1: 18) | -| Y14 | A tap; the tile. | the mode; the app follows the tile; no "Changed by the Buds" for the own change | ⚠️ | | BL-17, BL-18 (`ANC-001`…`004`) — BL-17: 4 taps at attempt 1, ACK +25…82 ms, the stream +55…94 ms, no cause line; BL-18 (the tile) not run | -| Y15 | One bud out / in (as Y6). | as Y6 | ✅ | | BL-19 (`PAIR-003`, `TOUCH-007`) — BL-19 done differently: the Buds' `DISC` 07:03:00 (the Right out of the case) → the re-open on ch 21, a push before its subscription, the read equal | -| Y16 | Bluetooth off / on. | ready again by itself | ✅ | | BL-20 (`PAIR-003`) — BL-20 — 07:02:39/44; "Bluetooth was switched off on this phone"; the LINK_BACK re-open subscribed again (H2 540) | -| Y17 | Controls tab: 5 holds; then ANC. | the last mode; "Changed by the Buds" | not run | | BL-21 (`TOUCH-007`) — BL-21 | -| Y18 | Disconnect; two holds while disconnected; Connect; the ANC (i). | after the Connect the mode read; **"Changed since the app last saw it (read at HH:MM:SS): a press-and-hold on a bud or the Buds' own change, made while this app was not connected."** | ⚠️ | | BL-22 (`PAIR-003`, `TOUCH-007`) — BL-22 done differently: Disconnect/Connect 07:04:12/15 with no hold in between — the read equal, no line (correct); the differing-read line not exercised here (see Y6) | -| Y19 | Both buds into the case, 15 s, out, 15 s. | the session ends and re-opens by itself, or "The Buds don't allow changing noise control right now …" with the mode kept; the mode read once worn; **a value 0 never shown as a mode** | ✅ | | BL-23 (`PAIR-003`) — BL-23 in part 1: both buds into the case 06:42:11 (`4:{13:0}`), the ACL's end, the Connect's "no mode settable"; **0 never shown as a mode** (s442) | -| Y20 | (optional) Home; two holds while hidden; return. | the holds' result on return | not run | | BL-24 (`TOUCH-007`) — BL-24 | -| Y21 | (optional) The official app on a second phone: an EQ slider; a switch. | OpenControl follows | not run | | BL-25 — BL-25 — no second phone | -| Y22 | Export; the system log; the APK pull. | "Debug log saved (N lines)." | ✅ | | BL-end2 — "Debug log saved (462 lines)."; the app logcat; no system log in user 10; the APK pulled by `ai-sessions/0092` (SHA-256 = the tested build's) | +| Y1 | **User 0 (Play).** Update over 1.2.2 (Debug mode on; the 1.2.2 card's choice on film first); open the app; Info; gear → Settings. | "App: 1.3.0, build ", no "-dirty"; Settings = Dark mode, Case sounds, Use different Buds — **no "Noise control with Google Play services" card, no "Google Play services: …" line** | the **seventeen** requests: `4:16`, the twelve settings, `SubscribeRuntimeInfo`, **`SubscribeToSettingsChanges`, `ReadSetting 4:13`**, `GetHardwareInfo`; the snapshot claim | ✅ | BL-1, BL-2 (`PAIR-003`, `BATT-004`, `FW-003`) — film 1: 1.2.2 card on film (s59), the update (s92–93), "App: 1.3.0, build ff27ee1" and Settings without the card or the Play line (s100–107); the LINK_BACK re-open with the seventeen requests (S1) | +| Y2 | Disconnect, Connect. | ready | the seventeen requests again — the subscription and the read once per session | ✅ | BL-3 (`PAIR-003`) — 06:39:41/44 — S2: the subscription and the read again (H1 2838/2839); seventeen requests per full session, not fifteen (`CAP-076-FINDINGS.md` §2) | +| Y3 | **Connection tab**, both worn: 10 press-and-holds ≈ 6 s apart; touch nothing; then open ANC. | the mode follows every hold (the notification's line; ANC shows the last) | one `4:{13:n}` stream packet per hold on DLCI 0x02; **no app `SABM` on DLCI 0x04** | ⚠️ | BL-4 (`TOUCH-007`, `ANC-001`…`004`) — done differently: 13 changes on the Connection tab 06:40:47–06:41:51 (holds and wear changes during podcast playback, three with the Buds' AVRCP PAUSE/PLAY) — each pushed (twice: ch 21 + 19), no app `SABM` except the re-open's snapshot; **that tab shows no mode** (the worn line's time follows); ANC opened later | +| Y4 | **ANC tab**: 10 press-and-holds ≈ 6 s apart; the (i) after one. | **10 of 10** shown within a second; the (i) "Changed by the Buds at HH:MM:SS: the Buds reported a new mode — a press-and-hold on a bud, or a change they made themselves."; the last sentence "The Buds report every change of the mode on the app's own channel. … that part is a hypothesis." | one stream packet per hold; no app `SABM`; no hint, no "Take it back again" | ✅ | BL-5, BL-6 (`TOUCH-007`, `ANC-001`…`004`) — **17 of 17 within 0.1 s** (06:43:14–06:44:11, Play holding DLCI 0x04 throughout); the (i) "Changed by the Buds at 06:44:11 …" and the stream sentence (s536) | +| Y5 | Tap a mode; the (i). The ANC tile once. | the mode at once; no "Changed by the Buds" line for the own tap; the tile steps one mode and the app follows | one claim per tap/tile (`08 11`, `08 12`, ACK), the Buds' `Notify`, **the stream packet of the tapped mode ≈ 0.3 s after the `Set`**, the `DISC` ≈ 1.5 s after the ACK | ⚠️ | BL-7, BL-8 (`ANC-001`…`004`) — 4 taps + 9 tile taps: 11 `Set`s ACKed, the stream 154–246 ms after, classed as the app's (no cause line), the `DISC` 1.507–1.516 s after the ACK; **one tile tap (06:46:08.96) cut by Play after the `Get`'s answer: no `Set`, no message** (`CAP-076-FINDINGS.md` §5.3) | +| Y6 | One bud out (both worn), 10 s, back in, 10 s; read the mode after each. | either the loss and the re-open by itself with the mode read ("Changed since the app last saw it (read at …)" if it differs), or the Buds' own switch as "Changed by the Buds" (🟡) | the Buds' `DISC` → the re-open's seventeen requests (the subscription and the read again), or a stream packet without a `DISC` — say which | ⚠️ | BL-9 (`PAIR-003`, `TOUCH-007`) — six Buds-side `DISC`s (06:39:53 … 06:47:44), every re-open 1.50–1.51 s later subscribed and read again; **the two differing reads (S4, S7) shown as "read" — the snapshot's "changed since" overwritten by the equal `4:13` answer** (§6.2) | +| Y7 | Bluetooth off, 5 s, on; wait. | "Bluetooth was switched off"; ready again by itself | the re-open's seventeen requests — the subscription again | not run | BL-10 (`PAIR-003`) — BL-10 — no Bluetooth off/on with 1.3.0 in this user (the one on film was before the 1.2.2 session) | +| Y8 | Taps while Play holds the channel; tap again after a failed one. | the mode after at most two taps; **no hint, no re-claim, no "Take it back again"** | a refused attempt 1 with Play's `DISC` (the `CAP-075` §5 collision — the known issue); the app never re-claims by itself | ⚠️ | BL-11 (`ANC-001`…`004`) — 13 taps with Play present in 12 claims: 8 refused at attempt 1 and done at attempt 2 (0.87–1.11 s later), 2 cut in their linger, 1 cut before the `Get`'s answer (toast), 1 cut after it (silent); nothing re-claimed (§5.2) | +| Y9 | 5 more holds on the ANC tab. | 5 of 5 shown | 5 stream packets; no app `SABM` | ✅ | BL-12 (`TOUCH-007`) — 12 holds (06:48:44–06:49:33), **12 of 12 within 0.1 s**; no app `SABM` | +| Y10 | (optional) The official app on a second phone: an EQ slider; In-ear detection off/on. | the equalizer and the switch follow in OpenControl ("read HH:MM:SS", not "changed by this app") | the stream packets for field 16 and field 2 on OpenControl's channel | not run | BL-13 — BL-13 — no second phone | +| Y11 | Export; the system log; switch to user 10. | "Debug log saved (N lines)." | — | ✅ | BL-end1 — "Debug log saved (1062 lines)."; the app logcat and the System log saved (s946–976) | +| Y12 | **User 10 (no Play).** Open the app; Info; Settings. | the build; no card, no Play line | the seventeen requests; the snapshot claim released ≈ 1.5 s after its answer | ✅ | BL-14 (`PAIR-003`, `FW-003`, `BATT-004`) — BL-14 — S10–S12; Info "App: 1.3.0, build ff27ee1", Settings without the card (film 2 s73–86) | +| Y13 | Connection tab: 10 holds; then ANC: 10 holds; the (i). | every hold followed / shown; "Changed by the Buds at …" | 20 stream packets; no app `SABM`; 0 Play claims (positive control: part 1's count); **no `DISC` of DLCI 0x04 while the tab is shown — there is no claim** | ⚠️ | BL-15, BL-16 (`TOUCH-007`, `ANC-001`…`004`) — BL-15 not run; BL-16: **12 of 12 within 0.1 s** (07:05:24–07:06:38), the (i) "Changed by the Buds at …" five times; 0 Play claims (positive control part 1: 18) | +| Y14 | A tap; the tile. | the mode; the app follows the tile; no "Changed by the Buds" for the own change | one claim each; the stream packet ≈ 0.3 s after the `Set`; the `DISC` ≈ 1.5 s after the ACK | ⚠️ | BL-17, BL-18 (`ANC-001`…`004`) — BL-17: 4 taps at attempt 1, ACK +25…82 ms, the stream +55…94 ms, no cause line; BL-18 (the tile) not run | +| Y15 | One bud out / in (as Y6). | as Y6 | as Y6 | ✅ | BL-19 (`PAIR-003`, `TOUCH-007`) — BL-19 done differently: the Buds' `DISC` 07:03:00 (the Right out of the case) → the re-open on ch 21, a push before its subscription, the read equal | +| Y16 | Bluetooth off / on. | ready again by itself | the re-open's requests — the subscription again | ✅ | BL-20 (`PAIR-003`) — BL-20 — 07:02:39/44; "Bluetooth was switched off on this phone"; the LINK_BACK re-open subscribed again (H2 540) | +| Y17 | Controls tab: 5 holds; then ANC. | the last mode; "Changed by the Buds" | 5 packets; no app `SABM` | not run | BL-21 (`TOUCH-007`) — BL-21 | +| Y18 | Disconnect; two holds while disconnected; Connect; the ANC (i). | after the Connect the mode read; **"Changed since the app last saw it (read at HH:MM:SS): a press-and-hold on a bud or the Buds' own change, made while this app was not connected."** | nothing while disconnected; the Connect's seventeen requests; the read's answer | ⚠️ | BL-22 (`PAIR-003`, `TOUCH-007`) — BL-22 done differently: Disconnect/Connect 07:04:12/15 with no hold in between — the read equal, no line (correct); the differing-read line not exercised here (see Y6) | +| Y19 | Both buds into the case, 15 s, out, 15 s. | the session ends and re-opens by itself, or "The Buds don't allow changing noise control right now …" with the mode kept; the mode read once worn; **a value 0 never shown as a mode** | the loss and the re-open's requests, or a stream packet `4:{13:0}`; the read's answer 0 in the case, 1–4 worn | ✅ | BL-23 (`PAIR-003`) — BL-23 in part 1: both buds into the case 06:42:11 (`4:{13:0}`), the ACL's end, the Connect's "no mode settable"; **0 never shown as a mode** (s442) | +| Y20 | (optional) Home; two holds while hidden; return. | the holds' result on return | 2 packets while hidden; no `SABM` | not run | BL-24 (`TOUCH-007`) — BL-24 | +| Y21 | (optional) The official app on a second phone: an EQ slider; a switch. | OpenControl follows | the stream packets | not run | BL-25 — BL-25 — no second phone | +| Y22 | Export; the system log; the APK pull. | "Debug log saved (N lines)." | — | ✅ | BL-end2 — "Debug log saved (462 lines)."; the app logcat; no system log in user 10; the APK pulled by `ai-sessions/0092` (SHA-256 = the tested build's) | + +## Z. 1.3.1 (`ai-sessions/0094`: the fixes of `ai-sessions/0093` A93-APP-01 … 04, 07, 08 — in the user without Google Play) + +In `CAP-077` as BM-1 … BM-9 (captures/CAP-077-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_BM/CAP-077-EVENT-NOTES.md, which has the reference frames). Nothing new on the +wire: the requests are 1.3.0's. + +| ID | Steps | Expected on screen | Expected on the wire | Result | Notes | +|---|---|---|---|---|---| +| Z1 | **User 10 (no Play).** Update over 1.3.0 (Debug mode on); open the app; Info; gear → Settings. | "App: 1.3.1, build ", no "-dirty" | the seventeen requests | | BM-1 (`PAIR-003`, `FW-003`) | +| Z2 | The ANC tab and its (i); the notification; the tile; the Sound tab's presets. | one name per mode everywhere ("Noise cancellation", "Off", "Adaptive", "Transparency"); "Connection: connected"; "Heavy bass" | — | | BM-2 (A93-APP-08) | +| Z3 | TalkBack on: the Case sounds switches, a Controls switch, the Bass slider, the bottom tabs; TalkBack off. | each switch read with its name and state; the slider "Bass, … dB"; each tab named once | — | | BM-3 (A93-APP-07) | +| Z4 | Disconnect; two holds while disconnected; Connect; the ANC (i). | **"Changed since the app last saw it (read at HH:MM:SS) …"** stays after the Connect | the `Get`'s `Notify`, then the equal `4:13` answer (`CAP-076` H1 4094 → 4135) | | BM-4 (A93-APP-02; `PAIR-003`, `TOUCH-007`) | +| Z5 | Within ≈ 2 s of "Ready": ten tile taps ≈ 3 s apart. | every tap changes the mode or says why not — none silent | per tap `08 11`, its `Notify`, `08 12 …`, the ACK | | BM-5 (A93-APP-03; `ANC-001`…`ANC-004`) | +| Z6 | A hold within ≈ 1 s of tapping Connect. | the held mode | the export's "Noise-control mode read" after the RESPONSE, not the push | | BM-6 (A93-APP-04) | +| Z7 | Use different Buds → Cancel; then → Forget these Buds; rotate; Connect. | Cancel changes nothing; after Forget the app asks to pair — not connected by name | no `SABM` on DLCI 0x02 after the Forget | | BM-7, BM-8 (A93-APP-01, A93-APP-08; `PAIR-003`) | +| Z8 | *Pair a device* → the same Buds in Android's picker. | connected | the seventeen requests | | BM-9 (`PAIR-003`) | +| Z9 | Export; the app logcat; the APK pull. | "Debug log saved (N lines)." | — | | BM-end | ## After the run (within 1 minute of the last action) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 4c70495f..4b9c2236 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -125,8 +125,8 @@ enforced by the build graph, not just by convention: or battery persistence exists yet — this project's own state-reconciliation design (§3.1) treats the hardware as the sole source of truth for those, by design, not as a gap. -- **Hardware Layer** (`:hardware`): Owns raw `BluetoothSocket`/`BluetoothGatt` - objects and the `ForegroundService` that keeps the RFCOMM channel alive +- **Hardware Layer** (`:hardware`): Owns the raw `BluetoothSocket` + objects (no `BluetoothGatt` client exists — §1; changed 2026-10-15, `ai-sessions/0094`, A93-ARCH-03) and the `ForegroundService` that keeps the RFCOMM channel alive during active use. Exposes a small interface (`BudsTransport`) so upper layers never touch Android BT APIs directly. Contains the `ConnectionStateMachine` (§2.1). @@ -150,7 +150,7 @@ Connection screen (start destination) │ own navigation stop; see §3.1's table) ├─ Find My Buds screen └─ Settings menu (since `ai-sessions/0062`; only reachable via the top app bar's gear — never shown - in the main bottom/side navigation), with three tabs: Settings (dark mode; since 1.2.1 the "Noise-control channel" choice, ADR-062, `ai-sessions/0087` — since 1.2.2 "Noise control with Google Play services", ADR-062 Update 2 and ADR-063, `ai-sessions/0089`; since 1.2.0 the two case sounds, `ai-sessions/0082`; "Use different Buds"), + in the main bottom/side navigation), with three tabs: Settings (dark mode; since 1.2.0 the two case sounds, `ai-sessions/0082`; "Use different Buds" — 1.2.1 … 1.2.2's noise-control-channel card was removed in 1.3.0, ADR-065), Debug (the developer-facing Debug screen, per AGENTS.md §6/§9 not part of ordinary use) and Info (the app's build, the licence and the project links — `ai-sessions/0064`, ADR-050 — the Buds' firmware and, since 1.2.0, their serial numbers — ADR-058) ``` @@ -244,7 +244,7 @@ These components are deliberately isolated from the rest of the app so that: | Component | Layer | Responsibility | |---|---|---| -| `BudsTransport` | `:hardware` (interface consumed by `:data`) | Abstracts the underlying RFCOMM `BluetoothSocket` (primary) and, where applicable, `BluetoothGatt` (secondary — case/charging characteristics). Upper layers see only `send(channelId: Int, frame: ByteArray)` / an inbound `Flow>`, never raw Android BT types. `channelId` addresses one of the three coexisting RFCOMM DLCIs (`PROTOCOL.md` §2.3) — it is not optional, since the three channels have independent framing and cannot share one send/receive path. `connectionLost` (added `ai-sessions/0038`, hardened `ai-sessions/0039`) emits at most once per connection, only after every socket of that connection is closed; `openChannel`/`closeChannel`/`channelClosed` (added `ai-sessions/0040`, ADR-032) manage the shared on-demand DLCI 0x04 channel — see §6.0b. | +| `BudsTransport` | `:hardware` (interface consumed by `:data`) | Abstracts the underlying RFCOMM `BluetoothSocket` (the only transport as built; a GATT client would need its own ADR — the Case battery comes from DLCI 0x02, ADR-043). Upper layers see only `send(channelId: Int, frame: ByteArray)` / an inbound `Flow>`, never raw Android BT types. `channelId` addresses one of the three coexisting RFCOMM DLCIs (`PROTOCOL.md` §2.3) — it is not optional, since the three channels have independent framing and cannot share one send/receive path. `connectionLost` (added `ai-sessions/0038`, hardened `ai-sessions/0039`) emits at most once per connection, only after every socket of that connection is closed; `openChannel`/`closeChannel`/`channelClosed` (added `ai-sessions/0040`, ADR-032) manage the shared on-demand DLCI 0x04 channel — see §6.0b. | | `ConnectionStateMachine` | `:hardware` | Explicit state machine (`Disconnected → Connecting → Discovering → Ready → ...`) driving `ConnectionState`. **As built (ADR-032):** `Ready` = "the MAESTRO channel (DLCI 0x02) is open"; `Discovering` is a zero-length pass-through (SDP resolution happens inside the socket open). These are app states, not wire-observed ones (`PROTOCOL.md` §5). | | `CodecRouter` (per-DLCI `FrameEncoder` / `FrameDecoder`) | `:data` | Routes each inbound `(channelId, bytes)` pair from `BudsTransport` to the codec for that DLCI, and routes each outbound command to the codec for whichever DLCI it belongs on. See §5 for the three channels and their framing. Pure Kotlin, no Android dependencies — fully unit-testable against fixed byte-array fixtures, per `AGENTS.md` §11. Each per-DLCI codec is implemented independently, gated on that DLCI's own framing reaching 🟢 FACT confidence (see §5's implementation gate) — one DLCI's codec is never blocked on another's. | | `BudsRepository` / `BudsRepositoryImpl` | interface in `:domain`, implementation in `:data` | Translates protocol-level events into domain models, exposed as `Flow`/`StateFlow` to the domain layer. | @@ -289,7 +289,10 @@ change state while this app is disconnected or backgrounded. never a mode. The last value received wins, whatever its channel (the Message Stream's `Notify` or the stream). The cause of the shown mode: an own `Set` within `ANC_SET_WINDOW_MS` → `SET_BY_APP`; a differing push → `CHANGED_BY_BUDS`; a differing read answer (the `Get`'s `Notify`, the `4:13` RESPONSE) → `CHANGED_SINCE_LAST_READ` — also across a Connect, since the kept mode of the ended session is the reference (the maintainer's choice, chat 2026-10-14, Q4; - before 1.3.0 the cause was reset at Connect). Nothing is written in reaction to a stream packet; the Message Stream is claimed only for a tap, the tile, + before 1.3.0 the cause was reset at Connect); **since 1.3.1** (`ai-sessions/0094`, A93-APP-02) the other read of the same session that finds the same mode keeps + that cause (1.3.0 replaced it with a reading — `CAP-076` §6.2), a same-mode read from another source or session is a reading. **Since 1.3.1** a stream packet + never ends a read's wait (only its RESPONSE does) and a Refresh waits for the `Notify` to its own `Get` (A93-APP-04); the `Get`'s answer never ends a tap's `Set` + wait (A93-APP-03, `CAP-076` §5.3). Nothing is written in reaction to a stream packet; the Message Stream is claimed only for a tap, the tile, Refresh, a pull and the Connect's snapshot (ADR-032). - **One rule for "current" (built `ai-sessions/0069`, 1.0.1; `ai-sessions/0068` A68-APP-02; the maintainer's choice in chat 2026-10-03, "Keep the value, mark it"; recorded as **ADR-057** on 2026-10-07):** a value is current only while the session is `Ready` **and** the Buds reported it on this connection — `isCurrent(ready, valueAt, @@ -298,7 +301,9 @@ change state while this app is disconnected or backgrounded. since `0069`, A68-APP-03, so the newest report is never dropped), the EQ and the settings while nothing is connected (at a Connect they are reset to "not read", as before), and the battery values, which are marked at Disconnect and at a session loss, not only at the next Connect. The Quick Settings tile and the notification take the mode through the same rule (`currentAncMode`) and show none that is not current. A value that was never read shows "—", not a - default — since 1.1.0 (`ai-sessions/0074`) also for a switch, and a screen reader says "Not read from the Buds yet" for it. + default — since 1.1.0 (`ai-sessions/0074`) also for a switch, and a screen reader says "Not read from the Buds yet" for it. Since 1.3.1 (`ai-sessions/0094`, A93-APP-07) each switch row is one toggleable element with its label (`Role.Switch`), each EQ band + slider carries its band name and its gain in dB as screen-reader text, and the bottom tabs' icons have no text of their own; each noise-control mode has one + name everywhere (`AncMode.label`, A93-APP-08). - Locally cached values are marked provisional/stale until reconciled against a fresh read. **Added `ai-sessions/0043`:** each cached value's own wall-clock receive time is threaded alongside it as a sibling `StateFlow` (`ancModeUpdatedAt`, `eqProfileUpdatedAt`, `batteryStatusUpdatedAt`, @@ -326,7 +331,7 @@ should not be assumed uniform):** | Feature | Reconciliation mechanism on (re)connect | Confidence | |---|---|---| -| ANC | **Since 1.2.0 (`ai-sessions/0082` item 1):** every `ancMode` carries its cause (`AncModeCause`): the answer to the app's own `Get` is READ, the app's ACKed `Set` (or the Buds' `Notify` of the requested mode while the `Set` waits) is SET_BY_APP, any other `Notify` with a mode other than the shown one is CHANGED_BY_BUDS — derived from what the app had just sent, no timer; reset at Connect; shown only as the (i) line "Changed by the Buds at HH:MM:SS (…)". **Since the 1.2.0 rebuild (ADR-061):** the claim is held while the noise-control tab is on screen, so such a `Notify` (a press-and-hold on a bud) reaches the app there (`CAP-072` §5: none did between claims, 17:44:34–17:48:46). **Corrected `ai-sessions/0040`:** the connect-time `Get`/`Notify` pair (ADR-021/ADR-022) is the *official client's* own query — a client that sends nothing gets no ANC `Notify`. This app therefore sends its own `AncFrame.Get` during each Message Stream claim (the Connect-time snapshot, ANC Refresh, and each ANC tap's reply) and treats the value as last-known between claims (ADR-032). **A `Set` is only counted once answered (2026-09-24):** ACK → requested mode, `Notify` → the Buds' mode, NAK/no answer → failure, previous mode kept. The `Notify`'s Settable byte is kept as `ancAvailability` (`ai-sessions/0048` I-3, ADR-024 Update 2026-09-25): while it reads `0x00` the Buds NAK a `Set` (reason `0x02`, `CAP-062` 10/10). **Since `ai-sessions/0054` (I-1, maintainer-confirmed in chat 2026-09-28; it reverses `0048` I-3's "claim and send nothing" for a user tap):** the ANC buttons and the tile stay tappable, with "ANC can only be changed while you wear the Buds (checked HH:MM:SS). Tapping a mode checks again first." (🟡 wording); such a tap does one ordinary claim with the `Get` and sends the `Set` **in that claim** only if the answering `Notify` reads Settable non-zero (then the usual ACK rules); if it still reads `0x00` nothing more is sent, the checked time moves and the tile shows a toast — because a `0x00` answer can be old (`CAP-063`: worn again 37 s after `Notify` 4774, disabled for 6 min). ~~Unknown (no `Notify` yet this connection) or non-zero ⇒ the `Set` is sent directly, as before.~~ **Since `ai-sessions/0062` (F-1/F-2, maintainer-confirmed in chat 2026-10-01): every ANC tap — the screen's and the tile's — does one claim with the `Get` first and sends the `Set` in that claim only if the claim's `Notify` reads Settable non-zero; on `0x00` nothing more is sent** (`CAP-064`: an 18-s-old `e8` led to `Set` 3433 → NAK 3440; `CAP-065` F7: an unknown availability sent `Set` 10790 without a `Get`). The tile's next mode is computed from that fresh `Notify`, not from the mode shown. Wording: "The Buds don't allow changing noise control right now (usually because no bud is in an ear). Tapping a mode checks again first." (tile subtitle "Not allowed now") — the byte means "not allowed now", not "not worn" (`CAP-064`: `e8` with no bud worn). **F-3:** a claim closed after its `Set`/`Get` was written and before the answer is `BudsError.AnswerCutOff` (never retried, never "didn't respond in time"); after a cut-off `Set` the shown mode is "not confirmed" (dimmed + the (i) dot) until the Buds' next `Notify` or ACK. **Hardware-verified in `CAP-066` (`ai-sessions/0063`, maintainer-approved in chat 2026-10-01):** 23 of 23 app claims sent `08 11` first; 4 `Set`s, each after that claim's `Notify` read `e8`, all ACKed; 12 `00` answers sent no `Set` (count corrected 2026-10-08, `ai-sessions/0081`: 23 app claims, 12 `00`, 0 `Set`s after `00`); the tile stepped from the fresh `Notify` (A2369 `40` → A2370 `20`). F-3 (`AnswerCutOff`) not exercised (no cut-off occurred). | 🟢 FACT for the `Get`/`Notify` pair; the per-claim use is ADR-032 | +| ANC | **Sources (as built at 1.3.0, ADR-064/065; rewritten 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-ARCH-01):** the Message Stream's `Notify` on the app's own claims (the Connect snapshot, Refresh, a tap, the tile — ADR-032), and on DLCI 0x02 the settings stream's `4:{13:n}` pushes plus the `ReadSetting 4:13` at every Connect and re-open (the bullet above). The last value received wins. Every `ancMode` carries its cause (`AncModeCause`): the app's ACKed `Set`, or a report of the requested mode within `ANC_SET_WINDOW_MS`, is SET_BY_APP; a differing push is CHANGED_BY_BUDS; a differing read answer is CHANGED_SINCE_LAST_READ — the cause is **not** reset at Connect (the kept mode of the ended session is the reference); the first mode ever shown is READ. Shown only in the (i) details. (1.2.0 … 1.2.2 held the claim while the noise-control tab was on screen — ADR-061/062, superseded by ADR-065; history in §6.0b.) **Corrected `ai-sessions/0040`:** the connect-time `Get`/`Notify` pair (ADR-021/ADR-022) is the *official client's* own query — a client that sends nothing gets no ANC `Notify`. This app therefore sends its own `AncFrame.Get` during each Message Stream claim (the Connect-time snapshot, ANC Refresh, and each ANC tap's reply) and treats the value as last-known between claims (ADR-032). **A `Set` is only counted once answered (2026-09-24):** ACK → requested mode, `Notify` → the Buds' mode, NAK/no answer → failure, previous mode kept. The `Notify`'s Settable byte is kept as `ancAvailability` (`ai-sessions/0048` I-3, ADR-024 Update 2026-09-25): while it reads `0x00` the Buds NAK a `Set` (reason `0x02`, `CAP-062` 10/10). **Since `ai-sessions/0054` (I-1, maintainer-confirmed in chat 2026-09-28; it reverses `0048` I-3's "claim and send nothing" for a user tap):** the ANC buttons and the tile stay tappable, with "ANC can only be changed while you wear the Buds (checked HH:MM:SS). Tapping a mode checks again first." (🟡 wording); such a tap does one ordinary claim with the `Get` and sends the `Set` **in that claim** only if the answering `Notify` reads Settable non-zero (then the usual ACK rules); if it still reads `0x00` nothing more is sent, the checked time moves and the tile shows a toast — because a `0x00` answer can be old (`CAP-063`: worn again 37 s after `Notify` 4774, disabled for 6 min). ~~Unknown (no `Notify` yet this connection) or non-zero ⇒ the `Set` is sent directly, as before.~~ **Since `ai-sessions/0062` (F-1/F-2, maintainer-confirmed in chat 2026-10-01): every ANC tap — the screen's and the tile's — does one claim with the `Get` first and sends the `Set` in that claim only if the claim's `Notify` reads Settable non-zero; on `0x00` nothing more is sent** (`CAP-064`: an 18-s-old `e8` led to `Set` 3433 → NAK 3440; `CAP-065` F7: an unknown availability sent `Set` 10790 without a `Get`). The tile's next mode is computed from that fresh `Notify`, not from the mode shown. Wording: "The Buds don't allow changing noise control right now (usually because no bud is in an ear). Tapping a mode checks again first." (tile subtitle "Not allowed now") — the byte means "not allowed now", not "not worn" (`CAP-064`: `e8` with no bud worn). **F-3:** a claim closed after its `Set`/`Get` was written and before the answer is `BudsError.AnswerCutOff` (never retried, never "didn't respond in time"); after a cut-off `Set` the shown mode is "not confirmed" (dimmed + the (i) dot) until the Buds' next `Notify` or ACK. **Hardware-verified in `CAP-066` (`ai-sessions/0063`, maintainer-approved in chat 2026-10-01):** 23 of 23 app claims sent `08 11` first; 4 `Set`s, each after that claim's `Notify` read `e8`, all ACKed; 12 `00` answers sent no `Set` (count corrected 2026-10-08, `ai-sessions/0081`: 23 app claims, 12 `00`, 0 `Set`s after `00`); the tile stepped from the fresh `Notify` (A2369 `40` → A2370 `20`). F-3 (`AnswerCutOff`) not exercised (no cut-off occurred). | 🟢 FACT for the `Get`/`Notify` pair; the per-claim use is ADR-032 | | EQ | No connect-time *push* exists for EQ, but the official app **reads** it — and so does this app since `ai-sessions/0041` (DECISIONS.md ADR-034, maintainer-approved 2026-09-20): the Connect sequence waits for the Buds' unsolicited `GetSoftwareInfo` announcement (which names this connection's pw_rpc channel), then sends `ReadSetting 4:16` (active EQ) on that channel and fills `eqProfile` from the answer; the EQ screen can re-read on demand. `eqProfile` is `null` only until that answer arrives or if it failed — `BudsRepository.eqError` then carries the reason (no announcement in time, a channel with no known address, an error status, a timeout). A write is only counted as done once the Buds' `RESPONSE` arrives (`status` absent/OK); anything else is surfaced, never assumed. Field 18 (last saved custom EQ) never replaces the active value. | 🟢 FACT (ADR-034: identification, `ReadSetting 4:N` semantics, three second-capture chains); 🟡 HYPOTHESIS for what a fresh client must send first and for request/response matching (handled conservatively, `// TODO(verify)`, hardware re-test) | | Battery (DLCI 0x04 Option B, **implemented `ai-sessions/0040`, ADR-033; charging flag `ai-sessions/0041`**) | Push-based: the Buds send three `Group 0x03 Code 0x03` frames within ~10 ms of the channel opening and again on every change — so each Message Stream claim yields a reading. Each of `b1`/`b2` is `0bSVVVVVVV`: `V` = 0–100 % and `S` = charging (maintainer-accepted 2026-09-20); `V = 0x7F`/`> 100` reads "unavailable"; `b3` (Case) is never decoded (it read `0xff` in 60/60 frames). **The Case comes from DLCI 0x02** (**ADR-043**, 2026-09-24): after the Connect-time EQ read the app sends one `SubscribeRuntimeInfo` request on the announced channel; the Buds then push `SERVER_STREAM` packets by themselves and entry 6.1 field 1 is the Case % — a packet without that entry reads "unavailable". *Refresh battery* re-reads Left/Right — **since `ai-sessions/0052`** always on a **fresh** claim (a claim still lingering is released and opened again, because the Buds send the `03 03` burst only on an open; no burst ⇒ "No new battery reading from the Buds — try again.", values and times unchanged) and it also re-sends one `SubscribeRuntimeInfo` (ADR-043 Update 2026-09-26; no wait, no retry). **Since `ai-sessions/0048` (ADR-043 Update):** the same stream's 6.2/6.3 field 2 (fallback 7.2/7.1) gives each bud's charging state, shown as "charging in the case" with its own time; the newest charging report of either source wins (I-8); a packet without 6.1 keeps the last Case value as "last seen HH:MM:SS" (I-5, in memory only, marked last seen after a reconnect). The DLCI 0x08 claim of ADR-035/038/039 is withdrawn (its `0e 04` got no answer in `CAP-061`, 8/8). | 🟢 FACT (ADR-031 identity, ADR-033 unblock + charging-flag update; `PROTOCOL.md` §4.3 Option F + ADR-043 for the Case) | | Battery (HFP, Option C) — **removed `ai-sessions/0042`** | Push-based on the wire (`AT+BIEV`/`AT+CIND`, ADR-015/023) but **not consumable by an app**: `CAP-059` shows `AT+BIEV=2,100` twelve times on the wire (seven dissected as HFP, five on the undissected DLCI after the reconnect — corrected 2026-10-03) and **zero** vendor-specific events in the app's receiver (and the value is one earbud's, never the Case). `HfpBatteryReader` and its wiring were removed on the maintainer's decision (chat 2026-09-20); the wire facts stay. | 🟢 FACT on the wire; 🟢 not app-consumable (`CAP-059-FINDINGS.md` §7) | @@ -826,11 +831,11 @@ returned when a *write* is refused by the Safe-Mode gate below; an inbound frame > for it, so the gate refused every write on the verified firmware. `Proto.fields` now skips fixed64/fixed32 fields and the tests use the real bytes of > `CAP-061` frame 1508 (`Cap061Fixtures.kt`). -Before sending any state-changing command on a new connection, the app reads -the firmware version string first (via DLCI 0x02's Rcvd block or DLCI 0x04's -Device Information, whichever is confirmed to carry it for a given -device/firmware — see `PROTOCOL.md` §2.2a/§4.1) and checks it against the set -of firmware versions this app has been verified against. +Before sending any state-changing command on a new connection, the app checks the firmware the Buds +announce on DLCI 0x02 (the unsolicited `GetSoftwareInfo` answer, `PROTOCOL.md` §2.2a) against the +verified firmware (`release_5.203`), and — for a Message Stream command — the Fast Pair Model ID of the +same claim (`da2db1`); `BudsRepositoryImpl.writeGate`, ADR-042 (rewritten as built 2026-10-15, +`ai-sessions/0094` — `ai-sessions/0093` A93-ARCH-04). - **Verified firmware:** proceed normally — Startup Handshake feeds directly into the state query described in §3.1. @@ -916,11 +921,12 @@ this sequence explicit rather than inferred from ADR-005's decision alone: **two or more** the app picks none — status `SeveralBudsPaired`, "More than one Pixel Buds device is paired with this phone. Tap Pair a device to choose the one to control." — and the user chooses in Android's picker (`PairingLogic.chooseBondedOrAsk`; the set of bonded devices has no order, so "the first" was arbitrary). Another model picked by mistake is caught by Safe Mode's Model ID check (ADR-042). - **Settings → "Use different Buds"** disconnects and removes this app's own CDM associations (`BudsCompanionPairing.forgetAssociations`; the Bluetooth - pairing in Android stays); until a new association exists no device is taken by its name (in memory — after a process restart a single bonded - "Pixel Buds" device is used again). No address is stored by the app and no in-app device list exists (`AGENTS.md` §7). -- **Local state persistence:** as built, only the Debug-mode switch, — since `ai-sessions/0062` — the dark-mode choice and — since 1.2.1 (`ai-sessions/0087`, - ADR-062) — the "Noise-control channel" choice are stored (plain AndroidX + **Settings → "Use different Buds"** (since 1.3.1 after a confirmation dialog) disconnects and removes this app's own CDM associations + (`BudsCompanionPairing.forgetAssociations`; the Bluetooth pairing in Android stays); until a new association exists no device is taken by its name — in one + `PairingChoiceMemory` shared by the Activity's pairing instance and the repository's singleton (since 1.3.1, `ai-sessions/0094` A93-APP-01: 1.3.0 set it on the + Activity's instance only, so a Connect still took the Buds by name); in memory — after a process restart a single bonded "Pixel Buds" device is used again. No address is stored by the app and no in-app device list exists (`AGENTS.md` §7). +- **Local state persistence:** as built, only the Debug-mode switch and — since `ai-sessions/0062` — the dark-mode choice are stored (1.2.1 … 1.2.2 also stored + the "Noise-control channel" choice; since 1.3.0 that key is ignored, ADR-065 item 5) (plain AndroidX DataStore Preferences, one file — non-sensitive values, §2). Future user data (e.g. custom EQ profiles) would use DataStore, **encrypted where applicable** (`AGENTS.md` §10); no EQ or battery value is persisted (the hardware is the source of truth, §3.1). Nothing is ever transmitted off-device (see `AGENTS.md` §1 and §9 for the enforcement rules). (Aligned 2026-09-24, @@ -1050,7 +1056,8 @@ As built (rewritten 2026-09-24, 0044 AR-2): > Move to `DECISIONS.md` once decided, following the ADR template. -**Re-checked `ai-sessions/0045` (2026-09-24):** the HID question below is closed; no architecture question is open. +**Re-checked `ai-sessions/0045` (2026-09-24):** the HID question below is closed. **Open (2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-ARCH-05):** +§6.0b's "automatic session connecting (b)" — connecting while the app is not visible — is a deferred proposal that needs a maintainer decision and an ADR. **Earlier, `ai-sessions/0033` (2026-09-18):** the HID-surface question below is confirmed still the only genuinely open *architecture* question — no new one surfaced during this session's Phase 1 refresh. A related but distinct gap *was* found (the missing consolidated implementation-unblock ADR for DLCI diff --git a/CAPTURE_BLUETOOTH_HCI_SNOOP.md b/CAPTURE_BLUETOOTH_HCI_SNOOP.md index ea7ca3e6..49eacdd9 100644 --- a/CAPTURE_BLUETOOTH_HCI_SNOOP.md +++ b/CAPTURE_BLUETOOTH_HCI_SNOOP.md @@ -217,36 +217,11 @@ you need to re-invoke to "keep it going" — and collect a single `adb bugreport following the usual rhythm: **wait ~5s → note the exact time → perform the action → wait ~5–10s → move to the next action.** -**⚠️ Priority tip (superseded 2026-08-14 — original text kept below, not deleted, per -`PROJECT_RULES.md` §3):** the original advice below was to prioritize Group K to confirm/refute -the RFCOMM framing hypothesis. That hypothesis question is now resolved for ANC specifically -(`PROTOCOL.md` §4.1 — the official Fast Pair Message Stream, Group `0x08`, byte- and -timing-verified against `CAP-001`), which is what Group K's own frame would have helped confirm. -**The new top priority is Group T (EQ command isolation, below)** — EQ's command channel is the -one major control feature this project still needs to attribute, now that the earlier assumption -"EQ probably shares ANC's channel" no longer holds (`PROTOCOL.md` §2.3's 2026-08-14 addendum). The -same caution applies to Group T's result as applied here to Group K: a superficial byte-pattern -match on a single capture is a HYPOTHESIS, not a FACT — see Group T's own cross-command check. - -**This "Group T is top priority" framing is itself now superseded (fixed 2026-09-18, -`ai-sessions/0032`, a plain staleness correction, not a new finding — `PROJECT_RULES.md` §1) — kept -below, not deleted, per `PROJECT_RULES.md` §3's precedent already established by this same block.** -EQ's command channel was fully resolved by `CAP-005`/`CAP-015` (Group T's own captures, see §9's -Capture Index): the field-to-band mapping, the outer envelope, and the individual settings are all -🟢 FACT and implementation-unblocked (`DECISIONS.md` ADR-013/ADR-019/ADR-020). Group T itself is no -longer an open capture priority — see `TODO.md`'s own current priority order for whatever this -project's actual current top priority is. - -**Original tip (2026-08-08 or earlier, kept for the record):** this document's own verification -methodology flags the "Play sound on Left earbud" action (group K below) as a specifically -valuable, low-risk target — its frame can be directly compared against the Fast Pair Message -Stream spec's own worked example (`0x04 0x01 ...` for a ring action) to confirm or refute the -framing hypothesis in `PROTOCOL.md` §2.1. If you only have time for a short session, prioritize -group K. -**Caution when you get there:** a superficial resemblance to the worked example (e.g. a -plausible-looking group/code byte pair) is not by itself confirmation — don't promote the -framing hypothesis to FACT off one frame that merely looks compatible. See the -cross-command check in Group K below. +**Priority (rewritten 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-CAP-02):** the framing and command questions these tips once ranked (Group K, +then Group T) are settled — ANC, Find, EQ and the settings are 🟢 and built (`PROTOCOL.md` §4, ADR-009/011/013/019/020). The current capture priorities are +`TODO.md` §3 ("Planned captures"). The earlier tips stood here until 2026-10-15; this guide is not under `PROJECT_RULES.md` rule 9a's keep-history scope, and +they remain in the git history (`git log -p -- CAPTURE_BLUETOOTH_HCI_SNOOP.md`). The caution they carried still holds for every Group: a byte pattern that +merely looks compatible on one capture is a 🟡 HYPOTHESIS, not a FACT. #### Group Z — Pipeline validation (do this first, before anything else) Before spending your pairing baseline (Group A) or any real reverse-engineering capture, @@ -696,7 +671,7 @@ same window. > succeeded.** This session was extracted via the raw BTSnoop file path (§3 step 3) instead of the > `btsnooz.py` fallback used for the three prior attempts — the resulting log is genuinely > untruncated (`frame.cap_len == frame.len` for all 2,455 frames) and its first frame -> (18:29:45.72) lands **~58s *before*** the on-screen Forget tap (18:30:42), and ~30s before the +> (18:29:45.72) lands **~56s *before*** the on-screen Forget tap (18:30:42; ~58s before its HCI effect, frame 768), and ~30s before the > video itself starts. **The primary question is now answered for this session: 🟢 no BLE link or > valid classic link key existed for the Buds anywhere in the covered pre-Forget window** > (`CAP-032-FINDINGS.md` §0.3) — zero classic connection events, exactly one LE connection (to an @@ -1161,7 +1136,7 @@ differs and also plausibly matches a known settings-value shape (e.g. a 5-band f `PROTOCOL.md` §4.2) is a strong candidate for a `libmaestro`-side state read-back; a byte-for-byte match despite genuinely different settings is a clean negative. See `CAP-041-EVENT-NOTES.md`'s Decode/Analysis checklist. **Outcome (pointer, 2026-09-24):** content-level clean negative (`CAP-041-FINDINGS.md` §8); the burst itself -is now identified — it includes a `ReadSetting` sweep of fields 1–32 (`PROTOCOL.md` §6). +is now identified — it includes a `ReadSetting` sweep of fields 1–38 without 6, 8, 9, 10, 14, 20 and 33 (`PROTOCOL.md` §6; corrected 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-CAP-02). #### Group AI — Long pure-idle bracket for the periodic DLCI 0x02/0x04/0x08/HFP push cadence (occasional, added 2026-09-05) @@ -1780,7 +1755,7 @@ run:** the case lid open before the update (P8), no app use before the film (P9) Pixel 9a / GrapheneOS, the release-signed **1.2.0** APK (`DECISIONS.md` ADR-058, ADR-059; `ai-sessions/0082`) in the secondary user without Google Play, installed **over 1.1.1** (P1 records `firstInstallTime` and `lastUpdateTime` before and after). Scope chosen by the maintainer (chat 2026-10-09, the checkpoint of `ai-sessions/0082`). The skeleton `captures/CAP-072-2026-10-09_17-27-26_18-23-39-Group_BH/CAP-072-EVENT-NOTES.md` holds: **I** the update and the start — the -Connect-time traffic now thirteen MAESTRO requests (the twelve settings reads, `SubscribeRuntimeInfo`, then **one `GetHardwareInfo`**, byte-identical to the +Connect-time traffic now fifteen MAESTRO requests (the EQ read and the twelve settings reads, `SubscribeRuntimeInfo`, then **one `GetHardwareInfo`**, byte-identical to the *(Counts corrected 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-CAP-01: earlier text said "thirteen"; a 1.2.x Connect sends fifteen MAESTRO requests — thirteen `ReadSetting`s (the EQ's `4:16` and twelve settings), `SubscribeRuntimeInfo`, `GetHardwareInfo` — `CAP-073`, 11 of 11 sessions; 1.3.0 sends seventeen.)* official app's `CAP-036` 1415 / `CAP-024` 801), the Info tab's three serial lines (Case, Right bud, Left bud — redacted in every note), the Case sounds card on gear → Settings and the Conversation detection card on Controls, "Both buds in the case" on the battery card; **II** ADR-059's six readings against the ears on film with a pull on the Connection tab after every wear change (`INEAR-006`: both in the case, only the Left in, both in, the Left out with both worn on @@ -1797,7 +1772,7 @@ the lid open before the app starts, no rehearsal before the film, the `adb bugre **Status:** ✅ analyzed (`ai-sessions/0083`, 2026-10-09/10) — film 1 only; film 2 (BH-25/BH-26) **not made**: the maintainer folded it into the re-test of the rebuilt 1.2.0 (chat 2026-10-10). Run note: 1.1.1 was freshly reinstalled at 17:24 (the release APK had been installed earlier that day); 31 sessions sent -the thirteen requests with one `GetHardwareInfo` each (30 sent, 29 answered, one cut by the Buds' `DISC`, never retried); 60 writes, 5 ANC `Set`s, 4 Ring/Stop +the fifteen requests with one `GetHardwareInfo` each (30 sent, 29 answered, one cut by the Buds' `DISC`, never retried); 60 writes, 5 ANC `Set`s, 4 Ring/Stop answered and byte-identical; the worn line followed ADR-059 in every reading (`e8` with both buds on a table at 42 s); lead L-1 4 of 4 and "only the Right out ⇒ 21" 6 of 6 (`PROTOCOL.md` §2.2a, promoted); **the press-and-holds were not shown because no Message Stream claim was open (ADR-032) — verdict "fix first", ADR-061**; the case chime varied by bud while every write and read was correct; an incoming call ran on the Buds over eSCO without touching the session. The @@ -1809,7 +1784,7 @@ films are committed with the caller's number, the Wi-Fi name and the browser his Pixel 9a / GrapheneOS, **1.2.0 rebuilt as the same version** (versionCode 10200) after `CAP-072`'s verdict "fix first" (the maintainer, chat 2026-10-10), in the secondary user without Google Play, installed **over the `ec6d163` 1.2.0** of `CAP-072`. **Short by design** (the maintainer's request in `ai-sessions/0083`; ≤ 20 min on film). The skeleton `captures/CAP-073-2026-10-10_08-23-35_08-33-24-Group_BI/CAP-073-EVENT-NOTES.md` holds: **I** the minimal release run on the -screen recording — the update, Info (build, firmware, serials), one Connect with the thirteen requests, Bluetooth off/on, one export; **II** `DECISIONS.md` +screen recording — the update, Info (build, firmware, serials), one Connect with the fifteen requests, Bluetooth off/on, one export; **II** `DECISIONS.md` ADR-061 with the camera on the buds and the head — the noise-control tab opened (one claim, no `DISC` while it is shown), a press-and-hold on the Left and the Right bud (the unprovoked `08 13` and the (i) line "Changed by the Buds at …"), a tap and the tile on the held channel (no second `SABM`), the phone's `DISC` ≈ 1.5 s after leaving the tab and after Home, a new claim on return, and the ADR-044 re-open's snapshot claim kept as the hold after both buds went into the @@ -1862,7 +1837,7 @@ Test-IDs: [`PAIR-003`], [`BATT-004`], [`FW-003`], [`ANC-001`]…[`ANC-004`], [`T exists yet: this run makes the first.** **Result (`ai-sessions/0088`, `CAP-075-FINDINGS.md`):** run in the reverse order and as two sets (set 1 the secondary user without Play on 2026-10-12, set 2 the Owner with Play on 2026-10-13). Without Play both positions are identical: 41 of 41 press-and-holds shown within a second, no foreign close, no hint. With Play: "Show a hint" showed the hint with the cut's time and no re-claim (0 of 9 holds shown — the app's claim lives only until Play's next connect, 5.3–5.8 s after Play's loss); "Take it back" ran three chains of exactly three re-claims (3 / 6 / 12 s scheduled; on the wire 4.0–4.8 s after the cut, every one colliding with Play's retry 2.1–2.8 s after the cut), each hold cut 5.3–5.8 s after Play's loss — ≈ 43 % of a 44-s chain held, 8 of 19 holds shown within a second; the stack path of each cut (`already at opened state` → both sockets closed) observed 15 of 15 in the system log. BK-11 … BK-13 and BK-19 … BK-21 not run; P0's permission film and the APK pull not made. Verdict (the maintainer, chat 2026-10-13): no heavy issue, 1.2.1 released as built; follow-up: a constant 3-s step with a longer chain, a "Take it back again" action, a cause label (the next FEATURE prompt). -**Status:** 🔲 planned (skeleton written 2026-10-12 by `ai-sessions/0087`; the run follows `RELEASING.md` B1–C2 for 1.2.1). +**Status:** ✅ run 2026-10-12/13 and analysed by `ai-sessions/0088` (the Result above; skeleton written 2026-10-12 by `ai-sessions/0087`) — status line corrected 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-CAP-02. #### Group BL — 1.3.0, the settings stream on the app's own channel (ADR-064, ADR-065) in both GrapheneOS users (skeleton by `ai-sessions/0089`, rewritten by `ai-sessions/0091`) @@ -1889,6 +1864,18 @@ to find them — in the skeleton's table. **Status:** ✅ captured 2026-10-15 (part 1 06:35–06:52, the Owner user with Google Play; part 2 07:01–07:08, the secondary user without) and analyzed 2026-10-15 by `ai-sessions/0092` (`CAP-076-FINDINGS.md`): the two requests once per session, every hold on the noise-control tab shown in both users (29/29, 12/12), the own taps as the app's change; three minor defects; verdict "release as built + Known issues" (the maintainer, chat 2026-10-15). The maintainer tested only the changed functionality; BL-10, BL-13, BL-15, BL-18, BL-21, BL-24, BL-25 not run (`CAP-076-EVENT-NOTES.md`). +#### Group BM — 1.3.1, the fixes of `ai-sessions/0093` A93-APP-01 … 04, 07, 08 in the user without Google Play (skeleton by `ai-sessions/0094`) + +Pixel 9a / GrapheneOS, **1.3.1** (versionCode 10301), installed over 1.3.0 `ff27ee1`, in the **secondary user (user 10) without Google Play** (`ai-sessions/0093` +§7 D4, the maintainer's choice in chat 2026-10-15 "1.3.1 met APP-01…06"). **One part, ≤ 15 min on film with sound.** The skeleton +captures/CAP-077-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_BM/CAP-077-EVENT-NOTES.md holds: the labels (one name per mode; "connected"; "Heavy bass"); TalkBack on the +switch rows, an EQ slider and the tabs; Disconnect, two holds while disconnected, Connect — the (i) "Changed since the app last saw it" kept across the equal +`ReadSetting 4:13` answer (reference `CAP-076` H1 4094 → 4135); ten tile taps right after a Connect, none silent (`CAP-076` §5.3); a hold during a Connect; "Use +different Buds" with its dialog (Cancel, then Forget, a rotation, Connect — no connection by name — and *Pair a device* with the same Buds). Nothing new on the +wire. Test-IDs: [`PAIR-003`], [`FW-003`], [`ANC-001`]…[`ANC-004`], [`TOUCH-007`]. + +**Status:** 🔲 planned (skeleton written 2026-10-15 by `ai-sessions/0094`; the run follows `RELEASING.md` B1–C2 for 1.3.1). + ### 4.3 Hardware Actions (either phone) **Source:** `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` sections 2 (User Actions via the Case & Buds) and 4 @@ -2028,7 +2015,7 @@ three actually happened, since they mean different things for protocol reconstru — it is not RFCOMM traffic and was never expected to match the §2 envelope hypothesis; don't force-fit it there or record a false "doesn't match" finding. - Record the confirmed values in this session's `CAP-NNN-FINDINGS.md` first, then - **propose** the promotion into `PROTOCOL.md` §4.1's opcode table (RFCOMM frames) or §4.3 + **propose** the promotion into the matching `PROTOCOL.md` §4 entry (RFCOMM frames) or §4.3 (battery): a 🟢 FACT needs the maintainer's explicit sign-off (`AGENTS.md` §6) — an AI session drafts it and asks, it never raises the confidence itself *(reworded 2026-09-30, `ai-sessions/0059`, A58-GOV-05; this step used to say "raise its confidence to 🟢")*. Once @@ -2157,7 +2144,7 @@ through the known, official UI. The GrapheneOS capture is for validating connection/pairing/passive behavior on the target OS, not for discovering new commands. **Q: How do I know when I've captured "enough" and can stop?** -When every row in `PROTOCOL.md` §4.1's opcode table and every open question in §6 +When every `PROTOCOL.md` §4 entry this app relies on and every open question in §6 that's answerable via traffic analysis (as opposed to, say, firmware version lookup) has moved from 🔴/🟡 to 🟢 with a `[VERIFIED-LOCAL]` tag — or when you've made a deliberate, documented decision to leave a specific low-priority item (e.g. Find My Buds) unverified @@ -2172,7 +2159,7 @@ session's `CAP-NNN-FINDINGS.md` and then promoted directly into `PROTOCOL.md` (`PROTOCOL_NOTES.md` has been retired — there is no intermediate buffer): - [ ] `PROTOCOL.md` §2 — envelope structure fields confirmed/corrected. -- [ ] `PROTOCOL.md` §4.1 — opcode table rows filled in, confidence raised to 🟢. +- [ ] `PROTOCOL.md` §4 — the entries the app relies on at 🟢 (the 2026-08 "opcode table" wording replaced 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-CAP-02). - [ ] `PROTOCOL.md` §4.3 — battery approach confirmed or ruled out (now split into Option A: BLE advertisement, and Option B: RFCOMM Message Stream — see the current version of that section). @@ -2218,51 +2205,51 @@ is how the 2026-08-18 `CAP-005`/`CAP-007`/`CAP-010` ID-reuse incident (see | ID | Date | Phone | Android | Buds FW | App version | Group(s) | Test(s) | Purpose | Bugreport file | Extracted log | Status | |---|---|---|---|---|---|---|---|---|---|---|---| | `CAP-001` | 2026-08-09 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | Z, A, B, M | `PAIR-001`, `CASE-003`, `CASE-004`, `CASE-005`, `ANC-001`, `ANC-002`, `ANC-003`, `ANC-004`, `CASE-006` | Pipeline validation; scope grew beyond Z into full pairing baseline + all 4 ANC modes + case/bud handling | `captures/CAP-001-2026-08-09_08-51-00_08-52-20-Group_Z/CAP-001-btsnoop_hci.log` | same file (already `btsnooz`-extracted) | analyzed — see `CAP-001-FINDINGS.md` in that folder; ANC-opcode attribution inconclusive due to lack of action isolation *(resolved since: ANC is 🟢, ADR-009 and `CAP-006`)* | -| `CAP-002` | 2026-08-09 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | A | `PAIR-001`, `CASE-003` | Fresh pairing/bonding baseline (deleted stored link key first) through the Pixel Buds app's first-run setup flow (Fast Pair save-to-account, CDM permission, Device details load) | `captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-btsnoop_hci.log` (sliced from a shared, non-restarted ~8h20m snoop log — see that folder's `CAP-002-EVENT-NOTES.md` process note) | same file | analyzed — see `CAP-002-FINDINGS.md` in that folder; Fast Pair Message Stream Device Information group tentatively identified (channel 2/DLCI 0x04); no RFCOMM traffic found during app setup/Device-details load; HFP handshake present (frames 48954–49079; the earlier "no AT-command traffic" was a filter artefact, corrected 2026-10-03) (contrast with `CAP-001`) | -| `CAP-003` | 2026-08-10 | Pixel 7a | 17 | release_5.203 | nRF Connect (generic BLE tool), then official app v1.0.955078536 (took over partway) | R | `PAIR-001` | Forced GATT rediscovery attempt (pairing removed via system settings, connected via nRF Connect instead of the official app) to resolve `CAP-002`'s open `0x0f2a`/`0x0c0X` handle UUIDs; classic pairing captured as a bonus data point | `captures/CAP-003-2026-08-10_20-59-16_21-00-37-Group_R/CAP-003-btsnoop_hci.log` (short, freshly-restarted log, no slicing needed) | same file | analyzed — see `CAP-003-FINDINGS.md` in that folder; **primary goal not achieved** — Android's GATT database cache survived the pairing removal, so zero `Read By Group Type`/characteristic-discovery traffic occurred; `0x0f2a`/`0x0c0X` UUIDs still unresolved; new handle `0x0f28` found (polled every ~60s, value `0x31`); reinforces that RFCOMM channel numbers are session-local while GATT handles are stable across sessions | -| `CAP-004` | 2026-08-11 | Pixel 7a | 17 | release_5.203 | nRF Connect (BLE phase), then system Bluetooth settings (no Pixel Buds app at any point — uninstalled) | S | `PAIR-001`, `CASE-003` | `GFPS-001` — Google Play Services disabled + Pixel Buds app uninstalled, to isolate whether `CAP-002`'s Fast Pair Message Stream traffic is Buds-initiated or GMS-driven; bonus classic pairing captured (procedure deviated from Group S's system-settings-only description — nRF Connect was used first) | `captures/CAP-004-2026-08-11_06-22-36_06-25-12-Group_S/CAP-004-btsnoop_hci.log` (contains unrelated background Fitbit Charge 6 traffic, excluded — see `CAP-004-FINDINGS.md` §1) | same file | analyzed — see `CAP-004-FINDINGS.md` in that folder; **mixed `GFPS-001` result**: `CAP-002` §3's channel-2/DLCI-0x04 TLV content (Model ID, "Revision 6", etc.) is absent (channel 2 never opens) — GMS-and/or-app-dependent, unresolved which (GMS was disabled **and** the official app was uninstalled together in this session, a confound not yet isolated — see `CAP-004-FINDINGS.md` §4a); but `CAP-001`'s channel-4/DLCI-0x08 content (`google-pixel-buds-pro-v1`, `Europe/Amsterdam`) reappears unchanged — not GMS-dependent (this one isn't confounded, since it's a presence-despite-both-removed result, not an absence); classic bonding used Cross-Transport Key Derivation (LE Secure Connections → classic key), not classic SSP as in `CAP-002`/`CAP-003`; new, unidentified Message Stream Groups `0x04`/`0x05`/`0x09` found; nRF Connect's cached GATT service list gives named candidate services (Google Fast Pair Service `0xFE2C`, Accessory Non-Owner Service, Device Information) for the still-open `0x0f2a`/`0x0c0X` handle questions | +| `CAP-002` | 2026-08-09 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | A | `PAIR-001`, `CASE-003`, `GFPS-002`, `APP-001`, `APP-002`, incidental `BATT-004` | Fresh pairing/bonding baseline (deleted stored link key first) through the Pixel Buds app's first-run setup flow (Fast Pair save-to-account, CDM permission, Device details load) | `captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-btsnoop_hci.log` (sliced from a shared, non-restarted ~8h20m snoop log — see that folder's `CAP-002-EVENT-NOTES.md` process note) | same file | analyzed — see `CAP-002-FINDINGS.md` in that folder; Fast Pair Message Stream Device Information group spec-verified (🟢, channel 2/DLCI 0x04); no RFCOMM traffic found during app setup/Device-details load; HFP handshake present (frames 48954–49079; the earlier "no AT-command traffic" was a filter artefact, corrected 2026-10-03) (contrast with `CAP-001`) | +| `CAP-003` | 2026-08-10 | Pixel 7a | 17 | release_5.203 | nRF Connect (generic BLE tool), then official app v1.0.955078536 (took over partway) | R | `PAIR-001`, `CASE-003`, `APP-001`, `APP-002`, `GATT-001` (attempted, no discovery) | Forced GATT rediscovery attempt (pairing removed via system settings, connected via nRF Connect instead of the official app) to resolve `CAP-002`'s open `0x0f2a`/`0x0c0X` handle UUIDs; classic pairing captured as a bonus data point | `captures/CAP-003-2026-08-10_20-59-16_21-00-37-Group_R/CAP-003-btsnoop_hci.log` (short, freshly-restarted log, no slicing needed) | same file | analyzed — see `CAP-003-FINDINGS.md` in that folder; **primary goal not achieved** — Android's GATT database cache survived the pairing removal, so zero `Read By Group Type`/characteristic-discovery traffic occurred; `0x0f2a`/`0x0c0X` UUIDs still unresolved; new handle `0x0f28` found (polled every ~60s, value `0x31`); reinforces that RFCOMM channel numbers are session-local while GATT handles are stable across sessions | +| `CAP-004` | 2026-08-11 | Pixel 7a | 17 | release_5.203 | nRF Connect (BLE phase), then system Bluetooth settings (no Pixel Buds app at any point — uninstalled) | S | `GFPS-001`, `PAIR-001`, `CASE-003` | `GFPS-001` — Google Play Services disabled + Pixel Buds app uninstalled, to isolate whether `CAP-002`'s Fast Pair Message Stream traffic is Buds-initiated or GMS-driven; bonus classic pairing captured (procedure deviated from Group S's system-settings-only description — nRF Connect was used first) | `captures/CAP-004-2026-08-11_06-22-36_06-25-12-Group_S/CAP-004-btsnoop_hci.log` (contains unrelated background Fitbit Charge 6 traffic, excluded — see `CAP-004-FINDINGS.md` §1) | same file | analyzed — see `CAP-004-FINDINGS.md` in that folder; **mixed `GFPS-001` result**: `CAP-002` §3's channel-2/DLCI-0x04 TLV content (Model ID, "Revision 6", etc.) is absent (channel 2 never opens) — GMS-and/or-app-dependent, unresolved which (GMS was disabled **and** the official app was uninstalled together in this session, a confound not yet isolated — see `CAP-004-FINDINGS.md` §4a); but `CAP-001`'s channel-4/DLCI-0x08 content (`google-pixel-buds-pro-v1`, `Europe/Amsterdam`) reappears unchanged — not GMS-dependent (this one isn't confounded, since it's a presence-despite-both-removed result, not an absence); classic bonding used Cross-Transport Key Derivation (LE Secure Connections → classic key), not classic SSP as in `CAP-002`/`CAP-003`; new, unidentified Message Stream Groups `0x04`/`0x05`/`0x09` found; nRF Connect's cached GATT service list gives named candidate services (Google Fast Pair Service `0xFE2C`, Accessory Non-Owner Service, Device Information) for the still-open `0x0f2a`/`0x0c0X` handle questions | | `CAP-005` | 2026-08-15 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | T | `EQP-002`, `EQS-004` | **Highest priority.** EQ command isolation — single isolated EQ preset change ("Heavy bass"), then a second isolated EQ Bass-slider drag + Save, to find EQ's command channel now that it's known not to share ANC's (`PROTOCOL.md` §4.1/§2.3) | `captures/CAP-005-2026-08-15_15-02-31_15-03-45-Group_T/CAP-005-btsnoop_hci.log` | same file | analyzed — see `CAP-005-FINDINGS.md` in that folder; **goal achieved (🟡 HYPOTHESIS level):** both actions isolate cleanly to DLCI 0x02 (`libmaestro`'s Pigweed `pw_hdlc` channel, `PROTOCOL.md` §2.2a) only — DLCI 0x04/0x08 silent in both windows; a nested, protobuf-tag-consistent envelope decodes down to a 5×`float32` band-gain quintet, with only the Bass field changing (+3.0 on preset, then −4.1 on the drag); the slider drag additionally revealed a second, later "Save"-tap wire burst (outer field 18) distinct from the live-drag burst (outer field 16), not anticipated by this capture's own event notes — first EQ-attributable content decoded on any channel, still 🟡 not 🟢 (single capture, field-to-band mapping inferred from one changed field) | -| `CAP-006` | 2026-08-15 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | B (repeat) | `ANC-001`, `ANC-002`, `ANC-003`, `ANC-004` | Clean, single-tap-per-window repeat of Group B to resolve `CAP-001`'s first-two-taps-have-no-`0x12`-frame open sub-question and re-confirm the ANC bit-mapping before implementation | `captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-btsnoop_hci.log` | same file | analyzed — see `CAP-006-FINDINGS.md` in that folder; **goal achieved, ADR-009 blocker resolved:** all 4 isolated single taps produced a matching `0x12` "Set ANC state" frame (exactly 4 `0x12` frames in the whole 233s log, one per tap, zero extras/misses), each within ~1.3s of its video-observed tap; bit-mapping re-confirmed byte-for-byte against `PROTOCOL.md` §4.1 (`0x08`=ANC, `0x20`=Off, `0x40`=Adaptive, `0x80`=Transparency); `CAP-001`'s 2/6 miss rate does not reproduce under isolated single-tap conditions — `FrameEncoder` implementation for this command proposed as unblocked, done — `DECISIONS.md` ADR-009 Update (2026-08-15) | -| `CAP-007` | 2026-08-16 | Pixel 7a | 17 (⚪ assumed, not visible on screen this capture) | release_5.203 | n/a (Android system Bluetooth "Device details" page, not the companion app) | U | `INEAR-004`(partial), `CASE-004`, `OBS-003` | DLCI 0x08 Group `0x04` Code `0x12` liveness/event bracket — bud-removed-from-case, case-closed-while-active, and multi-minute idle brackets, to test whether the alternating value is event-driven or a free-running counter (`CAP-004-FINDINGS.md` §5a Task 5) | `captures/CAP-007-2026-08-16_09-14-10_09-17-57-Group_U/CAP-007-btsnoop_hci.log` | same file | analyzed — see `CAP-007-FINDINGS.md` in that folder; **Group U's own question answered (🟢 FACT): Code `0x12` is Buds-initiated (Rcvd-only), fires on DLCI 0x08 channel-(re)open and also autonomously after a ~184s idle gap with no channel churn — neither purely reactive nor purely free-running**; value cycles `0x02`/`0x03`/`0x04` (corrects `CAP-004`'s "2↔3" characterization); **original in-ear-mapping goal not achieved** — no earbud was ever inserted into an ear in this session (only removed from the open case), and that one physical event produced no value change on any DLCI (clean negative result); case-lid-close bracket also produced zero wire traffic (clean negative result) | -| `CAP-008` | 2026-08-26 | Pixel 7a | 17 (⚪ assumed) | release_5.203 | 1.0.955078536 (⚪ assumed, not shown on screen this session) | V | `CALL-001` | First capture of an actual phone call — resolves whether HFP AT-command SLC setup reoccurs (`CAP-002-FINDINGS.md` §5) and whether channel 5/DLCI 0x0a carries any payload or SCO/eSCO HCI event (`CAP-001-FINDINGS.md` §6 Task 6) | `captures/CAP-008-2026-08-26_09-38-44_09-41-36-Group_V/CAP-008-btsnoop_hci.log` | same file | analyzed — see `CAP-008-FINDINGS.md` in that folder; **both open questions resolved**: the full HFP SLC handshake reoccurs on a fresh classic-link connection (frames 776–1132, on RFCOMM channel 6/DLCI 0x0c this session — a third distinct channel-number assignment for the same profile, reinforcing `CAP-001`'s "channel numbers are session-local" note); two clean `Enhanced Setup Synchronous Connection`/`Synchronous Connection Complete` (mSBC, eSCO) pairs appear, one per call — the first SCO/eSCO HCI events in this project, closing `CAP-001-FINDINGS.md` §6 Task 6. `CALL-001` itself is now sub-second wire/video-correlated for both call start and end, across two independent calls. DLCI 0x0a stays silent through both calls (14th consecutive negative result) but is now specifically ruled out as the call's audio path. **Bonus findings:** AVDTP Suspend/Start (A2DP) brackets each call almost exactly; `AT+BIEV` HF Indicator #2 tracks the **Left** earbud this session (vs. Right in `CAP-009`) — refines the open "fixed-Right vs. HFP-primary" question; one isolated unsolicited `+CIEV:6,...` (`battchg`) push, a new 🔴 open question | +| `CAP-006` | 2026-08-15 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | B (repeat) | `ANC-001`, `ANC-002`, `ANC-003`, `ANC-004`, incidental `PAIR-003` (first Pixel 7a occurrence) | Clean, single-tap-per-window repeat of Group B to resolve `CAP-001`'s first-two-taps-have-no-`0x12`-frame open sub-question and re-confirm the ANC bit-mapping before implementation | `captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-btsnoop_hci.log` | same file | analyzed — see `CAP-006-FINDINGS.md` in that folder; **goal achieved, ADR-009 blocker resolved:** all 4 isolated single taps produced a matching `0x12` "Set ANC state" frame (exactly 4 `0x12` frames in the whole 233s log, one per tap, zero extras/misses), each within ~1.3s of its video-observed tap; bit-mapping re-confirmed byte-for-byte against `PROTOCOL.md` §4.1 (`0x08`=ANC, `0x20`=Off, `0x40`=Adaptive, `0x80`=Transparency); `CAP-001`'s 2/6 miss rate does not reproduce under isolated single-tap conditions — `FrameEncoder` implementation for this command proposed as unblocked, done — `DECISIONS.md` ADR-009 Update (2026-08-15) | +| `CAP-007` | 2026-08-16 | Pixel 7a | 17 (⚪ assumed, not visible on screen this capture) | release_5.203 | n/a (Android system Bluetooth "Device details" page, not the companion app) | U | `INEAR-004`(partial), `CASE-004`, `OBS-003`, incidental `PAIR-003` | DLCI 0x08 Group `0x04` Code `0x12` liveness/event bracket — bud-removed-from-case, case-closed-while-active, and multi-minute idle brackets, to test whether the alternating value is event-driven or a free-running counter (`CAP-004-FINDINGS.md` §5a Task 5) | `captures/CAP-007-2026-08-16_09-14-10_09-17-57-Group_U/CAP-007-btsnoop_hci.log` | same file | analyzed — see `CAP-007-FINDINGS.md` in that folder; **Group U's own question answered (🟢 FACT): Code `0x12` is Buds-initiated (Rcvd-only), fires on DLCI 0x08 channel-(re)open and also autonomously after ~184s without Code `0x12` (DLCI 0x08's periodic `0e`/`04 03` pushes continued) and with no channel churn — neither purely reactive nor purely free-running**; value cycles `0x02`/`0x03`/`0x04` (corrects `CAP-004`'s "2↔3" characterization); **original in-ear-mapping goal not achieved** — no earbud was ever inserted into an ear in this session (only removed from the open case), and that one physical event produced no value change on any DLCI (clean negative result); case-lid-close bracket also produced zero wire traffic (clean negative result) | +| `CAP-008` | 2026-08-26 | Pixel 7a | 17 (⚪ assumed) | release_5.203 | 1.0.955078536 (⚪ assumed, not shown on screen this session) | V | `CALL-001` | First capture of an actual phone call — resolves whether HFP AT-command SLC setup reoccurs (`CAP-002-FINDINGS.md` §5) and whether channel 5/DLCI 0x0a carries any payload or SCO/eSCO HCI event (`CAP-001-FINDINGS.md` §6 Task 6) | `captures/CAP-008-2026-08-26_09-38-44_09-41-36-Group_V/CAP-008-btsnoop_hci.log` | same file | analyzed — see `CAP-008-FINDINGS.md` in that folder; **both open questions resolved**: the full HFP SLC handshake reoccurs on a fresh classic-link connection (frames 776–1132, on RFCOMM channel 6/DLCI 0x0c this session — as in `CAP-002`, while `CAP-001`/`CAP-003` used channel 4, reinforcing `CAP-001`'s "channel numbers are session-local" note); two clean `Enhanced Setup Synchronous Connection`/`Synchronous Connection Complete` (mSBC, eSCO) pairs appear, one per call — the first SCO/eSCO HCI events in this project, closing `CAP-001-FINDINGS.md` §6 Task 6. `CALL-001` itself is now sub-second wire/video-correlated for both call start and end, across two independent calls. DLCI 0x0a stays silent through both calls (14th consecutive negative result) but is now specifically ruled out as the call's audio path. **Bonus findings:** AVDTP Suspend/Start (A2DP) brackets each call almost exactly; `AT+BIEV` HF Indicator #2 tracks the **Left** earbud this session (vs. Right in `CAP-009`) — refines the open "fixed-Right vs. HFP-primary" question; one isolated unsolicited `+CIEV:6,...` (`battchg`) push, a new 🔴 open question | | `CAP-009` | 2026-08-23 | Pixel 7a (⚪ assumed) | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | n/a (system Bluetooth only, no companion app screen this session) | X | `BATT-006` | Battery-level discrepancy bracket — cross-check `AT+CIND`/`battchg` against `AT+BIEV` over a natural battery decline (`CAP-001-FINDINGS.md` §3); no phone call occurred, so not combined with `CAP-008` | `captures/CAP-009-2026-08-23_18-33-52_20-12-55-Group_X/CAP-009-btsnoop_hci.log` | same file | analyzed — see `CAP-009-FINDINGS.md` in that folder; **`BATT-006` resolved**: `AT+CIND?`'s `battchg` is a single non-repeating snapshot at SLC setup (never a live reading); `AT+BIEV=2` pushes at an irregular (not fixed ~6–7s) cadence and tracks the Right earbud's percentage specifically, not Left/Case/an aggregate, across 86 minutes; HFP/RFCOMM never reopens after the 20:02:27 case+USB reconnect (A2DP-only from then on). **Bonus:** 75-occurrence, 101-minute confirmation of the already-FACT `PROTOCOL.md` §4.3 Option E (DLCI 0x08 `Group 0x0e Code 0x01`, `DECISIONS.md` ADR-014) — first session to capture a live charge cycle (Left 93%→100%) and the case's empty→populated transition. **Re-analyzed 2026-08-2x** (independent video timeline, full wire re-run): all 7 maintainer-noted transitions confirmed (2 refined by minutes, periodic-manual-check lag not an error); corrected an assumed ~50s video-coverage gap to the actual ~2s; found a 🟡 HYPOTHESIS candidate for `PROTOCOL.md` §4.3 Option B's open battery code (DLCI 0x04 `Group 0x03 Code 0x03`) and a 🟡 HYPOTHESIS BLE Fast Pair scan explanation for the post-reconnect on-screen updates. All 5 proposals from this pass maintainer-approved (`DECISIONS.md` ADR-015) | -| `CAP-010` | 2026-08-16 | Pixel 7a | 17 | release_5.203 | official Pixel Buds Companion App (version not visible on screen) | W (attempted) | `PAIR-001` | Intended as Group W's stronger GATT cache-busting attempt; **actual on-screen procedure was a standard system-Settings forget-and-re-pair on the same Pixel 7a used throughout this project — neither `pm clear com.android.bluetooth` nor the Pixel 9a was used, so Group W's own method was not actually exercised (`CAP-010-FINDINGS.md` §1)** | `captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/CAP-010-btsnoop_hci.log` | same file | analyzed — see `CAP-010-FINDINGS.md` in that folder; **`GATT-001` still unresolved, 4th consecutive negative result** (`CAP-002`, `CAP-003`, `CAP-004`, `CAP-010`): zero `Read By Group Type`/`Find Information` traffic despite a genuinely fresh classic bond this time — explained by the procedure gap above, not new evidence against Group W's untried methods; independently reproduces the `0x0f2a`/`0x0c0X` handle cluster's stable numbering and FORM (now a 3rd–4th confirming session), the DLCI 0x08 private handshake incl. `release_5.203` (5th confirming session), and the classic fresh-pairing state machine (4th confirming session); new byte-level detail for `0x0c0c` (40B notify) and `0x0c13`/`0x0c14` (9/10/32B, doesn't fit the `0x0c04`/`0x0c05` AES-block pattern — 🟡 possibly a structurally distinct characteristic) | +| `CAP-010` | 2026-08-16 | Pixel 7a | 17 | release_5.203 | official Pixel Buds Companion App (version not visible on screen) | W (attempted) | `PAIR-001`, `CASE-003`, `GATT-001` (attempted, no discovery) | Intended as Group W's stronger GATT cache-busting attempt; **actual on-screen procedure was a standard system-Settings forget-and-re-pair on the same Pixel 7a used throughout this project — neither `pm clear com.android.bluetooth` nor the Pixel 9a was used, so Group W's own method was not actually exercised (`CAP-010-FINDINGS.md` §1)** | `captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/CAP-010-btsnoop_hci.log` | same file | analyzed — see `CAP-010-FINDINGS.md` in that folder; **`GATT-001` still unresolved, 4th consecutive negative result** (`CAP-002`, `CAP-003`, `CAP-004`, `CAP-010`): zero `Read By Group Type`/`Find Information` traffic despite a genuinely fresh classic bond this time — explained by the procedure gap above, not new evidence against Group W's untried methods; independently reproduces the `0x0f2a`/`0x0c0X` handle cluster's stable numbering and FORM (now a 3rd–4th confirming session), the DLCI 0x08 private handshake incl. `release_5.203` (5th confirming session), and the classic fresh-pairing state machine (4th confirming session); new byte-level detail for `0x0c0c` (40B notify) and `0x0c13`/`0x0c14` (9/10/32B, doesn't fit the `0x0c04`/`0x0c05` AES-block pattern — 🟡 possibly a structurally distinct characteristic) | | `CAP-011` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | official Pixel Buds Companion App (open for most of session) | Q (#18) | `BATT-002`, `BATT-003` | Passive BLE scan intended for case-closed/idle, to capture the Fast Pair Battery Notification advertisement independently of RFCOMM (`PROTOCOL.md` §4.3 Option A) | `captures/CAP-011-2026-08-21_09-45-17_09-55-16-Group_Q/CAP-011-btsnoop_hci.log` | same file | analyzed — see `CAP-011-FINDINGS.md` in that folder; **procedure deviation**: an active classic RFCOMM+GATT connection was present throughout (app left open on "Device details"), not the intended connection-free scan; `0xFE2C` Fast Pair Service BLE advertisements confirmed present (🟢 FACT, 634 frames, 5 rotating addresses) but the sampled payloads do **not** structurally match `PROTOCOL.md` §4.3 Option A's documented Battery Notification byte layout — recorded as inconclusive, not force-fit; the clean connection-free repeat was run as `CAP-043` (a second non-match). **Re-analyzed 2026-08-23** (maintainer spotted a 1% battery drop in the recording): pinpointed the exact change to 09:52:25.8 and found a DLCI 0x08 message (`Group 0x0e Code 0x01`, cross-confirmed by `Group 0x04 Code 0x03`) tracking the Left/Right values across 4 occurrences in the log — proposed as `PROTOCOL.md` §4.3 Option E, 🟡 HYPOTHESIS pending sign-off; see `CAP-011-FINDINGS.md` §7 | -| `CAP-012` | 2026-08-26 | Pixel 7a | 17 (⚪ assumed, not visible on screen this capture) | release_5.203 (⚪ assumed — not re-confirmed on the wire this session, log severely ACL-truncated) | uninstalled (GMS disabled) | S (repeat) | `GFPS-001`, `PAIR-001`, `CASE-003`, bonus `PAIR-003` (first Pixel 7a occurrence) | Repeat of Group S following its original system-settings-only procedure exactly (no nRF Connect at any point, independently confirmed both on screen and on the wire — zero BLE connection to the Buds anywhere in the log), to isolate whether `CAP-004`'s Cross-Transport-Key-Derivation bonding result was an artifact of nRF Connect's early BLE connection (`CAP-004-FINDINGS.md` §8 item 4) | `captures/CAP-012-2026-08-26_15-30-19_15-32-28-Group_S/CAP-012-btsnooz_hci.log` (**severely ACL-truncated, ~15-byte captured length per data packet — `btsnooz`-fallback extraction rather than a raw untruncated log; see `CAP-012-FINDINGS.md` §1**) | same file | analyzed — see `CAP-012-FINDINGS.md` in that folder; **hypothesis confirmed (🟢 FACT): this session uses classic Secure Simple Pairing, not CTKD** — `CAP-004`'s CTKD result is isolated to nRF Connect's early BLE connection, not to the GMS-disabled/no-app condition itself (§2/§10 there); `GFPS-001`'s channel-topology result reproduces `CAP-004`'s (DLCI 0x04 never opens) but the payload-content sub-question is **inconclusive**, not resolved, due to this log's truncation (§1/§4); bonus finding: a manual disconnect/reconnect (`PAIR-003`) retriggers the full HFP AT-command handshake (§6), narrowing a `PROTOCOL.md` §6 open question | +| `CAP-012` | 2026-08-26 | Pixel 7a | 17 (⚪ assumed, not visible on screen this capture) | release_5.203 (⚪ assumed — not re-confirmed on the wire this session, log severely ACL-truncated) | uninstalled (GMS disabled) | S (repeat) | `GFPS-001`, `PAIR-001`, `CASE-003`, bonus `PAIR-003` | Repeat of Group S following its original system-settings-only procedure exactly (no nRF Connect at any point, independently confirmed both on screen and on the wire — zero BLE connection to the Buds anywhere in the log), to isolate whether `CAP-004`'s Cross-Transport-Key-Derivation bonding result was an artifact of nRF Connect's early BLE connection (`CAP-004-FINDINGS.md` §8 item 4) | `captures/CAP-012-2026-08-26_15-30-19_15-32-28-Group_S/CAP-012-btsnooz_hci.log` (**severely ACL-truncated, ~15-byte captured length per data packet — `btsnooz`-fallback extraction rather than a raw untruncated log; see `CAP-012-FINDINGS.md` §1**) | same file | analyzed — see `CAP-012-FINDINGS.md` in that folder; **hypothesis confirmed (🟢 FACT): this session uses classic Secure Simple Pairing, not CTKD** — `CAP-004`'s CTKD result is isolated to nRF Connect's early BLE connection, not to the GMS-disabled/no-app condition itself (§2/§10 there); `GFPS-001`'s channel-topology result reproduces `CAP-004`'s (DLCI 0x04 never opens) but the payload-content sub-question is **inconclusive**, not resolved, due to this log's truncation (§1/§4); bonus finding: a manual disconnect/reconnect (`PAIR-003`) retriggers the full HFP AT-command handshake (§6), narrowing a `PROTOCOL.md` §6 open question | | `CAP-013` | 2026-08-26 | Pixel 7a | 17 (⚪ assumed, not screen-confirmed) | release_5.203 | official Pixel Buds Companion App (version not visible on screen) | A (repeat) | `PAIR-001`, `PAIR-004`, incidental `BATT-004`, `APP-001`, `APP-002` | Intended: HCI snoop logging started before any prior association with the device exists — resolves whether a clearing action fully clears prior bonding/BLE-association state (`CAP-001-FINDINGS.md` §6). **Approved as recorded (maintainer, chat 2026-09-30, `ai-sessions/0059`).** | `captures/CAP-013-2026-08-26_17-09-01_17-14-04-Group_A/CAP-013-btsnooz_hci.log` (**severely ACL-truncated, ~15-byte captured length per data packet — `btsnooz`-fallback extraction, same issue as `CAP-012`; see `CAP-013-FINDINGS.md` §1**) | same file | analyzed — **partial: pre-clearing-action window not captured** — see `CAP-013-FINDINGS.md`/`CAP-013-EVENT-NOTES.md` in that folder. Verified (not assumed) that the intended method wasn't actually followed: the on-screen clearing action was "Reset Bluetooth & Wi-Fi" (not a single-device "Forget"), and the log's first frame (17:11:45.8) starts **2m21s after** that action — also after Bluetooth re-enable, case-open, pair-button-press, and device-list-tap, none of which are logged. **Primary question (`CAP-001-FINDINGS.md` §6) remains 🔴 OPEN, not answered.** **Secondary question (`PAIR-004`) is answered: 🟢 CONFIRMED fresh classic SSP handshake** (full IO-Capability/User-Confirmation/new-Link-Key-Notification sequence, frames 117–270) for the bonding state active when this capture's window began — no key-reuse path observed. Bonus: DLCI 0x02 (`libmaestro` candidate) opens ~61s after the other 4 channels this session, coinciding with an app-permission "Allow" tap — 🟡 single-sample, not promoted; a second BLE link to an unrelated random address appears mid-session, unattributed (🔴 OPEN). A genuine repeat (logging before the clearing action itself) is still needed — see proposed follow-up capture ID below. | | `CAP-014` | 2026-08-27 | Pixel 7a | 17 (⚪ assumed, not re-confirmed on screen this session) | release_5.203 (confirmed on-screen 20:59:07) | nRF Connect for Mobile (Nordic Semiconductor); official Pixel Buds app also surfaced twice, 20:54:39 and 20:58:52–53, unintentionally | W (repeat — **superseded by `CAP-034`'s 🟢 handle↔UUID mapping (marker resolved 2026-09-30, maintainer, chat, `ai-sessions/0059`):** neither of Group W's own candidate methods was actually used this session either, see deviation note below) | `GATT-001`, incidental `PAIR-001` | Intended: repeat the `CAP-017` nRF-Connect procedure with (1) a fixed/longer HCI snoop snaplen and (2) an on-screen drill-down into "Accessory Non-Owner Service"/"Unknown Service" — **(2)'s Accessory-Non-Owner half was correctly not executed, that instruction conflicted with `DECISIONS.md` ADR-008** (`CAP-014-FINDINGS.md` §2) | `captures/CAP-014-2026-08-27_20-53-37_20-59-17-Group_W/CAP-014-btsnoop_hci.log` | same file | analyzed — see `CAP-014-FINDINGS.md` in that folder; **superseded by `CAP-034`'s 🟢 handle↔UUID mapping (marker resolved 2026-09-30, maintainer, chat, `ai-sessions/0059`):** (1) succeeded — snaplen confirmed fixed, 0 truncated frames out of 4,663 (§0); **but `GATT-001`'s handle↔UUID mapping is still 🔴 OPEN** — root cause now identified precisely (§4): this session reused the same, already-bonded Pixel 7a with a cached GATT client, so Android served the `0x0c0X`/`0x0f2X` cluster from its cached database instead of re-declaring it on the wire (only the GATT service itself, handles `0x0001`–`0x0009`, was genuinely re-discovered live). Neither Group W Option (a) `pm clear com.android.bluetooth` nor Option (b) (Pixel 9a) was actually tried — both remain the clearly-identified next step, now combined with a confirmed-working snaplen for the first time. Reproduces `0x0f2a`="Revision 6" (3rd session, byte-identical to `CAP-002`) and `0x0f32`=`0x64`/`0x0f33` CCCD (2nd session, byte-identical to `CAP-017`, narrowing that open question); bonding used Cross-Transport Key Derivation (2nd confirmed instance after `CAP-004`, consistent with `CAP-012`'s "BLE-tool-first" explanation) | -| `CAP-015` | 2026-08-18 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | T | `EQP-002`, `EQS-004` | Completes/supersedes `CAP-005`'s Group T goal: five distinct EQ presets tapped in sequence, then all five EQ sliders each dragged to both extremes and back near-zero (3 passes), to resolve `CAP-005`'s open field-to-band mapping question | `captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-btsnoop_hci.log` | same file | analyzed — see `CAP-015-FINDINGS.md` in that folder; **field-to-band mapping promoted to 🟢 FACT** (field 1↔Low bass, 2↔Bass, 3↔Mid, 4↔Treble, 5↔Upper treble, wire order reversed from on-screen order) — matches `CAP-005`'s single-band inference exactly; also established the ±6.0 band-gain clamp (🟢 FACT, units unconfirmed) and a confirmed preset-quintet reference table | +| `CAP-015` | 2026-08-18 | Pixel 7a | 17 | release_5.203 | 1.0.955078536 | T | `EQP-002`–`EQP-007`, `EQS-001`–`EQS-005` | Completes/supersedes `CAP-005`'s Group T goal: five distinct EQ presets tapped in sequence, then all five EQ sliders each dragged to both extremes and back near-zero (3 passes), to resolve `CAP-005`'s open field-to-band mapping question | `captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-btsnoop_hci.log` | same file | analyzed — see `CAP-015-FINDINGS.md` in that folder; **field-to-band mapping promoted to 🟢 FACT** (field 1↔Low bass, 2↔Bass, 3↔Mid, 4↔Treble, 5↔Upper treble, wire order reversed from on-screen order) — matches `CAP-005`'s single-band inference exactly; also established the ±6.0 band-gain clamp (🟢 FACT, units unconfirmed) and a confirmed preset-quintet reference table | | `CAP-016` | 2026-08-18 | Pixel 7a | 17 (build `CP2A.260705.006`, confirmed on-screen) | release_5.203 | n/a (Android system Bluetooth "Device details" page, not the companion app) | U | `CASE-004`, `CASE-005`, `CASE-006`, `OBS-003` | Re-run of Group U (distinct session from `CAP-007`): Bluetooth-on, both buds removed from case one at a time, empty-case lid close/reopen, both buds re-docked, disconnect, lid close — general case/bud-removal correlation rather than Group U's own narrower liveness-bracket procedure (see `CAP-016-FINDINGS.md` scope note) | `captures/CAP-016-2026-08-18_06-31-31_06-33-58-Group_U/CAP-016-btsnoop_hci.log` | same file | analyzed — see `CAP-016-FINDINGS.md` in that folder; **clean single-attempt, Buds-initiated reconnect on first bud removal (🟢 FACT, contrast `CAP-001`'s 3-attempt phone-initiated connect)**; **ACL disconnects the instant both buds are re-docked, Buds-initiated (reason `0x13`), 🟢 FACT**; **case-lid open/close while buds are elsewhere produces zero wire signal, 🟢 FACT — 2nd independent capture confirming this (`CAP-007` §3.4)**; a full RFCOMM channel bounce (all 4 DLCIs) recurs with **no camera-visible trigger this time**, ruling out "bud removal is the sole cause" for that class of event (`CAP-007` §3.3's hypothesis); ANC-state Notify's "settable-toggles" byte (`0x00` vs `0xe8`) newly observed to change value, tracking the app's own "no ANC mode selectable" UI state — proposed refinement to `PROTOCOL.md`'s ANC Notify field table, not yet promoted; **`INEAR-002`/`INEAR-003`/`INEAR-004` still not exercised** — no earbud is shown inserted into or removed from an ear on camera in this session either | | `CAP-017` | 2026-08-16 | Pixel 7a | 17 | release_5.203 (⚪ assumed — not re-confirmed on-the-wire this session: DLCI 0x08 opened, 71 frames, but the log is capped at 15 bytes per packet) | nRF Connect for Mobile (Nordic Semiconductor), not the official app | W | `GATT-001` | Second same-day Group W attempt (distinct session from `CAP-010`'s 11:42 attempt), this time driving a brand-new third-party GATT client (nRF Connect) instead of the official app, to force a real cache-miss discovery walk | `captures/CAP-017-2026-08-16_18-30-12_18-37-12-Group_W/CAP-017-btsnoop_hci.log` | same file | analyzed — see `CAP-017-FINDINGS.md` in that folder; **`GATT-001`'s discovery goal achieved for the first time in this project (🟢 FACT)** via a third path not previously identified (fresh GATT client UID has no cache to hit) — 137 live `Read By Type`/`Read By Group Type`/`Find Information` frames on the wire (vs. zero in every prior attempt); full 15-service GATT profile recovered from video (incl. Google Fast Pair Service `0xFE2C` and two previously-undocumented 128-bit UUIDs); **but the wire log itself is severely ACL-truncated (~15B snaplen), so UUID bytes aren't recoverable from the log, and no characteristic-level drill-down happened on screen — confirmed by a dedicated full-video verification pass (§4b) that the handle↔UUID mapping cannot be closed from this capture's artifacts**; recapture needed with (1) fixed snaplen and (2) on-screen characteristic drill-down into "Unknown Service" (`109b862f-…`) — **"Accessory Non-Owner Service" is out of scope for any drill-down/read, per `DECISIONS.md` ADR-008; do not include it in a future session's plan** (correction added post-`CAP-014`, which caught this exact instruction still standing here and had to decline it — see `CAP-014-FINDINGS.md` §2). `CAP-014`'s repeat (below) fixed the snaplen but did not achieve a genuine cache-miss discovery, so the mapping is still open — see that row | | `CAP-018` | 2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed, carried over) | 1.0.955078536 | Y | `GATT-002` | BLE-only connection isolation for the `0x0044` notification burst (`CAP-016-FINDINGS.md` §11) — enable Bluetooth and let the BLE link to the already-paired Buds form without touching the buds/case at all, to isolate whether the burst is triggered by BLE link establishment alone, independent of any bud/case action | `captures/CAP-018-2026-09-12_06-07-20_06-09-52-Group_Y/CAP-018-btsnoop_hci.log` | same file | analyzed — see `CAP-018-FINDINGS.md` in that folder; **isolation confirmed (buds/case untouched for the full 152s video), but the burst itself is not attributable to the Buds this session**: the only LE connection in the whole log (chandle `0x0004`, address distinct from the Buds' classic address) carries a GATT profile matching an unrelated nearby device (a standard Heart Rate service present, no Fast Pair Service/no Buds-known handle cluster) — the burst rides handle `0x0044`, inside that connection's own `0x0040`–`0x0045` "Unknown Service" range, structurally the same unattributed-background-device pattern already seen in `CAP-032-FINDINGS.md` §5. The Buds' own classic connection carries zero ATT traffic throughout. Bonus: this session's classic reconnect took an unusually long ~64s from Bluetooth-toggle to `Create Connection`, unexplained | -| `CAP-019` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed same as prior sessions) | C | `CONV-001`, `MULTI-001` | Conversation Detection and Multipoint on/off toggles | `captures/CAP-019-2026-08-21_07-35-50_07-39-30-Group_C/CAP-019-btsnoop_hci.log` | same file | analyzed — see `CAP-019-FINDINGS.md` in that folder; both toggles isolate to a single DLCI 0x02 `Sent` frame each (🟡 HYPOTHESIS, `field5{field4{field22=1}}`/`field5{field4{field11=1}}`); Multipoint additionally triggers a DLCI 0x04 Group `0x07` (SASS) burst including an ASCII `"in-use"` string — first content-level correlation of SASS traffic to a specific action | -| `CAP-020` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed) | F | `TOUCH-001`, `HEAD-001` | Touch controls and Head gestures top-level toggles, each OFF→ON→OFF (head gestures OFF: frame 2038; it was on again in `CAP-028`, read `29:2`) | `captures/CAP-020-2026-08-21_07-46-14_07-47-49-Group_F/CAP-020-btsnoop_hci.log` | same file | analyzed — see `CAP-020-FINDINGS.md` in that folder; both toggles isolate to a single DLCI 0x02 `Sent` frame each (🟡 HYPOTHESIS, `field5{field4{field4=1}}`/`field5{field4{field29=2}}`) — first identification of the general-purpose `field5{field4{...}}` settings-write envelope, reused by every other capture in this batch | -| `CAP-021` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed) | G | `HOLD-001`–`HOLD-005` | Per-earbud press-and-hold configuration and ANC-mode rotation list | `captures/CAP-021-2026-08-21_07-59-36_08-07-04-Group_G/CAP-021-btsnoop_hci.log` | same file | analyzed — see `CAP-021-FINDINGS.md` in that folder; all 4 Left/Right × ANC/Assistant combinations (`HOLD-001`–`HOLD-004`) isolate cleanly (🟡 HYPOTHESIS, `field5{field4{field7{field1\|2{field4=5\|6}}}}`, field1=Left/field2=Right, 5=ANC/6=Digital assistant); `HOLD-005`'s 16-frame checklist burst confirms field order matches on-screen order *(refuted 2026-09-28 by `CAP-056`: the order is 1 NC, 2 Off, 3 Transparency, 4 Adaptive — `PROTOCOL.md` §4.5.3)* but cannot be split between Left's/Right's lists from wire content alone | -| `CAP-022` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed) | H | `AUDIO-001`–`AUDIO-003` | Mono audio, Volume EQ, Volume balance | `captures/CAP-022-2026-08-21_08-15-24_08-17-27-Group_H/CAP-022-btsnoop_hci.log` | same file | analyzed — see `CAP-022-FINDINGS.md` in that folder; all 3 isolate to DLCI 0x02 writes (🟡 HYPOTHESIS: `field19`=Mono audio, `field15`=Volume EQ, `field17`=Volume balance, 7 samples from one continuous drag); Volume balance's persistent-write claim not tested (no disconnect/reconnect this session) | +| `CAP-019` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed same as prior sessions) | C | `CONV-001`, `MULTI-001` | Conversation Detection and Multipoint on/off toggles | `captures/CAP-019-2026-08-21_07-35-50_07-39-30-Group_C/CAP-019-btsnoop_hci.log` | same file | analyzed — see `CAP-019-FINDINGS.md` in that folder; each toggle isolates to one DLCI 0x02 write per tap — Conversation detection `qhr` field 22 (1720 OFF, 1808 ON, 🟢, `PROTOCOL.md` §4.5.1), Multipoint field 11 (2293 ON, 2482 OFF, 🟢 by ADR-019/025); each Multipoint write is followed by a DLCI 0x04 Group `0x07` (SASS) round (Code `0x21` only after the ON write; the `0x11`/`0x40`/`0x41` "in-use"/`0x42` codes also run at connect) | +| `CAP-020` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed) | F | `TOUCH-001`, `HEAD-001` | Touch controls and Head gestures top-level toggles, each OFF→ON→OFF (head gestures OFF: frame 2038; it was on again in `CAP-028`, read `29:2`) | `captures/CAP-020-2026-08-21_07-46-14_07-47-49-Group_F/CAP-020-btsnoop_hci.log` | same file | analyzed — see `CAP-020-FINDINGS.md` in that folder; each toggle isolates to one DLCI 0x02 write per tap — touch controls `qhr` field 4 (1741 ON, 1995 OFF, 🟢, ADR-019), head gestures field 29 (1935 `29:2` ON, 2038 `29:1` OFF, 🟡) — first identification of the general-purpose `field5{field4{...}}` settings-write envelope, reused by every other capture in this batch | +| `CAP-021` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed) | G | `HOLD-001`–`HOLD-005` | Per-earbud press-and-hold configuration and ANC-mode rotation list | `captures/CAP-021-2026-08-21_07-59-36_08-07-04-Group_G/CAP-021-btsnoop_hci.log` | same file | analyzed — see `CAP-021-FINDINGS.md` in that folder; all 4 Left/Right × ANC/Assistant combinations (`HOLD-001`–`HOLD-004`) isolate cleanly (🟢, `DECISIONS.md` ADR-019: `qhr` field 7, `field5{field4{field7{field1\|2{field4{field1=5\|6}}}}}`, field1=Left/field2=Right, 5=ANC/6=Digital assistant); `HOLD-005`'s 16-frame checklist burst confirms field order matches on-screen order *(refuted 2026-09-28 by `CAP-056`: the order is 1 NC, 2 Off, 3 Transparency, 4 Adaptive — `PROTOCOL.md` §4.5.3)* but cannot be split between Left's/Right's lists from wire content alone | +| `CAP-022` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed) | H | `AUDIO-001`–`AUDIO-003` | Mono audio, Volume EQ, Volume balance | `captures/CAP-022-2026-08-21_08-15-24_08-17-27-Group_H/CAP-022-btsnoop_hci.log` | same file | analyzed — see `CAP-022-FINDINGS.md` in that folder; all 3 isolate to DLCI 0x02 writes (🟢: `field19`=Mono audio, `field17`=Volume balance — ADR-019/026; `field15`=Volume EQ — ADR-025 Update 2026-09-08; 7 balance writes 6.6–8.8 s apart); Volume balance persists across a reconnect (🟢, this capture's 2099 → `CAP-023` 1047, `PROTOCOL.md` §4.5.7) | | `CAP-023` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | **release_5.203** | 1.0.955078536 (assumed) | I | `FW-001`, `FW-002` (`FW-003`/`FW-004` not exercised — gap) | Firmware/device-info screen; resolves `PROTOCOL.md` §0.1's open wire-baseline-vs-UI-baseline firmware-version question | `captures/CAP-023-2026-08-21_08-23-40_08-25-21-Group_I/CAP-023-btsnoop_hci.log` | same file | analyzed — see `CAP-023-FINDINGS.md` in that folder; **primary goal achieved**: on-screen firmware version (`release_5.203`, all 3 components) matches DLCI 0x08's private-envelope string byte-for-byte, same session — resolves which on-the-wire string the app calls "the firmware version"; `FW-001`'s manual check produces zero wire traffic (🟢 FACT, cached not live-queried); `FW-003`/`FW-004` not visited this session | -| `CAP-024` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (confirmed same day, `CAP-023`) | 1.0.955078536 (assumed) | J | `INEAR-001`, `CASE-001`, `CASE-002` | In-ear detection toggle and case-sound settings | `captures/CAP-024-2026-08-21_08-31-27_08-33-24-Group_J/CAP-024-btsnoop_hci.log` | same file | analyzed — see `CAP-024-FINDINGS.md` in that folder; all 3 isolate to DLCI 0x02 writes (🟡 HYPOTHESIS: `field2`=In-ear detection, `field28`="Bud return"/`CASE-001`, `field27`="Other alerts"/`CASE-002`); no case-specific vs. bud-specific channel distinction found — same shared envelope | -| `CAP-025` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed) | K | `FIND-001`, `FIND-002` (`FIND-003`/`FIND-004` attempted, resolve to a different mechanism) | Find My Buds/Ring, the last remaining fully unattributed app command; `PROTOCOL.md` §4.4's hypothesis tested | `captures/CAP-025-2026-08-21_08-40-52_08-45-26-Group_K/CAP-025-btsnoop_hci.log` | same file | analyzed — see `CAP-025-FINDINGS.md` in that folder; **`FIND-001`/`FIND-002` strongly confirmed, video-correlated**: DLCI 0x04 `Group 0x04 Code 0x01`, `Value`=`0x01`(start Right)/`0x02`(start Left)/`0x00`(stop) — matches the spec's worked ACK example exactly; **major structural finding**: `FIND-003`(Case)/`FIND-004`(both) route through a separate Find Hub/Find-My-Device-Network UI flow that produces **no** local Ring command on the wire — a different, likely GMS/account-mediated mechanism, not a capture gap | +| `CAP-024` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (confirmed same day, `CAP-023`) | 1.0.955078536 (assumed) | J | `INEAR-001`, `CASE-001`, `CASE-002` | In-ear detection toggle and case-sound settings | `captures/CAP-024-2026-08-21_08-31-27_08-33-24-Group_J/CAP-024-btsnoop_hci.log` | same file | analyzed — see `CAP-024-FINDINGS.md` in that folder; all 3 isolate to DLCI 0x02 writes (🟢: `field2`=In-ear detection, ADR-019 Update 2026-09-28; `field28`="Bud return"/`CASE-001`; `field27` case-sound category/`CASE-002`, its "Other alerts" label 🟡); no case-specific vs. bud-specific channel distinction found — same shared envelope | +| `CAP-025` | 2026-08-21 | Pixel 7a | 17 (⚪ assumed) | release_5.203 (⚪ assumed) | 1.0.955078536 (assumed) | K | `FIND-001`, `FIND-002` (`FIND-003`/`FIND-004` attempted, resolve to a different mechanism) | Find My Buds/Ring, the last remaining fully unattributed app command; `PROTOCOL.md` §4.4's hypothesis tested | `captures/CAP-025-2026-08-21_08-40-52_08-45-26-Group_K/CAP-025-btsnoop_hci.log` | same file | analyzed — see `CAP-025-FINDINGS.md` in that folder; **`FIND-001`/`FIND-002` strongly confirmed, video-correlated**: DLCI 0x04 `Group 0x04 Code 0x01`, `Value`=`0x01`(start Right)/`0x02`(start Left)/`0x00`(stop) — fits the spec's ACK shape (the Buds' ACK carries one state byte, the spec's example two; `CAP-025-FINDINGS.md` §6); **major structural finding**: `FIND-003`(Case)/`FIND-004`(both) route, in the official app, through a separate Find Hub/Find-My-Device-Network UI flow that produces **no** local Ring command on the wire — a different, likely GMS/account-mediated mechanism, not a capture gap (the spec's local Ring value `0x03` = both is untested, `DECISIONS.md` ADR-027 Update 2026-09-24) | | `CAP-026` | 2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | L | `BATT-001`, `OBS-001` | Passive observation windows (idle-wait-for-reconnect-notification, force-close/reopen) | `captures/CAP-026-2026-09-12_06-49-53_06-52-49-Group_L/CAP-026-btsnoop_hci.log` | same file | analyzed — see `CAP-026-FINDINGS.md`; **`BATT-001` wire-correlated for the first time**: connect-time battery data (DLCI 0x04 Option B candidate + DLCI 0x08 Option E) lands ~12–13s before the on-screen reconnect notification appears — Left/Right match on-screen exactly, Case reads 95 vs. on-screen 93% (a further instance of ADR-014's short/no-flag-form-may-be-stale pattern). **`OBS-001` clean negative**: app force-close+reopen (classic connection never drops) produces zero DLCI 0x02/0x04/0x08 traffic | -| `CAP-027` | 2026-08-30 | Pixel 7a (⚪ assumed, not screen-confirmed) | 17 (⚪ assumed) | release_5.203 (🟢 confirmed on-wire) | official Pixel Buds Companion App (not screen-confirmed; Spotify used as the music source) | N | `TOUCH-002`–`TOUCH-007` | Physical touch gestures on the bud hardware; `TOUCH-007`'s behavior depends on the per-earbud mode set in `CAP-021` (Group G) | `captures/CAP-027-2026-08-30_15-45-14_15-49-07-Group_N/CAP-027-btsnoop_hci.log` | same file | analyzed — see `CAP-027-FINDINGS.md` in that folder; **structural finding: `TOUCH-002`–`TOUCH-006` ride AVRCP (Pass Through/VolumeChanged), not any RFCOMM DLCI at all** — all 13 instances 1:1-correlated to a wire event; `TOUCH-007` (press-and-hold) is the exception, confirmed on DLCI 0x04 (official Fast Pair Message Stream, already-FACT "Notify ANC state" `0x13` shape from `PROTOCOL.md` §4.1), not DLCI 0x02 (`libmaestro`). Both `TOUCH-007` timestamps (previously `TBD`) recovered this way. Left-bud `TOUCH-007` shows two rotation-step Notify frames 5.68s apart, unresolved whether that's one long hold or two presses (🔴 open) | -| `CAP-028` | 2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | O | `HEAD-002`, `HEAD-003` | Physical head gestures; Head gestures already enabled from `CAP-020` (Group F) | `captures/CAP-028-2026-09-12_07-16-15_07-17-18-Group_O/CAP-028-btsnoop_hci.log` | same file | analyzed — see `CAP-028-FINDINGS.md`; camera points at the phone screen, not the user's head, so the gestures themselves are not video-visible. **Clean negative on the Buds' own connection**: zero DLCI 0x02/0x04/0x08 traffic during the entire claimed gesture window, no AVRCP traffic, no SCO/eSCO call anywhere in the log — consistent with (not distinguishable from) Nod/Shake being functionally inert with no active call/notification. **Bonus, third occurrence of a pattern**: a second, unrelated LE connection (standard Heart Rate service, no Fast Pair Service) coincidentally overlaps the gesture window with substantial ATT traffic on handle `0x0044`/`0x0042` — same signature as `CAP-018`, not Buds-attributable | -| `CAP-029` | 2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | P | `CONV-002`, `CASE-007`, `PAIR-002` (`CASE-008` not exercised) | Conversation Detection voice trigger, factory reset (`CASE-007`, run this session) and its re-pair (`PAIR-002`) | `captures/CAP-029-2026-09-12_07-53-53_07-58-05-Group_P/CAP-029-btsnoop_hci.log` | same file | analyzed — see `CAP-029-FINDINGS.md`; **`CONV-002` clean negative**: media visibly pauses on screen but zero DLCI 0x02/0x04/0x08 traffic accompanies it, no ANC-mode change anywhere pre-reset. **`CASE-007`/`PAIR-002` confirmed**: reproduces the established factory-reset pattern and a genuine fresh-SSP re-pair. **`CASE-008` not attempted.** **Major correction to the draft**: the draft's claimed final "forget the device again" did not happen — video and wire both confirm the device stays connected through the end of the session | -| `CAP-030` | *planned* | either phone | TBD | TBD | TBD | Q (items #19–20) | `LOUD-001`, `ADAPT-002` | Group Q's two remaining items (item #18 already planned separately as `CAP-011`) — attempt to observe Loud Noise Protection and Adaptive Audio engaging; requires firmware ≥4.467 | — | — | planned | +| `CAP-027` | 2026-08-30 | Pixel 7a (⚪ assumed, not screen-confirmed) | 17 (⚪ assumed) | release_5.203 (🟢 confirmed on-wire) | official Pixel Buds Companion App (not screen-confirmed; Spotify used as the music source) | N | `TOUCH-002`–`TOUCH-007` | Physical touch gestures on the bud hardware; `TOUCH-007`'s behavior depends on the per-earbud mode set in `CAP-021` (Group G) | `captures/CAP-027-2026-08-30_15-45-14_15-49-07-Group_N/CAP-027-btsnoop_hci.log` | same file | analyzed — see `CAP-027-FINDINGS.md` in that folder; **structural finding: `TOUCH-002`–`TOUCH-006` ride AVRCP (Pass Through/VolumeChanged), not any RFCOMM DLCI at all** — all 16 labelled `TOUCH-002`–`TOUCH-006` instances (5+2+5+2+2, `CAP-027-FINDINGS.md` §3.1, plus the two "+ tap once" PAUSE frames 2407/2587) 1:1-correlated to a wire event; `TOUCH-007` (press-and-hold) is the exception, confirmed on DLCI 0x04 (official Fast Pair Message Stream, already-FACT "Notify ANC state" `0x13` shape from `PROTOCOL.md` §4.1), not DLCI 0x02 (`libmaestro`). Both `TOUCH-007` timestamps (previously `TBD`) recovered this way. Left-bud `TOUCH-007` shows two rotation-step Notify frames 5.68s apart, unresolved whether that's one long hold or two presses (🔴 open) | +| `CAP-028` | 2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | O | `HEAD-002`, `HEAD-003` | Physical head gestures; Head gestures on in this session (ReadSetting frame 1350, `CAP-028-FINDINGS.md` §2 — `CAP-020`, Group F, left them off) | `captures/CAP-028-2026-09-12_07-16-15_07-17-18-Group_O/CAP-028-btsnoop_hci.log` | same file | analyzed — see `CAP-028-FINDINGS.md`; camera points at the phone screen, not the user's head, so the gestures themselves are not video-visible. **Clean negative on the Buds' own connection**: zero DLCI 0x02/0x04/0x08 traffic during the entire claimed gesture window, no AVRCP traffic, no SCO/eSCO call anywhere in the log — consistent with (not distinguishable from) Nod/Shake being functionally inert with no active call/notification. **Bonus, third occurrence of a pattern**: a second, unrelated LE connection (standard Heart Rate service, no Fast Pair Service) coincidentally overlaps the gesture window with substantial ATT traffic on handle `0x0044`/`0x0042` — same signature as `CAP-018`, not Buds-attributable | +| `CAP-029` | 2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | P | `CONV-002`, `CASE-007`, `PAIR-002` (`CASE-008` not exercised); incidental `APP-001`/`APP-002` | Conversation Detection voice trigger, factory reset (`CASE-007`, run this session) and its re-pair (`PAIR-002`) | `captures/CAP-029-2026-09-12_07-53-53_07-58-05-Group_P/CAP-029-btsnoop_hci.log` | same file | analyzed — see `CAP-029-FINDINGS.md`; **`CONV-002` negative for known commands**: media visibly pauses on screen with no ANC Set/Notify, settings write or AVRCP pass-through, no ANC-mode change anywhere pre-reset; one unidentified DLCI 0x08 frame (1592, `01 09 … 13`) precedes the phone's AVRCP Paused (1593) by 0.12 s (🔴, `CAP-029-FINDINGS.md` §2). **`CASE-007`/`PAIR-002` recorded**: the project's first wire record of a factory reset (Delete Stored Link Key 2091/2497/2860/3316) and a genuine fresh-SSP re-pair. **`CASE-008` not attempted.** **Major correction to the draft**: the draft's claimed final "forget the device again" did not happen — video and wire both confirm the device stays connected through the end of the session | +| `CAP-030` | *planned* | either phone | TBD | TBD | TBD | Q (items #19–20) | `LOUD-001`, `ADAPT-002` | Group Q's two remaining items (item #18 covered separately by `CAP-011`, analyzed, and its repeat `CAP-043`) — attempt to observe Loud Noise Protection and Adaptive Audio engaging; requires firmware ≥4.467 | — | — | planned | | `CAP-031` | 2026-08-27 | Pixel 7a | 17 (⚪ assumed, not screen-confirmed) | release_5.203 | official Pixel Buds Companion App (version not visible on screen) | A (repeat, 3rd attempt) | `PAIR-001`, `PAIR-004`, incidental `BATT-004` | Third attempt at `CAP-001-FINDINGS.md` §6's original goal — HCI snoop logging started before any prior association with the device exists, this time with a live in-recording file-size-polling check added specifically to avoid `CAP-013`'s failure mode. **Approved as recorded (maintainer, chat 2026-09-30, `ai-sessions/0059`).** | `captures/CAP-031-2026-08-27_06-04-48_06-08-10-Group_A/CAP-031-btsnooz_hci.log` (**`btsnooz`-format, inferred 15–126-byte captured length per packet, same truncation issue as `CAP-012`/`CAP-013`; see `CAP-031-FINDINGS.md` §1**) | same file | analyzed — **partial: pre-clearing-action window not captured, a second consecutive failure of this method** — see `CAP-031-FINDINGS.md`/`CAP-031-EVENT-NOTES.md` in that folder. This session used a genuine narrow, per-device "Forget" (screenshot-confirmed, unlike `CAP-013`'s broader reset) and a live snoop-log file-size-polling check during recording, but the log's first frame (06:06:37.16) still starts **66s after** the on-screen Forget tap (06:05:31) — also after case-open, pair-button-press, and the entire first "Pair new device" scan attempt, none of which are logged. **Primary question (`CAP-001-FINDINGS.md` §6) remains 🔴 OPEN, not answered, untested a third time.** **Secondary question (`PAIR-004`) is reconfirmed: 🟢 CONFIRMED fresh classic SSP handshake** (frames 598–689, a sixth confirming instance) — no key-reuse path observed. Bonus, both negative results: `CAP-013`'s DLCI 0x02 ~61s-delay does **not** reproduce (opens 1.64s after DLCI 0x00, within the initial burst) and `CAP-013`'s unattributed second BLE link does **not** reproduce (exactly one LE link this session, to the Buds' own public address) — both now look like single-session artifacts. A fourth attempt is still needed, this time verifying snoop-log *content* freshness (not just file size) before the Forget tap. | -| `CAP-032` | 2026-08-27 | Pixel 7a | 17 (⚪ assumed, not screen-confirmed) | release_5.203 | official Pixel Buds Companion App (version not visible on screen) | A (repeat, 4th attempt) | `PAIR-001`, `PAIR-004`, incidental `BATT-004` | Fourth attempt at `CAP-001-FINDINGS.md` §6's original goal — this time extracted via the raw BTSnoop file path (`CAPTURE_BLUETOOTH_HCI_SNOOP.md` §3 step 3) rather than the `btsnooz.py` fallback used for `CAP-012`/`CAP-013`/`CAP-031`. **Approved as recorded (maintainer, chat 2026-09-30, `ai-sessions/0059`).** | `captures/CAP-032-2026-08-27_18-30-15_18-32-33-Group_A/CAP-032-btsnoop_hci.log` (**genuine raw, untruncated BTSnoop — `frame.cap_len == frame.len` for all 2,455 frames, no `capinfos`-inferred size cap; see `CAP-032-FINDINGS.md` §0.1**) | same file | analyzed — **success: pre-clearing-action window captured for the first time in four attempts** — see `CAP-032-FINDINGS.md`/`CAP-032-EVENT-NOTES.md` in that folder. The log's first frame (18:29:45.72) starts **~58s before** the on-screen Forget tap (18:30:42) and ~30s before the video itself begins. **Primary question (`CAP-001-FINDINGS.md` §6) is now answered for this session: 🟢 no prior BLE link or valid classic link key existed for the Buds anywhere in the covered pre-Forget window** (`CAP-032-FINDINGS.md` §0.3) — this does not reproduce `CAP-001`'s original finding (a clean counter-example, not a contradiction; `CAP-001`'s own session-specific puzzle remains independently open). **Secondary question (`PAIR-004`) reconfirmed: 🟢 CONFIRMED fresh classic SSP handshake** (frames 1090–1153, a seventh confirming instance). Bonus: the untruncated log fully decodes DLCI 0x08's battery push (`[100,1,1]`/`[100,1,2]`/`[57,1,3]` = Left/Right/Case, matching the on-screen reading exactly) and firmware/capability-identifier fields; a previously-undocumented vendor-specific HCI command (`0xFD57`/`0x0157`, frame 91) embeds the Buds' address 105ms into the log, structurally consistent with bulk bonded-device provisioning at BT-enable time, not a connection — recorded 🔴 OPEN QUESTION, not bearing on the primary question. The `btsnooz`-vs-raw extraction-path hypothesis (`CAP-013-FINDINGS.md` §1, `CAP-031-FINDINGS.md` §1) is supported by this one data point (`CAP-032-FINDINGS.md` §0.1/§7 Test C), not yet independently isolated. | +| `CAP-032` | 2026-08-27 | Pixel 7a | 17 (⚪ assumed, not screen-confirmed) | release_5.203 | official Pixel Buds Companion App (version not visible on screen) | A (repeat, 4th attempt) | `PAIR-001`, `PAIR-004`, incidental `BATT-004` | Fourth attempt at `CAP-001-FINDINGS.md` §6's original goal — this time extracted via the raw BTSnoop file path (`CAPTURE_BLUETOOTH_HCI_SNOOP.md` §3 step 3) rather than the `btsnooz.py` fallback used for `CAP-012`/`CAP-013`/`CAP-031`. **Approved as recorded (maintainer, chat 2026-09-30, `ai-sessions/0059`).** | `captures/CAP-032-2026-08-27_18-30-15_18-32-33-Group_A/CAP-032-btsnoop_hci.log` (**genuine raw, untruncated BTSnoop — `frame.cap_len == frame.len` for all 2,455 frames, no `capinfos`-inferred size cap; see `CAP-032-FINDINGS.md` §0.1**) | same file | analyzed — **success: pre-clearing-action window captured for the first time in four attempts** — see `CAP-032-FINDINGS.md`/`CAP-032-EVENT-NOTES.md` in that folder. The log's first frame (18:29:45.72) starts **~56s before** the on-screen Forget tap (18:30:42; ~58s before its HCI effect, frame 768) and ~30s before the video itself begins. **Primary question (`CAP-001-FINDINGS.md` §6) is now answered for this session: 🟢 no prior BLE link or valid classic link key existed for the Buds anywhere in the covered pre-Forget window** (`CAP-032-FINDINGS.md` §0.3) — this does not reproduce `CAP-001`'s original finding (a clean counter-example, not a contradiction; `CAP-001`'s own session-specific puzzle remains independently open). **Secondary question (`PAIR-004`) reconfirmed: 🟢 CONFIRMED fresh classic SSP handshake** (frames 1090–1153, a seventh confirming instance). Bonus: the untruncated log fully decodes DLCI 0x08's battery push (`[100,1,1]`/`[100,1,2]`/`[57,1,3]` = Left/Right/Case, matching the on-screen reading exactly) and firmware/capability-identifier fields; a previously-undocumented vendor-specific HCI command (`0xFD57`/`0x0157`, frame 91) embeds the Buds' address 105ms into the log, structurally consistent with bulk bonded-device provisioning at BT-enable time, not a connection — recorded 🔴 OPEN QUESTION, not bearing on the primary question. The `btsnooz`-vs-raw extraction-path hypothesis (`CAP-013-FINDINGS.md` §1, `CAP-031-FINDINGS.md` §1) is supported by this one data point (`CAP-032-FINDINGS.md` §0.1/§7 Test C), not yet independently isolated. | | `CAP-033` | 2026-08-30 | Pixel 7a | not recorded | release_5.203 (🟢 confirmed on-wire) | n/a — app force-stopped throughout the entire recorded session | AA | `SDP-001`, `SDP-002`(not attempted — no update pending) | SDP UUID branch isolation for `gbm.a()`'s "default internal rfcomm socket" path — system-settings-only pairing (app force-stopped) to check whether the pre-app-fetch SDP UUID set ever differs from every existing capture's "pigweed"-only result (`REVERSE_ENGINEERING.md`'s `gbm`/`fzd` entries, `DECISIONS.md` ADR-018) | `captures/CAP-033-2026-08-30_15-17-03_15-19-52-Group_AA/CAP-033-btsnoop_hci.log` | same file | analyzed, **isolation not fully clean — see `CAP-033-FINDINGS.md` §1** ("Forget" preceded Force-stop by ~10s, reverse of procedure, though scoped away from the actual SDP-browse window; Step 3's app-open baseline was never executed at all, on- or off-camera). `SDP-001` result capped at 🟡 HYPOTHESIS: "default" UUID still zero occurrences (raw byte scan, both byte orders), "pigweed" UUID confirmed present and named **"MAESTRO APP"** on-the-wire (SDP service-name string, frame 1279) — first wire-level (not just APK-code) corroboration of `DECISIONS.md` ADR-018's channel identity. **Bonus structural finding:** the same SDP response names DLCI 0x08 as **"GSND CONTROL"** and DLCI 0x0a as **"GSND AUDIO"** — new, previously-undocumented leads for `PROTOCOL.md` §2.3's open DLCI-0x08 identity question and `CAP-021-FINDINGS.md` §4a's unattributed DLCI-0x0a burst, proposed pending maintainer review, not promoted | | `CAP-034` | 2026-09-01 | Pixel 9a (GrapheneOS) — Bluetooth cache cleared with `pm clear` before the session (it had connected to this Buds unit before, per the notes) | not recorded | ⚪ assumed `release_5.203` (not re-confirmed — no official app used, DLCI 0x08 never opens) | nRF Connect for Mobile (Nordic Semiconductor); official Pixel Buds Companion App not installed | W (4th attempt) | `GATT-001`, incidental `PAIR-001`/`PAIR-003`/`BATT-003` | 4th Group W attempt at the `0x0c0X`/`0x0f2X` GATT handle↔UUID mapping — combines `CAP-014`'s confirmed-unlimited snaplen fix with Group W's own untried cache-busting method (`pm clear com.android.bluetooth` on a phone never before connected to this Buds unit), the first session to have both at once | `captures/CAP-034-2026-09-01_06-46-31_06-52-45-Group_W/CAP-034-btsnoop_hci.log` | same file | analyzed, **✅ RESOLVED — maintainer sign-off obtained per `AGENTS.md` §6** — see `CAP-034-FINDINGS.md` in that folder. Confirmed 0/3,717 truncated frames (max 684B). The sole LE connection (chandle `0x0040`) yields one genuine, full `0x0001`–`0xffff` discovery walk (06:47:42.147–45.490) before bonding — the "reconnect" at 06:51:22 is a Database Hash cache-hit only, no second discovery pass. Resolves the full 15-primary-service GATT profile: `0x0c00`–`0x0c14` = **Google Fast Pair Service** (`0xFE2C`) with all 5 spec-defined characteristics (Model ID, Key-based Pairing, Passkey, Account Key, Additional Data) plus Message Stream PSM and one still-unnamed `FE2C1238…` characteristic; `0x0f20`–`0x0f2a` = Device Information (`0x0f28`=Serial Number String, `0x0f2a`=Firmware Revision String); `0x0f30`–`0x0f33` = Battery Service (`0x0f32`=Battery Level). Independently corroborated by nRF Connect's own on-screen UUID rendering and live-verified against the official Fast Pair spec. **Corrects** `CAP-017-FINDINGS.md` §6's hypothesis that "Unknown Service" (`109b862f-…`) might be the `0x0c0X` cluster's container — it does not; it occupies a separate range (`0x0f37`–`0x0f3e`), own purpose still unidentified. ADR-008 compliance confirmed (Accessory Non-Owner Service appears only in unavoidable discovery inventory, never read/written). See `PROTOCOL.md` §6 and §4.3 Option D for the promoted findings | -| `CAP-035` | 2026-09-02 | Pixel 9a (GrapheneOS) | not recorded | ⚪ assumed `release_5.203` (not re-confirmed — no official app used, DLCI 0x08's firmware string was never independently re-checked) | none — no Pixel Buds app, no nRF Connect, system Bluetooth settings only | AB | `GSND-001`, incidental `PAIR-001`/`PAIR-003`/`BATT-003` | GMS-independence check for DLCI 0x08 ("GSND CONTROL")/0x0a ("GSND AUDIO")/0x06 ("DEBUG APP")/0x12 ("BTIS"), per `CAP-033-FINDINGS.md` §3's negative APK-search result (see `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group AB) | `captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-btsnoop_hci.log` | same file | analyzed, **maintainer sign-off obtained per `AGENTS.md` §6** — see `CAP-035-FINDINGS.md` in that folder. Confirmed 0/1,945 truncated frames. Two videos turned out to be sequential with only a ~7s recording-stop/restart gap (not the ~5min originally logged) — resolved directly from frame comparison, correcting the event timeline including a ~91–97s error in the original disconnect/reconnect time estimates. **GMS precondition:** `com.google.android.gms` present but `dumpsys`-verified disabled (`enabled=3` = `COMPONENT_ENABLED_STATE_DISABLED_USER`) — not genuinely absent, so this is a rigorously verified second data point for `CAP-004-FINDINGS.md` §4a's existing "GMS present but disabled" finding, not a stronger novel "GMS-absent" confirmation. **Result:** DLCI 0x08 content reproduces byte-identical twice (connect + reconnect); DLCI 0x0a opens in lockstep both times but carries zero payload; DLCI 0x06/0x12 never open at all — clean negatives for both, first time either has been specifically checked. ADR-008 compliant. Still open: a repeat with GMS genuinely uninstalled, for full closure of the OS-stack-vs-GMS question | +| `CAP-035` | 2026-09-02 | Pixel 9a (GrapheneOS) | not recorded | ⚪ assumed `release_5.203` (not re-confirmed — no official app used, DLCI 0x08's firmware string was never independently re-checked) | none — no Pixel Buds app, no nRF Connect, system Bluetooth settings only | AB | `GSND-001`, incidental `PAIR-001`/`PAIR-003`/`BATT-003` | GMS-independence check for DLCI 0x08 ("GSND CONTROL")/0x0a ("GSND AUDIO")/0x06 ("DEBUG APP")/0x12 ("BTIS"), per `CAP-033-FINDINGS.md` §3's negative APK-search result (see `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group AB) | `captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-btsnoop_hci.log` | same file | analyzed, **maintainer sign-off obtained per `AGENTS.md` §6** — see `CAP-035-FINDINGS.md` in that folder. Confirmed 0/1,945 truncated frames. Two videos turned out to be sequential with only a ~3 s recording-stop/restart gap (06:50:53 + 67.69 s = 06:52:00.7 → 06:52:04; corrected 2026-10-03 in the capture's files) (not the ~5min originally logged) — resolved directly from frame comparison, correcting the event timeline including a ~91–97s error in the original disconnect/reconnect time estimates. **GMS precondition:** `com.google.android.gms` present but `dumpsys`-verified disabled (`enabled=3` = `COMPONENT_ENABLED_STATE_DISABLED_USER`) — not genuinely absent, so this is a rigorously verified second data point for `CAP-004-FINDINGS.md` §4a's existing "GMS present but disabled" finding, not a stronger novel "GMS-absent" confirmation. **Result:** DLCI 0x08 content reproduces byte-identical twice (connect + reconnect); DLCI 0x0a opens in lockstep both times but carries zero payload; DLCI 0x06/0x12 never open at all — clean negatives for both, first time either has been specifically checked. ADR-008 compliant. Still open: a repeat with GMS genuinely uninstalled, for full closure of the OS-stack-vs-GMS question | | `CAP-036` | 2026-09-04 | Pixel 7a | 17 (⚪ assumed — the "14" recorded here until 2026-10-03 had no source; the same phone reads 17 in the captures before and after, `ai-sessions/0069` A68-CAP-13) | ⚪ assumed `release_5.203` (carried over, not re-checked on-screen this session) | 1.0.955078535 | AC | `OBS-004`, incidental `PAIR-003` | Settings-state read-back isolation — official app + GMS enabled (normal baseline, the opposite of Group S/AB), idle-only observation windows (reconnect, EQ screen, Controls-and-gestures/Touch-controls screens, Multipoint screen) with **no setting touched at any point**, to isolate whether the official app ever issues a state *query* (as opposed to the write-direction-only evidence in `PROTOCOL.md` §4.2/§4.5) before any user-initiated change — the requirement `ARCHITECTURE.md` §3.1 imposes on this project's own app. A clean negative is a real result here, not a failed session | `captures/CAP-036-2026-09-04_06-35-58_06-41-18-Group_AC/CAP-036-btsnoop_hci.log` (raw path, confirmed untruncated: 0/2,492 mismatched frames, no snaplen cap) | same file | analyzed — see `CAP-036-FINDINGS.md` in that folder. **Split outcome, by trigger:** on **reconnection**, DLCI 0x04's spec-documented but never-before-observed "Get ANC state" (`0x11`) query fires once, 34ms after the channel opens, answered ~10.7ms later by "Notify ANC state" (`0x13`, current=Off, matching the on-screen state) — 🟡 HYPOTHESIS pending replication, proposed (not self-promoted) for `PROTOCOL.md` §4.1. On **settings-screen-open** (EQ, Controls-and-gestures, Touch controls, More settings, Multipoint — five clean, video-verified idle windows), zero query or write traffic occurs on DLCI 0x02/0x04/0x08 — a citable negative result. A frame-by-frame video re-pass corrected several of this session's own placeholder timestamps by 5–30 seconds against what the video actually shows (see `CAP-036-FINDINGS.md` §2). Three new 🔴 open questions raised (DLCI 0x08 Get-shaped connect-time frames with unmapped Group/Code pairs; an undecoded DLCI 0x02 connection-settling RPC burst; a `Settable toggles` byte discrepancy between this session's Notify frame and previously-documented Set frames) | | `CAP-037` | 2026-09-06 | Pixel 7a | 17 (⚪ assumed — the "14" recorded here until 2026-10-03 had no source; the same phone reads 17 in the captures before and after, `ai-sessions/0069` A68-CAP-13) | ⚪ assumed `release_5.203` | 1.0.955078536 (⚪ assumed) | AD | `OBS-004`, incidental `PAIR-003` | Purpose-built repeat of the "Get ANC state" reconnect-reliability + dock-state-transition question — planned as 5 isolated reconnects, alternating docked/undocked | `captures/CAP-037-2026-09-06_06-11-46_06-29-38-Group_AD/CAP-037-btsnoop_hci.log` | same file, raw path, 0/29,956 truncated | analyzed — see `CAP-037-FINDINGS.md`. **Procedure ran far longer than planned: 34 reconnects over ~20 minutes (not 5), 26 with real DLCI 0x04 payload — all 26 produce a `08 11`/`08 13` Get/Notify pair, zero misses, the largest single-session replication of `DECISIONS.md` ADR-022 on file.** All 26 `Settable-toggles` samples (16 docked=`0x00`, 10 undocked=`0xe8`) match video-confirmed dock state, reconfirming ADR-024; a perfect `Settable`↔`Current-state` co-occurrence pattern proposed as a new 🟡 HYPOTHESIS. One 🔴 open anomaly: a mid-connection `Settable` flip with no preceding Get on one chandle | | `CAP-038` | 2026-09-06 | Pixel 7a | 17 (⚪ assumed — the "14" recorded here until 2026-10-03 had no source; the same phone reads 17 in the captures before and after, `ai-sessions/0069` A68-CAP-13) | ⚪ assumed `release_5.203` | 1.0.955078536 (⚪ assumed) | AE | `OBS-005`, incidental `PAIR-003`, `INEAR`-family | Realistic physical reconnect trigger (Buds removed from case and worn) vs. `CAP-036`/`CAP-037`'s OS-toggle-only trigger | `captures/CAP-038-2026-09-06_06-49-50_06-53-57-Group_AE/CAP-038-btsnoop_hci.log` | same file, raw path, 0/4,426 truncated | analyzed — see `CAP-038-FINDINGS.md`. **Video (247.07s) ends ~2m10s before the log; the documented "Window 2" (case-lid-only trigger) has zero video or wire evidence — not verifiably executed, a capture gap not a result.** New finding: on the first (most "realistic") reconnect, the Fast Pair Message Stream/`libmaestro` channels opened under session-local DLCI numbers `0x05`/`0x03` instead of the usual `0x04`/`0x02` (consistent with `CAP-001-FINDINGS.md` §2's established session-local-numbering finding, not a new anomaly) — but that same reconnect's "Notify ANC state" frame read `Settable-toggles=0x00` (docked) immediately after physical removal from the case, an unreconciled 🔴 tension with ADR-024. Two ANC Notify frames occur with no Get and no Set anywhere in the log, plausibly a hardware press-and-hold gesture (matches `CAP-027-FINDINGS.md` §4's mechanism). No A2DP/AVDTP signaling found. `Settable-toggles` again tracks dock state correctly (reconfirms ADR-024) | | `CAP-039` | 2026-09-06 | Pixel 7a | 17 (⚪ assumed — the "14" recorded here until 2026-10-03 had no source; the same phone reads 17 in the captures before and after, `ai-sessions/0069` A68-CAP-13) | ⚪ assumed `release_5.203` | 1.0.955078536 (⚪ assumed) | AF | `OBS-006`, incidental `ANC`-family, `PAIR-003` | Same-session comparison of the "Notify ANC state" `Settable-toggles` byte after a `Set` vs. a `Get`, dock state held constant | `captures/CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF/CAP-039-btsnoop_hci.log` | same file, raw path, 0/6,184 truncated | analyzed — see `CAP-039-FINDINGS.md`. **The documented "one Set, one Get" plan did not occur: video/wire re-pass found 6 reconnects and 4 ANC taps (alternating Noise cancellation↔Adaptive, never "Off") instead.** All 10 same-session Set/Get `Settable-toggles` samples read `0xe8` (undocked, constant) — a strong, zero-counter-example same-session confirmation of `DECISIONS.md` ADR-024's trigger-independence. Bonus: DLCI 0x04 `Group 0x03 Code 0x03` reproduces the existing battery-code candidate with a live 98→97% Left transition | -| `CAP-040` | 2026-09-06 | Pixel 7a | 17 (⚪ assumed — the "14" recorded here until 2026-10-03 had no source; the same phone reads 17 in the captures before and after, `ai-sessions/0069` A68-CAP-13) | ⚪ assumed `release_5.203` | 1.0.955078536 (⚪ assumed) | AG | `PRIV-001`, incidental `PAIR-003`, `BATT`-family | DLCI 0x08's unmapped zero-length Get-shaped codes decoded via correlation against a known-changing value — executed as ~15 repeats of a docked/undocked cycle using the app's own Connect/Disconnect buttons | `captures/CAP-040-2026-09-06_07-28-00_07-57-05-Group_AG/CAP-040-btsnoop_hci.log` (its `.log.last` is **not** part of this session — see below) | main file only, raw path, 0/8,539 truncated | analyzed — see `CAP-040-FINDINGS.md`. **Integrity correction: `CAP-040-btsnoop_hci.log.last` is leftover on-device buffer content from `CAP-039`'s own session plus the idle gap before `CAP-040`, not an intra-session rotation — only the main log is this session's own evidence** (mirrors the same pattern independently found for `CAP-042`/`CAP-041`, below). **Major finding: the app's own in-app "Connect"/"Disconnect" buttons produce zero wire-visible signal** (no ACL teardown, no DLCI bounce) across ~15 taps — invalidating the session's own dock/undock bracketing for `PRIV-001`'s intended correlation test (DLCI 0x08 opens exactly once, so all 7 flagged codes have N=1). Bonus, in the video's own previously-unreviewed tail: a genuine real reconnect cluster and a 🟡 HYPOTHESIS that the `0xff` battery sentinel appears momentarily at the instant of physical docking | +| `CAP-040` | 2026-09-06 | Pixel 7a | 17 (⚪ assumed — the "14" recorded here until 2026-10-03 had no source; the same phone reads 17 in the captures before and after, `ai-sessions/0069` A68-CAP-13) | ⚪ assumed `release_5.203` | 1.0.955078536 (⚪ assumed) | AG | `PRIV-001`, incidental `PAIR-003`, `BATT`-family | DLCI 0x08's unmapped zero-length Get-shaped codes decoded via correlation against a known-changing value — executed as ~15 repeats of a hand-noted docked/undocked cycle using the app's own Connect/Disconnect buttons (the buds were in fact never docked before ~07:53:53 — no charging bit on DLCI 0x04 until frame 5729, film shows them beside the case; `CAP-040-EVENT-NOTES.md` correction 2026-10-15) | `captures/CAP-040-2026-09-06_07-28-00_07-57-05-Group_AG/CAP-040-btsnoop_hci.log` (its `.log.last` is **not** part of this session — see below) | main file only, raw path, 0/8,539 truncated | analyzed — see `CAP-040-FINDINGS.md`. **Integrity correction: `CAP-040-btsnoop_hci.log.last` is leftover on-device buffer content from `CAP-039`'s own session plus the idle gap before `CAP-040`, not an intra-session rotation — only the main log is this session's own evidence** (mirrors the same pattern independently found for `CAP-042`/`CAP-041`, below). **Major finding: the app's own in-app "Connect"/"Disconnect" buttons produce zero wire-visible signal** (no ACL teardown, no DLCI bounce) across ~15 taps — invalidating the session's own dock/undock bracketing for `PRIV-001`'s intended correlation test (DLCI 0x08 opens exactly once, so all 7 flagged codes have N=1). Bonus, in the video's own previously-unreviewed tail: a genuine real reconnect cluster and a 🟡 HYPOTHESIS that the `0xff` battery sentinel appears momentarily at the instant of physical docking | | `CAP-041` | 2026-09-06 | Pixel 7a | 17 (⚪ assumed — the "14" recorded here until 2026-10-03 had no source; the same phone reads 17 in the captures before and after, `ai-sessions/0069` A68-CAP-13) | 🟢 confirmed `release_5.203` (on-screen, 17:11:32) | 1.0.955078536 (⚪ assumed) | AH | `OBS-007`, incidental `PAIR-003` | DLCI 0x02's connect-time RPC burst captured with deliberately non-default EQ/touch-controls settings, diffed against `CAP-036`'s own default-settings burst | `captures/CAP-041-2026-09-06_17-10-39_17-17-48-Group_AH/CAP-041-btsnoop_hci.log` | same file, raw path, 0/4,003 truncated | analyzed — see `CAP-041-FINDINGS.md`. **Settings were changed continuously through the session instead of once beforehand, producing 3 wire-confirmed reconnects (not the planned 4) each against its own distinct, non-default settings state.** The connect-time burst's length/shape signature is essentially invariant across all 3 states and `CAP-036`'s default baseline — a scoped negative for a `libmaestro`-side settings read-back at the length level (full byte-for-byte content diff not completed, flagged as a follow-up). Bonus: a recurring 2-field sub-message inside the burst holds a constant value matching on-screen Case% throughout — consistent with, not confirming, `CAP-036-FINDINGS.md` §12.6's open item | -| `CAP-042` | 2026-09-06 | Pixel 7a | ⚪ assumed (carried from `CAP-036`, not visible — screen off) | ⚪ assumed `release_5.203` | n/a (backgrounded) | AI | `OBS-002` | Long, genuinely idle bracket to characterize the periodic DLCI 0x02/0x04/0x08/HFP cross-channel push cadence, app backgrounded | `captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-btsnoop_hci.log` (its `.log.last` is **not** part of this session — see below) | main file only, raw path, 0/8,560 truncated | analyzed — see `CAP-042-FINDINGS.md`. **Integrity correction: `CAP-042-btsnoop_hci.log.last` reproduces `CAP-041`'s own three Connection Complete events verbatim and simply continues through the idle gap before `CAP-042` — it is not this session's own data, contrary to this task's own initial "confirmed rotated" framing.** True session duration is ~37m39s (main log only), not ~55m45s. **Central finding: the periodic push recurs only twice in the whole session (~16m and ~35m in, far sparser than `CAP-036`'s short-session sample), and HFP's `AT+BIEV` drops out of the previously-documented 4-channel near-lockstep sync entirely — DLCI 0x02/0x04/0x08 keep their own mutual sync.** Proposed 🔴 open question: an app-foreground-driven trigger, not a Buds-autonomous mechanism. Also revises `CAP-027-FINDINGS.md`'s "streaming-specific" sync-breakdown framing — idle sessions desync too | -| `CAP-043` | 2026-09-13 | Pixel 7a | 17 | release_5.203 (⚪ assumed, maintainer-supplied context) | 1.0.955078536 (⚪ assumed, maintainer-supplied context) | Q (repeat) | `BATT-002`, `BATT-003` | Repeat of `CAP-011`'s Battery Notification BLE scan, this time with the official app force-stopped and no active classic RFCOMM+GATT connection throughout the entire capture — closes `CAP-011-FINDINGS.md` §4's open item either way | `captures/CAP-043-2026-09-13_09-50-31_09-53-41-Group_Q/CAP-043-btsnoop_hci.log` | same file | analyzed — see `CAP-043-FINDINGS.md` in that folder; isolation genuinely clean this time (zero classic/RFCOMM/SDP traffic to the Buds' address across the entire 306.9s log, confirmed by both dissector filter and raw-byte scan); own-Buds `0xFE2C` advertisement (RSSI -20..-23 dBm, 60 occurrences, byte-identical payload throughout) still does **not** structurally match `PROTOCOL.md` §4.3 Option A's documented Battery Notification layout (first byte `0x10`, not `0x00`; no `0x33`/`0x34` marker at any offset) — a second confirmed non-match, this time ruling out "active connection suppresses the advertisement" as an explanation; `BATT-003` not exercised (no value change occurred this session by design) | -| `CAP-044` | 2026-09-13 | Pixel 7a | 17 | release_5.203 (🟢 confirmed on-wire) | 1.0.955078536 (⚪ assumed, maintainer-supplied context) | AA (repeat, 2nd attempt) | `SDP-001`, `SDP-002` (opportunistic, not attempted) | Repeat of `CAP-033`'s SDP UUID branch isolation, this time with Force-stop strictly preceding Forget and step 3's app-open baseline browse actually executed | `captures/CAP-044-2026-09-13_13-05-51_13-11-07-Group_AA/CAP-044-btsnoop_hci.log` | same file | analyzed — see `CAP-044-FINDINGS.md` in that folder; **isolation partially achieved, for a different reason than `CAP-033`**: Force-stop does precede Forget this time, but no Force-stop is confirmed covering the 2m8s window between the in-app Forget and the actual "Pair" tap (13:08:07, corrected from the draft's assumed 13:07:00) that triggers the one-and-only SDP browse in this log; step 3 (app-open) is confirmed executed on-camera but produces no second SDP transaction since the classic connection/RFCOMM channels never close between the initial pairing and the later app reopen. "Default" UUID still absent (5th consecutive negative); "MAESTRO APP"/"GSND CONTROL"/"GSND AUDIO"/"GFPS RFCOMM" channel-name↔UUID↔RFCOMM-channel mappings from `CAP-033` all reproduce byte-for-byte via this session's targeted per-UUID SDP queries (a different query style than `CAP-033`'s single generic wildcard browse) | +| `CAP-042` | 2026-09-06 | Pixel 7a | not recorded (17 ⚪ assumed, as neighbouring captures; `CAP-036` did not record it either — screen off) | ⚪ assumed `release_5.203` | n/a (backgrounded) | AI | `OBS-002` | Long, genuinely idle bracket to characterize the periodic DLCI 0x02/0x04/0x08/HFP cross-channel push cadence, app backgrounded | `captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-btsnoop_hci.log` (its `.log.last` is **not** part of this session — see below) | main file only, raw path, 0/8,560 truncated | analyzed — see `CAP-042-FINDINGS.md`. **Integrity correction: `CAP-042-btsnoop_hci.log.last` reproduces `CAP-041`'s own three Connection Complete events verbatim and simply continues through the idle gap before `CAP-042` — it is not this session's own data, contrary to this task's own initial "confirmed rotated" framing.** True session duration is ~37m39s (main log only), not ~55m45s. **Central finding: the periodic push recurs only twice in the whole session (~16m and ~35m in, far sparser than `CAP-036`'s short-session sample), and HFP's `AT+BIEV` drops out of the previously-documented 4-channel near-lockstep sync entirely — DLCI 0x02/0x04/0x08 keep their own mutual sync.** Proposed 🔴 open question: an app-foreground-driven trigger, not a Buds-autonomous mechanism. Also revises `CAP-027-FINDINGS.md`'s "streaming-specific" sync-breakdown framing — idle sessions desync too | +| `CAP-043` | 2026-09-13 | Pixel 7a | 17 (⚪ assumed — the MP4 tag is the camera phone's) | release_5.203 (⚪ assumed, maintainer-supplied context) | 1.0.955078536 (⚪ assumed, maintainer-supplied context) | Q (repeat) | `BATT-002`, `BATT-003` | Repeat of `CAP-011`'s Battery Notification BLE scan, this time with the official app force-stopped and no active classic RFCOMM+GATT connection throughout the entire capture — closes `CAP-011-FINDINGS.md` §4's open item either way | `captures/CAP-043-2026-09-13_09-50-31_09-53-41-Group_Q/CAP-043-btsnoop_hci.log` | same file | analyzed — see `CAP-043-FINDINGS.md` in that folder; isolation genuinely clean this time (zero classic/RFCOMM/SDP traffic to the Buds' address across the entire 306.9s log, confirmed by both dissector filter and raw-byte scan); own-Buds `0xFE2C` advertisement (RSSI -20..-23 dBm, 60 occurrences, byte-identical payload throughout) still does **not** structurally match `PROTOCOL.md` §4.3 Option A's documented Battery Notification layout (first byte `0x10`, not `0x00`; no `0x33`/`0x34` marker at any offset) — a second confirmed non-match, this time ruling out "active connection suppresses the advertisement" as an explanation; `BATT-003` not exercised (no value change occurred this session by design) | +| `CAP-044` | 2026-09-13 | Pixel 7a | 17 (⚪ assumed — the MP4 tag is the camera phone's) | release_5.203 (🟢 confirmed on-wire) | 1.0.955078536 (⚪ assumed, maintainer-supplied context) | AA (repeat, 2nd attempt) | `SDP-001`, `SDP-002` (opportunistic, not attempted) | Repeat of `CAP-033`'s SDP UUID branch isolation, this time with Force-stop nominally preceding Forget (nullified: the app was relaunched at 13:05:55, before the in-app Forget at 13:05:59) and step 3's app-open baseline browse actually executed | `captures/CAP-044-2026-09-13_13-05-51_13-11-07-Group_AA/CAP-044-btsnoop_hci.log` | same file | analyzed — see `CAP-044-FINDINGS.md` in that folder; **isolation partially achieved, for a different reason than `CAP-033`**: Force-stop nominally precedes Forget this time, but the app was relaunched (13:05:55) before the in-app Forget, and no Force-stop is confirmed covering the 2m8s window between the in-app Forget and the actual "Pair" tap (13:08:07, corrected from the draft's assumed 13:07:00) that triggers the one-and-only SDP browse in this log; step 3 (app-open) is confirmed executed on-camera but produces no second SDP transaction since the classic connection/RFCOMM channels never close between the initial pairing and the later app reopen. "Default" UUID still absent (5th consecutive negative); "MAESTRO APP"/"GSND CONTROL"/"GSND AUDIO"/"GFPS RFCOMM" channel-name↔UUID↔RFCOMM-channel mappings from `CAP-033` all reproduce byte-for-byte via this session's targeted per-UUID SDP queries (a different query style than `CAP-033`'s single generic wildcard browse) | | `CAP-045` | 2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | AJ (new) | `HOLD-005` (not actually exercised), `TOUCH-007` (incidental) | Intended: `HOLD-005` Left/Right ANC-rotation-checklist split | `captures/CAP-045-2026-09-12_08-22-51_08-24-32-Group_AJ/CAP-045-btsnoop_hci.log` | same file | analyzed — see `CAP-045-FINDINGS.md`; **the planned procedure was not run**: the checklist screen was never opened — instead the maintainer physically long-pressed each earbud to cycle ANC mode (`TOUCH-007`'s already-documented Notify-without-Set mechanism, `CAP-027-FINDINGS.md` §4). Zero rotation-checklist (`qhr` field 12) writes anywhere in the log. `HOLD-005`'s own Left/Right question remains open; a genuine re-run is still needed. Bonus: 10-sample replication confirms the Notify frame structurally has no Left/Right field — side is only inferable from timing/video | -| `CAP-046` | 2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | AK (new) | `AUDIO-003` | Volume balance (`field 17`) scale/direction — isolated extreme-position samples with tight video correlation | `captures/CAP-046-2026-09-12_17-02-59_17-05-49-Group_AK/CAP-046-btsnoop_hci.log` | same file | analyzed — see `CAP-046-FINDINGS.md`; **polarity resolved, opposite of the working assumption**: `field17=+100`=Left, `field17=-100`=Right (3/3 video-confirmed), range exactly ±100. Only 2 of the draft's claimed 4 samples actually occurred — no intermediate-position data exists. Bonus: `field19` (Mono audio) fires in exact lockstep (8/8) with every Balance-extreme/center transition | +| `CAP-046` | 2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | AK (new) | `AUDIO-003` | Volume balance (`field 17`) scale/direction — isolated extreme-position samples with tight video correlation | `captures/CAP-046-2026-09-12_17-02-59_17-05-49-Group_AK/CAP-046-btsnoop_hci.log` | same file | analyzed — see `CAP-046-FINDINGS.md`; **polarity resolved, opposite of the working assumption**: `field17=+100`=Left, `field17=-100`=Right (4/4 video-confirmed), range exactly ±100. Only 2 of the draft's claimed 4 samples actually occurred — no intermediate-position data exists. Bonus: `field19` (Mono audio) fires in exact lockstep (8/8) with every Balance-extreme/center transition | | `CAP-047` | 2026-09-14 | Pixel 7a | 17 | `release_5.203` | 1.0.955078536 | AL (new) | none existing — a candidate Test-ID (area `CASE`) is proposed in `CAP-047-FINDINGS.md` §9 item 4 for "swapped-slot (mismatched L/R) docking", awaiting maintainer sign-off | Purpose-built hypothesis test for the `CAP-021` DLCI 0x0a burst trigger — only Trigger candidate 3 (charge-state change) run this session, across two recordings due to a repeated swapped-slot (not corrected) docking attempt; Trigger candidates 1/2 not attempted | `captures/CAP-047-2026-09-14_05-51-38_06-35-30-Group_AL/CAP-047-btsnoop_hci.log` (Recording 1) + `CAP-047-btsnoop_hci-2.log` (Recording 2); its `.log.last` is **not** part of this session — see below | same files, raw path, 0/3,250 and 0/4,716 truncated | analyzed — see `CAP-047-FINDINGS.md`. **Integrity correction: `CAP-047-btsnoop_hci.log.last` is leftover pre-session (logging-setup) buffer content, not part of this session — only the two named logs above are this session's own evidence** (mirrors the `CAP-040`/`CAP-042` pattern, not `CAP-049`'s). **Trigger 3: clean, complete negative** — zero DLCI 0x0a-role payload across six bracketed charge-state transitions in two full, untruncated logs (raises the burst's "1 of N" denominator to at least 20 sessions checked). **Major bonus finding**: a dense video re-check (1fps + 4fps, cross-validated against the phone's own per-earbud charging-icon indicator) confirms no corrected (matching-slot) docking ever occurred in either recording, contrary to a maintainer recollection. **Further bonus findings**: `DECISIONS.md` ADR-024's dock-state byte reads "both docked" during a swapped-slot seating (confirms it doesn't check slot correctness); Recording 1's swapped dock never disconnects the ACL link while both of Recording 2's swapped docks do, despite an identical dock-sensor reading — an unreconciled tension with ADR-016, flagged for maintainer review; two new ADR-024 counter-example readings found, alongside `CAP-048-FINDINGS.md`'s existing ones | | `CAP-048` | 2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | AD (repeat) | `OBS-004` | Repeat of `CAP-037`'s dock-state anomaly with an open ACL, with continuous physical dock-state video | `captures/CAP-048-2026-09-12_17-41-41_17-52-44-Group_AD/CAP-048-btsnoop_hci.log` (`.log.last` present, determined leftover pre-session content, not used) | same file | analyzed — see `CAP-048-FINDINGS.md`. **`CAP-037`'s own flagged anomaly directly resolved**: a mid-connection `Settable-toggles` flip with no preceding Get is confirmed, via video at the exact wire timestamp, to be a genuine real-time docking action — consistent with ADR-016 (disconnect follows once the second bud docks too). 13/13 zero-miss ADR-022 replication. **A genuine, video-confirmed counter-example to a simple ADR-024 reading**: two fresh reconnects report "docked" while the case is visibly empty on video — reported plainly, not reconciled away. The draft's own claimed 25-reconnect rapid-alternation table does not survive verification — actual cadence was much slower/less regular. Bonus: a connection-retry burst — six page timeouts (status `0x04`) and one successful connect, corrected 2026-10-03 (was "7-event") — plausibly linked to a closed case | | `CAP-049` | 2026-09-12 | Pixel 7a | 17 | release_5.203 (⚪ assumed) | 1.0.955078536 | AF (repeat) | `OBS-006` | Repeat of `CAP-039`'s unexplained disconnect/reconnect cycling, with continuous phone-screen recording | `captures/CAP-049-2026-09-12_18-13-54_18-21-49-Group_AF/CAP-049-btsnoop_hci-combined.log` (genuine same-session `.log.last` rotation, safely combined per this session's own Step G method — see `CAP-049-FINDINGS.md` §0; video renamed from `-recordings.mp4`) | combined file (main+`.log.last`) | analyzed — see `CAP-049-FINDINGS.md`. **Clean negative: the cycling did not reproduce** — after one deliberate reconnect, the classic connection stays open and stable for the rest of the ~9-minute session (RFCOMM traffic still flowing near the log's own end). `OBS-006`'s Set-vs-Get `Settable-toggles` comparison confirmed consistent (both `0xe8`), a further same-session ADR-024 confirmation | @@ -2281,25 +2268,26 @@ is how the 2026-08-18 `CAP-005`/`CAP-007`/`CAP-010` ID-reuse incident (see | `CAP-062` | 2026-09-25 | Pixel 9a | 17 (`CP2A.260805.005`) | `release_5.203` | OpenControl, `ai-sessions/0046` code (`7498cbc`, identified by log wording; hash not recorded) | AX (new) | `PAIR-001`, `PAIR-003`, `CASE-004`–`CASE-006`, `BATT-004`, `ANC-001`–`004`, `FIND-001`/`002`, `EQS-001`, `EQP-*` (Clarity) | `APP_TESTPLAN.md` run of the `0046` build — R1–R7 confirmed; ANC `Set` NAKed (`0x02`) iff the Buds report no settable mode (not worn, ADR-024 Update); the Buds close DLCI 0x02 on every wear/dock change (ADR-044); per-bud fields of `SubscribeRuntimeInfo` (🟢, `PROTOCOL.md` §4.3 Option F); I4 ring notice defect; `CAP-062-FINDINGS.md` | — (no bugreport archive; raw, untruncated `btsnoop_hci.log`, `CAP-062-EVENT-NOTES.md`) | `CAP-062-btsnoop_hci.log` | analyzed | | `CAP-063` | 2026-09-27 | Pixel 9a | 17 (`CP2A.260805.005`) | `release_5.203` | OpenControl, `ai-sessions/0052` build (`94e4fb1` or later, identified by log wording; hash not recorded) | AY (new) | `PAIR-001`, `PAIR-003`, `CASE-003`–`CASE-006`, `BATT-004`, `ANC-001`–`003`, `FIND-001`, `EQP-002`–`004`, `EQS-001`, `EQS-005`, `CONV-001`/`002`, `TOUCH-001`/`002`, `HOLD-001`–`004`, `AUDIO-001`, `AUDIO-003`, `INEAR-004` | Hardware re-test of the `0048`/`0052` builds — nothing refuted; ADR-044 re-open held in all 14 session ends; settings 90 reads / 46 writes OK, byte-identical to the fixtures; a second `SubscribeRuntimeInfo` is answered (8/8, 🟢); a docked bud ACKs a ring; ANC stays disabled after re-wearing (app defect); Digital assistant: nothing on the wire (GSND closed); `CAP-063-FINDINGS.md` | — (no bugreport archive; raw, untruncated `btsnoop_hci.log`, plus `.log.last`; debug export cut at 64 KiB) | `CAP-063-btsnoop_hci.log` | analyzed | | `CAP-064` | 2026-10-01 | Pixel 9a | 17 (`CP3A.260905.009`) | `release_5.203` | OpenControl, ≥ the `ai-sessions/0057` build with the `0056` writes, installed 16 min after `b65085a` (hash not recorded) | AZ (new) | `ANC-001`, `ANC-003`, `INEAR-001`–`004`, `HOLD-001`–`005`, `CASE-004`–`006`, `BATT-004`, `AUDIO-003`, `PAIR-003` (`ANC-002`/`004` only via long presses) | Wear and settings re-test of the `0054`/`0056`/`0059` builds, ears on film: lid-open with docked buds brings no link (the auto-connect observation qualified), 4/4 LINK_BACK re-opens; AY-3 one worn ⇒ `e8`; `e8` with none worn (in-ear off; 28 s with it on); AZ-3 never `40`; I-1c tile NAK on a stale `e8`; `CAP-064-EVENT-NOTES.md`, `CAP-064-FINDINGS.md` (`ai-sessions/0060`) | — (no bugreport archive; the raw `btsnoop_hci.log`) | `CAP-064-btsnoop_hci.log` | analyzed | -| `CAP-065` | 2026-10-01 | Pixel 9a | 17 (`CP3A.260905.009`) | `release_5.203` | OpenControl, the same installation as `CAP-064` (process continuous, no reinstall; contains `b65085a`'s EQ note) | BA (new) | `ANC-001`–`004` (tile; `ANC-002` also in the app), `PAIR-003`, `CASE-004`–`006`, `BATT-004`, `EQS-001`, `AUDIO-003`, `HOLD-001`, `INEAR-001` (reads); not exercised: `PAIR-001`, `EQP-002`, `AUDIO-001` | The `0059` fixes (EQ note on film; tile subtitle not on film — compact tile), Settable with the ears on film (28 × `00` none worn, `e8` worn; ADR-049 Update), L-1 one bud out ⇒ Left 19 / Right 21 (7/7), pulls exactly per tab, a claim closed under an ACKed `Set`; robustness steps after K4 not done; `CAP-065-EVENT-NOTES.md`, `CAP-065-FINDINGS.md` (`ai-sessions/0061`) | — (no bugreport archive; the raw `btsnoop_hci.log`) | `CAP-065-btsnoop_hci.log` | analyzed | +| `CAP-065` | 2026-10-01 | Pixel 9a | 17 (`CP3A.260905.009`) | `release_5.203` | OpenControl, the same installation as `CAP-064` (process continuous, no reinstall; contains `b65085a`'s EQ note) | BA (new) | `ANC-001`–`004` (tile; `ANC-002` also in the app), `PAIR-003`, `CASE-004`–`006`, `BATT-004`, `EQS-001`, `AUDIO-003`, `HOLD-001`, `INEAR-001` (reads); not exercised: `PAIR-001`, `EQP-002`, `AUDIO-001` | The `0059` fixes (EQ note on film; tile subtitle not on film — compact tile), Settable with the ears on film (28 × `00` none worn, `e8` worn; ADR-049 Update), L-1 one bud out ⇒ Left 19 / Right 21 (8/8), pulls exactly per tab, a claim closed under an ACKed `Set`; robustness steps after K4 not done; `CAP-065-EVENT-NOTES.md`, `CAP-065-FINDINGS.md` (`ai-sessions/0061`) | — (no bugreport archive; the raw `btsnoop_hci.log`) | `CAP-065-btsnoop_hci.log` | analyzed | | `CAP-066` | 2026-10-01 | Pixel 9a | 17 (`CP3A.260905.009`) | `release_5.203` | OpenControl build `043a09b` (app code `ad0c4ba`, the `ai-sessions/0062` build; Info tab on film) | BB (new) | `ANC-001`, `ANC-003`, `ANC-004`, `INEAR-002`–`004`, `CASE-004`–`006`, `BATT-004`, `AUDIO-003`, `PAIR-003`; not exercised: `ANC-002`, `PAIR-001` | First hardware run of the `0062` build: F-1/F-2 verified (23/23 `08 11` first, 4 ACKed `Set`s, 0 NAK), F-3 not exercised; Settable `00` none worn ×28 / `e8` worn ×48, straight from the case `00` 2.6–175 s; L-1 Right out on 21 ⇒ `DISC` + 19 (2/2); settings menu, Info, dark mode On/System; rotation resets the tab; K1–K3 (K3 `0x08`); BB-10, BB-12, L3, K5, A5, (E), B4, Z1 not done; `CAP-066-EVENT-NOTES.md`, `CAP-066-FINDINGS.md` (`ai-sessions/0063`) | — (no bugreport archive; the raw logs) | `CAP-066-btsnoop_hci.log.last` (first part), `CAP-066-btsnoop_hci.log` (after the Bluetooth off/on) | analyzed | | `CAP-067` | 2026-10-03 | Pixel 9a (secondary user without Google Play) | 17 (`CP3A.260905.009`) | `release_5.203` | OpenControl for Pixel Buds Pro 2 **1.0.0**, build `8d8af4b`, release-signed (Info tab on film) | BC (new) | `AUDIO-003`, `INEAR-002`–`004` (`004`: bud not identifiable), `PAIR-003`, `BATT-004`, `PAIR-001` | The 1.0.0 release test without Play services: 6 app claims, 0 Play-services claims; Definition of done 1–3 ticked (`PROJECT.md`; the ANC change from the maintainer's own test — no `08 12` in this run); F-1 14/14; Info, licence, links, ADR-051 on film; Right 4 = `17:7` reached; L-1 21 → 19 (3rd) and 19 → 21 (bud not identifiable); L3 export with Debug mode off; F-3 not verifiable (no export after the Bluetooth off/on); A5 revocation ended the process; (E) + Z1 (SSP); BC-12, B4 not done. `CAP-067-FINDINGS.md` | — (no bugreport archive; the raw logs) | `CAP-067-btsnoop_hci.log.last` (first part), `CAP-067-btsnoop_hci.log` (after the Bluetooth off/on) | analyzed | | `CAP-068` | 2026-10-04 | Pixel 9a (secondary user 10 without Google Play) | 17 (`CP3A.260905.009`) | `release_5.203` | OpenControl for Pixel Buds Pro 2 **1.0.1**, build `e1fc886`, release-signed (Info tab on film, no -dirty) | BD (new) | `ANC-001`–`ANC-004` (`003` tile only), `ANC-006`, `FIND-001`, `FIND-005` (not observable), `PAIR-003`, `BATT-004`, `EQS-001`, `INEAR-004` | The 1.0.1 release test without Play services: 12 ANC `Set` → 12 ACK (tab and tile, on film), a `Set` for the current mode ACKed, Bluetooth off/on with exports (F-3 verified), Use different Buds + picker, page timeout with the new text; 0 Play-services claims; force-stop in no file; `CAP-068-FINDINGS.md` | — (no bugreport archive; the raw logs) | `CAP-068-btsnoop_hci.log.last` (before the film), `CAP-068-btsnoop_hci2.log.last` (07:23–07:34; `CAP-068-btsnoop_hci.log` is its prefix), `CAP-068-btsnoop_hci2.log` (after the Bluetooth off/on) | analyzed | | `CAP-069` | 2026-10-04 | Pixel 7a | 17 (build not on film) | `release_5.203` | official Pixel Buds app **1.0.990706425** (updated 2026-09-30, on film) | BE (new) | `HEAD-001`, `MULTI-001`, `HOLD-003`, `HOLD-004`, `HOLD-005`, `ANC-001`–`ANC-004`, `ANC-006`, `EQP-001`, `EQS-004`, `INEAR-002`–`INEAR-004`, `CASE-004`, `CASE-005`, `FIND-001` (via Find Hub, failed), `FIND-005` (not observable), `PAIR-003`, `BATT-004` | Head gestures (field 29: 1 off / 2 on, 6 filmed writes) and Multipoint (field 11 + SASS bit 2, 4 filmed writes) — ADR-052/053; assistant sessions open with GSND `01 09 … 0a 01 03`; no `Set` for the selected ANC mode (0/3); Default = 0.0 × 5; wear sequence vs GSND Code `0x05`; no Find device in the app; new service `0xbf6c9399` and JitterBuffer on the wire; `CAP-069-FINDINGS.md` | — (no bugreport archive; the raw log) | `CAP-069-btsnoop_hci.log` (from the Bluetooth switch-on; no `.log.last`) | analyzed | | `CAP-070` | 2026-10-07 | Pixel 9a (secondary user 10 without Google Play) | 17 (`CP3A.260905.009`) | `release_5.203` | OpenControl for Pixel Buds Pro 2 **1.1.0**, build `0323849`, release-signed (Info tab on film, no -dirty); a first install in user 10 (🟡), not an update | BF (new) | `MULTI-001`, `HEAD-001`, `CASE-001`, `CASE-002` (writes; the case-sound observation not recorded), `AUDIO-002` (writes; audibility not recorded), `AUDIO-003`, `INEAR-005` (wear not on film), `INEAR-002`–`INEAR-004` (ear changes not identifiable), `CASE-004`, `CASE-005`, `EQS-001`, `PAIR-003`, `BATT-004` | The 1.1.0 release test without Play services (`ai-sessions/0075`): 22 sessions each read exactly the twelve settings; 89 of 89 writes byte-identical to the expected frames and ACKed — incl. the first captures of 11 and 29 on channel 19 and `15:1` on channel 21; L-1: the Left taken off on 19 ⇒ `DISC` + 21; Bluetooth off/on + a new process; "—" on film; no audio, no screen recording, no `uiautomator` dumps; release verdict: release `0323849`. `CAP-070-FINDINGS.md` | — (no bugreport archive; the raw logs) | `CAP-070-btsnoop_hci1.log.last` (the unfilmed rehearsal, 05:44–06:10), `CAP-070-btsnoop_hci2.log.last` (06:10–06:37; `CAP-070-btsnoop_hci1.log` is its prefix), `CAP-070-btsnoop_hci2.log` (after the Bluetooth off/on) | analyzed | | `CAP-071` | 2026-10-08 | Pixel 9a (secondary user 10 without Google Play) | 17 (`CP3A.260905.009`) | `release_5.203` | OpenControl for Pixel Buds Pro 2 **1.1.1**, build `86a6fb3`, release-signed (Info tab on film, no -dirty; installed APK SHA-256 = `dist/1.1.1`), an update over 1.1.0 (`0323849`, itself added to user 10 at 17:40) | BG (new) | `PAIR-003`, `BATT-004`, `ANC-001`–`ANC-004`, `EQP-001` (preset Balanced), `EQS-001`, `AUDIO-001`–`AUDIO-003`, `CONV-001`, `HOLD-005`, `HEAD-001`, `MULTI-001`, `CASE-001`, `CASE-002` (writes; no chime recorded), `CASE-004`, `CASE-005`, `FIND-001`, `FIND-002`; `INEAR-004` not identifiable, `INEAR-005` not run | The 1.1.1 release test (`ai-sessions/0080`): the update kept data, dark mode and Debug mode; 11 sessions read the twelve settings in order; 24 writes, 9 ANC `Set`s (during playback) and 4 Ring/Stop byte-identical and answered; the read-back after a reconnect; 9 session ends recovered; no behaviour change from the toolchain; film with sound (the Find ring; no case chime), no screen recording, no film 2 (L-1, C12, S12, T11 open); release verdict: release `86a6fb3`. `CAP-071-FINDINGS.md` | — (two `bugreportz` archives, not kept; the raw logs) | `CAP-071-btsnoop_hci1.log.last` (17:37–17:51, before the film), `CAP-071-btsnoop_hci2.log.last` (17:51–18:26; `CAP-071-btsnoop_hci1.log` is its prefix), `CAP-071-btsnoop_hci2.log` (after the Bluetooth off/on) | analyzed | -| `CAP-072` | 2026-10-09 | Pixel 9a (secondary user 10 without Google Play) | 17 (`CP3A.261005.005`) | `release_5.203` | OpenControl for Pixel Buds Pro 2 **1.2.0**, build `ec6d163`, release-signed (Info tab on film, no -dirty), an update over a 1.1.1 (`86a6fb3`) reinstalled at 17:24 | BH (new) | `PAIR-003`, `BATT-004`, `FW-003`, `INEAR-006`, `INEAR-005`, `INEAR-004`, `ANC-001`–`ANC-004`, `CASE-001`, `CASE-002`, `CONV-001`, `EQP-001`, `EQS-001`, `AUDIO-001`, `AUDIO-003` (`AUDIO-002` not done), `HOLD-005`, `HEAD-001`, `MULTI-001`, `CASE-004`, `CASE-005`, `FIND-001`, `FIND-002` | The 1.2.0 release test (`ai-sessions/0083`): thirteen requests per session incl. one `GetHardwareInfo` (30 sent, 29 answered, byte-identical to `CAP-036` 1415 / `CAP-024` 801); serials on the Info tab Case/Right/Left; 60 writes, 5 ANC `Set`, 4 Ring/Stop answered; worn line per ADR-059 (`e8` on a table at 42 s); L-1 4/4 and "only the Right out ⇒ 21" 6/6; press-and-holds not shown (no claim open, ADR-032) → verdict **fix first** (ADR-061); case chime bud-dependent (🟡); a call over eSCO; 0 Play-services claims; film 2 not made (folded into the rebuild's run); `CAP-072-FINDINGS.md` | — (two `bugreportz` archives, not kept; the raw logs) | `CAP-072-btsnoop_hci1.log.last` (before the run), `CAP-072-btsnoop_hci2.log.last` (15:40–18:18; `…hci1.log` a prefix of it), `CAP-072-btsnoop_hci2.log` (18:18–18:23); four exports, the app logcat, the system-log extract; films blurred at the privacy ranges | analyzed — film 2 not made | -| `CAP-073` | 2026-10-10 | Pixel 9a (secondary user 10 without Google Play) | 17 (`CP3A.261005.005`) | `release_5.203` | OpenControl for Pixel Buds Pro 2 **1.2.0** rebuilt, build `5b4d5db`, release-signed (Info tab on film, no -dirty; installed APK SHA-256 = B1's), an update over the `ec6d163` 1.2.0 | BI (new) | `PAIR-003`, `BATT-004`, `FW-003`, `ANC-001`–`ANC-004`, `TOUCH-007`, `CASE-004`, `CASE-005`, `INEAR-006`, `AUDIO-003`; `AUDIO-002`, `INEAR-004` not run | The re-test of the rebuilt 1.2.0 (`ai-sessions/0085`): 11 sessions with the thirteen requests byte-identical to `CAP-072`; the ADR-061 hold held DLCI 0x04 31 s on the noise-control tab, four unprovoked `Notify` shown without a tap, four taps on the held channel (no `SABM`), released 1.509 s after leaving the tab; balance one write per release, "Right 4" on the third; 6 writes and 4 `Set`s answered; no camera, no audio; verdict OK — ready to release | — (one `bugreportz` archive, not kept; the raw logs) | `CAP-073-btsnoop_hci.log` (08:22:51–08:35:07, the run), `CAP-073-btsnoop_hci.log.last` (2026-10-09 18:32 – 2026-10-10 08:22, before the run, the Owner user); one export, the app logcat, `CAP-073-logcat-extract.txt`, the shell log, `CAP-073-screen.mp4` (blurred) | analyzed | +| `CAP-072` | 2026-10-09 | Pixel 9a (secondary user 10 without Google Play) | 17 (`CP3A.261005.005`) | `release_5.203` | OpenControl for Pixel Buds Pro 2 **1.2.0**, build `ec6d163`, release-signed (Info tab on film, no -dirty), an update over a 1.1.1 (`86a6fb3`) reinstalled at 17:24 | BH (new) | `PAIR-003`, `BATT-004`, `FW-003`, `INEAR-006`, `INEAR-005`, `INEAR-004`, `ANC-001`–`ANC-004`, `CASE-001`, `CASE-002`, `CONV-001`, `EQP-001`, `EQS-001`, `AUDIO-001`, `AUDIO-003` (`AUDIO-002` not done), `HOLD-005`, `HEAD-001`, `MULTI-001`, `CASE-004`, `CASE-005`, `FIND-001`, `FIND-002` | The 1.2.0 release test (`ai-sessions/0083`): fifteen requests per session incl. one `GetHardwareInfo` (30 sent, 29 answered, byte-identical to `CAP-036` 1415 / `CAP-024` 801); serials on the Info tab Case/Right/Left; 60 writes, 5 ANC `Set`, 4 Ring/Stop answered; worn line per ADR-059 (`e8` on a table at 42 s); L-1 4/4 and "only the Right out ⇒ 21" 6/6; press-and-holds not shown (no claim open, ADR-032) → verdict **fix first** (ADR-061); case chime bud-dependent (🟡); a call over eSCO; 0 Play-services claims; film 2 not made (folded into the rebuild's run); `CAP-072-FINDINGS.md` | — (two `bugreportz` archives, not kept; the raw logs) | `CAP-072-btsnoop_hci1.log.last` (before the run), `CAP-072-btsnoop_hci2.log.last` (15:40–18:18; `…hci1.log` a prefix of it), `CAP-072-btsnoop_hci2.log` (18:18–18:23); four exports, the app logcat, the system-log extract; films blurred at the privacy ranges | analyzed — film 2 not made | +| `CAP-073` | 2026-10-10 | Pixel 9a (secondary user 10 without Google Play) | 17 (`CP3A.261005.005`) | `release_5.203` | OpenControl for Pixel Buds Pro 2 **1.2.0** rebuilt, build `5b4d5db`, release-signed (Info tab on film, no -dirty; installed APK SHA-256 = B1's), an update over the `ec6d163` 1.2.0 | BI (new) | `PAIR-003`, `BATT-004`, `FW-003`, `ANC-001`–`ANC-004`, `TOUCH-007`, `CASE-004`, `CASE-005`, `INEAR-006`, `AUDIO-003`; `AUDIO-002`, `INEAR-004` not run | The re-test of the rebuilt 1.2.0 (`ai-sessions/0085`): 11 sessions with the fifteen requests byte-identical to `CAP-072`; the ADR-061 hold held DLCI 0x04 31 s on the noise-control tab, four unprovoked `Notify` shown without a tap, four taps on the held channel (no `SABM`), released 1.509 s after leaving the tab; balance one write per release, "Right 4" on the third; 6 writes and 4 `Set`s answered; no camera, no audio; verdict OK — ready to release | — (one `bugreportz` archive, not kept; the raw logs) | `CAP-073-btsnoop_hci.log` (08:22:51–08:35:07, the run), `CAP-073-btsnoop_hci.log.last` (2026-10-09 18:32 – 2026-10-10 08:22, before the run, the Owner user); one export, the app logcat, `CAP-073-logcat-extract.txt`, the shell log, `CAP-073-screen.mp4` (blurred) | analyzed | | `CAP-074` | 2026-10-12 | Pixel 9a (**the Owner user 0 with Google Play services**) | 17 (`CP3A.261005.005`) | `release_5.203` | OpenControl for Pixel Buds Pro 2 **1.2.0**, build `5b4d5db`, release-signed (the published release; Info tab on film, no -dirty; the installed APK not pulled) | BJ (new) | `PAIR-003`, `ANC-001`–`ANC-004`, `TOUCH-007`, `BATT-004`, `FW-003`; `AUDIO-002` not run | The maintainer's ad-hoc run in the user with Play services (`ai-sessions/0086`): nine Disconnect/Connect pairs, after each the ANC tab and press-and-holds; 10 sessions byte-identical to `CAP-073`; **every ADR-061 hold cut 4.7–5.1 s after its `SABM` by Play services' Fast Pair recovery** (5.2–5.7 s after it lost the channel, 20 of 20) — 9 holds shown, 35 reached Play services: the cause of the first-hold-only pattern (`CAP-074-FINDINGS.md` §4, §8); fix: a Settings switch, the maintainer's choice | — (`bugreportz` by the maintainer, not kept; the raw logs) | `CAP-074-btsnoop_hci.log` (2026-10-11 19:50 – 2026-10-12 06:52: the run and a 06:30–06:31 window of an earlier process), `CAP-074-btsnoop_hci.log.last` (2026-10-11); one export (06:38–07:03), the app logcat, `CAP-074-logcat-extract.txt`, `CAP-074-recording.mp4` (with audio, as is), `CAP-074-sha256sum.txt` | analyzed | | `CAP-075` | 2026-10-12 (set 1, 17:22:31–17:28:43 on film) and 2026-10-13 (set 2, 06:29:04–06:34:32 on film) | Pixel 9a (**set 1: the secondary user without Google Play; set 2: the Owner user 0 with Google Play services**) | 17 (`CP3A.261005.005` in `CAP-074`; not re-read) | `release_5.203` | OpenControl for Pixel Buds Pro 2 **1.2.1** (versionCode 10201), build `31ee183`, release-signed (Info tab on film in both users, no -dirty; the installed APK not pulled), updated over 1.2.0 `5b4d5db` on film | BK (new) | `PAIR-003`, `BATT-004`, `FW-003`, `ANC-001`–`ANC-004` (no mode tap: shown from `Notify` only), `TOUCH-007`; `AUDIO-002`, `CASE-004`/`005` not observed | The hardware run of the "Noise-control channel" switch (`DECISIONS.md` ADR-062): without Play both positions identical, 41 of 41 holds shown, no foreign close; with Play "Show a hint" = the hint with the cut's time, 0 of 9 holds shown; "Take it back" = three chains × three re-claims (3/6/12 s scheduled, 4.0–4.8 s on the wire), every re-claim colliding with Play's retry, every hold cut 5.3–5.8 s after Play's loss, 43 % coverage then nothing, 8 of 19 holds shown; the cut's stack path 15 of 15 in the system log (`CAP-075-FINDINGS.md` §4, §5, §8); verdict no heavy issue — 1.2.1 released as built; proposals P1/P2/P4 to the next FEATURE prompt | — (`bugreportz` 06:35:41 and the GrapheneOS log viewer's full log, both local only) | `CAP-075-btsnoop_hci1.log.last` + `CAP-075-btsnoop_hci1.log` (set 1, split at the Bluetooth off/on 17:23:04/08), `CAP-075-btsnoop_hci2.log.last` (2026-10-12 18:13 – 2026-10-13 06:29, incl. the Owner's unfilmed evening session) + `CAP-075-btsnoop_hci2.log` (06:29–06:36); two exports, two app logcats, `CAP-075-logcat-extract.txt` (set 2's system log, redacted), `CAP-075-recording1.mp4` / `CAP-075-recording2.mp4` (with audio, as is), `CAP-075-sha256sum.txt` | analyzed | | `CAP-076` | 2026-10-15 (part 1, 06:35:26–06:51:47 on film; part 2, 07:02:31–07:08:20 on film) | Pixel 9a (**part 1: the Owner user 0 with Google Play services; part 2: the secondary user 10 without**) | 17 (`CP3A.261005.005`, film 1 s595) | `release_5.203` | OpenControl for Pixel Buds Pro 2 **1.3.0** (versionCode 10300), build `ff27ee1`, release-signed, installed over 1.2.2 `bfd7891` on film 1; the installed APK pulled: SHA-256 = the tested build's | BL (new) | `PAIR-003`, `BATT-004`, `FW-003`, `ANC-001`–`ANC-004`, `TOUCH-007`, `CASE-004`/`005`, `INEAR-001`–`003`, `FIND-001`; BL-10/15/18/21/24 not run | The hardware run of ADR-064/ADR-065: the two new requests once per MAESTRO session (12 of 12, byte-identical); holds on the noise-control tab shown within 0.1 s — 29 of 29 with Play holding DLCI 0x04, 12 of 12 without; 15 own `Set`s ACKed and classed as the app's; nothing held or re-claimed; every change pushed twice (channels 21 and 19) with both buds out; the re-open's "changed since" cause overwritten by the equal `4:13` answer; one tile tap lost silently to Play; verdict release as built with two Known issues (`CAP-076-FINDINGS.md`, `ai-sessions/0092`) | — (the GrapheneOS log viewer's system log and `adb logcat -b all` of part 1, both local only) | `CAP-076-btsnoop_hci1.log.last` + `CAP-076-btsnoop_hci1.log` (part 1, split at the Bluetooth off/on 06:35:34/06:36:10), `CAP-076-btsnoop_hci2.log.last` + `CAP-076-btsnoop_hci2.log` (part 2, split at 07:02:39/44); two exports, two app logcats, `CAP-076-logcat-extract.txt` (part 1, redacted), `CAP-076-adb-shell.log`, `CAP-076-adb-checks-0092.log`, `CAP-076-recording1.mp4` / `CAP-076-recording2.mp4` | analyzed | +| `CAP-077` | TBD | Pixel 9a (**the secondary user 10 without Google Play**) | 17 | `release_5.203` expected | OpenControl for Pixel Buds Pro 2 **1.3.1** (versionCode 10301), the release APK of `scripts/release.sh 1.3.1`, installed over 1.3.0 `ff27ee1` — build hash on the Info tab on film, the installed APK pulled | BM (new) | `PAIR-003`, `FW-003`, `ANC-001`…`ANC-004`, `TOUCH-007` | The hardware run of the 1.3.1 fixes (`ai-sessions/0094`): the kept "Changed since the app last saw it" across the equal `ReadSetting 4:13` answer, tile taps right after a Connect never silent, a hold during a Connect, "Use different Buds" with its dialog and no connection by name, TalkBack on the switch rows and an EQ slider, one name per mode — nothing new on the wire | TBD | TBD | planned | **Column notes:** - **Phone** — `Pixel 7a` (primary, official app) or `Pixel 9a` (secondary, GrapheneOS), per the two-device setup described at the top of this document. -- **Group(s)** — the letter(s) from §4 (Z, A–Q, R, S — Z, R, and S are +- **Group(s)** — the letter(s) from §4 (Z, A–Z, then AA–BL as of 2026-10-15 — Z, R, and S are special-purpose groups that intentionally sort outside the A–Q run-through: Z is pipeline-validation, always done first; R is the occasional forced-GATT-discovery procedure; S is the occasional GMS-disabled/no-app @@ -2316,9 +2304,7 @@ is how the 2026-08-18 `CAP-005`/`CAP-007`/`CAP-010` ID-reuse incident (see run session, per its own capture-scenario paragraph in §4; every other column stays `TBD`/`—` until the session actually happens), `captured` (extracted, not yet reviewed), `analyzed` (reviewed in Wireshark per §5, - findings recorded per §8), `promoted` (a finding from this capture has been - written into `PROTOCOL.md` with a `[VERIFIED-LOCAL]` - tag), `discarded` (unusable — note why, e.g. in an extra remarks column if + findings recorded per §8) — `promoted` is retired (2026-10-15, `ai-sessions/0094`: no row used it; a capture's promotions are in its FINDINGS and `PROTOCOL.md` §8), `discarded` (unusable — note why, e.g. in an extra remarks column if needed). `withdrawn` (planned and never run because a later finding made it unnecessary — the row and the number stay; added to this legend 2026-10-03). - Reference a capture from `PROTOCOL.md` by its ID (e.g. diff --git a/CHANGELOG.md b/CHANGELOG.md index 31f9cebb..74eecb3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,34 @@ build commit `bfd7891`, `ai-sessions/0089`; tested by the maintainer on the phon ## [Unreleased] +## [1.3.1] + +Prepared in `ai-sessions/0094` (2026-10-15) from the audit `ai-sessions/0093` and the `CAP-076` findings (the maintainer's choice in chat 2026-10-15, "1.3.1 met +APP-01…06"). Nothing new on the wire: the app's requests are 1.3.0's. Not yet on hardware: the run is `CAP-077` (Group BM, planned). + +### Fixed + +- **"Use different Buds" now really forgets the Buds.** Before, the app could still connect to the same Buds by their name right afterwards; now it asks you to + pair (*Pair a device*) until you choose Buds in Android's picker. It also asks first, with **Cancel** and **Forget these Buds**. +- **After a reconnect, a mode that changed while the app was not connected keeps its line** "Changed since the app last saw it" (1.3.0's known issue, `CAP-076`). +- **A tap on the tile right after a Connect no longer does nothing without a word** (`CAP-076`): it changes the mode or says why not. +- A change the Buds push while the app is reading a setting no longer counts as the answer to that read; the read waits for its own answer. +- A settings file that cannot be read now gives the default settings instead of stopping the app's dark-mode and Debug-mode switches. + +### Changed + +- **One name for each noise-control mode everywhere**: "Noise cancellation", "Off", "Adaptive", "Transparency" — on the buttons, the (i), the Quick Settings tile + and the notification ("Connected — noise control: …"). The (i) says "Connection: connected" instead of a class name; the presets read "Heavy bass", not "HEAVY + BASS". +- **TalkBack** reads each switch with its name ("Earbuds replaced, switch, on"), each equalizer slider with its band and gain in dB, and each bottom tab once. + +### Known issues + +- As in 1.3.0: a tap on a noise-control mode claims the channel shared with Google Play services for about 1.5 seconds; on a phone with Play it may fail with a + message — tap again. +- As in 1.3.0: when the Buds close the session themselves, the mode shown can be a few seconds old until the re-open has read it again. +- Since 1.0.1: after you stop a ring by touching the bud, the app keeps saying it is ringing until you tap Stop. + ## [1.3.0] - 2026-10-15 Prepared in `ai-sessions/0091` (2026-10-14) after `ai-sessions/0090`'s cross-check of the official app (`DECISIONS.md` ADR-064, ADR-065): the Buds report every @@ -44,6 +72,7 @@ Connect, both byte-identical to the official app's. moment (about 5 seconds after a claim) — the tap then usually fails with a message (in `CAP-076` once without one, from the tile: nothing changed); tap again. - When the Buds close the session themselves (one bud out while both are worn — 12 of 77 such changes in the captures), the app re-opens it and reads the mode again; in between, the mode shown can be a few seconds old. + *(Corrected 2026-10-15, `ai-sessions/0094`: 12 of **89** Buds-side changes in the official app's captures, not "12 of 77" — `PROTOCOL.md` §4.1, Correction of 2026-10-15.)* - After a reconnect (or when the Buds re-open the session themselves), a mode that changed while the app was not connected shows as read: the (i) line "Changed since the app last saw it" does not appear (`CAP-076`). A fix follows. @@ -140,7 +169,7 @@ places, a held noise-control channel on its tab, a finer balance slider, and one their (i) adds "These settings live on the case and are read when the app connects." The requests are unchanged. - **Conversation detection** moved from Sound to **Controls** (its own card after In-ear detection, the same subtitle). Sound keeps Balance, Mono audio and Volume EQ. The request is unchanged. -- The app sends thirteen requests at connect instead of twelve settings reads plus the EQ read and the subscription: the one `GetHardwareInfo` above. +- The app sends thirteen requests at connect instead of twelve settings reads plus the EQ read and the subscription: the one `GetHardwareInfo` above. *(Corrected 2026-10-15, `ai-sessions/0094`, A93-CAP-01: fifteen requests at connect — thirteen `ReadSetting`s (the EQ's `4:16` and twelve settings), `SubscribeRuntimeInfo` and the new `GetHardwareInfo`; `CAP-073`, 11 of 11 sessions.)* ### Studied, not built @@ -173,7 +202,7 @@ AndroidX) so later work starts from a supported base. Prepared in `ai-sessions/0 9.3.3 with its built-in Kotlin, Kotlin 2.0.20 → 2.4.20, KSP → 2.3.12, Hilt 2.52 → 2.60.1, Compose BOM 2024.09.00 → 2026.09.00 (material3 1.4.0), AndroidX and coroutines to their current stable releases, JUnit 5 → 6.1.3, Robolectric 4.17; Kotest removed (never used). Compiled against API 37; `minSdk` and `targetSdk` stay 34 (`DECISIONS.md` ADR-029 Update). The app's permissions, components, texts and bytes on the wire are unchanged — same unit-test - counts (plus one new test for the settings-menu tab order), the same mutations caught; not yet hardware-tested (with the next release's run). + counts (plus one new test for the settings-menu tab order), the same mutations caught; not yet hardware-tested (with the next release's run). *(Hardware run `CAP-071`, `ai-sessions/0080`, 2026-10-08: passed — noted 2026-10-15, `ai-sessions/0094`, A93-HK-06.)* - CI: `actions/checkout` v7.0.1 in all four workflows, `setup-python` v7.0.0, `setup-gradle` v6.4.0. ### Known issues diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 58336e71..4e0fbfa1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,19 @@ # Contributing -This project is currently a solo reverse-engineering effort (see `TODO.md` for -status), but is open source and PRs are welcome. Before contributing, read -`AGENTS.md`, `PROJECT_RULES.md`, and `PROJECT.md` — the same rules that bind -AI agents working on this project bind human contributors too. +This project is maintained by one person: an Android app with public releases (see `README.md` and +`CHANGELOG.md`) built on a reverse-engineered protocol (`PROTOCOL.md`). It is open source and PRs are +welcome. Before contributing, read `AGENTS.md`, `PROJECT_RULES.md`, and `PROJECT.md` — the same rules that +bind AI agents working on this project bind human contributors too. + +## How a change gets in + +`main` accepts changes only through a pull request (`RELEASING.md`, "Repository rules"). Fork, work on a +branch, and open a pull request against `main`. Two CI jobs must be green: `.github/workflows/android.yml` +(build, unit tests, lint, and the no-`INTERNET` check on the source and merged manifests) for a change under +`android/`, and `.github/workflows/lint-docs.yml` (`python3 scripts/lint_docs.py`) for a Markdown, registry or +lint-script change — run both locally first (`cd android && ./gradlew assembleDebug testDebugUnitTest test lint`; +`python3 scripts/lint_docs.py`). The maintainer reviews and merges. *(Section added 2026-10-15, +`ai-sessions/0094` — `ai-sessions/0093` A93-GOV-09.)* ## Code contributions diff --git a/DECISIONS.md b/DECISIONS.md index a17a1eef..a5cb7e35 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -57,7 +57,7 @@ motivated this). ## ADR-002 — License: GNU AGPL-3.0 - **Date**: 2026-08-07 -- **Status**: Accepted +- **Status**: Accepted *(provenance, 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "ADR-herkomst": decided by the maintainer at project start; no chat record exists — `ai-sessions/0093` A93-DEC-04)* - **Context**: The project is open source and reconstructs a protocol through reverse engineering; the license needs to protect that reverse-engineered knowledge and any modified version of the app — including one deployed as a @@ -129,7 +129,7 @@ motivated this). ## ADR-004 — No dependency on Google Play Services or any network permission - **Date**: _(project start)_ -- **Status**: Accepted +- **Status**: Accepted *(provenance, 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "ADR-herkomst": decided by the maintainer at project start; no chat record exists — `ai-sessions/0093` A93-DEC-04)* - **Context**: The project's core motivation is an app that works fully offline and independent of Google Play Services, including on GrapheneOS where GMS may be absent or sandboxed. @@ -147,7 +147,7 @@ motivated this). ## ADR-005 — Device discovery via CompanionDeviceManager, no continuous BLE scanning - **Date**: _(project start)_ -- **Status**: Accepted +- **Status**: Accepted *(provenance, 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "ADR-herkomst": decided by the maintainer at project start; no chat record exists — `ai-sessions/0093` A93-DEC-04)* - **Context**: Continuous background BLE scanning is fingerprintable and conflicts with GrapheneOS's threat model, which the app targets as its primary reference OS. @@ -222,7 +222,7 @@ motivated this). ## ADR-007 — `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Groups are capture scenarios, not tests; `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` is the test/behavior catalog - **Date**: 2026-08-08 -- **Status**: Accepted +- **Status**: Accepted *(provenance, 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "ADR-herkomst": decided by the maintainer at project start; no chat record exists — `ai-sessions/0093` A93-DEC-04)* - **Context**: `CAPTURE_BLUETOOTH_HCI_SNOOP.md`'s Groups A–Q and `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s four action tables had grown to describe largely the same ~66–70 Buds actions/behaviors, but with different @@ -2697,7 +2697,7 @@ motivated this). ## ADR-061 — Hold the Message Stream claim while the noise-control tab is on screen - **Date**: 2026-10-10 -- **Status**: Accepted (maintainer, chat 2026-10-10, `ai-sessions/0083`) — built in the 1.2.0 rebuild (`ai-sessions/0084`). **Superseded by ADR-065 (2026-10-14, +- **Status**: Superseded by ADR-065 (2026-10-14) — was: Accepted (maintainer, chat 2026-10-10, `ai-sessions/0083`) — built in the 1.2.0 rebuild (`ai-sessions/0084`). **Superseded by ADR-065 (2026-10-14, `ai-sessions/0091`): the hold is removed in 1.3.0 — the mode comes from the Buds' settings stream on the app's own channel.** The text below is kept as written. - **Note on process**: drafted by an AI agent (`ai-sessions/0083`); the decision is the maintainer's, given in the chat of 2026-10-10 (`AskUserQuestion` "Hold result", option *"Fix first: hold 0x04 on ANC tab"*, and "Hold ADR", option *"Accept as ADR-061 now (Recommended)"*, with this text in the preview), per @@ -2733,7 +2733,7 @@ motivated this). ## ADR-062 — A user-selectable re-claim of the noise-control channel after a foreign close, with a back-off - **Date**: 2026-10-12 -- **Status**: Accepted (maintainer, chat 2026-10-12, `ai-sessions/0087`) — supersedes ADR-061's clause "the Buds (or another client) closing DLCI 0x04 ends it +- **Status**: Superseded by ADR-065 (2026-10-14) — was: Accepted (maintainer, chat 2026-10-12, `ai-sessions/0087`) — supersedes ADR-061's clause "the Buds (or another client) closing DLCI 0x04 ends it without a re-claim" for the switch's mode "take it back" only; ADR-061's hold and ADR-032's claim model stand. Built in 1.2.1 (`ai-sessions/0087`); hardware-verified by `CAP-075` (its Update of 2026-10-13). **Superseded by ADR-065 (2026-10-14, `ai-sessions/0091`), with its Updates: the re-claim, the setting, its card and "Take it back again" are removed in 1.3.0; a stored `hold_reclaim_mode` is ignored.** The text below is kept as written. @@ -2809,7 +2809,7 @@ motivated this). ## ADR-063 — The Settings card may say whether Google Play services is installed on this phone (information only) - **Date**: 2026-10-13 -- **Status**: Accepted (maintainer, chat 2026-10-13, `ai-sessions/0089`) — built in 1.2.2; never hardware-verified (1.2.2 was released without `CAP-076`). +- **Status**: Superseded by ADR-065 (2026-10-14) — was: Accepted (maintainer, chat 2026-10-13, `ai-sessions/0089`) — built in 1.2.2; never hardware-verified (1.2.2 was released without `CAP-076`). **Superseded by ADR-065 (2026-10-14, `ai-sessions/0091`): the Play-services line, `PlayServicesProbe` and the `` entry are removed in 1.3.0.** The text below is kept as written. - **Note on process**: drafted by an AI agent (`ai-sessions/0089`); the information line is the maintainer's choice in chat 2026-10-13 (`AskUserQuestion` "Play @@ -2838,7 +2838,7 @@ motivated this). ## ADR-064 — DLCI 0x02: `SubscribeToSettingsChanges` and `ReadSetting 4:13` — the noise-control mode on the app's own channel (read only) - **Date**: 2026-10-14 -- **Status**: Accepted (maintainer, chat 2026-10-14, `ai-sessions/0090`) — not yet built; its hardware run comes with the build +- **Status**: Accepted (maintainer, chat 2026-10-14, `ai-sessions/0090`) — not yet built; its hardware run comes with the build *(Status pointer, 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "ADR-teksten", A93-DEC-01: built in 1.3.0 (`ai-sessions/0091`), hardware run `CAP-076` (`ai-sessions/0092`), released 2026-10-15; items 2 and 6 amended by ADR-065)* - **Note on process**: drafted by an AI agent (`ai-sessions/0090`); the decision is the maintainer's, given in the chat of 2026-10-14 (`AskUserQuestion` "ADR-064", option *"Accept now, build next (Recommended)"*, with this text in the preview), per `AGENTS.md` §6. It rests on the `PROTOCOL.md` §4.1 Update of 2026-10-14 (setting 13 = the mode, 🟢), approved in the same chat. @@ -2851,6 +2851,9 @@ motivated this). DLCI 0x02 is the app's own session channel, never contended by another client; the Buds close it themselves on some wear changes (L-1, ADR-044). - **Options considered**: (a) keep the Message Stream as the only source (ADR-061/062); (b) this decision, read only; (c) (b) plus switching ANC with `WriteSetting 4:{13:n}` — not now: no capture holds such a write (0 in 96 logs). +- **Note (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "ADR-teksten", `ai-sessions/0093` A93-DEC-02):** the Context's "73 of 73 Buds-side … changes" counts the class defined as "pushed"; re-derived: + 75 of 89 Buds-side changes pushed within 1 s, 12 of 89 lost to the Buds' `DISC` of MAESTRO, 2 at re-opens (`PROTOCOL.md` §4.1, Correction of 2026-10-15). The decision + is unchanged — ADR-065 covers the `DISC` case with the re-open's `ReadSetting 4:13`. - **Decision**: 1. Once per MAESTRO session (Connect and every ADR-044 re-open), after `SubscribeRuntimeInfo`: one `SubscribeToSettingsChanges` REQUEST (empty payload, byte-identical to `CAP-051` 1177 on channel 21 — `7e 00 4b 03 10 15 1d ea 71 de 7d 5e 25 f5 ad 21 28 01 97 54 b0 7e` — or `CAP-056` 1451 on channel 19) diff --git a/DESKRESEARCH_FINDINGS.md b/DESKRESEARCH_FINDINGS.md index 7f1b8e08..b55c524c 100644 --- a/DESKRESEARCH_FINDINGS.md +++ b/DESKRESEARCH_FINDINGS.md @@ -488,13 +488,13 @@ re-pair). Every `0xe8` sample sits in a session where the buds are actively in u (`CAP-019`–`CAP-025`'s settings-toggle tests; `CAP-006`'s first, mid-ANC-test). **Revises `CAP-036-FINDINGS.md` §3/§11 and the previous entry's own over-generalization** ("this is a connect-time pattern") — it is not connect-time as such; 12 of 17 connect/reopen-time samples show -`0xe8`. The sharper reading: `Settable=0x00` correlates with the Buds being physically in/near the +`0xe8` *(14 of 19 — the count corrected above on 2026-10-03; this sentence corrected 2026-10-15, `ai-sessions/0094`)*. The sharper reading: `Settable=0x00` correlates with the Buds being physically in/near the case, independent of whether DLCI 0x04 just (re)opened. - **Promoted to:** `PROTOCOL.md` §4.1 (`Get ANC state` trigger-reliability — **promoted to 🟢 FACT, maintainer sign-off obtained 2026-09-04, `DECISIONS.md` ADR-022**); `PROTOCOL.md` §6's Settable-toggles item (corrected reading, in/near-case not connect-time); - `PROTOCOL.md` §4.3 (Option A GrapheneOS confirmation, Option E cross-mechanism note, cross-sync + `PROTOCOL.md` §4.3 (Option A GrapheneOS confirmation *(Option **C** — HFP — is meant: Result 1; corrected 2026-10-15, `ai-sessions/0094`)*, Option E cross-mechanism note, cross-sync caveat). ### 2026-09-05 — Video verification of the Settable-toggles "in-case vs. worn" hypothesis @@ -969,6 +969,9 @@ gestures"; the case is open and empty, and the buds are out of frame for the who - 🔴 OPEN: what the wearer did at those four moments is not on film (a press-and-hold on a bud is the candidate; press-and-hold was set to the digital assistant during this capture). The reading "Buds microphone audio to the phone for the assistant" stays 🟡. Step for it: `CAP-069` (Group BE). +- *Update (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "§6 en lead" — `ai-sessions/0093` lead L93-1):* the wire names the trigger's kind — each wave starts with + the Buds' GSND CONTROL `01 09 00 03 0a 01 03` 1–2 ms before its first DLCI 0x0a frame (2092, 2963, 3645, 4340), the assistant-session opener `CAP-069` + found at 3 of 3 filmed assistant sessions. 🟡 (strong): the four waves are assistant sessions (`PROTOCOL.md` §6, the DLCI 0x0a item's Update of 2026-10-15). Over every capture log (scratch script, `tshark` per log: DLCI 0x0a/0x0b payload frames grouped into waves at gaps > 2 s, and the opener on DLCI 0x08/0x09 within 0.2 s before each wave start): GSND AUDIO payload occurs only in `CAP-021` and `CAP-069`, and **8 of 8** waves start with the opener (`CAP-021` 2093/2964/3646/4341, `CAP-069` 3404/4085/4807/4829). **Proposals awaiting the maintainer (not applied as status changes):** (a) reword §4.3 Option E's "cross-confirms the Right value" to "equals the lower of the two bud levels (🟡)"; (b) add Code diff --git a/MAINTAINING_DOCS_SITE.md b/MAINTAINING_DOCS_SITE.md index ac31cfa2..79594e55 100644 --- a/MAINTAINING_DOCS_SITE.md +++ b/MAINTAINING_DOCS_SITE.md @@ -6,8 +6,9 @@ markdown from GitHub Pages live, on every page load. What you need to do after a ## You edited the content of an existing file -Nothing extra to do. Commit and push to `main` as normal — the live site reflects it within -seconds (no rebuild, no regeneration). +Nothing extra to do. Commit on a branch and open a pull request — `main` takes changes only by pull request (`RELEASING.md`, +"Repository rules"); once it is merged the live site reflects it within seconds (no rebuild, no regeneration). *(Flow updated 2026-10-15, +`ai-sessions/0094` — `ai-sessions/0093` A93-HK-04.)* ## You added a new capture, or renamed a capture folder (e.g. the `yyyy-MM-dd` placeholder ## became a real date after processing) @@ -34,8 +35,9 @@ git push ## Fully automatic (optional) `.github/workflows/update-docs-sidebar.yml` (added alongside this file) re-runs -`generate_sidebar.sh` and commits the result automatically on every push to `main` that touches -`captures/**` or any `*.md` file — so the capture-rename/add case above needs no manual step +`generate_sidebar.sh` on every push to `main` that touches +`captures/**` or any `*.md` file and, because `main` takes no direct push, force-pushes the result to the branch `bot/docs-sidebar` and opens a pull +request for the maintainer to merge (`bot/sitemap` likewise for the sitemap) — so the capture-rename/add case above needs no manual step either. New root-level docs still need the one-line addition to the script by hand, since the script can't guess which sidebar section a new doc belongs in. diff --git a/PROJECT.md b/PROJECT.md index ea6e269c..d06d18be 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -24,7 +24,7 @@ Buds app or Google Play Services. 5. **Implementation** — build the Android app in Kotlin, based on the protocol specification, following the guardrails in `AGENTS.md` and `PROJECT_RULES.md`. 6. **Test & validation** — functional testing against real hardware, preventing - regressions; see `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`. + regressions; see `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` (the protocol behaviours) and `APP_TESTPLAN.md` (the app's own hardware runs). 7. **Documentation** — keep every conclusion traceable and reproducible for future contributors, using each capture's `CAP-NNN-FINDINGS.md` (hypothesis → conclusion, next to the evidence it tests), `DECISIONS.md` (ADR-style @@ -36,7 +36,7 @@ To be finalized based on what is actually found in the protocol. Candidate features offered by the official app (it still needs to be verified which of these run over local BLE/RFCOMM versus over the cloud/a Google account): -> **Status (2026-09-24; updated 2026-10-06 for 1.1.0):** a tick means *implemented in the app*; nothing is claimed hardware-verified unless `ARCHITECTURE.md` §5a +> **Status (2026-09-24; updated 2026-10-15 for 1.3.0):** a tick means *implemented in the app*; nothing is claimed hardware-verified unless `ARCHITECTURE.md` §5a > says so. The authoritative per-feature state (and its unblocking ADR) is `ARCHITECTURE.md` §5a. - [x] Read battery status (left, right: DLCI 0x04, ADR-033; case and per-bud "charging in the case": DLCI 0x02 `SubscribeRuntimeInfo`, ADR-043 and its Update) @@ -133,22 +133,18 @@ Without Google Play Services installed, the app can: > *Row 2 updated 2026-10-04 (`ai-sessions/0070`, maintainer-approved in chat, `AskUserQuestion` "DoD text", option *"Approve as shown (Recommended)"*):* the ANC change was > "maintainer-attested, not captured" for 1.0.0; `CAP-068` (`CAP-068-FINDINGS.md` §2; "A" = `CAP-068-btsnoop_hci2.log.last`) replaces it with frames. -## Status after 1.0.x (added 2026-10-03, `ai-sessions/0069`; 1.1.0 added 2026-10-06, `ai-sessions/0074`) - -1.0.0 was published on 2026-10-03 (`RELEASING.md`, Release log); 1.0.1, a fix release prepared in `ai-sessions/0069`, was tested in `CAP-068` and released on 2026-10-04 (`ai-sessions/0070`). v1 contains: connection and Safe Mode, -noise control with a Quick Settings tile, the equalizer, battery (Left / Right / Case), Find My Buds (Left / Right), touch controls, press and hold and its mode -list, balance, mono audio, conversation detection and the in-ear detection setting. The next features the maintainer chose (chat 2026-10-03, "Features") reached -🟢 FACT and their ADRs in `CAP-069` (`ai-sessions/0071`): head gestures (`qhr` field 29, ADR-052) and Multipoint (field 11, ADR-053); the case sounds (27, 28, -ADR-054) and Volume EQ (15, ADR-055) followed on 2026-10-06. **1.1.0** (prepared in `ai-sessions/0074`, released 2026-10-07; 1.1.1 followed on 2026-10-08) builds all five switches and the -screen-reader text for "—"; its release run `CAP-070` (`ai-sessions/0075`, 2026-10-07) passed — every switch request on both control channels confirmed by the Buds, the three forms not seen before now on the wire; verdict: release build `0323849`. The Battery Notification on case-open was tested in `CAP-054`: no clear battery field -(ADR-006 Update of 2026-10-06). -**1.1.1** (`ai-sessions/0078`/`0079`: the same app built with the current toolchain) passed its release run `CAP-071` (`ai-sessions/0080`, 2026-10-08) in the -user without Play: installed over 1.1.0 it kept the data and settings, and every Connect read, write, ANC `Set` and Ring was byte-identical to 1.1.0's and answered; -verdict: release build `86a6fb3` (the maintainer, chat 2026-10-08). -**1.2.0** (prepared in `ai-sessions/0082`, 2026-10-09; published 2026-10-10, build `5b4d5db`): "Changed by the Buds" in the noise-control details, the component serial numbers on the -Info tab (ADR-058), the "probably worn" indicator on the battery card (ADR-059), the Case sounds switches moved to gear → Settings and Conversation detection -to Controls; volume-level notifications studied, not built (a draft ADR-060, a 1.3.0 candidate). Its hardware run `CAP-072` (`ai-sessions/0083`, 2026-10-09) found no heavy defect — every request answered and byte-identical, the serials and the worn line as specified — but showed that a press-and-hold is never shown (no claim open between actions, ADR-032); verdict **fix first** (the maintainer, chat 2026-10-10): ADR-061 holds the noise-control channel while that tab is on screen, then a short re-test. **Rebuilt** as the same version in `ai-sessions/0084` (2026-10-10): the hold built (`setAncTabShown`), the serial fixtures from OpenControl's own `CAP-072` exchange, a finer balance slider (the finger's value while dragging, steps of 1 near the centre); the re-test `CAP-073` (Group BI, `ai-sessions/0085`, 2026-10-10) found no heavy defect — the noise-control channel held on its tab, four press-and-holds shown without a tap, every request byte-identical to `CAP-072`; verdict **OK — ready to release** (the maintainer, chat 2026-10-10). -**`CAP-074`** (`ai-sessions/0086`, 2026-10-12, the released 1.2.0 in the GrapheneOS user **with** Google Play services) found the limit of that user: Play's Fast Pair takes the held noise-control channel back ≈ 5 s later and the app did not re-claim, so only the first press-and-hold after a Connect was shown. **1.2.1** (prepared in `ai-sessions/0087`, 2026-10-12; published 2026-10-13, build `31ee183`): the Settings tab's "Noise-control channel" choice — "Show a hint, do not take it back" (the default) or "Take it back while the noise-control tab is open" (a re-claim 3, 6 and 12 s after the first, second and third foreign close, then the hint) — ADR-062; its hardware run `CAP-075` (Group BK, `ai-sessions/0088`, 2026-10-12/13, both users) passed: without Play services both choices are identical and every press-and-hold on the tab is shown (41 of 41); with Play services the hint shows the cut's time and "Take it back" holds the channel about 43 % of a 44-s chain (Play's recovery 5.3–5.8 s, retry 2.1–2.8 s) — verdict "no heavy issue — release as built" (the maintainer, chat 2026-10-13); the next step is a constant re-claim step with a longer chain (`TODO.md` §5). **1.2.2** (prepared in `ai-sessions/0089`, 2026-10-13; not released): the setting renamed "Noise control with Google Play services" with options that say whether the phone has Google Play services and a line whether Android finds it (ADR-063), the re-claim 3 s after every cut up to 20 times, "Take it back again" on the hint, and a cause line for a mode the app's own read found changed (ADR-062 Update 2); released 2026-10-14 (build `bfd7891`) without a hardware run. **1.3.0** (prepared in `ai-sessions/0091`, 2026-10-14; not released): the app subscribes to the Buds' settings stream and reads the noise-control mode on its own channel (ADR-064), so a press-and-hold on a bud shows on any tab and every setting follows a change made elsewhere; the hold, the re-claim, the "Noise control with Google Play services" setting and the Play-services line are removed (ADR-065); its hardware run `CAP-076` (Group BL, `ai-sessions/0092`, 2026-10-15, both users) passed: every press-and-hold on the noise-control tab shown within 0.1 s — 29 of 29 with Google Play services holding the shared channel, 12 of 12 without — and three minor defects; verdict "release as built + Known issues" (the maintainer, chat 2026-10-15). +## Status (rewritten 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-GOV-01/02) + +The current release is **1.3.0** (published 2026-10-15, tag `v1.3.0` on build `ff27ee1`; `RELEASING.md` §13): the app subscribes to the Buds' settings stream +and reads the noise-control mode on its own channel (ADR-064/065), so a press-and-hold on a bud shows on any tab with or without Google Play services; its +hardware run `CAP-076` (`ai-sessions/0092`) showed every hold on the noise-control tab within 0.1 s (29 of 29 with Play, 12 of 12 without). A fix release 1.3.1 +is prepared by `ai-sessions/0094` (one pairing memory for "Use different Buds" with a confirmation, the kept "changed since" line, no silent tile tap, stream +pushes that no longer end a read, one name per mode, TalkBack labels; `CHANGELOG.md` [1.3.1]) — not released; its hardware run `CAP-077` is planned. + +v1 contains: connection and Safe Mode, noise control with a Quick Settings tile, the equalizer (with Volume EQ), battery (Left / Right / Case), Find My Buds +(Left / Right), touch controls, press and hold and its mode list, head gestures, Multipoint on/off, balance, mono audio, conversation detection, in-ear +detection, the case sounds, the component serial numbers and a "probably worn" line. Volume-level notifications are studied, not built (draft ADR-060). +The release history, with each version's hardware run and known issues, is in `CHANGELOG.md`; the per-feature evidence is `ARCHITECTURE.md` §5a. --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/PROJECT.md - https://tedsluis.github.io/opencontrolpixelbudspro2/PROJECT diff --git a/PROJECT_RULES.md b/PROJECT_RULES.md index 0e92bf94..a7c0561d 100644 --- a/PROJECT_RULES.md +++ b/PROJECT_RULES.md @@ -161,9 +161,11 @@ document disconnected from the capture it belongs to. ## 5. AI behavior within this project 13. An AI model working on this project: - - reads `AGENTS.md`, `PROJECT_RULES.md`, `ARCHITECTURE.md`, and the relevant - sections of `PROTOCOL.md` at the start of a session (or whenever context - has been reset). + - reads, at the start of a session (or whenever context has been reset), the + documents `AGENTS.md` §0.1 lists, in that order (changed 2026-10-15, + maintainer-approved in chat, `ai-sessions/0094` — `ai-sessions/0093` + A93-GOV-04: the shorter list here omitted `PROJECT.md`, `DECISIONS.md` and + `TODO.md`). - explicitly distinguishes FACT / HYPOTHESIS / ASSUMPTION in every answer that concerns the protocol or the reverse-engineered code. - proposes a verifying experiment when in doubt, rather than guessing. diff --git a/PROTOCOL.md b/PROTOCOL.md index 6bedad20..475bc2d2 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -109,7 +109,7 @@ above it. > extension page (`developers.google.com/nearby/fast-pair/specifications/extensions/mac`, fetched 2026-09-24) defines a Device information event with code > `0x0A`, 8 bytes, that the Provider "should generate and send to the Seeker when Message Stream connects". On the wire every DLCI 0x04 open is followed by a > Buds frame `03 0a 00 08 <8 bytes>` with a fresh value: 19 of 19 opens in `CAP-059` (e.g. frame 1049 `03 0a 00 08 04 d6 2f e3 c2 2d 30 3b`, frame 1509 -> `03 0a 00 08 4c b5 93 ed 88 3f 5a ef`), also `CAP-001` 1004/1826 and `CAP-006` 727/2102 (command: `tshark -r -Y "btrfcomm.dlci==4 && btrfcomm.len>0" +> `03 0a 00 08 4c b5 93 ed 88 3f 5a ef` *(frame 1508 — corrected 2026-10-15, `ai-sessions/0094`, A93-PROT-08)*), also `CAP-001` 1004/1826 and `CAP-006` 727/2102 (command: `tshark -r -Y "btrfcomm.dlci==4 && btrfcomm.len>0" > -T fields -e frame.number -e frame.p2p_dir -e data.data`). `CAP-002-FINDINGS.md` §3 had recorded it only as "8 bytes, changes every occurrence". (The > `deviceinformation` page itself lists no `0x0A` row; the code is defined on the MAC page.) Its role in MAC-protected messages: §4.1. > @@ -150,7 +150,7 @@ above it. | Transport | Used for | Status | |---|---|---| | Bluetooth Classic RFCOMM (`BluetoothSocket`, SPP-style) | Three coexisting, independently-framed DLCIs (see §2.3's table): the official Fast Pair Message Stream (DLCI 0x04, 🟢 FACT, carries the confirmed ANC command), `libmaestro`'s Pigweed `pw_hdlc` channel (DLCI 0x02 — pw_rpc `maestro_pw.Maestro`, 🟢 FACT since ADR-034; the earlier 🟡 wording is superseded), and a third, still-unidentified private envelope (DLCI 0x08, 🔴 open identity) | 🟢 FACT that these are three separate channels, not one shared channel — see §2.3 | -| BLE advertisement | Fast Pair "Battery Notification" extension — passive battery status broadcast, no active connection required | 🟢 FACT (mechanism, official spec); 🟡 HYPOTHESIS (confirmed as what the Buds Pro 2 send) | +| BLE advertisement | Fast Pair "Battery Notification" extension — passive battery status broadcast, no active connection required | 🟢 FACT (mechanism, official spec); 🟡 HYPOTHESIS (confirmed as what the Buds Pro 2 send) *(relabelled 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "FACT-teksten", A93-PROT-05: 🔴 not observed from the Pixel Buds Pro 2 — no Battery Notification field in clear in `CAP-011`/`043`/`054`, Option A Update of 2026-10-06)* | | BLE GATT | A standard Battery Service (`0x180F`, handles `0x0f30`–`0x0f33`) exists in the Buds' GATT database (🟢, `CAP-034`, §4.3 Option D); whether anything reads it for the app's purposes is 🟡; not used for control and not used by this app | 🟢 for the service's existence (§4.3 Option D); ⚪ ASSUMPTION for any use *(row corrected 2026-10-03, `ai-sessions/0069`)* | ## 2. RFCOMM framing (three structurally distinct sub-protocols: DLCI 0x02 / 0x04 / 0x08) @@ -418,6 +418,10 @@ about every field of every message). Evidence and commands (`PROJECT_RULES.md` r Command: the scratch decoder of `CAP-065-FINDINGS.md` (DLCI 2 **and** 3, per direction, CRC-32 per frame). ADR-034 item 3 (send only on a tabulated channel) is unchanged. + *(Correction 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "FACT-teksten" — `ai-sessions/0093` A93-CAP-07: the + count is **8 of 8**, not "7 of 7": `CAP-065-FINDINGS.md` §4 has eight rows with one bud (or one bud first) out — the seven frames above plus 4962 (#4, the + Right first out, `10 15` = 21); every one fits. Status unchanged.)* + - **Update (2026-10-01, `ai-sessions/0062`, maintainer-approved in chat 2026-10-01, `AskUserQuestion` "Info tab", option *"Promote + label (Recommended)"*, with this text in the preview) — which announcement entry is which component.** 🟢 **FACT (code, the official app `v1.0.955078536-10253511`):** the `GetSoftwareInfo` response type `qjb` (`fux.java:57`) carries in field 4 a `qie` whose fields 1/2/3 (Java `c`/`d`/`e`, `qie.java:29`) are each a `qid` @@ -873,6 +877,16 @@ never decides which extracted finding is relevant (see `AGENTS.md` §4/§6, `7e80a3032a0422026801080710131dea71de7d5e25f5ad2128117bf5557e`), with one bud in the case once, on that bud's channel — 🟡 one packet per bud core (19 = LEFT_BT_CORE, 21 = RIGHT_BT_CORE, §2.2a); the official app's captures show one packet per change (❓, `CAP-076-FINDINGS.md` §4.3). The §6 item "a stored mode per wear state" is **not** settled by this run (three wear-driven switches within 2–7 ms of the Buds' AVRCP PAUSE/PLAY, no ears on film). +- **Correction (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "FACT-teksten", option *"Alles zoals voorgesteld + (Recommended)"* — `ai-sessions/0093` A93-PROT-01/02; no status change).** (1) The Update of 2026-10-14 counts its own class: "73 of 73" is the class defined as + "a stream packet within 1 s", and "12 of 77" divides by the changes that were **not** missed. Re-run (`python3 scripts/anc_setting13_crosscheck.py --census` over + the 54 `btsnoop` logs of the 46 subscribing official-app captures, `CAP-049`'s combined log left out; the same numbers as 2026-10-14): the Buds reported **89** + changes themselves (unprovoked Notifies); **75 of 89** were pushed within 1 s (73 with the predicted value, 2 as two close changes matched crosswise — + `CAP-056` 3983/4001, `CAP-040` `.log.last` 10841/10851); **12 of 89** were not, because the Buds closed MAESTRO 2–30 ms after the Notify; **2 of 89** fell on a + re-open or a fresh Message Stream open (`CAP-047` `-2.log` 3996, `CAP-005` 2348). The 39 of 39 Set-driven changes and 359 of 382 reads stand. The FACT + (setting 13 is the mode and is pushed to a subscriber) is unchanged. (2) The Note of 2026-10-15: "41 of 41 holds … while Google Play services held the Message + Stream" joins two users — **29 of 29** in the Owner user, where Play held the Message Stream, and **12 of 12** in the secondary user without Play, where nobody + held it (`CAP-076-FINDINGS.md` §4.2). ### 4.2 Equalizer (EQ) @@ -1076,7 +1090,8 @@ event-observation coroutines. #### Option A — BLE advertisement (Fast Pair "Battery Notification" extension) - **Status**: 🟢 FACT (mechanism, official spec) / 🟡 HYPOTHESIS (confirmed as - used by the Buds Pro 2 specifically). + used by the Buds Pro 2 specifically). *(Relabelled 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "FACT-teksten", A93-PROT-05: 🔴 not + observed from the Pixel Buds Pro 2 — see this Option's 2026-10-06 Update (`CAP-054`).)* - **Payload structure** (`[OFFICIAL-SPEC]`, `developers.google.com/nearby/fast-pair/specifications/extensions/batterynotification`): @@ -1709,6 +1724,11 @@ implementation gate. `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group C's own hint that Multipoint "may trigger an SDP/connection update, not just an RFCOMM command." This SASS correlation remains 🟡 HYPOTHESIS, unaffected by the field-11 promotion above. + **Correction (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "§6 en lead", A93-PROT-03):** the `"in-use"` string is in Code **`0x41`**, not the last code `0x42`: `CAP-019` 804 + (phone → Buds) `07 41 00 16 69 6e 2d 75 73 65 …`, 806 `07 42 00 11 00 64 …`, the Buds' ACK 810 `ff 01 00 08 07 41 69 6e 2d 75 73 65` (`tshark -r CAP-019-btsnoop_hci.log + -Y 'btrfcomm.len>0 && btrfcomm.dlci==4 && frame.number>=780 && frame.number<=810' -T fields -e frame.number -e frame.p2p_dir -e data.data`, exit 0). The Fast Pair + SASS page (developers.google.com/nearby/fast-pair/specifications/extensions/sass, fetched 2026-10-15) defines 0x41 "Indicate in use account key" with + `UTF8 ("in-use")` at octets 4–9; 0x42 is "Send custom data". **Correction (2026-09-30, `ai-sessions/0059`, A58-CAP-02):** the same `CAP-019` log shows the `0x11`/`0x40`/`0x41`/`0x42` exchange also at connect (frames 785–807, 07:35:58) and at 07:39:13 (2487–2496), and Code `0x34` 15 times across the session — only `0x21` (2301) is unique to the Multipoint burst (message-level parse, `CAP-019-FINDINGS.md` §4). 🟡: Multipoint triggers one more round of a recurring SASS exchange, not a Multipoint-only set. @@ -1780,7 +1800,7 @@ implementation gate. values, and — independently — the app's own schema/field identity for both); 🟢 FACT for the rotation-checklist's field-number identity only; 🟡 HYPOTHESIS for the rotation-checklist's own field-order/on-screen-mapping reading and for whether it's the same feature as `qht`'s "ANC gesture - loop." + loop." *(Pointer 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "FACT-teksten", A93-PROT-06: both 🟢 since 2026-09-28 — this section's Update of that date, ADR-046.)* - **Evidence**: `CAP-020-FINDINGS.md` §3, §8 (2026-08-30 addendum); `CAP-021-FINDINGS.md` §3–§4, §8 (2026-08-30 addendum); `REVERSE_ENGINEERING.md`'s `qhr`/`qjo`/`qju`/`qjg`/`qht` entries (2026-08-30 updates); `DECISIONS.md` ADR-019. @@ -1831,7 +1851,7 @@ implementation gate. Head gestures; the wire write fires on the tap itself, ~1s before the explainer dialog even renders. - **Sent to**: DLCI 0x02. -- **Status**: 🟡 HYPOTHESIS. +- **Status**: 🟡 HYPOTHESIS. *(Pointer 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "FACT-teksten", A93-PROT-06: 🟢 since 2026-10-04 — this section's Update of that date, ADR-052.)* - **Evidence**: `CAP-020-FINDINGS.md` §4 (`[VERIFIED-LOCAL]`, 2026-08-21, frame 1935). - **Verified with experiment**: `CAP-020` (2026-08-21), single OFF→ON sample. - **Update (2026-10-03, `ai-sessions/0069`, maintainer-approved in chat 2026-10-03, `AskUserQuestion` "MP / gestures", option *"Dated Updates, statuses as drafted (Recommended)"* — @@ -2188,6 +2208,11 @@ the case-open/bud-removal event first, not yet tested directly. **DisplayYesNo** and the link key is type **0x05** (authenticated P-256): `CAP-058` frames 2204/2235 and, before the film, `.log.last` 10253/10333 and 11947/11982; in a pairing from Settings → Pair new device they answer **NoInputNoOutput** and the key is type **0x04** (unauthenticated P-256): 3789/3804, the confirmation reply 3801 at the filmed *Pair* tap. The fresh-pairing sequence above is unchanged. Evidence: `CAP-058-FINDINGS.md` §3. +*(Correction 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "FACT-teksten" — `ai-sessions/0093` A93-CAP-05: the +key types are the **P-192** combination keys — 0x05 "Authenticated Combination Key, P-192", 0x04 "Unauthenticated Combination Key, P-192"; `tshark -r +CAP-058-btsnoop_hci.log -Y 'frame.number==2235' -V | grep -i 'key type'` → "Key Type: Authenticated Combination Key, P-192 (0x05)". The P-256 keys are 0x07/0x08 +(Linux kernel commit 11015c79, "Bluetooth: Add support for P-256 authenticated/unauthenticated link keys"; the Core Specification's HCI Link Key Notification +event). The IO capabilities above are unchanged.)* **Common tail, both paths:** `Authentication Complete` → `Set Connection Encryption` → `Encryption Change`, converging to the same encrypted classic @@ -2693,10 +2718,18 @@ leaving them buried in prose elsewhere. capture (`CAP-046`, Group AK) confirmed range ±100, `+100`=Left, `-100`=Right, 3/3 video-confirmed, zero counter-examples. Intermediate-position scaling remains untested, unaffected by this promotion — see `PROTOCOL.md` §4.5.7. -- [ ] **Added 2026-08-21, `CAP-019-FINDINGS.md` §4:** what do Fast Pair SASS (DLCI 0x04 Group +- [x] **Added 2026-08-21, `CAP-019-FINDINGS.md` §4:** what do Fast Pair SASS (DLCI 0x04 Group `0x07`) Codes `0x11`/`0x21`/`0x40`/`0x42` encode beyond their raw bytes? Is Code `0x34` (which also fires with no Multipoint action nearby) a periodic/keepalive SASS code unrelated to Multipoint specifically? + **Answered (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "§6 en lead", `ai-sessions/0093` A93-PROT-04 — 🟢 for the names and the lengths, 🔴 for the encrypted + contents).** The Fast Pair SASS page (developers.google.com/nearby/fast-pair/specifications/extensions/sass, fetched 2026-10-15) names Group `0x07` + "Audio switch": `0x10` Get / `0x11` Notify capability of Audio switch (both directions — a Provider sends length 4: version `0x0102` and two capability-flag + bytes, `CAP-019` 799 `07 11 00 04 01 02 98 00`; a Seeker length 20 with nonce and MAC, 786), `0x21` Get switching preference (Seeker → Provider, length 0; + the answer is `0x22`), `0x34` Notify connection status (Provider → Seeker, sent when the connection status changes on a multipoint Provider and as the + answer to `0x33`; octet 4 the active-device flag, then encrypted status and a nonce — **event-driven, not a keepalive**), `0x40` Notify Audio switch initiated + connection, `0x41` Indicate in use account key (`"in-use"`, `CAP-019` 804), `0x42` Send custom data (one byte + nonce + MAC, 806). SASS stays out of the app's + scope (ADR-008); nothing is built from this. - [ ] **Added 2026-08-21, `CAP-011-FINDINGS.md` §4:** why do the Fast Pair Service (`0xFE2C`) BLE advertisement payloads sampled in `CAP-011` not match `PROTOCOL.md` §4.3 Option A's documented Battery Notification byte layout (no sampled byte equals the expected `0x33`/`0x34` @@ -2774,7 +2807,14 @@ leaving them buried in prose elsewhere. least **twenty**. Trigger candidate 3 is ruled out as an explanation for `CAP-021`'s burst; Trigger candidates 1 (app backgrounded/foregrounded) and 2 (a scheduled idle sync window) remain untested. See `CAP-047-FINDINGS.md` §3 for the full command+hex evidence. -- [ ] **Added 2026-08-18, `CAP-016-FINDINGS.md` §11; sharpened 2026-09-12, `CAP-018-FINDINGS.md` + **Update (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "§6 en lead", `ai-sessions/0093` lead L93-1 — 🟡 HYPOTHESIS (strong)):** each of `CAP-021`'s four + waves starts with the Buds' GSND CONTROL message `01 09 00 03 0a 01 03` 1–2 ms before its first DLCI 0x0a frame — 2092 (08:02:29.503 → 2093 at 29.504), + 2963 (08:02:50.108), 3645 (08:03:27.109), 4340 (08:03:58.307) — the opener `CAP-069` found at 3 of 3 filmed assistant sessions (§6, `CAP-069` item: 3403, + 4084, 4806); `CAP-021`'s press-and-hold was set to the digital assistant during that capture (`CAP-021-FINDINGS.md` §3). Reading: the four waves are + assistant sessions started by press-and-holds (not on film). Each wave also carries `01 09 00 03 0a 01 05`, `… 01 01` and `… 01 21` (e.g. 2297, 2340, 2341): + 🔴 their meaning. Command: `tshark -r CAP-021-btsnoop_hci.log -Y 'btrfcomm.len>0 && (btrfcomm.dlci==8 || btrfcomm.dlci==9) && frame contains 01:09:00:03' + -T fields -e frame.number -e frame.time -e frame.p2p_dir -e data.data` (exit 0); positive control: the 1123 DLCI 0x0a payload frames of this item. Over every capture log (scratch script, `tshark` per log: DLCI 0x0a/0x0b payload frames grouped into waves at gaps > 2 s, and the opener on DLCI 0x08/0x09 within 0.2 s before each wave start): GSND AUDIO payload occurs only in `CAP-021` and `CAP-069`, and **8 of 8** waves start with the opener (`CAP-021` 2093/2964/3646/4341, `CAP-069` 3404/4085/4807/4829). +- [x] **Added 2026-08-18, `CAP-016-FINDINGS.md` §11; sharpened 2026-09-12, `CAP-018-FINDINGS.md` §3–§4 (recorded at 🟡/🔴 as written — marker resolved 2026-09-30, maintainer, chat, `ai-sessions/0059`):** a 73-frame `Handle Value Notification` burst on BLE ATT handle `0x0044` (connection handle `0x0002`), confined to a ~29s window right after the BLE link forms and before the classic link exists; 23 of the 73 contain a @@ -2792,6 +2832,13 @@ leaving them buried in prose elsewhere. device coincidentally connecting near the same time as a bud/case action? Not force-fit into either reading, per `AGENTS.md` §13.6. **Update 2026-10-03 (`ai-sessions/0069`, `A68-CAP-16`, 🟢 FACT for the link):** `CAP-016`'s own burst is on the same kind of link. `tshark -r CAP-016-btsnoop_hci.log -Y "btatt.uuid16==0x180d" -T fields -e frame.number -e bthci_acl.chandle` → `760 0x0002`: the peer on connection handle `0x0002` — the handle that carries all 73 notifications — exposes the Heart Rate service, which the Buds' attribute table (`CAP-034-FINDINGS.md` §4) does not have. `CAP-001` shows the same (Heart Rate in frame 370 on handle `0x0002`; 13 notifications on `0x0044` there). Every occurrence examined so far is another device's traffic; no occurrence on a Buds link is known. The item stays listed only because no check was run on every capture. + **Closed (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "§6 en lead", `ai-sessions/0093` A93-PROT-07 — 🟢):** the check over every capture log (103 `btsnoop`/`btsnooz` logs; + per log `tshark -Y "btatt.opcode==0x1b && btatt.handle==0x0044" -T fields -e bthci_acl.chandle` and `-Y "btatt.uuid16==0x180d"`): notifications on `0x0044` + in 76 logs, every carrying handle shows the Heart Rate service in the same log, except `CAP-060`…`066` and `CAP-072` `hci1.log.last`, where the carrier + `0x0040`/`0x0041` is the LE peer `c8:cc:…` (LE Connection Complete in all 12 logs), which shows Heart Rate in `CAP-072-btsnoop_hci1.log`; in `CAP-059` the Buds' + LE address held `0x0041` earlier (538) but the notifications (5529–5702) come after its reuse by another peer (5237; Heart Rate 5305). No `0x0044` + notification on a Buds link in any capture; the Buds have no Heart Rate service (`CAP-034-FINDINGS.md` §4). Positive control: `CAP-016` handle `0x0002`, + 73 notifications, Heart Rate frame 760. - [ ] **Added 2026-08-18, `CAP-016-FINDINGS.md` §10:** Bluetooth HID (PSM `0x0011`) Feature Report Id `0x01`'s response (frame 1983) is only 3 bytes (`a3 00 00`) — too short to carry any content comparable to Report Id `0x02`'s decoded `AndroidHeadTracker` string (same section, @@ -2911,6 +2958,9 @@ leaving them buried in prose elsewhere. `PRIV-001` row already reflects this resolved/inconclusive split (confirmed 2026-09-18, `ai-sessions/0031` — the mechanical follow-through `CAP-050-FINDINGS.md` §8 called for is done, this note was stale). + *(Pointer 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "§6 en lead", A93-PROT-09: `CAP-069` named two neighbours of these Get-shaped codes on the wire — + Code `0x05` = wear state (`03`/`04`/`05`/`06`, 🟡) and Code `0x16` = head gestures active (🟡), §2.3 note of 2026-10-04; the zero-length requests + `04 04` and `04 15` are their likely Gets (🟡). The other codes stay open.)* - [ ] **Added 2026-09-06, `CAP-040-FINDINGS.md` §1:** the official app's own in-app "Connect"/ "Disconnect" buttons, tapped ~15 times across a 27-minute stretch, produced **zero** wire-visible signal — no ACL disconnect/reconnect, no DLCI channel bounce of any kind. The @@ -2943,6 +2993,9 @@ leaving them buried in prose elsewhere. handling the Buds (matches `CAP-027-FINDINGS.md` §4's established Buds-initiated Notify-without-Set mechanism for `TOUCH-007`), but not video-confirmed (camera was on the phone screen, not the buds/ears) — could equally be some other still-unidentified trigger. + *(Pointer 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "§6 en lead", `ai-sessions/0093` A93-PROT-09: a second candidate since 2026-10-14 — the Buds' own switch on a wear change (§6 item + of 2026-10-14, "a stored mode per wear state", 🟡 strong); `CAP-038` 3847 is one of the 12 Notifies that fell on the Buds' `DISC` of MAESTRO (§4.1 + Correction of 2026-10-15). The trigger stays unfilmed; the item stays open.)* **Second candidate added 2026-09-07 (`AUDIT_REPORT_2026-09-07.md` §1.1), code-evidenced but not wire-tested:** `REVERSE_ENGINEERING.md`'s `qhr` entry documents `qhr` field 13 (`ANC_STATE`-typed, alongside the already-FACT field 12/`qht`) as reachable from `fye.java`'s `a(qhs)` method with @@ -3628,6 +3681,11 @@ leaving them buried in prose elsewhere. `CAP-063` 5389, a positive control). 🟡: here the phone paused without a Buds PAUSE command (compare `CAP-063`, where the Buds sent one). Genuinely open whether the pause is driven by a mechanism this project hasn't identified, or whether the documented "switches to Transparency" behavior simply didn't trigger this time (ANC was already Adaptive). + **Update (2026-10-15, `ai-sessions/0094`, the 0093 capture re-check):** "no Buds command" does not hold for DLCI 0x08: the Buds sent + `01 09 00 05 0a 03 0b 06 13` at 1592 (07:54:17.547), 0.12 s before the phone's Paused (1593), and `01 09 00 05 0a 03 0b 06 12` at 1711 (07:54:32.736), + 0.07 s before Playing (1713) — `tshark -r CAP-029-btsnoop_hci.log -Y "frame.number==1592 || frame.number==1593 || frame.number==1711 || + frame.number==1713" -T fields -e frame.number -e frame.time -e bthci_acl.chandle -e btrfcomm.dlci -e data.data` (exit 0; handle `0x0002`, DLCI 0x08). + 🔴 open: what the two frames mean (only the last byte differs, `13`/`12`); they are the candidate signal for this item. - [ ] **Added 2026-09-28, `CAP-063-FINDINGS.md` §7 (maintainer-approved in chat 2026-09-28, `AskUserQuestion` "Notities"):** 🟡 conversation detection pauses and resumes the media with ordinary AVRCP pass-through commands from the Buds (PAUSE 5389 at 16:09:22.952, PLAY 5433 at 16:09:30.364 while the maintainer spoke) — a tap @@ -3636,6 +3694,8 @@ leaving them buried in prose elsewhere. Google app's Assistant-headphones service (`BistoRealService`), not by HFP voice recognition: no `AT+BVRA` in either direction during the hold tests (16:10:36–16:12:09), while Android had stopped that service and the phone had closed DLCI 0x08/0x0a (2572/2573, 373 ms after the stop). Bisto stop → phone `DISC` DLCI 0x08/0x0a: 4 of 4 in `CAP-063`, 9 of 9 with `CAP-061`/`CAP-062`. Test: the Google app disabled vs enabled. + *(Pointer 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15, `AskUserQuestion` "§6 en lead", lead L93-1: `CAP-021`'s four GSND AUDIO waves begin with the assistant-session opener `01 09 00 03 0a 01 03` — the DLCI 0x0a + item's Update of 2026-10-15.)* - [ ] **Added 2026-09-28, `CAP-063-FINDINGS.md` §6:** 🔴 after an ADR-016 ACL drop with the lid open, Android did **not** re-create the ACL by itself (6378 → nothing until a user action 11 s later; 7566 → 27 s), whereas `CAP-062` 7226 did within 12 ms. Also 🟡: putting the buds into the ears did not close DLCI 0x02 here (16:06:48), unlike `CAP-062` (1/1). @@ -3648,7 +3708,7 @@ leaving them buried in prose elsewhere. `08 05` one bud out (a different value per side of the head), `08 03` both out — and reads `08 01` whenever in-ear detection is off (2253, 2318, 2453, 3687, 2854, 4099); this conflicts in part with `CAP-050` §4 (`03` docked, `04` both on the table). 🔴 `Group 0x04 Code 0x16` flips `01`→`02` when both buds leave the ears and back, with in-ear detection on or off (3689/3985, 4103/4167) — meaning open (`CAP-050`: `02` docked). -- [ ] **Added 2026-09-28, `CAP-056-FINDINGS.md` §4:** 🟡 `SubscribeToSettingsChanges 4:{13:n}` (field 13, `qhs` ANC state) mirrors the `Notify ANC state` mode on wear +- [x] **Added 2026-09-28, `CAP-056-FINDINGS.md` §4:** 🟡 `SubscribeToSettingsChanges 4:{13:n}` (field 13, `qhs` ANC state) mirrors the `Notify ANC state` mode on wear changes with in-ear detection on — `13:4` with `0x40` (2257/2259), `13:2` with `0x08` (2322/2324), and a read of `13:0` with no bud worn (3870) — matching `qhs.java`'s `ANC_STATE_ADAPTIVE(4)`, `ANC_STATE_ACTIVE(2)`, `ANC_STATE_UNKNOWN(0)`. *(Pointer 2026-10-14, `ai-sessions/0090`, maintainer-approved in chat: 🟢 FACT now — §4.1, Update of 2026-10-14.)* @@ -3766,6 +3826,7 @@ leaving them buried in prose elsewhere. | 2026-10-13 | **`ai-sessions/0088` — `CAP-075` (Group BK, OpenControl 1.2.1 in both GrapheneOS users), maintainer-approved in chat 2026-10-13.** §4.1: a 🟢 FACT note on Google Play services' Fast Pair client — DLCI 0x04 in all its claims, recovery 5.3–5.8 s after a loss, retry 2.1–2.8 s after a collision, the stack's `already at opened state` refusal closing both sockets (15 of 15 cuts in the system log). No status change for the opcode. | Claude (AI), maintainer-approved | | 2026-10-14 | **`ai-sessions/0090` — the pbpctrl cross-check (E-01 … E-23), maintainer-approved in chat 2026-10-14.** **§4.1** 🟢 FACT: setting 13 = the noise-control mode on DLCI 0x02, pushed on the settings stream to a subscribed client for every reported change (73/73 Buds-side, 39/39 Set-driven), incl. while no client holds the Message Stream; `ReadSetting 4:13` = the latest mode (359/382) — used by ADR-064; §6's two 🟡 items on field 13 point there. **§6** 🟡 one-bud / two-bud mode (pbpctrl names 9/10). **§2.2** correction: the format was never pbpctrl's. **§4.5.7** note: pbpctrl's balance = −zigzag (same mapping). **§0.1** note: Class of Device `0x244404`. | Claude (AI), maintainer-approved | | 2026-10-15 | **`ai-sessions/0092` — `CAP-076` (Group BL, OpenControl 1.3.0 in both GrapheneOS users), maintainer-approved in chat 2026-10-15.** §4.1: a note — the settings stream in OpenControl's own logs (12 of 12 sessions subscribed, 41 of 41 holds pushed while Play held the Message Stream), every change pushed twice with both buds out of the case (🟡 one per bud core). No status change; §6's wear item unchanged. | Claude (AI), maintainer-approved | +| 2026-10-15 | **`ai-sessions/0094` — the 0093 audit processed, maintainer-approved in chat 2026-10-15** (`AskUserQuestion` "FACT-teksten", "§6 en lead"). §4.1: a Correction — the setting-13 ratios (75 of 89 pushed, 12 of 89 lost to the Buds' `DISC`, 2 at re-opens; not "73 of 73" / "12 of 77") and the two users of `CAP-076` (29 of 29 with Play, 12 of 12 without). §5: link-key types are P-192 (0x04/0x05), not P-256. §2.2a: L-1 "8 of 8" in `CAP-065`. §1/§4.3 Option A: 🔴 not observed from the Pro 2. §4.5.2: "in-use" is Code `0x41`. §4.5.3/§4.5.4: Status pointers. §6: SASS item answered from the spec; ATT `0x0044` item closed (103-log scan); DLCI 0x0a item — 🟡 the `CAP-021` waves are assistant sessions (L93-1); field-13 mirror item ticked; pointers on the `CAP-038` and DLCI 0x08 Get-code items. One frame number 1509 → 1508. No FACT promoted or withdrawn. | Claude (AI), maintainer-approved | --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/PROTOCOL.md - https://tedsluis.github.io/opencontrolpixelbudspro2/PROTOCOL diff --git a/README.md b/README.md index 78c61d7f..0e05b391 100644 --- a/README.md +++ b/README.md @@ -93,63 +93,24 @@ recovery from every failure (details: [`WORKSTATION_PREPARATIONS.md`](./WORKSTAT ## Status -**1.0.1 is released** (2026-10-04, see above): a hotfix for 1.0.0 whose release APK passed its hardware run `CAP-068` in a GrapheneOS user without Google -Play services — noise control from the app and the Quick Settings tile, the equalizer, Bluetooth off/on, "Use different Buds" and the connect error, all on film -and in the Bluetooth log (`PROJECT.md` Definition of done ticked). Tested on one phone — a Pixel 9a with GrapheneOS (Android 17) — with Buds firmware `release_5.203`. -Per feature, what is hardware-verified and what is only unit-tested is in `ARCHITECTURE.md` §5a; the history of every change is in -[`CHANGELOG.md`](./CHANGELOG.md). Found a bug? [Open an issue](https://github.com/tedsluis/opencontrolpixelbudspro2/issues/new/choose) — a suspected -security problem goes through [`SECURITY.md`](./SECURITY.md) instead. - -**1.1.0 was released on 2026-10-07** (prepared in `ai-sessions/0074`): the switches for head gestures, Multipoint, the two case sounds and Volume EQ, -and the screen-reader text for "—". Its hardware run (`CAP-070`, `ai-sessions/0075`) passed: every request of the five switches, on both control -channels, byte-identical to the expected frames and confirmed by the Buds. Known issues of 1.1.0 -(also in [`CHANGELOG.md`](./CHANGELOG.md)): the app says what a switch sets; the screen-reader text for "—" is checked by the unit -tests, not yet on a phone. - -**1.1.1** (2026-10-08, `ai-sessions/0079`): the same app built with current build tools and libraries — nothing changes for you. Its hardware run -`CAP-071` passed (`ai-sessions/0080`: the update keeps your settings, every request and answer is the same as 1.1.0's). Known issues: as 1.1.0. - -**1.2.0** (2026-10-10; prepared 2026-10-09 in `ai-sessions/0082`; rebuilt 2026-10-10 in `ai-sessions/0084` after its hardware run `CAP-072` -(`ai-sessions/0083`): the app keeps its noise-control channel open while the noise-control tab is on screen, so a press-and-hold on a bud shows — ADR-061; -re-test `CAP-073` passed, `ai-sessions/0085`: the hold works on the tab, a press-and-hold changes the mode on screen without a tap — verdict OK): "Changed by the Buds" in the noise-control details, the serial numbers on the Info tab, a "probably worn" line on the battery card, a finer -balance slider, and the Case sounds switches on gear → Settings and Conversation detection on Controls. Known issues of 1.2.0 (also in -[`CHANGELOG.md`](./CHANGELOG.md)): the worn line is a hedged reading of a hypothesis (the Buds say "at least one bud", never which, and have reported it for 40 -seconds or more with both buds on a table); the serial labels are the official app's reading of the answer's order; "Changed by the Buds" shows only what the -Buds report while the noise-control tab is open. - -**1.2.1** (2026-10-13; prepared 2026-10-12 in `ai-sessions/0087` after `CAP-074`, `ai-sessions/0086`; build `31ee183`): a **Noise-control channel** setting on -gear → Settings for phones with Google Play services, where Play's Fast Pair takes the channel back about 5 seconds after this app opened it on the noise-control -tab — "Show a hint" (the default: the card says when it was taken) or "Take it back" (3, 6, 12 seconds later, three times per visit, a tug of war with Play -meanwhile). Its hardware run `CAP-075` (Group BK, `ai-sessions/0088`, both users) passed — without Play services both choices are identical and every -press-and-hold on the tab is shown (41 of 41); verdict: release build `31ee183`. Known issues of 1.2.1 (also in [`CHANGELOG.md`](./CHANGELOG.md)): in a user -with Google Play services "Show a hint" shows a press-and-hold only while this app's claim lives (until Play's next connect, 5.3–5.8 s after Play lost the -channel), then the hint; "Take it back" holds the channel about 43 % of a 44-second chain (three re-claims per visit of the tab), then nothing until a tap — -the timings are one Play version's. - -**1.2.2** (2026-10-14; prepared 2026-10-13 in `ai-sessions/0089` after `CAP-075`; build `bfd7891`; tested by the maintainer on the phone without a recorded -capture — `CAP-076`, Group BL, was not run): the setting is now called -**Noise control with Google Play services**, and its options say which phone they are for — "This phone has no Google Play services" (the default) or "This -phone has Google Play services"; a choice made in 1.2.1 is kept. The card says whether Android finds Google Play services on the phone (information only, -ADR-063). With Google Play services the app takes the channel back **3 seconds after every time Play takes it, up to 20 times** (about 3 minutes) per visit of the -noise-control tab, so a press-and-hold is shown about half of the time for the whole 3 minutes (ADR-062 Update 2); the hint has a **Take it back again** button -(no mode written); and the noise-control (i) says when a mode the app read had changed while it did not have the channel. Expected numbers are `CAP-075`'s (one -Play version), not measured with 1.2.2. - -**1.3.0** (2026-10-15; prepared 2026-10-14 in `ai-sessions/0091` after `ai-sessions/0090`'s cross-check of the official app; build `ff27ee1`): the app subscribes to the Buds' -settings stream and reads the noise-control mode on its own channel at every Connect and re-open (ADR-064), so **a press-and-hold on a bud shows on any tab, with -or without Google Play services**, and every setting the app shows follows a change made elsewhere; the hold, the re-claim, the "Noise control with Google Play -services" setting and the Play-services line are removed (ADR-065; a stored choice is ignored). Its hardware run `CAP-076` (`ai-sessions/0092`, both users) showed -every press-and-hold on the noise-control tab within 0.1 s — 29 of 29 with Google Play services, 12 of 12 without. Known issues of 1.3.0 (also in -[`CHANGELOG.md`](./CHANGELOG.md)): a tap can still collide with Play's claim of the shared channel (it then usually fails with a message; tap again); after the -Buds' own close of the session (one bud out while both are worn) the mode shown can be a few seconds old until the re-open's read; after a reconnect a mode -that changed meanwhile is shown without the "Changed since the app last saw it" line. - -**Known issue since 1.0.1** (also in [`CHANGELOG.md`](./CHANGELOG.md)): - -- After you stop a ring by touching the bud, the app keeps saying it is ringing until you tap Stop. - -The three known issues of 1.0.0 (the connect error that blamed another app, values of the last connection shown as current, a silent choice between -several paired Buds) are fixed in 1.0.1. +**1.3.0 is the current release** (2026-10-15, build `ff27ee1`): the app asks the Buds to report every change of their settings on its own channel and reads +the noise-control mode at every Connect (ADR-064/065), so **a press-and-hold on a bud shows on any tab, with or without Google Play services**, and every +setting the app shows follows a change made elsewhere. Its hardware run `CAP-076` (`ai-sessions/0092`, both GrapheneOS users) showed every press-and-hold on the +noise-control tab within 0.1 s — 29 of 29 with Google Play services, 12 of 12 without. Tested on one phone — a Pixel 9a with GrapheneOS (Android 17) — with Buds +firmware `release_5.203`. Per feature, what is hardware-verified and what is only unit-tested is in `ARCHITECTURE.md` §5a; every release, its hardware run +and its known issues are in [`CHANGELOG.md`](./CHANGELOG.md). Found a bug? [Open an issue](https://github.com/tedsluis/opencontrolpixelbudspro2/issues/new/choose) — +a suspected security problem goes through [`SECURITY.md`](./SECURITY.md) instead. + +**Known issues of 1.3.0** (also in [`CHANGELOG.md`](./CHANGELOG.md)): + +- A tap on a noise-control mode can collide with Play's claim of the shared channel; it then usually fails with a message — tap again. +- After the Buds' own close of the session (one bud out while both are worn) the mode shown can be a few seconds old until the re-open's read. +- After a reconnect, a mode that changed meanwhile is shown without the "Changed since the app last saw it" line. +- After you stop a ring by touching the bud, the app keeps saying it is ringing until you tap Stop (since 1.0.1). + +**1.3.1 is prepared, not released** (`ai-sessions/0094`, 2026-10-15; its hardware run `CAP-077` is planned): it fixes the third issue above, makes "Use +different Buds" forget the Buds for real (with a confirmation), keeps a tile tap right after a Connect from failing silently, gives each noise-control mode one +name everywhere and lets TalkBack read the switches and the equalizer sliders — [`CHANGELOG.md`](./CHANGELOG.md) [1.3.1]. --- @@ -175,7 +136,7 @@ the Pixel Buds Pro 2 first has to be reconstructed through Bluetooth traffic analysis and reverse engineering of the Android APK. That knowledge is then used to design, implement, test, and document a native Android app. -## Current state (2026-10-14) +## Current state (2026-10-15) - **Captures:** 76 registered sessions (`CAP-001`–`CAP-076`): 72 analyzed (the latest, `CAP-076` of 2026-10-15: the 1.3.0 release build in both GrapheneOS users — every press-and-hold on the noise-control tab shown through the settings stream, 29 of 29 with Google Play services and 12 of 12 without, `ai-sessions/0092`), 2 planned, 2 withdrawn (`CAP-052`, `CAP-057`) — see `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 and `id_registry.csv`. `CAP-059`–`CAP-068` and `CAP-070`–`CAP-075` are captures of this project's own app (`CAP-067`: the 1.0.0 @@ -183,7 +144,7 @@ to design, implement, test, and document a native Android app. - **APK analysis:** one companion-app version fully pulled, decompiled, and analyzed (`v1.0.955078536-10253511`) — see `reverse-engineering/APK_VERSIONS.md`. DLCI 0x04/0x08's transport code is not in it (ADR-025): both channels are implemented independently, from wire-capture evidence (and, for DLCI 0x04, the public Fast Pair spec). -- **Decisions:** 63 accepted ADRs (ADR-065, 2026-10-14, the settings stream replaces the hold and the re-claim — built in 1.3.0, `ai-sessions/0091`; ADR-064, 2026-10-14, the subscription and the read of setting 13, `ai-sessions/0090`; ADR-061/062/063 superseded by ADR-065) and one labelled draft (ADR-060, proposed, not accepted) in `DECISIONS.md`; every 🟢 FACT in `PROTOCOL.md` has a recorded maintainer sign-off, except ADR-009, whose +- **Decisions:** 58 accepted, 6 superseded and one labelled draft ADR in `DECISIONS.md` (ADR-065, 2026-10-14, the settings stream replaces the hold and the re-claim — built in 1.3.0, `ai-sessions/0091`; ADR-064, 2026-10-14, the subscription and the read of setting 13, `ai-sessions/0090`; ADR-061/062/063 superseded by ADR-065, the draft: ADR-060, proposed, not accepted); every 🟢 FACT in `PROTOCOL.md` has a recorded maintainer sign-off, except ADR-009, whose approval was given but whose record is missing (the ADR says so). - **Implemented in the app:** ANC/Transparency/Adaptive (DLCI 0x04, ADR-009), Find My Buds Left/Right (ADR-011), EQ read and write (DLCI 0x02 pw_rpc, ADR-020/034), battery Left/Right with charging (ADR-033) and the Case (DLCI 0x02 `SubscribeRuntimeInfo`, ADR-043), @@ -192,7 +153,7 @@ to design, implement, test, and document a native Android app. the "probably worn" line (ADR-059) and "Changed by the Buds" (`ai-sessions/0082`; 1.2.0 … 1.2.2 held the noise-control channel on its tab and re-claimed it from Play services — ADR-061/062/063, retired), from 1.3.0 the Buds' settings stream on the app's own channel (`SubscribeToSettingsChanges` and `ReadSetting 4:13` at every Connect and re-open: a press-and-hold shows on any tab and every setting follows a change made elsewhere — ADR-064/065, `ai-sessions/0091`), firmware line, ANC Quick Settings tile, Safe Mode (ADR-042), the session re-open while visible (ADR-044). - **Protocol-known but not built:** the "Volume level notifications" switch (`qhr` field 21, 🟢 since 2026-10-06) and the notification behind it — studied in - `ai-sessions/0082` (the phone decides, from the `Dosimeter` daily doses; draft ADR-060, a 1.3.0 candidate, needs `WELL-002` first); the BLE battery advertisement + `ai-sessions/0082` (the phone decides, from the `Dosimeter` daily doses; draft ADR-060, a candidate for a later release, needs `WELL-002` first); the BLE battery advertisement (never matched on the wire). - **Still open (protocol):** EQ field 16-vs-18 save semantics, DLCI 0x08's own identity, why the Buds sometimes close the RFCOMM channels, whether the announced Maestro channel names the hosting bud — see `PROTOCOL.md` §6. @@ -245,9 +206,9 @@ environment, and without them the build stops with a clear message (it never sig the full procedure, including creating and backing up the key, is in [`RELEASING.md`](./RELEASING.md). A debug build and a release build are signed with different keys, so one cannot be installed over the other: uninstall first (this deletes the app's settings). -**Before testing against real hardware**, read `APP_TESTPLAN.md` — it says, step by step, what is to be checked on hardware (`CAP-070-EVENT-NOTES.md` +**Before testing against real hardware**, read `APP_TESTPLAN.md` — it says, step by step, what is to be checked on hardware (`CAP-076-EVENT-NOTES.md` shows how the last run followed it); the newest analysed run -(`CAP-070-FINDINGS.md`, the 1.1.0 release APK) and `ARCHITECTURE.md` §5a say what was seen working and what is only unit-tested. Given this project's own hardware-risk disclaimer above, do not assume "the tests pass" means "safe against your +(`CAP-076-FINDINGS.md`, the 1.3.0 release APK) and `ARCHITECTURE.md` §5a say what was seen working and what is only unit-tested. Given this project's own hardware-risk disclaimer above, do not assume "the tests pass" means "safe against your earbuds" — it means the wire bytes match known-good captures, nothing more. ## Approach diff --git a/RELEASING.md b/RELEASING.md index e7bb1ec6..818d06db 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -70,8 +70,10 @@ Added 2026-10-04 (`ai-sessions/0069`, the maintainer's request). `X.Y.Z` is the **E. After publishing — a small pull request of its own** -- [ ] E1. `CHANGELOG.md`: the date in the `[X.Y.Z]` block, a new empty `[Unreleased]`. `README.md`: "Latest release", Status, known issues — on a branch, - by pull request (`main` takes no direct push); a bot pull request may follow its merge. +- [ ] E1. `CHANGELOG.md`: the date in the `[X.Y.Z]` block, a new empty `[Unreleased]`. `README.md`: "Latest release", Status, known issues, "Current + state" (its date and counts); `PROJECT.md`: the Status section ("released", the tag); `ARCHITECTURE.md` §5a where the hardware run verified a feature; + then `grep -rn " … not released\| candidate" -- '*.md'` for leftovers — on a branch, by pull request (`main` takes no direct + push); a bot pull request may follow its merge. *(List extended 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` §6 row 17, A93-GOV-01/07.)* - [ ] E2. `RELEASING.md` §13: the Release log row (build commit, date, APK SHA-256, certificate SHA-256, capture). - [ ] E3. The session log that prepared the release: its "Commits" section and Status (`AI_SESSION_LOG_PROCEDURE.md` §4b); `TODO.md` §1 emptied. - [ ] E4. `scripts/release_notes.template`: remove this version's summary line, ready for the next one. @@ -143,6 +145,10 @@ flowchart TD maintainer-attested row is allowed only when it is written down as such, with the capture that will replace it. *Closed 2026-10-04 (`ai-sessions/0070`, maintainer-approved in chat, `AskUserQuestion` "DoD text"):* `CAP-068` holds the ANC change by the release build 1.0.1 with frames (`CAP-068-FINDINGS.md` §2); the exception applied to 1.0.0 only. + **Exception recorded for 1.2.2 (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 — `ai-sessions/0093` A93-SES-09, D10):** 1.2.2 was + published on 2026-10-14 after the maintainer's own test on the phone, without its hardware run (`CAP-076` was not run for it; the maintainer's choice in chat + 2026-10-14, `ai-sessions/0089`) — checklist C1–C4 were skipped. Its Release log row says "— (CAP-076 not run)"; 1.3.0's run `CAP-076` covered the code that + followed. The exception applied to 1.2.2 only. - Every arrow that publishes (tag push, release create/edit/upload) is the maintainer's own act (see the top of this document). (GitHub renders the diagram; the docs site shows its source text.) diff --git a/REVERSE_ENGINEERING.md b/REVERSE_ENGINEERING.md index a5a48a65..0ca8819c 100644 --- a/REVERSE_ENGINEERING.md +++ b/REVERSE_ENGINEERING.md @@ -985,7 +985,7 @@ the first time against every one of its 20 real discriminators + default branch. --class gcn` shows `defpackage.gck` (the already-known `device_info` sink this document and `DECISIONS.md` ADR-025 already cite) **holds a `gcn`-typed field** (`gck.b`) — i.e. `gck` itself wraps this exact DAO internally. Reading `gcn.java` directly (the abstract base `gcp extends gcn`) - confirms it: `gcn.f(String, gcm)` — the very method `esk`'s default branch (line 894 above) calls — + confirms it: `gcn.f(String, gcm)` — the very method `esk`'s default branch (the "Default branch" bullet above) calls — is `gcn`'s own inherited implementation, and its body **constructs `new gcl(...)`** (`gcn.java`: `gwv gwvVar = new gwv((Object) str, (Object) Long.valueOf(...), (Object) new gcl(gcmVar.a(...)), ...)` ) before calling the abstract `g(gwvVar)` write — i.e. `gcl` (the domain/value-object type) is @@ -1152,7 +1152,7 @@ the first time against every one of its 20 real discriminators + default branch. | 2 | BOOL | `fyo.java:169-188` (`l`) | case 2 (`:52-59`), no distinct log | **On-Head/In-ear Detection — category-level identity confirmed 2026-09-08**, via `MaestroDeviceSettingsProviderService` case `2102`'s internal category log `CATEGORY_OHD` (see that entry's update) | | 3 | BOOL | `fyo.java:212-232` (`n`) | case 3 (`:60-78`), `"Log OOBE Is Finished setting"` | **OOBE (out-of-box-experience) completion flag** | | 4 | BOOL | `fyo.java:124-144` (`h`) | case 4 (`:79-97`), `"Log Gestures Enable setting"` | **Head/touch gestures master enable toggle** | - | 5 | BOOL | `fyo.java:102-122` (`f`) | case 5 (`:98-105`), no distinct log | not independently named | + | 5 | BOOL | `fyo.java:102-122` (`f`) | case 5 (`:98-105`), no distinct log | not independently named *(named since 2026-10-06: "diagnostics" — the `0073` Update below; pointer 2026-10-15, `ai-sessions/0094`)* | | 6 | BOOL | `fyo.java:191-210` (`m`) — **found 2026-09-08, corrects this row's prior "not found" entry**; own caller not located (checked `fyb`'s cases 0-18 and `hqy.java`, neither reaches `.m()`) | unhandled (default) | write site exists; still read-inert and un-triggered from any found caller | | 7 | MESSAGE→`qju` | `fyo.java:300-374` (`t(gdx)`) | case 7 (`:124-219`), `"Log Gestures Customization for touch and hold setting, left: %s, right: %s"` | **touch-and-hold gesture customization, Left/Right** — see updated `qju` entry below | | 8 | BOOL | not found | unhandled (default) | write-silent, read-inert | @@ -1161,7 +1161,7 @@ the first time against every one of its 20 real discriminators + default branch. | 11 | BOOL | `fyo.java:146-166` (`j`) | case 11 (`:220-227`), no distinct log | **Multipoint — confirmed** (self-describing log one layer up, at the ViewModel: `hiy.java:32`'s `"Set device Multipoint as: %s"`, reached from `MultipointFragment`'s toggle — see 2026-09-08 update below) | | 12 | MESSAGE→`qht` | `hgj.java:216-331` (ANC gesture-loop preference screen) | case 12 (`:228-277`), `"Log ANC gesture loop to Clearcut"` | **ANC gesture-loop membership (On/Off/Transparency/Adaptive)** — see updated `qjg`/`qht` entry below | | 13 | ENUM `qhs` | `fye.java:17-39` (2 callers, see main finding above) | case 13 (`:278-311`), `"Log ANC settings to Clearcut"` | **ANC state (Off/Active/Aware/Adaptive) — confirmed** | - | 14 | BOOL | `fyo.java:234-254` (`o`) | unhandled (default) | write-only; response ignored | + | 14 | BOOL | `fyo.java:234-254` (`o`) | unhandled (default) | write-only; response ignored *(the eartip seal test, OTTS — the `0073` Update below; pointer 2026-10-15, `ai-sessions/0094`)* | | 15 | BOOL | `fyo.java:168-188`→`u` (`fyo.java:376-396`) | case 15 (`:312-319`), no distinct log | **Volume EQ — confirmed** (self-describing log + literal preference-key string one layer up: `hlv.java:2127`'s `"Set volume eq: %s"`, gated on `str.equals("volume_eq_switch")` — see 2026-09-08 update below) | | 16 | MESSAGE→`qjw` | `fyp.java:301-322` (`f`, "update user eq") | case 16 (`:320-323`), `"received user eq setting value"` | **live/current user EQ curve** — see updated `qjw` entry below | | 17 | `SINT32` (per this entry's own field register above, line 856) | `fxf.java:82-133` (case 16 of that dispatcher) | case 17 (`:324-327`), `"received last saved volume balance setting value"` | **volume balance** | @@ -1231,7 +1231,7 @@ the first time against every one of its 20 real discriminators + default branch. raw logs, plus the same static call-site tracing already used above): - **Field 17 ("volume balance") — full identity promoted.** `CAP-022` frames 1922/1944/2019/2039/2056/2073/2099, CRC-32 verified, decode to `qhr` field 17 across a - continuous drag gesture. This entry's own field register (line 856) already types field 17 as + continuous drag gesture. This entry's own field register (the 38-field decode at the top of this entry) already types field 17 as `SINT32`; the bonus table above previously mistyped it as "INT32/BOOL-adjacent" (corrected in place this session) — re-reading the 7 sampled wire values as zigzag-encoded `SINT32` (not raw unsigned varints) gives `-100, -62, -25, 15, 75, 100, 5` (previously `199, 123, 49, 30, 150, 200, @@ -1244,7 +1244,7 @@ the first time against every one of its 20 real discriminators + default branch. independent write paths to the same field, not one:** (a) the dedicated Mono-audio toggle (`fyo.java:278-298`, `s(boolean)`) — this field's established, primary identity, unchanged by the below; and (b) `fxf.java:113-133`'s volume-balance-extreme-value side effect (part of the - same case-16 dispatcher that writes field 17, line 999 above) — a genuine **secondary** write + same case-16 dispatcher that writes field 17, the register's row 17 above) — a genuine **secondary** write to field 19, triggered when an extreme balance value is set, not an alternative identity for the field. Both write sites were independently re-confirmed maintainer-side (`ai-sessions/ 0008_CROSSCHECK_RESULT_2026_09_11.md` §1.18/§1.22); "Mono audio" remains field 19's sole @@ -1430,7 +1430,7 @@ the first time against every one of its 20 real discriminators + default branch. duplicate. Not promoted (per dimension (a)/(b) of `DECISIONS.md` ADR-019's 2026-09-03 clarifying note: an independent code-side path now exists and is self-describing, but no wire capture has ever observed field 6, so this is a code-level HYPOTHESIS only). This closes the field-6-specific - part of item G; the field 6 row in this entry's own bonus register (line ~1068) should be read + part of item G; the field 6 row in this entry's own bonus register (the table above) should be read together with this update. - **Correction (2026-10-06, `ai-sessions/0073`, citation check; maintainer-approved in chat 2026-10-06, `AskUserQuestion` "Corrections", option *"Apply all eight (Recommended)"*):** in the register above, row 15's first citation @@ -2504,8 +2504,8 @@ MaestroEmptyMarker entry above). - **Update (2026-10-07, `ai-sessions/0077`, maintainer-approved in chat 2026-10-07, `AskUserQuestion` "D3 Wallclock"):** the wire carries a top-level field 2 in the `qiy` (`SubscribeRuntimeInfo`) and `qjb` (`GetSoftwareInfo`) answers that this schema does not name — 🟡 HYPOTHESIS: the Buds' wall clock in ms since the Unix epoch, present only after a `SetWallclock` (52–548 ms before the capture time in `CAP-036`/`042`/`050`; absent in OpenControl's `CAP-062`, `CAP-067` and `CAP-070` — 0 of 155 — which send no `SetWallclock`). `DESKRESEARCH_FINDINGS.md` 2026-10-03 item 3. Field 3 stays open. **Open questions**: none of the "plausibly" readings above are anything more than ⚪ ASSUMPTION from -field count/type/RPC-name context — none is capture-correlated. Included here only to keep this *(pointer 2026-10-03, `ai-sessions/0069`, A68-RE-03: `qiy` (`SubscribeRuntimeInfo`) and `qiv` (`GetHardwareInfo`) are capture-correlated since ADR-043 / `PROTOCOL.md` §6, 2026-09-24)* -register complete for whoever picks up the corresponding capture-correlation work next. +field count/type/RPC-name context — none is capture-correlated. Included here only to keep this +register complete for whoever picks up the corresponding capture-correlation work next. *(pointer 2026-10-03, `ai-sessions/0069`, A68-RE-03: `qiy` (`SubscribeRuntimeInfo`) and `qiv` (`GetHardwareInfo`) are capture-correlated since ADR-043 / `PROTOCOL.md` §6, 2026-09-24)* *(moved out of the sentence 2026-10-15, `ai-sessions/0094`, A93-RE-06)* **Update (2026-09-13, `ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.md` Phase 3) — `qin`'s single `ENUM` field's own validity range decoded; no Nod/Shake-named constant found anywhere in the @@ -3228,6 +3228,8 @@ natural next step for whoever picks this up (search for `X.class` and `X.a` refe either); case 2104's `CATEGORY_MULTIPOINT`-vs-`fpm.ENABLED_HEAD_GESTURES` naming tension; what `"Feature A"`/`kjj`/"premiumAudioHelper" actually is (cases 2104's side effect, 2115's direct toggle); `fyo.m()` (field 6)'s own caller. + *(Pointer 2026-10-15, `ai-sessions/0094`, A93-RE-06: field 5 is named "diagnostics" and field 6's caller is `guy`'s OOBE mode — the `qhr` entry's + Updates of 2026-09-16 (`0025`) and 2026-10-06 (`0073`); "Feature A" = "Quartz", the Resource/string-table sweep.)* - **Hypothesis test**: none required for the case-ID→accessor traces above (🟢 FACT, static call-graph tracing with no ambiguous branch); the remaining open questions are further static-analysis tasks (field 5/32/6/"Feature A" naming) or a capture task (wire-confirming field @@ -3281,7 +3283,7 @@ natural next step for whoever picks this up (search for `X.class` and `X.a` refe RxJava-style hot-publisher field) — the log fires whenever something else pushes a new `goq` value onto that field, not on a fixed schedule or directly on connect. - `fuh` — a two-method start/stop-shaped lifecycle interface (`void b(); void i();`); 6 classes - implement it: `fxm`, `fvp` (Dosimeter), `fwc`, `fwx` (RuntimeInfo/low-latency-mode), `fxg`, + implement it *(seven — the count "6" corrected 2026-10-15, `ai-sessions/0094`, A93-RE-02)*: `fxm`, `fvp` (Dosimeter), `fwc`, `fwx` (RuntimeInfo/low-latency-mode), `fxg`, `fyq`, `geg`. - `glk.j():530-534` — `if (optional.isPresent() && this.g.F() && this.v.q()) { fuh.i() }`, inside a class whose surrounding code (`glk.java:220-260`, `500-530`) is unambiguously **firmware OTA @@ -3345,6 +3347,7 @@ natural next step for whoever picks this up (search for `X.class` and `X.a` refe ftjVar.i(str).p(); // == gjv.p(), since gjv is giz's sole subclass } ``` + *(Correction 2026-10-15, `ai-sessions/0094`, A93-RE-02: `ai-sessions/0013` had already found this call site on 2026-09-13 — its Update is the one placed below this one; "both prior passes" means the two `giz`-field searches.)* This was missed by both prior passes because the call is an **inline chain** (`ftjVar.i(str).p()`, the `giz` result never stored in a field) rather than a call through a `giz`-typed field — the prior strategy ("find classes holding a `giz`-typed field, check if any @@ -3371,7 +3374,8 @@ natural next step for whoever picks this up (search for `X.class` and `X.a` refe obtained 2026-09-16** (chat session continuing `ai-sessions/0023`): the call-site finding is a direct code fact; the interpretive "OTA-lifecycle, not connect-lifecycle" reading is accepted at 🟡 HYPOTHESIS in `PROTOCOL.md` §6, per `AGENTS.md` §6. - decoded shape) is now the more promising route — pursued this same session, see `PROTOCOL.md` §6's + *(Restored 2026-10-15, `ai-sessions/0094`, `ai-sessions/0093` A93-RE-02: an edit of 2026-09-15 cut this sentence; its start, from commit `08fc3ad`, was "the byte-level + capture-correlation path (against `qjb`'s")* decoded shape) is now the more promising route — pursued this same session, see `PROTOCOL.md` §6's `CAP-041`/`CAP-036` connect-time-burst item and `ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.md` Phase 4 item 1. - **Correlation with `PROTOCOL.md`**: §6 Commands & schemas' existing open item on the connect-time @@ -4074,7 +4078,7 @@ via a capture, update its status here **and** promote it into `PROTOCOL.md`. | UUID | Found in (file:line) | Suspected function | Status | |---|---|---|---| | `3a046f6d-24d2-7655-6534-0d7ecb759709` (byte-reversed alias: `099775cb-7e0d-3465-5576-d2246d6f043a`) | `fzd.java:9`, `gbm.java:38` | App's own log label: "default internal rfcomm socket" | 🟡 HYPOTHESIS — not yet matched against a capture's SDP record *(status 2026-10-03, A68-RE-03: never seen in any capture — exhaustive negative in the `gbm` entry; not DLCI 0x08, whose SDP record is "GSND CONTROL", `CAP-033` frame 1279)* | -| `25e97ff7-24ce-4c4c-8951-f764a708f7b5` (byte-reversed alias: `b5f708a7-64f7-5189-4c4c-ce24f77fe925`) | `fzd.java:9`, `gbm.java:35` | App's own log label: "pigweed internal rfcomm socket" — SDP-confirmed (`CAP-001`/`CAP-002`/`CAP-032`) as RFCOMM server channel 1 = DLCI 0x02, AGENTS.md §6's Pigweed `pw_hdlc` channel | 🟢 FACT for channel ownership (confirmed by capture IDs `CAP-001`/`CAP-002`/`CAP-032`, `DECISIONS.md` ADR-018, `PROTOCOL.md` §2.2a); 🟡 HYPOTHESIS (strong) that Sent-direction payload content specifically carries `libmaestro`'s settings commands | +| `25e97ff7-24ce-4c4c-8951-f764a708f7b5` (byte-reversed alias: `b5f708a7-64f7-5189-4c4c-ce24f77fe925`) | `fzd.java:9`, `gbm.java:35` | App's own log label: "pigweed internal rfcomm socket" — SDP-confirmed (`CAP-001`/`CAP-002`/`CAP-032`) as RFCOMM server channel 1 = DLCI 0x02, AGENTS.md §6's Pigweed `pw_hdlc` channel | 🟢 FACT for channel ownership (confirmed by capture IDs `CAP-001`/`CAP-002`/`CAP-032`, `DECISIONS.md` ADR-018, `PROTOCOL.md` §2.2a); 🟡 HYPOTHESIS (strong) that Sent-direction payload content specifically carries `libmaestro`'s settings commands *(🟢 since ADR-019 (2026-08-30) and ADR-034 (2026-09-20): pw_rpc `maestro_pw.Maestro`, `WriteSetting`/`ReadSetting` — pointer 2026-10-15, `ai-sessions/0094`, A93-RE-05)* | | `00001124-0000-1000-8000-00805f9b34fb` | `fxm.java:12` | Bluetooth SIG-assigned HID Profile UUID (public spec, not project-specific) — app checks for it before triggering `fetchUuidsWithSdp()` | 🟢 FACT (that this official UUID is checked for); whether the Buds actually expose it is capture-dependent — cross-reference `CAP-002`/`CAP-016` | **Checked and confirmed absent, 2026-09-09 (`ai-sessions/0004_MAINTENANCE_RESULT_2026_09_09.md` @@ -4141,7 +4145,7 @@ smali). **0 hits** for the 19 UUIDs the captures show: the Buds' SDP/EIR UUIDs ` ## Message Group / Code register (Fast Pair Message Stream) -If the Message Stream framing hypothesis (`PROTOCOL.md` §2.1) is confirmed, +If the Message Stream framing hypothesis (`PROTOCOL.md` §2.1) is confirmed *(it is — 🟢 since ADR-009; pointer 2026-10-15, `ai-sessions/0094`)*, use this table for vendor-specific Message Group/Code values found in the APK, in addition to the officially documented ones. @@ -4482,7 +4486,7 @@ only the same two files listed above — no new native library has appeared for > **Updated 2026-08-30 (`DECISIONS.md` ADR-017, superseding ADR-003):** native > `.so` disassembly is now in scope for AI *mechanical* assistance, on the -> same terms as DEX/Java-level work — an AI session may run `pbtk-from-binary` +> same terms as DEX/Java-level work *(`pbtk` does not apply to this APK — ADR-041; pointer 2026-10-15, `ai-sessions/0094`)* — an AI session may run `pbtk-from-binary` > or disassembler tooling (Ghidra/radare2), search its output, and explain the > syntax/structure of already-surfaced disassembly. An AI session still does > **not** decide which disassembled function/struct is relevant, and does @@ -4509,7 +4513,9 @@ gbm.a() [InternalRfcommSocketSelector] -> gbd (via b or b2) [InternalRfcommConnection — wraps BluetoothSocket] fxm.i() [MaestroSoftwareInfoAndHidUuidCheck] - -> fxm.c(goq, goq) x4 [maestro_pw.Maestro/GetSoftwareInfo pw_rpc unary, + -> fxm.c(goq, goq) x4 [opens 4 pending GetSoftwareInfo unary calls — no request is sent; + the Buds' unsolicited announcement completes them (corrected 2026-10-15, 0094, A93-RE-04; + the qjb entry's Update of 2026-10-06) — maestro_pw.Maestro/GetSoftwareInfo pw_rpc unary, one per {MAESTRO_A,MAESTRO_B} x {LEFT_BT_CORE,RIGHT_BT_CORE}] -> BluetoothDevice.fetchUuidsWithSdp() [only if HID UUID 0x1124 not yet present] @@ -4577,7 +4583,12 @@ promoted into the protocol documentation, to avoid the same finding being | `qie` entries 1/2/3 → Case / Left / Right (the official app's firmware screen) | §2.2a | 2026-10-01 | `ai-sessions/0062`; 191 announcements | | `qhr` field 29 write site (`cmi`, discriminator 5, `key_head_gestures_toggle`) — recorded, **not promoted** (🟡 1 = off, 2 = on) | §4.5.4 (2026-10-03 Update) | 2026-10-03 | `CAP-020` 1183/1935/2038; `ai-sessions/0069` | | The pw_rpc name table (`0xbf6c9399` = `a10a20.kpi.Kpi`/`KpiStream`, `0x8d99df93` = `JitterBuffer.SetJitterBufferSizePreference`), the sweep `fyo.a()`, the write census, the device-type → schema map, the announcement path, the charger-type enum — recorded, **not promoted** | §2.2a, §4.2, §4.3 Option F, §4.5.9, §6 (dated notes) | 2026-10-06 | 80 logs; `CAP-069` 965/1894; `ai-sessions/0073` | -| | | | | +| `qhr` field 21 = the switch "Volume level notifications" (Device details → Hearing wellness; the write site `hey.java:165–190`) | §4.5.8a | 2026-10-06 | `CAP-058` 5747/5753/5767/5790; `ai-sessions/0072` | +| The wall-clock field 2 of the `qiy`/`qjb` answers — recorded 🟡, **not promoted** | §6 / this document's schema table (2026-10-07 Update) | 2026-10-07 | `CAP-036`/`042`/`050`; `ai-sessions/0077` | +| `GetHardwareInfo` field 7 read by position (7.1 Case, 7.2 Right, 7.3 Left; `gaa.java:45–96`, `fwg.java:182–215`) | §6 (2026-10-09 Update) | 2026-10-09 | `ai-sessions/0082`; ADR-058 | +| Setting 13 = the companion app's only ANC-state source (`fxb.smali:1237` → `gea.I`); the stream path `fxg` → `fvo` → `fxb` | §4.1 (2026-10-14 Update) | 2026-10-14 | 46 official-app captures; `ai-sessions/0090`; ADR-064 | + +*(Rows added 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-RE-03; the empty template row removed.)* ## Known limitations of this analysis @@ -4592,7 +4603,7 @@ promoted into the protocol documentation, to avoid the same finding being or capture correlation before being treated as 🟢 FACT. - A class or method being present in the APK does not prove it is actually exercised by the specific user actions listed in - `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` — treat static findings as 🟡 HYPOTHESIS + `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` — treat static findings *(as a reading of protocol behaviour; that the code exists is 🟢 (code) — clarified 2026-10-15, `ai-sessions/0094`)* as 🟡 HYPOTHESIS until correlated with a capture showing the corresponding traffic. - **Low priority, noted 2026-09-07 (`EXTERNAL_REVIEW_VALIDATION_2026-09-07.md` — *retired after processing, `CHANGELOG.md`*):** file+line citations in this document are only reproducible against the exact decompiler version that produced diff --git a/SECURITY.md b/SECURITY.md index a316df2b..749555a4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -15,7 +15,8 @@ What the code does about it (added 2026-09-24, `ai-sessions/0045`; corrected 202 `MalformedFrame`/`UnidentifiedFrame` instead of throwing — **in 1.0.0 two length checks did not hold that promise** (a declared length near the top of its integer range overflowed the check and the reader threw or looped; found by the `ai-sessions/0068` audit, fixed in 1.0.1). Since 1.0.1 the router also decodes each frame inside a guard, so a reader that fails costs that one frame and never the stream, and the fuzz tests mutate real captured frames -*inside* their payload and re-seal them with a valid CRC (random bytes rarely get past the CRC, so the older fuzz tests did not reach those readers); the per-channel frame splitters are reset +*inside* their payload and re-seal them with a valid CRC (random bytes rarely get past the CRC, so the older fuzz tests did not reach those readers) — and since +`ai-sessions/0094` they also count the guard's catches and require none, so a reader that throws fails the test instead of being hidden as a malformed frame; the per-channel frame splitters are reset whenever a channel opens, closes or is lost, and cap a single frame (1024 data bytes on the Message Stream channels, 4096 bytes for a pw_hdlc frame), so one corrupted length field cannot stall later frames; and every write — ANC, Find My Buds, EQ and the settings (ADR-045/046/047) — passes the Safe-Mode gate @@ -24,6 +25,11 @@ announced the Pixel Buds Pro 2's Fast Pair Model ID; on DLCI 0x02 (EQ, settings) (`BudsRepositoryImpl.writeGate`; corrected 2026-09-30, `ai-sessions/0059` A58-HK-05). A report that one of these can be bypassed is in scope. +Since 1.3.0 (ADR-064/065) the app also parses the Buds' **settings stream** on DLCI 0x02 (`SERVER_STREAM` packets of +`SubscribeToSettingsChanges`) through the same settings decoders as a read answer; it is fuzzed like the other decoders +(random, truncated, oversized and unknown-field packets, field 13 with every byte value — `SettingsStreamCodecTest`). Nothing is +written in reaction to a stream packet. *(Added 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` §6 row 18.)* + Out of scope: the reverse-engineering research itself (`REVERSE_ENGINEERING.md`, `captures/`) is not a security-sensitive artifact — it documents protocol *behavior*, not a vulnerability in Google's software. diff --git a/TESTPLAN_BLUETOOTH_HCI_SNOOP.md b/TESTPLAN_BLUETOOTH_HCI_SNOOP.md index afe71dcf..72fc60ab 100644 --- a/TESTPLAN_BLUETOOTH_HCI_SNOOP.md +++ b/TESTPLAN_BLUETOOTH_HCI_SNOOP.md @@ -22,7 +22,7 @@ for why findings live in exactly one place. ### 0.1 A Test-ID is not a Group — this is the core design decision -`CAPTURE_BLUETOOTH_HCI_SNOOP.md` groups actions into **capture scenarios** (Group A–Z, then AA–BE as of 2026-10-03; +`CAPTURE_BLUETOOTH_HCI_SNOOP.md` groups actions into **capture scenarios** (Group A–Z, then AA–BL as of 2026-10-15; Z is the pipeline validation) — these describe *how to efficiently run a capture session* covering several related actions at once. A Group is an operational bundle, not a research question. @@ -115,10 +115,10 @@ _Make sure the buds are connected and active._ | ID | Description | Initiator | Capture scenario(s) | Existence source | Note | Evidence | |---|---|---|---|---|---|---| -| `ANC-001` | ANC → Off | User (App) | B, AU, AV, AX, AY, AZ, BA, BB, BG, BE, BI, BJ, BK, BL | 🟢 | | `PROTOCOL.md` §4.1; also `CAP-059`, `CAP-060` (`ai-sessions/0043`, app-validation runs); `CAP-061`: attempted, nothing sent (Safe Mode, `CAP-061-FINDINGS.md` §1); `CAP-062`: OFF via the tile ACKed worn (frame 8755), NAK `0x02` when not worn (`CAP-062-FINDINGS.md` §2); `CAP-063` (Group AY, `CAP-063-FINDINGS.md`); `CAP-064`: tile ADAPTIVE→OFF worn, `Set 20` 3835 → ACK 3843; a tile `Set` with the buds on the table NAKed 3440 (reason 0x02); `CAP-065`: tile ADAPTIVE→OFF `Set 20` 2567 → ACK 2579 and 3337 → 3346 (worn); BA-5 Refresh with both loose → `Notify … 00 20` 5465; `CAP-066` (Group BB, `0062` build): BB-1 Refresh `08 11` → `Notify … 00 20` (A1652), BB-3 tile with `00` → no `Set` (A1951–1972), BB-4t tile `Notify 40` → `Set 20` A2370 → ACK A2372, BB-8 `Set 20` A5030 → ACK A5033, BB-5 six Refreshes → `00` (`CAP-066-FINDINGS.md` §2–§3) Planned: `CAP-068` (Group BD) — a `Set` by the release build. `CAP-068`: release build 1.0.1 without Play, tab and tile (`CAP-068-FINDINGS.md` §2).; `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §3); `CAP-069` (Group BE, `CAP-069-EVENT-NOTES.md`) | -| `ANC-002` | ANC → Noise Cancellation | User (App) | B, AU, AV, AX, AY, AZ, BA, BG, BE, BI, BJ, BK, BL | 🟢 | Sends a configuration command to the buds. | `PROTOCOL.md` §4.1; also `CAP-059`, `CAP-060`; `CAP-061`: attempted, nothing sent (Safe Mode, `CAP-061-FINDINGS.md` §1); `CAP-062`: ACK worn (10348), NAK `0x02` not worn (6107, 8344 …); `CAP-063` (Group AY, `CAP-063-FINDINGS.md`); `CAP-065`: in the app (F7) `Set 08` 10790 → ACK 10799, Home 1 s later; via the tile 2806 → 2814; one tile `Set 08` ACKed (2651) after the app's claim was closed — lost to the app (`CAP-065-FINDINGS.md` §3) `CAP-068`: release build 1.0.1, tab and tile (`CAP-068-FINDINGS.md` §2).; `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §3); `CAP-069` (Group BE, `CAP-069-EVENT-NOTES.md`) | -| `ANC-003` | ANC → Adaptive | User (App) | B, AU, AV, AX, AY, AZ, BA, BB, BG, BE, BI, BJ, BK, BL | 🟢🔵 | Pro 2-specific; added in firmware 4.467 (Sept. 2025). Automatically adjusts volume to the environment. | `PROTOCOL.md` §4.1; also `CAP-059`, `CAP-060`; `CAP-061`: attempted, nothing sent (Safe Mode, `CAP-061-FINDINGS.md` §1); `CAP-062`: ACK worn (8705, 8929), NAK `0x02` not worn (6050, 10127); `CAP-063` (Group AY, `CAP-063-FINDINGS.md`); `CAP-064`: ADAPTIVE tapped with the buds on the table → ACKed 2698 (Settable `e8`, §3) and worn → ACK 2988; `CAP-065`: via the tile `Set 40` 3045 → ACK 3052 (worn); `CAP-066`: BB-2 Adaptive with `00` → `08 11` only, no `Set` (A1778–1800); BB-4 worn → `08 11` → `e8` → `Set 40` A2202 → ACK A2204 `CAP-068`: release build 1.0.1, tile only (`CAP-068-FINDINGS.md` §2).; `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §3); `CAP-069` (Group BE, `CAP-069-EVENT-NOTES.md`) | -| `ANC-004` | ANC → Transparency | User (App) | B, AU, AV, AX, AZ, BA, BB, BG, BE, BI, BJ, BK, BL | 🟢 | | `PROTOCOL.md` §4.1; also `CAP-059`, `CAP-060`; `CAP-061`: attempted, nothing sent (Safe Mode, `CAP-061-FINDINGS.md` §1); `CAP-062`: ACK worn (8864), NAK `0x02` not worn (5998, 6155); `CAP-065`: via the tile `Set 80` 2869 → ACK 2879 (worn); not tapped in the app (→ `CAP-066` BB-8); `CAP-066`: BB-8 in the app → `08 11` → `e8` → `Set 80` A4879 → ACK A4881 `CAP-068`: release build 1.0.1, tab and tile (`CAP-068-FINDINGS.md` §2).; `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §3); `CAP-069` (Group BE, `CAP-069-EVENT-NOTES.md`) | +| `ANC-001` | ANC → Off | User (App) | B, AU, AV, AX, AY, AZ, BA, BB, BG, BE, BI, BJ, BK, BL, BM | 🟢 | | `PROTOCOL.md` §4.1; also `CAP-059`, `CAP-060` (`ai-sessions/0043`, app-validation runs); `CAP-061`: attempted, nothing sent (Safe Mode, `CAP-061-FINDINGS.md` §1); `CAP-062`: OFF via the tile ACKed worn (frame 8755), NAK `0x02` when not worn (`CAP-062-FINDINGS.md` §2); `CAP-063` (Group AY, `CAP-063-FINDINGS.md`); `CAP-064`: tile ADAPTIVE→OFF worn, `Set 20` 3835 → ACK 3843; a tile `Set` with the buds on the table NAKed 3440 (reason 0x02); `CAP-065`: tile ADAPTIVE→OFF `Set 20` 2567 → ACK 2579 and 3337 → 3346 (worn); BA-5 Refresh with both loose → `Notify … 00 20` 5465; `CAP-066` (Group BB, `0062` build): BB-1 Refresh `08 11` → `Notify … 00 20` (A1652), BB-3 tile with `00` → no `Set` (A1951–1972), BB-4t tile `Notify 40` → `Set 20` A2370 → ACK A2372, BB-8 `Set 20` A5030 → ACK A5033, BB-5 six Refreshes → `00` (`CAP-066-FINDINGS.md` §2–§3) `CAP-068` (Group BD, run 2026-10-04) — a `Set` by the release build: release build 1.0.1 without Play, tab and tile (`CAP-068-FINDINGS.md` §2).; `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §3); `CAP-069` (Group BE, `CAP-069-EVENT-NOTES.md`) | +| `ANC-002` | ANC → Noise Cancellation | User (App) | B, AU, AV, AX, AY, AZ, BA, BG, BE, BI, BJ, BK, BL, BM | 🟢 | Sends a configuration command to the buds. | `PROTOCOL.md` §4.1; also `CAP-059`, `CAP-060`; `CAP-061`: attempted, nothing sent (Safe Mode, `CAP-061-FINDINGS.md` §1); `CAP-062`: ACK worn (10348), NAK `0x02` not worn (6107, 8344 …); `CAP-063` (Group AY, `CAP-063-FINDINGS.md`); `CAP-065`: in the app (F7) `Set 08` 10790 → ACK 10799, Home 1 s later; via the tile 2806 → 2814; one tile `Set 08` ACKed (2651) after the app's claim was closed — lost to the app (`CAP-065-FINDINGS.md` §3) `CAP-068`: release build 1.0.1, tab and tile (`CAP-068-FINDINGS.md` §2).; `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §3); `CAP-069` (Group BE, `CAP-069-EVENT-NOTES.md`) | +| `ANC-003` | ANC → Adaptive | User (App) | B, AU, AV, AX, AY, AZ, BA, BB, BG, BE, BI, BJ, BK, BL, BM | 🟢🔵 | Pro 2-specific; added in firmware 4.467 (Sept. 2025). Automatically adjusts volume to the environment. | `PROTOCOL.md` §4.1; also `CAP-059`, `CAP-060`; `CAP-061`: attempted, nothing sent (Safe Mode, `CAP-061-FINDINGS.md` §1); `CAP-062`: ACK worn (8705, 8929), NAK `0x02` not worn (6050, 10127); `CAP-063` (Group AY, `CAP-063-FINDINGS.md`); `CAP-064`: ADAPTIVE tapped with the buds on the table → ACKed 2698 (Settable `e8`, §3) and worn → ACK 2988; `CAP-065`: via the tile `Set 40` 3045 → ACK 3052 (worn); `CAP-066`: BB-2 Adaptive with `00` → `08 11` only, no `Set` (A1778–1800); BB-4 worn → `08 11` → `e8` → `Set 40` A2202 → ACK A2204 `CAP-068`: release build 1.0.1, tile only (`CAP-068-FINDINGS.md` §2).; `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §3); `CAP-069` (Group BE, `CAP-069-EVENT-NOTES.md`) | +| `ANC-004` | ANC → Transparency | User (App) | B, AU, AV, AX, AZ, BA, BB, BG, BE, BI, BJ, BK, BL, BM | 🟢 | | `PROTOCOL.md` §4.1; also `CAP-059`, `CAP-060`; `CAP-061`: attempted, nothing sent (Safe Mode, `CAP-061-FINDINGS.md` §1); `CAP-062`: ACK worn (8864), NAK `0x02` not worn (5998, 6155); `CAP-065`: via the tile `Set 80` 2869 → ACK 2879 (worn); not tapped in the app (→ `CAP-066` BB-8); `CAP-066`: BB-8 in the app → `08 11` → `e8` → `Set 80` A4879 → ACK A4881 `CAP-068`: release build 1.0.1, tab and tile (`CAP-068-FINDINGS.md` §2).; `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §3); `CAP-069` (Group BE, `CAP-069-EVENT-NOTES.md`) | | `ANC-006` | Tap the ANC mode that is already selected | User (App) | BD, BE | 🟡 | **Added 2026-10-03 (`ai-sessions/0069`, lead L68-7).** `CAP-001`'s two taps without a `Set` (08:51:32, 08:51:39) came when the Buds' `Notify` already read the tapped mode (1895 `… e8 e8 80`, 1990 `… e8 e8 20`) — 🟡 the official app sends nothing for a tap on the current mode. For OpenControl: does a `Set` of the current mode get an ACK or NAK reason `0x04` ("redundant device action")? (`ANC-005` is a declined ID.) | `CAP-001-FINDINGS.md` §5; `PROTOCOL.md` §4.1 `CAP-068` (`ai-sessions/0070`): OpenControl 1.0.1 sends the `Set`, the Buds ACK it (A2034 → A2036; `PROTOCOL.md` §4.1 Update 2026-10-04).; `CAP-069` §4: official app sends no `Set` (0 of 3) | | `CONV-001` | Toggle 'Conversation Detection' on/off | User (App) | C, AY, BG | 🟢 | Switches to Transparency and pauses media when you speak. | `PROTOCOL.md` §4.5.1; `CAP-063` (Group AY, `CAP-063-FINDINGS.md`); `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §3) | | `MULTI-001` | Toggle 'Multipoint' on/off | User (App) | C, BE, BF, BG | 🟢 | Connects to 2 Bluetooth devices simultaneously; may trigger an SDP/connection update. | `PROTOCOL.md` §4.5.2 (both directions in `CAP-019`: ON 2293, OFF 2482); `CAP-069` §2 (4 filmed writes + SASS bit 2, ADR-053); `CAP-070` §3 (OpenControl 1.1.0: ch 21 and ch 19, ACKed); `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §3) | @@ -147,7 +147,7 @@ _Make sure the buds are connected and active._ | `AUDIO-003` | Shift the 'Volume balance' (Left/Right) | User (App) | H, AK, AY, AZ, BA, BB, BC, BF, BG, BI | 🟢 | **Note:** stored locally on the earbuds themselves (persistent write, works across devices) — explicitly stated in the app's own screenshot. **Scale/direction resolved 2026-09-12 (`CAP-046`, Group AK), promoted 🟢 FACT 2026-09-13 (`DECISIONS.md` ADR-026)**: range ±100, `+100`=Left, `-100`=Right (opposite of the prior working assumption). | `PROTOCOL.md` §4.5.7, `CAP-046-FINDINGS.md` §2; `CAP-063` (Group AY, `CAP-063-FINDINGS.md`); `CAP-064` I-3: 26 writes OK, Centre = `17:0` ×5, persisted (read 7044); `CAP-065`: field 17 read `17:0` (Centre) every time; no write — restore to Right 4 is `CAP-066` BB-10; `CAP-066` BB-10: 32 writes OK (A7723–7873), none `17:7` (Right 4) — the drag reached only Right 6 on the right side; ends at Centre `17:0`; `CAP-067` (1.0.0, no Play services): BC-3 Right 4 = `17:7` on the 17th drag (A876, = `CAP-064` 6671), 18 + 1 writes OK, BC-5 `17:0`, BC-R `17:7` (B834) read back; `CAP-070` §3 (ch-19 `17:7`, 53 writes OK); `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §3) | | `FW-001` | Tap the 'Firmware up to date' check | User (App) | I | 🟢 | Forces a manual update check. | `CAP-023-FINDINGS.md` §4 (cached, not live-queried) | | `FW-002` | View firmware version per component (L/R/Case) | User (App, view) | I, AS (withdrawn) | 🟢 | Possibly triggers a status query when 'More settings' is opened. | `PROTOCOL.md` §0.1, `CAP-023-FINDINGS.md` §3 | -| `FW-003` | View serial numbers per component | User (App, view) | I, AS (withdrawn), BH, BI, BJ, BK, BL | 🟢 | Same as above. **Since 1.2.0 OpenControl reads the serials too** (`DECISIONS.md` ADR-058: one `GetHardwareInfo` per Connect, field 7 by position: Case, Right bud, Left bud). | `PROTOCOL.md` §6 (2026-10-09 Update); `CAP-072-FINDINGS.md` §2 (30 requests, 29 answers; the Info tab on the screen recording, Case/Right/Left) | +| `FW-003` | View serial numbers per component | User (App, view) | I, AS (withdrawn), BH, BI, BJ, BK, BL, BM | 🟢 | Same as above. **Since 1.2.0 OpenControl reads the serials too** (`DECISIONS.md` ADR-058: one `GetHardwareInfo` per Connect, field 7 by position: Case, Right bud, Left bud). | `PROTOCOL.md` §6 (2026-10-09 Update); `CAP-072-FINDINGS.md` §2 (30 requests, 29 answers; the Info tab on the screen recording, Case/Right/Left) | | `FW-004` | View connection status ("Earbud status: Connected") | User (App, view) | I | 🟢 | | — (not exercised, `CAP-023-FINDINGS.md` §6 gap) | | `INEAR-001` | Toggle 'In-ear detection' on/off | User (App) | J, AR, AZ, BA, BL | 🟢 | Automatically plays/pauses audio when worn/not worn. | `PROTOCOL.md` §4.5.5; `CAP-056` 2173/2849/3627/4048/4344 — the "In-ear detection" switch = `qhr` field 2 on film, 🟢 (`PROTOCOL.md` §4.5.5 2026-09-28 Update, ADR-047); `CAP-064`: OpenControl's `4:{2:0}` 9922 / `4:{2:1}` 10817 → OK, SASS bit 4 follows (9927 `b0 00`, 10822 `b8 00`); with it off no pause on removal; `CAP-065`: field 2 read `2:1` at every Connect and on the Controls/Sound pulls (BA-12/13); no write | | `CASE-001` | Toggle case sound 'Earbuds replaced' on/off | User (App) | J, BF, BG, AT | 🟢 | Setting stored on the chip inside the case. | `PROTOCOL.md` §4.5.8; `CAP-070` §3 (OpenControl 1.1.0, ACKed; the case-sound observation not recorded); `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §3 (no chime recorded)); `CAP-058` (Group AT, `CAP-058-EVENT-NOTES.md`) | @@ -178,10 +178,10 @@ _Physical interactions with the device._ | `TOUCH-004` | Triple-tap on a bud | User (Hardware) | N | 🔵 | Previous track. **Captured 2026-08-30 (`CAP-027`)** — confirmed on AVRCP, not RFCOMM. | `CAP-027-FINDINGS.md` §3 | | `TOUCH-005` | Swipe forward on a bud | User (Hardware) | N | 🔵 | Raise volume. **Captured 2026-08-30 (`CAP-027`)** — confirmed on AVRCP, not RFCOMM. | `CAP-027-FINDINGS.md` §3 | | `TOUCH-006` | Swipe backward on a bud | User (Hardware) | N | 🔵 | Lower volume. **Captured 2026-08-30 (`CAP-027`)** — confirmed on AVRCP, not RFCOMM. | `CAP-027-FINDINGS.md` §3 | -| `TOUCH-007` | Press and hold on a bud | User (Hardware) | N, AM, BI, BJ, BK, BL | 🔵 | Cycles ANC mode (incl. Adaptive) **or** activates Gemini/digital assistant, depending on per-earbud configuration (see `HOLD-*`). Requires Android 6.0+. **Captured 2026-08-30 (`CAP-027`)** — unlike `TOUCH-002`–`TOUCH-006`, confirmed on RFCOMM DLCI 0x04 (official Fast Pair Message Stream ANC Notify), not `libmaestro`. **Second confirming session, 2026-09-14/15 (`CAP-051`, Group AM):** three isolated physical gestures, video-confirmed, each producing a `Notify`(`0x13`)-only frame with no preceding `Set`/`Get` — reproduces `CAP-027`'s mechanism and directly confirms `CAP-038-FINDINGS.md` §5's own previously-unconfirmed "physical gesture" reading for its two Get-less/Set-less Notify frames. No accompanying DLCI 0x02 write found (clean negative, `CAP-051-FINDINGS.md` §3). | `CAP-027-FINDINGS.md` §4, `CAP-051-FINDINGS.md` §2/§3 | +| `TOUCH-007` | Press and hold on a bud | User (Hardware) | N, AM, BI, BJ, BK, BL, BM | 🔵 | Cycles ANC mode (incl. Adaptive) **or** activates Gemini/digital assistant, depending on per-earbud configuration (see `HOLD-*`). Requires Android 6.0+. **Captured 2026-08-30 (`CAP-027`)** — unlike `TOUCH-002`–`TOUCH-006`, confirmed on RFCOMM DLCI 0x04 (official Fast Pair Message Stream ANC Notify), not `libmaestro`. **Second confirming session, 2026-09-14/15 (`CAP-051`, Group AM):** three isolated physical gestures, video-confirmed, each producing a `Notify`(`0x13`)-only frame with no preceding `Set`/`Get` — reproduces `CAP-027`'s mechanism and directly confirms `CAP-038-FINDINGS.md` §5's own previously-unconfirmed "physical gesture" reading for its two Get-less/Set-less Notify frames. No accompanying DLCI 0x02 write found (clean negative, `CAP-051-FINDINGS.md` §3). | `CAP-027-FINDINGS.md` §4, `CAP-051-FINDINGS.md` §2/§3 | | `HEAD-002` | Head gesture: Nod | User (Hardware) | O, AQ | 🔵 | Answers a call. Can also reply to a text via dictation if 'Spoken notifications' is on — English only. **Attempted 2026-09-12 (`CAP-028`) — inconclusive**: gestures not camera-visible (recording frames the phone, not the head); zero wire-visible traffic on the Buds' own connection, but no active call/notification existed for the gesture to act on, so this cannot distinguish "functionally inert, as expected" from "gesture not actually triggered." **Re-checked 2026-09-13 (`ai-sessions/0017`)**: the same clean-negative result re-confirmed across `CAP-028`'s *entire* 227.71s log (not just the originally-checked window), plus zero AT+/HFP, AVRCP, or SCO/eSCO traffic anywhere in the log — no new signal found. A correctly-scoped repeat (an actual call/notification active, camera also framing the gesture itself) is designed as `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group AQ (planned `CAP-055`). | `CAP-028-FINDINGS.md` §4 | | `HEAD-003` | Head gesture: Shake | User (Hardware) | O, AQ | 🔵 | Rejects a call. Can also dismiss a text reply under the same condition. **Attempted 2026-09-12 (`CAP-028`) — inconclusive, same caveat as `HEAD-002`.** **Re-checked 2026-09-13 (`ai-sessions/0017`)** — same full-log re-verification and Group AQ repeat design as `HEAD-002` above. | `CAP-028-FINDINGS.md` §4 | -| `CONV-002` | User starts speaking (voice), triggering Conversation Detection | User (Hardware) | P, AY | 🟢 | Triggers Conversation Detection (if on) — pauses media, switches to Transparency. `CAP-029` (2026-09-12): media paused with no Buds command on RFCOMM or AVRCP — only the phone's own AVRCP `PlaybackStatusChanged` → Paused (frame 1593); `CAP-063`: the Buds sent AVRCP PAUSE/PLAY pass-through while the maintainer spoke (5389/5433, 🟡 — a tap sends the same). | `CAP-029-FINDINGS.md` §2; `CAP-063` (Group AY, `CAP-063-FINDINGS.md` §7) | +| `CONV-002` | User starts speaking (voice), triggering Conversation Detection | User (Hardware) | P, AY | 🟢 | Triggers Conversation Detection (if on) — pauses media, switches to Transparency. `CAP-029` (2026-09-12): media paused with no Buds command on AVRCP — the phone's own AVRCP `PlaybackStatusChanged` → Paused (frame 1593), 0.12 s after a Buds frame on DLCI 0x08, `01 09 00 05 0a 03 0b 06 13` (1592; `… 12` at 1711 before Playing 1713 — meaning 🔴 open, `PROTOCOL.md` §6, corrected 2026-10-15 `ai-sessions/0094`); `CAP-063`: the Buds sent AVRCP PAUSE/PLAY pass-through while the maintainer spoke (5389/5433, 🟡 — a tap sends the same). | `CAP-029-FINDINGS.md` §2; `CAP-063` (Group AY, `CAP-063-FINDINGS.md` §7) | | `CASE-006` | Place buds back in the case and close the lid | User (Hardware) | M, AW, AX, AY, AZ, BA, BB | 🔵 | Terminates the active Bluetooth Classic connection. | —; `CAP-061` (second bud seated → ACL `0x13` frame 5597; lid closed → 6068); `CAP-062` (second bud seated → ACL `0x13` frames 3814, 4667, 6277, 7225); `CAP-063` (Group AY, `CAP-063-FINDINGS.md`); `CAP-064`: ACL `0x13` when both seated (1808, 4978, 7147, 8398); lid opened with both docked: no ACL until a bud left the case (`CAP-064-FINDINGS.md` §1); `CAP-065`: the last bud docked → ACL `0x13` 5 times (1498, 3646, 4653, 8613, 9683), also with the lid open; `CAP-066`: Right into the right slot 16:16:54 / 16:21:38 (stream A2633 / A5344 `6.3.2 = 2`), the second bud docked → ACL `0x13` (A2745, A5444), lid open throughout | | `CASE-007` | Hold the case button for 30 seconds (case open, buds inside, plugged into power) | User (Hardware) | P | 🔵 | This is a **full factory reset**, not just pairing mode. Also resets the Find My Device link on the Pro 2 — destructive, do this deliberately and last (see `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group P #16). | `CAP-029-FINDINGS.md` §3 (a full 30 s factory reset and the re-pair, 2026-09-12) | | `CASE-008` | Press the case button briefly/differently to force pairing mode | User (Hardware) | P, AT | 🔴 | **Still to verify** — no officially confirmed press duration found for a shorter pairing trigger, separate from the 30s reset. Open question, see §9 below and `PROTOCOL.md` §6. | `CAP-058` (Group AT, `CAP-058-EVENT-NOTES.md`) | @@ -292,7 +292,7 @@ official-app capture can decide whether to exercise them._ |---|---|---|---|---|---|---| | `PAIR-001` | Pairing / bonding handshake (forget-and-re-pair baseline) | User (Hardware) | A, AU, AV, AW, AX, AY, BA, BC, AT | 🔵 | Lightweight, safely repeatable — see `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group A #1 for the exact procedure (do not confuse with `CASE-007`'s factory reset). Also exercised on the Pixel 9a session (§4.2 #1), and incidentally on Group R (forced bond removal there also clears the classic bond). | `CAP-059` (Group AU, 2026-09-20, OpenControl-app validation), `CAP-060` (Group AV, 2026-09-21); `CAP-061` (app CDM pairing, LE-first CTKD, `CAP-061-FINDINGS.md` §6); `CAP-062` (app CDM pairing, frames 1068–1179); `CAP-063` (Group AY, `CAP-063-FINDINGS.md`); `CAP-065`: not exercised (B4/Z1 not done → `CAP-066` VII); `CAP-067`: (E) forget (B1145/B1151) + Z1 CDM pairing, SSP Just Works (IO caps B1294/B1299, key type `0x04` B1304), automatic re-open B1601; B4 not exercised; `CAP-058` (Group AT, `CAP-058-EVENT-NOTES.md`) | | `PAIR-002` | Pairing / bonding handshake from a true factory-reset state | User (Hardware) | P (via `CASE-007`) | 🔵 | Optional, one-time, destructive comparison capture — see `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group P #16. | — | -| `PAIR-003` | Disconnect and reconnect to an already-bonded device | User (Hardware) | AV, AW, AX, AY, AZ, BA, BB, BC, BF, BG, AO, AP, BE, BI, BJ, BK, BL | 🟡 | Distinct protocol state from `PAIR-001`/`PAIR-002` (no bonding handshake, per `PROTOCOL.md` §5) — captured on the Pixel 9a session (§4.2 #4). **Corrected 2026-08-28 — first Pixel 7a occurrence is actually `CAP-006` (2026-08-15), not `CAP-012`:** `CAP-006-EVENT-NOTES.md` (17:23:53–56) tags a Bluetooth-radio-toggle-triggered reconnect to the already-bonded Buds as `PAIR-003`-shaped — stored link key reused, no fresh pairing/link-key exchange — 11 days before `CAP-012` was captured; the earlier "first Pixel 7a occurrence... `CAP-012`" wording was wrong, not just imprecise. **`CAP-012` (2026-08-26, incidental — a manual disconnect/reconnect via system Bluetooth settings during a Group S session, `CAP-012-FINDINGS.md` §6) is a second, independent confirming instance**, also reusing the stored classic link key (no SSP), and additionally the first to show this kind of reconnect retriggering a full HFP AT-command SLC handshake — relevant to `PROTOCOL.md` §6's open question on when that handshake recurs. Also `CAP-060` (`ai-sessions/0043`, Group AV): four reconnect cycles in one session, each after a distinct drop mechanism (see `CAP-060-FINDINGS.md` §1). | `CAP-006-EVENT-NOTES.md` (17:23:53–56), `CAP-012-FINDINGS.md` §6, `CAP-060-FINDINGS.md` §1; `CAP-061` (5 app sessions); `CAP-062` (14 app sessions, `CAP-062-FINDINGS.md` §3); `CAP-063` (Group AY, `CAP-063-FINDINGS.md`); `CAP-064`: Disconnect/Connect 10:16:26/42 and 10:27:44/47; a Connect with both docked and the lid open paged the Buds (7201 → 7204); `CAP-065`: 10 session opens — 7 automatic (6 LINK_BACK, 1 AFTER_LOSS), Connect taps 11:19:52/11:20:14, one pull (BA-16); none after a Disconnect tap until Connect; `CAP-066`: 10 session opens, all automatic (ADR-044 LINK_BACK ×5 on link-up incl. after both Bluetooth-ons and the K3 return, AFTER_LOSS ×4, 1 at the restart after a force-stop); no Connect tap; `CAP-067`: re-open after Bluetooth on (B372, 2.8 s after the ACL; no app log), after the permission re-grant (B965) and after Z1 (B1601) `CAP-068`: 15 session ends recovered (`CAP-068-FINDINGS.md` §5).; `CAP-070` §2, §6; `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §2, §6); `CAP-053` (Group AO, `CAP-053-EVENT-NOTES.md`); `CAP-054` (Group AP, `CAP-054-EVENT-NOTES.md`); `CAP-069` (Group BE, `CAP-069-EVENT-NOTES.md`) | +| `PAIR-003` | Disconnect and reconnect to an already-bonded device | User (Hardware) | AV, AW, AX, AY, AZ, BA, BB, BC, BF, BG, AO, AP, BE, BI, BJ, BK, BL, BM | 🟡 | Distinct protocol state from `PAIR-001`/`PAIR-002` (no bonding handshake, per `PROTOCOL.md` §5) — captured on the Pixel 9a session (§4.2 #4). **Corrected 2026-08-28 — first Pixel 7a occurrence is actually `CAP-006` (2026-08-15), not `CAP-012`:** `CAP-006-EVENT-NOTES.md` (17:23:53–56) tags a Bluetooth-radio-toggle-triggered reconnect to the already-bonded Buds as `PAIR-003`-shaped — stored link key reused, no fresh pairing/link-key exchange — 11 days before `CAP-012` was captured; the earlier "first Pixel 7a occurrence... `CAP-012`" wording was wrong, not just imprecise. **`CAP-012` (2026-08-26, incidental — a manual disconnect/reconnect via system Bluetooth settings during a Group S session, `CAP-012-FINDINGS.md` §6) is a second, independent confirming instance**, also reusing the stored classic link key (no SSP), and additionally the first to show this kind of reconnect retriggering a full HFP AT-command SLC handshake — relevant to `PROTOCOL.md` §6's open question on when that handshake recurs. Also `CAP-060` (`ai-sessions/0043`, Group AV): four reconnect cycles in one session, each after a distinct drop mechanism (see `CAP-060-FINDINGS.md` §1). | `CAP-006-EVENT-NOTES.md` (17:23:53–56), `CAP-012-FINDINGS.md` §6, `CAP-060-FINDINGS.md` §1; `CAP-061` (5 app sessions); `CAP-062` (14 app sessions, `CAP-062-FINDINGS.md` §3); `CAP-063` (Group AY, `CAP-063-FINDINGS.md`); `CAP-064`: Disconnect/Connect 10:16:26/42 and 10:27:44/47; a Connect with both docked and the lid open paged the Buds (7201 → 7204); `CAP-065`: 10 session opens — 7 automatic (6 LINK_BACK, 1 AFTER_LOSS), Connect taps 11:19:52/11:20:14, one pull (BA-16); none after a Disconnect tap until Connect; `CAP-066`: 10 session opens, all automatic (ADR-044 LINK_BACK ×5 on link-up incl. after both Bluetooth-ons and the K3 return, AFTER_LOSS ×4, 1 at the restart after a force-stop); no Connect tap; `CAP-067`: re-open after Bluetooth on (B372, 2.8 s after the ACL; no app log), after the permission re-grant (B965) and after Z1 (B1601) `CAP-068`: 15 session ends recovered (`CAP-068-FINDINGS.md` §5).; `CAP-070` §2, §6; `CAP-071` (1.1.1 release run, `CAP-071-FINDINGS.md` §2, §6); `CAP-053` (Group AO, `CAP-053-EVENT-NOTES.md`); `CAP-054` (Group AP, `CAP-054-EVENT-NOTES.md`); `CAP-069` (Group BE, `CAP-069-EVENT-NOTES.md`) | | `PAIR-004` | 'Forget' clears a pre-existing BLE association/link-key completely, even one not established via the Pixel Buds app | User (Hardware) | A (repeat), AT | 🔴 | Added 2026-08-14. `CAP-001-FINDINGS.md` §6 found a BLE link and a still-valid link key both existing *before* the on-screen "Forget" tap and before the case reopened — distinct claim from `PAIR-001`'s "fresh pairing procedure." See `CAPTURE_BLUETOOTH_HCI_SNOOP.md`'s Group-A repeat note. **Update 2026-08-26 (`CAP-013`) — approved as recorded (maintainer, chat 2026-09-30, `ai-sessions/0059`):** this question splits into two — the **primary** claim above (did a BLE link/key exist *before* the clearing action?) remains 🔴 fully OPEN, since `CAP-013`'s logging only started *after* the clearing action and the entire subsequent discovery/case-open/pair-button sequence (`CAP-013-FINDINGS.md` §0). The **secondary** claim — does the re-pairing *following* the clearing action show a fresh SSP handshake (clearing worked for whatever state existed right before logging began) or a reused key — is 🟢 answered: a complete fresh-SSP sequence was observed (`Delete Stored Link Key` → `Negative` Link Key Reply → IO Capability/User Confirmation/Simple Pairing Complete → new Link Key Notification, frames 117–270), no key-reuse path. A further repeat with logging verified to start *before* the clearing action is still needed for the primary claim. **Update 2026-08-27 (`CAP-031`) — approved as recorded (maintainer, chat 2026-09-30, `ai-sessions/0059`):** a third attempt, this time with a genuine narrow per-device "Forget" (screenshot-confirmed) and a live snoop-log file-size-polling check — **still did not capture the pre-clearing-action window**: the log's first frame lands 66s after the Forget tap (`CAP-031-FINDINGS.md` §0). **Primary claim remains 🔴 fully OPEN, untested a third time.** The secondary claim is reconfirmed 🟢 (sixth confirming fresh-SSP instance, frames 598–689, `CAP-031-FINDINGS.md` §2/§7). A fourth attempt is still needed, verifying the snoop log's own *content* freshness (not just file size) immediately before the Forget tap. **Update 2026-08-27 (`CAP-032`) — approved as recorded (maintainer, chat 2026-09-30, `ai-sessions/0059`): fourth attempt succeeded.** Extracted via the raw BTSnoop file path instead of the `btsnooz.py` fallback the three prior attempts used — the log is genuinely untruncated and its first frame (18:29:45.72) lands ~58s *before* the on-screen Forget tap (18:30:42), and ~30s before the video itself starts. **The primary claim is now answered for this session: 🟢 no BLE link and no valid classic link key existed for the Buds anywhere in the covered pre-Forget window** — zero classic connection events, exactly one LE connection (to an unrelated random-address device, not the Buds), and a `Delete Stored Link Key` issued at the Forget tap's own moment reporting `Num_Keys_Deleted = 0` (`CAP-032-FINDINGS.md` §0.3/§1). **This does not reproduce `CAP-001`'s original finding** — read as a clean counter-example, not a refutation; `CAP-001`'s own session-specific puzzle (why *that* session had residual state) remains independently 🔴 OPEN, tracked separately. The secondary claim is reconfirmed a seventh time (frames 1090–1153, `CAP-032-FINDINGS.md` §2/§7 Test B). The existence column stays 🔴 (legend §0.3: it records the *source* of existence, not a capture result); the result is the evidence pointer — `CAP-032` answers the primary question for that session (no BLE link or valid key before Forget), `CAP-001`'s own puzzle stays 🔴 (`PROTOCOL.md` §6). | `CAP-013-FINDINGS.md` §0/§2/§7, `CAP-031-FINDINGS.md` §0/§2/§7/§8, `CAP-032-FINDINGS.md` §0/§1/§2/§7; `CAP-058` (Group AT, `CAP-058-EVENT-NOTES.md`) | | `APP-001` | Tap 'Set up' — hands off from the pairing UI (system settings or a generic BLE tool) to the official Pixel Buds app's first-run onboarding screen | User (App) | A | 🟢 | **Added retroactively 2026-08-20** — in active use since `CAP-002`/`CAP-003` (2026-08-09/10) but never had a catalog row — gap found by `scripts/lint_docs.py`. No RFCOMM traffic tied to this specific tap in either capture; it is a local UI-navigation event, not a protocol event. | — | | `APP-002` | Tap 'Continue' on the app's "Allow a connection to your Pixel Buds" nearby-device permission screen | User (App) | A | 🟢 | **Added retroactively 2026-08-20** — in active use since `CAP-002`/`CAP-003` (2026-08-09/10) but never had a catalog row — gap found by `scripts/lint_docs.py`. Immediately precedes the system `CompanionDeviceManager` "Allow" dialog (`DECISIONS.md` ADR-005) in both captures. | — | @@ -354,7 +354,7 @@ Consolidated list of catalog rows not yet covered by a Group in no real or `planned` capture's Test(s) column names either — added 2026-08-30 audit finding; `CAP-016-FINDINGS.md` already independently noted these as "still not exercised") — **closed 2026-09-28:** exercised in `CAP-056` (Group AR, ears on film; which ear is Left/Right is not identifiable from that film — `CAP-056-FINDINGS.md` §4) -- [ ] `SWITCH-001`, `WELL-001`, `FIT-001` — official-app features with no capture scenario (§4c, added 2026-10-03) +- [ ] `FIT-001` — an official-app feature with no capture scenario (§4c, added 2026-10-03); `SWITCH-001` and `WELL-001` have one since `CAP-058` (Group AT — their rows; corrected 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-CAP-03) - [ ] `ADAPT-001` — whether Adaptive Audio processing requires the app to stay active (assigned "Q (partial)", but `planned` `CAP-030` explicitly scopes itself to Group Q's *other* two remaining items, `LOUD-001`/`ADAPT-002`, only — added 2026-08-30 audit finding; needs its own capture diff --git a/TODO.md b/TODO.md index 64ecaabf..71b2f930 100644 --- a/TODO.md +++ b/TODO.md @@ -13,20 +13,15 @@ Legend: 🔴 open question · 🟡 hypothesis to test · **M** = needs the maint ## 1. After release 1.3.0 (published 2026-10-15, `ai-sessions/0092` and its continuation in chat) -- [x] **1.2.2 published 2026-10-14** (tag `v1.2.2` on build commit `bfd7891`, PR #49 merged with `5b204ea`; E1–E4 by the pull request of `release/1.2.2-after`). - **No hardware capture:** the maintainer tested 1.2.2 on the phone and released it without `CAP-076` (chat 2026-10-14). **D4 done 2026-10-14:** the published APK - installed on the Pixel 9a with Obtainium (the maintainer, chat 2026-10-14: "Ik heb opencontrol for buds middels obtainium geupdate naar 1.2.2 op mijn pixel - 9a telefoon"); the published APK's SHA-256 = the tested build's (`d750551a…8a6b`, re-checked with `gh release download`). E1–E4: PR #52. - (1.2.1: D4 done 2026-10-13 with Obtainium; E1–E4 by PR #45.) -- [x] **1.3.0 published 2026-10-15** (tag `v1.3.0` on build commit `ff27ee1`, PR #56 merged with `aec17a5`; E1–E4 by the pull request of `release/1.3.0-after`) - after `CAP-076` (Group BL, `ai-sessions/0092`: every hold on the noise-control tab shown — 29/29 with Play, 12/12 without; verdict "release as built + - Known issues", the maintainer, chat 2026-10-15). **D4 done 2026-10-15:** installed with Obtainium on the Pixel 9a (the maintainer: "Het werkt!"). Not run - in `CAP-076`: BL-10, BL-13, BL-15, BL-18, BL-21, BL-24, BL-25. Next: the fixes P1–P3 of §5 when the maintainer chooses (no FEATURE prompt yet). +*(The finished release items of 1.2.2 and 1.3.0 were deleted 2026-10-15 by `ai-sessions/0094` — `ai-sessions/0093` A93-HK-01; they are in `CHANGELOG.md`, +`RELEASING.md` §13 and the session RESULTs.)* +- [ ] **1.3.1** (prepared by `ai-sessions/0094`, the maintainer's choice in chat 2026-10-15): the hardware run `CAP-077` (Group BM, `APP_TESTPLAN.md` §Z; the + skeleton captures/CAP-077-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_BM/CAP-077-EVENT-NOTES.md), then `RELEASING.md` B1 … E4 (**M**); `CHANGELOG.md` [1.3.1] gets its date then. Not run in `CAP-076` and still open for a release run: BL-10, BL-13, BL-15, BL-18, BL-21, BL-24, BL-25. - **The Buds' state after `CAP-076` (2026-10-15):** EQ `[−1, 0, 4, 2, 1.69]`, **in-ear detection off** (written 07:06:56, not switched on again), all four press-and-hold modes, balance Centre — restore in-ear detection if wanted (**M**). - The Buds' state after `CAP-074`/`CAP-075`: a custom EQ `[−3.5, 0.5, 1.0, −1.0, 2.5]`, all four press-and-hold modes, balance Centre (`CAP-074-FINDINGS.md` §0; - unchanged by `CAP-075`: 0 writes) — restore if wanted (**M**). The Owner user's stored "Noise-control channel" choice is "Take it back" and the secondary - user's too (`CAP-075-FINDINGS.md` §4) — the default "Show a hint" is not restored. + unchanged by `CAP-075`: 0 writes) — restore if wanted (**M**). (The stored "Noise-control channel" choice of 1.2.1/1.2.2 is ignored since 1.3.0, ADR-065 item 5 — nothing to + restore; sentence changed 2026-10-15, `ai-sessions/0094`, A93-HK-02.) ## 2. Hardware verification debt (the app, on film) @@ -44,13 +39,6 @@ ADR-061, chat 2026-10-10). **`CAP-073`** (the rebuilt 1.2.0, analysed in `ai-sessions/0085`, verdict OK — ready to release) settled the ADR-061 hold's main path and balance "Right 4" (both removed here); the maintainer tested only the changed functionality, so T11, C12, S12, H5, Volume EQ and the worn line with in-ear detection off stay open, with the hold's untested branches below. **Next app run:** none planned — these items go into the hardware run of the next release (`RELEASING.md` §11a). -- [x] ~~The ADR-061 hold's other branches on hardware~~ — **moot since 1.3.0** (`ai-sessions/0091`, ADR-065: the hold and the re-claim are removed; the tile, Home, - a loss and the re-open with the tab shown are in `CAP-076` as BL-8/BL-9/BL-10/BL-24 for the stream instead). History: (`CAP-073-FINDINGS.md` §4): the tile with the noise-control tab shown (no second `SABM`, no `DISC`); Home and - return (the `DISC` ≈ 1.5 s after Home, a new claim on return); a session loss and the ADR-044 re-open while the tab is shown (the snapshot kept); and the - noise-control (i) after a press-and-hold ("Changed by the Buds at HH:MM:SS …" and the ADR-061 sentence) — not run in `CAP-073`. **`CAP-074` (the Owner user with - Play services, `ai-sessions/0086`) ran the "another client closes DLCI 0x04 while the tab is shown" branch twelve times:** the hold ended "the channel was closed, - not by this app" and nothing re-claimed — as designed; the branch works, and it is the cause of the first-hold-only symptom (`CAP-074-FINDINGS.md` §4, §8). Still - not run: the tile, Home, a session loss / ADR-044 re-open with the tab shown, the (i) line. **`CAP-075` (Group BK, planned by `ai-sessions/0087`) takes the Play-user branch in both switch positions (BK-5 … BK-14) and, optionally, the tile, Home and the loss with the tab shown (BK-19 … BK-21).** **Run (`ai-sessions/0088`, `CAP-075-FINDINGS.md` §4): the Play-user branch in both positions done — hint only 2 cuts, take it back three chains of three re-claims; still not run: the tile, Home/return, a loss with the tab shown (BK-19 … BK-21), and BK-11 … BK-13 (a tab change, Home or the switch within 2 s of a cut — the cancel path ran once, pre-film).** **`CAP-076` (Group BL, skeleton by `ai-sessions/0089`) takes BK-11 … BK-13 again as BL-9 … BL-11 and the optional branches as BL-22 … BL-24.** - [ ] The screen-reader text on the phone: every "—" with the description "Not read from the Buds yet" — two `uiautomator dump`s (Controls, Sound) with Bluetooth off; not done in `CAP-070` (no dump; "—" itself is on film). (`APP_TESTPLAN.md` T11) — not done in `CAP-071` (no film 2, no dump). @@ -119,8 +107,8 @@ The five switches the maintainer chose (Multipoint, head gestures, the two case (their answers carry fields 1.0.955078536 does not know), whether the `find_device` placeholder (Play services item 1002) is still requested, and why it calls the KPI stream and the jitter-buffer preference. Pulled 2026-10-05 from the Pixel 7a (`adb pull` of base + 2 splits, same signing certificate as 1.0.955078536) into `reverse-engineering/apk/v1.0.990706425-10260911/` (gitignored); not decompiled yet (**M**, chat - 2026-10-05: *"Voer het nog niet uit. Dat kan later."*). Also add the three files' SHA-256 to `reverse-engineering/APK_VERSIONS.md`, whose - 1.0.990706425 paragraph still says "Not pulled". Start with `citation_checker` and `pwrpc_name_table` on the new tree + 2026-10-05: *"Voer het nog niet uit. Dat kan later."*). The three files are registered in `reverse-engineering/APK_VERSIONS.md` since 2026-10-15 + (`ai-sessions/0094`). Start with `citation_checker` and `pwrpc_name_table` on the new tree (`APK_REVERSE_ENGINEERING_PROCEDURE.md` §7 steps 7–8; design notes for the diff in `reverse-engineering/tools/BACKLOG.md`). Open questions (each with where it is described): @@ -202,14 +190,6 @@ Open questions (each with where it is described): ## 5. App: later work -- [x] **Done 2026-10-14 (`ai-sessions/0091`, branch `feature/settings-stream`): ADR-064 built as 1.3.0** — the stream feeding every read setting, ADR-061/062/063 - superseded by ADR-065 (the hold, the re-claim, the card and the Play line removed), the cause line rewritten, `CAP-076` rewritten for 1.3.0; mutation - checks M1–M5 caught. **Next:** the maintainer's push, PR and CI; then `RELEASING.md` B1–B3 for 1.3.0 and `CAP-076` (C1–C2), analysed by a CAPTURE - session (C3/C4). Deferred documentation of 0091: none beyond the items in this file. The prompt's earlier outline (accepted 2026-10-14, `ai-sessions/0090`): one `SubscribeToSettingsChanges` and one `ReadSetting 4:13` - per MAESTRO session, field 13 only, the mode shown with "Changed by the Buds" without a Message Stream claim; fixtures from `CAP-051` 1177/1755 and `CAP-056` - 1451; then its hardware run (skeleton written by that session — the maintainer's choice, chat 2026-10-14): both users, holds with the noise-control tab - closed, a wear-change re-home and the re-open's read; optionally the read-only `ReadSetting 4:8/9/10` (§4 — **not** in the 1.3.0 run, the maintainer's - choice in chat 2026-10-14; a later session may add it). The retirement of ADR-061/062's hold and re-claim is ADR-065 (done). - [ ] **The noise-control channel in the Play user — after 1.3.0 (ADR-065: the re-claim is gone, the holds no longer depend on the channel):** what remains is the per-tap collision with Play's claim (`CAP-075-FINDINGS.md` §5; 1.3.0's known issue) — P3 a shorter attempt-2 latency for the on-demand channel (§9: attempt 2 0.7–1.0 s after attempt 1's refusal) is still open (**M**). Whether repeated collisions drive Play's "Retry times remaining" to 0 and what Play @@ -240,9 +220,7 @@ Open questions (each with where it is described): `Role.Switch` on the row, the `Switch` without its own click) would fix it; it changes every switch's semantics (and the tests that find a switch by its sibling label) — **M** decides whether and when. - [ ] `CAP-064` §10: whether the stale "100 %⚡" (I-4a) is drawn dimmed — a `:ui` Robolectric screenshot test settles it without hardware. -- [x] **M — the 1.2.1 release steps:** done — PR #39 merged (the build commit `31ee183`), `CAP-075` run and analysed (`ai-sessions/0088`, PR #44), verdict - "no heavy issue", tag `v1.2.1` and the release published 2026-10-13 (approved in chat); E1–E4 by the pull request of `release/1.2.1-after` (#45); D4 done 2026-10-13 (Obtainium, §1). -- [ ] **1.3.0 candidate (M):** volume-level notifications from `maestro_pw.Dosimeter` — draft ADR-060 (`DECISIONS.md`, proposed, not accepted; `ai-sessions/0082` +- [ ] **Candidate for a later release (M; "1.3.0 candidate" until 2026-10-15, `ai-sessions/0094`):** volume-level notifications from `maestro_pw.Dosimeter` — draft ADR-060 (`DECISIONS.md`, proposed, not accepted; `ai-sessions/0082` RESULT §C.4): two new requests per Connect (`SubscribeToLiveDb`, `FetchDailySummaries`), the 7-day dose on the phone, a notification channel, the field-21 switch. Blocked until `WELL-002` (the stream against a sound-level meter on film) and the other 🟡 of the draft are 🟢. - [ ] **String resources:** the UI texts are Kotlin literals; moving them to `strings.xml` is the precondition for any translation. @@ -273,9 +251,14 @@ Open questions (each with where it is described): — the helper prints every thread's stack if it recurs. - Keep in step: `android/ui/src/main/res/raw/license.txt` with `LICENSE` (`SettingsMenuTest` fails otherwise). +- [ ] **targetSdk 34 → the current target** (lint `OldTargetApi`; ADR-029): the maintainer's choice 2026-10-15 (`ai-sessions/0094`, "34 houden, stap plannen"): + its own FEATURE session — an ADR-029 Update, the behaviour changes of each API level read from the Android Developer docs (foreground services, + broadcasts, Bluetooth permissions), and a hardware run in both users (**M**). + ## 6. Documents -Restructures the maintainer deferred (chat 2026-10-03, "Restructures"); each is a MAINTENANCE session of its own, with a before/after check that no +Restructures the maintainer deferred (chat 2026-10-03, "Restructures"; re-confirmed 2026-10-15, `ai-sessions/0094` "Later, als eigen sessies in TODO.md" — with +`ai-sessions/0093` §6 rows 6, 9 and 13 as the current proposals); each is a MAINTENANCE session of its own, with a before/after check that no statement is lost (`ai-sessions/0068` §6 has the proposal for each): - [ ] `ARCHITECTURE.md` — "as built" text; dated notes become current text or move to `DECISIONS.md`; §5a gets a "verified in capture" column (§6.1). @@ -302,10 +285,19 @@ statement is lost (`ai-sessions/0068` §6 has the proposal for each): Corrections found by `ai-sessions/0068` and not yet made (sub-review items, each with its check in that RESULT's appendix): -- [ ] Slips still open after `ai-sessions/0081` (interpretive — with the rule-9a session): `CAP-008-FINDINGS.md:381` "eleven silent captures"; +- [ ] Slips still open after `ai-sessions/0081` (interpretive — with the rule-9a session; `CAP-008`'s "eleven silent captures" was corrected by `ai-sessions/0094`): `CAP-033-EVENT-NOTES.md:146–147`; `CAP-047-FINDINGS.md:305–306`, `:379–380`; `CAP-050-FINDINGS.md:81–97`, `:204`, `:210` (re-open/sample counts); `CAP-049` (capinfos start, the quoted tail) — `ai-sessions/0068` appendix capA/capB/capD. The 27 arithmetic slips of that list were corrected in place by `ai-sessions/0081` (its ledger §B.C). +- [ ] Found while `ai-sessions/0094` re-checked the `ai-sessions/0093` capture items (each verified by its sub-agent, not yet corrected — outside the 0093 list): + `CAP-002-FINDINGS.md` §2a counts 39 DLCI 0x08 messages in 17:04:35–17:07:05 (a reassembled decode finds 27) and §2's "this session's own 371 frames" is + the whole log's DLCI 0x02 count (it includes `CAP-001`); `CAP-010-FINDINGS.md` "now four independent sessions" for the `0x0f2a` read (three: `CAP-002`, + `003`, `010` — `CAP-014` is the fourth later); `CAP-001-FINDINGS.md` §2's channel 1 row still says "structure suggests AVRCP", superseded by the pw_hdlc + upgrade below it; `CAP-012`'s film clock runs ≈ 1–2 s behind the log ("matches within 1 s" lines need a look); `CAP-022`'s `0x2001…0x2026` (38 values) + against "a 34-frame burst" (32 `ReadSetting` requests — what the `0x20NN` sequence counts is not derived); `CAP-050-EVENT-NOTES.md`'s Test-device row mixes + film-relative and wall-clock time; `CAP-058-FINDINGS.md:31` subtracts a laptop-clock time from a phone-clock time; `CAP-074` §7 "9 of 44" while its parts + add up to 49; the `CAP-072` skeleton sentence at line ≈ 303 miscounts the requests (historical plan text). Agent drafts: + `ai-sessions/0094` RESULT Appendix. - [ ] Folder-name end times: `CAP-036` (film ends 06:41:16), `CAP-041` (17:17:40), `CAP-042` (named after the log, not the film). A rename breaks every link to the folder — do it with the index restructure. - [ ] EVENT-NOTES left as templates: unticked "Next steps" (`CAP-037`…`042`), leftover placeholders (`CAP-035`, `CAP-036`), the placeholder timeline @@ -313,7 +305,9 @@ Corrections found by `ai-sessions/0068` and not yet made (sub-review items, each - [ ] Rule 9a: FINDINGS with more than one dated layer (`CAP-036` 7+, `CAP-041` 5, `CAP-032`/`034` 4, `CAP-017` 5, and others) — fold into current text; FINDINGS without a status banner that still need none checked: `CAP-006`…`008`, `011`…`013`, `019`, `020`, `023`, `025`, `027`, `029`, `031`…`033`, `035`, `043`, `044`, `051`. -- [ ] Reading and checks `ai-sessions/0073` owed (its "Files read" and "Deferred documentation"): `ARCHITECTURE.md`, the five older tool +- *(The reading owed by `ai-sessions/0089` and `0090` was done by `ai-sessions/0093` — `PROTOCOL.md`, `ARCHITECTURE.md`, `REVERSE_ENGINEERING.md`, `RELEASING.md` in + full, the capture FINDINGS by its sub-reviews; items deleted 2026-10-15, `ai-sessions/0094`.)* +- [ ] Reading and checks `ai-sessions/0073` owed (its "Files read" and "Deferred documentation"; `ARCHITECTURE.md` was read in full by `ai-sessions/0093`): the five older tool `SPEC.md` files and the earlier APK-session RESULTs were not read in full by the main session; `PROTOCOL.md` only in the sections it touched (three sub-reviews swept it); 63 of the citations its documentation edits added resolve inside their file but carry no quoted token for `citation_checker` to compare; the statements marked "per the trace" in its §4 were taken from a sub-review without @@ -326,12 +320,6 @@ Corrections found by `ai-sessions/0068` and not yet made (sub-review items, each `pwrpc_name_table census` on the de-duplicated set and correct the figures as dated notes (**M** for the texts). - [ ] Reading owed by earlier sessions (`A68-SES-03`): `ai-sessions/0061` did not read `APP_TESTPLAN.md` and several app sources with their tests in full; `ai-sessions/0063` read its prompt's full-reading list only in part (its §B) — carry into the next MAINTENANCE reading pass (the rule-9a session). -- [ ] Reading owed by `ai-sessions/0089` (its RESULT "Files read"): `RELEASING.md`, the `ai-sessions/0087`/`0088` PROMPT and RESULT files, `CAP-074-FINDINGS.md` - §4/§8/§9, `CAP-073-FINDINGS.md` §4 and `PROTOCOL.md` §2.3/§8 were not read in full (none of them was changed; the facts used were re-derived from `CAP-075`) - — carry into the next reading pass. -- [ ] Reading owed by `ai-sessions/0090` (its RESULT "Files read"): `PROTOCOL.md`, `ARCHITECTURE.md` and `REVERSE_ENGINEERING.md` were read only in the sections - named by its prompt (and the sections it changed), not in full; the FINDINGS of `CAP-038`, `CAP-045`, `CAP-064`, `CAP-069`, `CAP-075` only at the cited frames - — carry into the next reading pass. - Order decided 2026-10-09 (maintainer, chat, `ai-sessions/0081`): all of the above after the 1.2.0 work — first the `ai-sessions/0073` reading and the census recount (one CROSSCHECK session), then the rule-9a folds with the slip residue (MAINTENANCE), then the EVENT-NOTES templates (MAINTENANCE), the folder renames with the index restructure. diff --git a/WORKSTATION_PREPARATIONS.md b/WORKSTATION_PREPARATIONS.md index 95bfd329..2892dc8b 100644 --- a/WORKSTATION_PREPARATIONS.md +++ b/WORKSTATION_PREPARATIONS.md @@ -41,8 +41,17 @@ fedora ~/git/opencontrolpixelbudspro2 [main L|✔] $ sudo chown -R $(whoami):$(w fedora ~/git/opencontrolpixelbudspro2 [main L|✔] $ npm config set allow-scripts=@anthropic-ai/claude-code,@github/keytar,node-pty --location=user ``` +*Recommended instead of the `chown` above (note 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-GOV-10):* npm's own guidance for global installs is a Node version manager, or a user-owned +prefix — `npm config set prefix ~/.local` and `~/.local/bin` on the `PATH` (docs.npmjs.com, "Resolving EACCES permissions errors when installing packages +globally", fetched 2026-10-15). The `chown` of `/usr/local` recorded above works but is not what npm recommends. + ## Install Claude-code +*Recommended now (note 2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-GOV-10):* Claude Code's setup page (code.claude.com/docs/en/setup, fetched 2026-10-15) lists the **native installer** +(`curl -fsSL https://claude.ai/install.sh | bash`, "Native Install (Recommended)", auto-updating) and a signed dnf repository (`gpgcheck=1`, key fingerprint +`31DD DE24 DDFA B679 F42D 7BD2 BAA9 29FF 1A7E CACE`); npm stays supported, but "Do NOT use `sudo npm install -g`". The npm transcript below is what this +workstation ran. + ```bash # Install Claude Code fedora ~/git/opencontrolpixelbudspro2 [main L|✔] $ npm install -g @anthropic-ai/claude-code @@ -63,6 +72,10 @@ fedora ~/git/opencontrolpixelbudspro2 [main L|✔] $ claude --version ## Install Antigravity +*Note (2026-10-15, `ai-sessions/0094` — `ai-sessions/0093` A93-GOV-10):* Antigravity's release page (antigravity.google/releases, fetched 2026-10-15) says: "These APT and RPM repositories were provided +for legacy Antigravity 1.x releases and are no longer updated" — and its RPM example itself has `gpgcheck=0` (no signing key for the RPM repository). For +current versions use the direct downloads of antigravity.google/download; do not rely on this unsigned repository for updates. + ```bash # Add Antigravity repo fedora ~/git/opencontrolpixelbudspro2 [main L|✔] sudo tee /etc/yum.repos.d/antigravity.repo << EOL @@ -132,7 +145,7 @@ VSCODE_EXTENSIONS=( redhat.vscode-xml ) fedora ~/git/opencontrolpixelbudspro2 [main L|✔] $ for ext in "${VSCODE_EXTENSIONS[@]}"; do - code-insiders --install-extension "$ext" || warn "Can not install extention $ext" + code-insiders --install-extension "$ext" || echo "[WARN] Can not install extension $ext" >&2 # `warn` is only defined further below done ``` @@ -170,6 +183,8 @@ ls ~/Android/Sdk/platforms ~/Android/Sdk/build-tools # android-37.0, 36.0.0 tshark --version | head -1 # capture analysis, scripts/pwrpc_decode.py ffmpeg -version | head -1 # film stills, scripts/readme_media.sh git lfs version # the capture logs are LFS files: run `git lfs install` once +capinfos --version | head -1 # capture checks (CAPTURE_BLUETOOTH_HCI_SNOOP.md §3, DESKRESEARCH_FINDINGS.md) +uv --version # the reverse-engineering/tools/*/.venv set-ups (added 2026-10-15, ai-sessions/0094) adb version | head -1 python3 --version # the scripts use the standard library only ``` diff --git a/ai-sessions/0069_MAINTENANCE_RESULT_2026_10_03.md b/ai-sessions/0069_MAINTENANCE_RESULT_2026_10_03.md index 0bb9ce15..18ca6a17 100644 --- a/ai-sessions/0069_MAINTENANCE_RESULT_2026_10_03.md +++ b/ai-sessions/0069_MAINTENANCE_RESULT_2026_10_03.md @@ -443,5 +443,12 @@ this session. The intermediate commits were not gated one by one — the gate ra Next, by the maintainer: `RELEASING.md`, "Release checklist", from step A5 (push, pull request) on. + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- the Summary's "Nothing is committed, tagged or published." was written before the commit; the Status line is right (committed, released as 1.0.1). +- "59 short rows in five capture notes" (Summary) and "68 short rows" (ledger A68-HK-08) describe the same item — the ledger's 68 is the count of record. +- the commit that recorded this RESULT's commits is `e1fc886` (also the build commit tagged `v1.0.1`). + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0069_MAINTENANCE_RESULT_2026_10_03.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0069_MAINTENANCE_RESULT_2026_10_03 diff --git a/ai-sessions/0070_CAPTURE_RESULT_2026_10_04.md b/ai-sessions/0070_CAPTURE_RESULT_2026_10_04.md index 23eb6f10..bd4f150a 100644 --- a/ai-sessions/0070_CAPTURE_RESULT_2026_10_04.md +++ b/ai-sessions/0070_CAPTURE_RESULT_2026_10_04.md @@ -145,5 +145,10 @@ Committed and pushed to `maintenance/0069` on the maintainer's word (chat 2026-1 `git diff e1fc886..HEAD -- android` is empty: the tested APK stays the build commit's. + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- the commits not listed: `9507d39` (this RESULT's commits recorded) and `5a9de44` (after publishing 1.0.1). + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0070_CAPTURE_RESULT_2026_10_04.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0070_CAPTURE_RESULT_2026_10_04 diff --git a/ai-sessions/0071_CAPTURE_RESULT_2026_10_04.md b/ai-sessions/0071_CAPTURE_RESULT_2026_10_04.md index 854c9825..2b34ddc4 100644 --- a/ai-sessions/0071_CAPTURE_RESULT_2026_10_04.md +++ b/ai-sessions/0071_CAPTURE_RESULT_2026_10_04.md @@ -145,5 +145,10 @@ The maintainer said "yes" to commit, push and a pull request (chat 2026-10-04). - `e16aeef` chore(capture): add CAP-069 film and HCI log (Group BE, official app 1.0.990706425) — LFS objects `c4f4678…` (film), `45d0aa9…` (log). - The documentation commit that carries this file (its hash is completed by the next session, `AI_SESSION_LOG_PROCEDURE.md` §4b item 2). + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- the commits not listed: `93a930c` (`CAP-069` analysed; ADR-052/053) and `874ec28` (the sign-off on `CAP-069` §13). + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0071_CAPTURE_RESULT_2026_10_04.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0071_CAPTURE_RESULT_2026_10_04 diff --git a/ai-sessions/0072_CAPTURE_RESULT_2026_10_05.md b/ai-sessions/0072_CAPTURE_RESULT_2026_10_05.md index b0b16ee2..7674477a 100644 --- a/ai-sessions/0072_CAPTURE_RESULT_2026_10_05.md +++ b/ai-sessions/0072_CAPTURE_RESULT_2026_10_05.md @@ -174,5 +174,10 @@ On branch `capture/0072-cap-053-054-058` (maintainer: *"Ja, commit, push en maak - `75e5c37` docs: analyse CAP-053, CAP-054 and CAP-058 (EQ field 18, case-open advertisement, SDP-001) - this list's own commit (docs: record the commits of ai-sessions/0072) — its hash is back-filled by the next session (§4b item 2). + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- the commit that recorded this RESULT's commits: `c809aa7`. + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0072_CAPTURE_RESULT_2026_10_05.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0072_CAPTURE_RESULT_2026_10_05 diff --git a/ai-sessions/0073_CROSSCHECK_RESULT_2026_10_06.md b/ai-sessions/0073_CROSSCHECK_RESULT_2026_10_06.md index d0b08781..1661d3ca 100644 --- a/ai-sessions/0073_CROSSCHECK_RESULT_2026_10_06.md +++ b/ai-sessions/0073_CROSSCHECK_RESULT_2026_10_06.md @@ -1106,10 +1106,15 @@ or `reverse-engineering/apk/`, no `.venv/`, no cache directory, no JSON or CSV d | `0a27276` | chore(scripts): name the Kpi and JitterBuffer ids in pwrpc_decode, remove decode_qhr_settings | | `b4405d8` | docs: apply the approved results of the second APK pass (ai-sessions/0073) | | `04a4070` | docs: add ai-sessions/0073 (second reverse-engineering pass over the companion APK) — this file, its prompt and the `INDEX.md` row | -| (this commit) | docs: record the commits of ai-sessions/0073 | +| `4a709c8` *(back-filled 2026-10-15, `ai-sessions/0094`)* | docs: record the commits of ai-sessions/0073 | Pushed on 2026-10-06; pull request: https://github.com/tedsluis/opencontrolpixelbudspro2/pull/5 (open, not merged when this was written). The hash of the last commit above and the merge: to be completed by the next session's first step (`AI_SESSION_LOG_PROCEDURE.md` §4b item 2). + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- "968 citations in 19 documents (850 of them in `REVERSE_ENGINEERING.md`)" — the appendix gives 758 for `REVERSE_ENGINEERING.md` and sums to 968 with 758; 850 is wrong. + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0073_CROSSCHECK_RESULT_2026_10_06.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0073_CROSSCHECK_RESULT_2026_10_06 diff --git a/ai-sessions/0075_CAPTURE_RESULT_2026_10_07.md b/ai-sessions/0075_CAPTURE_RESULT_2026_10_07.md index 0b07f8d9..8583467b 100644 --- a/ai-sessions/0075_CAPTURE_RESULT_2026_10_07.md +++ b/ai-sessions/0075_CAPTURE_RESULT_2026_10_07.md @@ -141,7 +141,7 @@ On `feature/0074-settings-switches` (pull request #7), after the build commit `0 | `4bcc409` | docs(capture): CAP-070 (Group BF) — the 1.1.0 release run, analysed | | `0f142e8` | docs: follow CAP-070 — known issues, L-1 update, hardware-verified switches | | `714694b` | docs(session): ai-sessions/0075 prompt and result, INDEX row, 0074 back-fill | -| (this commit) | docs(session): ai-sessions/0075 commits back-filled, status complete | +| `8256c74` *(back-filled 2026-10-15, `ai-sessions/0094`)* | docs(session): ai-sessions/0075 commits back-filled, status complete | The first push uploaded all LFS objects (1.4 GB) but the SSH connection closed before the refs; the second push moved the branch `0323849..714694b`. diff --git a/ai-sessions/0076_FEATURE_RESULT_2026_10_07.md b/ai-sessions/0076_FEATURE_RESULT_2026_10_07.md index 78a6aaf4..e3221b14 100644 --- a/ai-sessions/0076_FEATURE_RESULT_2026_10_07.md +++ b/ai-sessions/0076_FEATURE_RESULT_2026_10_07.md @@ -4,7 +4,7 @@ **Category:** FEATURE **Date:** 2026-10-07 **Title:** Replace the labelled, derived test fixtures of `ai-sessions/0074` with the real `CAP-070` frames and remove the `// TODO(verify)` on `SettingsCodec.FIELD_VOLUME_EQ` (ADR-055); test and comment changes only -**Status:** complete (checkpoint approved in chat 2026-10-07; committed and pushed to `feature/0076-capture-fixtures`, pull request opened — see Commits) +**Status:** complete (checkpoint approved in chat 2026-10-07; committed and pushed to `feature/0076-capture-fixtures`, pull request opened — see Commits) *(Status update 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-06/07: PR #9 merged 2026-10-07 as `6167843`.)* ## Progress @@ -128,7 +128,7 @@ On `feature/0076-capture-fixtures` (from `bdcc583`); the maintainer answered *"C | `cfc8537` | test(data): CAP-070's real frames replace the derived 1.1.0 fixtures | | `ba804c5` | docs: ADR-055 Update — the channel-21 Volume EQ on frame is captured | | `ad4fe6b` | docs(session): ai-sessions/0076 prompt and result, INDEX row | -| (this commit) | docs(session): ai-sessions/0076 commits back-filled | +| `2d39048` *(back-filled 2026-10-15, `ai-sessions/0094`)* | docs(session): ai-sessions/0076 commits back-filled | --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0076_FEATURE_RESULT_2026_10_07.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0076_FEATURE_RESULT_2026_10_07 diff --git a/ai-sessions/0077_MAINTENANCE_RESULT_2026_10_07.md b/ai-sessions/0077_MAINTENANCE_RESULT_2026_10_07.md index 179416ce..4b9151ee 100644 --- a/ai-sessions/0077_MAINTENANCE_RESULT_2026_10_07.md +++ b/ai-sessions/0077_MAINTENANCE_RESULT_2026_10_07.md @@ -4,7 +4,7 @@ **Category:** MAINTENANCE **Date:** 2026-10-07 **Title:** Prepare and ask every decision that waits only for the maintainer — desk-research proposals, older capture-FINDINGS proposals, ADRs for two 1.0.1 behaviours, untracking the .pyc, restoring the Buds' state -**Status:** complete (all 13 decisions approved in chat 2026-10-07; committed and pushed to `maintenance/0077-decisions`, pull request opened — see Commits) +**Status:** complete (all 13 decisions approved in chat 2026-10-07; committed and pushed to `maintenance/0077-decisions`, pull request opened — see Commits) *(Status update 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-06/07: PR #10 merged 2026-10-07 as `42660fd`.)* ## Progress @@ -164,7 +164,7 @@ On `maintenance/0077-decisions` (from `6167843`); the maintainer answered *"Comm | `a09a18e` | chore: untrack scripts/__pycache__/lint_docs.cpython-314.pyc | | `f475f51` | docs: the maintainer's pending decisions — proposals, ADR-056/057, Buds state | | `b3ed405` | docs(session): ai-sessions/0077 prompt and result, INDEX row | -| (this commit) | docs(session): ai-sessions/0077 commits back-filled | +| `5f75dda` *(back-filled 2026-10-15, `ai-sessions/0094`)* | docs(session): ai-sessions/0077 commits back-filled | A first attempt (`git commit -- ` for the untracked file failed, and the `.pyc` removal went into the session commit) was undone before any push with `git reset --soft HEAD~1` and recommitted in this order; nothing was lost. diff --git a/ai-sessions/0078_MAINTENANCE_RESULT_2026_10_07.md b/ai-sessions/0078_MAINTENANCE_RESULT_2026_10_07.md index 830173b8..fab54d01 100644 --- a/ai-sessions/0078_MAINTENANCE_RESULT_2026_10_07.md +++ b/ai-sessions/0078_MAINTENANCE_RESULT_2026_10_07.md @@ -4,7 +4,7 @@ **Category:** MAINTENANCE **Date:** 2026-10-07 **Title:** Upgrade the build toolchain and every dependency of `android/gradle/libs.versions.toml` to current stable releases, each pinned and justified; re-check the Material 3 opt-ins and `TabRow`/`PrimaryTabRow`; add `distributionSha256Sum`; align the CI actions and the documents; prove with the gate, test counts, mutations and an APK comparison that the app's behaviour does not change -**Status:** complete (committed and pushed to `maintenance/0078-toolchain-upgrade`, pull request opened — see Commits) +**Status:** complete (committed and pushed to `maintenance/0078-toolchain-upgrade`, pull request opened — see Commits) *(Status update 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-06/07: PR #11 merged 2026-10-07 as `188d19e`.)* ## Progress @@ -409,9 +409,14 @@ to the gated state. | `6f17d1f` | ci: actions/checkout v7.0.1 in all workflows; setup-python v7.0.0; setup-gradle v6.4.0 | | `462b829` | docs: toolchain versions, ADR-029 Update (compileSdk 37), TODO and CHANGELOG | | `a4cfaf1` | docs(session): ai-sessions/0078 prompt and result, INDEX row | -| (this commit) | docs(session): ai-sessions/0078 commits back-filled | +| `fea6618` *(back-filled 2026-10-15, `ai-sessions/0094`)* | docs(session): ai-sessions/0078 commits back-filled | Pull request: https://github.com/tedsluis/opencontrolpixelbudspro2/pull/11 — CI must be green before the maintainer merges. + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- "13 of 13" changed Kotlin files with the AGPL header — `git diff --name-only 42660fd a4cfaf1 -- '*.kt'` lists 14 (this RESULT's own list is also 14); all 14 carry the header. + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0078_MAINTENANCE_RESULT_2026_10_07.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0078_MAINTENANCE_RESULT_2026_10_07 diff --git a/ai-sessions/0079_MAINTENANCE_RESULT_2026_10_08.md b/ai-sessions/0079_MAINTENANCE_RESULT_2026_10_08.md index e63cd344..a5396530 100644 --- a/ai-sessions/0079_MAINTENANCE_RESULT_2026_10_08.md +++ b/ai-sessions/0079_MAINTENANCE_RESULT_2026_10_08.md @@ -4,7 +4,7 @@ **Category:** MAINTENANCE **Date:** 2026-10-08 **Title:** Prepare the next release — the toolchain and dependency upgrade of `ai-sessions/0078` shipped as a version of its own — up to the maintainer's `scripts/release.sh`; the skeleton of its hardware run; `RELEASING.md` brought in line with the repository rules; no tag, no build by the agent, no publication -**Status:** complete (committed and pushed to `release/1.1.1`, pull request open, not merged — see Commits) +**Status:** complete (committed and pushed to `release/1.1.1`, pull request open, not merged — see Commits) *(Status update 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-06/07: PR #15 merged 2026-10-08 as `f359aa2`; 1.1.1 released (tag `v1.1.1` on `86a6fb3`) after `CAP-071`.)* ## Progress @@ -232,9 +232,14 @@ On `release/1.1.1` (from `origin/main` = `7101d9c`); the maintainer answered *"C | `68d214d` | docs(capture): CAP-071 skeleton (Group BG) for the 1.1.1 release run, registered planned | | `ab8c64b` | docs: TODO.md — items planned in CAP-071, repository proposals | | `22af9f1` | docs(session): ai-sessions/0079 result, INDEX row | -| (this commit) | docs(session): ai-sessions/0079 commits back-filled | +| `86a6fb3` *(back-filled 2026-10-15, `ai-sessions/0094`)* | docs(session): ai-sessions/0079 commits back-filled | Pull request: https://github.com/tedsluis/opencontrolpixelbudspro2/pull/15 — the release checklist; A6 is the maintainer's check, D1 waits for `CAP-071`. + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- "38 commits (31 without merges)" — `git log --oneline --no-merges v1.1.0..7101d9c | wc -l` gives 30. + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0079_MAINTENANCE_RESULT_2026_10_08.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0079_MAINTENANCE_RESULT_2026_10_08 diff --git a/ai-sessions/0081_REVIEW_RESULT_2026_10_08.md b/ai-sessions/0081_REVIEW_RESULT_2026_10_08.md index 1d2011f5..49b85abd 100644 --- a/ai-sessions/0081_REVIEW_RESULT_2026_10_08.md +++ b/ai-sessions/0081_REVIEW_RESULT_2026_10_08.md @@ -6,7 +6,7 @@ **Title:** A targeted consistency check before the 1.2.0 work starts: the capture registry against the Capture Index, the FINDINGS/EVENT-NOTES statuses and `ARCHITECTURE.md` §5a; the small correction work `ai-sessions/0068` left open; the open items of the captures of the last week (`CAP-053`, `CAP-054`, `CAP-058`, `CAP-064` … `CAP-071`); and `TODO.md` itself. Documentation only — no app change, no new capture skeleton, no full audit -**Status:** complete — all approved edits applied and verified; commit/push awaiting the maintainer's word (see "Commits") +**Status:** complete — all approved edits applied and verified; commit/push awaiting the maintainer's word (see "Commits") *(Status update 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-06/07: committed (`6d31902`, `d336884`, `a8f69a6`); PR #20 merged 2026-10-09 as `9e08a0a`.)* ## Summary (plain language) @@ -307,5 +307,10 @@ Approved in chat ("Yes, commit, push and open the PR"), branch `review/0081-cons - `d336884` docs: README, registry, TESTPLAN, ARCHITECTURE, ADR-040, PROTOCOL, TODO after the 0081 check - the session files (this RESULT, the PROMPT, the `ai-sessions/INDEX.md` row) follow in the third commit of the same branch; PR against `main`. + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- "26 Test-ID rows" / "26 of 26 TESTPLAN rows carry the new pointer" — commit `d336884` changed 22 rows (`git show d336884 -- TESTPLAN_BLUETOOTH_HCI_SNOOP.md | grep -c '^+|'` → 22); 26 is the number of capture pointers (`PAIR-003` and `BATT-004` carry three each). + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0081_REVIEW_RESULT_2026_10_08.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0081_REVIEW_RESULT_2026_10_08 diff --git a/ai-sessions/0082_FEATURE_RESULT_2026_10_09.md b/ai-sessions/0082_FEATURE_RESULT_2026_10_09.md index 8452cfe8..e24147c8 100644 --- a/ai-sessions/0082_FEATURE_RESULT_2026_10_09.md +++ b/ai-sessions/0082_FEATURE_RESULT_2026_10_09.md @@ -9,7 +9,7 @@ indicator built on ADR-049's Settable byte and the runtime-info in-case flags, s the Controls tab to gear → Settings; (6) the Conversation detection switch moved from Sound to Controls — plus (4) a design study, no code, of volume-level notifications from `maestro_pw.Dosimeter`. Real capture bytes as fixtures, a green gate with mutation checks, the documents, the hardware-run skeleton and the release preparation for 1.2.0 (no tag, no publication) -**Status:** complete (the task was resumed once after a context reset; finished 2026-10-09 — commits pending the maintainer's answer, "Commits" below) +**Status:** complete (the task was resumed once after a context reset; finished 2026-10-09 — commits pending the maintainer's answer, "Commits" below) *(Status update 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-06/07: committed (`531c99d` and the feature commits); PR #21 merged 2026-10-09 as `ec6d163`; released as 1.2.0 after the rebuild of `ai-sessions/0084`.)* --- @@ -586,5 +586,10 @@ Proposed draft; `CAP-072` in one sitting). Six commits, not the nine sketched in Not staged: `android/domain/bin/` (an untracked build-tool output that appeared during the session; not the project's). Then `git push -u origin feature/1.2.0` and a pull request to `main` (not merged by the agent). + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- the `:ui` test delta "+14" — the per-phase counts add to 15 (4 + 3 + 3 + 3 + 1 + 1); one of the two figures is off (the gate's JUnit total is the count of record). + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0082_FEATURE_RESULT_2026_10_09.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0082_FEATURE_RESULT_2026_10_09 diff --git a/ai-sessions/0086_CAPTURE_RESULT_2026_10_12.md b/ai-sessions/0086_CAPTURE_RESULT_2026_10_12.md index c1c66aa8..bf875997 100644 --- a/ai-sessions/0086_CAPTURE_RESULT_2026_10_12.md +++ b/ai-sessions/0086_CAPTURE_RESULT_2026_10_12.md @@ -144,7 +144,7 @@ The maintainer's answer (chat 2026-10-12, `AskUserQuestion` "Commit"): *"Commit 2. `1343e97` `docs: record CAP-074 — ADR-061/ADR-032 Updates, PROTOCOL §4.1 note, registers, test plan, TODO, architecture` — `DECISIONS.md`, `PROTOCOL.md`, `CAPTURE_BLUETOOTH_HCI_SNOOP.md`, `id_registry.csv`, `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`, `TODO.md`, `ARCHITECTURE.md`. 3. `5deb859` `docs(ai-sessions): 0086 CAPTURE result, 0087 FEATURE prompt, INDEX rows` — this file, `ai-sessions/0087_FEATURE_PROMPT_2026_10_12.md` and `ai-sessions/INDEX.md`. -4. the back-fill of these hashes and the pull-request number (this commit). +4. the back-fill of these hashes and the pull-request number (this commit — `c03faab`, back-filled 2026-10-15, `ai-sessions/0094`). `git fetch` (nothing behind `origin/main`, no rebase needed), pushed without force (7 LFS objects); **pull request #37** opened with `gh pr create --base main` (https://github.com/tedsluis/opencontrolpixelbudspro2/pull/37); not merged by this session — the maintainer merges with a merge commit when CI is green diff --git a/ai-sessions/0087_FEATURE_RESULT_2026_10_12.md b/ai-sessions/0087_FEATURE_RESULT_2026_10_12.md index d415648c..312b5ad4 100644 --- a/ai-sessions/0087_FEATURE_RESULT_2026_10_12.md +++ b/ai-sessions/0087_FEATURE_RESULT_2026_10_12.md @@ -337,5 +337,10 @@ per concern, each with its *why* and the attribution line: **Release (E3, added 2026-10-13):** PR #39 merged with a merge commit on 2026-10-12 → `31ee183`, built by the maintainer (`scripts/release.sh 1.2.1`, 2026-10-12 17:17, kept as ~/opencontrol-1.2.1-tested; APK SHA-256 `a60a3cc9b2a80e9dc5fecb8810c9cb8e7cbb20fa9eb7a2a0ce663b89e964963e`, certificate `a7530f5ceadcdfc9cddcbb0e9889e7699961e8a4ef18898c4ec7738cc79d8dcb`); the hardware run `CAP-075` (both users) analysed in `ai-sessions/0088` (PR #44, merged 2026-10-13) with the verdict "no heavy issue — release as built"; tag `v1.2.1` on `31ee183`, the release published 2026-10-13 (`RELEASING.md` §13). + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- the commit that back-filled the hashes: `3950736`. + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0087_FEATURE_RESULT_2026_10_12.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0087_FEATURE_RESULT_2026_10_12 diff --git a/ai-sessions/0088_CAPTURE_RESULT_2026_10_12.md b/ai-sessions/0088_CAPTURE_RESULT_2026_10_12.md index ef149e75..b252822b 100644 --- a/ai-sessions/0088_CAPTURE_RESULT_2026_10_12.md +++ b/ai-sessions/0088_CAPTURE_RESULT_2026_10_12.md @@ -153,5 +153,10 @@ Branch `capture/cap-075` on `main` `9c52a4e` (`git fetch`: nothing behind at com - **Next:** Checkpoint 0 and the Phase D checkpoint (`AskUserQuestion`), then the registers as approved, `ensure_footers.py` + `lint_docs.py`, the RESULT's summary and the INDEX row, then the commit question. - **Files touched but not yet verified by the maintainer:** captures/CAP-075-2026-10-12_17-22-31_2026-10-13_06-34-32-Group_BK/CAP-075-EVENT-NOTES.md (modified), captures/CAP-075-2026-10-12_17-22-31_2026-10-13_06-34-32-Group_BK/CAP-075-FINDINGS.md (new), this RESULT. + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- the commits not listed: `4a8929c` (the RESULT and INDEX row) and `a57cbed` (the push and PR #44 recorded). + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0088_CAPTURE_RESULT_2026_10_12.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0088_CAPTURE_RESULT_2026_10_12 diff --git a/ai-sessions/0089_FEATURE_RESULT_2026_10_13.md b/ai-sessions/0089_FEATURE_RESULT_2026_10_13.md index c91cca7c..6b7c9617 100644 --- a/ai-sessions/0089_FEATURE_RESULT_2026_10_13.md +++ b/ai-sessions/0089_FEATURE_RESULT_2026_10_13.md @@ -263,5 +263,10 @@ merged with merge commit `5b204ea` (`gh pr merge --merge`); tag `v1.2.2` (SSH-si 2026-10-14 ("Ik heb versie 1.2.2 getest zonder CAP-076 uit te voeren en het werkt"). E1–E4: PR #52 (`release/1.2.2-after`). D4 done 2026-10-14: the published 1.2.2 installed with Obtainium on the Pixel 9a (the maintainer, chat). + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- "+8" tests in `BudsRepositoryImplTest` with "one rewritten" — §D lists 7 new tests; the gate's `:data` delta is +8 including the store test. + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0089_FEATURE_RESULT_2026_10_13.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0089_FEATURE_RESULT_2026_10_13 diff --git a/ai-sessions/0090_CROSSCHECK_RESULT_2026_10_14.md b/ai-sessions/0090_CROSSCHECK_RESULT_2026_10_14.md index 7fcc691b..3fe6571f 100644 --- a/ai-sessions/0090_CROSSCHECK_RESULT_2026_10_14.md +++ b/ai-sessions/0090_CROSSCHECK_RESULT_2026_10_14.md @@ -5,7 +5,7 @@ **Date:** 2026-10-14 **Title:** Point-by-point check of the pbpctrl-derived findings list (E-01 … E-23) against the external sources at pinned commits and this repository's own captures, APK notes and documents -**Status:** complete — every proposal answered in chat 2026-10-14 and applied (branch `research/pbpctrl-crosscheck`, PR #55; the merge is the maintainer's act); the build of ADR-064 is `ai-sessions/0091` (corrected from "awaiting maintainer sign-off" on the maintainer's feedback, `AI_SESSION_LOG_PROCEDURE.md` §4a) +**Status:** complete — every proposal answered in chat 2026-10-14 and applied (branch `research/pbpctrl-crosscheck`, PR #55; the merge is the maintainer's act); the build of ADR-064 is `ai-sessions/0091` (corrected from "awaiting maintainer sign-off" on the maintainer's feedback, `AI_SESSION_LOG_PROCEDURE.md` §4a) *(Status update 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-06/07: PR #55 merged 2026-10-14 as `c1d898b`.)* ## Summary @@ -434,5 +434,11 @@ the same on the 33 OpenControl logs → `{'no report within 2.5 s': 18, 'first v **Maintainer's answer (chat 2026-10-14, `AskUserQuestion` "E-03 docs"):** *"🟡 strong + desk + script (Recommended)"* — applied: `PROTOCOL.md` §6 item of 2026-10-14 updated, `DESKRESEARCH_FINDINGS.md` 2026-10-14 §9–§11, `scripts/wear_mode_crosscheck.py`; committed on `research/pbpctrl-crosscheck` (PR #55). + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- the ratios "73 of 73" and "12 of 77" — 75 of 89 Buds-side changes pushed, 12 of 89 lost to the Buds' `DISC` (`PROTOCOL.md` §4.1, Correction of 2026-10-15; A93-PROT-01). +- the last Commits bullet "(the PR number: the follow-up commit that records it)" is a placeholder: PR #55, merged as `c1d898b`. + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0090_CROSSCHECK_RESULT_2026_10_14.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0090_CROSSCHECK_RESULT_2026_10_14 diff --git a/ai-sessions/0091_FEATURE_RESULT_2026_10_14.md b/ai-sessions/0091_FEATURE_RESULT_2026_10_14.md index cedac95a..0ca15ed6 100644 --- a/ai-sessions/0091_FEATURE_RESULT_2026_10_14.md +++ b/ai-sessions/0091_FEATURE_RESULT_2026_10_14.md @@ -277,5 +277,12 @@ ADR-065 and the documents, `CAP-076`, 1.3.0. The split is by file and concern `scripts/release.sh 1.3.0`, B2, B3; C1–C2 the `CAP-076` run in both users (the skeleton); a CAPTURE session for C3/C4 and the verdict; only then the release (never by this session: no tag, no `dist/`, no merge). + +## Corrections (2026-10-15, `ai-sessions/0094`, maintainer-approved in chat 2026-10-15 (`AskUserQuestion` "Sessieverslagen") — `ai-sessions/0093` A93-SES-03/05/06/07) + +- §D's compliance line lists `BLUETOOTH_SCAN` among the manifest's permissions — it is not declared (removed 2026-09-24; `android/app/src/main/AndroidManifest.xml:10`, `android/hardware/src/main/AndroidManifest.xml:17`); the permissions are `BLUETOOTH_CONNECT`, `POST_NOTIFICATIONS`, `FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_CONNECTED_DEVICE`. +- §C "the Connect-sequence counts (15 requests)" — the test asserts `5 + settingReads.size` = 17 (`BudsRepositoryImplTest.kt:467–471`). +- §G's "next steps: push … and open the PR" were already done (Commits paragraph); PR #56 merged 2026-10-15 as `aec17a5`, released as 1.3.0. + --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0091_FEATURE_RESULT_2026_10_14.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0091_FEATURE_RESULT_2026_10_14 diff --git a/ai-sessions/0094_MAINTENANCE_PROMPT_2026_10_15.md b/ai-sessions/0094_MAINTENANCE_PROMPT_2026_10_15.md new file mode 100644 index 00000000..38f6d03f --- /dev/null +++ b/ai-sessions/0094_MAINTENANCE_PROMPT_2026_10_15.md @@ -0,0 +1,269 @@ +# 0094_MAINTENANCE_PROMPT_2026_10_15.md — Process every finding, lead, document proposal and decision of the 0093 audit into the project files, the scripts and the app + +**Number:** 0094 +**Category:** MAINTENANCE +**Date:** 2026-10-15 +**Title:** Validate and process all 69 findings (A93-GOV/PROT/CAP/ARCH/DEC/HK/SES/APP/RE), the 5 protocol leads (L93-1 … L93-5), the 19 document proposals (§6), +the 10 decisions (D1 … D10, §7), the 12 action rows (§8) and the grouped sub-review items (Appendix B) of `ai-sessions/0093_AUDIT_RESULT_2026_10_15.md` — +documentation and scripts first, the OpenControl app last — with the maintainer's approval in chat where `AGENTS.md` §6 requires it, and a capture skeleton for +what only hardware can settle. + +--- + +## 0. How to use this prompt + +You are an expert software architect, reverse engineer and technical auditor. This prompt is for a **fresh** Claude Code session in this repository. Your +task is to carry out the improvements that `ai-sessions/0093_AUDIT_RESULT_2026_10_15.md` reports. Run the phases of §5 **strictly in order**; do not start a +phase before the previous one is finished and recorded. + +**Number check first.** `0094` is the next free number after `0093` (`ai-sessions/INDEX.md`, checked 2026-10-15). If another session took it, use the next free +number and rename this file and its RESULT (`AI_SESSION_LOG_PROCEDURE.md` §3). + +**Reading first (mandatory, `AGENTS.md` §0.1, `AI_SESSION_LOG_PROCEDURE.md` §8).** Before any other action, take the time to read, **in full** and in this order: +`AGENTS.md`, `PROJECT_RULES.md`, `PROJECT.md`, `ARCHITECTURE.md`, `PROTOCOL.md`, `DECISIONS.md` (every ADR, ADR-001 … ADR-065 with every dated Update, and the draft +ADR-060), `TODO.md`. Then `AI_SESSION_LOG_PROCEDURE.md`, `ai-sessions/INDEX.md`, `id_registry.csv`, `RELEASING.md`, `CHANGELOG.md`, `README.md`, +`APP_TESTPLAN.md`, `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`, `CAPTURE_BLUETOOTH_HCI_SNOOP.md` (§4, the Group sections, §9), `REVERSE_ENGINEERING.md`, +`DESKRESEARCH_FINDINGS.md`, and then **`ai-sessions/0093_AUDIT_RESULT_2026_10_15.md` in full, including Appendix A (the main session's working notes) and +Appendix B (the six sub-review reports)**. Read the prompt of 0093 (`ai-sessions/0093_AUDIT_PROMPT_2026_10_15.md`) for its scope. These are large files +(`PROTOCOL.md` ≈ 3,800 lines, `REVERSE_ENGINEERING.md` ≈ 4,600, `DECISIONS.md` ≈ 2,900, the 0093 RESULT ≈ 1,900): read in chunks of ≈ 400 lines (the Read tool +truncates around 25,000 tokens; table-dense files need smaller chunks). Say in the RESULT what you read in full and what in part. + +**Nothing is taken on trust** — not this prompt, not the 0093 result, not a FACT label, not a sub-review. 0093 is an AI-written audit. Its §4 marks each finding +M (verified by its main session) or S (found by a sub-review, re-checked on its quoted lines or command); Appendix B's raw items were **not** all re-checked. +Re-derive every frame, count, line number and quote before you act on it; line numbers in 0093 are as of commit `b9828ba` and may have moved. + +**Follow every guardrail in `AGENTS.md` throughout**, in particular §1 (Zero-GMS, no `INTERNET`), §3 (Kotlin, coroutines/Flow, no hidden APIs or reflection), +§6 (no promotion to 🟢 FACT and no ADR or ADR Update without the maintainer's answer in this chat), §7 (no scanning; `CompanionDeviceManager` only), §8 (the +sealed error model), §9 (no MAC at INFO or above; payload only in debug mode), §11 (real capture bytes as fixtures), §12 (AGPL header; no code copied from the +official app or from `qzed/pbpctrl`), §13 (traceability, zero creativity on hex, a negative needs a positive control). + +**Approvals are given by the maintainer in this chat, not taken from a file.** The chat that wrote this prompt recorded **no decisions** — 0093's §7 holds +advice only. Ask once, in one consolidated checkpoint (Phase 2), with your own recommendation per question; record the answers verbatim in the RESULT. Do not +ask again what is recorded there. + +**Subagents may only read and report.** Every write is done by you, in the main session, and verified. A subagent writes its findings progressively to a scratch +file outside the repository (it may be cut off by a usage limit). Never two Gradle builds at once; never two `tshark` jobs at once; this laptop has 15 GB. +Before moving, renaming or deleting any file, list it fresh and diff the listing; never use wildcard deletes; ask before deleting anything not on an approved +list. + +**This session changes project files, scripts and app code.** It commits only after the maintainer confirms (Phase 9). `main` takes changes **only by pull +request** (`RELEASING.md`, "Repository rules"): work on a branch. It does **not** merge, tag, run `scripts/release.sh` or publish anything. + +--- + +## 1. Objective + +1. **Process the review items.** Validate, check and assess every recommendation, new finding, improvement, proposal, decision and lead in + `ai-sessions/0093_AUDIT_RESULT_2026_10_15.md`, item by item. Where it is right and worth doing, make the change in the files concerned; where it is wrong, + only partly right, already fixed or not worth its cost, reject or narrow it **with the evidence**. Where the maintainer's approval is needed, give a clear + summary and a recommendation, and ask. **At the end every item is either processed or rejected** — none is left silently open. +2. **Order of work: documentation and scripts first, the OpenControl app last.** No Kotlin file is changed before Phases 3–5 are finished and gated. +3. **Carry the findings into both the documentation and the app.** A code fix is also described wherever the documents describe that behaviour + (`ARCHITECTURE.md`, `README.md`, `CHANGELOG.md`, `APP_TESTPLAN.md`, `SECURITY.md`); a corrected protocol statement is also corrected in every document, test name + and code comment that repeats it (`grep` the whole repository). +4. **Test what existing evidence cannot settle** in a new capture skeleton (§5 Phase 4). Use the next free capture number and Group letter from `id_registry.csv` + and the Capture Index (at the time of writing the highest are `CAP-076` and Group BL); register it *planned*. Only items that genuinely need hardware go in. + +--- + +## 2. Strict execution rules + +- **No assumptions.** Verify everything. Re-run the exact `tshark` / `python3 scripts/pwrpc_decode.py` / `python3 scripts/anc_setting13_crosscheck.py` / `grep` / + `git` / `gh` (read-only) commands 0093 gives; where it gives none, run and record your own. Every changed claim links to a frame and capture, a file:line or a + specification section. +- **Validate externally** where an item rests on external ground truth, and record the URL and the exact quoted sentence in the RESULT: the Fast Pair + specification (the SASS extension page for A93-PROT-03/04; the Message Stream and Device Information pages), the **Bluetooth Core Specification** (the HCI Link + Key Notification key types for A93-CAP-05 — P-192 versus P-256), the Android Developer / AOSP reference (`CompanionDeviceManager`, `TileService`, accessibility + semantics in Compose for A93-APP-07, target-SDK behaviour changes for D7), the kotlinx.coroutines reference (`MutableSharedFlow`, `CoroutineStart.UNDISPATCHED`, + dispatchers — A93-APP-03/04), GitHub Docs on `GITHUB_TOKEN` permissions (A93-APP-09), the Claude Code setup page and the vendors' install guidance + (A93-GOV-10). Do not rely on a fetch that came back truncated or summarised; say so if a page could not be read in full. +- **No sampling.** Every item is handled, including every grouped item of Appendix B that a §4 finding points to (A93-CAP-07/08/09, A93-SES-06/07, A93-APP-08). + Every file an item touches is read in full before you edit it. A corrected statement is corrected **everywhere it appears**: `grep` the whole repository first + (0093 lists the copies it found — e.g. "thirteen requests" in six documents, "12 of 77" in five places — find the rest). +- **Best practice.** Evidence traceability (every claim → a frame/capture/file:line/spec section); structural integrity (headers, section numbers, + cross-references, table columns vs rows, footers); consistency (one fact, one wording, everywhere). +- **Labels and rule 9a.** Every new or changed protocol statement carries 🟢 FACT / 🟡 HYPOTHESIS / ⚪ ASSUMPTION / 🔴 OPEN QUESTION. `CAP-NNN-FINDINGS.md` and + `-EVENT-NOTES.md` state the current truth and are **rewritten in place**; `PROTOCOL.md`, `DECISIONS.md` and `REVERSE_ENGINEERING.md` get dated, non-destructive + Updates or Corrections. `ARCHITECTURE.md`, `CAPTURE_BLUETOOTH_HCI_SNOOP.md` and the other guides are not under rule 9a's keep-history scope: rewrite them in place. + **You never promote a finding to 🟢 FACT and never write or change an ADR without the maintainer's answer in this chat** (`AGENTS.md` §6). +- **A negative needs a positive control** (`AGENTS.md` §13 step 8): the command, its exit status, and the same filter matching a frame known to exist. Scope every + filter to the Buds' connection handle first (`bluetooth.addr` matches nothing on several logs); a filter on `data.data` does not see frames a dissector claimed + (HFP, AVDTP) — use `frame contains` or the protocol's own field. +- **Tests.** Real capture bytes as fixtures with file, frame number and command in a comment; a hand-built array only as a clearly labelled supplementary + structural test next to real fixtures (`AGENTS.md` §11). In `BudsRepositoryImplTest`, `advanceUntilIdle()` does not run the repository's `backgroundScope` + collectors — use `runCurrent()` (the existing `settle()` helper). Every code fix gets a regression test that fails without the fix. +- **Build gate.** `cd android && ./gradlew --offline --max-workers=2 assembleDebug testDebugUnitTest test lint` — green before the first change and at the end + of every phase that touches code or a Gradle/CI file (drop `--offline` only if the dependency cache is empty, and say so). Record exact test counts per module + from the JUnit XML. 0093's baseline (`b9828ba`): `:data` 1672 per variant, `:hardware` 64 per variant, `:domain` 42, `:ui` 92 per variant, `:app` none, 0 + failures; lint `:app` 6 warnings (3 `AndroidGradlePluginVersion`, 2 `NewerVersionAvailable`, 1 `OldTargetApi`), `:ui` 1 (`ModifierParameter`). No new lint + suppression; no new compiler warning. **Mutation checks one at a time** with `--max-workers=2`; after a crash, check for a leftover mutation before anything + else; restore and verify byte-identical (`sha256sum`/`cmp`). +- **Document gate.** `PYTHONDONTWRITEBYTECODE=1 python3 scripts/lint_docs.py` → exit 0 at the end of every documentation phase. The lint rejects an ID that is + not in `id_registry.csv` and a backticked file name that does not exist — register a new ID before you cite it, and write scratch file names without backticks. + If a script rewrites `scripts/__pycache__`, restore it with `git checkout -- scripts/__pycache__`. +- **App behaviour rules that stay binding:** no background, periodic or automatic connecting and no timer loops (ADR-044's visible-only re-open is the one + exception); DLCI 0x04 is claimed only by a user action (ADR-032); the settings stream on DLCI 0x02 stays read-only (ADR-064/065 — **`WriteSetting 4:{13:n}` is + out of scope**); nothing is sent that no ADR unblocks; nothing on the two GSND channels; every write through the Safe-Mode gate (ADR-042); no new permission; + no new dependency without `AGENTS.md` §10's justification and the maintainer's OK. +- **Scope** (`PROJECT.md`): nothing beyond the items. A new feature is a checkpoint question, never a silent addition. +- **Privacy:** no MAC addresses, serial numbers, names, Wi-Fi names or street addresses in new text; films may show them — do not quote them. + +--- + +## 3. Items that need the maintainer (ask in Phase 2 — nothing here is decided yet) + +For each: verify first, then ask with your own recommendation and the exact text in the question's preview. IDs are 0093's; D-numbers are its §7. + +1. **FACT and ADR text corrections** (each a dated Update/Correction): + - **D1 / A93-PROT-01, A93-DEC-02** — the setting-13 ratios in `PROTOCOL.md` §4.1 (the 2026-10-14 Update), §8's row, ADR-064's Context, the `[1.3.0]` known issue + in `CHANGELOG.md`, the 0090 summary and `CAP-076-FINDINGS.md` §6.1. Re-run `python3 scripts/anc_setting13_crosscheck.py --census` over the same 54 logs first + (0093: 73 + 2 + 2 + 12 = 89 Buds-side Notifies; 75 pushed; 12 lost to the Buds' `DISC`). The FACT itself is not in question. + - **D2 / A93-CAP-05** — "P-256" → "P-192" for link-key types 0x04/0x05 in `PROTOCOL.md` (≈ 2188–2189) and `CAP-058` (validate against the Core Specification). + - **A93-PROT-02** — "41 of 41 … while Play held the Message Stream" (`PROTOCOL.md` §4.1 note and §8 row of 2026-10-15) split into 29/29 with Play and 12/12 without. + - **D3 / A93-PROT-03, -04, -07** — the "in-use" code (`0x41`, not `0x42`) in `PROTOCOL.md` §4.5.2; closing the §6 SASS item with the spec's names and layouts; + closing the §6 ATT `0x0044` item with the 103-log scan (re-run it); and recording lead **L93-1** (`CAP-021`'s audio waves begin with the assistant-session + opener) as 🟡 in `PROTOCOL.md` §6 and `DESKRESEARCH_FINDINGS.md`. + - **A93-PROT-05, -06, -08, -09** — the Battery Notification labels, the two stale Status lines (§4.5.3, §4.5.4), the off-by-one frame (1509 → 1508) and the + `[ ]` items of §6 that are answered. + - **A93-DEC-01** — ADR-064's Status line ("not yet built" → built in 1.3.0); **A93-DEC-03** — one Status convention for superseded ADRs. + - The corrections in the capture FINDINGS that change a 🟢 statement (A93-CAP-08, A93-CAP-09: `CAP-007`, `CAP-004`/`CAP-002`, `CAP-012`, `CAP-021`, `CAP-029`, + `CAP-040`, `CAP-047`, `CAP-050`, `CAP-065` "7 of 7", `CAP-062` NAK count) — list each with its re-derived evidence. +2. **D4 — a fix release 1.3.1** for A93-APP-01 (one `BudsCompanionPairing` instance), A93-APP-02 (keep the cause on an equal read), A93-APP-03 (no `Notified` for a + `Get`'s answer), A93-APP-04 (stream pushes do not end reads) and the small items of A93-APP-05/06/08 — version 1.3.1 / 10301, `CHANGELOG.md` text, and the + reduced hardware run (the capture skeleton of Phase 4). Show each design before building it. +3. **D5 / A93-SES-08 — draft ADRs in `DECISIONS.md`**: allow a labelled "DRAFT — proposed" heading by a note in `AGENTS.md` §6, or change `lint_docs.py`. +4. **D6 / A93-DEC-04** — provenance lines for ADR-002, -004, -005, -007 (only the maintainer can say who decided them). +5. **D7** — `targetSdk` 34 (ADR-029): keep for 1.3.x, or plan the step (an ADR-029 Update) — no change without an answer. +6. **D8 / L93-4** — registering and decompiling the official app 1.0.990706425 (A93-HK-03): register it in `reverse-engineering/APK_VERSIONS.md` in this + session (no decompile here); the decompile as a later CROSSCHECK session in `TODO.md`. +7. **D9** — the two large restructurings (§6 rows 6 and 13 of 0093: split `REVERSE_ENGINEERING.md`; "Current" blocks in `PROTOCOL.md`). Propose per document: in + this session, or the corrections now and the restructure as a named later MAINTENANCE session in `TODO.md`. A restructure never changes a FACT/ADR text and never + drops evidence — prove it with a before/after check (every frame number, ADR number and capture ID present before is present after). +8. **D10 / A93-SES-09** — the dated exception note for 1.2.2's release without its hardware run in `RELEASING.md`. +9. **Project law** — `AGENTS.md` §6 framing paragraph (A93-GOV-05), §10 ViewModel bullet (A93-GOV-06); `PROJECT_RULES.md` rule 13 (A93-GOV-04). +10. **App UX choices** in A93-APP-07/08 — a confirmation before "Use different Buds", one label for the noise-control mode, human words for states and presets. +11. **Session records** — the dated corrections to the RESULTs of 0069, 0073, 0079, 0081, 0082, 0089, 0090, 0091 (A93-SES-03/05/06/07) and the INDEX cells + (A93-HK-05): ask once whether this session may add §4a status/correction lines to those files (they are history). + +Anything 0093 raised that is not listed here is processed under the same rules; if it turns out to need a decision, ask. + +--- + +## 4. Resumability (you must be able to continue automatically after a token or rate limit) + +Follow `AI_SESSION_LOG_PROCEDURE.md` §5: + +- Create **ai-sessions/0094_MAINTENANCE_RESULT_2026_10_15.md** at the very start, with `**Prompt:** ai-sessions/0094_MAINTENANCE_PROMPT_2026_10_15.md`, + `**Status:** partial — resumed`, a **Progress** block and a **per-item ledger**: one row per 0093 item — the 69 findings (A93-SES-04 is a pointer), L93-1 … L93-5, + the 19 document proposals (§6 rows 1–19), D1 … D10, the 12 action rows (§8) and one row per Appendix B item that a §4 finding groups — with its ID, verdict + (processed / narrowed / rejected / not reproduced / already fixed / awaiting maintainer / moved to the capture skeleton / registered in `TODO.md`), the evidence, + the files changed and the phase. An item with several parts gets one row **per part**. +- Update the Progress block and the ledger at the end of every phase **and** after every large file or file group: which items are done and tested, which files are + touched but not yet gated, the last gate and lint results, the checkpoint answers verbatim, where intermediate results live (the scratchpad directory — + re-create them if it is gone). +- **When resuming:** re-read this prompt, then the RESULT's Progress block and ledger, then `git status` / `git diff`; re-read the files the unfinished step touched; + continue at the first unfinished item. Never redo a finished, recorded item; never assume an unrecorded one was done; never skip one silently. Checkpoint answers + already recorded in the RESULT are not asked again. +- Phases run **strictly in sequence**. A phase is finished only when every item assigned to it has a final verdict, or is "awaiting maintainer" with the question + actually asked. + +--- + +## 5. Phases (strictly sequential) + +### Phase 0 — set-up +1. Create the RESULT file (§4). Record `git log -1`, `git status --short`, `git fetch` and `git log --oneline HEAD..origin/main`. Create a branch from an + up-to-date `main` (`git switch main && git pull --ff-only && git switch -c maintenance/0094-process-0093`; say the name used). `android/domain/bin/` is IDE build + output — never stage it. +2. Baseline gate and `lint_docs.py` (§2) before any change; record the counts. If the gate is not green, stop and report. + +### Phase 1 — triage and verification of every 0093 item (read-only) +3. For every item: re-run its evidence, give a verdict, and assign it to a phase (3–7), to the checkpoint, or to the capture skeleton. Check every S-marked item + and every Appendix B item a §4 finding groups — they are the least certain. Note where cited line numbers have moved. +4. Collect every question for the maintainer (§3) with draft texts and your recommendation. + +### Phase 2 — maintainer checkpoint (one consolidated round in this chat; follow-ups only if needed) +5. A short plain-language summary first (what 0093 found, what you verified, what did not reproduce, what you will change), then the questions (`AskUserQuestion`, + one per decision, options with pros and cons, "(Recommended)" on one). Record the answers verbatim in the RESULT. Apply only what was approved. + +### Phase 3 — governance, decisions, protocol, reverse-engineering record (documents) +6. The approved dated Updates in `DECISIONS.md` and `PROTOCOL.md` (D1–D3, A93-PROT-*, A93-DEC-*), with every copy of each corrected statement elsewhere (`CHANGELOG.md`, + `DESKRESEARCH_FINDINGS.md`, `ARCHITECTURE.md`, `TODO.md`, the 0090 summary by a §4a line if approved; code comments and test names are listed for Phase 6/7). +7. The approved project-law changes (`AGENTS.md`, `PROJECT_RULES.md`); `PROJECT.md` (A93-GOV-01 … 03); `README.md` (A93-GOV-07/08). +8. `ARCHITECTURE.md` rewritten as built at 1.3.0 where 0093 found it stale (A93-ARCH-01 … 05) — the parts that depend on a Phase 6/7 change wait for Phase 8. +9. `REVERSE_ENGINEERING.md` corrections (A93-RE-01 … 06: restore the broken sentence from `git log -S`, the line self-references, the correlation table, the call + graph, the UUID register row, the small items), and the restructure if approved (D9). + +### Phase 4 — captures, test plans, capture plan (documents) +10. Rewrite in place every FINDINGS / EVENT-NOTES 0093 names (A93-CAP-06 … 09 and the Appendix B items they group), after re-deriving each frame; the Test-ID swaps + against `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`; the Capture Index rows and `id_registry.csv` where they change. +11. `CAPTURE_BLUETOOTH_HCI_SNOOP.md` (A93-CAP-01/02), `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` (A93-CAP-03: the stale lines; the Evidence cells to pointers if approved under + D9), `APP_TESTPLAN.md` (A93-CAP-01, A93-CAP-04). +12. **Desk analysis** of what existing logs settle: L93-1 (the assistant-session opener in every capture with a GSND AUDIO burst, not only `CAP-021` — tabulate), + L93-2/3 (re-run), and any Appendix B claim that needs a re-count. Results go into the captures' FINDINGS and, as labelled proposals, to the maintainer — not into + `PROTOCOL.md` as FACT without approval. +13. **The capture skeleton** for the hardware run of the approved app fixes (D4) — next free capture number and Group letter, `captures/CAP-NNN-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_/` + with its EVENT-NOTES in the layout of `CAP-076-EVENT-NOTES.md` (build, metadata, preparation, steps with expected screen and expected HCI bracket, "Refuted if", + don'ts, collection, analysis checklist with Test-IDs). Candidate steps, each only if still needed: "Use different Buds" then Connect with two paired Pixel Buds + or the same pair (A93-APP-01); a reconnect after a hold made while disconnected — the "changed since" line (A93-APP-02); ten tile taps with Google Play services + present (A93-APP-03); a Refresh and a read right after the subscription (A93-APP-04); TalkBack on the switches and EQ sliders (A93-APP-07). Keep it short; add the + Group section and the §9 row; register the capture *planned*. + +### Phase 5 — scripts, CI, release machinery and the remaining documents +14. `scripts/lint_docs.py`: the checks 0093 proposes — INDEX status cells (A93-HK-05), no `[x]` in `TODO.md` (A93-HK-01), the README ADR count against the registry + (A93-GOV-07), the first word of an ADR Status against the registry (A93-DEC-03), "(this commit)" left unfilled (A93-SES-07) — run each against the repository + and fix what it finds; `.github/workflows/lint-docs.yml` `permissions: contents: read` (A93-APP-09); `.gitignore` for `android/**/bin/` (A93-HK-07). +15. `TODO.md` (A93-HK-01/02), `RELEASING.md` (E1 list, D10), `MAINTAINING_DOCS_SITE.md` (A93-HK-04), `CHANGELOG.md` (A93-HK-06 and the other slips), + `reverse-engineering/APK_VERSIONS.md` (A93-HK-03, D8), `reverse-engineering/tools/BACKLOG.md` and the leftover `scratch_0024/` (A93-HK-08 — ask before deleting), + `CONTRIBUTING.md` (A93-GOV-09), `WORKSTATION_PREPARATIONS.md` (A93-GOV-10/11), `APK_REVERSE_ENGINEERING_PROCEDURE.md`, `DESKRESEARCH_FINDINGS.md`, + `AI_SESSION_LOG_PROCEDURE.md` (0093 §6 row 7), `SECURITY.md` (0093 §6 row 18), the session records as approved (§3 item 11), and every other §6 proposal approved. +16. Gate (if a Gradle/CI file changed) and `lint_docs.py`. **Documentation and scripts are now finished; only now does app work start.** + +### Phase 6 — the OpenControl app: the user-facing defect first +17. **A93-APP-01.** A failing test first (two lookups through the repository's provider after "Use different Buds" on the Activity's path); inject the singleton + `BudsCompanionPairing` into `MainActivity` (or move the flag to one shared holder) without breaking the `IntentSender` launch; then the fix; regression test green. +18. **A93-APP-02 / A93-APP-03 / A93-APP-04** as approved: fix `BudsRepositoryImplTest`'s re-open test to answer the `Get` and the `4:13` read with the **same** mode + (CAP-076 H1 4094 → 4135) so it fails today; then return `null` for an equal read; a unit test that a Notify answering a `Get` emits no `AncOutcome.Notified`; + carry `stream` in the replies so only a RESPONSE ends a read's wait, and let `_ancModeFresh` come only from the Message Stream Notify. +19. Gate. Mutation checks, one at a time: each fix reverted must fail ≥ 1 test; restore and verify byte-identical. + +### Phase 7 — the OpenControl app: the remaining items +20. As approved: A93-APP-05 (catch around `CompanionDeviceManager.associate`; the transport's check-and-put under its lock; a scope exception handler; `.catch` on the + preference flows), A93-APP-06 (the real `CAP-051` 1527 bytes or delete the fake fixture; the fuzz tests assert the decoder-fault count is 0; the stale test names), + A93-APP-07 (accessibility), A93-APP-08 (labels, the KDoc of removed 1.2.x parameters, `ModifierParameter`, the stale `TODO(verify)`, the small code items), and + every stale code comment that repeats a statement corrected in Phases 3–4. +21. List every `transport.send(` call site before and after; any difference is explained (expect none — no new wire traffic). +22. Gate after each item; the final gate after `./gradlew --offline clean`. Compliance: no `INTERNET`, no new permission, no manifest change except what was approved; + `git diff --name-only -- '*.gradle.kts' '*.toml' '*AndroidManifest.xml'` empty or only what was approved; AGPL header on new files; nothing sent on a channel or + with a command no ADR unblocks; version 1.3.1 / 10301 only if D4 was approved. + +### Phase 8 — documents that follow the code, and verification +23. `ARCHITECTURE.md`, `README.md`, `SECURITY.md`, `CHANGELOG.md` (`[1.3.1]` as approved, with known issues), `APP_TESTPLAN.md` (steps for every app change; add them to + the capture skeleton), `TODO.md` (every deferred item, every lead not closed, every approved later session), `ai-sessions/INDEX.md` (the 0094 row; rows whose + status changed). +24. Re-grep every corrected statement across the repository (nothing left in another file, test name or code comment); re-run every command whose result you wrote + into a document; `python3 scripts/lint_docs.py` → exit 0; final gate. + +### Phase 9 — finish +25. Finish the RESULT: a plain-language summary first (what changed for the maintainer and for the app user), the per-item ledger (every 0093 item with its verdict — + none open), the items that did not reproduce, the checkpoint answers verbatim, the gate table, the mutation table, compliance, the capture-skeleton summary, external + sources (URL + quoted sentence), what was read in full and in part, and the closing sections "Deferred documentation" (each item also in `TODO.md`) and "Commits". + Status per `AI_SESSION_LOG_PROCEDURE.md` §4. +26. Show the maintainer a short summary and **ask whether to commit and push**. Only after a yes: Conventional Commits, one commit per concern, each building and + passing on its own (`PROJECT_RULES.md` rule 16) — e.g. `docs` (decisions/protocol), `docs` (captures/test plans), `chore` (scripts/CI), `fix(app)` for + A93-APP-01 on its own, further `fix`/`test` commits — each with a *why* and the attribution line from the session's system reminder; `git fetch` and rebase before + pushing, never force; nothing from a build directory, `android/domain/bin/`, `dist/`, `.vscode/` or `__pycache__` staged. Opening the pull request is a separate + question; never merge it; no tag and no release. + +--- + +## 6. Deliverables + +- **ai-sessions/0094_MAINTENANCE_RESULT_2026_10_15.md** — Progress block, per-item ledger (all 69 findings, 5 leads, 19 document proposals, 10 decisions, 12 action rows + and the grouped Appendix B items closed), checkpoint answers, gate/mutation/compliance tables, external sources, "Deferred documentation", "Commits", footer. +- The changed project files, scripts and app code, and the new capture skeleton — each change traceable to a 0093 item and to its evidence. +- Nothing left silently open: every item processed, narrowed, rejected, not reproduced, already fixed, moved to the capture skeleton, registered in `TODO.md` for a + named later session, or "awaiting maintainer" with the question asked. + +--- +https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0094_MAINTENANCE_PROMPT_2026_10_15.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0094_MAINTENANCE_PROMPT_2026_10_15 diff --git a/ai-sessions/0094_MAINTENANCE_RESULT_2026_10_15.md b/ai-sessions/0094_MAINTENANCE_RESULT_2026_10_15.md new file mode 100644 index 00000000..79b36be4 --- /dev/null +++ b/ai-sessions/0094_MAINTENANCE_RESULT_2026_10_15.md @@ -0,0 +1,276 @@ +# 0094_MAINTENANCE_RESULT_2026_10_15.md — Process every finding, lead, document proposal and decision of the 0093 audit + +**Number:** 0094 +**Category:** MAINTENANCE +**Date:** 2026-10-15 +**Prompt:** ai-sessions/0094_MAINTENANCE_PROMPT_2026_10_15.md +**Title:** Validate and process the 69 findings, 5 leads, 19 document proposals, 10 decisions and 12 action rows of `ai-sessions/0093_AUDIT_RESULT_2026_10_15.md` — +documentation and scripts first, the app last +**Status:** complete + +## Summary + +Every item of `ai-sessions/0093` has a verdict (ledger below — no row open): the documentation and scripts first, then the app. The maintainer's checkpoint +answers decided the 🟢 texts, the ADR texts, the project-law notes and the fix release; two project-law proposals (A93-GOV-05/06) were declined. The capture +notes were corrected from three read-only sub-agents' verified drafts: 330 items confirmed and 654 edits in 139 files, six items not reproduced and left as +they were. The app is **1.3.1** (versionCode 10301, not released): "Use different Buds" now acts on the pairing the repository connects with and asks first; a +re-open keeps "Changed since the app last saw it"; a tap's `Set` no longer takes the `Get`'s answer; stream pushes no longer end reads; the error-model gaps are +closed; the fuzz tests fail on a throwing decoder; TalkBack reads the switches and sliders; each mode has one name. Each app fix has a test that fails on the 1.3.0 +code or under a mutation. Nothing new on the wire. Its hardware run is `CAP-077` (Group BM, planned, skeleton written). Commits wait for the maintainer. + +## Progress + +| Phase | State | Notes | +|---|---|---| +| 0 Set-up | done | branch `maintenance/0094-process-0093`; baseline gate green (below); `lint_docs.py` exit 0 | +| 1 Triage | done | every item assigned (ledger column Phase); M-verified items of 0093 stand (same commit, no data change); S items re-checked as noted | +| 2 Checkpoint | done | three rounds, answers below | +| 3 Governance/decisions/protocol/RE documents | done | `PROTOCOL.md`, `DECISIONS.md`, `AGENTS.md`, `PROJECT_RULES.md`, `PROJECT.md`, `README.md`, `ARCHITECTURE.md`, `REVERSE_ENGINEERING.md`, `DESKRESEARCH_FINDINGS.md`, `CHANGELOG.md` (three dated notes); lint exit 0 | +| 4 Captures and test plans | done | the CAPTURE guide, `APP_TESTPLAN.md`, the TESTPLAN (P3/P4 items); the capture files from three read-only sub-agents' verified drafts, applied by the main session (Appendix A.4); the `CAP-077` skeleton (Group BM) | +| 5 Scripts, CI, remaining documents | done | `lint_docs.py` `check_governance()` (h)–(l) and the retired `promoted` status; `.github/workflows/lint-docs.yml` permissions; `.gitignore`; `RELEASING.md`, `CONTRIBUTING.md`, `WORKSTATION_PREPARATIONS.md`, `MAINTAINING_DOCS_SITE.md`, `SECURITY.md`, session records — **deviation:** started while the Phase 4 sub-agents were still running (no shared file; their drafts were applied afterwards) | +| 6 App: user-facing defect first | done | A93-APP-01 … 04, each test first (failing on 1.3.0) — **deviation:** started before the Phase 4 drafts were applied (app code and capture notes share no file) | +| 7 App: remaining items | done | A93-APP-05 … 08; version 1.3.1 / 10301; the `transport.send` call sites unchanged (8 MAESTRO, 3 Message Stream, before and after) | +| 8 Documents that follow the code | done | `ARCHITECTURE.md` (cause rule, stream replies, pairing memory, accessibility), `CHANGELOG.md` [1.3.1], `README.md`, `PROJECT.md`, `SECURITY.md` (fuzz), `APP_TESTPLAN.md` §Z, `TODO.md` §1/§6, the CONV-002 note | +| 9 Finish | done | this RESULT; commits await the maintainer | + +### Phase 0 — recorded facts + +- `git log -1`: `cb41e7d` 2026-10-15 20:29:44 +0200 "Merge pull request #60 from tedsluis/audit/0093-full-audit"; `git fetch` → nothing new (`HEAD..origin/main` empty). +- `git status --short` (before the branch): ` M ai-sessions/INDEX.md` (the planned 0094 row), `?? ai-sessions/0094_MAINTENANCE_PROMPT_2026_10_15.md`, `?? android/domain/bin/`. +- Branch: `maintenance/0094-process-0093` (from `main` at `cb41e7d`; the two uncommitted prompt-writing changes carried along). +- **Reading.** This session runs in the same chat as 0093, which read every mandatory document in full at `b9828ba` (0093 RESULT §3). `git diff --name-only + b9828ba..HEAD` lists only `ai-sessions/0093_AUDIT_PROMPT_2026_10_15.md`, `ai-sessions/0093_AUDIT_RESULT_2026_10_15.md` and `ai-sessions/INDEX.md` — no other file + changed since that reading, so it is not repeated; every file is re-read (the part concerned, or in full when it is rewritten) before it is edited. + +## Ledger (one row per 0093 item; verdicts: processed / narrowed / rejected / not reproduced / already fixed / awaiting maintainer / capture skeleton / `TODO.md`) + +Phase key: CP = maintainer checkpoint (Phase 2), P1 … P8 = the phase that changes the files. + +| Item | Title (0093) | Phase | Verdict | Evidence / files changed | Notes | +|---|---|---|---|---|---| +| A93-GOV-01 | `PROJECT.md` says 1.3.0 is "not released". | P3 | processed | `PROJECT.md` §Status rewritten for 1.3.0 (released) | | +| A93-GOV-02 | `PROJECT.md`'s status stamp and status paragraph lag the releases. | P3 | processed | `PROJECT.md:39` stamp; status narrative → a short current-release box + pointer to `CHANGELOG.md` | | +| A93-GOV-03 | `PROJECT.md` phase 6 names only the protocol test plan. | P3 | processed | `PROJECT.md:27` names `APP_TESTPLAN.md` | | +| A93-GOV-04 | `PROJECT_RULES.md` rule 13's reading list differs from `AGENTS.md` §0.1. | CP | processed | `PROJECT_RULES.md` rule 13 → `AGENTS.md` §0.1 (approved, "Projectwet") | | +| A93-GOV-05 | `AGENTS.md` §6's framing paragraph still opens "Framing is not yet settled". | CP | rejected (maintainer) | not chosen at the checkpoint ("Projectwet") — `AGENTS.md` §6 framing paragraph unchanged | | +| A93-GOV-06 | `AGENTS.md` §10's preferred stack names ViewModel-Compose; ADR-048 decided "no ViewModel". | CP | rejected (maintainer) | not chosen at the checkpoint — `AGENTS.md` §10 unchanged | | +| A93-GOV-07 | `README.md` "Current state" and contributor section are out of date in four places. | P3 | processed | `README.md` Current state: date 2026-10-15, "58 accepted, 6 superseded, one draft", ADR-060 "a later release", newest run `CAP-076` | | +| A93-GOV-08 | `README.md`'s Status section repeats nine release blocks. | P3 | processed | `README.md` §Status: the current release + its known issues; older releases → `CHANGELOG.md` | | +| A93-GOV-09 | `CONTRIBUTING.md` describes a pre-release project and omits the repository rules. | P5 | processed | `CONTRIBUTING.md`: framing + "How a change gets in" | | +| A93-GOV-10 | `WORKSTATION_PREPARATIONS.md` installs tools against their vendors' current guidance. | P5 | processed (narrowed) | `WORKSTATION_PREPARATIONS.md`: dated recommendation notes beside the recorded transcripts (npm docs, Claude Code setup, Antigravity release page — its RPM repo is legacy and unsigned by the vendor) | | +| A93-GOV-11 | `WORKSTATION_PREPARATIONS.md` small defects. | P5 | processed | `warn` → an inline echo; `capinfos`, `uv` added to the check list | | +| A93-PROT-01 | The 2026-10-14 FACT on setting 13 counts a class against itself ("73 of 73") and uses a wrong denominator ("12 | CP/P3 | processed | `PROTOCOL.md` §4.1 Correction 2026-10-15 (75/89, 12/89, 2/89; census numbers re-used — no log changed since the 2026-10-15 re-run); §8 row; `CHANGELOG.md` [1.3.0] note | | +| A93-PROT-02 | "41 of 41 holds … while Play held the Message Stream" conflates the two users. | CP/P3 | processed | `PROTOCOL.md` §4.1 Correction 2026-10-15 item (2): 29/29 with Play, 12/12 without | | +| A93-PROT-03 | The SASS burst's "in-use" string is attributed to Code `0x42`; it is in `0x41`. | CP/P3 | processed | `PROTOCOL.md` §4.5.2 Correction: "in-use" in 0x41 (`CAP-019` 804/806/810; SASS page) | | +| A93-PROT-04 | The open §6 item on SASS Codes `0x11`/`0x21`/`0x40`/`0x42`/`0x34` is answerable from the official page. | CP/P3 | processed | `PROTOCOL.md` §6 SASS item ticked with the spec's names/lengths (🟢), encrypted contents 🔴 | | +| A93-PROT-05 | The BLE Battery Notification is still labelled "confirmed as what the Buds Pro 2 send". | CP/P3 | processed | `PROTOCOL.md` §1 row and Option A Status relabelled (🔴 not observed from the Pro 2) | | +| A93-PROT-06 | Two 🟢 promotions are not reflected in their entries' own Status lines. | CP/P3 | processed | `PROTOCOL.md` §4.5.3/§4.5.4 Status pointers | | +| A93-PROT-07 | The ATT `0x0044` notification burst item can be closed. | CP/P3 | processed | `PROTOCOL.md` §6 ATT `0x0044` item closed with the 103-log scan | | +| A93-PROT-08 | One frame number off by one. | CP/P3 | processed | `PROTOCOL.md:112` frame 1508 note | | +| A93-PROT-09 | §6's open items lag findings made elsewhere in the same document. | CP/P3 | processed (narrowed) | §6: field-13 mirror item ticked; pointers on the `CAP-038` two-Notify item (stays open — trigger unfilmed), the DLCI 0x08 Get-code item and the GSND assistant item; DLCI 0x0a item updated (L93-1) | | +| A93-CAP-01 | "Thirteen MAESTRO requests at Connect" (1.2.x) is a miscount in six documents. | P4 | processed | "thirteen" → fifteen in `APP_TESTPLAN.md` (8) and `CAPTURE_BLUETOOTH_HCI_SNOOP.md` (5) with a dated note; 1.3.0 "fifteen" → seventeen; `CHANGELOG.md` [1.2.0] note; capture files via the sub-review edits | | +| A93-CAP-02 | `CAPTURE_BLUETOOTH_HCI_SNOOP.md` stale statements. | P4 | processed | capture guide: Group AH sweep range, Group BK status, "opcode table" wording (3), Group column note, `promoted` retired, §4.1 superseded tip layers replaced by a current note | | +| A93-CAP-03 | `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` breaks its own Evidence-column rule and has stale lines. | P4 | processed (narrowed) | `TESTPLAN`: groups "AA–BL", §9 `SWITCH-001`/`WELL-001`, `ANC-001` "Planned" wording; Evidence cells → pointers deferred to the restructure session (D9) | | +| A93-CAP-04 | `APP_TESTPLAN.md` section Y has its columns shifted. | P4 | processed | `APP_TESTPLAN.md` §Y: the 22 rows' "Expected on the wire" restored from `8a72572` (counts → seventeen), results moved to "Result"; note under §Y | | +| A93-CAP-05 | `PROTOCOL.md` and `CAP-058` name the link-key type wrongly: P-192, not P-256. | CP | processed | PROTOCOL.md key-type Correction (P-192; kernel commit 11015c79; tshark CAP-058 2235); CAP-058-FINDINGS.md :89 and :93 corrected (tshark 2235, 3804, .log.last 10333/11982 all "Combination Key, P-192"); the EVENT-NOTES name no P-256 (not reproduced there) | | +| A93-CAP-06 | Test-IDs swapped or misused in five EVENT-NOTES (traceability, `AGENTS.md` §13 step 7). | P4 | processed | Test-ID tags re-checked against the TESTPLAN titles and corrected in the EVENT-NOTES (sub-agent items SC1/SC2/SC3, e.g. SC3-14/40/45/47/56/60/64; GATT-001 "attempted, no discovery" for CAP-003/010); Appendix A.4 | | +| A93-CAP-07 | Counts in the 1.x capture FINDINGS that do not add up. | P4 | processed | every count re-derived (tshark/pwrpc_decode, one process at a time) and corrected in place with the 🟢 correction marker; differences from the sub-reviews' numbers recorded (SC3-30/31/54/67/68, SC2-117/119, SC1-20/106); Appendix A.4 | | +| A93-CAP-08 | Wrong or stale statements in `CAP-026` … `CAP-051` files. | CP/P4 | processed | CAP-026…050: 118 of 122 items confirmed and applied (226 edits); SC2-05, SC2-50, SC2-97 not reproduced (left as is, reasons in Appendix A.4); SC2-116 withdrawn by its sub-review; SC2-18 extended (DLCI 0x08 frames 1592/1711 — PROTOCOL.md §6 CONV-002 Update and TESTPLAN CONV-002 row, re-checked with tshark) | | +| A93-CAP-09 | Wrong or stale statements in `CAP-001` … `CAP-025`. | CP/P4 | processed | CAP-001…025: 142 of 143 items confirmed and applied (270 edits); SC1-133 not reproduced (no exact correction derivable — TODO.md §6); CAP-051…075: 70 of 72 + 3 extras applied (158 edits; SC3-07, SC3-38 not reproduced); 654 edits in 139 files, each OLD exactly once, lint exit 0; further slips found → TODO.md §6 | | +| A93-ARCH-01 | §3.1's ANC reconciliation row describes 1.2.x behaviour as current. | P3 | processed | `ARCHITECTURE.md` §3.1 ANC row rewritten as built at 1.3.0 (sources, causes, no reset at Connect, hold as history); re-checked in Phase 8 after the app fixes | | +| A93-ARCH-02 | §2.4's navigation tree and §9's persistence line still list removed 1.2.x settings. | P3 | processed | §2.4 tree and §9 persistence: the 1.2.x card/key as history | | +| A93-ARCH-03 | `BluetoothGatt` is named as owned by the hardware layer; no GATT client exists. | P3 | processed | §2 Hardware layer and §2.1 `BudsTransport`: no `BluetoothGatt` as built | | +| A93-ARCH-04 | §8.1 keeps the 2026-08 design text below the as-built note. | P3 | processed | §8.1 design paragraph → the as-built gate (announcement firmware + Model ID, ADR-042) | | +| A93-ARCH-05 | §15 says no architecture question is open; §6.0b keeps one open. | P3 | processed | §15 lists §6.0b (b) as open | | +| A93-DEC-01 | ADR-064's Status still says "not yet built". | CP | processed | ADR-064 Status pointer (built in 1.3.0, CAP-076, released) | | +| A93-DEC-02 | ADR-064's Context repeats the circular "73 of 73". | CP | processed | ADR-064 Note 2026-10-15 (75 of 89; decision unchanged) | | +| A93-DEC-03 | Superseded ADRs use two Status conventions. | CP | processed | ADR-061/062/063 Status begin "Superseded by ADR-065 (2026-10-14) — was: …" | | +| A93-DEC-04 | Four early ADRs record no decision-maker. | CP | processed | ADR-002/004/005/007: provenance line "decided by the maintainer at project start" (maintainer's statement, "ADR-herkomst") | | +| A93-HK-01 | `TODO.md` keeps finished items although its own rule and `RELEASING.md` E3 say to delete them. | P5 | processed | five `[x]` items deleted from `TODO.md`; lint (i) now fails on `[x]` | | +| A93-HK-02 | Three open `TODO.md` items describe a world before 1.3.0. | P5 | processed | `TODO.md` stored-choice sentence and "1.3.0 candidate" relabelled | | +| A93-HK-03 | `reverse-engineering/APK_VERSIONS.md` says the second APK was "Not pulled". | P5 | processed | `reverse-engineering/APK_VERSIONS.md`: three rows (SHA-256, `aapt2 dump badging`), dated note; `TODO.md` §4 updated | | +| A93-HK-04 | `MAINTAINING_DOCS_SITE.md` still describes pushing to `main` and bots that commit to it. | P5 | processed | `MAINTAINING_DOCS_SITE.md`: PR flow and the bot branches | | +| A93-HK-05 | `ai-sessions/INDEX.md` status cells drift from the RESULT headers; nothing checks them. | P5 | processed | INDEX cells 0069, 0079, 0089, 0091 aligned; lint (h) checks every cell against its RESULT | | +| A93-HK-06 | `CHANGELOG.md` `[1.1.1]` contradicts itself on hardware testing. | P5 | processed | `CHANGELOG.md` [1.1.1] dated note (CAP-071 passed) | | +| A93-HK-07 | IDE build output in `android/domain/bin/` is not ignored. | P5 | processed | `.gitignore` `android/**/bin/`; `android/domain/bin/` deleted (25 files, all byte-identical to `src/*/kotlin`, checked with `cmp`) | | +| A93-HK-08 | Small leftovers. | P5 | processed | empty `scratch_0024/` removed; `reverse-engineering/tools/BACKLOG.md` doubled rule removed | | +| A93-SES-01 | This audit's own prompt omits two releases. | P1 | rejected (no action) | the 0093 prompt's own context line; noted in 0093, nothing to change | | +| A93-SES-02 | `0091` did not finish the `ARCHITECTURE.md` update its prompt required, and its RESULT says it did. | P3 | processed | the `ARCHITECTURE.md` gaps it describes are fixed (ARCH-01/02) | | +| A93-SES-03 | `0091`'s compliance record lists a permission the app does not declare, and a wrong request count. | CP | processed | 0091 RESULT Corrections block (`BLUETOOTH_SCAN`, 17 requests, next-steps) | | +| A93-SES-04 | `0090`'s headline ratios | P1 | processed with PROT-01 | a pointer | | +| A93-SES-05 | `0090`/`0091`/`0092` small record defects. | CP | processed | 0090 RESULT Corrections (ratios, PR placeholder); `CAP-076` overwrite-delay wording via the capture edits | | +| A93-SES-06 | Session records that disagree with the repository (sub-review of `0069` … `0089`, each re-verified). | CP | processed | Corrections blocks/status updates in 0069, 0073, 0078, 0079, 0081, 0082, 0089; status updates 0076, 0077, 0078, 0079, 0081, 0082, 0090 | | +| A93-SES-07 | `AI_SESSION_LOG_PROCEDURE.md` §4b is not followed consistently. | CP/P5 | processed | "(this commit)" back-filled in 0073, 0075, 0076, 0077, 0078, 0079, 0086; missing commits listed in 0069, 0070, 0071, 0072, 0087, 0088; lint (l); `AI_SESSION_LOG_PROCEDURE.md` §4b items 5–6 | | +| A93-SES-08 | ADR-060 entered `DECISIONS.md` as a "Proposed" heading, against the prompt; the lint forced it. | CP | processed | = D5 | | +| A93-SES-09 | 1.2.2 was released without its hardware run and without the exception note `RELEASING.md` uses for 1.0.0. | CP | processed | = D10, `RELEASING.md` exception note | | +| A93-APP-01 | "Use different Buds" acts on a second `BudsCompanionPairing`; the repository keeps connecting to the old Buds | CP/P6 | processed | PairingChoiceMemory shared by the Activity instance and the DI singleton (BudsCompanionPairing.kt, RepositoryModule.kt, MainActivity.kt); CompanionPairingChoiceTest 2 tests; mutation (per-instance memory) 2 of 2 fail | | +| A93-APP-02 | A re-open's "Changed since the app last saw it" is overwritten by the equal `4:13` answer (CAP-076 defect (a)) | CP/P6 | processed | causeOfReadAnswer keeps CHANGED_SINCE_LAST_READ for the other read source of the same read round (readRound in launchInitialEqRead); test with real CAP-076 H1 4094 → 4135 (Cap076Fixtures.kt) fails on 1.3.0, passes now; two mutations killed. The audit's note that the re-open test answers Get and read differently is inexact: that test has no Get (it reads only); left as is | | +| A93-APP-03 | A tap's `Set` can take the `Get`'s own Notify as its answer (CAP-076 defect (b), the silent tile tap). | CP/P6 | processed | no AncOutcome.Notified for a Notify that answers a Get; test with the caller on UnconfinedTestDispatcher reproduces the interleaving (1.3.0: Success with no answer; now Timeout) | | +| A93-APP-04 | Settings-stream pushes can end a pending read's wait. | CP/P6 | processed | stream flag carried in RoutedFrame.Eq and MaestroReply.Value/Setting; the reads accept only RESPONSEs; applyAncState no longer feeds _ancModeFresh; four tests (4:13 read, EQ read, settings read, Refresh) — three fail on 1.3.0, the fourth by mutation | | +| A93-APP-05 | Narrow error-model gaps (`AGENTS.md` §3/§8). | CP/P7 | processed | associate() in try/catch with the in-flight flag reset; RfcommBudsTransport attach() under the connection's lock with closeAll (by construction — no deterministic test of the window); application scope CoroutineExceptionHandler that logs; DataStore readableData() .catch(IOException) → defaults, test with a corrupt file (mutation killed) | | +| A93-APP-06 | Tests that do not test what they say. | P7 | processed | SET_TRANSPARENT_CAP051_1527 = the real frame (tshark CAP-051 1527, the official app's Set with its 16 trailing bytes); fuzz tests in SettingsStreamCodecTest, OversizedLengthTest, HardwareInfoCodecTest, CodecRouterTest count onDecoderFault and assert 0 (mutation: a throwing router fails the fuzz test); three stale test names renamed | | +| A93-APP-07 | Accessibility. | CP/P7 | processed | toggleable rows (Role.Switch / Role.Checkbox) in SettingsUi, ControlsScreen, DebugScreen; EQ band sliders with contentDescription + stateDescription (dB / not read); tab icons contentDescription = null; the UI tests' finders follow the merged rows (5 failed on the 1.3.0 layout); two new slider tests | | +| A93-APP-08 | UI wording and small code issues. | CP/P7 | processed | AncMode.label used by buttons, (i), tile, notification; connectionStateText; presetLabel; Use different Buds confirmation dialog (test: Cancel 0, confirm 1); KDoc of removed parameters and MESSAGE_STREAM_LINGER_MS; modifier first optional parameter; log line "unchanged"; Toast on applicationContext; null-safe launch intent; stale TODO(verify) in BudsCompanionPairing replaced by the hardware confirmation | | +| A93-APP-09 | `.github/workflows/lint-docs.yml` runs with the default token permissions. | P5 | processed | `.github/workflows/lint-docs.yml` `permissions: contents: read` (GitHub Docs "Automatic token authentication": "grant the GITHUB_TOKEN the least required access") | | +| A93-RE-01 | Self-references by line number have drifted. | P3 | processed | four line self-references replaced by entry references | | +| A93-RE-02 | A broken edit and a duplicated discovery in the `frb`/`fuh`/`glk`/`gjv` entry. | P3 | processed | broken sentence restored from `08fc3ad` (marked); correction on 0023's "both prior passes"; "seven classes" | | +| A93-RE-03 | The "Correlation status with `PROTOCOL.md`" table stops on 2026-10-06. | P3 | processed | correlation table: four rows added, empty template row removed | | +| A93-RE-04 | The call-graph block still shows four `GetSoftwareInfo` calls. | P3 | processed | call-graph block: "opens 4 pending calls — no request sent" | | +| A93-RE-05 | The UUID register keeps a superseded 🟡. | P3 | processed | UUID register row: 🟢 pointer (ADR-019/034) | | +| A93-RE-06 | Smaller document defects. | P3 | processed | mid-sentence pointer moved; Message Group intro, pbtk mention, "static findings 🟡" clarified; register rows 5/14 and the provider-service open questions pointed | | +| L93-1 | lead | CP/P4 | processed | `PROTOCOL.md` §6 DLCI 0x0a Update (🟡 strong), `DESKRESEARCH_FINDINGS.md` 2026-10-03 item 4 Update; the census over every GSND burst in Phase 4 | | +| L93-2 | lead | CP/P4 | processed | = A93-PROT-03/04 | | +| L93-3 | lead | CP/P4 | processed | = A93-PROT-07 | | +| L93-4 | lead | CP/P4 | TODO.md | `TODO.md` §4 decompile item (settings 23/31/39 named there) | | +| L93-5 | lead | CP/P4 | TODO.md | recorded in the same `TODO.md` §4 item (the newer APK is where the next pass starts) | | +| DOC-01 | 0093 §6 row 1 | P3–P5 | processed | = A93-ARCH-01…05 | | +| DOC-02 | 0093 §6 row 2 | P3–P5 | processed (narrowed) | the two slips fixed; the template relaxation is part of the restructure sessions (`TODO.md` §6) | | +| DOC-03 | 0093 §6 row 3 | P3–P5 | rejected (maintainer) | `AGENTS.md` §5/§6 shortening not chosen; only the D5 note added | | +| DOC-04 | 0093 §6 row 4 | P3–P5 | processed (narrowed) | the stale lines fixed; the index-as-one-line restructure stays in `TODO.md` §6 (D9) | | +| DOC-05 | 0093 §6 row 5 | P3–P5 | processed | = A93-HK-04 | | +| DOC-06 | 0093 §6 row 6 | P3–P5 | TODO.md | the split stays a later session (D9); the corrections were made (RE-01…06) | | +| DOC-07 | 0093 §6 row 7 | P3–P5 | processed | `AI_SESSION_LOG_PROCEDURE.md` §4b items 5–6 + lint (h)/(l) | | +| DOC-08 | 0093 §6 row 8 | P3–P5 | processed | = A93-GOV-01…03 | | +| DOC-09 | 0093 §6 row 9 | P3–P5 | TODO.md | Evidence cells → pointers: later session (D9) | | +| DOC-10 | 0093 §6 row 10 | P3–P5 | processed | three dated corrections in `CHANGELOG.md` | | +| DOC-11 | 0093 §6 row 11 | P3–P5 | processed | = A93-GOV-04 | | +| DOC-12 | 0093 §6 row 12 | P3–P5 | processed | = A93-HK-01 + lint (i) | | +| DOC-13 | 0093 §6 row 13 | P3–P5 | TODO.md | "Current" blocks: later session (D9); the corrections were made | | +| DOC-14 | 0093 §6 row 14 | P3–P5 | processed | = A93-GOV-10/11 | | +| DOC-15 | 0093 §6 row 15 | P3–P5 | processed | `APK_REVERSE_ENGINEERING_PROCEDURE.md` intro and §7 step 1 | | +| DOC-16 | 0093 §6 row 16 | P3–P5 | processed | = A93-GOV-09 | | +| DOC-17 | 0093 §6 row 17 | P3–P5 | processed | `RELEASING.md` E1 list | | +| DOC-18 | 0093 §6 row 18 | P3–P5 | processed | `SECURITY.md` settings-stream paragraph (the fuzz-assert sentence follows APP-06 in Phase 8) | | +| DOC-19 | 0093 §6 row 19 | P3–P5 | processed | = A93-CAP-01/04 | | +| D1 | 0093 §7 decision | CP | processed | approved ("FACT-teksten"); applied in `PROTOCOL.md`, ADR-064, `CHANGELOG.md` | | +| D2 | 0093 §7 decision | CP | processed | approved; `PROTOCOL.md` (CAP-058 files in Phase 4) | | +| D3 | 0093 §7 decision | CP | processed | approved ("§6 en lead") | | +| D4 | 0093 §7 decision | CP | processed | maintainer: "1.3.1 met APP-01…06 (Recommended)" — version 1.3.1 / 10301, CHANGELOG [1.3.1] without date, CAP-077 (Group BM) planned with skeleton, APP_TESTPLAN §Z | | +| D5 | 0093 §7 decision | CP | processed | approved; `AGENTS.md` §6 note | | +| D6 | 0093 §7 decision | CP | processed | approved ("Ik, bij de projectstart"); four provenance lines | | +| D7 | 0093 §7 decision | CP | TODO.md | approved "34 houden" — a planned step in `TODO.md` §5 (Phase 5) | | +| D8 | 0093 §7 decision | CP | processed | APK registered (HK-03); decompile stays a CROSSCHECK session in `TODO.md` §4 | | +| D9 | 0093 §7 decision | CP | TODO.md | approved "Later" — three named MAINTENANCE sessions in `TODO.md` §6 (Phase 5) | | +| D10 | 0093 §7 decision | CP | processed | `RELEASING.md` exception for 1.2.2 | | +| ACT-01 | 0093 §8 action row | P1 | processed | A93-APP-01 … 04 fixed with tests and real bytes; the short capture is CAP-077 (planned skeleton) | | +| ACT-02 | 0093 §8 action row | P1 | processed | Phase 3 | | +| ACT-03 | 0093 §8 action row | P1 | processed | Phase 3 | | +| ACT-04 | 0093 §8 action row | P1 | processed | A93-CAP-06/08/09 — the sub-agents' verified edits applied (Appendix A.4) | | +| ACT-05 | 0093 §8 action row | P1 | processed | Phase 4 | | +| ACT-06 | 0093 §8 action row | P1 | processed | Phases 3–5 + lint | | +| ACT-07 | 0093 §8 action row | P1 | processed | A93-APP-06 — fuzz fault assertions and the real 1527 bytes | | +| ACT-08 | 0093 §8 action row | P1 | processed | README, PROJECT, TODO, RELEASING E1 | | +| ACT-09 | 0093 §8 action row | P1 | processed (narrowed) | registered; decompile in `TODO.md` | | +| ACT-10 | 0093 §8 action row | P1 | processed (narrowed) | corrections done; split in `TODO.md` | | +| ACT-11 | 0093 §8 action row | P1 | processed | A93-APP-07, A93-APP-08 | | +| ACT-12 | 0093 §8 action row | P1 | processed | HK-04/07/08, GOV-10/11, APP-09 | | + +## Phase 1 — triage notes + +- The 0093 items marked M were verified by the 0093 main session in this same chat at `b9828ba`; no capture, document or source file changed since + (`git diff --name-only b9828ba..HEAD` → only the 0093 files), so their evidence stands and is re-read at the edit. +- S items that feed a `PROTOCOL.md` change, re-checked now (M): `CAP-062` NAKs — `tshark -r CAP-062-btsnoop_hci.log -Y 'btrfcomm.len>0 && (btrfcomm.dlci==4||btrfcomm.dlci==5) + && frame.p2p_dir==1 && frame contains ff:02' -T fields -e data.data | grep -o 'ff02[0-9a-f]\{10\}' | sort | uniq -c` → `10 ff020003020812` (positive control: the same + filter finds 10 — `PROTOCOL.md:810` already says ×10; `CAP-062-FINDINGS.md` says 11); `CAP-065` "7 of 7" — its own table (§4 rows 1–4, 6–9) has **8** rows with one bud + (or one bud first) out, each fitting, so "8 of 8" (repeated at `PROTOCOL.md:411/3754`). +- External (A93-CAP-05): the Linux kernel commit 11015c79 (Marcel Holtmann, "Bluetooth: Add support for P-256 authenticated/unauthenticated link keys" — renames the + older types with a P-192 suffix) and the Zephyr/Pigweed link-key definitions: 0x04 unauthenticated P-192, 0x05 authenticated P-192, 0x07 unauthenticated P-256, + 0x08 authenticated P-256 (the Core Specification text itself was not retrieved; Wireshark prints the same names for `CAP-058` 2235). +- A93-SES-01 (the 0093 prompt's release list) and A93-SES-04 (a pointer to A93-PROT-01): no change needed — verdict "rejected (no action)" / "processed with PROT-01". + +## Checkpoint answers (chat 2026-10-15, `AskUserQuestion`, three rounds — the maintainer's choices verbatim) + +| Question (short) | Answer | +|---|---| +| FACT texts in `PROTOCOL.md` (A93-PROT-01/02/05/06/08, A93-CAP-05, the `CAP-062`/`CAP-065` counts) | "Alles zoals voorgesteld (Recommended)" — the preview's texts | +| §6 closures and the lead (A93-PROT-03/04/07/09, L93-1) | "Alles zoals voorgesteld (Recommended)" | +| Capture notes (A93-CAP-06 … 09, Appendix B) | "Alles, na hercontrole (Recommended)" | +| ADR texts (A93-DEC-01/02/03) | "Alle drie (Recommended)" | +| Fix release (D4) | "1.3.1 met APP-01…06 (Recommended)" | +| UX (A93-APP-07/08) | "Toegankelijkheid (Recommended), Eén label voor de modus, Bevestiging bij 'Use different Buds', Opruimen kleine code-punten" | +| Restructures (D9) | "Later, als eigen sessies in TODO.md (Recommended)" | +| targetSdk (D7) | "34 houden, stap plannen in TODO.md (Recommended)" | +| Project law | "AGENTS.md §6: concept-ADR toegestaan (D5) (Recommended), PROJECT_RULES.md regel 13 (GOV-04)" — GOV-05 (§6 framing paragraph) and GOV-06 (§10 ViewModel) **not chosen** → declined | +| Session records (A93-SES-03/05/06/07, A93-HK-05) | "Ja, gedateerde regels + INDEX (Recommended)" | +| Provenance of ADR-002/004/005/007 (D6) | "Ik, bij de projectstart" | +| Smaller decisions | "RELEASING.md: uitzondering 1.2.2 (D10) (Recommended), APK 1.0.990706425 registreren (D8) (Recommended), scratch_0024/ en android/domain/bin/ opruimen (Recommended), Lint-controles uitbreiden (Recommended)" | + +## Gate and lint + +| When | Command | Result | +|---|---|---| +| Phase 0 baseline | `cd android && ./gradlew --offline --max-workers=2 assembleDebug testDebugUnitTest test lint` | BUILD SUCCESSFUL in 48 s, exit 0; JUnit `:data` 1672 per variant, `:hardware` 64 per variant, `:domain` 42, `:ui` 92 per variant, `:app` none, 0 failures (= 0093's baseline) | +| Phase 0 | `python3 scripts/lint_docs.py` | exit 0 | +| Phase 9 final (after an OOM kill of the first attempt; resumed in a new chat, `--max-workers=1`) | `cd android && ./gradlew --max-workers=1 clean build` | BUILD SUCCESSFUL in 5 m 42 s, exit 0; JUnit `:data` 1679 per variant, `:hardware` 66 per variant, `:domain` 42, `:ui` 95 per variant, 0 failures; Android lint 0 errors (`:ui` 0 warnings — the `ModifierParameter` warning is gone; `:app` 6 warnings) | +| Phase 9 final | `PYTHONDONTWRITEBYTECODE=1 python3 scripts/lint_docs.py` | exit 0 | +| Phase 9 privacy | the MAC-like strings on the diff's added lines: 4 addresses, each with the same count in `HEAD` and the working tree (`git grep -o`) | rewritten lines only — nothing new | + +## Mutation checks (one at a time, `--max-workers=2`; every file restored and compared with `diff -q`) + +| Fix | Mutation | Result | +|---|---|---| +| A93-APP-01 | each `BudsCompanionPairing` gets its own `PairingChoiceMemory` (the 1.3.0 behaviour) | `CompanionPairingChoiceTest` 2 of 2 fail | +| A93-APP-02 | the other source's equal read in the same round returns `READ` (the 1.3.0 rule) | 1 of 172 fail ("the equal read after a differing Get keeps changed since") | +| A93-APP-02 | no `readRound++` in `launchInitialEqRead` | the same test fails at "another session's read" | +| A93-APP-03 | the 1.3.0 line (`Notified` emitted for the `Get`'s answer) | the race test fails: `Success` with no answer instead of `Timeout` | +| A93-APP-04 | the 1.3.0 code, per read | the 4:13 read (1 hardware request instead of 0), the EQ read (16 Maestro frames instead of 1), the Refresh (TRANSPARENT instead of ACTIVE) fail | +| A93-APP-04 | the settings read accepts a stream packet | "a stream push does not end a settings read" fails | +| A93-APP-05 | `readableData()` without its `catch` | "a corrupt settings file reads as the defaults" fails | +| A93-APP-06 | `routeMaestro` throws for a 5-byte payload | the settings fuzz test fails on "no decoder threw" (+3 other tests) | +| A93-APP-07 | the 1.3.0 layout (label beside the switch) | the UI tests' merged-row finders: 5 tests fail | + +Not testable on the JVM: the transport's attach window (A93-APP-05 — closed by construction, under the connection's lock with `closeAll`) and the application +scope's exception handler (`:app` has no unit tests). + +## Compliance (`AGENTS.md`) + +- No `INTERNET` permission, no new permission, no new dependency (`git diff` of the manifests and `libs.versions.toml`: none). No GMS reference added. +- No new wire request: the `transport.send` call sites are the same before and after (8 MAESTRO, 3 Message Stream); no `WriteSetting 4:{13:n}`. +- No background or periodic connect; DLCI 0x04 is still claimed only for a tap, the tile, Refresh, a pull and the Connect's snapshot. +- No MAC address or serial in any new text (scan of the 654 drafted texts: 0 matches); every new Kotlin file carries the AGPL header. +- Fixtures are real bytes (`CAP-076` H1 4094/4135, `CAP-051` 1527), each with its command; negatives carry their command and a positive control. +- No 🟢 promotion and no ADR without the maintainer's answer in chat (the checkpoint above); the CONV-002 note is 🔴 open. + +## External sources + +- Fast Pair, "Smart Audio Source Switching" (developers.google.com/nearby/fast-pair/specifications/extensions/sass) — the SASS codes (A93-PROT-04). +- Linux kernel commit 11015c79 ("Bluetooth: Add support for P-256 authenticated/unauthenticated link keys") — the link-key type names (A93-CAP-05). +- Claude Code setup (code.claude.com/docs) — the native installer (WORKSTATION). +- npm Docs, "Resolving EACCES permissions errors when installing packages globally" (docs.npmjs.com) — WORKSTATION. +- antigravity.google/releases — the RPM repository note (WORKSTATION). +- GitHub Docs, "Automatic token authentication — Modifying the permissions for the GITHUB_TOKEN" — `.github/workflows/lint-docs.yml` (A93-APP-09). + +## Deferred documentation + +- The restructures of `TODO.md` §6 (the maintainer's choice D9: later, each its own session). +- The slips the sub-agents found outside the 0093 list (`TODO.md` §6, item "Found while `ai-sessions/0094` re-checked …"). +- `CHANGELOG.md` [1.3.1] gets its date at the release (`RELEASING.md`). + +## Commits + +The maintainer approved in chat 2026-10-15 ("Ja, commit en push het"), on branch `maintenance/0094-process-0093`, pushed; no PR, no merge. + +| Commit | Content | +|---|---| +| `f483b98` | documents, scripts and CI of Phases 3–5 | +| `bbc45fe` | the capture corrections, the test plans and the `CAP-077` skeleton | +| `9999d28` | the app 1.3.1 with its tests | +| (this commit) | `ARCHITECTURE.md`, `CHANGELOG.md`, `README.md`, `PROJECT.md`, `SECURITY.md`, `TODO.md`, this prompt and RESULT, the INDEX row | + +## Appendix A.4 — the capture sub-agents + +Three read-only sub-agents re-checked the 0093 Appendix B items per capture range, one `tshark` process at a time, and wrote verified OLD/NEW drafts; the main +session applied only CONFIRMED items, each OLD occurring exactly once at its turn (a dry run first: 0 failures), then ran `lint_docs.py` (exit 0). + +| Range | Confirmed | Not reproduced / withdrawn | Edits | +|---|---|---|---| +| `CAP-001` … `CAP-025` | 142 | SC1-133 (the `0x20NN` count cannot be re-derived) | 270 | +| `CAP-026` … `CAP-050` | 118 | SC2-05 (the procedure's own numbering), SC2-50 (no film frame shows the version), SC2-97 (the film fits `CAP-044`); SC2-116 withdrawn | 226 | +| `CAP-051` … `CAP-075` | 70 + 3 extras | SC3-07 (all three numbers match the decode), SC3-38 (each line cites what it names) | 158 | + +Where the right fix differed from the sub-review (re-derived numbers, recorded in the edits): SC1-20, SC1-35, SC1-38, SC1-106, SC1-107; SC2-18 (DLCI 0x08 frames +1592/1711 — also `PROTOCOL.md` §6 and the TESTPLAN `CONV-002` row, re-checked by the main session with `tshark`, exit 0), SC2-31, SC2-75, SC2-104/105/108, +SC2-117, SC2-119; SC3-30, SC3-31, SC3-54, SC3-67, SC3-68. The drafts are scratch files of this session (not kept in the repository). + +--- +https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/0094_MAINTENANCE_RESULT_2026_10_15.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/0094_MAINTENANCE_RESULT_2026_10_15 diff --git a/ai-sessions/INDEX.md b/ai-sessions/INDEX.md index d5f57939..b41d1926 100644 --- a/ai-sessions/INDEX.md +++ b/ai-sessions/INDEX.md @@ -75,7 +75,7 @@ a new session** — the same "check before assigning" discipline `id_registry.cs | 0066 | FEATURE | 2026-10-02 | Two app changes before `CAP-067` (balance slider without the `‹`/`›` steps; no `LICENSE` link on Info, ADR-050 Update) and answers on testing without Play services, clean release builds, README video, the app name and R8 | complete — ADR-050 Update approved in chat; gate green; not hardware-verified | | 0067 | CAPTURE | 2026-10-03 | Fully analyse `CAP-067` (Group BC: the release-signed 1.0.0 APK in a GrapheneOS profile without Google Play — two HCI logs, two debug exports, one app logcat, no system log): the real events, the findings, the Definition-of-done evidence and the release verdict for 1.0.0; no app change, no publishing | complete — Definition of done 1–3 ticked and release of `8d8af4b` as 1.0.0 approved in chat; publishing is the maintainer's step | | 0068 | AUDIT | 2026-10-03 | Exhaustive end-to-end audit after the first release (1.0.0): documentation, protocol, captures, architecture, decisions, backlog, release process and app code; deep re-review of `0059` … `0067`; improvement proposals for 18 project documents now that 1.0.0 is published; review only — writes only its RESULT | complete — 85 findings (1 critical, 21 major, 57 minor, 3 nit, 3 without a severity), 10 protocol leads, proposals for 18 documents; review only: no fix applied, no FACT or ADR written; nothing tested on a phone | -| 0069 | MAINTENANCE | 2026-10-03 | Process every finding (85), lead (10) and document proposal (18) of the 0068 audit into the project files, scripts and the app — documentation and scripts first, the app last — within the maintainer's decisions asked in that session, and write the `CAP-068` skeleton for what must be tested on hardware first | complete (closed 2026-10-04 by `ai-sessions/0070`: committed, tested in `CAP-068`, released as 1.0.1) — awaiting maintainer sign-off — all 173 ledger rows closed (134 processed, 26 narrowed, 7 in `TODO.md`, 5 moved to a capture, 1 rejected); the critical `A68-APP-01` fixed with failing-first tests and a router guard; app 1.0.1 (10001): one "current value" rule, no silent device pick + "Use different Buds", reworded messages, "—", preset "Flat", socket exceptions converted — no new send, permission or dependency; documents corrected in place / by dated Updates within the checkpoint answers; `TODO.md` open items only; three new lint checks, the decoder for DLCI 2 and 3, `release.sh` hardened, CI builds the release variant; `CAP-068` (BD) and `CAP-069` (BE) skeletons, `CAP-054` redesigned; gate green after `clean` (`:data` 1601, `:hardware` 64, `:ui` 58, `:domain` 37), 16 mutations caught; **nothing committed, tagged or published** | +| 0069 | MAINTENANCE | 2026-10-03 | Process every finding (85), lead (10) and document proposal (18) of the 0068 audit into the project files, scripts and the app — documentation and scripts first, the app last — within the maintainer's decisions asked in that session, and write the `CAP-068` skeleton for what must be tested on hardware first | complete (closed 2026-10-04 by `ai-sessions/0070`: committed, tested in `CAP-068`, released as 1.0.1) — originally awaiting sign-off — all 173 ledger rows closed (134 processed, 26 narrowed, 7 in `TODO.md`, 5 moved to a capture, 1 rejected); the critical `A68-APP-01` fixed with failing-first tests and a router guard; app 1.0.1 (10001): one "current value" rule, no silent device pick + "Use different Buds", reworded messages, "—", preset "Flat", socket exceptions converted — no new send, permission or dependency; documents corrected in place / by dated Updates within the checkpoint answers; `TODO.md` open items only; three new lint checks, the decoder for DLCI 2 and 3, `release.sh` hardened, CI builds the release variant; `CAP-068` (BD) and `CAP-069` (BE) skeletons, `CAP-054` redesigned; gate green after `clean` (`:data` 1601, `:hardware` 64, `:ui` 58, `:domain` 37), 16 mutations caught; (committed and released as 1.0.1 by `ai-sessions/0070`) *(cell aligned with the RESULT 2026-10-15, `ai-sessions/0094`)* | | 0070 | CAPTURE | 2026-10-04 | Full analysis of CAP-068 (Group BD: the 1.0.1 release APK in a GrapheneOS profile without Google Play — two films, four HCI logs, five debug exports, one logcat); the release verdict for 1.0.1 and the capture evidence for Definition-of-done criterion 2 | complete — 1.0.1 (`e1fc886`) released 2026-10-04 and installed from the release page; 12 ANC `Set` → 12 ACK by the release build without Play (Definition of done 2 with frames); a `Set` for the current mode is ACKed; `FIND-005` not observable; README media renewed; no app change | | 0071 | CAPTURE | 2026-10-04 | Full analysis of CAP-069 (Group BE: the official Pixel Buds app 1.0.990706425 on the Pixel 7a — head gestures, Multipoint, assistant hold, a tap on the current ANC mode, EQ Default, wear states, Find now in Find Hub): the real events, the findings per protocol and the improvements for OpenControl; no app change | complete (signed off in chat 2026-10-04, all `CAP-069-FINDINGS.md` §13 proposals) — film + log fully analysed; head gestures (field 29) and Multipoint (SASS bit 2) promoted, ADR-052/053 written (maintainer-approved in chat); official app sends no `Set` for the selected mode (OpenControl keeps its `Set`); Default = 0.0 × 5 (keep "Flat"); the app has no Find device; new MAESTRO service `0xbf6c9399`; no app change | | 0072 | CAPTURE | 2026-10-05 | Full analysis of CAP-053 (Group AO: EQ field 16 vs 18), CAP-054 (Group AP: the `0xFE2C` advertisement on case-open) and CAP-058 (Group AT: `SDP-001` 3rd attempt, plus 14 extra tests) — films, five HCI logs, a `dumpsys` file; findings and the approved documentation edits; no app change | complete (all four checkpoint questions approved in chat 2026-10-06; committed on `capture/0072-cap-053-054-058`) | @@ -85,7 +85,7 @@ a new session** — the same "check before assigning" discipline `id_registry.cs | 0076 | FEATURE | 2026-10-07 | Replace the labelled, derived test fixtures of 1.1.0 with the real `CAP-070` frames (Multipoint and head gestures on channel 19, balance Right 4 on channel 19, Volume EQ on on channel 21) and remove ADR-055's `// TODO(verify)`; tests and comments only | complete — gate green, M1–M5 killed, ADR-055 Update approved in chat 2026-10-07 | | 0077 | MAINTENANCE | 2026-10-07 | The maintainer's pending decisions: the three desk-research proposals of 2026-10-03, seven older capture-FINDINGS proposals, ADRs for two 1.0.1 behaviours (ADR-056 device choice, ADR-057 current value), untracking the `.pyc`, restoring the Buds' state | complete — 13 decisions approved in chat 2026-10-07 and applied; committed on `maintenance/0077-decisions` (`a09a18e`…) | | 0078 | MAINTENANCE | 2026-10-07 | Upgrade the build toolchain and every catalog dependency to current stable releases (Gradle, AGP, Kotlin, KSP, Compose BOM, Hilt, AndroidX, tests), each pinned and justified; re-check the Material 3 opt-ins and `TabRow`; add `distributionSha256Sum`; align the CI actions; prove with the gate, test counts, mutations and an APK comparison that the app's behaviour does not change | complete — Gradle 9.7.1, AGP 9.3.3 (built-in Kotlin), Kotlin 2.4.20, KSP 2.3.12, Hilt 2.60.1, BOM 2026.09.00, compileSdk 37 (ADR-029 Update), targetSdk 34 kept; gate green, counts equal (+1 tab-order test), M1–M7 killed; permissions and components unchanged; not hardware-tested | -| 0079 | MAINTENANCE | 2026-10-08 | Prepare release 1.1.1 (the toolchain upgrade of 0078) up to the maintainer's `scripts/release.sh`: version, CHANGELOG, README, release notes, the release checklist on a pull request; bring `RELEASING.md` in line with the repository rules; write the skeleton of its hardware run `CAP-071` (Group BG) — a regression run of the release APK over 1.1.0 plus the open film items of `TODO.md` §2 | complete — 1.1.1 / 10101 on `release/1.1.1`; `RELEASING.md` "Repository rules"; `CAP-071` skeleton registered planned, `APP_TESTPLAN.md` U; gate equal to 0078; no build, tag or publication by the agent | +| 0079 | MAINTENANCE | 2026-10-08 | Prepare release 1.1.1 (the toolchain upgrade of 0078) up to the maintainer's `scripts/release.sh`: version, CHANGELOG, README, release notes, the release checklist on a pull request; bring `RELEASING.md` in line with the repository rules; write the skeleton of its hardware run `CAP-071` (Group BG) — a regression run of the release APK over 1.1.0 plus the open film items of `TODO.md` §2 | complete — 1.1.1 / 10101 on `release/1.1.1`; `RELEASING.md` "Repository rules"; `CAP-071` skeleton registered planned, `APP_TESTPLAN.md` U; gate equal to 0078; no build, tag or publication by the agent; PR #15 merged 2026-10-08 (`f359aa2`), 1.1.1 released (tag `v1.1.1` on `86a6fb3`) *(cell aligned with the RESULT 2026-10-15, `ai-sessions/0094`)* | | 0080 | CAPTURE | 2026-10-08 | Full analysis of `CAP-071` (Group BG: the 1.1.1 release APK installed over 1.1.0 in the GrapheneOS user without Google Play — one film with sound, four HCI logs, two exports, the app logcat, the system log): the real events, the findings, the 1.1.0 → 1.1.1 comparison and the release verdict | complete — 1.1.1 released 2026-10-08 (tag `v1.1.1` on `86a6fb3`); verdict approved in chat 2026-10-08: release `86a6fb3` as 1.1.1, known issues unchanged; no behaviour change from the toolchain (11 sessions, 24 writes + 9 ANC `Set`s + 4 Ring/Stop byte-identical and answered; update kept data and settings; installed APK = `dist/1.1.1`); film 2 (L-1, C12, S12, T11) not made; two `PROTOCOL.md` notes approved; system log committed as an extract; no app change | | 0081 | REVIEW | 2026-10-08 | Targeted cross- and consistency check before 1.2.0 (not a full audit): registry ↔ Capture Index ↔ folders ↔ notes/findings statuses; `ARCHITECTURE.md` §5a against ADR-001…057 and the FINDINGS; the `ai-sessions/0068` corrections still listed in `TODO.md` §6, item by item; the open items of `CAP-053`, `054`, `058`, `064`…`071` and `TODO.md` as a whole | complete — 36 mechanical fixes applied (README 57 ADRs, 7 registry dates, 26 TESTPLAN back-pointers, `ARCHITECTURE.md` §3.1/§5a cells, 27 capture slips re-derived with `tshark`), 5 FACT-text corrections and ADR-040's Update approved in chat (`CAP-060`/`PROTOCOL.md` 0.38 → 0.56 s, `CAP-062` 22 → 43 of 43 and its Set list, `CAP-004` group 0x04 🟢 → 🟡), status banners on `CAP-064`…`070`, seven lost items into `TODO.md`, own-session order decided (all after 1.2.0, the 0073 debt first); documentation only, no app change | | 0082 | FEATURE | 2026-10-09 | Build 1.2.0: "Changed by the Buds" in the ANC (i), the component serial numbers (ADR-058, one `GetHardwareInfo` per Connect), the "probably worn" indicator (ADR-059), Case sounds → gear → Settings, Conversation detection → Controls; a design study + RE of volume-level notifications (draft ADR-060, not accepted); fixtures from `CAP-045`/`CAP-036`/`CAP-024`/`CAP-065`/`CAP-064`/`CAP-062`; the `CAP-072` (Group BH) run skeleton; release preparation 1.2.0 (10200) | complete | @@ -95,11 +95,12 @@ a new session** — the same "check before assigning" discipline `id_registry.cs | 0086 | CAPTURE | 2026-10-12 | Full analysis of `CAP-074` (Group BJ: the released 1.2.0 `5b4d5db` in the GrapheneOS user **with** Google Play services — one screen recording with audio, two HCI logs, one debug export, the app logcat, the system log; a Disconnect/Connect series with several press-and-holds, of which only the first changes the app's ANC mode): the real events, the DLCI 0x04 claims of both clients, the holds and their closes, every `Notify` and its recipient, the comparison with `CAP-073`, the root cause and the fix options for the maintainer | complete — every checkpoint answered in chat 2026-10-12: root cause 🟢 contention (Play services' Fast Pair re-claims DLCI 0x04 5.2–5.7 s after losing it and the stack cuts the app's hold) / 🟡 the stack path in this run; ADR-061 and ADR-032 Updates; the PROTOCOL §4.1 note, registers, test plan, TODO, ARCHITECTURE updated; fix = a Settings switch between hint-only and re-claim-with-back-off → `0087`; commits `31ee819`, `1343e97`, `5deb859`, PR #37 | | 0087 | FEATURE | 2026-10-12 | The Play-user hold fix (`CAP-074`): a Settings switch between "hint only, no re-claim" and "re-claim after a foreign close while the noise-control tab is shown, with a back-off" — the switch, the hint, the corrected ANC (i), a draft ADR-062 for mode 2, tests with `FakeBudsTransport`, mutation checks, the `CAP-075` (Group BK) skeleton for the hardware run in both users, the documents and the release preparation (no tag, no publication) | complete — built on `feature/hold-reclaim-switch` (gate green: `:data` 1664, `:domain` 42, `:hardware` 64, `:ui` 95; M1–M7 killed): `HoldReclaimMode` HINT_ONLY (default) / RECLAIM (3, 6, 12 s then stop), the Settings card, the hint line, the corrected ANC (i); ADR-062 and the ADR-061 pointer Update approved in chat 2026-10-12; 1.2.1 (10201) prepared; `CAP-075` (Group BK) skeleton for both users, `APP_TESTPLAN.md` §X; commits `64bb0ef` … `4778d32`, pull request #39 | | 0088 | CAPTURE | 2026-10-12 | Full analysis of `CAP-075` (Group BK: 1.2.1 build `31ee183`, the "Noise-control channel" switch — **two sets**: the secondary user without Google Play (2026-10-12: one screen recording with audio, two HCI logs, one export, the app logcat; 1.2.0 → 1.2.1 updated on film) and the Owner with Google Play (2026-10-13: one screen recording with audio, two HCI logs, one export, the app logcat, the system log, a full `logcat -b all`): the real events per set, the holds per switch position, the Play user's cut mechanism frame by frame, the four-way comparison `CAP-073`/`CAP-074`/`CAP-075`, the answer per user to "which works best", improvement proposals for the Play user, the release verdict proposal; no app change | complete (2026-10-13) — `CAP-075-EVENT-NOTES.md` (the two timelines, the steps as run, Test-IDs) and `CAP-075-FINDINGS.md` §0–§12: without Play both positions identical (41 of 41 holds shown, no foreign close); with Play the hint 2 of 2 and 0 of 9 holds shown, "Take it back" three chains × three re-claims at 3/6/12 s (4.0–4.8 s on the wire, every one colliding with Play's retry 2.1–2.8 s after the cut), holds cut 5.3–5.8 s after Play's loss, 43 % coverage then nothing, 8 of 19 shown; the cut's stack path 15 of 15 in the system log; Play's timers = `CAP-074`'s. Checkpoint 0 + Phase D approved in chat: the folder/recording renames, modes, the logcat extract, the films as is, the ADR-062 Update, a 🟢 `PROTOCOL.md` §4.1 note, the CHANGELOG Known-issues text, the verdict "no heavy issue — 1.2.1 released as built", P1 + P2 + P4 → the next FEATURE prompt; the leftover worktree of 0087's prompt removed; no app change. **Continuation in chat 2026-10-13:** PR #44 merged, 1.2.1 published (tag `v1.2.1` on `31ee183`, the notes' Known-issues text measured), E1–E4 by the pull request of `release/1.2.1-after` (#45); D4 done — installed with Obtainium | -| 0089 | FEATURE | 2026-10-13 | The noise-control channel in the Play user after `CAP-075` (`ai-sessions/0088`, the maintainer's choice "P1 + P2 + P4"): a constant 3-s re-claim step with a longer chain (capped), a "Take it back again" action on the hint card, a cause label for a mode learned by a re-claim's own read; the switch renamed (the maintainer's example "Channel control") with options that say whether the phone has Google Play services and plainer texts; real `CAP-075` bytes as fixtures, mutation checks, the `CAP-076` (Group BL) skeleton, the documents, the release preparation (no tag, no publication) | awaiting maintainer sign-off — built on `feature/channel-control` (gate green: `:data` 1672, `:domain` 42, `:hardware` 64, `:ui` 102; M1–M6 killed): the re-claim 3 s after every cut up to 20 (`HOLD_RECLAIM_STEP_MS`, `HOLD_RECLAIM_CAP`), "Take it back again" (`reclaimAncChannel`, no `Set`), `AncModeCause.CHANGED_SINCE_LAST_READ` and its (i) line, the card renamed "Noise control with Google Play services" with options that say whether the phone has Play services and ADR-063's "found / not found" line (a `` entry); ADR-062 Update 2, ADR-063 and the `AGENTS.md` §1 note approved in chat 2026-10-13; 1.2.2 (10202) prepared; `CAP-076` (Group BL) skeleton for both users, `APP_TESTPLAN.md` §Y; commits `e60fb7c` … `bfd7891`, PR #49 merged (`5b204ea`); **1.2.2 published 2026-10-14** (tag `v1.2.2` on `bfd7891`) after the maintainer's own test, without `CAP-076`; installed with Obtainium (D4, 2026-10-14) | +| 0089 | FEATURE | 2026-10-13 | The noise-control channel in the Play user after `CAP-075` (`ai-sessions/0088`, the maintainer's choice "P1 + P2 + P4"): a constant 3-s re-claim step with a longer chain (capped), a "Take it back again" action on the hint card, a cause label for a mode learned by a re-claim's own read; the switch renamed (the maintainer's example "Channel control") with options that say whether the phone has Google Play services and plainer texts; real `CAP-075` bytes as fixtures, mutation checks, the `CAP-076` (Group BL) skeleton, the documents, the release preparation (no tag, no publication) | complete (closed 2026-10-14: PR #49 merged, 1.2.2 published) — built on `feature/channel-control` (gate green: `:data` 1672, `:domain` 42, `:hardware` 64, `:ui` 102; M1–M6 killed): the re-claim 3 s after every cut up to 20 (`HOLD_RECLAIM_STEP_MS`, `HOLD_RECLAIM_CAP`), "Take it back again" (`reclaimAncChannel`, no `Set`), `AncModeCause.CHANGED_SINCE_LAST_READ` and its (i) line, the card renamed "Noise control with Google Play services" with options that say whether the phone has Play services and ADR-063's "found / not found" line (a `` entry); ADR-062 Update 2, ADR-063 and the `AGENTS.md` §1 note approved in chat 2026-10-13; 1.2.2 (10202) prepared; `CAP-076` (Group BL) skeleton for both users, `APP_TESTPLAN.md` §Y; commits `e60fb7c` … `bfd7891`, PR #49 merged (`5b204ea`); **1.2.2 published 2026-10-14** (tag `v1.2.2` on `bfd7891`) after the maintainer's own test, without `CAP-076`; installed with Obtainium (D4, 2026-10-14) *(cell aligned with the RESULT 2026-10-15, `ai-sessions/0094`)* | | 0090 | CROSSCHECK | 2026-10-14 | Point-by-point check of the pbpctrl-derived findings list (E-01 … E-23: `qzed/pbpctrl`, MagicPodsCore's Pixel Buds branch, omarchy-pixelbuds at pinned commits) against this project's captures, APK notes and documents — led by the question whether the app can learn a press-and-hold's noise-control mode on DLCI 0x02 (Maestro setting 13, `SubscribeToSettingsChanges`) instead of the contested Message Stream; proposals only where the rules require the maintainer; no app code, no wire bytes | complete — the leading question answered yes: setting 13 = the mode on DLCI 0x02, pushed on `SubscribeToSettingsChanges` for 73/73 Buds-side and 39/39 app-driven changes (also with no Message Stream open), `ReadSetting 4:13` 359/382; OpenControl never subscribed (0/33 logs); the APK feeds its ANC state only from 13 (`fxb.smali:1237`). Approved in chat 2026-10-14 and applied: `PROTOCOL.md` §4.1 🟢 Update (+ §6, §2.2, §4.5.7, §0.1 notes), **ADR-064** (subscribe + read 13, read only; build next) with pointers on ADR-025/034/036, ADR-060 item (vii), the `qhr` register Update, a desk entry, `CAP-051-FINDINGS.md` §3, `AGENTS.md` §5 note, `ARCHITECTURE.md` §5a rule, `TODO.md`, `scripts/anc_setting13_crosscheck.py`; no app code; commits `7bfb078` … `0b1681a` on `research/pbpctrl-crosscheck`, pushed, PR #55. **Continuation in chat 2026-10-14:** the existing captures checked for the new findings — one-bud/two-bud stored mode 28 of 28 in the official app's captures (🟡 strong, `PROTOCOL.md` §6, `scripts/wear_mode_crosscheck.py`); settings 8/9/10 and `SubscribeToOobeActions` never on any wire; runtime-info 7.x = the charging flags in 96.9 % | -| 0091 | FEATURE | 2026-10-14 | Build ADR-064: one `SubscribeToSettingsChanges` and one `ReadSetting 4:13` per MAESTRO session (Connect and every ADR-044 re-open), the stream feeding every setting the app reads; ADR-061/062/063 (the hold, the re-claim, the "Noise control with Google Play services" card, the Play-services line) replaced by one new ADR and removed from the app; the cause line rewritten (wear-driven changes stay 🟡); version 1.3.0; real fixtures (`CAP-051`, `CAP-056`, `CAP-038`, `CAP-045`, `CAP-021`, `CAP-027`), mutation checks, `CAP-076` rewritten as the 1.3.0 run in both users; `WriteSetting 4:{13:n}` out of scope | done 2026-10-14 — built on branch `feature/settings-stream` (ADR-065 accepted in chat; the gate green, M1–M5 caught; `CAP-076` skeleton rewritten; pushed 2026-10-14, commits `bb7a843` … `391b0c5`, PR #56 open); RESULT `0091_FEATURE_RESULT_2026_10_14.md`; hardware run analysed by 0092 (`CAP-076`, 2026-10-15: verdict release as built + Known issues) | +| 0091 | FEATURE | 2026-10-14 | Build ADR-064: one `SubscribeToSettingsChanges` and one `ReadSetting 4:13` per MAESTRO session (Connect and every ADR-044 re-open), the stream feeding every setting the app reads; ADR-061/062/063 (the hold, the re-claim, the "Noise control with Google Play services" card, the Play-services line) replaced by one new ADR and removed from the app; the cause line rewritten (wear-driven changes stay 🟡); version 1.3.0; real fixtures (`CAP-051`, `CAP-056`, `CAP-038`, `CAP-045`, `CAP-021`, `CAP-027`), mutation checks, `CAP-076` rewritten as the 1.3.0 run in both users; `WriteSetting 4:{13:n}` out of scope | complete — built on branch `feature/settings-stream` (ADR-065 accepted in chat; the gate green, M1–M5 caught; `CAP-076` skeleton rewritten; pushed 2026-10-14, commits `bb7a843` … `391b0c5`, PR #56 merged 2026-10-15 (`aec17a5`), released as 1.3.0); RESULT `0091_FEATURE_RESULT_2026_10_14.md`; hardware run analysed by 0092 (`CAP-076`, 2026-10-15: verdict release as built + Known issues) *(cell aligned with the RESULT 2026-10-15, `ai-sessions/0094`)* | | 0092 | CAPTURE | 2026-10-15 | Full analysis of `CAP-076` (Group BL: the 1.3.0 build `ff27ee1`, the settings stream on the app's own channel — ADR-064/065 — **two parts**: the Owner user with Google Play (06:35–06:52, the update 1.2.2 → 1.3.0 on film: one screen recording with audio, two HCI logs, one export, the app logcat, the system log, a full `logcat -b all`, the adb shell log) and the secondary user without Google Play (07:01–07:08: one screen recording with audio, two HCI logs, one export, the app logcat)): the real events per part, the two new requests once per MAESTRO session, every press-and-hold against the screen, the own taps' stream packets, the re-homes and re-opens, the comparison with `CAP-075`, the counts for the verdict, the release verdict proposal (`RELEASING.md` C3/C4); no app change | complete 2026-10-15 — `CAP-076` analysed (every hold on the noise-control tab shown: 29/29 with Play, 12/12 without; three minor defects); verdict release as built + Known issues; commits `c32b297` … `b06181e`; 1.3.0 released 2026-10-15 (tag `v1.3.0` on `ff27ee1`, PR #56) | | 0093 | AUDIT | 2026-10-15 | Exhaustive end-to-end audit of the whole project after 1.3.0 — documentation, protocol, captures, architecture, decisions, `TODO.md`, release process and app code; deep re-review of `0069` … `0092` with `0090` (the pbpctrl cross-check behind ADR-064), `0091` (the 1.3.0 build) and `0092` (the `CAP-076` run) in depth; a structured reverse-engineering pass over the official APK `v1.0.955078536-10253511` (settings 23/24/26/30/31/32/39, the setting-13 stream schema, DLCI 0x08/0x0a, the MAESTRO service descriptors, Message Stream groups, gestures/wear/dosimeter, BLE) to clarify, correct and extend the protocol findings; improvement proposals for 19 documents; decisions for the maintainer with advice; review only — no file other than the RESULT changed | complete — review only (no file changed but its RESULT): 69 findings (0 critical, 7 major, 50 minor, 12 nit), 5 protocol leads, proposals for 19 documents, 10 decisions with advice (§7), a 12-row action list (§8); six read-only sub-reviews (Appendix B); the session moved from Fable 5.1 to Opus 5.5 after a usage limit; committed on `audit/0093-full-audit` | +| 0094 | MAINTENANCE | 2026-10-15 | Process every finding (69), lead (5), document proposal (19), decision (D1–D10) and action row of the 0093 audit into the project files, scripts and the app — documentation and scripts first, the app last — with the maintainer's approval in chat, and a capture skeleton for the fixes | complete — every 0093 item processed or declined (no ledger row open); 330 capture items corrected (654 edits, 139 files); 1.3.1 (versionCode 10301, not released) with A93-APP-01 … 08 and their tests; `CAP-077` (Group BM) planned; committed and pushed on `maintenance/0094-process-0093` (no PR yet) | --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/ai-sessions/INDEX.md - https://tedsluis.github.io/opencontrolpixelbudspro2/ai-sessions/INDEX diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 28e6f9a8..3d62d35b 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -55,8 +55,8 @@ android { // above the previous release. 1.0.1 = the hotfix of `ai-sessions/0069` (the maintainer's choice in chat 2026-10-03); 1.1.1 = the toolchain of `ai-sessions/0078` // (`ai-sessions/0079`, the maintainer's choice in chat 2026-10-08); 1.2.1 = the Noise-control channel switch of `ai-sessions/0087` (chat 2026-10-12). // 1.2.2 = the constant re-claim step, "Take it back again" and the renamed card of `ai-sessions/0089` (chat 2026-10-13, "Version"). - versionCode = 10300 - versionName = "1.3.0" + versionCode = 10301 + versionName = "1.3.1" buildConfigField("String", "GIT_COMMIT", "\"$gitCommit\"") buildConfigField("String", "GIT_COMMIT_DATE", "\"$gitCommitDate\"") } diff --git a/android/app/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/app/MainActivity.kt b/android/app/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/app/MainActivity.kt index 5f2bb500..fda497a6 100644 --- a/android/app/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/app/MainActivity.kt +++ b/android/app/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/app/MainActivity.kt @@ -78,6 +78,7 @@ import io.github.tedsluis.opencontrolpixelbuds.hardware.BluetoothStateObserver import io.github.tedsluis.opencontrolpixelbuds.hardware.BondedLookup import io.github.tedsluis.opencontrolpixelbuds.hardware.BudsCompanionPairing import io.github.tedsluis.opencontrolpixelbuds.hardware.OsConnectionObserver +import io.github.tedsluis.opencontrolpixelbuds.hardware.PairingChoiceMemory import io.github.tedsluis.opencontrolpixelbuds.hardware.PairingFailure import io.github.tedsluis.opencontrolpixelbuds.hardware.PairingState import io.github.tedsluis.opencontrolpixelbuds.hardware.settled @@ -138,9 +139,13 @@ class MainActivity : ComponentActivity() { @Inject lateinit var uiSession: AppUiSession + /** "Use different Buds", the same memory as the repository's pairing singleton (`ai-sessions/0094`, A93-APP-01). */ + @Inject + lateinit var pairingChoiceMemory: PairingChoiceMemory + // The system picker (associate) and the bonded lookup stay on this Activity-context instance; the bonding wait runs on the application-context - // singleton in [AppUiSession] (A58-APP-08), so a rotation no longer cancels it. - private val companionPairing by lazy { BudsCompanionPairing(this) } + // singleton in [AppUiSession] (A58-APP-08), so a rotation no longer cancels it. Both share [pairingChoiceMemory]. + private val companionPairing by lazy { BudsCompanionPairing(this, pairingChoiceMemory) } private val bluetoothStateObserver by lazy { BluetoothStateObserver(this) } private val osConnectionObserver by lazy { OsConnectionObserver(this) { (companionPairing.lookupBonded() as? BondedLookup.Found)?.address } @@ -214,7 +219,8 @@ class MainActivity : ComponentActivity() { onFailure = { "The debug log could not be saved: ${BleLogger.describe(it)}" }, ) BleLogger.logConnectionEvent(if (result.isSuccess) "Debug log exported ($lines lines)" else message) - withContext(Dispatchers.Main) { Toast.makeText(this@MainActivity, message, Toast.LENGTH_LONG).show() } + // A93-APP-08: the application context — this runs on the application scope and may outlive the Activity (a rotation during the save). + withContext(Dispatchers.Main) { Toast.makeText(applicationContext, message, Toast.LENGTH_LONG).show() } } } diff --git a/android/app/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/app/OpenControlApplication.kt b/android/app/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/app/OpenControlApplication.kt index 112ec03e..876648b9 100644 --- a/android/app/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/app/OpenControlApplication.kt +++ b/android/app/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/app/OpenControlApplication.kt @@ -93,6 +93,6 @@ class OpenControlApplication : Application() { ConnectionState.Disconnected, is ConnectionState.Failed -> null ConnectionState.Connecting -> "Connecting…" ConnectionState.Discovering -> "Discovering services…" - ConnectionState.Ready -> mode?.let { "Connected — ANC: ${it.name}" } ?: "Connected" + ConnectionState.Ready -> mode?.let { "Connected — noise control: ${it.label}" } ?: "Connected" // A93-APP-08: the same label as the app } } diff --git a/android/app/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/app/di/RepositoryModule.kt b/android/app/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/app/di/RepositoryModule.kt index c063fa06..cb7d1fea 100644 --- a/android/app/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/app/di/RepositoryModule.kt +++ b/android/app/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/app/di/RepositoryModule.kt @@ -29,9 +29,12 @@ import io.github.tedsluis.opencontrolpixelbuds.data.BudsRepositoryImpl import io.github.tedsluis.opencontrolpixelbuds.data.settings.DarkModeSettingsStore import io.github.tedsluis.opencontrolpixelbuds.data.settings.DebugSettingsStore import io.github.tedsluis.opencontrolpixelbuds.domain.BudsRepository +import io.github.tedsluis.opencontrolpixelbuds.hardware.BleLogger import io.github.tedsluis.opencontrolpixelbuds.hardware.BudsCompanionPairing import io.github.tedsluis.opencontrolpixelbuds.hardware.BudsTransport import io.github.tedsluis.opencontrolpixelbuds.hardware.ConnectionStateMachine +import io.github.tedsluis.opencontrolpixelbuds.hardware.PairingChoiceMemory +import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -54,7 +57,12 @@ object RepositoryModule { * any single screen's lifecycle). */ @Provides @Singleton - fun provideApplicationScope(): CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + fun provideApplicationScope(): CoroutineScope = CoroutineScope( + SupervisorJob() + Dispatchers.Default + + // A93-APP-05 (`ai-sessions/0094`): an exception no action caught is logged (no payload, the address redacted by [BleLogger.describe]) instead of + // ending the process; the SupervisorJob keeps every other coroutine of the scope running. + CoroutineExceptionHandler { _, e -> BleLogger.logConnectionEvent("Uncaught error in a background action: ${BleLogger.describe(e)}") }, + ) @Provides @Singleton @@ -67,10 +75,15 @@ object RepositoryModule { fun provideDarkModeSettingsStore(@ApplicationContext context: Context): DarkModeSettingsStore = DarkModeSettingsStore(context) + /** "Use different Buds", shared by the Activity's pairing instance and this singleton (`ai-sessions/0094`, A93-APP-01). */ + @Provides + @Singleton + fun providePairingChoiceMemory(): PairingChoiceMemory = PairingChoiceMemory() + @Provides @Singleton - fun provideBudsCompanionPairing(@ApplicationContext context: Context): BudsCompanionPairing = - BudsCompanionPairing(context) + fun provideBudsCompanionPairing(@ApplicationContext context: Context, choiceMemory: PairingChoiceMemory): BudsCompanionPairing = + BudsCompanionPairing(context, choiceMemory) @Provides @Singleton diff --git a/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/BudsRepositoryImpl.kt b/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/BudsRepositoryImpl.kt index 60589349..461debf7 100644 --- a/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/BudsRepositoryImpl.kt +++ b/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/BudsRepositoryImpl.kt @@ -245,7 +245,7 @@ class BudsRepositoryImpl( override val sessionSince: StateFlow = _sessionSince /** replay=0 counterpart of [_ancMode], used only so [refreshAncMode] waits for a - * genuinely fresh response rather than immediately observing [_ancMode]'s current value. */ + * genuinely fresh response rather than immediately observing [_ancMode]'s current value. Fed only by a Message Stream `Notify` (A93-APP-04). */ private val _ancModeFresh = MutableSharedFlow(extraBufferCapacity = 8) private val _ancModeUpdatedAt = MutableStateFlow(null) @@ -277,6 +277,20 @@ class BudsRepositoryImpl( @Volatile private var lastSetAt: Long = Long.MIN_VALUE + /** + * A93-APP-02 (`ai-sessions/0094`, `CAP-076` §6.2 / P1): a session reads the mode twice — the Connect's snapshot `Get` and `ReadSetting 4:13` — and in 12 of + * 12 `CAP-076` sessions the `Get`'s `Notify` came first. [readRound] counts the sessions' read sequences ([launchInitialEqRead]); [changedSinceBy] is the + * round and the source of the read answer that last gave "changed since the app last saw it", so the other read of the same round, finding the same mode, + * keeps that cause instead of replacing it with a reading. + */ + @Volatile + private var readRound = 0 + + @Volatile + private var changedSinceBy: Pair? = null + + private enum class ReadSource { GET, READ_SETTING } + private val _eqProfile = MutableStateFlow(null) override val eqProfile: StateFlow = _eqProfile @@ -374,16 +388,31 @@ class BudsRepositoryImpl( * run is a reading (nothing was shown that could have changed); the same mode again changes nothing (`null`). */ private fun causeOfNotify(mode: AncMode, answersGet: Boolean): AncModeCause? = when { - answersGet -> causeOfReadAnswer(mode) + answersGet -> causeOfReadAnswer(mode, ReadSource.GET) setPendingMode == mode -> AncModeCause.SET_BY_APP _ancMode.value == null -> AncModeCause.READ mode != _ancMode.value -> AncModeCause.CHANGED_BY_BUDS else -> null } - /** ADR-065 item 4: the cause of a read answer — the `Notify` to the app's `Get`, or the RESPONSE to its `ReadSetting 4:13`. */ - private fun causeOfReadAnswer(mode: AncMode): AncModeCause = - if (_ancMode.value != null && mode != _ancMode.value) AncModeCause.CHANGED_SINCE_LAST_READ else AncModeCause.READ + /** + * ADR-065 item 4: the cause of a read answer — the `Notify` to the app's `Get`, or the RESPONSE to its `ReadSetting 4:13`: one that differs from the shown + * mode is "changed since the app last saw it"; the same mode is a reading — except the other read of the same session after such an answer, which keeps + * the cause (`null`, A93-APP-02: before `ai-sessions/0094` the equal `4:13` answer replaced it, `CAP-076` H1 4094 → 4135). + */ + private fun causeOfReadAnswer(mode: AncMode, source: ReadSource): AncModeCause? { + val shown = _ancMode.value + val changedSince = changedSinceBy + return when { + shown != null && mode != shown -> { + changedSinceBy = readRound to source + AncModeCause.CHANGED_SINCE_LAST_READ + } + _ancModeCause.value == AncModeCause.CHANGED_SINCE_LAST_READ && changedSince != null && + changedSince.first == readRound && changedSince.second != source -> null + else -> AncModeCause.READ + } + } /** * ADR-065 item 4 (`ai-sessions/0091`): the cause of a `4:{13:n}` settings-stream packet — the app's own `Set` of that mode within [ANC_SET_WINDOW_MS] @@ -412,11 +441,12 @@ class BudsRepositoryImpl( BleLogger.logConnectionEvent("Noise-control mode from $source: no mode settable") return } - val cause = if (stream) causeOfStream(mode) else causeOfReadAnswer(mode) + val cause = if (stream) causeOfStream(mode) else causeOfReadAnswer(mode, ReadSource.READ_SETTING) _ancModeUnconfirmedAt.value = null // the Buds' own report — whatever a cut-off answer would have said, this is their mode now (F-3) emitAncMode(mode, cause) - _ancModeFresh.tryEmit(mode) - BleLogger.logConnectionEvent("Noise-control mode from $source (${(cause ?: _ancModeCause.value)?.name?.lowercase() ?: "unchanged"})") + // No [_ancModeFresh] here (A93-APP-04): a Refresh waits for the Message Stream `Notify` to its own `Get` — a Maestro packet is not that answer. + // A93-APP-08 (`CAP-076` defect (c)): a packet that changes nothing logs "unchanged", not the cause it kept from an earlier packet. + BleLogger.logConnectionEvent("Noise-control mode from $source (${cause?.name?.lowercase() ?: "unchanged"})") } private fun updateEqProfile(gains: EqBandGains?) { @@ -705,7 +735,9 @@ class BudsRepositoryImpl( _ancModeUnconfirmedAt.value = null emitAncMode(it, causeOfNotify(it, answersGet)) _ancModeFresh.tryEmit(it) - _ancOutcomes.tryEmit(AncOutcome.Notified(it)) + // A93-APP-03 (`ai-sessions/0094`, `CAP-076` §5.3 / P2): the Get's own answer never answers a Set — the Get's waiter can resume on + // another thread, send the Set and subscribe before this line runs; only a later Notify (or the ACK/NAK) ends the Set's wait. + if (!answersGet) _ancOutcomes.tryEmit(AncOutcome.Notified(it)) } } is AncFrame.Set -> emitAncMode(anc.mode) @@ -738,7 +770,7 @@ class BudsRepositoryImpl( updateEqProfile(frame.frame.gains) _eqError.value = null } - _maestroReplies.tryEmit(MaestroReply.Value(frame.frame)) + _maestroReplies.tryEmit(MaestroReply.Value(frame.frame, frame.stream)) } is RoutedFrame.MaestroHello -> { @@ -770,7 +802,7 @@ class BudsRepositoryImpl( is RoutedFrame.Setting -> { val value = frame.value if (value is SettingValue.AncState) applyAncState(value, frame.stream) else applySetting(value, clock()) - _maestroReplies.tryEmit(MaestroReply.Setting(value)) + _maestroReplies.tryEmit(MaestroReply.Setting(value, frame.stream)) } // ADR-065 item 2: a pushed change of a field this app does not read — said (payload-free, AGENTS.md §9), not shown, not an UnidentifiedFrame. @@ -1101,7 +1133,7 @@ class BudsRepositoryImpl( val wire = Hdlc.encode(channel.requestAddress, PW_HDLC_CONTROL_UI, PwRpc.encode(request)) // An OK answer that is not a decodable EQ value ends the wait too (A58-APP-03): reported as unreadable, not as "no answer". val accept: (MaestroReply) -> Boolean = { - (it is MaestroReply.Value && !it.frame.persist) || + (it is MaestroReply.Value && !it.stream && !it.frame.persist) || (it is MaestroReply.Result && it.result.methodId == Maestro.METHOD_READ_SETTING) } awaitWriteQuarantine() @@ -1486,6 +1518,7 @@ class BudsRepositoryImpl( * `internal` so a test can start it without a `BluetoothDevice`. */ internal fun launchInitialEqRead() { + readRound++ // A93-APP-02: a new session's reads (the snapshot `Get` launched just before belongs to it — its answer needs a Buds round trip) eqReadJob?.cancel() eqReadJob = scope.launch { readEq() @@ -1543,7 +1576,7 @@ class BudsRepositoryImpl( val request = Maestro.readSettingRequest(channel.channelId, SettingsCodec.FIELD_ANC_STATE) ?: return@withLock val wire = Hdlc.encode(channel.requestAddress, PW_HDLC_CONTROL_UI, PwRpc.encode(request)) val accept: (MaestroReply) -> Boolean = { - (it is MaestroReply.Setting && it.value.field == SettingsCodec.FIELD_ANC_STATE) || + (it is MaestroReply.Setting && !it.stream && it.value.field == SettingsCodec.FIELD_ANC_STATE) || (it is MaestroReply.Result && it.result.methodId == Maestro.METHOD_READ_SETTING) } awaitWriteQuarantine() @@ -1616,7 +1649,7 @@ class BudsRepositoryImpl( // A58-APP-03 (`ai-sessions/0059`): an OK `ReadSetting` answer whose value the codec rejects arrives as an OK [MaestroReply.Result] — it ends // the wait at once as [BudsError.UnreadableAnswer] (the field stays "not read") instead of a 2 s `Timeout` that would say the Buds did not answer. val accept: (MaestroReply) -> Boolean = { - (it is MaestroReply.Setting && it.value.field == field) || + (it is MaestroReply.Setting && !it.stream && it.value.field == field) || (it is MaestroReply.Result && it.result.methodId == Maestro.METHOD_READ_SETTING) } awaitWriteQuarantine() @@ -1727,7 +1760,7 @@ class BudsRepositoryImpl( /** Wait for the ANC state during the Connect-time snapshot. */ private const val SNAPSHOT_TIMEOUT_MS = 1_000L - /** How long the channel stays claimed after an action so replies land, then it is released — also after the noise-control tab or the app is left (ADR-061). */ + /** How long the channel stays claimed after an action so replies land, then it is released (ADR-065 item 5: no hold — `ai-sessions/0094` dropped ADR-061's tab clause). */ private const val MESSAGE_STREAM_LINGER_MS = 1_500L /** @@ -1782,11 +1815,14 @@ private fun BatteryLevel.stamped(atMillis: Long): BatteryLevel = if (this is Bat private fun BatteryLevel.chargingReading(atMillis: Long): ChargingReading? = (this as? BatteryLevel.Known)?.isCharging?.let { ChargingReading(it, atMillis, ChargingSource.MESSAGE_STREAM) } -/** What the Buds answered to one of our Maestro requests (DECISIONS.md ADR-034). */ +/** + * What the Buds answered to one of our Maestro requests (DECISIONS.md ADR-034) — or pushed: [Value.stream] / [Setting.stream] mark a settings-stream packet, + * which never ends a read's wait (`ai-sessions/0094`, A93-APP-04 — only the read's RESPONSE does). + */ private sealed class MaestroReply { - data class Value(val frame: EqFrame) : MaestroReply() + data class Value(val frame: EqFrame, val stream: Boolean = false) : MaestroReply() data class Result(val result: RoutedFrame.RpcResult) : MaestroReply() - data class Setting(val value: SettingValue) : MaestroReply() + data class Setting(val value: SettingValue, val stream: Boolean = false) : MaestroReply() /** The `GetHardwareInfo` answer carried serials (ADR-058) — only their [count] travels here; the strings went straight to `deviceInfo`. */ data class Hardware(val count: Int) : MaestroReply() diff --git a/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/CodecRouter.kt b/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/CodecRouter.kt index 33923f98..63f5309c 100644 --- a/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/CodecRouter.kt +++ b/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/CodecRouter.kt @@ -40,7 +40,8 @@ sealed class RoutedFrame { /** The Model ID the Buds send on every Message Stream open (Safe-Mode gate, DECISIONS.md ADR-042). */ data class ModelId(val frame: ModelIdFrame) : RoutedFrame() - data class Eq(val frame: EqFrame) : RoutedFrame() + /** An EQ value (field 16/18) — a `ReadSetting`/`WriteSetting` answer ([stream] false) or a settings-stream push ([stream] true, `ai-sessions/0094` A93-APP-04). */ + data class Eq(val frame: EqFrame, val stream: Boolean = false) : RoutedFrame() /** * A non-EQ setting value (`4:{N: …}`, ADR-036/045) from a `ReadSetting` answer ([stream] false, a `RESPONSE`) or the settings-change stream the app @@ -303,9 +304,9 @@ internal fun routeMaestro(packet: RpcPacket): RoutedFrame? { if (settingsMethod && carriesValue && packet.payload.isNotEmpty() && (packet.status == null || packet.status == 0) ) { - val eq = EqFrameDecoder.decode(packet) - if (eq is BudsResult.Success) return RoutedFrame.Eq(eq.value) val stream = packet.type == PwRpc.TYPE_SERVER_STREAM + val eq = EqFrameDecoder.decode(packet) + if (eq is BudsResult.Success) return RoutedFrame.Eq(eq.value, stream) SettingsCodec.decode(packet.payload)?.let { return RoutedFrame.Setting(it, stream) } // ADR-065 item 2 (`ai-sessions/0091`): a pushed change of a field this app does not read is said, not shown — and not an UnidentifiedFrame. if (stream && method == Maestro.METHOD_SUBSCRIBE_TO_SETTINGS_CHANGES) { diff --git a/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/DarkModeSettingsStore.kt b/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/DarkModeSettingsStore.kt index 92ea889a..0933c448 100644 --- a/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/DarkModeSettingsStore.kt +++ b/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/DarkModeSettingsStore.kt @@ -38,7 +38,8 @@ class DarkModeSettingsStore internal constructor(private val dataStore: DataStor constructor(context: Context) : this(context.openControlSettings) - val darkMode: Flow = dataStore.data.map { prefs -> DarkMode.fromStored(prefs[DARK_MODE_KEY]) } + /** A file that cannot be read (corrupt, I/O error) reads as the default ([readableData], A93-APP-05). */ + val darkMode: Flow = dataStore.readableData().map { prefs -> DarkMode.fromStored(prefs[DARK_MODE_KEY]) } suspend fun setDarkMode(mode: DarkMode) { dataStore.edit { prefs -> prefs[DARK_MODE_KEY] = mode.name } diff --git a/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/DebugSettingsStore.kt b/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/DebugSettingsStore.kt index c0ba42a5..0eb526b6 100644 --- a/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/DebugSettingsStore.kt +++ b/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/DebugSettingsStore.kt @@ -20,6 +20,8 @@ package io.github.tedsluis.opencontrolpixelbuds.data.settings import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import kotlinx.coroutines.flow.Flow @@ -37,14 +39,17 @@ import kotlinx.coroutines.flow.map * must encrypt it (wording aligned 2026-09-24, 0044 finding AR-3). The file is shared with [DarkModeSettingsStore] since `ai-sessions/0062` * ([openControlSettings]). */ -class DebugSettingsStore(private val context: Context) { +class DebugSettingsStore internal constructor(private val dataStore: DataStore) { - val debugModeEnabled: Flow = context.openControlSettings.data.map { prefs -> + constructor(context: Context) : this(context.openControlSettings) + + /** A file that cannot be read (corrupt, I/O error) reads as off ([readableData], A93-APP-05). */ + val debugModeEnabled: Flow = dataStore.readableData().map { prefs -> prefs[DEBUG_MODE_KEY] ?: false } suspend fun setDebugModeEnabled(enabled: Boolean) { - context.openControlSettings.edit { prefs -> prefs[DEBUG_MODE_KEY] = enabled } + dataStore.edit { prefs -> prefs[DEBUG_MODE_KEY] = enabled } } companion object { diff --git a/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/SettingsDataStore.kt b/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/SettingsDataStore.kt index 98ae6035..b7473173 100644 --- a/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/SettingsDataStore.kt +++ b/android/data/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/SettingsDataStore.kt @@ -20,7 +20,14 @@ package io.github.tedsluis.opencontrolpixelbuds.data.settings import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.preferencesDataStore +import io.github.tedsluis.opencontrolpixelbuds.hardware.BleLogger +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import java.io.IOException /** * The app's one local settings file (plain AndroidX DataStore Preferences, `opencontrol_settings`), shared by [DebugSettingsStore] and [DarkModeSettingsStore] @@ -28,3 +35,14 @@ import androidx.datastore.preferences.preferencesDataStore * Nothing here is sensitive (a Debug-mode switch and a dark-mode choice), so it is not encrypted (`ARCHITECTURE.md` §2/§9, `AGENTS.md` §10). */ internal val Context.openControlSettings by preferencesDataStore(name = "opencontrol_settings") + +/** + * [DataStore.data] that reads a file it cannot read (a corrupt file — `CorruptionException` is an [IOException] — or an I/O error) as empty preferences, + * i.e. every setting at its default, instead of ending the collector with an exception (`ai-sessions/0094`, A93-APP-05; `AGENTS.md` §8). Logged without a + * payload. Writes are unchanged: the next write replaces the unreadable file. + */ +internal fun DataStore.readableData(): Flow = data.catch { e -> + if (e !is IOException) throw e + BleLogger.logConnectionEvent("Settings file unreadable (${e.javaClass.simpleName}): the defaults are used") + emit(emptyPreferences()) +} diff --git a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/BudsRepositoryImplTest.kt b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/BudsRepositoryImplTest.kt index 2e4abb6f..bcf7c859 100644 --- a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/BudsRepositoryImplTest.kt +++ b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/BudsRepositoryImplTest.kt @@ -30,6 +30,7 @@ import io.github.tedsluis.opencontrolpixelbuds.data.codec.Cap065 import io.github.tedsluis.opencontrolpixelbuds.data.codec.Cap066Balance import io.github.tedsluis.opencontrolpixelbuds.data.codec.Cap072 import io.github.tedsluis.opencontrolpixelbuds.data.codec.Cap075 +import io.github.tedsluis.opencontrolpixelbuds.data.codec.Cap076 import io.github.tedsluis.opencontrolpixelbuds.data.codec.Dlci import io.github.tedsluis.opencontrolpixelbuds.data.codec.HardwareInfoFixtures import io.github.tedsluis.opencontrolpixelbuds.data.codec.Hdlc @@ -71,6 +72,7 @@ import io.github.tedsluis.opencontrolpixelbuds.domain.currentAncMode import io.github.tedsluis.opencontrolpixelbuds.domain.isCurrent import io.github.tedsluis.opencontrolpixelbuds.hardware.FakeBudsTransport import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import android.bluetooth.BluetoothDevice @@ -78,6 +80,7 @@ import android.bluetooth.stubBluetoothDevice import kotlinx.coroutines.flow.first import kotlinx.coroutines.async import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.currentTime import kotlinx.coroutines.test.runCurrent @@ -191,6 +194,20 @@ class BudsRepositoryImplTest { assertEquals(AncMode.ADAPTIVE, repo.ancMode.first()) } + @Test + @DisplayName("A93-APP-03 (`ai-sessions/0094`, CAP-076 §5.3 / P2): the Get's own Notify never answers the Set that follows it — even when that Notify resumes the caller in place (unconfined; on hardware the repository's Dispatchers.Default can run the Set before the Notify's handling ends), the Set waits for its own answer") + fun `the Get's Notify does not answer the following Set`() = runTest { + val (repo, transport) = buildRepository() + // Only the Get is answered (CAP-065 6334, OFF); the Set gets no ACK. + transport.onSent = { ch, frame -> if (ch == Dlci.FAST_PAIR_MESSAGE_STREAM && frame.toHex().startsWith("0811")) transport.emit(ch, hex(Cap065.NOTIFY_E8_OFF_6334)) } + + val result = withContext(UnconfinedTestDispatcher(testScheduler)) { repo.setAncMode(AncMode.ADAPTIVE) } + + assertEquals(listOf("08110000", "0812001401e8e840" + "00".repeat(16)), transport.sent.map { it.second.toHex() }, "the Get, then the Set") + assertEquals(BudsResult.Failure(BudsError.Timeout), result, "no ACK and no Notify of its own: the Set was not answered") + assertEquals(AncMode.OFF, repo.ancMode.first(), "the Get's answer is shown") + } + @Test fun `a real inbound ANC Notify frame updates ancMode`() = runTest { val (repo, transport) = buildRepository() @@ -3094,10 +3111,10 @@ class BudsRepositoryImplTest { assertEquals(AncModeCause.CHANGED_BY_BUDS, repo.ancModeCause.first(), "and so is its cause (before 0091 it was reset to null here)") } - // ---- DECISIONS.md ADR-062 Update 2 (`ai-sessions/0089`): the cause of a mode learned by the app's own Get (CAP-075 H2's bytes) ------------------------------- + // ---- ADR-065 item 4 (first ADR-062 Update 2, `ai-sessions/0089`): the cause of a mode learned by the app's own Get (CAP-075 H2's bytes; renamed `ai-sessions/0094`) --- @Test - @DisplayName("ADR-062 Update 2: a re-claim's Get answer that differs from the mode last seen on this connection is changed since last read (CAP-075 2735 → 2980, 4893 → 5102, 5102 → 5361)") + @DisplayName("ADR-065 item 4: a Get answer (a Refresh or a tap's claim) that differs from the mode last shown is changed since the app last saw it (CAP-075 2735 → 2980, 4893 → 5102, 5102 → 5361)") fun `a differing Get answer is changed since the last read`() = runTest { val (repo, transport) = buildRepository() transport.onSent = null @@ -3114,7 +3131,7 @@ class BudsRepositoryImplTest { assertEquals(AncModeCause.CHANGED_SINCE_LAST_READ, repo.ancModeCause.first(), "2980: TRANSPARENT after 2735's ADAPTIVE") assertEquals(currentTime, repo.ancModeUpdatedAt.first(), "the line's time is the read's receive time") - // An unprovoked Notify on the held claim stays the Buds' change (3006). + // A Notify with no Get waiting, while the claim is still open, stays the Buds' change (3006). transport.emit(Dlci.FAST_PAIR_MESSAGE_STREAM, hex(Cap075.NOTIFY_ACTIVE_3006)); settle() assertEquals(AncModeCause.CHANGED_BY_BUDS, repo.ancModeCause.first()) @@ -3166,7 +3183,7 @@ class BudsRepositoryImplTest { } @Test - @DisplayName("ADR-062 Update 2: the app's own Set stays set by this app when its Get found another mode first") + @DisplayName("ADR-065 item 4: the app's own Set stays set by this app when its Get found another mode first") fun `the app's own Set after a differing Get stays set by this app`() = runTest { val (repo, transport) = buildRepository() // autoAck: the Get answered by CAP-065 6334 (OFF), the Set ACKed transport.onSent = null @@ -3368,6 +3385,131 @@ class BudsRepositoryImplTest { assertEquals(true, "Noise-control mode from the mode read: no mode settable" in BleLogger.exportLog()) } + @Test + @DisplayName("A93-APP-02 (`ai-sessions/0094`, CAP-076 P1): a session's snapshot Get that finds another mode stays changed since the app last saw it when the same session's ReadSetting 4:13 answers that mode (CAP-076 H1 4094 → 4135, +747 ms); a later Refresh of the same mode is a reading") + fun `the equal read after a differing Get keeps changed since`() = runTest { + val (repo, transport) = connectedLikeCap051() + advanceTimeBy(10_000) + transport.emit(Dlci.MAESTRO, hex(SettingsStream.STREAM_13_TRANSPARENT_CH21_CAP051_1802)); settle() + assertEquals(AncMode.TRANSPARENT, repo.ancMode.first(), "the last push before the session ended (CAP-076 H1 3972: Transparency)") + + // A new session's sequence; the 4:13 answer is held back so the Get's Notify comes first, as in 12 of 12 CAP-076 sessions (§6.2). + answerReadsLikeCap051(transport, read13 = null) + val maestroOnly = transport.onSent!! + transport.onSent = { ch, frame -> if (ch == Dlci.MAESTRO) maestroOnly(ch, frame) } + advanceTimeBy(10_000) + repo.launchInitialEqRead() + val get = launch { repo.refreshAncMode() } + runCurrent() + transport.emit(Dlci.FAST_PAIR_MESSAGE_STREAM, hex(Cap076.NOTIFY_ACTIVE_H1_4094)) + get.join() + assertEquals(AncMode.ACTIVE, repo.ancMode.first()) + assertEquals(AncModeCause.CHANGED_SINCE_LAST_READ, repo.ancModeCause.first(), "4094: Noise cancellation after Transparency") + + advanceTimeBy(747) + transport.emit(Dlci.MAESTRO, hex(Cap076.READ_13_RESP_ACTIVE_CH21_H1_4135)); settle() + assertEquals(AncMode.ACTIVE, repo.ancMode.first()) + assertEquals(AncModeCause.CHANGED_SINCE_LAST_READ, repo.ancModeCause.first(), "4135, the same mode by the other read of the same session, keeps the cause") + assertEquals(currentTime, repo.ancModeUpdatedAt.first(), "the time moves to the later answer") + + // A Refresh (the same source again) that finds the same mode is a reading. + val refresh = launch { repo.refreshAncMode() } + runCurrent() + transport.emit(Dlci.FAST_PAIR_MESSAGE_STREAM, hex(Cap076.NOTIFY_ACTIVE_H1_4094)) + refresh.join() + assertEquals(AncModeCause.READ, repo.ancModeCause.first()) + + // The next session's 4:13 read of the same mode is a reading, even after this session's Get found a change. + transport.emit(Dlci.MAESTRO, hex(SettingsStream.STREAM_13_TRANSPARENT_CH21_CAP051_1802)); settle() + val again = launch { repo.refreshAncMode() } + runCurrent() + transport.emit(Dlci.FAST_PAIR_MESSAGE_STREAM, hex(Cap076.NOTIFY_ACTIVE_H1_4094)) + again.join() + assertEquals(AncModeCause.CHANGED_SINCE_LAST_READ, repo.ancModeCause.first()) + answerReadsLikeCap051(transport, read13 = Cap076.READ_13_RESP_ACTIVE_CH21_H1_4135) + val maestroOnlyNext = transport.onSent!! + transport.onSent = { ch, frame -> if (ch == Dlci.MAESTRO) maestroOnlyNext(ch, frame) } + advanceTimeBy(10_000) + repo.launchInitialEqRead(); settle() + assertEquals(AncModeCause.READ, repo.ancModeCause.first(), "another session's read: nothing changed since this app last saw it") + } + + /** The settings reads answered as [answerReadsLikeCap051], except the fields in [heldBack]; nothing on the Message Stream is answered. */ + private fun answerMaestroReadsExcept(transport: FakeBudsTransport, read13: String?, vararg heldBack: Int) { + answerReadsLikeCap051(transport, read13) + val answers = transport.onSent!! + transport.onSent = { ch, frame -> + if (ch == Dlci.MAESTRO) { + val rpc = (PwRpc.decode((Hdlc.decode(frame) as BudsResult.Success).value.payload) as BudsResult.Success).value + val field = if (rpc.methodId == Maestro.METHOD_READ_SETTING) rpc.payload[1].toInt() else null + if (field == null || field !in heldBack) answers(ch, frame) + } + } + } + + @Test + @DisplayName("A93-APP-04 (`ai-sessions/0094`): a settings-stream push of 13 (CAP-051 1802) while ReadSetting 4:13 waits does not end that read — GetHardwareInfo follows only the RESPONSE (CAP-051 1225)") + fun `a stream push does not end the read of 13`() = runTest { + BleLogger.clear() + val (repo, transport) = buildRepository() + answerMaestroReadsExcept(transport, read13 = null) + fun hardwareRequests() = transport.maestroHex().count { it == HardwareInfoFixtures.REQUEST_CH21_CAP072_A7593 } + repo.launchInitialEqRead(); settle() + assertEquals(1, transport.maestroHex().count { it == SettingsStream.READ_13_REQ_CH21_CAP051_1178 }, "the read of 13 is out, unanswered") + + transport.emit(Dlci.MAESTRO, hex(SettingsStream.STREAM_13_TRANSPARENT_CH21_CAP051_1802)); settle() + assertEquals(AncMode.TRANSPARENT, repo.ancMode.first(), "the push is shown") + assertEquals(0, hardwareRequests(), "the push is not the read's answer: the sequence still waits") + + advanceTimeBy(300) + transport.emit(Dlci.MAESTRO, hex(SettingsStream.READ_13_RESP_ADAPTIVE_CH21_CAP051_1225)); settle() + assertEquals(1, hardwareRequests(), "the RESPONSE ends the read") + assertEquals(true, "Noise-control mode read (channel 21)" in BleLogger.exportLog()) + } + + @Test + @DisplayName("A93-APP-04 (`ai-sessions/0094`): a settings-stream push of field 16 (CAP-015 2116) while ReadSetting 4:16 waits does not end the EQ read — the read times out without its RESPONSE") + fun `a stream push does not end the EQ read`() = runTest { + val (repo, transport) = buildRepository() + answerMaestroReadsExcept(transport, read13 = null, Maestro.FIELD_EQ_ACTIVE) + repo.launchInitialEqRead(); settle() + assertEquals(1, transport.maestroHex().size, "only the EQ read is out") + + transport.emit(Dlci.MAESTRO, hex(SettingsStream.STREAM_16_FLAT_CH19_CAP015_2116)); settle() + assertEquals(1, transport.maestroHex().size, "the push is not the read's answer: the settings reads still wait") + + advanceTimeBy(2_100); settle() + assertEquals(BudsError.Timeout, repo.eqError.first(), "no RESPONSE: the EQ read timed out") + } + + @Test + @DisplayName("A93-APP-04 (`ai-sessions/0094`): a settings-stream push of field 12 (CAP-056 1696) while ReadSetting 4:12 waits does not end that read") + fun `a stream push does not end a settings read`() = runTest { + val (repo, transport) = buildRepository() + answerMaestroReadsExcept(transport, read13 = null, 12) + repo.launchInitialEqRead(); settle() + val sentBefore = transport.maestroHex().size + assertEquals(true, sentBefore >= 2, "the EQ read answered, the reads up to field 12 out") + + transport.emit(Dlci.MAESTRO, hex(SettingsStream.STREAM_12_CH19_CAP056_1696)); settle() + assertEquals(sentBefore, transport.maestroHex().size, "the push is not the read's answer: the next read is not sent yet") + } + + @Test + @DisplayName("A93-APP-04 (`ai-sessions/0094`): a settings-stream push of 13 while a Refresh's Get waits does not end the Refresh — the Notify that follows is its answer (CAP-051 1802, CAP-076 H1 4094)") + fun `a stream push does not end a Refresh`() = runTest { + val (repo, transport) = connectedLikeCap051() + transport.onSent = null + val refresh = async { repo.refreshAncMode() } + runCurrent() + transport.emit(Dlci.MAESTRO, hex(SettingsStream.STREAM_13_TRANSPARENT_CH21_CAP051_1802)); settle() + assertEquals(AncModeCause.CHANGED_BY_BUDS, repo.ancModeCause.first(), "the push: Transparency after Adaptive, nothing set by the app") + transport.emit(Dlci.FAST_PAIR_MESSAGE_STREAM, hex(Cap076.NOTIFY_ACTIVE_H1_4094)) + + assertEquals(BudsResult.Success(AncMode.ACTIVE), refresh.await(), "the Get's answer is the Notify, not the push") + assertEquals(AncModeCause.CHANGED_SINCE_LAST_READ, repo.ancModeCause.first(), "a read answer that differs from the shown mode") + } + @Test @DisplayName("ADR-065 item 4: a read answer (ReadSetting 4:13) that differs from the shown mode is changed since the app last saw it; the same mode again is a reading; 13:0 after the Buds' DISC (CAP-051 2247) keeps the mode and its cause") fun `a differing read of 13 is changed since the app last saw it`() = runTest { diff --git a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/Cap076Fixtures.kt b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/Cap076Fixtures.kt new file mode 100644 index 00000000..51d62321 --- /dev/null +++ b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/Cap076Fixtures.kt @@ -0,0 +1,33 @@ +/* + * OpenControl for Pixel Buds Pro 2 + * Copyright (C) 2026 Ted Sluis + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero + * General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package io.github.tedsluis.opencontrolpixelbuds.data.codec + +/** + * Real bytes from `CAP-076` H1 (`CAP-076-btsnoop_hci1.log`, the Buds' ACL handle `0x000b`; `ai-sessions/0092`, app 1.3.0) for `ai-sessions/0094` + * (A93-APP-02, `CAP-076-FINDINGS.md` §6.2 / §13 P1). Extracted with + * `tshark -r CAP-076-btsnoop_hci1.log -Y "frame.number==4094 || frame.number==4135" -T fields -e frame.number -e bthci_acl.chandle -e btrfcomm.dlci -e data.data`. + */ +internal object Cap076 { + /** H1 4094 (06:41:33.418), Buds → phone, DLCI 0x04: the S4 re-open's snapshot `Get` answered — Noise cancellation (`08`), after the last push 3972 (Transparency). */ + const val NOTIFY_ACTIVE_H1_4094 = "0813000401e8e808" + + /** H1 4135 (06:41:34.165, +747 ms), Buds → phone, DLCI 0x02, channel 21: the same session's `ReadSetting 4:13` answered — `4:{13:2}` = Noise cancellation. */ + const val READ_13_RESP_ACTIVE_CH21_H1_4135 = "7e00a5032a0422026802080110151dea71de7d5e2551aed0aef4aa8ebc7e" +} diff --git a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/CodecRouterTest.kt b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/CodecRouterTest.kt index cb1b2628..d563200a 100644 --- a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/CodecRouterTest.kt +++ b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/CodecRouterTest.kt @@ -317,6 +317,8 @@ class CodecRouterTest { @DisplayName("fuzz: random chunks on any in-scope or out-of-scope channel never throw") fun `random chunks never throw`(input: Pair) { val router = CodecRouter() - router.feed(input.first, input.second, timestampMillis = 0) + var faults = 0 // A93-APP-06 (`ai-sessions/0094`): decodeGuarded turns a throw into a malformed frame — count it, so a throwing decoder fails here + router.feed(input.first, input.second, timestampMillis = 0, onDecoderFault = { faults++ }) + assertEquals(0, faults, "no decoder threw") } } diff --git a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/HardwareInfoCodecTest.kt b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/HardwareInfoCodecTest.kt index 183289b1..95bb210a 100644 --- a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/HardwareInfoCodecTest.kt +++ b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/HardwareInfoCodecTest.kt @@ -124,11 +124,13 @@ class HardwareInfoCodecTest { } // Through the router, re-sealed with a valid CRC, so the reader is reached (the pattern of OversizedLengthTest). val router = CodecRouter() + var faults = 0 // A93-APP-06 (`ai-sessions/0094`): a decoder that threw is caught by decodeGuarded — a fuzz run must see none repeat(500) { val mutated = real.copyOf() repeat(random.nextInt(1, 4)) { mutated[random.nextInt(mutated.size)] = random.nextInt(256).toByte() } - router.feed(Dlci.MAESTRO, Hdlc.encode(10496, PW_HDLC_CONTROL_UI, mutated), timestampMillis = 0) + router.feed(Dlci.MAESTRO, Hdlc.encode(10496, PW_HDLC_CONTROL_UI, mutated), timestampMillis = 0, onDecoderFault = { faults++ }) } + assertEquals(0, faults, "no decoder threw") } private fun ByteArray.toHex() = joinToString("") { "%02x".format(it) } diff --git a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/OversizedLengthTest.kt b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/OversizedLengthTest.kt index 52c5cd73..5a760cf3 100644 --- a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/OversizedLengthTest.kt +++ b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/OversizedLengthTest.kt @@ -60,10 +60,13 @@ class OversizedLengthTest { private class Seen { val malformed = ArrayList() var unidentified = 0 + + /** A decoder that threw (caught by `decodeGuarded`, `ai-sessions/0094` A93-APP-06): a fuzz run must see none. */ + var faults = 0 } private fun CodecRouter.feedCounting(channel: Int, bytes: ByteArray, seen: Seen): List = - feed(channel, bytes, 0L, onUnidentified = { seen.unidentified++ }, onMalformed = { seen.malformed += it }) + feed(channel, bytes, 0L, onUnidentified = { seen.unidentified++ }, onMalformed = { seen.malformed += it }, onDecoderFault = { seen.faults++ }) @Test @DisplayName("synthetic: Proto.fields refuses a length that overflows the offset (was: IllegalArgumentException from copyOfRange)") @@ -182,5 +185,6 @@ class OversizedLengthTest { // Nothing was poisoned: a real frame still decodes on each channel afterwards. assertInstanceOf(RoutedFrame.Setting::class.java, router.feedCounting(Dlci.MAESTRO, hex(Settings036.READ_2_RESP), seen).single()) assertTrue(seen.unidentified + seen.malformed.size > 0) + assertEquals(0, seen.faults, "no decoder threw (a throw would be turned into a malformed frame and hidden)") } } diff --git a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/SettingsStreamCodecTest.kt b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/SettingsStreamCodecTest.kt index d102d333..577e5549 100644 --- a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/SettingsStreamCodecTest.kt +++ b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/SettingsStreamCodecTest.kt @@ -164,10 +164,13 @@ class SettingsStreamCodecTest { private class Seen { var unidentified = 0 var malformed = 0 + + /** A decoder that threw (caught by `decodeGuarded`, `ai-sessions/0094` A93-APP-06): a fuzz run must see none. */ + var faults = 0 } private fun CodecRouter.feedCounting(bytes: ByteArray, seen: Seen): List = - feed(Dlci.MAESTRO, bytes, 0L, onUnidentified = { seen.unidentified++ }, onMalformed = { seen.malformed++ }) + feed(Dlci.MAESTRO, bytes, 0L, onUnidentified = { seen.unidentified++ }, onMalformed = { seen.malformed++ }, onDecoderFault = { seen.faults++ }) @Test @DisplayName("synthetic: field 13 with every value 0–255 — 0 is none, 1–4 the modes, 5–255 not interpreted (an UnidentifiedFrame, never a mode, never a throw)") @@ -271,6 +274,7 @@ class SettingsStreamCodecTest { } } assertTrue(seen.unidentified + seen.malformed > 0) + assertEquals(0, seen.faults, "no decoder threw (a throw would be turned into a malformed frame and hidden)") assertEquals( listOf(RoutedFrame.Setting(SettingValue.AncState(AncMode.ACTIVE), stream = true)), router.feedCounting(hex(SettingsStream.STREAM_13_ACTIVE_CH21_CAP051_1717), seen), diff --git a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/SettingsStreamFixtures.kt b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/SettingsStreamFixtures.kt index 08f82515..56105b12 100644 --- a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/SettingsStreamFixtures.kt +++ b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/codec/SettingsStreamFixtures.kt @@ -115,8 +115,12 @@ internal object SettingsStream { /** `CAP-051` 1801 and 1530/1533 (Transparency); 1993 (21:45:45.603, before the Buds' own `DISC` 1996). */ const val NOTIFY_TRANSPARENT_CAP051_1801 = "0813000401e8e880" - /** `CAP-051` 1527 (21:43:28.107): the app's own `Set` Transparency. */ - const val SET_TRANSPARENT_CAP051_1527 = "0812001401e8e880" + "00000000000000000000000000000000" + /** + * `CAP-051` 1527 (21:43:28.107, handle `0x0002`, DLCI 0x04): the **official app's** `Set` Transparency, with its own 16 trailing bytes (OpenControl sends + * 16 zero bytes there). Real bytes since `ai-sessions/0094` (A93-APP-06; before, the trailing bytes were zeros): `tshark -r CAP-051-btsnoop_hci.log -Y + * "frame.number==1527" -T fields -e data.data` (exit 0). + */ + const val SET_TRANSPARENT_CAP051_1527 = "0812001401e8e880e4e68882be02fffe63abde07ef49dae8" /** `CAP-051` 1529 (21:43:28.219): the Buds' ACK of that Set. */ const val ACK_TRANSPARENT_CAP051_1529 = "ff010006081201e8e880" diff --git a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/DarkModeSettingsStoreTest.kt b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/DarkModeSettingsStoreTest.kt index 4da0eb7e..0c4afe70 100644 --- a/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/DarkModeSettingsStoreTest.kt +++ b/android/data/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/data/settings/DarkModeSettingsStoreTest.kt @@ -71,4 +71,16 @@ class DarkModeSettingsStoreTest { assertEquals(setOf("hold_reclaim_mode", "dark_mode"), prefs.asMap().keys.map { it.name }.toSet(), "no new key") second.cancel() } + + @Test + @DisplayName("A93-APP-05 (`ai-sessions/0094`): a corrupt settings file reads as the defaults (dark mode System, Debug mode off) instead of failing the collector") + fun `a corrupt settings file reads as the defaults`() = runBlocking { + val file = File(dir, "opencontrol_settings.preferences_pb") + file.writeBytes(byteArrayOf(0x0a, 0x7f, 0x01, 0x02)) // a length-delimited field longer than the file: not a Preferences proto + val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + val dataStore = PreferenceDataStoreFactory.create(scope = scope) { file } + assertEquals(DarkMode.DEFAULT, DarkModeSettingsStore(dataStore).darkMode.first()) + assertEquals(false, DebugSettingsStore(dataStore).debugModeEnabled.first()) + scope.cancel() + } } diff --git a/android/domain/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/domain/AncMode.kt b/android/domain/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/domain/AncMode.kt index bf2f99ad..dab71a15 100644 --- a/android/domain/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/domain/AncMode.kt +++ b/android/domain/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/domain/AncMode.kt @@ -32,6 +32,18 @@ enum class AncMode(val wireBit: Int) { ACTIVE(0x08), ; + /** + * The one name a person sees for the mode — on the buttons, the (i) line, the Quick Settings tile and the notification (`ai-sessions/0094`, A93-APP-08: + * four spellings before — "ACTIVE", "Noise cancellation", "Active", "ANC: ACTIVE"); the official app's words (`CAP-056` film, 17:31:33). + */ + val label: String + get() = when (this) { + ACTIVE -> "Noise cancellation" + OFF -> "Off" + ADAPTIVE -> "Adaptive" + TRANSPARENT -> "Transparency" + } + /** * The mode a Quick Settings tap moves to (`ai-sessions/0042`): Noise cancelling → Transparent → Adaptive → Off → Noise * cancelling. A fixed, documented order so the tile is predictable; an unknown current mode starts the cycle. diff --git a/android/domain/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/domain/AncTile.kt b/android/domain/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/domain/AncTile.kt index 4117bbd9..73b17029 100644 --- a/android/domain/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/domain/AncTile.kt +++ b/android/domain/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/domain/AncTile.kt @@ -39,7 +39,7 @@ fun ancTileState(session: ConnectionState, mode: AncMode?, availability: AncAvai // `ai-sessions/0062` F-2 (the maintainer's wording, chat 2026-10-01): the byte says "not allowed now", not "not worn" (`CAP-064`: e8 with no bud worn). availability == AncAvailability.NOT_ALLOWED -> "Not allowed now" mode == null -> "Tap to switch" - else -> mode.toString().lowercase().replaceFirstChar { it.uppercase() } + else -> mode.label } return AncTileState(ready, mode, availability, subtitle, active = ready && mode != null && mode != AncMode.OFF) } diff --git a/android/hardware/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/BudsCompanionPairing.kt b/android/hardware/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/BudsCompanionPairing.kt index 1cfc4dfa..02529d8f 100644 --- a/android/hardware/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/BudsCompanionPairing.kt +++ b/android/hardware/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/BudsCompanionPairing.kt @@ -48,23 +48,20 @@ import java.util.regex.Pattern * outcome means) lives in [PairingLogic] and is unit-tested; this class is the thin Android glue around it, and logs every * step (always-on, no address — AGENTS.md §9/§13). * - * // TODO(verify): the CDM `associate()` callback and picker are not exercised against a real device in this environment - * // — the maintainer re-test (b) in `ai-sessions/0041` confirms/refutes the fixes for the "could not resolve the selected - * // device" failure. + * The CDM `associate()` callback and picker were confirmed on the maintainer's device (`ai-sessions/0041` re-test (b), `ai-sessions/0043`). + * + * [choiceMemory] is shared by every instance (`ai-sessions/0094`, A93-APP-01): the Activity's instance runs "Use different Buds", the repository's + * singleton connects — both must see the same choice. */ -class BudsCompanionPairing(private val context: Context) { +class BudsCompanionPairing(private val context: Context, private val choiceMemory: PairingChoiceMemory) { private val companionDeviceManager: CompanionDeviceManager? = context.getSystemService() /** True from `associate()` until CDM answers (created/failed) or the picker is cancelled — a second request is refused. */ private val associationInFlight = AtomicBoolean(false) - /** - * Set by [forgetAssociations] ("Use different Buds", A68-APP-05): until a new association exists, no bonded device is taken by its name — the user - * asked to choose. In memory only; after a process restart a single bonded "Pixel Buds" device is used again, as it always was. - */ - @Volatile - private var chooseAgainRequested = false + /** False after "Use different Buds" until a new association exists ([PairingChoiceMemory]). */ + internal fun nameFallbackAllowed(): Boolean = !choiceMemory.chooseAgainRequested /** The picker was dismissed without a callback (`RESULT_CANCELED`): the next tap may start a new request. */ fun cancelPendingAssociation() { @@ -94,7 +91,7 @@ class BudsCompanionPairing(private val context: Context) { return BondedLookup.PermissionMissing } val associated = associations().mapNotNull { (_, view) -> view.address }.toSet() - return when (val choice = PairingLogic.chooseBondedOrAsk(bonded, associated, nameFallback = !chooseAgainRequested)) { + return when (val choice = PairingLogic.chooseBondedOrAsk(bonded, associated, nameFallback = nameFallbackAllowed())) { is PairingLogic.BondedChoice.One -> BondedLookup.Found(choice.candidate.address) PairingLogic.BondedChoice.None -> BondedLookup.NoneBonded PairingLogic.BondedChoice.Several -> BondedLookup.SeveralCandidates @@ -107,6 +104,8 @@ class BudsCompanionPairing(private val context: Context) { * Android's picker instead of reusing the old association. Returns how many were removed. */ fun forgetAssociations(): Int { + choiceMemory.chooseAgainRequested = true + associationInFlight.set(false) val manager = companionDeviceManager ?: return 0 var removed = 0 associations().forEach { (info, _) -> @@ -117,8 +116,6 @@ class BudsCompanionPairing(private val context: Context) { BleLogger.logConnectionEvent("Pairing: could not remove an association (${BleLogger.describe(e)})") } } - chooseAgainRequested = true - associationInFlight.set(false) BleLogger.logConnectionEvent("Pairing: use different Buds — removed $removed association(s) of this app; the next Pair a device opens the picker") return removed } @@ -141,8 +138,8 @@ class BudsCompanionPairing(private val context: Context) { * `IntentSender` [onPending] receives must be launched via an `ActivityResultLauncher` by the caller (`:app`, which * owns the `ComponentActivity`). * - * // TODO(verify): [NAME_PATTERN] filters CDM's own device picker to names containing "Pixel Buds" (case-insensitive) — - * // confirmed against the maintainer's real device (`ai-sessions/0036`). + * [NAME_PATTERN] filters CDM's own device picker to names containing "Pixel Buds" (case-insensitive) — confirmed on the maintainer's device + * (`ai-sessions/0036`). */ fun requestAssociation( onPending: (IntentSender) -> Unit, @@ -158,7 +155,7 @@ class BudsCompanionPairing(private val context: Context) { val reuse = PairingLogic.pickAssociation(existing.map { it.second }) if (reuse != null) { BleLogger.logConnectionEvent("Pairing: reusing existing association (${existing.size} total) — no new picker") - chooseAgainRequested = false + choiceMemory.chooseAgainRequested = false existing.first { it.second.id == reuse.id }.first.let(onCreated) return } @@ -174,6 +171,23 @@ class BudsCompanionPairing(private val context: Context) { .setSingleDevice(true) // ARCHITECTURE.md §15: single-device support only for v1. .build() BleLogger.logConnectionEvent("Pairing: association requested (CDM picker)") + // A93-APP-05 (`ai-sessions/0094`): a throwing associate() must not leave the request "in flight" — every later tap would read "already in progress". + try { + associate(manager, request, onPending, onCreated, onFailure) + } catch (e: RuntimeException) { + associationInFlight.set(false) + BleLogger.logConnectionEvent("Pairing: association request refused by the system (${BleLogger.describe(e)})") + onFailure(PairingFailure.AssociationFailed(e.javaClass.simpleName)) + } + } + + private fun associate( + manager: CompanionDeviceManager, + request: AssociationRequest, + onPending: (IntentSender) -> Unit, + onCreated: (AssociationInfo) -> Unit, + onFailure: (PairingFailure) -> Unit, + ) { manager.associate( request, ContextCompat.getMainExecutor(context), @@ -181,7 +195,7 @@ class BudsCompanionPairing(private val context: Context) { override fun onAssociationPending(intentSender: IntentSender) = onPending(intentSender) override fun onAssociationCreated(associationInfo: AssociationInfo) { associationInFlight.set(false) - chooseAgainRequested = false // the user chose: this association identifies the Buds from now on + choiceMemory.chooseAgainRequested = false // the user chose: this association identifies the Buds from now on BleLogger.logConnectionEvent("Pairing: association created") onCreated(associationInfo) } @@ -367,3 +381,13 @@ class BudsCompanionPairing(private val context: Context) { const val BOND_TIMEOUT_MS = 45_000L } } + +/** + * "Use different Buds" (A68-APP-05), remembered once per process (`ai-sessions/0094`, A93-APP-01): until a new association exists, no bonded device is + * taken by its name — the user asked to choose. One instance is shared by every [BudsCompanionPairing] (the DI singleton in `:app`); a rotation keeps + * it. In memory only: after a process restart a single bonded "Pixel Buds" device is used again, as it always was. + */ +class PairingChoiceMemory { + @Volatile + var chooseAgainRequested: Boolean = false +} diff --git a/android/hardware/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/BudsForegroundService.kt b/android/hardware/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/BudsForegroundService.kt index 23cbdb62..05a7c0de 100644 --- a/android/hardware/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/BudsForegroundService.kt +++ b/android/hardware/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/BudsForegroundService.kt @@ -63,13 +63,9 @@ class BudsForegroundService : Service() { .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) .setOngoing(true) .setPriority(NotificationCompat.PRIORITY_LOW) // AGENTS.md §2: persistent, low-priority. + // A93-APP-08 (`ai-sessions/0094`): `getLaunchIntentForPackage` may return null — then the notification opens nothing instead of failing. .setContentIntent( - PendingIntent.getActivity( - this, - 0, - packageManager.getLaunchIntentForPackage(packageName), - PendingIntent.FLAG_IMMUTABLE, - ), + packageManager.getLaunchIntentForPackage(packageName)?.let { PendingIntent.getActivity(this, 0, it, PendingIntent.FLAG_IMMUTABLE) }, ) .build() } diff --git a/android/hardware/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/RfcommBudsTransport.kt b/android/hardware/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/RfcommBudsTransport.kt index 7da7d276..fc631943 100644 --- a/android/hardware/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/RfcommBudsTransport.kt +++ b/android/hardware/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/RfcommBudsTransport.kt @@ -106,12 +106,23 @@ class RfcommBudsTransport( /** Cancels the readers, then closes every socket (session and on-demand). Idempotent. */ fun closeAll() { - if (!closed.compareAndSet(false, true)) return + synchronized(this) { if (!closed.compareAndSet(false, true)) return } scope.cancel() sockets.values.forEach(::closeQuietly) auxSockets.values.forEach(::closeQuietly) auxSockets.clear() } + + /** + * Attaches an on-demand socket — only while this connection is not closed, in one step with [closeAll]'s flag (`ai-sessions/0094`, A93-APP-05: + * a check followed by a separate put could attach a DLCI 0x04 socket after [closeAll] had run, and leave it open — `ai-sessions/0039`'s + * one-connection-per-channel collision). False = closed: the caller closes the socket. + */ + fun attach(channelId: Int, socket: RfcommSocket): Boolean = synchronized(this) { + if (closed.get()) return false + auxSockets[channelId] = socket + true + } } private val lock = Any() @@ -333,11 +344,10 @@ class RfcommBudsTransport( is ChannelOpen.Opened -> { // The connection may have been lost or replaced while this (possibly retried) open // was in flight — never attach a socket to a dead connection. - if (current !== connection || !connection.isOpen) { + if (current !== connection || !connection.attach(channelId, result.socket)) { closeQuietly(result.socket) return@withContext BudsResult.Failure(BudsError.ConnectionLost) } - connection.auxSockets[channelId] = result.socket connection.auxJobs[channelId] = startReader(connection, channelId, result.socket, onDemand = true) BudsResult.Success(Unit) } diff --git a/android/hardware/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/CompanionPairingChoiceTest.kt b/android/hardware/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/CompanionPairingChoiceTest.kt new file mode 100644 index 00000000..125e3d7b --- /dev/null +++ b/android/hardware/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/hardware/CompanionPairingChoiceTest.kt @@ -0,0 +1,56 @@ +/* + * OpenControl for Pixel Buds Pro 2 + * Copyright (C) 2026 Ted Sluis + * + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero + * General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package io.github.tedsluis.opencontrolpixelbuds.hardware + +import android.content.ContextWrapper +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +/** + * `ai-sessions/0094` (A93-APP-01): "Use different Buds" ran on the Activity's own [BudsCompanionPairing], while the repository connects through the + * application singleton — the singleton kept taking the single bonded "Pixel Buds" by name. Both instances now share one [PairingChoiceMemory]. + * The context is an empty [ContextWrapper] (`unitTests.isReturnDefaultValues`): no CompanionDeviceManager, no adapter. + */ +@DisplayName("Use different Buds — one memory for every pairing instance (A93-APP-01)") +class CompanionPairingChoiceTest { + + @Test + fun `use different Buds on one instance turns the name fallback off on the other`() { + val memory = PairingChoiceMemory() + val activityInstance = BudsCompanionPairing(ContextWrapper(null), memory) + val repositoryInstance = BudsCompanionPairing(ContextWrapper(null), memory) + assertTrue(repositoryInstance.nameFallbackAllowed()) + + activityInstance.forgetAssociations() + + assertFalse(repositoryInstance.nameFallbackAllowed()) + assertFalse(activityInstance.nameFallbackAllowed()) + } + + @Test + fun `a new Activity instance after a rotation still sees the choice`() { + val memory = PairingChoiceMemory() + BudsCompanionPairing(ContextWrapper(null), memory).forgetAssociations() + + assertFalse(BudsCompanionPairing(ContextWrapper(null), memory).nameFallbackAllowed()) + } +} diff --git a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/AncScreen.kt b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/AncScreen.kt index 6753851e..8cea7ee2 100644 --- a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/AncScreen.kt +++ b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/AncScreen.kt @@ -159,8 +159,8 @@ private fun AncModeButton(label: String, selected: Boolean, enabled: Boolean, mo internal fun ancModeLine(ancMode: AncMode?, updatedAt: Long?, fromLastConnection: Boolean = false): String = when { ancMode == null -> "ANC mode: unknown" // The maintainer's wording (chat 2026-10-03): "Mode: … - from the last connection (updated 14:32:07)". - fromLastConnection -> "ANC mode: ${ancMode.name} — from the last connection" + (formatUpdatedAt(updatedAt)?.let { " (updated $it)" } ?: "") - else -> "ANC mode: ${ancMode.name}" + (formatUpdatedAt(updatedAt)?.let { " (updated $it)" } ?: "") + fromLastConnection -> "ANC mode: ${ancMode.label} — from the last connection" + (formatUpdatedAt(updatedAt)?.let { " (updated $it)" } ?: "") + else -> "ANC mode: ${ancMode.label}" + (formatUpdatedAt(updatedAt)?.let { " (updated $it)" } ?: "") } /** @@ -183,7 +183,7 @@ internal fun ancDetailLines( if (cause == AncModeCause.CHANGED_BY_BUDS && ancMode != null) ancChangedByBudsLine(ancModeUpdatedAt) else null, if (cause == AncModeCause.CHANGED_SINCE_LAST_READ && ancMode != null) ancChangedSinceLastReadLine(ancModeUpdatedAt) else null, if (notAllowed) ancNotAllowedLine(checkedAt) else null, - "Connection: ${connectionState::class.simpleName}", + "Connection: ${connectionStateText(connectionState)}", ANC_STREAM_HINT_TEXT, ) diff --git a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/ConnectionBanner.kt b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/ConnectionBanner.kt index 9472298d..2f677730 100644 --- a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/ConnectionBanner.kt +++ b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/ConnectionBanner.kt @@ -26,6 +26,14 @@ import androidx.compose.ui.Modifier import io.github.tedsluis.opencontrolpixelbuds.domain.BudsError import io.github.tedsluis.opencontrolpixelbuds.domain.ConnectionState +/** Human words for the connection state in an (i) line (`ai-sessions/0094`, A93-APP-08: the class name was shown — "Discovering", "Failed"). */ +internal fun connectionStateText(state: ConnectionState): String = when (state) { + ConnectionState.Disconnected -> "not connected" + ConnectionState.Connecting, ConnectionState.Discovering -> "connecting" + ConnectionState.Ready -> "connected" + is ConnectionState.Failed -> "not connected (${state.error.userMessage()})" +} + /** True only when the app's own control channels are open — every command screen gates on this. */ internal fun ConnectionState.isReady(): Boolean = this is ConnectionState.Ready diff --git a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/ControlsScreen.kt b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/ControlsScreen.kt index de52880f..05645e54 100644 --- a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/ControlsScreen.kt +++ b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/ControlsScreen.kt @@ -22,6 +22,7 @@ package io.github.tedsluis.opencontrolpixelbuds.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.selection.toggleable import androidx.compose.material3.Checkbox import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -35,6 +36,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import io.github.tedsluis.opencontrolpixelbuds.domain.AncMode import io.github.tedsluis.opencontrolpixelbuds.domain.AncModeList @@ -164,12 +166,8 @@ internal fun showAncModeList(settings: BudsSettings): Boolean = settings.holdLeft?.value == HoldAction.NOISE_CONTROL || settings.holdRight?.value == HoldAction.NOISE_CONTROL /** The four boxes in the official app's on-screen order (`CAP-056` film, 17:31:33) — which wire boolean each sets is [AncModeList]'s job. */ -internal val ANC_MODE_LIST_ORDER: List> = listOf( - AncMode.ACTIVE to "Noise cancellation", - AncMode.OFF to "Off", - AncMode.ADAPTIVE to "Adaptive", - AncMode.TRANSPARENT to "Transparency", -) +internal val ANC_MODE_LIST_ORDER: List> = + listOf(AncMode.ACTIVE, AncMode.OFF, AncMode.ADAPTIVE, AncMode.TRANSPARENT).map { it to it.label } internal const val ANC_MODE_LIST_TITLE: String = "Modes for press and hold (both buds)" @@ -209,12 +207,15 @@ private fun AncModeListSection(reading: SettingReading?, enabled: B Text(ANC_MODE_LIST_TITLE, style = MaterialTheme.typography.titleMedium) val list = reading?.value ANC_MODE_LIST_ORDER.forEach { (mode, label) -> - Row(verticalAlignment = Alignment.CenterVertically) { - Checkbox( - checked = list?.isSelected(mode) ?: false, - onCheckedChange = { onChange(mode, it) }, - enabled = enabled && list != null && !list.isLocked(mode), - ) + val checked = list?.isSelected(mode) ?: false + val boxEnabled = enabled && list != null && !list.isLocked(mode) + // A93-APP-07 (`ai-sessions/0094`): the row is the checkbox — TalkBack reads the mode's name with its state. + Row( + modifier = Modifier.fillMaxWidth() + .toggleable(value = checked, enabled = boxEnabled, role = Role.Checkbox, onValueChange = { onChange(mode, it) }), + verticalAlignment = Alignment.CenterVertically, + ) { + Checkbox(checked = checked, onCheckedChange = null, enabled = boxEnabled) Text(label, style = MaterialTheme.typography.bodyLarge) } } diff --git a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/DebugScreen.kt b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/DebugScreen.kt index fd3e2560..7894dc29 100644 --- a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/DebugScreen.kt +++ b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/DebugScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.selection.toggleable import androidx.compose.material3.Button import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme @@ -35,6 +36,7 @@ import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import io.github.tedsluis.opencontrolpixelbuds.domain.UnidentifiedFrame @@ -58,9 +60,14 @@ fun DebugScreen( Surface(modifier = modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { // Reached from the settings menu's Debug tab (`ai-sessions/0062`); the bar shows "Settings" and the back arrow. - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + // A93-APP-07 (`ai-sessions/0094`): the row is the switch — TalkBack reads its label with its state. + Row( + modifier = Modifier.fillMaxWidth() + .toggleable(value = debugModeEnabled, role = Role.Switch, onValueChange = onDebugModeChanged), + horizontalArrangement = Arrangement.SpaceBetween, + ) { Text("Debug mode (verbose hex-dump logging)") - Switch(checked = debugModeEnabled, onCheckedChange = onDebugModeChanged) + Switch(checked = debugModeEnabled, onCheckedChange = null) } // Local-only (AGENTS.md §9) — the system's "save as" dialog writes the whole ring buffer to a file the user picks diff --git a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/EqScreen.kt b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/EqScreen.kt index 76d77ccc..3d28ce3a 100644 --- a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/EqScreen.kt +++ b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/EqScreen.kt @@ -43,6 +43,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.text.style.TextAlign @@ -204,7 +205,7 @@ private fun EqPresetRows(enabled: Boolean, onPresetSelected: (EqPreset) -> Unit) modifier = Modifier.weight(1f), label = { Text( - preset.name.replace('_', ' '), + presetLabel(preset), style = MaterialTheme.typography.labelMedium, maxLines = 1, textAlign = TextAlign.Center, @@ -362,6 +363,14 @@ private fun EqBandSlider(label: String, value: Float, enabled: Boolean, known: B }, enabled = enabled, valueRange = EqBandGains.RANGE.start..EqBandGains.RANGE.endInclusive, + // A93-APP-07 (`ai-sessions/0094`): a screen reader names the band and says its gain in dB, as the balance slider says its side — not a percentage. + modifier = Modifier.semantics { + contentDescription = label + stateDescription = if (known) "%.1f dB".format(shown) else NOT_READ_DESCRIPTION + }, ) } } + +/** A preset's name in words — "Heavy bass", not the enum's "HEAVY BASS" (`ai-sessions/0094`, A93-APP-08). */ +internal fun presetLabel(preset: EqPreset): String = preset.name.lowercase().replace('_', ' ').replaceFirstChar { it.uppercase() } diff --git a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/OpenControlNavHost.kt b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/OpenControlNavHost.kt index 37f7bb4f..15acadc0 100644 --- a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/OpenControlNavHost.kt +++ b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/OpenControlNavHost.kt @@ -319,7 +319,8 @@ fun OpenControlNavHost(state: OpenControlUiState, actions: OpenControlActions) { NavigationBarItem( selected = currentIndex == index, onClick = { navigateToTab(index) }, - icon = { Icon(destination.icon, contentDescription = destination.label) }, + // A93-APP-07 (`ai-sessions/0094`): the label below already names the tab — the icon says nothing more. + icon = { Icon(destination.icon, contentDescription = null) }, label = { Text(destination.label) }, ) } diff --git a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/SettingsMenu.kt b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/SettingsMenu.kt index 6215ef89..be92813f 100644 --- a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/SettingsMenu.kt +++ b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/SettingsMenu.kt @@ -75,9 +75,8 @@ internal enum class SettingsTab(val label: String) { SETTINGS("Settings"), DEBUG /** * The settings menu (`ai-sessions/0062` F-4, the maintainer's design in chats 2026-10-01): a full-screen destination reached from the top app bar's gear, outside - * the five bottom tabs (ARCHITECTURE.md §2.4), with three tabs — **Settings** (dark mode; since `ai-sessions/0087` the "Noise-control channel" card, ADR-062 — renamed - * "Noise control with Google Play services" by ADR-062 Update 2, `ai-sessions/0089`; - * since `ai-sessions/0082` item 5 also the "Case sounds" card, moved from Controls; "Use different Buds"), **Debug** (today's [DebugScreen], unchanged) and + * the five bottom tabs (ARCHITECTURE.md §2.4), with three tabs — **Settings** (dark mode; since `ai-sessions/0082` item 5 the "Case sounds" card, moved from + * Controls; "Use different Buds", confirmed in a dialog since `ai-sessions/0094`), **Debug** (today's [DebugScreen], unchanged) and * **Info** (the app's build, the Buds' firmware and — ADR-058 — their serial numbers). The selected tab survives a rotation; back (the top bar's arrow or the * system's) returns to the tab the menu was opened from. */ @@ -91,9 +90,9 @@ fun SettingsMenuScreen( onExportLog: () -> Unit, appBuild: AppBuildInfo, deviceInfo: DeviceInfo?, + modifier: Modifier = Modifier, /** Hands a URL to another app (the browser) — `:app` starts `Intent.ACTION_VIEW` (`ai-sessions/0064` F-6, DECISIONS.md ADR-050); injected so `:ui` stays testable. */ onOpenUrl: (String) -> Unit = {}, - modifier: Modifier = Modifier, /** "Use different Buds" (`ai-sessions/0069`, A68-APP-05): `:app` disconnects and removes this app's own pairing association. */ onUseDifferentBuds: () -> Unit = {}, /** Why the serial numbers were not read on this connection (DECISIONS.md ADR-058, `ai-sessions/0082`); `null` = read, or not attempted yet. */ @@ -104,8 +103,6 @@ fun SettingsMenuScreen( settingsError: SettingsFailure? = null, onCaseSoundEarbudsReplacedChanged: (Boolean) -> Unit = {}, onCaseSoundOtherAlertsChanged: (Boolean) -> Unit = {}, - /** DECISIONS.md ADR-062 (`ai-sessions/0087`): the "Noise control with Google Play services" choice (1.2.1: "Noise-control channel") — stored by `:app`, applied by the repository at once. */ - /** DECISIONS.md ADR-063 (`ai-sessions/0089`): whether Android finds Google Play services on this phone — information only; `null` = no line. */ ) { var selected by rememberSaveable { mutableIntStateOf(SettingsTab.SETTINGS.ordinal) } Column(modifier = modifier.fillMaxSize()) { @@ -198,13 +195,32 @@ private fun SettingsTabContent( // `ai-sessions/0069` A68-APP-05 (the maintainer's wording, chat 2026-10-03): the only way to control other Buds used to be clearing the app's data. Text(USE_DIFFERENT_BUDS_TITLE, style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(top = 16.dp)) Text(USE_DIFFERENT_BUDS_TEXT, style = MaterialTheme.typography.bodyMedium) - OutlinedButton(onClick = onUseDifferentBuds) { Text(USE_DIFFERENT_BUDS_TITLE) } + // A93-APP-08 (`ai-sessions/0094`): removing the association is confirmed first — a stray tap no longer forgets the Buds. + var confirming by rememberSaveable { mutableStateOf(false) } + OutlinedButton(onClick = { confirming = true }) { Text(USE_DIFFERENT_BUDS_TITLE) } + if (confirming) { + AlertDialog( + onDismissRequest = { confirming = false }, + confirmButton = { + TextButton(onClick = { + confirming = false + onUseDifferentBuds() + }) { Text(USE_DIFFERENT_BUDS_CONFIRM) } + }, + dismissButton = { TextButton(onClick = { confirming = false }) { Text("Cancel") } }, + title = { Text("$USE_DIFFERENT_BUDS_TITLE?") }, + text = { Text(USE_DIFFERENT_BUDS_TEXT) }, + ) + } } } } internal const val USE_DIFFERENT_BUDS_TITLE: String = "Use different Buds" +/** The dialog's confirm button (`ai-sessions/0094`, A93-APP-08). */ +internal const val USE_DIFFERENT_BUDS_CONFIRM: String = "Forget these Buds" + /** * `ai-sessions/0074`: the "Case sounds" card — ADR-054's labels, chosen by the maintainer in chat 2026-10-06: 28 = "Earbuds replaced" (the official settings * list's wording; its switch reads "Bud return", PROTOCOL.md §4.5.8), 27 = "Other alerts"; no note under the switches. On gear → Settings since diff --git a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/SettingsUi.kt b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/SettingsUi.kt index 2915632d..fe1eb665 100644 --- a/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/SettingsUi.kt +++ b/android/ui/src/main/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/SettingsUi.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.selection.toggleable import androidx.compose.material3.ElevatedCard import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch @@ -32,6 +33,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle @@ -148,8 +150,12 @@ internal fun SettingSwitchRow( enabled: Boolean, onChange: (Boolean) -> Unit, ) { + // A93-APP-07 (`ai-sessions/0094`): the whole row is the switch, so TalkBack reads its title with its state ("Switch, on" had no name). + val toggle = reading?.let { + Modifier.toggleable(value = it.value, enabled = enabled, role = Role.Switch, onValueChange = onChange) + } ?: Modifier Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), + modifier = Modifier.fillMaxWidth().then(toggle).padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { @@ -160,7 +166,7 @@ internal fun SettingSwitchRow( if (reading == null) { NotReadValue() } else { - Switch(checked = reading.value, onCheckedChange = { onChange(it) }, enabled = enabled) + Switch(checked = reading.value, onCheckedChange = null, enabled = enabled) } } } diff --git a/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/AncScreenTest.kt b/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/AncScreenTest.kt index 37ba9d73..b2af42a8 100644 --- a/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/AncScreenTest.kt +++ b/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/AncScreenTest.kt @@ -130,14 +130,14 @@ class AncScreenTest { fun `a mode reported before this connection opened marks the (i) and says so`() { show(sessionSince = at + 5_000) // a new Connect five seconds after the last report compose.onNodeWithContentDescription("Noise control: $DETAILS_NOT_CURRENT_DESCRIPTION").performClick() - compose.onNodeWithText("ANC mode: OFF — from the last connection (updated ${formatUpdatedAt(at)})").assertExists() + compose.onNodeWithText("ANC mode: Off — from the last connection (updated ${formatUpdatedAt(at)})").assertExists() } @Test fun `after Disconnect the last mode stays, marked as from the last connection`() { show(connectionState = ConnectionState.Disconnected) compose.onNodeWithContentDescription("Noise control: $DETAILS_NOT_CURRENT_DESCRIPTION").performClick() - compose.onNodeWithText("ANC mode: OFF — from the last connection (updated ${formatUpdatedAt(at)})").assertExists() + compose.onNodeWithText("ANC mode: Off — from the last connection (updated ${formatUpdatedAt(at)})").assertExists() } // ---- `ai-sessions/0082` item 1: "Changed by the Buds" in the (i), only for a Notify the app did not provoke ---- @@ -147,14 +147,14 @@ class AncScreenTest { show(cause = AncModeCause.CHANGED_BY_BUDS) compose.onNodeWithContentDescription("Noise control: $DETAILS_DESCRIPTION").performClick() // the (i) is plain: the mode is current compose.onNodeWithText("Changed by the Buds at ${formatUpdatedAt(at)}: the Buds reported a new mode — a press-and-hold on a bud, or a change they made themselves.").assertExists() - compose.onNodeWithText("ANC mode: OFF (updated ${formatUpdatedAt(at)})").assertExists() + compose.onNodeWithText("ANC mode: Off (updated ${formatUpdatedAt(at)})").assertExists() } @Test fun `a mode read from the Buds has no changed-by-the-Buds line`() { show(cause = AncModeCause.READ) compose.onNodeWithContentDescription("Noise control: $DETAILS_DESCRIPTION").performClick() - compose.onNodeWithText("ANC mode: OFF (updated ${formatUpdatedAt(at)})").assertExists() + compose.onNodeWithText("ANC mode: Off (updated ${formatUpdatedAt(at)})").assertExists() compose.onNodeWithText("Changed by the Buds", substring = true).assertDoesNotExist() } @@ -162,7 +162,7 @@ class AncScreenTest { fun `a mode set by this app has no changed-by-the-Buds line`() { show(cause = AncModeCause.SET_BY_APP) compose.onNodeWithContentDescription("Noise control: $DETAILS_DESCRIPTION").performClick() - compose.onNodeWithText("ANC mode: OFF (updated ${formatUpdatedAt(at)})").assertExists() + compose.onNodeWithText("ANC mode: Off (updated ${formatUpdatedAt(at)})").assertExists() compose.onNodeWithText("Changed by the Buds", substring = true).assertDoesNotExist() } @@ -170,7 +170,7 @@ class AncScreenTest { fun `the changed-by-the-Buds line leaves the time out when there is none, and needs a mode`() { assertEquals("Changed by the Buds: the Buds reported a new mode — a press-and-hold on a bud, or a change they made themselves.", ancChangedByBudsLine(null)) assertEquals( - listOf("ANC mode: unknown", "Connection: Ready", ANC_STREAM_HINT_TEXT), + listOf("ANC mode: unknown", "Connection: connected", ANC_STREAM_HINT_TEXT), ancDetailLines(ConnectionState.Ready, null, null, notAllowed = false, checkedAt = null, cause = AncModeCause.CHANGED_BY_BUDS), ) } @@ -198,7 +198,7 @@ class AncScreenTest { @Test fun `the mode line of a current mode is unchanged`() { - assertEquals("ANC mode: OFF (updated ${formatUpdatedAt(at)})", ancModeLine(AncMode.OFF, at)) + assertEquals("ANC mode: Off (updated ${formatUpdatedAt(at)})", ancModeLine(AncMode.OFF, at)) assertEquals("ANC mode: unknown", ancModeLine(null, null, fromLastConnection = true)) } @@ -214,7 +214,7 @@ class AncScreenTest { compose.onNodeWithContentDescription("Noise control: $DETAILS_DESCRIPTION").performClick() compose.onNodeWithText(expected).assertExists() assertEquals( - listOf("ANC mode: OFF (updated $time)", expected, "Connection: Ready", ANC_STREAM_HINT_TEXT), + listOf("ANC mode: Off (updated $time)", expected, "Connection: connected", ANC_STREAM_HINT_TEXT), ancDetailLines(ConnectionState.Ready, AncMode.OFF, at, notAllowed = false, checkedAt = null, cause = AncModeCause.CHANGED_BY_BUDS), ) } @@ -234,7 +234,7 @@ class AncScreenTest { compose.onNodeWithText(expected).assertExists() compose.onNodeWithText("Changed by the Buds", substring = true).assertDoesNotExist() assertEquals( - listOf("ANC mode: OFF (updated $time)", expected, "Connection: Ready", ANC_STREAM_HINT_TEXT), + listOf("ANC mode: Off (updated $time)", expected, "Connection: connected", ANC_STREAM_HINT_TEXT), ancDetailLines(ConnectionState.Ready, AncMode.OFF, at, notAllowed = false, checkedAt = null, cause = AncModeCause.CHANGED_SINCE_LAST_READ), ) } diff --git a/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/ControlsScreenTest.kt b/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/ControlsScreenTest.kt index d0ef4218..91eba0ac 100644 --- a/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/ControlsScreenTest.kt +++ b/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/ControlsScreenTest.kt @@ -105,7 +105,7 @@ class ControlsScreenTest { compose.onNodeWithContentDescription("Multipoint: $DETAILS_DESCRIPTION").performScrollTo().performClick() compose.onNodeWithText("Multipoint: changed ${formatUpdatedAt(at)}").assertExists() compose.onNodeWithText("Close").performClick() - compose.onNode(isToggleable() and hasAnySibling(hasText(MULTIPOINT_LABEL))).performScrollTo().assertIsOn().performClick() + compose.onNode(isToggleable() and hasText(MULTIPOINT_LABEL)).performScrollTo().assertIsOn().performClick() assertEquals("one call with the other value; the switch itself follows only the Buds' answer", listOf("multipoint=false"), taps) } @@ -115,7 +115,7 @@ class ControlsScreenTest { compose.onNodeWithContentDescription("Head gestures: $DETAILS_DESCRIPTION").performScrollTo().performClick() compose.onNodeWithText("Use head gestures: read ${formatUpdatedAt(at)}").assertExists() compose.onNodeWithText("Close").performClick() - compose.onNode(isToggleable() and hasAnySibling(hasText(HEAD_GESTURES_LABEL))).performScrollTo().assertIsOff().performClick() + compose.onNode(isToggleable() and hasText(HEAD_GESTURES_LABEL)).performScrollTo().assertIsOff().performClick() assertEquals(listOf("headGestures=true"), taps) } @@ -127,7 +127,7 @@ class ControlsScreenTest { compose.onNodeWithText("Close").performClick() compose.onNodeWithText(CONVERSATION_DETECTION_SUBTITLE).performScrollTo().assertExists() compose.onNodeWithText("Switch from noise cancellation to transparency when you talk").assertExists() // the literal `0052` wording - compose.onNode(isToggleable() and hasAnySibling(hasText(CONVERSATION_DETECTION_LABEL))).performScrollTo().assertIsOn().performClick() + compose.onNode(isToggleable() and hasText(CONVERSATION_DETECTION_LABEL)).performScrollTo().assertIsOn().performClick() assertEquals(listOf("conversationDetection=false"), taps) // Order on the tab (by position): In-ear detection, then Conversation detection, then Multipoint; no Case sounds card any more. val top = { title: String -> compose.onNodeWithContentDescription("$title: $DETAILS_DESCRIPTION").performScrollTo().getBoundsInRoot().top } @@ -177,14 +177,14 @@ class ControlsScreenTest { show(ConnectionState.Ready, read.copy(multipoint = null)) compose.onNode(notReadDash and hasAnySibling(hasText(MULTIPOINT_LABEL))).performScrollTo().assertExists() compose.onAllNodes(notReadDash).assertCountEquals(1) - compose.onAllNodes(isToggleable() and hasAnySibling(hasText(MULTIPOINT_LABEL))).assertCountEquals(0) + compose.onAllNodes(isToggleable() and hasText(MULTIPOINT_LABEL)).assertCountEquals(0) } @Test fun `not connected with the last connection's values - switches stay switches, no dash`() { show(ConnectionState.Disconnected, read) compose.onAllNodes(notReadDash).assertCountEquals(0) - compose.onNode(isToggleable() and hasAnySibling(hasText(MULTIPOINT_LABEL))).performScrollTo().assertIsNotEnabled() + compose.onNode(isToggleable() and hasText(MULTIPOINT_LABEL)).performScrollTo().assertIsNotEnabled() } @Test diff --git a/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/EqScreenTest.kt b/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/EqScreenTest.kt index 539dbc8e..2f43c97c 100644 --- a/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/EqScreenTest.kt +++ b/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/EqScreenTest.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsOn import androidx.compose.ui.test.hasAnySibling +import androidx.compose.ui.test.hasContentDescriptionExactly import androidx.compose.ui.test.hasText import androidx.compose.ui.test.isToggleable import androidx.compose.ui.test.assertIsEnabled @@ -102,7 +103,7 @@ class EqScreenTest { val sliders = compose.onAllNodes(eqSlider) sliders.assertCountEquals(5) for (i in 0 until 5) sliders[i].assertIsNotEnabled() - compose.onNodeWithText(EqPreset.entries.first().name.replace('_', ' ')).assertIsEnabled() + compose.onNodeWithText(presetLabel(EqPreset.entries.first())).assertIsEnabled() } @Test @@ -113,6 +114,21 @@ class EqScreenTest { for (i in 0 until 5) sliders[i].assertIsEnabled() } + @Test + fun `A93-APP-07 - each band slider is named by its band and says its gain in dB, or not read`() { + show(gains = EqBandGains(upperTreble = 0f, treble = 0f, mid = 0f, bass = 3.0f, lowBass = 5.0f)) + val bass = compose.onNode(eqSlider and hasContentDescriptionExactly("Bass")).fetchSemanticsNode() + assertEquals("3.0 dB", bass.config.getOrNull(SemanticsProperties.StateDescription)) + compose.onAllNodes(eqSlider and hasContentDescriptionExactly("Low bass")).assertCountEquals(1) + } + + @Test + fun `A93-APP-07 - an unread band slider says not read`() { + show(gains = null) + val mid = compose.onNode(eqSlider and hasContentDescriptionExactly("Mid")).fetchSemanticsNode() + assertEquals(NOT_READ_DESCRIPTION, mid.config.getOrNull(SemanticsProperties.StateDescription)) + } + // ---- `ai-sessions/0069` A68-APP-08 (visible text only) and A68-APP-16 ---- @Test @@ -160,7 +176,7 @@ class EqScreenTest { fun `an unread switch on Sound shows the dash with the screen-reader text instead of a switch`() { show(gains = EqBandGains.FLAT) // Volume EQ not read compose.onNode(notReadDash and hasAnySibling(hasText(VOLUME_EQ_LABEL))).assertExists() - compose.onAllNodes(isToggleable() and hasAnySibling(hasText(VOLUME_EQ_LABEL))).assertCountEquals(0) + compose.onAllNodes(isToggleable() and hasText(VOLUME_EQ_LABEL)).assertCountEquals(0) } @Test @@ -187,7 +203,7 @@ class EqScreenTest { EqScreen(ConnectionState.Ready, EqBandGains.FLAT, null, null, {}, { chosen += it }, {}) } } - compose.onNodeWithText("FLAT").performScrollTo().performClick() + compose.onNodeWithText("Flat").performScrollTo().performClick() assertEquals(listOf(EqPreset.FLAT), chosen) } @@ -217,7 +233,7 @@ class EqScreenTest { compose.onNodeWithContentDescription("Equalizer: $DETAILS_DESCRIPTION").performClick() compose.onNodeWithText("Volume EQ: changed ${formatUpdatedAt(at)}").assertExists() compose.onNodeWithText("Close").performClick() - compose.onNode(isToggleable() and hasAnySibling(hasText(VOLUME_EQ_LABEL))).performScrollTo().assertIsOn().performClick() + compose.onNode(isToggleable() and hasText(VOLUME_EQ_LABEL)).performScrollTo().assertIsOn().performClick() assertEquals(listOf(false), volumeEqTaps) } diff --git a/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/SettingsMenuTest.kt b/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/SettingsMenuTest.kt index 562b73ef..a6038bb2 100644 --- a/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/SettingsMenuTest.kt +++ b/android/ui/src/test/kotlin/io/github/tedsluis/opencontrolpixelbuds/ui/SettingsMenuTest.kt @@ -356,14 +356,34 @@ class SettingsMenuTest { compose.onNodeWithText("Other alerts: changed ${formatUpdatedAt(at)}").assertExists() compose.onNodeWithText("These settings live on the case and are read when the app connects.").assertExists() compose.onNodeWithText("Close").performClick() - // Both switches share the card with both labels (siblings): layout order, Earbuds replaced first, then Other alerts. - val switches = compose.onAllNodes(isToggleable() and hasAnySibling(hasText(CASE_SOUND_EARBUDS_REPLACED_LABEL)) and hasAnySibling(hasText(CASE_SOUND_OTHER_ALERTS_LABEL))) + // Each row is its switch, with its label (A93-APP-07, `ai-sessions/0094`): layout order, Earbuds replaced first, then Other alerts. + val switches = compose.onAllNodes(isToggleable() and (hasText(CASE_SOUND_EARBUDS_REPLACED_LABEL) or hasText(CASE_SOUND_OTHER_ALERTS_LABEL))) switches.assertCountEquals(2) switches[0].performScrollTo().assertIsOn().performClick() switches[1].performScrollTo().assertIsOff().performClick() assertEquals(listOf("earbudsReplaced=false", "otherAlerts=true"), taps) } + @Test + fun `A93-APP-08 - Use different Buds asks first - Cancel forgets nothing, the confirm button forgets`() { + var forgotten = 0 + compose.setContent { + OpenControlTheme(darkTheme = false) { + SettingsMenuScreen(DarkMode.SYSTEM, {}, false, {}, emptyList(), {}, AppBuildInfo.UNKNOWN, null, onUseDifferentBuds = { forgotten++ }) + } + } + val button = hasText(USE_DIFFERENT_BUDS_TITLE) and hasClickAction() + compose.onNode(button).performScrollTo().performClick() + compose.onNodeWithText(USE_DIFFERENT_BUDS_CONFIRM).assertExists() + compose.onNodeWithText("Cancel").performClick() + assertEquals(0, forgotten) + compose.onNodeWithText(USE_DIFFERENT_BUDS_CONFIRM).assertDoesNotExist() + + compose.onNode(button).performScrollTo().performClick() + compose.onNodeWithText(USE_DIFFERENT_BUDS_CONFIRM).performClick() + assertEquals(1, forgotten) + } + @Test fun `the Case sounds card not connected keeps the last values dimmed and marked, and unread values are dashes`() { compose.setContent { @@ -374,7 +394,7 @@ class SettingsMenuTest { compose.onNodeWithContentDescription("Case sounds: $DETAILS_NOT_CURRENT_DESCRIPTION").performScrollTo().performClick() compose.onNodeWithText(FROM_LAST_CONNECTION_DETAIL).assertExists() compose.onNodeWithText("Close").performClick() - compose.onAllNodes(isToggleable() and hasAnySibling(hasText(CASE_SOUND_EARBUDS_REPLACED_LABEL))).assertCountEquals(2) + compose.onAllNodes(isToggleable() and (hasText(CASE_SOUND_EARBUDS_REPLACED_LABEL) or hasText(CASE_SOUND_OTHER_ALERTS_LABEL))).assertCountEquals(2) } @Test diff --git a/captures/CAP-001-2026-08-09_08-51-00_08-52-20-Group_Z/CAP-001-EVENT-NOTES.md b/captures/CAP-001-2026-08-09_08-51-00_08-52-20-Group_Z/CAP-001-EVENT-NOTES.md index 7dddcc28..f86b973c 100644 --- a/captures/CAP-001-2026-08-09_08-51-00_08-52-20-Group_Z/CAP-001-EVENT-NOTES.md +++ b/captures/CAP-001-2026-08-09_08-51-00_08-52-20-Group_Z/CAP-001-EVENT-NOTES.md @@ -37,15 +37,15 @@ comparable to log frame timestamps without offset correction. | 08:50:57 | Start video recording. Screen already shows buds recognized (Left 100%, Case 63%, Right 100%), `Connect`/`Forget` both enabled | — | — | A BLE link to the peer already exists from 08:50:36.27 (frame 290, `LE Enhanced Connection Complete`) — i.e. some association predates this recording | | 08:51:02–03 | Tap **Forget** | User (App) | — | No immediately visible log effect; a link key was still available and reused at 08:51:12 (frame 911) — see `CAP-001-FINDINGS.md` §6 open question | | 08:51:06–11 | Case lid opened; buds visible, case LED lit | User (Hardware) | `CASE-003` | 1st `Create Connection` (08:51:01.98, frame 732) fails with **Page Timeout** (frame 737, status `0x04`) — consistent with the peer not yet reachable until the lid was fully open | -| 08:51:11 | System notification card appears: "Pixel Buds Pro 2 van Ted / Left 100% Case 62% Right 100%" | App (Auto) | — | — | | 08:51:07–09 | 2nd `Create Connection` attempt | — | — | Succeeds (frame 775, 08:51:09.33, status `0x00`) then is torn down ~0.24s later (`Disconnect Complete`, frame 831, 08:51:09.56) | +| 08:51:11 | System notification card appears: "Pixel Buds Pro 2 van Ted / Left 100% Case 62% Right 100%" | App (Auto) | — | — | | 08:51:14–15 | App briefly shows **"Problem connecting. Turn device off & back on"** | App (Auto) | — | Coincides exactly with the connect-then-disconnect cycle above | | 08:51:09–12 | 3rd `Create Connection` attempt — this one persists | — | `PAIR-001` | Succeeds (frame 855, 08:51:12.10, status `0x00`); Link Key Request/Reply → Authentication Complete → Encryption Change all complete by 08:51:12.21 (frames 911–917), using a **stored** link key (no fresh pairing dialog) | | 08:51:12–15 | (not directly visible on screen — app UI mid-transition) | — | `PAIR-001` | RFCOMM channels 0, 1, 2, 4, 5 opened (frames 984–1070, 1334); SDP queries for Audio Sink/AVRCP/HFP/A2DP (frames 902–1231); full HFP AT-command handshake incl. `AT+BIEV=2,100` battery report (frames 1236–1310) — see `CAP-001-FINDINGS.md` §3 | | 08:51:16 | Hand touches left bud in case | User (Hardware) | — | — | | 08:51:17 | App shows **Disconnect** button, ANC row still greyed out, "Active" label under Case | App (Auto) | — | — | | 08:51:20–21 | Left earbud removed from case (held in hand) | User (Hardware) | `CASE-004` | — | -| 08:51:23 | Left ring shows not-charging state ("0" instead of charging bolt) | App (Auto) | `INEAR-001`(partial) | — | +| 08:51:23 | Left ring shows not-charging state ("0" instead of charging bolt) — app display following the `CASE-004` removal above | App (Auto) | — | — | | 08:51:32 | Tap **Transparency** (1st) — ANC row now fully active | User (App) | `ANC-004` | No confidently attributable command frame identified — see `CAP-001-FINDINGS.md` §5 | | 08:51:36 | Right earbud removed from case | User (Hardware) | `CASE-005` | — | | 08:51:39 | Tap **Off** | User (App) | `ANC-001` | See `CAP-001-FINDINGS.md` §5 | @@ -65,8 +65,8 @@ comparable to log frame timestamps without offset correction. attempts, including one Page Timeout failure and one connect-then-immediately-disconnect cycle, with a corresponding transient "Problem connecting" warning shown in the app around 08:51:14–15. The original draft only listed "08:51:17 Connected" with no indication of this. -- The right-bud-removal and left-bud-removal timestamps were adjusted slightly (08:51:20/36 - instead of 08:51:24/36) based on the actual video frames showing hands physically removing +- The left-bud-removal timestamp was adjusted to 08:51:20 (from 08:51:24); the right-bud removal + stays at 08:51:36 — both based on the actual video frames showing hands physically removing each bud, rather than the resulting battery-icon state change (which lags the physical action by 1–3s). - Added explicit `CAP-001-btsnoop_hci.log` frame references for every event where log evidence exists, diff --git a/captures/CAP-001-2026-08-09_08-51-00_08-52-20-Group_Z/CAP-001-FINDINGS.md b/captures/CAP-001-2026-08-09_08-51-00_08-52-20-Group_Z/CAP-001-FINDINGS.md index aaa15304..1e48f4bd 100644 --- a/captures/CAP-001-2026-08-09_08-51-00_08-52-20-Group_Z/CAP-001-FINDINGS.md +++ b/captures/CAP-001-2026-08-09_08-51-00_08-52-20-Group_Z/CAP-001-FINDINGS.md @@ -79,8 +79,8 @@ throughout, single L2CAP connection carrying the whole multiplexer session. > part replicates), but the *channel-2* traffic in that session was independently identified — > and spec-verified against Google's actual Fast Pair Message Stream / Device Information > documentation — as the **Fast Pair Message Stream, Device Information group (`0x03`)**, not -> AVRCP. This document's own §2 already correctly flagged channel 2/DLCI 0x04 here (this -> session) as unresolved (🔴 OPEN QUESTION, not attributed to AVRCP) — the AVRCP speculation was +> AVRCP. This document's own §2 flagged channel 2/DLCI 0x04 here (this session) as unresolved at +> the time (🔴 OPEN QUESTION, not attributed to AVRCP; 🟢 since 2026-08-12, §5) — the AVRCP speculation was > specifically about *channel 1*, which remains genuinely unresolved as of this correction; only > the *channel-2* content has since been identified, in the other capture. Per > `PROJECT_RULES.md` §3, this note supersedes the *channel 1 = possibly AVRCP* framing above @@ -90,7 +90,7 @@ throughout, single L2CAP connection carrying the whole multiplexer session. > > **Reusable note for all future capture analysis — RFCOMM channel numbers are not stable > per-profile labels.** RFCOMM server channel numbers are negotiated per-connection, not fixed -> per Bluetooth profile. `CAP-001` and `CAP-002-CAP-002` — two independent sessions against the same +> per Bluetooth profile. `CAP-001` and `CAP-002` — two independent sessions against the same > physical device — assigned *different* channel numbers to structurally similar traffic (this > capture put HFP-adjacent Device-Information-group traffic on what was locally numbered > "channel 2"; `CAP-002` also happened to land it on "channel 2", but `CAP-002`'s HFP itself came @@ -105,7 +105,7 @@ throughout, single L2CAP connection carrying the whole multiplexer session. > which — since it's the same shared buffer — includes this capture's own frames plus many more > hours) on the `0x7e` delimiter byte. Every one of the resulting sub-frames starts with a > consistent 3-byte micro-header: **byte 0 ∈ {`0x00`, `0x80`}, byte 1 ∈ {`0x3b`, `0x4b`, `0xa5`, -> `0xa3`}, byte 2 = `0x03` always** (854 sub-frames checked across both files' full logs, zero +> `0xa3`}, byte 2 = `0x03` always** (400 sub-frames checked in `CAP-002-btsnoop_hci.log`, whose DLCI 0x02 payloads include this capture's 160 — `tshark -Y 'btrfcomm.dlci==0x02 && btrfcomm.len>0'`, split on `0x7e`, unescaped *(corrected 2026-10-15, `ai-sessions/0094`)*; zero > exceptions to byte 2 being `0x03`). `{00,3b}`/`{00,4b}` only ever appear in "Sent" (phone→Buds) > frames; `{00,a5}`/`{80,a3}` only ever appear in "Rcvd" (Buds→phone) frames — direction-correlated, > not random. Two payload shapes follow this header: @@ -153,7 +153,7 @@ throughout, single L2CAP connection carrying the whole multiplexer session. > empirically against every RFCOMM payload on this DLCI in `CAP-001`, `CAP-002`, and `CAP-003` > (`CAP-004` never opens it) — HDLC-unescaping each `0x7E`-delimited sub-frame (`0x7D ` → `X XOR > 0x20`, standard byte-stuffing) and computing a CRC-32 (IEEE 802.3/zlib polynomial, little-endian) -> over everything but the trailing 4 bytes — gives a **640/640 (100%) match** against those +> over everything but the trailing 4 bytes — gives a **640/640 (100%) match** (160 `CAP-001` + 400 `CAP-002` + 80 `CAP-003` sub-frames; `CAP-002`'s log contains `CAP-001`'s 160, so 480 distinct sub-frames *(corrected 2026-10-15, `ai-sessions/0094`)*) against those > trailing 4 bytes, with zero exceptions: > ``` > CAP-001 frame 1348, raw (between flags): 004b0310151dea71de7d5e25e3a5ec28f96761b5 @@ -204,7 +204,7 @@ AT+VGM=7 / AT+VGS=8 → OK (mic/speaker gain) AT+NREC=0 → +CME ERROR: 4 (not supported) AT+COPS=3,0 / AT+CMEE=1 → OK AT+BIEV=2,100 → OK ← repeats at 08:51:14.106, 20.070, 20.136, 34.392, 34.860, - 41.410, 08:52:04.148, 08:52:04.438 — roughly every 6–7s (last two corrected 2026-10-08, `ai-sessions/0081`) + 41.410, 08:52:04.148, 08:52:04.438 — irregular gaps of 0.07–22.7 s (frames 1303, 1558, 1572, 1949, 1968, 2027, 2245, 2268), not a fixed cadence; see CAP-009-FINDINGS.md §2 *(corrected 2026-10-15, `ai-sessions/0094`)* ``` (Full frame list: 1236–1310, 1558–1574, 1949–1969, 2027–2028, 2245–2246, 2268–2269.) @@ -221,8 +221,9 @@ mechanisms on this device, but they disagree — worth a dedicated follow-up cap real battery-level change to see which one (if either) tracks it accurately, and whether `battchg` is a stale/init-time-only value. -Neither HFP indicator distinguishes Left/Right/Case — both appear to report a single aggregate -value. The BLE Fast Pair Battery Notification advertisement (`PROTOCOL.md` §4.3 Option A, which +This session cannot tell which bud or the case either HFP indicator reports (all values were 100% +here); `CAP-009-FINDINGS.md` §3 later showed that `AT+BIEV=2` tracks the Right earbud and §1 that +`battchg` is a one-time snapshot at SLC setup *(corrected 2026-10-15, `ai-sessions/0094`)*. The BLE Fast Pair Battery Notification advertisement (`PROTOCOL.md` §4.3 Option A, which *does* have separate L/R/Case fields) was not captured in this RFCOMM-focused pass — no `btle`-filtered advertising data was extracted from this log in this session. @@ -231,9 +232,9 @@ value. The BLE Fast Pair Battery Notification advertisement (`PROTOCOL.md` §4.3 | Video (wall-clock) | On-screen event | Correlated log evidence | |---|---|---| | 08:51:02–03 | Tap **Forget** | No log-visible effect before 08:51:12 (see §6 — a BLE link and cached link key both still existed afterward) | -| 08:51:06–11 | Case lid opened, buds visible with case LED lit | 1st `Create Connection` at 08:51:01.98 fails with Page Timeout (frame 733/737) — consistent with the case/buds not yet reachable until the lid was fully open | +| 08:51:06–11 | Case lid opened, buds visible with case LED lit | 1st `Create Connection` at 08:51:01.98 fails with Page Timeout (frames 732 → 737) — consistent with the case/buds not yet reachable until the lid was fully open | | 08:51:14–15 | App briefly shows **"Problem connecting. Turn device off & back on"** | Coincides with the successful-then-immediately-torn-down 2nd connection (frames 775 connect / 831 disconnect, 08:51:09.33–09.56) | -| 08:51:12–15 | (not directly visible on screen) | RFCOMM channels 0, 2, 4, 5 opened (frames 984–1070); SDP queries for Audio Sink/AVRCP/HFP (frames 902–1231); HFP AT handshake (frames 1236–1310) | +| 08:51:12–15 | (not directly visible on screen) | RFCOMM channels 0, 1, 2, 4, 5 opened (frames 984–1070, 1334); SDP queries for Audio Sink/AVRCP/HFP (frames 902–1231); HFP AT handshake (frames 1236–1310) | | 08:51:32 | Tap **Transparency** (1st) | See §5 — no confidently attributable command frame identified | | 08:51:39 | Tap **Off** | See §5 | | 08:51:43 | Tap **Adaptive** | See §5 | @@ -242,7 +243,7 @@ value. The BLE Fast Pair Battery Notification advertisement (`PROTOCOL.md` §4.3 | 08:52:00 | Tap **Off** (2nd) | See §5 | | 08:52:08–13 | Buds placed back in case, lid closed | `Disconnect Complete` at 08:52:08.343 (frame 2302) | -## 5. ANC command attribution attempt — inconclusive (🔴 OPEN QUESTION) +## 5. ANC command attribution — resolved: DLCI 0x04 Message Stream Group `0x08` (🟢 FACT, see "Full resolution" below; the first two taps stay unmatched) For each of the six ANC-mode taps above, the RFCOMM traffic in the surrounding ±3s window was inspected on every open channel. Findings: @@ -272,7 +273,7 @@ capture with genuinely isolated actions (wait ~10s of silence, single ANC tap, w silence, repeat) is needed before promoting any ANC-opcode claim to `PROTOCOL.md`. > **Resolution (2026-08-12), deskresearch task — the `e8e8XX` open question closes to a 🟢 FACT -> characterization, but NOT as an ANC opcode.** First, a channel-label correction: this bullet +> characterization.** (Its "not an ANC opcode" reading is superseded by the "Full resolution" below: the one-hot byte is the ANC mode.) First, a channel-label correction: this bullet > says "Channel 4/DLCI 0x08" for the `e8e8` pattern, but §2's own table above (and this capture's > raw frames) place it on **Channel 2/DLCI 0x04** — §2's table was correct, this bullet's channel > label was not; flagged here rather than silently fixed. Second, and substantively: `CAP-002`'s @@ -304,7 +305,7 @@ silence, repeat) is needed before promoting any ANC-opcode claim to `PROTOCOL.md > semantic meaning of the bitmask remains 🔴 OPEN QUESTION, narrower and better-defined than the > original "ANC-mode byte?" framing this bullet started from. This capture's own six ANC taps > happened not to correlate with distinct `e8e8XX` values in any consistent way, which is now -> understood as expected — this bitmask answers a different question than ANC state entirely. +> explained by the "Full resolution" below: the bitmask is the ANC state, and the first two taps produced no `0x12` frame. > **Full resolution (2026-08-12), deskresearch task — this is not just "ANC-relevant," it is the > confirmed ANC set/get/notify command channel, byte-for-byte, spec- and capture-verified.** First @@ -382,9 +383,10 @@ silence, repeat) is needed before promoting any ANC-opcode claim to `PROTOCOL.md SSP handshake, not a reused key) across a combined seven confirming instances. Full evidence chain: `CAP-013-FINDINGS.md` §0, `CAP-031-FINDINGS.md` §0/§2/§7/§8, `CAP-032-FINDINGS.md` §0.3/§1/§2/§5/§7, and `PROTOCOL.md` §6's "Behavior" section. -- Channel 1 (DLCI 0x02) and channel 2 (DLCI 0x04)'s exact protocol identity is still unconfirmed - — candidates are AVRCP and/or A2DP signaling (both present in SDP), but this was not verified - byte-for-byte against either spec in this pass. +- Channel 1 (DLCI 0x02) and channel 2 (DLCI 0x04) are neither AVRCP nor A2DP signaling: DLCI 0x02 + carries Pigweed `pw_hdlc` framing (§2's "Upgrade (2026-08-12)" note, CRC-32 match on every + sub-frame), and DLCI 0x04 is the Fast Pair Message Stream, including the Hearable Controls ANC + exchange (§5's "Full resolution", 🟢 FACT; `PROTOCOL.md` §4.1). - Channel 5 (DLCI 0x0a) opened/closed repeatedly but never carried a payload in this session — worth checking whether it's HFP's audio channel (SCO/eSCO, which wouldn't appear as RFCOMM data) or simply unused here. @@ -402,6 +404,10 @@ silence, repeat) is needed before promoting any ANC-opcode claim to `PROTOCOL.md > any of these four sessions, full stop** — this isn't a timing-correlation question (channel 5 > opening lining up with a SCO event) because there is no SCO event to correlate against at all. > This rules out the SCO/eSCO hypothesis definitively rather than leaving it "worth checking." + > Command (re-run 2026-10-15, `ai-sessions/0094`): `tshark -r CAP-00N-btsnoop_hci.log -Y + > 'bthci_evt.code==0x2c || bthci_evt.code==0x2d || bthci_cmd.opcode==0x0428 || + > bthci_cmd.opcode==0x043d'` → 0 frames, exit 0, for N = 1–4; positive control: the same filter + > on `CAP-008-btsnoop_hci.log` (a phone call) matches 4 frames (1646, 1656, 2274, 2282). > Channel 5/DLCI 0x0a's silence remains 🔴 OPEN QUESTION — genuinely unused in every capture to > date, not explained by an out-of-band audio path. (Unsurprising in hindsight: none of these > four sessions ever place or receive a live phone call, the only scenario that would trigger @@ -410,17 +416,16 @@ silence, repeat) is needed before promoting any ANC-opcode claim to `PROTOCOL.md ## 7. Recommended next steps -1. A properly isolated Group B capture (single ANC action per window, per +1. (Done: `CAP-006`, Group B repeat.) A properly isolated Group B capture (single ANC action per window, per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §4) to re-attempt ANC opcode attribution. 2. A passive BLE scan (Group Q #18) to capture the Fast Pair Battery Notification advertisement independently, to cross-check against the two conflicting HFP battery indicators found here. 3. Correlate channel 1/2 traffic against the AVRCP/A2DP specs directly (byte-level) before spending more capture time on them. ~~Partially done 2026-08-12~~ — see the "Follow-up (2026-08-12)" note under §2's table: channel 1/DLCI 0x02 is now characterized in detail - (HDLC/PPP-*style* flag-delimited framing, not AVRCP, not standard PPP either) but its exact - protocol identity is still unconfirmed; channel 2/DLCI 0x04's `e8e8XX` traffic is now resolved - to a Group `0x08` Message-Stream request/notify/ACK cycle with a one-hot state byte (§5's - "Resolution (2026-08-12)" note) — neither is AVRCP/A2DP, so this item's original premise (they + (Pigweed `pw_hdlc` framing, §2's "Upgrade (2026-08-12)" note — not AVRCP); channel 2/DLCI + 0x04's `e8e8XX` traffic is the Hearable Controls Group `0x08` Get/Set/Notify ANC state cycle, + `XX` = the one-hot ANC mode (§5's "Full resolution", 🟢 FACT) — neither is AVRCP/A2DP, so this item's original premise (they might be AVRCP/A2DP signaling) is superseded rather than completed as originally framed. --- diff --git a/captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-EVENT-NOTES.md b/captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-EVENT-NOTES.md index ad91af6f..b6a806bc 100644 --- a/captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-EVENT-NOTES.md +++ b/captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-EVENT-NOTES.md @@ -15,7 +15,7 @@ the *event timeline*, `CAP-002-FINDINGS.md` is *what it means for the protocol*. | Date | 2026-08-09 | | Firmware version | release_5.203 | | Test device | Pixel 7a, Android 17 (Official Pixel Buds Companion App v1.0.955078536) | -| Video file | `CAP-002-recording.mp4` — 114.2s, starts 08:04:53(=17:04:53 local), ends 17:06:46 (wall clock, +0200) | +| Video file | `CAP-002-recording.mp4` — 114.2s, starts 17:04:53, ends 17:06:46 (wall clock, +0200) | | Log file | `CAP-002-btsnoop_hci.log` — **note: this is a long-running, non-restarted snoop log spanning 08:50:32–17:10:58 (~8h20m), the same buffer used since `CAP-001`.** 50,468 packets total; this capture's actual window is only the ~150s slice 17:04:35–17:07:05 (1,877 packets), everything else is unrelated background Bluetooth activity accumulated over the day. | **Scope note:** unlike `CAP-001` (a reconnection to an already-bonded device), this session is a @@ -50,7 +50,7 @@ log uses the same wall clock (+0200), directly comparable. | 17:05:33 | Tap **Pair** | User (App) | `PAIR-001` | `Simple Pairing Complete` (17:05:33.608) → `Link Key Notification` (new key stored, 17:05:33.622) → `Authentication Complete` → `Set Connection Encryption` → `Encryption Change` (17:05:33.721) | | 17:05:34–48 | (not directly visible — app UI transitioning) | — | `PAIR-001` | Large SDP/L2CAP service-enumeration burst (Audio Sink, AVRCP, HFP, HID-Control, HID-Interrupt, generic RFCOMM — frames 48710–49215); RFCOMM channels opened: **6 (labeled "Hands-Free" by Wireshark)**, 4, 5, 1, 2 (frames 48892–49238); channel 2 closed then reopened (frames 49470–49527) | | 17:05:53 | Notification banner: **"Pixel Buds Pro 2 van Ted connected — Left 100% Case 57% Right 100%"** | App (Auto) | — | Lags the actual technical connection (complete by ~17:05:48) by ~5–15s — UI/notification delay, not a protocol event | -| 17:05:51–17:06:03 | **"Save device to ted.sluis@gmail.com..."** dialog shown (Fast Pair account-linking prompt) | App (Auto) | `GFPS-001` | No further RFCOMM activity during this window — this step appears to be cloud/GMS-side, not locally observable | +| 17:05:51–17:06:03 | **"Save device to ted.sluis@gmail.com..."** dialog shown (Fast Pair account-linking prompt) | App (Auto) | — | No further RFCOMM activity during this window — this step appears to be cloud/GMS-side, not locally observable | | 17:06:04 | Tap **Save** | User (App) | `GFPS-002` | No corresponding local Bluetooth traffic — consistent with this being an account-linking (cloud) action | | 17:06:06 | **"Set up device"** prompt appears | App (Auto) | — | — | | 17:06:09 | Tap **Set up** | User (App) | `APP-001` | — | @@ -78,18 +78,18 @@ log uses the same wall clock (+0200), directly comparable. Permission prompt / Click 'Allow'" at 17:06:34 was really this CDM dialog, occurring earlier (tap at 17:06:31) and specifically evidencing CDM usage, which is architecturally significant (`DECISIONS.md` ADR-005). -- Flagged that the log evidence for `GFPS-001`/`GFPS-002` (Save-to-account) is **absent** — +- Flagged that the log evidence for `GFPS-002` (Save-to-account) is **absent** — this is expected if that step is cloud-side, but it means this capture cannot confirm or refute anything about the Fast Pair account-linking protocol itself, only that the local Bluetooth link showed no activity during it. -- Flagged that **no RFCOMM data traffic occurs anywhere after ~17:05:48**, including during the +- Flagged that **no RFCOMM data traffic occurs anywhere after ~17:05:48** (`-Y 'frame.time >= "2026-08-09 17:05:48" && frame.time <= "2026-08-09 17:07:05" && btrfcomm.len>0'` → 0 frames, exit 0; the same filter from 17:05:30 → 213), including during the entire app setup / CDM permission / "Device details" loading sequence (17:06:04–17:06:46) — see `CAP-002-FINDINGS.md` §4 for what this means and possible explanations. ## Traceability check (2026-10-03, `ai-sessions/0069`, `A68-CAP-15`, `AGENTS.md` §13 step 7) - `BATT-004` — **present on the wire, not referenced in the timeline** (see `CAP-002-FINDINGS.md`). -- `APP-001` (one mention) and `GFPS-002` (two) are referenced; `APP-002` is not — **expected, not observed in the notes**. +- `APP-001` (Tap **Set up**, 17:06:09), `APP-002` (Tap **Continue**, 17:06:23) and `GFPS-002` (Tap **Save**, 17:06:04) are referenced in the timeline. --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-EVENT-NOTES.md - https://tedsluis.github.io/opencontrolpixelbudspro2/captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-EVENT-NOTES diff --git a/captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-FINDINGS.md b/captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-FINDINGS.md index 22000dfe..c5cc9ae4 100644 --- a/captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-FINDINGS.md +++ b/captures/CAP-002-2026-08-09_17-04-53_17-06-46-Group_A/CAP-002-FINDINGS.md @@ -24,7 +24,7 @@ buffer) — add a constant offset of `+47984` to translate a frame number cited current file's own numbering (e.g. this document's "frame 1578" = the current file's frame 49562; independently confirmed via two more citation pairs in this same file, `ai-sessions/0012_CROSSCHECK_RESULT_2026_09_12.md` Finding 6).** The rule above holds for frame numbers ≤ 1,877; any larger number in -this file (e.g. 17233, 17610, 48642, 49024, 49136) is already the stored file's own numbering. The stored file's first 2,663 frames are `CAP-001`'s +this file (e.g. 17233, 17610, 48642, 49024, 49136) is already the stored file's own numbering, and so are frames 1004 and 1826 in §3's Task 1/Task 6 lists (08:51:12 / 08:51:26, inside `CAP-001`'s window — exceptions to the ≤ 1,877 rule). The stored file's first 2,663 frames are `CAP-001`'s (`PROTOCOL.md` §2.2a, `DECISIONS.md` ADR-018 2026-09-30 Update). **Video:** `CAP-002-recording.mp4` (114.2s, 17:04:53–17:06:46 local, on-screen wall-clock overlay). **Devices:** phone `Google_7e:ca:81` (Pixel 7a, `E8:D5:2B:7E:CA:81`, BD_ADDR partially visible on-screen in this capture — same phone as `CAP-001`), peer @@ -72,7 +72,7 @@ session over PSM 0x0003, starting immediately after encryption completes. |---|---|---|---|---| | 0 | 0x00 | 908 | Multiplexer control (PN negotiation) | 🟢 FACT | | 6 | 0x0c | 930 / 937, labeled **"Hands-Free"** directly by Wireshark's SDP-driven heuristic | The HFP Service Level Connection handshake (`AT+BRSF=921` … `AT+BIEV=2,100`), frames 48954–49079 of the shared log (17:05:34.541–.759) — see §5 (rewritten 2026-10-03: the earlier "no AT-command byte stream" was a filter artefact) | 🟢 FACT | -| 4 | 0x08 | 933 | Short frames (4–69 bytes) in the initial 17:05:34.5–34.8 burst only; not decoded in this pass | 🔴 OPEN QUESTION | +| 4 | 0x08 | 933 | Short frames (4–69 bytes) in the initial 17:05:34.5–34.8 burst only; decoded in §2a as the private `[Group][Code][Len:2B-BE][Value]` envelope | 🟢 FACT for the envelope (§2a); the protocol's identity/purpose stays 🔴 OPEN QUESTION | | 5 | 0x0a | 978 | No data-carrying frames observed | 🔴 OPEN QUESTION (same as `CAP-001`) | | 1 | 0x02 | 1125 | `0x7e`-delimited HDLC-style frames, same shape as `CAP-001`'s channel-1 traffic | 🟢 FACT (2026-08-12) — confirmed as Pigweed `pw_hdlc` framing (CRC-32 FCS verified 100% match, including this session's own 371 frames); see `CAP-001-FINDINGS.md` §2 "Upgrade (2026-08-12)" and `PROTOCOL.md` §2.2a for the full evidence | | 2 | 0x04 | 1251, closed 17:05:42 (frame 1486), reopened 17:05:47 (frame 1541) | **Fast Pair Message Stream, Device Information group — spec-verified, see §3** | 🟢 FACT (framing + group + two field codes spec-verified 2026-08-10; upgraded from `CAP-001`'s 🔴) | @@ -140,8 +140,9 @@ frame 49028 0302003f08061001220d72656c656173655f352e3230332a0030e60138004a07373 frame 49030 090200020800 Group=0x09 Code=0x02 Len=2 pb: field1=0 ``` -Immediately followed, still within the same burst window (frames 49052–49089), by content **not -seen in `CAP-001` or `CAP-004`'s captured windows at all** — new groups `0x01` and `0x02`: +Immediately followed, still within the same burst window (frames 49052–49089), by groups `0x01` +and `0x02` — the same messages also occur in `CAP-001` (frames 1139–1151) and `CAP-004` (frames +2358–2372), reassembled DLCI 0x08 decode *(corrected 2026-10-15, `ai-sessions/0094`)*: ``` frame 49066 0107000e08807d101818b009200228003001 Group=0x01 Code=0x07 Len=14 pb: field1=16000 field2=24 field3=1200 field4=2 field5=0 field6=1 @@ -327,7 +328,7 @@ pages directly (not a search-summary), specifically > **Follow-up (2026-08-12), source: `CAP-004`'s `CAP-004-FINDINGS.md` §5a.** The correction directly above > already separates this section's DLCI-0x04 content from `CAP-001`'s DLCI-0x08 content. `CAP-004`'s > 2026-08-12 addendum sharpens this further: `CAP-004` frame 2305 and this file's own frame 49028 -> (§2a below) are byte-for-byte identical 67-byte messages, both on DLCI 0x08, both reading +> (§2a above) are byte-for-byte identical 67-byte messages, both on DLCI 0x08, both reading > `Group 0x03 Code 0x02` — the exact same Group/Code label this section uses for its own > spec-verified DLCI-0x04 content. A pure-protobuf reading of that header's first byte is provably > invalid (protobuf field number 0 does not exist), which rules out "coincidence" and confirms DLCI @@ -341,7 +342,7 @@ pages directly (not a search-summary), specifically > below for this file's own copy of the byte-identical frame. > > **Task 3 (2026-08-12): Group `0x07` Code `0x41` = `"in-use"` — upgraded from 🟡 HYPOTHESIS to 🟢 -> FACT.** §3's bullet above (line ~205) found the phrase "Indicate in-use Account Key" on a +> FACT.** §3's Group `0x07` Code `0x41` bullet above found the phrase "Indicate in-use Account Key" on a > *GATT-level* Fast Pair spec page (phrase-match only, kept at 🟡). `CAP-004` `CAP-004-FINDINGS.md` §5 > subsequently found Google's [SASS (Smart Audio Source Switching)](https://developers.google.com/nearby/fast-pair/specifications/extensions/sass) > extension page, which states explicitly that it uses **Message Group `0x07`** and gives a code @@ -383,7 +384,7 @@ pages directly (not a search-summary), specifically > co-occurring BLE address (Code `0x02`)** — twice in this data the address stays identical between > two consecutive occurrences (17610↔18895, 49251↔49538) while the Code `0x0a` value still changes > completely both times, ruling out any static function of the address alone. Consistent with a -> per-message nonce or salt; actual purpose remains 🔴 OPEN QUESTION — Google's own spec table for +> per-message nonce or salt — resolved 2026-09-24 as the Fast Pair MAC extension's session nonce (🟢 FACT, see §3's Code `0x0a` bullet); Google's own spec table for > this Message Group (fetched 2026-08-10, above) does not list code `0x0a` at all. > > **Task 4 (2026-08-12): searched every other known Fast Pair extension page for code `0x0a` under @@ -409,9 +410,11 @@ pages directly (not a search-summary), specifically ## 4. No RFCOMM traffic during app setup — resolved: it moves to BLE/GATT (🟢 FACT) All RFCOMM data-carrying frames in this entire ~150s capture window fall inside two short -bursts: **17:05:34.56–17:05:38.92** (the post-pairing SDP/profile-probing burst) and +bursts: **17:05:34.54–17:05:38.92** (frames 48954–49366, from the first HFP frame on DLCI 0x0c; multiplexer PN frames on DLCI 0x00 start at 17:05:34.443, frame 48900 *(corrected 2026-10-15, `ai-sessions/0094`)*; the post-pairing SDP/profile-probing burst) and **17:05:47.62–17:05:47.90** (channel 2's brief reopen). **Nothing at all** was found on RFCOMM -between 17:05:48 and the end of the sliced window (17:07:05). +between 17:05:48 and the end of the sliced window (17:07:05): `tshark -r CAP-002-btsnoop_hci.log -Y +'frame.time >= "2026-08-09 17:05:48" && frame.time <= "2026-08-09 17:07:05" && btrfcomm.len>0'` → 0 +frames, exit 0; positive control: the same filter for 17:05:30–17:05:48 matches 213 frames (48900–49578). **Update 2026-08-10 — explanation #2 from the previous version of this document is confirmed correct:** the app-setup activity has a clear local Bluetooth footprint, it is simply on the @@ -543,7 +546,7 @@ of the whole 8h20m log) was checked directly, without slicing to this device. ## 8. Recommended next steps -1. **Highest priority:** a capture that includes a **full GATT service discovery** against the +1. (Done: `CAP-034` resolved the handle↔UUID map, see `CAP-034-FINDINGS.md`.) **Highest priority:** a capture that includes a **full GATT service discovery** against the Buds (not a cached-database-hash reconnect) — e.g. by having Android forget and re-discover the device, or by using a generic BLE scanner tool — to resolve handle `0x0f2a` and the `0x0c0X` handle cluster from §4 to real UUIDs. §7 confirms this cannot be recovered from @@ -599,12 +602,12 @@ this document already clears that bar: addendum rules out two candidate explanations (simple counter; simple function of the co-occurring BLE address) but does not resolve the actual meaning — still needs a more complete spec source or its own targeted investigation. -- The `0x0c0X` GATT handle cluster's identity — needs the UUID-resolving capture from §8 item 1 +- ~~The `0x0c0X` GATT handle cluster's identity~~ — **resolved by `CAP-034`: `0x0c00`–`0x0c14` = the Fast Pair Service `0xFE2C` (🟢).** Original note: needs the UUID-resolving capture from §8 item 1 before any claim beyond "structurally resembles"; **the structural resemblance itself was substantially strengthened 2026-08-12** — see `CAP-003` `CAP-003-FINDINGS.md` §4's own 2026-08-12 addendum for exact byte-length validation against the official Key-based Pairing/Passkey spec (16-byte AES blocks, CCCD-gated flow, cross-confirmed in `CAP-002` and `CAP-003`). -- The HFP handshake at a fresh pairing (§5) — 🟢 FACT: it occurs (frames 48954–49079); `PROTOCOL.md` §6's "never recurs" item was ticked 2026-10-03. +- ~~The HFP handshake at a fresh pairing (§5)~~ — no longer "not ready": 🟢 FACT, it occurs (frames 48954–49079); `PROTOCOL.md` §6's "never recurs" item was ticked 2026-10-03. - DLCI 0x08's private Group/Code/Length envelope's *purpose/identity* (§2a) — decodability is now a 🟢 FACT, but whether it's `libmaestro`'s own handshake or an unrelated companion-device negotiation remains 🔴 open. diff --git a/captures/CAP-003-2026-08-10_20-59-16_21-00-37-Group_R/CAP-003-FINDINGS.md b/captures/CAP-003-2026-08-10_20-59-16_21-00-37-Group_R/CAP-003-FINDINGS.md index f72770ac..0308c686 100644 --- a/captures/CAP-003-2026-08-10_20-59-16_21-00-37-Group_R/CAP-003-FINDINGS.md +++ b/captures/CAP-003-2026-08-10_20-59-16_21-00-37-Group_R/CAP-003-FINDINGS.md @@ -46,7 +46,9 @@ checked exhaustively, not assumed: pair (frames 1649, 1657, 20:59:38.38–38.42) — not a characteristic-discovery query. - This was checked against the **entire log**, not just the video-covered window, in case discovery happened outside the recorded portion — same result: zero discovery-opcode traffic - anywhere in the full 302s capture. + anywhere in the full 302s capture. Command: `tshark -r CAP-003-btsnoop_hci.log -Y 'btatt.opcode==0x10 || + btatt.opcode==0x11'` → 0 frames, exit 0 (adding `0x08`/`0x09` → only 1649, 1657); positive control: the + same filter on `CAP-017-btsnoop_hci.log` (a live discovery) → 9 frames, first 678. - **Why:** the "Database Hash" mechanism (a standard BLE GATT Caching feature, Bluetooth Core Spec 5.1+) lets a GATT client skip re-discovery entirely if it already holds a cached copy of the server's database matching the advertised hash. Android's Bluetooth stack evidently still @@ -150,8 +152,8 @@ carrying similarly-shaped data — itself informative: > -T fields -e frame.number -e frame.time -e btatt.opcode -e btatt.handle -e btatt.value > ``` > `CAP-002`: only `0x0f2a` (frames 49423→49425, value `"Revision 6"`). `CAP-003` (this file): only -> `0x0f2a` (1819→1835) and `0x0f28` (eight reads total, four request/response pairs, each pair -> itself two-back-to-back as already noted above — 2506/2508, 2510/2512, 2693/2695, 2697/2699, +> `0x0f2a` (1819→1835) and `0x0f28` (eight request/response pairs in four polling instances +> about 60 s apart, two back-to-back pairs each, as already noted above — 2506/2508, 2510/2512, 2693/2695, 2697/2699, > 2791/2793, 2794/2796, 2801/2803, 2804/2806). `CAP-004`: **zero** reads in this range at all > (expected — no Pixel Buds app ever ran there, so "More settings"/Device details, the screen that > triggers these reads, was never opened). **`0x0f26`, `0x0f27`, and `0x0f29` are never read in any @@ -317,8 +319,8 @@ of the three captures to date. Not claimed as FACT; requires UUID confirmation. - Any UUID for handle `0x0f2a`, `0x0f28`, or the `0x0c0X` cluster — **this session's stated goal, still unresolved.** Needs the stronger cache-busting approach in §7 item 1. **Re-confirmed 2026-08-12: `CAP-004` (a fourth, later session) also failed to trigger live discovery for the - Buds — see `CAP-004` `CAP-004-FINDINGS.md` §6's 2026-08-12 update — so this is now a four-for-four - negative result, not specific to this session's method.** + Buds — see `CAP-004` `CAP-004-FINDINGS.md` §6's 2026-08-12 update — so this is now a three-for-three + negative result (`CAP-002`'s full-day log, which includes `CAP-001`'s window, `CAP-003`, `CAP-004`), not specific to this session's method.** - The Passkey-characteristic hypothesis in §5 — **strengthened 2026-08-12 (§4 Task-5 addendum) to a precise byte-length/flow match against the spec**, but still not UUID-confirmed. - Neighboring handles `0x0f26`/`0x0f27`/`0x0f29` around the `0x0f28`/`0x0f2a` pair — **checked @@ -329,9 +331,10 @@ of the three captures to date. Not claimed as FACT; requires UUID confirmation. yet correlated to any specific Bluetooth log traffic (a `PROTOCOL.md`-relevant gap: is this purely a cloud/GMS-side check, or does it involve a local protocol exchange? Not established here). -- The `libmaestro`/ANC-EQ control channel identity — **still completely unaddressed by any of - `CAP-001`, `CAP-002`, or `CAP-003`**; all three captures so far cover pairing/setup/discovery, - not actual ANC/EQ commands under clean isolation. +- The `libmaestro`/ANC-EQ control channel identity — not addressed by `CAP-001`, `CAP-002`, or + `CAP-003` (pairing/setup/discovery only); resolved since by other captures: ANC is the DLCI 0x04 + Message Stream Hearable Control group (`DECISIONS.md` ADR-009) and DLCI 0x02 is pw_rpc + `maestro_pw.Maestro`, which carries EQ and the other settings (ADR-034). --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/captures/CAP-003-2026-08-10_20-59-16_21-00-37-Group_R/CAP-003-FINDINGS.md - https://tedsluis.github.io/opencontrolpixelbudspro2/captures/CAP-003-2026-08-10_20-59-16_21-00-37-Group_R/CAP-003-FINDINGS diff --git a/captures/CAP-004-2026-08-11_06-22-36_06-25-12-Group_S/CAP-004-EVENT-NOTES.md b/captures/CAP-004-2026-08-11_06-22-36_06-25-12-Group_S/CAP-004-EVENT-NOTES.md index acbe8ac7..9306fdae 100644 --- a/captures/CAP-004-2026-08-11_06-22-36_06-25-12-Group_S/CAP-004-EVENT-NOTES.md +++ b/captures/CAP-004-2026-08-11_06-22-36_06-25-12-Group_S/CAP-004-EVENT-NOTES.md @@ -44,15 +44,14 @@ third-party app UI at all — confirming the Pixel Buds app truly never appears | ~06:23:00 | Tap **CONNECT** on the Buds entry | User (App) | — | `LE Extended Create Connection` at 06:23:01.558 (frame 1497) | | 06:23:01 | nRF Connect shows `CONNECTED` / `NOT BONDED`; `CLIENT` GATT tab lists 9 primary services (Generic Attribute, Generic Access, Broadcast Audio Scan Service, Audio Stream Control Service, Published Audio Capabilities Service, Volume Control, Microphone Control, Audio Input Control, Common Audio Service) — **not from live discovery, see `CAP-004-FINDINGS.md` §1** | App (Auto) | `PAIR-001`(BLE) | `Sent Read By Type Request, Database Hash` (frame 1524) — cached-database check, not fresh discovery | | 06:23:01 | System notification: **"Enable Google Play services — Google Play services must be enabled for..."** persists on screen from here through the end of the video | OS (Auto) | — | Confirms GMS is genuinely disabled/non-functional for this flow — matches the test setup | -| 06:23:55–56 | User scrolls the service list further: `Telephony and Media Audio Service`, **`Google Fast Pair Service (0xFE2C)`**, **`Accessory Non-Owner Service (15190001-12f4-c226-88ed-2ac5579f2a85)`** (corrected 2026-08-28 — | — | — | — | -originally transcribed `13190001-…`; see `CAP-004-FINDINGS.md` §6's correction note), `Device Information (0x180A)`, `Battery Service (0x180F)`, and an unnamed **`Unknown Service (109b8b21-50e3-45cc-8ea1-ac62de4846d1)`** | User (App) | — | Same caveat as above — this list is UI evidence of the cached GATT database, not of live discovery in this session | +| 06:23:35–36 | User scrolls the service list further: `Telephony and Media Audio Service`, **`Google Fast Pair Service (0xFE2C)`**, **`Accessory Non-Owner Service (15190001-12f4-c226-88ed-2ac5579f2a85)`** (corrected 2026-08-28 — originally transcribed `13190001-…`; see `CAP-004-FINDINGS.md` §6's correction note), `Device Information (0x180A)`, `Battery Service (0x180F)`, and an unnamed **`Unknown Service (109b862f-50e3-45cc-8ea1-ac62de4846d1)`** (corrected 2026-10-15 from `109b8b21-…`, film frame 06:23:36) | User (App) | — | Same caveat as above — this list is UI evidence of the cached GATT database, not of live discovery in this session | | 06:24:01 | Open Android Bluetooth settings from the notification shade | User (App) | — | — | | 06:24:06 | System settings → **"Pair new device"**, classic inquiry starts | User (App) | — | — | | ~06:24:07 | "Pixel Buds Pro 2 van Ted" appears in the classic device list | App (Auto) | — | — | | 06:24:24 | Tap **"Pixel Buds Pro 2 van Ted"** in the list | User (App) | `PAIR-001` | `Delete Stored Link Key` (frame 1854, 06:24:24.668) → SMP `Pairing Request` (frame 1856, 06:24:24.672, LE Secure Connections, requesting `Linkkey` key distribution — Cross-Transport Key Derivation) — see `CAP-004-FINDINGS.md` §2 | | 06:24:25 | System **"Pair with Pixel Buds Pro 2 van Ted?"** dialog appears | App (Auto) | — | Coincides with the SMP Public-Key/Confirm/Random exchange (frames 1869–1879) | | 06:24:29 | Tap **Pair** | User (App) | — | SMP `DHKey Check` completes (frames 1880/1882) immediately before; classic `Create Connection` sent at 06:24:29.878 (frame 1891) | -| 06:24:30 | Switch back to nRF Connect | User (App) | — | Classic `Connect Complete` (frame 1933), `Link Key Request` → **`Reply`** (not Negative — frames 1969–1977, using the CTKD-derived key), `Authentication Complete`, `Encryption Change` (frame 2037) — RFCOMM channels 0/6/4/5 open through 06:24:31.04 | +| 06:24:30 | Switch back to nRF Connect | User (App) | `GFPS-001` | Classic `Connect Complete` (frame 1933), `Link Key Request` → **`Reply`** (not Negative — frames 1969–1977, using the CTKD-derived key), `Authentication Complete`, `Encryption Change` (frame 2037) — RFCOMM channels 0/6/4/5 open through 06:24:31.04 | | 06:24:59 | nRF Connect shows the device as `CONNECTED`, `BONDED` | App (Auto) | — | — | | 06:25:04 | Open the Buds' device-details page in **system Bluetooth settings** (not nRF Connect, not a Pixel Buds app screen) | User (App) | — | — | | 06:25:11 | System settings device page: **"Pixel Buds Pro 2 van Ted — Active. 100% battery."**, `Forget`/`Disconnect`, `Phone calls`/`Media audio`/`Input device` toggles (all on), "Allow access to contacts..." toggle, no ANC/EQ/Sound section at all (unlike the Pixel Buds app's own Device details screen in `CAP-002`/`CAP-003`) | App (Auto) | — | Confirms this is the generic Android per-device Bluetooth settings page, not any Buds-specific app UI — consistent with no Pixel Buds app being installed | diff --git a/captures/CAP-004-2026-08-11_06-22-36_06-25-12-Group_S/CAP-004-FINDINGS.md b/captures/CAP-004-2026-08-11_06-22-36_06-25-12-Group_S/CAP-004-FINDINGS.md index 91cbda22..00ec77bc 100644 --- a/captures/CAP-004-2026-08-11_06-22-36_06-25-12-Group_S/CAP-004-FINDINGS.md +++ b/captures/CAP-004-2026-08-11_06-22-36_06-25-12-Group_S/CAP-004-FINDINGS.md @@ -193,7 +193,7 @@ spec page, which states explicitly: *"Device action event"* = `0x04`, and define **Ring** (already known to this project via `PROTOCOL.md` §4.4's Ring/Find-My-Buds hypothesis). The page's visible content only documents code `0x01` in detail; codes `0x02`, `0x03`, `0x04`, `0x05`, `0x11`, `0x12`, `0x13`, `0x14`, `0x15`, `0x16` observed here are **not** covered by the -fetched excerpt — group identity confirmed, individual code meanings within it remain 🔴 open. +fetched excerpt. The page's group `0x04` is the Message Stream's (DLCI 0x04), so it does not identify this DLCI 0x08 envelope's group `0x04`, which stays open; individual code meanings within it remain 🔴 open. **Bonus, directly relevant to `CAP-002` §3's separate open item:** the same search pass also found Google's [SASS (Smart Audio Source Switching)](https://developers.google.com/nearby/fast-pair/specifications/extensions/sass) @@ -202,7 +202,7 @@ including **`0x41` = "Indicate in use account key."** This is an exact match to `CAP-002-FINDINGS.md` §3's Group `0x07` Code `0x41` `"in-use"` finding — previously 🟡 on the strength of an *English-phrase* match against a different (GATT-level) spec page. This SASS page gives the *same transport* (Message Stream) and the *exact* group/code pair, which is materially stronger -evidence. Recommend `CAP-002` §3 be updated to 🟢 FACT for that specific bullet (not done in this +evidence. (Done: `CAP-002-FINDINGS.md` §3 Task 3 upgraded that bullet to 🟢 FACT, 2026-08-12.) Recommend `CAP-002` §3 be updated to 🟢 FACT for that specific bullet (not done in this file — out of this session's scope, flagged for a maintainer/future pass, consistent with how `CAP-002` §7 item 4 already deferred a similar cross-file update). @@ -312,7 +312,7 @@ directions answered explicitly:** of its two occurrences in `CAP-001`'s own log (frame 1113: single 31-byte frame containing header `03 01 00 1b` + the full 27-byte value together; frame 1673: single 35-byte frame containing an empty `09 03 00 00` message immediately followed by the same header+value, again packed whole) — - confirmed via `tshark -r CAP-004-btsnoop_hci.log -Y 'data.data contains "Europe/Amsterdam"' -e bthci_acl.pb_flag -e bthci_acl.continuation_to` + confirmed via `tshark -r CAP-001-btsnoop_hci.log -Y 'data.data contains "Europe/Amsterdam"' -e bthci_acl.pb_flag -e bthci_acl.continuation_to` against `CAP-001`'s log, no continuation flags set on either frame. **So this exact logical message is demonstrably NOT always packet-sized in a fixed way — it is sometimes sent as one write (`CAP-001`, twice) and sometimes split header/value across two writes (`CAP-004`, @@ -368,14 +368,14 @@ channel-4/DLCI-0x08 burst) — full decode of the DLCI 0x08 private envelope, an `CAPTURE_BLUETOOTH_HCI_SNOOP.md`'s Capture Index describes Message Stream groups `0x04`, `0x05`, `0x09` as newly discovered *in `CAP-004`*. A full-session, reassembling decode of DLCI 0x08 in -**all three** captures that have this channel (`CAP-001`, `CAP-002`, `CAP-004` — `CAP-003` also has -it, 67 payload frames — 27 reassembled messages per §5a; corrected 2026-10-08, `ai-sessions/0081`) shows this framing is **not new to `CAP-004` at all** — it is present, +**all four** captures that have this channel (`CAP-001`, `CAP-002`, `CAP-003`, `CAP-004`; `CAP-003` has +27 payload frames = 27 reassembled messages per §5a, `tshark -Y 'btrfcomm.dlci==0x08 && btrfcomm.len>0'` *(corrected 2026-10-15, `ai-sessions/0094`)*) shows this framing is **not new to `CAP-004` at all** — it is present, byte-for-byte structurally identical, from the very first capture (`CAP-001`, 2026-08-09 08:51) — simply never decoded before because both `CAP-001` §2 and `CAP-002` §2 explicitly deferred this channel as "not decoded in this pass." Corrected finding: **groups `0x01`, `0x02`, `0x03`, `0x04`, -`0x05`, `0x09`, `0x0e` all appear on DLCI 0x08 in `CAP-001`, `CAP-002`, and `CAP-004` alike** (`0x01` -and `0x02` additionally only surface in `CAP-002`'s richer, genuinely-fresh-pairing session — see -below). +`0x05`, `0x09`, `0x0e` all appear on DLCI 0x08 in `CAP-001`, `CAP-002`, `CAP-003`, and `CAP-004` alike**, +including `0x01` and `0x02` (e.g. Group `0x01` Code `0x07`: `CAP-001` frame 1139, this capture frame 2358; +Group `0x02` Code `0x05`: `CAP-001` frames [1141, 1142], this capture [2360, 2361]) *(corrected 2026-10-15, `ai-sessions/0094`)*. Unique Code/Value shapes for groups `0x04`/`0x05`/`0x09` (this file's `CAP-004-btsnoop_hci.log`, reassembled): @@ -454,7 +454,7 @@ same-numbered *group*). `0x0a`'s embedded build-ID string, none of groups `0x04`/`0x05`/`0x09`'s values are ASCII — they're small, fixed-length (0/2/4/13 bytes) varint-encoded protobuf fragments, consistent with a capability/feature-flag negotiation rather than data transfer. Combined with (a) every one of these -groups' *first* occurrence landing within ~1 second of DLCI 0x08 opening in all three captures, and +groups' *first* occurrence landing within ~1 second of DLCI 0x08 opening in all four captures, and (b) Group `0x04` Code `0x12` being the only code that keeps recurring afterward — this reads as a **one-time capability/setup handshake for whatever runs on DLCI 0x08** (plausibly `libmaestro` itself, or a lower-level companion-device negotiation independent of Fast Pair — it survives GMS @@ -572,16 +572,15 @@ traffic in this specific session. Full list observed on screen (`CAP-004-EVENT-N `Common Audio Service (0x1853)`, `Telephony and Media Audio Service (0x1855)`, **`Google Fast Pair Service (0xFE2C)`**, **`Accessory Non-Owner Service (15190001-12f4-c226-88ed- 2ac5579f2a85)`**, `Device Information (0x180A)`, `Battery Service (0x180F)`, and an unnamed -**`Unknown Service (109b8b21-50e3-45cc-8ea1-ac62de4846d1)`**. +**`Unknown Service (109b862f-50e3-45cc-8ea1-ac62de4846d1)`** (corrected 2026-10-15, `ai-sessions/0094`: earlier transcribed `109b8b21-…`; the film frame at 06:23:36 shows `109b862f-…`, as do `CAP-014`/`CAP-017`). > **Correction, added 2026-08-28 (audit follow-up):** this UUID was originally transcribed as > `13190001-…`. `CAP-014-FINDINGS.md` §2 and `CAP-017-FINDINGS.md` §4c independently agree on > `15190001-…`, read from nRF Connect's own machine-generated text-log export rather than a video > screenshot transcription. Re-checked against this session's own video frame-by-frame -> (06:23:55–56, 30fps): the relevant scroll is a fast swipe with no legible frame showing this -> UUID at all, so the original digit could not be independently re-verified from its own source -> material. Corrected to `15190001-…` on the strength of the two independently-agreeing, -> more-reliably-sourced captures — not a re-confirmed read of this capture's own evidence. +> (2026-10-15, `ai-sessions/0094`): the scrolled list is on screen at 06:23:35–36, not 06:23:55–56; +> the frame at 06:23:36 (`ffmpeg -ss 60`) shows `15190001-12f4-c226-88ed-2ac5579f2a85` legibly, +> so the corrected value is confirmed from this capture's own film as well. This is the first time this project has a candidate **named** service list for the Buds at all. Combined with `CAP-002`/`CAP-003`'s handle numbers, a structural hypothesis (not wire-confirmed): @@ -622,8 +621,13 @@ frames and only four `Read By Type Response` frames, all resolving low, standard handles (`0x0002`–`0x0009`: `0x2a00` Device Name, `0x2a05` Service Changed, `0x2b29` Client Supported Features, `0x2b2a` Database Hash, `0x2b3a` Server Supported Features) — nowhere near the `0x0f2a`/`0x0f28`/`0x0c0X` range this task asked about. **Cross-checked against `CAP-002`'s own -full, non-restarted 8h20m log for the same opcode/address combination — also zero results, across -the entire day, not just the ~150s slice `CAP-002`'s own §7 already checked.** Combined with +full, non-restarted 8h20m log — also no Buds discovery, across the entire day, not just the ~150s +slice `CAP-002`'s own §7 already checked.** (Re-checked 2026-10-15, `ai-sessions/0094`, by service +content, since the address filter matches nothing in that log: `tshark -r CAP-002-btsnoop_hci.log -Y +'btatt.opcode==0x11 && (btatt.uuid16==0x184e || btatt.uuid16==0x184f || btatt.uuid16==0x1850)'` → 0 +frames, exit 0; positive control, the same filter on `CAP-034-btsnoop_hci.log` → frame 3273. The +log's one `0xFE2C` discovery response, frame 18144 on LE handle 0x0003, is another device: FE2C at +handles 0x0015–0x0020, not the Buds' 0x0c00.) Combined with `CAP-003` §1's identical finding, this makes it **three for three**: no capture taken so far has ever triggered a live GATT primary-service discovery against the Buds — Android's cached GATT database has survived every bond-removal/reconnect attempted to date. §6's handle→UUID hypotheses @@ -667,8 +671,9 @@ never connected to this device) is the only path left untried. even though this capture's core `GFPS-001` answer (§4) is not expected to change. **Done, 2026-08-26 (`CAP-012`):** confirmed — classic SSP, not CTKD, in the no-BLE-tool repeat; - this session's own CTKD result is specifically attributable to nRF Connect's early BLE - connection, not to the GMS-disabled/no-app condition. See `CAP-012-FINDINGS.md` §2/§10. The + this session's own CTKD result is not attributable to the GMS-disabled/no-app condition; it + coincides with the SMP pairing on the LE link (frame 1856), which is the 🟡 candidate gate + (`DECISIONS.md` ADR-030 Update 2026-10-03). See `CAP-012-FINDINGS.md` §2/§10. The `GFPS-001` channel-topology result (§4 above) reproduced as expected, but `CAP-012`'s log was severely ACL-truncated, so its payload-content result is inconclusive rather than a second confirming data point for §4a/§4b specifically. @@ -707,16 +712,14 @@ different)** — more precisely, "present for one sub-mechanism, absent for anot - RFCOMM channel numbers continue to vary session-to-session (channels 1/2 never opened this time at all) — a fourth confirming data point for the reusable note in `CAP-001`'s `CAP-001-FINDINGS.md` §2. -- **Message Stream Group `0x04` = "Device action"**, spec-confirmed directly against Google's - own `deviceaction` page (§5) — corroborates `PROTOCOL.md` §4.4's existing Ring/Find-My-Buds - hypothesis (code `0x01` = Ring) with an authoritative source, and gives the group itself a - confirmed identity even though most individual codes within it (`0x02`–`0x16` observed here) - remain open. +- ~~Message Stream Group `0x04` = "Device action" for this capture's group `0x04`~~ — withdrawn + (label correction 2026-10-08, §5): the Device action page defines group `0x04` on the Message + Stream (DLCI 0x04, `PROTOCOL.md` §4.4), while the group `0x04` observed here is DLCI 0x08's + private envelope, whose identity is open (`PROTOCOL.md` §2.3). - **Message Stream Group `0x07` Code `0x41` = "Indicate in use account key,"** spec-confirmed via the SASS extension page (§5), which names the exact group **and** code together over the same transport — materially stronger than `CAP-002`'s original phrase-match evidence. `CAP-002` - §3's corresponding bullet is flagged there for upgrade to 🟢 (not edited in that file this - round, per this session's scope). + §3's corresponding bullet was upgraded to 🟢 there (Task 3, 2026-08-12). - Fragmentation behavior discovered: a single Message Stream message can split its header and value across two consecutive RFCOMM packets (§5's frame 2272+2274 correction) — a methodological finding relevant to how *all* prior and future captures' RFCOMM data should be @@ -735,7 +738,7 @@ different)** — more precisely, "present for one sub-mechanism, absent for anot DLCI-0x04 channel. **Not ready yet:** -- **🟡 HYPOTHESIS: Cross-Transport Key Derivation as *the* alternate classic-bonding path used +- (Resolved, see the 2026-08-26 update below — CTKD 🟢 per ADR-030, its gate 🟡.) **🟡 HYPOTHESIS: Cross-Transport Key Derivation as *the* alternate classic-bonding path used when an LE Secure Connections link already exists** (not yet a `PROTOCOL.md` §5 connection-lifecycle mechanism). The wire evidence for what happened *in this specific session* is solid (frame-by-frame, §2, stays 🟢 FACT for this capture). Generalizing it into @@ -746,9 +749,11 @@ different)** — more precisely, "present for one sub-mechanism, absent for anot of Group S without nRF Connect (§8 item 4, `CAP-012` planned). **Update 2026-08-26 (`CAP-012`):** the clean repeat happened — see §8 item 4's own update above. - Result confirms the hypothesis (classic SSP without an early BLE link, CTKD with one), but per - `AGENTS.md` §6 this was recorded in `PROTOCOL.md` §5.1 as a proposal; promoted to 🟢 FACT 2026-09-18 (`DECISIONS.md` ADR-030, `CAP-012`/`CAP-014`) - for promotion to 🟢 FACT, not committed as settled by either capture's findings file alone. + `CAP-012` used classic SSP without nRF Connect. CTKD as a third bonding path was promoted to + 🟢 FACT 2026-09-18 (`DECISIONS.md` ADR-030, `CAP-012`/`CAP-014`, `PROTOCOL.md` §5.1); per ADR-030's + 2026-10-03 Update the CTKD sessions are the ones with an SMP pairing on the LE link (`CAP-004` + frame 1856, `CAP-014` frame 2365), not merely an early LE link (`CAP-003` had one and used SSP) — + that the SMP pairing gates CTKD stays 🟡 HYPOTHESIS (`CAP-012-FINDINGS.md` §2). - Groups `0x05`/`0x09`'s identity (§5) — search results kept redirecting to Device Information (Group `0x03`) codes `0x05`/`0x06`/`0x09` instead of confirming standalone groups; **not a reassembly artifact (ruled out 2026-08-12, §5a — reassembly is confirmed correct and stable diff --git a/captures/CAP-005-2026-08-15_15-02-31_15-03-45-Group_T/CAP-005-FINDINGS.md b/captures/CAP-005-2026-08-15_15-02-31_15-03-45-Group_T/CAP-005-FINDINGS.md index b5e2d864..fd8cf2d6 100644 --- a/captures/CAP-005-2026-08-15_15-02-31_15-03-45-Group_T/CAP-005-FINDINGS.md +++ b/captures/CAP-005-2026-08-15_15-02-31_15-03-45-Group_T/CAP-005-FINDINGS.md @@ -10,8 +10,8 @@ staged here for later promotion into `PROTOCOL.md` per `PROJECT_RULES.md` §2. M - 🟢 **FACT** — directly observed in this capture, with a frame number, and (per `PROJECT_RULES.md` §1 item 4a) reproducible via the stated command/script against the stated hex. - 🟡 **HYPOTHESIS** — plausible reading of the capture, not yet independently confirmed (this is the - *first* pass at decoding this content — nothing here is promoted past 🟡, per this session's own - task instructions). + *first* pass at decoding this content — no payload *meaning* here is promoted past 🟡, per this + session's own task instructions; the HDLC framing/CRC checks are 🟢 FACT). - ⚪ **ASSUMPTION** — not tested here, carried over from other sources. - 🔴 **OPEN QUESTION** — genuinely unresolved by this capture. @@ -105,8 +105,10 @@ tshark -r CAP-005-btsnoop_hci.log \ ``` **Result: traffic appears on DLCI 0x02 only** — DLCI 0x04 (official Fast Pair Message Stream) and -DLCI 0x08 (private envelope) are completely silent in this window (checked explicitly, zero rows — -see §4's identical check reused for both windows). Three frames, one Sent + two Rcvd: +DLCI 0x08 (private envelope) are completely silent in this window: the command above with +`btrfcomm.dlci==0x04` or `btrfcomm.dlci==0x08` added → 0 rows each, exit 0; positive control, the +same filters without the time bound → 79 (DLCI 0x04, 660 … 2381) and 65 (DLCI 0x08, 790 … 2241) +frames. Three frames, one Sent + two Rcvd: | Frame | Time | Dir | DLCI | Raw hex | |---|---|---|---|---| @@ -166,6 +168,7 @@ Again, **DLCI 0x02 only** — explicitly re-checked for DLCI 0x04 and 0x08 in th ``` tshark -r CAP-005-btsnoop_hci.log -Y "bthci_acl.chandle==0x0002 and btrfcomm.dlci==0x04 and btrfcomm.len>0 and frame.time>=\"2026-08-15 15:03:20\" and frame.time<=\"2026-08-15 15:03:30\"" -T fields -e frame.number # 0 rows tshark -r CAP-005-btsnoop_hci.log -Y "bthci_acl.chandle==0x0002 and btrfcomm.dlci==0x08 and btrfcomm.len>0 and frame.time>=\"2026-08-15 15:03:20\" and frame.time<=\"2026-08-15 15:03:30\"" -T fields -e frame.number # 0 rows +# positive control: both filters without the time bound → 79 (DLCI 0x04) and 65 (DLCI 0x08) rows ``` **Result: two separate, structurally-identical-shaped bursts, not one** — this is the direct wire @@ -195,7 +198,7 @@ HDLC decode method as §3: ## 5. Cross-command correlation & hypothesis (🟡 all HYPOTHESIS — first pass on this channel's content) -All four `Sent` payloads (1245, 1321, 1338, and 1321≡1338's shared value) line up byte-for-byte +All three `Sent` payloads (1245, 1321, 1338; 1321 and 1338 share one value) line up byte-for-byte except in two narrow regions. Byte offsets below are relative to the 45-byte payload (i.e. *after* the HDLC address+control, *before* the 4-byte CRC trailer): @@ -367,7 +370,7 @@ tshark -r CAP-005-btsnoop_hci.log \ ## 6. Open Questions -- 🔴 **Field-to-band mapping (§5b) is inferred from a single changed field, not confirmed.** Needs a +- 🟢 (resolved by `CAP-015`, see below) **Field-to-band mapping (§5b) was inferred here from a single changed field.** It needed a follow-up capture isolating one *different* single slider (e.g. Treble or Upper treble alone) to confirm field 3/4/5's assignment, and ideally one isolating Low bass alone to confirm field 1. **Resolved 2026-08-18**, superseded by `CAP-015` — see `CAP-015-FINDINGS.md` §5 and `PROTOCOL.md` @@ -428,8 +431,8 @@ file's own changelog for the corresponding addition.) - This is the first concrete candidate wire format for `PROTOCOL.md` §4.2 ("Opcode/payload structure: not yet extracted" → a specific, byte-verified 🟡 HYPOTHESIS now exists). -**Not ready:** the field-to-band mapping, the field-16/18 preview/save interpretation, and any -generalization to ANC or other `libmaestro` commands — all explicitly flagged 🔴 in §6, needing +**Not ready:** the field-16/18 preview/save interpretation and any generalization to ANC or other +`libmaestro` commands — flagged 🔴/🟡 in §6 (the field-to-band mapping is 🟢 since `CAP-015`), needing further isolated captures before any promotion to FACT. --- diff --git a/captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-EVENT-NOTES.md b/captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-EVENT-NOTES.md index 4d4fd75c..ee78c9c3 100644 --- a/captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-EVENT-NOTES.md +++ b/captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-EVENT-NOTES.md @@ -66,8 +66,8 @@ are directly comparable to log frame timestamps without offset correction. | 17:24:38 | Tap **Adaptive** | User (App) | `ANC-003` | `Set ANC state`, `new_mode=0x40` (Adaptive) — frame 1731, 17:24:38.666; ACK frame 1735, 17:24:38.708; Notify frames 1736/1738, 17:24:38.709/38.711 | | 17:24:51 | Tap **Transparency** | User (App) | `ANC-004` | `Set ANC state`, `new_mode=0x80` (Transparent/Aware) — frame 1862, 17:24:50.825 (0.2s *before* the observed on-screen tap, within this capture's ±1s video-sampling tolerance); ACK frame 1864, 17:24:50.862; Notify frames 1865/1867, 17:24:50.863/50.864 | | 17:24:52–55 | UI shows a brief transitional/all-muted button state, then **Transparency** fully selected/active | App (Auto) | — | Matches the Notify frames above; no further `0x12` frames follow | -| ~17:24:56–17:25:02 | (not directly visible on screen — video decode is truncated in this window, see video recovery note) RFCOMM channels 1 and 2 are cleanly torn down (`DISC`/`UA`) and a fresh SDP query + full channel/multiplexer renegotiation runs on DLCIs 0x02/0x04/0x08/0x0a, without any preceding HCI `Disconnect` or `Create Connection` event — the underlying ACL link is never dropped | — | — | `DISC` Channel=1/2 (frames 1888/1890, 17:24:56.689–.690); fresh SDP Service Search (frame 1908, 17:24:58.398); RFCOMM SABM on DLCI 0x0a/0x08/0x02/0x04 (frames 1920/1949/2030/2091, 17:24:58.450–17:25:01.828); a repeat Device-Information/capability burst and a fresh ANC Notify (`e8e880`, still Transparent — frame 2115, 17:25:02.084) follow, consistent with a full profile/channel re-sync rather than a new user action — see `CAP-006-FINDINGS.md` §6 | -| ~17:25:07 | End video recording (nominal, per file naming/original draft — last independently recovered frame is 17:25:05) | — | — | — | +| ~17:24:56–17:25:02 | (on film: no user action; the ANC button row is greyed out 17:24:58–17:25:03 and active again, Transparency selected, from 17:25:04) RFCOMM channels 1 and 2 are cleanly torn down (`DISC`/`UA`) and a fresh SDP query + full channel/multiplexer renegotiation runs on DLCIs 0x02/0x04/0x08/0x0a, without any preceding HCI `Disconnect` or `Create Connection` event — the underlying ACL link is never dropped | — | — | `DISC` Channel=1/2 (frames 1888/1890, 17:24:56.689–.690); fresh SDP Service Search (frame 1908, 17:24:58.398); RFCOMM SABM on DLCI 0x0a/0x08/0x02/0x04 (frames 1920/1949/2030/2091, 17:24:58.450–17:25:01.828); a repeat Device-Information/capability burst and a fresh ANC Notify (`e8e880`, still Transparent — frame 2115, 17:25:02.084) follow, consistent with a full profile/channel re-sync rather than a new user action — see `CAP-006-FINDINGS.md` §6 | +| ~17:25:07 | End video recording (the re-pulled file's last frame shows 17:25:08) | — | — | — | ## Corrections vs. the original draft of this file @@ -89,8 +89,8 @@ are directly comparable to log frame timestamps without offset correction. `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §5's correlation workflow and `AGENTS.md` §13's traceability check. - Added the post-Transparency RFCOMM channel renegotiation window (~17:24:56–17:25:02), which the - original draft did not mention at all — flagged as not independently attributable to an on-screen - action since the video decode is truncated in that exact window. + original draft did not mention at all — the re-pulled film shows no user action in that window, + only the ANC row greyed out 17:24:58–17:25:03 while the channels re-open. --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-EVENT-NOTES.md - https://tedsluis.github.io/opencontrolpixelbudspro2/captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-EVENT-NOTES diff --git a/captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-FINDINGS.md b/captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-FINDINGS.md index 8345f766..a46f29f1 100644 --- a/captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-FINDINGS.md +++ b/captures/CAP-006-2026-08-15_17-23-49_17-25-06-Group_B/CAP-006-FINDINGS.md @@ -21,7 +21,7 @@ a status per `PROJECT_RULES.md` §1: | Test device | Pixel 7a, Android 17 (official Pixel Buds Companion App v1.0.955078536) | | Log file | [`CAP-006-btsnoop_hci.log`](./CAP-006-btsnoop_hci.log) — 233.16s, 2026-08-15 17:23:37.30–17:27:30.45 (+0200), 3,441 packets | | Notes file | [`CAP-006-EVENT-NOTES.md`](./CAP-006-EVENT-NOTES.md) — full event timeline with tap-level video↔log correlation | -| Video file | [`CAP-006-recording.mp4`](./CAP-006-recording.mp4) — nominal 17:23:49–17:25:07; container index corrupted (see `CAP-006-EVENT-NOTES.md`'s video recovery note), recovered up to 17:25:05 via VLC's `scene` filter, which covers every action in this session | +| Video file | [`CAP-006-recording.mp4`](./CAP-006-recording.mp4) — 17:23:49–~17:25:08 (79.49 s, 1280×720); the originally corrupted file was re-pulled from the phone and now decodes cleanly in `ffmpeg`/`ffprobe` (see `CAP-006-EVENT-NOTES.md`'s video recovery note); covers every action in this session | | Buds MAC (partial, per `AGENTS.md` §7/§9) | `...cf:6e:07` — same physical device as `CAP-001`/`CAP-002`/`CAP-004` | | Phone MAC (partial) | `...7e:ca:81` — same phone as `CAP-001` | @@ -83,7 +83,7 @@ $ tshark -r CAP-006-btsnoop_hci.log -Y "btrfcomm.dlci == 0x04 and data.data[0:2] | `ANC-003` Adaptive | 17:24:38 | 1731 | 17:24:38.666 | +0.7s | | `ANC-004` Transparency | 17:24:51 | 1862 | 17:24:50.825 | −0.2s | -All four offsets are within this capture's own ±1s video-sampling tolerance (in fact, tighter than +All four offsets are within −0.2…+1.3 s of the filmed tap (three within this capture's ±1s video-sampling tolerance, `ANC-002` at +1.3 s; no wider than `CAP-001`'s already-accepted ~1–1.5s window). **This directly closes the open question `DECISIONS.md` ADR-009 was blocking `FrameEncoder` implementation on**: with genuinely isolated, single taps against an already-active ANC row, the hit rate is 4/4 (100%), not `CAP-001`'s 4/6 (67%). The @@ -155,9 +155,8 @@ for this capture's purpose). ## 5. Conclusions & Next Steps - **The ANC command channel is fully confirmed. `FrameEncoder`/`FrameDecoder` implementation for - this specific command is unblocked**, pending the maintainer's own review and a `DECISIONS.md` - entry recording this capture's result (per `AGENTS.md` §6 — this document proposes that - conclusion; it does not itself promote `PROTOCOL.md` or write the ADR). Both conditions + this specific command is unblocked** — recorded by the maintainer as `DECISIONS.md` ADR-009's + Update (2026-08-15). Both conditions `DECISIONS.md` ADR-009 set are now met on the same evidence base: 1. Opcode/payload structure — already 🟢 FACT since `CAP-001` (`PROTOCOL.md` §4.1), unchanged and reconfirmed byte-for-byte by this capture's four frames. @@ -165,7 +164,7 @@ for this capture's purpose). already-active ANC row produced a matching `0x12` frame, at the expected mode value, in the expected order, within video-sampling tolerance. `CAP-001`'s 2/6 miss rate does not reproduce under the isolated single-tap protocol this capture used. -- **Recommended next step:** the maintainer records a `DECISIONS.md` update (superseding/extending +- **Done (ADR-009 Update, 2026-08-15).** Original recommended next step: the maintainer records a `DECISIONS.md` update (superseding/extending ADR-009, not a fresh independent decision — see `PROJECT_RULES.md` §3) stating the `CAP-006` result and formally lifting the `FrameEncoder` block for this command, then implementation of the ANC `FrameEncoder`/`FrameDecoder` may proceed per `AGENTS.md` §6. @@ -183,14 +182,14 @@ for this capture's purpose). - **Post-Transparency RFCOMM channel renegotiation (~17:24:56–17:25:02, `CAP-006-EVENT-NOTES.md`'s timeline)** — RFCOMM channels 1 and 2 are cleanly torn down and a full SDP + multiplexer renegotiation runs across DLCIs 0x02/0x04/0x08/0x0a, without any HCI-level `Disconnect`/`Create - Connection` in between (the underlying ACL link never drops). Not attributable to a specific - on-screen action since the video decode is truncated in this exact window (see - `CAP-006-EVENT-NOTES.md`'s video recovery note). 🔴 Not investigated further here — out of scope + Connection` in between (the underlying ACL link never drops). The re-pulled film shows no user + action in this window; the app greys out the ANC row 17:24:58–17:25:03 and re-enables it, still + on Transparency, from 17:25:04 (after Notify frame 2115). 🔴 Its trigger is not investigated further here — out of scope for this capture's ANC-focused purpose. A repeat capture with an intact video recording through this window (or a targeted repeat isolating whatever triggers it — e.g., the app being backgrounded) would be needed to attribute a cause. -- **Recording pipeline reliability** — this is the second capture in a row (after none previously - reported) where the screen-recording `.mp4` itself was corrupted (truncated `mdat` relative to +- **Recording pipeline reliability** — the first copy of this capture's screen recording was + corrupted (the re-pulled file decodes cleanly, `CAP-006-EVENT-NOTES.md`; truncated `mdat` relative to its own `moov` sample tables). Worth a note for the maintainer's own recording workflow (e.g., whether the recorder app was force-stopped rather than stopped normally) — not a protocol finding, but noted here since it materially affected this capture's analysis effort. diff --git a/captures/CAP-007-2026-08-16_09-14-10_09-17-57-Group_U/CAP-007-EVENT-NOTES.md b/captures/CAP-007-2026-08-16_09-14-10_09-17-57-Group_U/CAP-007-EVENT-NOTES.md index 3d064158..b6063edf 100644 --- a/captures/CAP-007-2026-08-16_09-14-10_09-17-57-Group_U/CAP-007-EVENT-NOTES.md +++ b/captures/CAP-007-2026-08-16_09-14-10_09-17-57-Group_U/CAP-007-EVENT-NOTES.md @@ -49,10 +49,10 @@ to video overlay times without offset correction (video's `t=0` = wall 09:14:10, | 09:14:42 | A hand briefly enters the frame near the top edge of the phone, then withdraws; no change to the case/bud layout or the app screen | User (Hardware?) | — | No RFCOMM/L2CAP traffic beyond routine housekeeping identified in this window — inconclusive, not attributed to any Test-ID | | 09:15:17 | Same as above — hand briefly enters frame, same area, no visible effect | User (Hardware?) | — | Same as above — inconclusive | | 09:15:38 | **Left earbud is visibly lifted out of the still-open case** (Right earbud and the case itself are undisturbed). In the same ~1s window, the app's "Active noise control" row greys out/disables. | User (Hardware) | `INEAR-004`/`CASE-004` — see `CAP-007-FINDINGS.md` §3.1 for why this maps ambiguously to `INEAR-004`: the bud was never observed inserted in an ear at any point in this capture, only resting in the open case, so this is more precisely a case-removal event | `DISC Channel=1`, buds-initiated, 09:15:38.438 (frame 1351) opens a full RFCOMM teardown/rebuild of channels 1/2/4/5 (DLCI 0x02/0x04/0x08/0x0a), complete by 09:15:44.352 (frame 1572); the ANC-state notify re-fires with the **same, unchanged** value `0x80` (Transparency) at 09:15:38.436 and again at 09:15:44.352 (frames 1350, 1572); a fresh Group `0x04` Code `0x12` notify (value `0x02`) fires on DLCI 0x08 within ~60ms of that channel reopening, at 09:15:39.271/.285 (frames 1436, 1444) | -| 09:15:52–53 | "Active noise control" row **re-enables** in the app UI. The Left earbud remains physically out of the case, unchanged. | App/OS (Auto) | — | No RFCOMM/L2CAP data frame of any kind exists anywhere in the log within many seconds of this moment (only HCI-level `Mode Change`/`Sniff Subrating` baseband events and unrelated LE advertising reports from other nearby devices) — not attributable to any wire-level event, see `CAP-007-FINDINGS.md` §3.3 | +| 09:15:52–53 | "Active noise control" row **re-enables** in the app UI. The Left earbud remains physically out of the case, unchanged. | App/OS (Auto) | — | No RFCOMM/L2CAP data frame of any kind exists between 09:15:45.125 (frame 1607) and 09:16:02.052 (frame 1632) (only HCI-level `Mode Change`/`Sniff Subrating` baseband events and unrelated LE advertising reports from other nearby devices) — not attributable to any wire-level event, see `CAP-007-FINDINGS.md` §3.3 | | 09:16:44–48 | User closes the case lid (Right earbud still inside the whole time; Left earbud remains outside, set down next to the case) | User (Hardware) | `OBS-003` (Group U step 2 — "close the case lid while the connection is still active") | Zero RFCOMM data traffic on any DLCI in a ±10s window around this action (checked 09:16:33–09:16:53) — clean negative result, see `CAP-007-FINDINGS.md` §3.4 | | 09:17:56/57 | End video recording. On-screen app state unchanged since 09:15:53 (Active, Transparency selected). Left bud lies separately on the table; the case appears fully closed, standing on its hinge edge. | — | — | — | -| 09:18:43–09:19:45 | *(beyond the video — log continues unattended, no camera coverage)* Following an uninterrupted **~184s (~3m 4s) silence** on DLCI 0x08 (09:15:39.285 → 09:18:43.428, frames 1444 → 2074) — long enough to match the intended "≥3 minute idle wait", `OBS-003`'s third bracket, even though it fell after the camera stopped — Group `0x04` Code `0x12` notify frames resume on DLCI 0x08, now cycling between value `0x02` and `0x04` (one transitional `0x03` on the very first frame back) at irregular ~1–12s intervals, through 09:19:45.019 (frames 2074–2463). No RFCOMM channel teardown/rebuild accompanies this resumption (contrast with 09:15:38 above). | — | `OBS-003` | Frames 2074, 2173, 2234, 2251, 2255, 2306, 2318, 2335, 2344, 2356, 2359, 2370, 2399, 2463 — see `CAP-007-FINDINGS.md` §3.2 for the full decoded table | +| 09:18:43–09:19:45 | *(beyond the video — log continues unattended, no camera coverage)* Following **~184s (~3m 4s) without any Code `0x12` frame** on DLCI 0x08 (09:15:39.285 → 09:18:43.428, frames 1444 → 2074; the DLCI still carried the periodic `0e 02`/`0e 01`/`04 03` push groups at 09:16:02, 09:16:23, 09:17:02 and 09:17:52, e.g. frames 1632/1633/1642) — long enough to match the intended "≥3 minute idle wait", `OBS-003`'s third bracket, even though it fell after the camera stopped — Group `0x04` Code `0x12` notify frames resume on DLCI 0x08, now cycling between value `0x02` and `0x04` (one transitional `0x03` on the very first frame back) at irregular ~1–12s intervals, through 09:19:45.019 (frames 2074–2463). No RFCOMM channel teardown/rebuild accompanies this resumption (contrast with 09:15:38 above). | — | `OBS-003` | Frames 2074, 2173, 2234, 2251, 2255, 2306, 2318, 2335, 2344, 2356, 2359, 2370, 2399, 2463 — see `CAP-007-FINDINGS.md` §3.2 for the full decoded table | | 09:20:14.579 | Latest packet in the log; capture ends | — | — | — | ## Corrections vs. the original draft of this file diff --git a/captures/CAP-007-2026-08-16_09-14-10_09-17-57-Group_U/CAP-007-FINDINGS.md b/captures/CAP-007-2026-08-16_09-14-10_09-17-57-Group_U/CAP-007-FINDINGS.md index 2a99d576..7d18975b 100644 --- a/captures/CAP-007-2026-08-16_09-14-10_09-17-57-Group_U/CAP-007-FINDINGS.md +++ b/captures/CAP-007-2026-08-16_09-14-10_09-17-57-Group_U/CAP-007-FINDINGS.md @@ -157,8 +157,10 @@ phone-side query. This directly answers Group U's own research question: > **🟢 FACT: Code `0x12` is autonomously pushed by the Buds, not a phone-polled value.** The 14 > pushes between 09:18:43 and 09:19:45 (§3.2's table, frames 2074–2463) fire with **no** > corresponding RFCOMM channel reopen, no Sent-side query, and — per §3.5 below — no camera -> coverage of any physical action at all, on a channel that had otherwise been completely silent -> for the preceding ~184s. This rules out "value only changes because the phone just asked" and +> coverage of any physical action at all, after ~184 s without any Code `0x12` frame (1444 at +> 09:15:39.285 → 2074 at 09:18:43.428) during which DLCI 0x08 carried only the periodic `0e 02`/`0e 01`/`04 03` +> push groups (09:16:02, 09:16:23, 09:17:02, 09:17:52; e.g. frames 1632/1633/1642) and no channel was +> opened or closed *(corrected 2026-10-15, `ai-sessions/0094`)*. This rules out "value only changes because the phone just asked" and > rules out "value only changes because the channel/session was just re-established" as the sole > explanations — some of these pushes are genuinely autonomous, buds-initiated events on an > already-idle, already-open channel. @@ -168,8 +170,8 @@ phone-side query. This directly answers Group U's own research question: Extraction: ``` -tshark -r CAP-007-btsnoop_hci.log -Y "frame.time_relative>=90.0 && frame.time_relative<=96.1" \ - -Y "btrfcomm.frame_type or bthci_evt" +tshark -r CAP-007-btsnoop_hci.log \ + -Y "frame.time_relative>=90.0 && frame.time_relative<=96.1 && (btrfcomm.frame_type or bthci_evt)" ``` 🟢 FACT, all frame numbers/timestamps independently re-checked: @@ -219,7 +221,7 @@ the physical event** (§3.5). **One value did change (added 2026-10-03, `ai-sessions/0069`, lead L68-5; 🟢 FACT for the bytes, 🟡 for the meaning):** DLCI 0x08 Group `0x04` Code `0x05` reads `04 05 00 02 08 06` in frame 717 -(09:14:17, both buds worn) and `04 05 00 02 08 05` in frame 1345 (8 ms before frame 1350's Notify, +(09:14:17, both buds in the open case) and `04 05 00 02 08 05` in frame 1345 (8 ms before frame 1350's Notify, at the removal) and again in 1434 after the re-open. So the earlier statement "no byte anywhere changed" is withdrawn. The cross-capture tabulation of this code is in `DESKRESEARCH_FINDINGS.md` (entry of 2026-10-03). @@ -234,7 +236,7 @@ in the log). tshark -r CAP-007-btsnoop_hci.log -Y "btrfcomm.channel && data.data && frame.time_relative>=145 && frame.time_relative<=165" ``` -Zero results — 🟢 FACT. Not one RFCOMM data-carrying frame, on any DLCI, appears in a ±10s window +Zero results, exit 0 (positive control: the same filter without the time bound matches 310 frames) — 🟢 FACT. Not one RFCOMM data-carrying frame, on any DLCI, appears in a ±10s window around the lid closing. This directly answers Group U step 2's own question: **closing the case lid, while a bud remains outside the case and the connection stays active, produces no observable RFCOMM wire signal** in this capture — no ANC-state re-announcement, no Group `0x04` Code `0x12` @@ -247,13 +249,15 @@ The app's "Active noise control" row greys out at 09:15:38 (same second as the b re-enables at 09:15:52–53, while the Left bud stays physically out of the case the whole time. ``` -tshark -r CAP-007-btsnoop_hci.log -Y "frame.time_relative>=95 && frame.time_relative<=108" +tshark -r CAP-007-btsnoop_hci.log -Y "frame.time_relative>=97 && frame.time_relative<=108" +# with "&& btrfcomm.len>0" → 0 frames, exit 0; from rel 95 the same filter matches 32 RFCOMM data frames +# (1545–1607, 09:15:43.738–09:15:45.125: the end of the DLCI 0x04/0x02 re-open burst) — positive control ``` Result (🟢 FACT, full frame list in `CAP-007-EVENT-NOTES.md`'s companion analysis): the only events in this window are HCI-level `Mode Change` (frame 1621, 09:15:51.916) and `Sniff Subrating` (frame 1623, 09:15:53.899) baseband events, plus unrelated `LE Extended Advertising Report` events from -other nearby BLE devices — **zero RFCOMM/L2CAP data frames of any kind**. The `Mode Change` event's +other nearby BLE devices — **zero RFCOMM/L2CAP data frames of any kind** between 09:15:45.125 (frame 1607) and 09:16:02.052 (frame 1632) *(corrected 2026-10-15, `ai-sessions/0094`)*. The `Mode Change` event's timing (~1–2s before the visible re-enable) is suggestively close, but a baseband active/sniff-mode transition has no documented mechanism for driving an app-level UI control directly — 🔴 **not attributable**. The more likely explanation, 🟡 HYPOTHESIS: the ANC row's @@ -274,7 +278,7 @@ bounce**, not about in-ear/out-of-case sensing at all. channel-reopen traffic already characterized in `CAP-001-FINDINGS.md`/`CAP-004-FINDINGS.md`). - **Does not unblock `FrameEncoder`/`FrameDecoder` for any in-ear/wear-state feature.** Per `AGENTS.md` §6, the promotion gate is per channel/feature, evidenced with a 🟢 FACT entry in - `PROTOCOL.md`. This capture found **no byte, on any DLCI, that changed value in response to the + `PROTOCOL.md`. Apart from DLCI 0x08 Group `0x04` Code `0x05` (`08 06` → `08 05`, frames 717 → 1345, meaning 🟡, §3.3), this capture found **no byte, on any DLCI, that changed value in response to the one physical bud-removal event it captured** (§3.3, §3.5) — the opposite of what would be needed to propose a FACT-level wear-state opcode. `INEAR-004` remains without protocol evidence. - **Proposed update to `CAP-004-FINDINGS.md` §5a / `PROTOCOL.md`'s characterization of DLCI 0x08 @@ -282,11 +286,11 @@ bounce**, not about in-ear/out-of-case sensing at all. this document): revise from "alternates 2↔3, irregular interval, 🟡 HYPOTHESIS free-running liveness/sequence bit" to "🟢 FACT: Buds-initiated (Rcvd-only, never seen as a direct reply to a Sent query), fires (a) immediately after DLCI 0x08 is opened or re-opened — at least once with - value unchanged across a real physical event (§3.3) — and (b) autonomously during an otherwise - fully idle connection, with a `0x02`↔`0x04` steady-state alternation (one `0x03` transitional + value unchanged across a real physical event (§3.3) — and (b) autonomously on an open connection + with no channel churn and no user action, with a `0x02`↔`0x04` steady-state alternation (one `0x03` transitional value seen at a resumption boundary) at irregular ~1–12s intervals; 🔴 still unresolved what real-world condition the value itself encodes — not confirmed to be in-ear state, not confirmed - to be a simple counter (a true free-running counter would not go fully silent for ~184s, §3.2)." + to be a simple counter (a true free-running counter would not pause for ~184s while the other DLCI 0x08 pushes continue, §3.2)." *(Done: the event-driven-and-autonomous behaviour was promoted with `CAP-016` — `DECISIONS.md` ADR-016 item 7, `PROTOCOL.md` §6 Resolved; the value's meaning stays 🔴.)* ## 5. Conclusions & Next Steps @@ -294,7 +298,7 @@ bounce**, not about in-ear/out-of-case sensing at all. **Group U's own research question (is Code `0x12` event-driven or free-running?) is answered: 🟢 FACT, neither, cleanly.** It is Buds-initiated and can fire with no phone-side trigger and no channel churn (ruling out "purely a reaction to phone queries or channel reopening"), but it also -goes completely silent for ~184s during genuine idle time (ruling out "purely free-running, +pauses for ~184s while DLCI 0x08's periodic `0e`/`04 03` push groups continue (ruling out "purely free-running, fixed-period"). The most defensible reading is an event-driven push tied to some *internal* Buds condition not observed directly in this capture — plausibly connection-supervision/heartbeat- adjacent, given its behavior around the channel bounce, but this is 🔴 not confirmed. @@ -306,8 +310,8 @@ reasons, both stated plainly per `PROJECT_RULES.md` §1 rather than papered over 1. **No earbud was ever inserted into an ear in this capture** (§3.1) — only removed from the open case. Whatever in-ear sensing does on the wire, if anything, was not exercised here. -2. **The one physical case-removal event that *was* captured produced no value change on any - byte, on any DLCI, that could be attributed to it** (§3.3, §3.5) — every candidate signal near +2. **The one physical case-removal event that *was* captured produced one value change only — + DLCI 0x08 Group `0x04` Code `0x05` `08 06` → `08 05` (frames 717 → 1345), meaning 🟡** (§3.3, §3.5) — every other candidate signal near that moment (ANC re-announcement, Group `0x04` Code `0x12`) turned out to be a generic channel-(re)initialization side effect with an unchanged value, not a state-specific signal. @@ -332,8 +336,9 @@ matched against a *known* physical state rather than inferred after the fact. channel bounce (🟡 leading hypothesis, §3.5) or to something else entirely? No wire-level frame of any kind is attributable to the 09:15:52–53 re-enable moment specifically. - 🔴 Does DLCI 0x08 Group `0x04` have other Codes/values that *do* respond to in-ear state - specifically? Only Code `0x12` (and empty `0x11`/`0x13` placeholders) were observed on this DLCI - in this capture — a Get-all/enumerate pass against a firmware reference (if one becomes + specifically? Group `0x04` on this DLCI in this capture: Rcvd Codes `0x03`, `0x05`, `0x12`, + `0x14`, `0x16` with values (e.g. frames 714, 717, 721, 723, 727) and Sent empty `0x02`/`0x04`/`0x11`/`0x13`/`0x15` + placeholders (frames 698–703, 1426); only `0x05` changed value (§3.3) — a Get-all/enumerate pass against a firmware reference (if one becomes available) could reveal Codes not exercised by this particular session's actions. - 🔴 Right earbud was never removed in this capture — whether Group `0x04` Code `0x12` (or anything else) carries any Left/Right distinction at all remains completely untested; this diff --git a/captures/CAP-008-2026-08-26_09-38-44_09-41-36-Group_V/CAP-008-EVENT-NOTES.md b/captures/CAP-008-2026-08-26_09-38-44_09-41-36-Group_V/CAP-008-EVENT-NOTES.md index c60a407b..8f37da92 100644 --- a/captures/CAP-008-2026-08-26_09-38-44_09-41-36-Group_V/CAP-008-EVENT-NOTES.md +++ b/captures/CAP-008-2026-08-26_09-38-44_09-41-36-Group_V/CAP-008-EVENT-NOTES.md @@ -5,9 +5,10 @@ wire/video correlation. Every timestamp below was independently re-verified agai recording's burned-in camera clock (1s resolution) and matches to within 1 second — no corrections were needed. Folder already renamed to its actual session date/start/end time. -**Purpose (`CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group V):** `CAP-002-FINDINGS.md` §5 found zero -`AT+` traffic anywhere outside `CAP-001`'s own pairing-time handshake across a full 8+ hour log, -and `CAP-001-FINDINGS.md` §6 Task 6 ruled out any SCO/eSCO HCI event in all four captures to +**Purpose (`CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group V):** `CAP-002-FINDINGS.md` §5 then reported zero +`AT+` traffic outside `CAP-001`'s own pairing-time handshake (a filter artefact: §5, rewritten +2026-10-03, finds the SLC handshake at `CAP-002`'s fresh pairing, frames 48954–49079, and three +more in that log), and `CAP-001-FINDINGS.md` §6 Task 6 ruled out any SCO/eSCO HCI event in all four captures to date — both findings converge on the same missing scenario: **none of the captures so far ever contains an actual phone call**, the one trigger that would exercise HFP's Service Level Connection setup and channel 5/DLCI 0x0a's audio path. diff --git a/captures/CAP-008-2026-08-26_09-38-44_09-41-36-Group_V/CAP-008-FINDINGS.md b/captures/CAP-008-2026-08-26_09-38-44_09-41-36-Group_V/CAP-008-FINDINGS.md index da318c8b..965f3aa6 100644 --- a/captures/CAP-008-2026-08-26_09-38-44_09-41-36-Group_V/CAP-008-FINDINGS.md +++ b/captures/CAP-008-2026-08-26_09-38-44_09-41-36-Group_V/CAP-008-FINDINGS.md @@ -24,9 +24,9 @@ Buds/case) — same physical units as every prior capture in this project. **Scope note:** this session's purpose (`CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group V) is `CALL-001` — the first capture in this project containing an actual phone call, closing -the gap `CAP-001-FINDINGS.md` §6 Task 6 and `CAP-002-FINDINGS.md` §5 both flagged (no -prior capture ever exercised HFP's Service Level Connection re-setup or a real -SCO/eSCO audio path). The session also incidentally exercises a Bluetooth-radio +the gap `CAP-001-FINDINGS.md` §6 Task 6 flagged (no prior capture ever exercised a real +SCO/eSCO audio path; `CAP-002-FINDINGS.md` §5's "no SLC re-setup" was later found to be a +filter artefact, §3 below). The session also incidentally exercises a Bluetooth-radio power-cycle (radio was off at the start), a Spotify play/pause sequence, and opening the Pixel Buds companion app — none of these are Group V's own scope, but are noted where they help correlate wire evidence to on-screen timing. @@ -42,7 +42,7 @@ overlay directly — all match the maintainer's manual notes to within 1 second ## 1. Connection lifecycle (🟢 FACT) Bluetooth was off at the start of `CAP-008-recording1.mp4` (09:38:44, screen shows -"Bluetooth is off") and switched on by the user at 09:38:48 (video). This produced a +"Bluetooth is off") and switched on by the user at 09:38:48 (video; the wire's `Create Connection`, frame 230, is at 09:38:47.919 — before the filmed tap, so the film clock runs at least ~0.1 s behind the log clock). This produced a single, successful classic BR/EDR connection — no failed attempts, unlike `CAP-001`'s three-attempt sequence: @@ -75,16 +75,16 @@ link authenticates: | Order | DLCI | RFCOMM channel | Opened (frame / time) | Content this session | Cross-session identity | |---|---|---|---|---|---| | 1 | 0x00 | 0 | 734 / 09:38:50.624 | Multiplexer control (PN/MSC negotiation for the other 5) | Always channel 0 | -| 2 | 0x0c | **6**, Wireshark-labelled "(Hands-Free)" | 749 / 09:38:50.640 | Full HFP AT-command SLC handshake, then both calls' RING/CIEV/BCS traffic — see §3/§4 | `CAP-001` put this content on channel 4/DLCI 0x08–0x09; **channel numbers are session-local**, per `CAP-001-FINDINGS.md` §2's own reusable note — reconfirmed here with a third distinct channel-number assignment for the same profile | +| 2 | 0x0c | **6**, Wireshark-labelled "(Hands-Free)" | 749 / 09:38:50.640 | Full HFP AT-command SLC handshake, then both calls' RING/CIEV/BCS traffic — see §3/§4 | `CAP-001` put this content on channel 4/DLCI 0x08–0x09; **channel numbers are session-local**, per `CAP-001-FINDINGS.md` §2's own reusable note — here channel 6, the same as `CAP-002`, while `CAP-001`/`CAP-003` used channel 4 *(corrected 2026-10-15, `ai-sessions/0094`)* | | 3 | 0x04 | 2 | 924 / 09:38:51.004 | Official Fast Pair Message Stream (ANC Notify, Device Information) — not specifically exercised this session, background traffic only | `PROTOCOL.md` §2.1/§4.1 | | 4 | 0x0a | 5 | 1042 / 09:38:51.196 | **SABM/UA only — zero payload for the rest of the 309s session** | Silent in this capture too — see §6 | | 5 | 0x08 | 4 | 1076 / 09:38:51.238 | Private `[Group][Code][Length][Value]` envelope — periodic capability/firmware/status traffic (incl. the `release_5.203` string, frame 1116) | `PROTOCOL.md` §2.3 | | 6 | 0x02 | 1 | 1222 / 09:38:51.935 | `libmaestro`/Pigweed `pw_hdlc` channel (§2.2a) — a steady ~3 exchanges/second heartbeat (43-byte Rcvd + 14-byte Sent UID-ack pairs) that continues completely unperturbed through both calls, their RING/answer/hangup transitions, and the eSCO setup/teardown | `PROTOCOL.md` §2.2a | **HFP's RFCOMM channel assignment is new information, not a contradiction:** this is -the third distinct RFCOMM channel number this project has seen HFP-adjacent AT-command -traffic land on (channel 4/DLCI 0x09 in `CAP-001` — corrected 2026-10-08, `ai-sessions/0081`; channel 6/DLCI 0x0c here), which -reinforces — with a third independent data point — that RFCOMM server channel numbers +the same channel as `CAP-002` (channel 6/DLCI 0x0c), while `CAP-001` and `CAP-003` put HFP on +channel 4/DLCI 0x09 (`tshark -Y 'frame contains "AT+BRSF"' -e btrfcomm.channel`) *(corrected 2026-10-15, `ai-sessions/0094`)*, which +reinforces that RFCOMM server channel numbers are negotiated per-connection and must never be treated as a stable per-profile label (the point `CAP-001-FINDINGS.md` §2 already made from two sessions). @@ -148,8 +148,9 @@ including the one of its own fresh pairing (`CAP-002-FINDINGS.md` §5, rewritten handshake has been seen on every classic connect examined (`CAP-001`, `CAP-002`, this capture, `CAP-012`'s reconnect without a radio toggle). -**No second full handshake precedes call 2** (§4) — only a codec renegotiation -(`AT+BCS`, §5) and the same RING/CIEV sequence repeat. The SLC, once established, +**No second full handshake precedes call 2** (§4) — not even a codec renegotiation: the +`AT+BCS` exchange occurs only before call 1 (frames 1641/1644/1645, `tshark -Y 'frame contains "BCS"'`); +call 2 reuses mSBC, its eSCO setup (2274) follows `+CIEV: 2,1` (2273) directly *(corrected 2026-10-15, `ai-sessions/0094`)*; the same RING/CIEV sequence repeats. The SLC, once established, persists across both calls in this session. ## 4. Both calls, cross-correlated wire ↔ video (🟢 FACT) @@ -161,7 +162,7 @@ persists across both calls in this session. | `+CIEV: 2,1` (CALLSETUP=1, alerting begins) | 09:39:19.689 | 1639 | *(end of recording1, not on camera — see gap note below)* | | `RING` ×3 (5.000s apart: .823, 24.826, 29.827) | 09:39:19.823–29.827 | 1658, 1730, 1767 | Incoming-call screen already showing at 09:39:29 (recording2 start) | | `+CIEV: 1,1` + `+CIEV: 2,0` (CALL=1, answered) | 09:39:33.861 | 1842/1843 | Finger taps green Answer button at 09:39:34 (Δ≈0.14s) | -| `+CIEV: 1,0` (CALL=0, call ends) | 09:39:51.694 | 2022 | Finger taps red hang-up at 09:39:49–50, screen shows "Device details" by 09:39:51 (Δ≈0s) | +| `+CIEV: 1,0` (CALL=0, call ends) | 09:39:51.694 | 2022 | Finger taps red hang-up at 09:39:49–50, screen shows "Device details" by 09:39:51 (Δ ≈ 1.7–2.7 s from the tap) | Talk time: 09:39:33.861–09:39:51.694 ≈ **17.8s**. `AVDTP Start` (podcast A2DP media resumes) fires 0.21s after call end (frame 2034, 09:39:51.904) — matches the @@ -180,7 +181,7 @@ already up when recording2 starts 10s later, consistent with the wire timeline. | `+CIEV: 2,1` (CALLSETUP=1, alerting begins) | 09:40:23.468 | 2273 | Incoming-call screen visible from 09:40:56 (recording4 start) | | `RING` ×8 (5.000s apart, .732 → 58.734) | 09:40:23.732–58.734 | 2284…2627 | — | | `+CIEV: 1,1` + `+CIEV: 2,0` (answered) | 09:40:59.340 | 2632/2633 | Finger taps green Answer button at 09:40:59–41:00 (Δ≈0.7s) | -| `+CIEV: 1,0` (call ends) | 09:41:18.344 | 2797 | Finger taps red hang-up at 09:41:16–17, note's 09:41:18 (Δ≈0s) | +| `+CIEV: 1,0` (call ends) | 09:41:18.344 | 2797 | Finger taps red hang-up at 09:41:16–17, note's 09:41:18 (Δ ≈ 1.3–2.3 s from the tap) | Talk time: 09:40:59.340–09:41:18.344 ≈ **19.0s**. Ringing this time lasted ~35.9s (8 full RING cycles) before answer, vs. call 1's ~14.2s (3 RING cycles) — both consistent @@ -203,10 +204,10 @@ Sent Enhanced Setup Synchronous Connection (0x043d), Connection Handle 0x0002 Rcvd Synchronous Connection Complete (0x2c), Status Success → new sync Connection Handle (0x0005 for call 1, 0x0006 for call 2), Link Type: eSCO ``` -Call 1: frames 1646 (cmd) / 1656 (complete), 09:39:19.697/.821 — **7.6ms after** -`+CIEV: 2,1` and 76ms before the first `RING`. Call 2: frames 2274/2282, -09:40:23.468/.731 — essentially identical timing relative to that call's own -`+CIEV: 2,1`. Both sync connections are cleanly torn down (`Disconnect Complete`, +Call 1: frames 1646 (cmd) / 1656 (complete), 09:39:19.697/.821 — the command **7.8 ms after** +`+CIEV: 2,1` (1639), the complete 1.8 ms before the first `RING` (1658, 09:39:19.823) *(corrected 2026-10-15, `ai-sessions/0094`)*. Call 2: frames 2274/2282, +09:40:23.468/.731 — the command 0.3 ms after that call's own `+CIEV: 2,1` (2273, no `+BCS` in +between), the complete 0.8 ms before its first `RING` (2284). Both sync connections are cleanly torn down (`Disconnect Complete`, reason `0x16`) at the same moment their respective call's `+CIEV: 1,0` fires (§1, §4). **Reproduction (added 2026-09-03, same hex-and-script-rule gap as §3 above):** @@ -236,13 +237,13 @@ Raw HCI command/event bytes, via `tshark -r CAP-008-btsnoop_hci.log -Y "frame.nu ``` Per Wireshark's own HCI dissector (`tshark -r CAP-008-btsnoop_hci.log -Y "frame.number==1646" -V`, not a hand-decode of the raw bytes above): both the Transmit and Receive Coding Format fields -report `Codec: mSBC (0x05)`, `Transmit`/`Receive Codec Frame Size: 60`, `Input`/`Output Bandwidth: -32000` — an exact match to this section's "mSBC (wideband speech), Tx/Rx bandwidth 8000 B/s, frame -size 60" summary. +report `Codec: mSBC (0x05)`, `Transmit`/`Receive Codec Frame Size: 60`, and `Tx`/`Rx Bandwidth +(bytes/s): 8000` (bytes `40 1f 00 00` ×2) — an exact match to this section's "mSBC (wideband speech), Tx/Rx bandwidth 8000 B/s, frame +size 60" summary; `Input`/`Output Bandwidth: 32000` are separate fields *(corrected 2026-10-15, `ai-sessions/0094`)*. `mSBC` (wideband) was selected via a standard Codec Connection Setup exchange just -before each eSCO setup: `Sent +BCS: 2` → `Rcvd AT+BCS=2` → `OK` (frames 1641/1644/1645 -for call 1). This matches `AT+BAC=1,2,3`'s advertised codec list from §3 (codec ID 2 = +before call 1's eSCO setup: `Sent +BCS: 2` → `Rcvd AT+BCS=2` → `OK` (frames 1641/1644/1645); +call 2 has no `+BCS` exchange and reuses the codec *(corrected 2026-10-15, `ai-sessions/0094`)*. This matches `AT+BAC=1,2,3`'s advertised codec list from §3 (codec ID 2 = mSBC); codec ID 3 (LC3/Super Wideband) was offered but not selected in either call. **This closes `CAP-001-FINDINGS.md` §6 Task 6's open question**: SCO/eSCO absence in @@ -250,7 +251,7 @@ every prior capture was correctly attributed to "no capture ever contained a rea call," not to the Buds/phone never using this mechanism — confirmed here with two clean, matching occurrences. -## 6. DLCI 0x0a (RFCOMM channel 5) — 14th consecutive silent result, now narrowed further (🔴 OPEN QUESTION, narrowed) +## 6. DLCI 0x0a (RFCOMM channel 5) — 14th silent result, now narrowed further (🔴 OPEN QUESTION, narrowed) Channel 5/DLCI 0x0a opens (`SABM`/`UA`, frames 1042/1049) exactly like every other DLCI, but carries **zero payload frames** for the rest of the 309s session — the same @@ -378,7 +379,7 @@ noted for a future capture if that data point is wanted. watching for further unsolicited `+CIEV: 6,...` events would help confirm whether this is a real, general mechanism or a one-off. 5. DLCI 0x0a (§6) remains unidentified — now with "the call's audio path" ruled out on - top of the existing eleven silent captures. No further hypothesis is currently + top of the 13 earlier silent captures (§6). No further hypothesis is currently proposed for what would trigger payload on it. --- diff --git a/captures/CAP-009-2026-08-23_18-33-52_20-12-55-Group_X/CAP-009-EVENT-NOTES.md b/captures/CAP-009-2026-08-23_18-33-52_20-12-55-Group_X/CAP-009-EVENT-NOTES.md index 17f9a2a0..a7174a22 100644 --- a/captures/CAP-009-2026-08-23_18-33-52_20-12-55-Group_X/CAP-009-EVENT-NOTES.md +++ b/captures/CAP-009-2026-08-23_18-33-52_20-12-55-Group_X/CAP-009-EVENT-NOTES.md @@ -55,7 +55,7 @@ in `recording1`'s last ~70 seconds, not inferred from the log alone. ### MAC verification (independent re-derivation, not taken on trust) -The task brief's MAC placeholder was not filled in. Re-derived and cross-checked four independent +The task brief's MAC placeholder was not filled in. Re-derived and cross-checked five independent ways before filtering on it: 1. **Connection-event address.** The log's only `HCI_Connection_Complete` events @@ -73,9 +73,9 @@ ways before filtering on it: 75 ``` - (Note: `tshark -Y 'frame contains "google-pixel-buds-pro-v1"'` returned 0 matches for reasons - not fully diagnosed — likely a display-filter string-escaping quirk with the embedded hyphens — - so this check was done as a raw byte search instead; the hex is directly visible in every + (Note: `tshark -r CAP-009-btsnoop_hci.log -Y 'frame contains "google-pixel-buds-pro-v1"'` matches + 75 frames, exit 0 — the same count as the raw byte search above (re-run 2026-10-15, + `ai-sessions/0094`; an earlier "0 matches" did not reproduce); the hex is directly visible in every `Group 0x0e Code 0x02` frame preceding a `Code 0x01` battery push, e.g. frame 875's payload.) 3. **SDP profile fingerprint.** The peer at this connection advertises/queries Hands-Free (HFP), Audio Sink/A2DP, and AVRCP service records (frames 736, 861, 931, 1367, 1403, ...) — consistent @@ -108,7 +108,7 @@ $ ffmpeg -ss -t 600 -i CAP-009-recording1.mp4 \ each step rather than trusting the coarse pass's own row/column reading — **this caught and corrected several misreadings in the coarse pass itself** (see the two flagged rows below), which is exactly the kind of self-check `PROJECT_RULES.md`'s evidence bar calls for: a "clean match" at -low resolution turned out, on close reading, to be off by minutes in more than one case, until +low resolution turned out, on close reading, to be off by ~9 s and ~35–45 s in two cases, until verified at 1-frame precision. **Mandatory full-density coverage of 19:50:00–20:12:55:** this window (both buds going into the @@ -124,7 +124,7 @@ a value change, since a percentage-only crop cannot show *why* a value changed. ## Independent findings — diff against the maintainer's original notes All 7 value-change events the maintainer had noted **exist and are confirmed** by the independent -pass. Two of the maintainer's own timestamps are **refined by minutes** (not contradicted — the +pass. Several of the maintainer's own timestamps are **refined by a few seconds** (B 2–5 s, C ~2 s, E ~1 s; not contradicted — the maintainer's times were "when I happened to check," not "when it changed," which is exactly the periodic-manual-check limitation the capture design (§Procedure) anticipated). No event the maintainer noted was found to be spurious, and no *additional* battery-value change was found @@ -244,8 +244,9 @@ numbering happens to match several prior sessions', but that is not guaranteed i own notes (this session's specific requirement, beyond the original Group X checklist). - [x] Extract every `AT+CIND?` (`battchg`) value across the session with its timestamp. → exactly **one** occurrence (frame 884, 18:34:01.90); never repeated. See `CAP-009-FINDINGS.md` §1. -- [x] Extract every `AT+BIEV=2,...` value across the session with its timestamp. → 69 pushes (73 - total `AT+BIEV`/`AT+CIND` frames combined), 5 distinct values, irregular cadence. See +- [x] Extract every `AT+BIEV=2,...` value across the session with its timestamp. → 68 pushes (71 + total `AT+BIEV`/`AT+CIND` frames combined: plus `AT+BIEV=1,1`, `AT+CIND=?`, `AT+CIND?` — + `tshark -Y 'frame contains "AT+BIEV" or frame contains "AT+CIND"'`), 5 distinct values, irregular cadence. See `CAP-009-FINDINGS.md` §2. - [x] Compare both trends against the on-screen battery percentage — done independently (video) and cross-checked against the maintainer's notes, table above. diff --git a/captures/CAP-009-2026-08-23_18-33-52_20-12-55-Group_X/CAP-009-FINDINGS.md b/captures/CAP-009-2026-08-23_18-33-52_20-12-55-Group_X/CAP-009-FINDINGS.md index afddcdab..d84b5058 100644 --- a/captures/CAP-009-2026-08-23_18-33-52_20-12-55-Group_X/CAP-009-FINDINGS.md +++ b/captures/CAP-009-2026-08-23_18-33-52_20-12-55-Group_X/CAP-009-FINDINGS.md @@ -117,10 +117,10 @@ RFCOMM/HFP): | 20632 | 19:26:28.135 | 89 | `41 54 2b 42 49 45 56 3d 32 2c 38 39 0d` | | 26459 | 19:50:29.368 | 88 | `41 54 2b 42 49 45 56 3d 32 2c 38 38 0d` | -**Timing is not a fixed ~6–7s cadence over this session.** Gap statistics across all 68 -consecutive-frame intervals (`frame.time` deltas, computed from the full 69-row extraction above): +**Timing is not a fixed ~6–7s cadence over this session.** Gap statistics across the 67 +intervals between the 68 `AT+BIEV=2` pushes (`frame.time` deltas; the `AT+BIEV=1,1` row excluded): minimum 0.015s (a near-duplicate retransmission), maximum 878.9s (≈14.6 min), median 20.5s; 15 of -68 gaps are under 10s, 5 are over 5 minutes. **This does not contradict `CAP-001-FINDINGS.md` +67 gaps are under 10s, 5 are over 5 minutes *(corrected 2026-10-15, `ai-sessions/0094`)*. **This does not contradict `CAP-001-FINDINGS.md` §3's ~6–7s observation** — that observation was made in the few seconds immediately following SLC setup (a plausible "settling burst"), which this capture also shows (frames 972 and the next several pushes cluster tightly after 18:34:02). What this capture adds is that the cadence does @@ -143,8 +143,8 @@ timeline (`CAP-009-EVENT-NOTES.md`): | Left earbud | 96 → 95 (19:26:28.02–29.02) → 94 (19:50:29.02–30.02) → 100 (20:00:01.68–02.01, placed in case) | No — none of 96/95/94/100 appear in the `AT+BIEV` sequence | | Case | 72 → 71 (19:02:47.35–48.02) → 68 (20:00:01.68–02.01) → 75 (20:11:02.03–05.70) | No — none of 72/71/68/75 appear in the `AT+BIEV` sequence | -Every `AT+BIEV` transition lands at or *extremely close to* the on-screen change for R (within -~1s), but *minutes before* the on-screen change for L (which `AT+BIEV` doesn't carry at all — the +Every `AT+BIEV` transition lands at or before the on-screen change for R — the first (frame 14612) +within ~1s, the later two ~3 min and ~9.5 min earlier (table below) *(corrected 2026-10-15, `ai-sessions/0094`)* — and *minutes before* the on-screen change for L (which `AT+BIEV` doesn't carry at all — the L values below are `PROTOCOL.md` §4.3 Option E's DLCI `0x08` timestamps, §6) — independently confirming the on-screen UI does not repaint on every individual wire push, but on some other, coarser trigger (see the case-jump timing in §4 for the clearest example of this): @@ -280,7 +280,7 @@ recorded in `PROTOCOL.md` §4.3 Option A at this same HYPOTHESIS level (`AGENTS. | Does `AT+CIND?`'s `battchg` track a real battery-level change over time? | 🟢 **No** — it is queried exactly once, at SLC setup, and never refreshes again, even across 5 percentage points (93 → 88; corrected 2026-10-03, was "~13") of real, confirmed change on the peer's Right earbud during the same HFP session. | | Does `AT+BIEV=2` track a real battery-level change over time? | 🟢 **Yes, for the Right earbud specifically**, in this session — 5 distinct values over 86 minutes, each matching R's on-screen value, each landing at or before the corresponding on-screen check. | | Is either indicator a case- or Left-earbud-aware aggregate? | 🟢 **No** — neither indicator's value ever matches Left or Case at any point in this 101-minute session (§3). | -| Is `AT+BIEV`'s push cadence a fixed ~6–7s regardless of value change (as `AGENTS.md` §5 currently states, based on `CAP-001`)? | 🟡 **Not over this session's idle stretches** — tight (~6–7s-class) spacing is seen right after SLC setup, consistent with `CAP-001`, but gaps widen to multiple minutes (max ≈14.6 min) later in the same session. Flagged for maintainer review, not silently corrected here (`AGENTS.md` §6). | +| Is `AT+BIEV`'s push cadence a fixed ~6–7s regardless of value change (as `AGENTS.md` §5 stated before it was updated to cite this capture)? | 🟡 **Not over this session's idle stretches** — tight (~6–7s-class) spacing is seen right after SLC setup, consistent with `CAP-001`, but gaps widen to multiple minutes (max ≈14.6 min) later in the same session. Flagged for maintainer review, not silently corrected here (`AGENTS.md` §6). | | Does the on-screen UI repaint immediately on each wire push? | 🟡 **Evidence points to no** — R's on-screen lag behind its own wire value grows from sub-second to ~9.5 minutes across the session; a coarser repaint trigger (foreground/timer/connection-state-change) is a plausible explanation, not confirmed (§3). | | Does a second, independent RFCOMM battery mechanism exist on DLCI `0x04`? | 🟡 **Likely** — `Group 0x03 Code 0x03` tracks Left/Right in near-lockstep with `AT+BIEV`/Option E for the whole session outside the L-charging period, and is proposed as a candidate for `PROTOCOL.md` §4.3 Option B's still-unconfirmed battery code (§7). | | What updates the screen after the HFP/Option E channels close post-reconnect (20:02:27+)? | 🟡 **Likely a BLE Fast Pair Battery Notification scan** (`PROTOCOL.md` §4.3 Option A) — a `LE Set Extended Scan Enable` + advertising-report stream starts within ~0.5s of the reconnect and brackets the on-screen R update, but the specific advertisement wasn't confirmed as belonging to this device (§4). | @@ -323,7 +323,7 @@ frame 28698 19:59:33.360 0e0100230a210a03616c6c121a0a060864100118010a060858100 ``` **🟢 FACT (this capture, 4th independent confirming session):** across all 75 occurrences, L and R -track the maintainer's on-screen Left/Right values exactly, at every transition, for the entire +match the maintainer's on-screen Left/Right values in value and order at every transition (the screen updates up to ~13 min later, §3) *(corrected 2026-10-15, `ai-sessions/0094`)*, for the entire 101-minute session — the same result as §3's `AT+BIEV` cross-check for R, now independently reproduced via a structurally unrelated channel/message (DLCI `0x08` vs. DLCI `0x0c`/HFP), and extended to L (which HFP's `AT+BIEV=2` does **not** carry — §3). This is also the **first** @@ -378,7 +378,7 @@ $ tshark -r CAP-009-btsnoop_hci.log -Y 'bthci_acl.chandle==0x0002 and btrfcomm.d **208 occurrences** (dominant message on this DLCI — see the full Group/Code inventory below). Decoding each as `03 03 00 03 ff` and printing only the frames where `(b1,b2)` changes -from the previous one gives **14 transitions across the whole session**: +from the previous one gives **13 transitions across the whole session** (14 rows incl. the first frame): | Frame | Time | b1 | b2 | Note | | --- | --- | --- | --- | --- | @@ -437,7 +437,7 @@ out of `BATT-006` scope):** ``` $ tshark -r CAP-009-btsnoop_hci.log -Y 'bthci_acl.chandle==0x0002 and btrfcomm.dlci==0x04 and btrfcomm.len>0' \ -T fields -e data.data | cut -c1-4 | sort | uniq -c | sort -rn - 208 0303 (this section) 6 0813 6 030a ("Revision 6" device-info string, cf. CAP-002-FINDINGS.md §2a) + 208 0303 (this section) 6 0813 6 030a (Device Information burst, starting with the session nonce 03 0a; "Revision 6" is code 03 09; cf. CAP-002-FINDINGS.md §3) 34 ff01 6 0811 6 0308 29 0734 6 0741 3 030b 18 0711 6 0740 diff --git a/captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/CAP-010-EVENT-NOTES.md b/captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/CAP-010-EVENT-NOTES.md index 0999aa2b..a516c128 100644 --- a/captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/CAP-010-EVENT-NOTES.md +++ b/captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/CAP-010-EVENT-NOTES.md @@ -8,8 +8,8 @@ correlation — this file is the *event timeline*, `CAP-010-FINDINGS.md` is *wha protocol*. **⚠️ Procedure note (see `CAP-010-FINDINGS.md` §1 for the full evidenced writeup):** this session -is filed under Group W, but the video and log both show a standard "Forget Buds, re-pair via the -official app" flow on the **same** Pixel 7a used for `CAP-001`–`CAP-007` — not Group W's actual +is filed under Group W, but the video and log both show a standard "forget, re-pair via system +Bluetooth settings, then the official app's setup" flow on the **same** Pixel 7a used for `CAP-001`–`CAP-007` — not Group W's actual defined cache-busting method (`adb shell pm clear com.android.bluetooth`, or a first-ever connection from the project's Pixel 9a). This does not make the capture useless — it independently reproduces several prior findings — but it does **not** achieve Group W's stated goal of forcing a @@ -52,7 +52,7 @@ comparable to log frame timestamps without offset correction. | 11:43:35–41 | (not directly visible on screen) SDP service discovery, RFCOMM channel setup, HFP AT-command handshake | App (Auto) | `PAIR-001` | SDP `Service Search Attribute Request/Response` for PnP/L2CAP/Phonebook/Audio Sink/HID/Hands-Free/AVRCP (frames 1320–1844); RFCOMM channels 0, 6 (Hands-Free), 4, 5, 1, 2 opened (frames 1483–1853); `AT+BRSF`…`AT+CMEE` handshake incl. `AT+BIEV=2,100` battery report (frames 1515–1680) | | 11:43:40–44 | (not directly visible on screen) BLE GATT exchange — Fast Pair Key-based-Pairing-shaped write/notify cluster on handles `0x0c04`/`0x0c05`/`0x0c0a`/`0x0c0c`/`0x0c0d`/`0x0c13`/`0x0c14`, plus reads of `0x0f28` (`0x31`) and `0x0f2a` (`"Revision 6"`) — **no primary-service or characteristic discovery request anywhere** | App (Auto) / Buds (Auto) | `GATT-001` (attempted) | `LE Enhanced Connection Complete` 11:43:40.061 (frame 1965); full ATT exchange frames 1985–2089; see `CAP-010-FINDINGS.md` §2–§3 for the complete decode | | 11:43:42 | Popup: **"Pixel Buds Pro 2 — Save device to [account] to connect more quickly to your other devices"** (Cancel / Save) | App (Auto) | — | — | -| 11:43:47 | Notification banner appears: **"Pixel Buds Pro 2: Left 100% Case 42% Right 100%"** (while the Save popup is still showing) | App (Auto) | — | `AT+BIEV=2,100` (frame 1671, 11:43:36.247) reports the same 100% aggregate battery indicator | +| 11:43:47 | Notification banner appears: **"Pixel Buds Pro 2: Left 100% Case 42% Right 100%"** (while the Save popup is still showing) | App (Auto) | — | `AT+BIEV=2,100` (frame 1671, 11:43:36.247) reports 100% on the HF battery indicator (which tracks the Right earbud, `CAP-009-FINDINGS.md` §3) | | ~11:43:57 | User taps **Save** | User (App) | — | — | | ~11:43:58 | Popup: **"Allow a connection to your Pixel Buds — Continue"** (setup intro screen) | App (Auto) | — | — | | 11:44:07 | User taps **Set up** (from a Pixel Buds app card, distinct from the Continue screen above) | User (App) | — | — | @@ -62,7 +62,7 @@ comparable to log frame timestamps without offset correction. | 11:44:34 | **Device details** screen shown: `Pixel Buds Pro 2 van Ted`, Left 100% / Case 42% / Right 100%, `Active`; Forget/Disconnect buttons; Digital assistant / Controls and gestures / Sound / Hearing wellness / More settings rows — setup complete | App (Auto) | — | — | | ~11:45:00 | Device details screen still shown, unchanged (Left 100%, Case 42%, Right 100%) | — | — | — | | 11:45:01 | End video recording | — | — | — | -| *(after video ends)* | `LE Disconnect Complete` at 11:43:49.236 (frame 2195, inside the video window but not a visible on-screen event) and a full RFCOMM channel teardown/reopen cycle at ~11:46:58–11:47:04 (frames 2604–2837, well after recording stopped) | — | — | See `CAP-010-FINDINGS.md` §5 | +| *(log-only events)* | `LE Disconnect Complete` at 11:43:49.236 (frame 2195, inside the video window but not a visible on-screen event) and a full RFCOMM channel teardown/reopen cycle at ~11:46:58–11:47:04 (frames 2604–2837, well after recording stopped) | — | — | See `CAP-010-FINDINGS.md` §5 | ## Corrections vs. the original draft of this file diff --git a/captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/CAP-010-FINDINGS.md b/captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/CAP-010-FINDINGS.md index a7b68c10..3adc2e1b 100644 --- a/captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/CAP-010-FINDINGS.md +++ b/captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/CAP-010-FINDINGS.md @@ -70,7 +70,7 @@ Checked exhaustively across the **entire** log, not just the video-covered windo tshark -r CAP-010-btsnoop_hci.log -Y "btatt.opcode in {0x04,0x05,0x10,0x11}" ``` -→ **zero matches.** No `Find Information` (`0x04`/`0x05` — characteristic/descriptor discovery) +→ **zero matches**, exit 0 (positive control: the same filter on `CAP-034-btsnoop_hci.log`, a live discovery, matches 63 frames). No `Find Information` (`0x04`/`0x05` — characteristic/descriptor discovery) and no `Read By Group Type` (`0x10`/`0x11` — primary service discovery) request or response exists anywhere in this 2,903-packet, 350.7s capture. @@ -80,7 +80,7 @@ Every one of the 35 ATT packets that *does* exist — tshark -r CAP-010-btsnoop_hci.log -Y "btatt" -T fields -e frame.number -e frame.time -e btatt.opcode -e _ws.col.Info ``` -— is one of three kinds, none of which is discovery: +— is one of these kinds, none of which is discovery (35 = 2 `Exchange MTU` packets, frames 1985/1990, + 6 `Read By Type` + 27 handle-based packets) *(corrected 2026-10-15, `ai-sessions/0094`)*: 1. **Three `Read By Type Request/Response` pairs for standard, well-known 16-bit GAP/GATT UUIDs** (`Device Name` `0x2a00` — frames 1988/1994; `Database Hash` `0x2b2a` — frames 1995/1997; @@ -93,7 +93,7 @@ tshark -r CAP-010-btsnoop_hci.log -Y "btatt" -T fields -e frame.number -e frame. 2. **The `Database Hash` read is specifically the standard GATT Caching mechanism** (Core Spec 5.1+, already characterized in `CAP-003-FINDINGS.md` §1) — a client uses it to check whether its cached database is still valid, skipping full discovery if the hash matches. -3. **Ten `Read`/`Write`/`Handle Value Notification` exchanges against raw, literal 16-bit +3. **27 `Read`/`Write`/`Handle Value Notification` packets (frames 2003–2041, plus two more `0x0f28` read pairs 2084/2086 and 2087/2089) against raw, literal 16-bit handles** (`0x0f28`, `0x0f2a`, `0x0c04`, `0x0c05`, `0x0c0a`, `0x0c0c`, `0x0c0d`, `0x0c13`, `0x0c14`) — see §3. Accessing a handle directly by number is only possible if the client already knows that number; no ATT sub-procedure resolves an unknown handle from a UUID without @@ -246,8 +246,8 @@ This is a **fourth** independent capture (`CAP-001` reconnect, `CAP-002`/`CAP-00 Service 0xFE2C`, `Device Information 0x180A`) remain the leading hypotheses to check the resolved handles against. (`Accessory Non-Owner Service` is explicitly **not** a candidate to check against — investigating it is out of scope per `DECISIONS.md` ADR-008.) -3. Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md`'s Capture Index row for `CAP-010` (currently - `*planned*`/`TBD`) and `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s `GATT-001` row to record this +3. (Done: the Capture Index row for `CAP-010` records this attempt.) Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md`'s Capture Index row for `CAP-010` + and `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s `GATT-001` row to record this attempt and its actual outcome, so a future session doesn't re-attempt the same already-insufficient method under the Group W label again. 4. Investigate the ~11:46:58–11:47:04 post-video RFCOMM reconnect cycle (§5) with a session that diff --git a/captures/CAP-011-2026-08-21_09-45-17_09-55-16-Group_Q/CAP-011-EVENT-NOTES.md b/captures/CAP-011-2026-08-21_09-45-17_09-55-16-Group_Q/CAP-011-EVENT-NOTES.md index dccdeb58..ce522cf0 100644 --- a/captures/CAP-011-2026-08-21_09-45-17_09-55-16-Group_Q/CAP-011-EVENT-NOTES.md +++ b/captures/CAP-011-2026-08-21_09-45-17_09-55-16-Group_Q/CAP-011-EVENT-NOTES.md @@ -35,7 +35,7 @@ so this isn't silently treated as if the intended isolation held. | Test device | Pixel 7a, Android 17 (⚪ ASSUMPTION), official Pixel Buds Companion App (version not visible on screen) | | Video file | `CAP-011-recording.mp4` (599.9s, 09:45:17–09:55:16 local time) | | Log file | `CAP-011-btsnoop_hci.log` (09:45:01.437–10:02:36.612, brackets the video) | -| Buds MAC (partial, per `AGENTS.md` §7/§9) | Classic peer not confirmed this session (no `bthci_evt.code==0x03` Connection Complete checked); BLE side uses rotating private addresses — see §Decode | +| Buds MAC (partial, per `AGENTS.md` §7/§9) | Classic peer: one Connection Complete (`-Y 'bthci_evt.code==0x03'` → frame 728, 09:45:24.958, handle `0x0002`); all 338 RFCOMM payload frames are on that handle. BLE side uses rotating private addresses — see §Decode | ## Procedure (per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group Q #18) @@ -49,7 +49,7 @@ so this isn't silently treated as if the intended isolation held. | **09:45:17** | **Observation window start.** Video first frame: Bluetooth off, case open and empty, buds sitting beside it | — | `BATT-002`, `BATT-003` | Log itself starts 16s earlier (09:45:01), already showing BLE advertisement traffic (see §Decode) — i.e. before the video's own start, while the case was presumably in the same open/idle state off-camera | | ~09:45:20–09:45:47 | Bluetooth turned on; official app opened; Buds connect via classic RFCOMM; "Device details" screen opened (Left 93%, Case 89%, Right 88%, "Active") | User (App) | — | Classic connection + GATT discovery traffic begins in the log around this window (not isolated further, since this is not this Group's actual target traffic) | | 09:45:47–09:52:25 | "Device details" screen left open and idle, no further taps; percentages remain unchanged (Left 93%, Case 89%, Right 88%) | User (idle) | `BATT-002`, `BATT-003` | Fast Pair Service (UUID `0xFE2C`) BLE advertisements continue throughout — see §Decode | -| **09:52:25.8** (±1 video frame, ~0.03s) | **Correction, added 2026-08-23 — re-examined on request.** On-screen battery display changes: **Left 93%→92%, Right 88%→87%** (Case unchanged at 89%). Originally logged as "around 09:45:47," which was actually the moment the screen first opened at its *initial* values, not a change — corrected here after re-extracting video frames at sub-second granularity (`ffmpeg`, frame-by-frame contact sheets) and cross-checking against the burned-in wall-clock overlay (frame at video t=428.8s reads "09:52:26" with 92/89/87 already showing). Last frame showing 93/88: video-relative t=428.779s; first frame showing 92/87: t=428.812s. See `CAP-011-FINDINGS.md` §7 for the full wire correlation | User (Hardware, passive — battery drain) | `BATT-002`, `BATT-003` | See `CAP-011-FINDINGS.md` §7 — a DLCI 0x08 private-envelope message (`Group 0x0e Code 0x01`, frame 6103, 09:52:24.925) decodes to a 3-entry structure whose first two entries (92, index 1) and (87, index 2) match these exact new values, ~0.86s before the UI updates; a second message (`Group 0x04 Code 0x03`, frame 6115) independently confirms the Right value (87) | +| **09:52:25.8** (±1 video frame, ~0.03s) | **Correction, added 2026-08-23 — re-examined on request.** On-screen battery display changes: **Left 93%→92%, Right 88%→87%** (Case unchanged at 89%). Originally logged as "around 09:45:47," which was actually the moment the screen first opened at its *initial* values, not a change — corrected here after re-extracting video frames at sub-second granularity (`ffmpeg`, frame-by-frame contact sheets) and cross-checking against the burned-in wall-clock overlay (frame at video t=428.8s reads "09:52:26" with 92/89/87 already showing). Last frame showing 93/88: video-relative t=428.779s; first frame showing 92/87: t=428.812s. See `CAP-011-FINDINGS.md` §7 for the full wire correlation | User (Hardware, passive — battery drain) | `BATT-002`, `BATT-003` | See `CAP-011-FINDINGS.md` §7 — a DLCI 0x08 private-envelope message (`Group 0x0e Code 0x01`, frame 6103, 09:52:24.925) decodes to a 3-entry structure whose first two entries (92, index 1) and (87, index 2) match these new values — index 2 changed ~0.86s before the UI update (frame 6103), index 1 already at 09:49:28.326 (frame 4222), ~2 min 57 s before it; a second message (`Group 0x04 Code 0x03`, frame 6115) independently confirms the Right value (87) | | 09:52:25.8–09:55:16 | "Device details" screen remains open, no further taps; percentages stay 92/89/87 for the rest of the video | User (idle) | `BATT-002`, `BATT-003` | — | | **09:55:16** | **Video ends** (log continues about 7 more minutes, to 10:02:36, unreviewed on camera; corrected 2026-10-08, `ai-sessions/0081`) | — | — | — | | *(off-camera, log only)* 09:57:… / 10:00:45 | Not video-observable, but the wire shows the same `Group 0x0e`/`Group 0x04 Code 0x03` pattern recurring twice more after the video ends, with Right declining further (87%→86% by 10:00:45) | Buds (Auto) | `BATT-002`, `BATT-003` | See `CAP-011-FINDINGS.md` §7 | diff --git a/captures/CAP-011-2026-08-21_09-45-17_09-55-16-Group_Q/CAP-011-FINDINGS.md b/captures/CAP-011-2026-08-21_09-45-17_09-55-16-Group_Q/CAP-011-FINDINGS.md index 17b24762..b5c66da9 100644 --- a/captures/CAP-011-2026-08-21_09-45-17_09-55-16-Group_Q/CAP-011-FINDINGS.md +++ b/captures/CAP-011-2026-08-21_09-45-17_09-55-16-Group_Q/CAP-011-FINDINGS.md @@ -20,7 +20,7 @@ staged here per `PROJECT_RULES.md` §2. Every claim below carries a status per ` | Log file | [`CAP-011-btsnoop_hci.log`](./CAP-011-btsnoop_hci.log) — 1,055.2s, 2026-08-21 09:45:01.437–10:02:36.612 (+0200) | | Notes file | [`CAP-011-EVENT-NOTES.md`](./CAP-011-EVENT-NOTES.md) | | Video file | [`CAP-011-recording.mp4`](./CAP-011-recording.mp4) — 599.9s, 09:45:17–09:55:16 local time | -| Buds MAC (partial, per `AGENTS.md` §7/§9) | Classic peer not confirmed this session; BLE side uses 5 rotating private addresses, see §3 | +| Buds MAC (partial, per `AGENTS.md` §7/§9) | Classic peer: the log's only Connection Complete (`-Y 'bthci_evt.code==0x03'`) is frame 728, 09:45:24.958, handle `0x0002`; all 338 RFCOMM payload frames are on that handle. BLE side uses 5 rotating private addresses, see §3 | ## 2. Methodology & procedure deviation @@ -178,11 +178,12 @@ constant in all 4 samples): | 09:52:24.925 | value=**92**, flag=1 | value=**87**, flag=1 | value=92 | | 10:00:45.105 | value=**92**, flag=1 | value=**86**, flag=1 | value=92 | -**Entries idx=1 and idx=2 match the on-screen Left/Right percentages exactly at every sampled -point** — including the 09:45:26 sample (93/88, matching the screen's first-opened values, -video-confirmed) and the 09:52:24→09:52:25.8 transition (92/87, this session's own finding, wire -event **~0.86s before** the UI visibly updates — plausible app-side render latency, not -independently measured further). Entry idx=2 (Right) also **keeps declining** across the two +**Entries idx=1 and idx=2 carry the on-screen Left/Right values, but the screen lags the wire** +*(corrected 2026-10-15, `ai-sessions/0094`)* — the 09:45:26 sample (93/88) matches the screen's +first-opened values (video-confirmed); Left 93→92 is on the wire at 09:49:28.326 (frame 4222) +while the screen keeps Left 93% until 09:52:25.8 (~2 min 57 s later, cf. `CAP-009-FINDINGS.md` +§3's UI-lag observation); Right 88→87 (frame 6103, 09:52:24.925) precedes the same UI update by +**~0.86s**. Entry idx=2 (Right) also **keeps declining** across the two off-camera recurrences (88→87→86), consistent with ongoing natural battery drain, not a one-off coincidence. @@ -206,10 +207,10 @@ further decoded. **Method:** since the DLCI 0x08 `[Group:Length:2BE][Value]` envelope is the same shape used throughout this DLCI (§2.2a's sibling private envelope, `PROTOCOL.md` §2.3), the same decode -pipeline from §7b was re-run against every other real capture with DLCI 0x08 traffic (18 sessions -checked: `CAP-001`–`CAP-007`, `CAP-010`, `CAP-015`–`CAP-017`, `CAP-019`–`CAP-025`), each time -picking a `Group 0x0e Code 0x01` frame near a moment where that session's own `CAP-NNN-EVENT-NOTES.md` -independently records an on-screen Left/Case/Right triple: +pipeline from §7b was re-run against the other real captures with DLCI 0x08 traffic (18 sessions: +`CAP-001`–`CAP-007`, `CAP-010`, `CAP-015`–`CAP-017`, `CAP-019`–`CAP-025`); a comparison against an +on-screen Left/Case/Right triple recorded in that session's own `CAP-NNN-EVENT-NOTES.md` is +documented for two of them, `CAP-001` and `CAP-002` (no per-session outcome is recorded for the other 16): ``` CAP-001 frame 1114 (08:51:12.657715+0200) — on-screen ref (CAP-001-EVENT-NOTES.md, 08:51:11 @@ -246,8 +247,7 @@ ADR-014): DLCI 0x08's `Group 0x0e Code 0x01` battery-status message's 3 repeated Left (index 1), Right (index 2), and Case (index 3) battery percentages, each with a `flag` field (`field2`) whose meaning is unconfirmed (observed as `1` whenever a value is fresh; absent when `CAP-011`'s idx=3 carries its stale reading — consistent with, not proof of, a "valid/fresh" bit). -Pushed at irregular intervals (`CAP-011`: 4:02, 2:56, 8:21 apart — not a fixed cadence like HFP's -~6–7s), independently of and in addition to the already-🟢-FACT HFP `AT+BIEV` mechanism +Pushed at irregular intervals (`CAP-011`: 4:02, 2:57, 8:20 apart, frames 1156/4222/6103/11632 *(corrected 2026-10-15, `ai-sessions/0094`)*; HFP's `AT+BIEV` is irregular too, `CAP-009-FINDINGS.md` §2), independently of and in addition to the already-🟢-FACT HFP `AT+BIEV` mechanism (`PROTOCOL.md` §4.3 Option C). This is a **refinement of an already-known message shape**, not a newly-discovered packet type: `CAP-002-FINDINGS.md` §2a (2026-08-12) already documented this exact structure (`field1="all"` + "nested field2 x3 (varint triples)") but did not attempt a semantic diff --git a/captures/CAP-012-2026-08-26_15-30-19_15-32-28-Group_S/CAP-012-EVENT-NOTES.md b/captures/CAP-012-2026-08-26_15-30-19_15-32-28-Group_S/CAP-012-EVENT-NOTES.md index 7d66d257..43d4717b 100644 --- a/captures/CAP-012-2026-08-26_15-30-19_15-32-28-Group_S/CAP-012-EVENT-NOTES.md +++ b/captures/CAP-012-2026-08-26_15-30-19_15-32-28-Group_S/CAP-012-EVENT-NOTES.md @@ -12,7 +12,9 @@ whether `CAP-004`'s Cross-Transport Key Derivation bonding result was an artifac Connect's early BLE connection, or a genuine effect of GMS being disabled (`CAP-004-FINDINGS.md` §8 item 4). The core `GFPS-001` result itself is **not** expected to change — only the §2-equivalent CTKD-vs-classic-SSP bonding-mechanism finding. -**Result: confirmed classic SSP, not CTKD — see `CAP-012-FINDINGS.md` §2/§10.** +**Result: confirmed classic SSP, not CTKD — see `CAP-012-FINDINGS.md` §2/§10.** What gates CTKD is +an SMP pairing on the LE link (🟡 HYPOTHESIS, `DECISIONS.md` ADR-030 Update 2026-10-03), not merely +nRF Connect's early LE link (`CAP-003` had one and used SSP). **Setup, confirmed for this session:** Pixel Buds app uninstalled and Google Play Services disabled; paired via system Bluetooth settings only — **no BLE tool (nRF Connect or otherwise) is @@ -46,7 +48,10 @@ unlike that earlier attempt. ## Event Timeline Timestamps are the video's own on-screen wall-clock overlay; the log uses the same wall clock -(+0200), directly comparable. All timestamps below are video-confirmed to within 1s. +(+0200), but the film clock runs ~1–2 s behind the log clock in this capture: tap-caused wire +events precede the filmed taps by 0.2–2.0 s (`Delete Stored Link Key` 15:31:05.955 vs the 15:31:08 +tap; `User Confirmation Request Reply` 15:31:18.675, frame 515, vs the 15:31:20 Pair tap; +`Create Connection` 15:32:07.802 vs the 15:32:08 tap). Video timestamps below are to within 1s of the film overlay. | Time | Action / Event | Initiator | Test-ID | Wire evidence / Notes | |----------|---|---|---|---| @@ -59,7 +64,7 @@ Timestamps are the video's own on-screen wall-clock overlay; the log uses the sa | 15:30:58 | **"Pixel Buds Pro 2 van Ted"** appears in "Available devices" list | App (Auto) | — | — | | 15:31:08 | User taps "Pixel Buds Pro 2 van Ted" in the list | User (App) | `PAIR-001` | `Delete Stored Link Key` (frame 457, 15:31:05.955) → `Create Connection` (frame 459, 15:31:05.958) → `Connect Complete` (frame 463, 15:31:06.410) → classic `IO Capability Request/Response` (frames 485–495, 15:31:06.448–.466) — see `CAP-012-FINDINGS.md` §2 | | 15:31:09 | System **"Pair with Pixel Buds Pro 2 van Ted?"** dialog appears | App (Auto) | — | Coincides with the IO Capability exchange having just completed (frame 495, 15:31:06.466) | -| 15:31:20 | User taps **Pair** | User (App) | — | `Simple Pairing Complete` (frame 517, 15:31:18.872) — ~1s before this on-screen tap timestamp, within this project's usual ±1–2s video/wire tolerance; `Link Key Notification`/`Authentication Complete`/`Encryption Change` follow through 15:31:19.008 (frames 518–526) | +| 15:31:20 | User taps **Pair** | User (App) | — | `User Confirmation Request Reply` (frame 515, 15:31:18.675) → `Simple Pairing Complete` (frame 517, 15:31:18.872) — the reply is the wire effect of this tap, ~1.3 s before the filmed tap (film clock behind, see above); `Link Key Notification`/`Authentication Complete`/`Encryption Change` follow through 15:31:19.008 (frames 518–526) | | 15:31:21 | App returns to the phone's home screen (Bluetooth status-bar icon now shown) | App (Auto) | — | RFCOMM channels 0/4/5/6 open and settle 15:31:19.39–20.39; channels 4/5 (DLCI 0x08/0x0a) are torn down again (`DISC`, frames 897–902) once the connection-setup handshake completes, leaving only channels 0/6 (mux/HFP) open | | 15:31:35 | User opens system Bluetooth settings again: **"Pixel Buds Pro 2 van Ted — Active. 100% battery."** shown | User (App) | — | Generic Android per-device Bluetooth settings page, no ANC/EQ/Sound section — same as `CAP-004-FINDINGS.md` §7's identical observation for this GMS-disabled/no-app condition | | 15:32:04 | User taps the **"Pixel Buds Pro 2 van Ted"** device row itself (not the gear icon) — this disconnects the device; chip changes to **"Saved"** by 15:32:05 | User (App) | `PAIR-003` | `Sent DISC Channel=6` (frame 962, 15:32:03.203) → `Sent DISC Channel=0` (frame 971, 15:32:03.589) → L2CAP `Disconnection Request`/`Response` (15:32:03.652–.666) — phone-initiated, no hardware event nearby | @@ -101,8 +106,8 @@ Timestamps are the video's own on-screen wall-clock overlay; the log uses the sa values above — no timestamp needed correcting. - **Added:** the 15:32:03–15:32:10 manual disconnect/reconnect (`PAIR-003`) — not present in the original draft at all. This is a real, on-screen, wire-confirmed event window (user taps the - device row twice), not a capture artifact, and it incidentally supplies this project's first - Pixel-7a `PAIR-003` data point — see `CAP-012-FINDINGS.md` §6. + device row twice), not a capture artifact, and it incidentally supplies a further Pixel-7a + `PAIR-003` data point (the first is `CAP-006`) — see `CAP-012-FINDINGS.md` §6. - **Added:** the 15:31:21 "app returns to home screen" / RFCOMM-channel-settling row, and precise wire-evidence citations (frame numbers) for every row, which the original draft's skeleton left as `-`/`TBD`. diff --git a/captures/CAP-012-2026-08-26_15-30-19_15-32-28-Group_S/CAP-012-FINDINGS.md b/captures/CAP-012-2026-08-26_15-30-19_15-32-28-Group_S/CAP-012-FINDINGS.md index d92166e4..c0c34b85 100644 --- a/captures/CAP-012-2026-08-26_15-30-19_15-32-28-Group_S/CAP-012-FINDINGS.md +++ b/captures/CAP-012-2026-08-26_15-30-19_15-32-28-Group_S/CAP-012-FINDINGS.md @@ -131,7 +131,7 @@ its own classic inquiry/pairing. | `Connect Complete` (status `0x00`) | 15:31:06.410 | 463 | | | `Authentication Requested` → `Link Key Request` → **`Link Key Request Negative Reply`** | 15:31:06.439–.446 | 479–484 | No stored key available — consistent with the just-deleted key above | | `IO Capability Request` → `IO Capability Request Reply` (io_capability=`0x01`, DisplayYesNo) → `IO Capability Response` (Buds report io_capability=`0x03`, NoInputNoOutput) | 15:31:06.448–.466 | 485–495 | **Classic SSP negotiation — this step alone already rules out CTKD**, which requires an SMP `Pairing Request` over an LE link instead of this classic IO-capability exchange | -| `Simple Pairing Complete` | 15:31:18.872 | 517 | ~12.4s after the IO Capability exchange, matching the on-screen "Pair with Pixel Buds Pro 2 van Ted?" confirmation dialog being shown (video: appears 15:31:09, tapped 15:31:20 — see §7) rather than a passkey step; no passkey digits are ever shown, same "silent SSP behind a confirmation dialog" shape as `CAP-002-FINDINGS.md` §1 | +| `Simple Pairing Complete` | 15:31:18.872 | 517 | ~12.4s after the IO Capability exchange, matching the on-screen "Pair with Pixel Buds Pro 2 van Ted?" confirmation dialog being shown (video: appears 15:31:09, tapped 15:31:20 — the tap's `User Confirmation Request Reply`, frame 515, is at 15:31:18.675, so the film clock runs ~1–2 s behind the log here; see §7) rather than a passkey step; no passkey digits are ever shown, same "silent SSP behind a confirmation dialog" shape as `CAP-002-FINDINGS.md` §1 | | `Link Key Notification` (new key stored) → `Authentication Complete` | 15:31:18.881 | 518–519 | | | `Set Connection Encryption` → `Encryption Change [v2]` | 15:31:18.900–15:31:19.008 | 521–526 | | @@ -155,10 +155,10 @@ triggers this) — reuses the just-established stored key, no SSP at all:** **Conclusion, answering this session's own hypothesis test directly:** with no BLE tool used and no pre-existing BLE link to the Buds at any point, classic bonding follows the **same classic SSP path as `CAP-002`/`CAP-003`** (`PROTOCOL.md` §5.1's "fresh pairing" state machine) — **not** -`CAP-004`'s Cross-Transport Key Derivation path. This directly confirms the hypothesis -`CAP-004-FINDINGS.md` §8 item 4 raised: **CTKD was an artifact of nRF Connect's early BLE -connection specifically, not a genuine effect of GMS being disabled or the Pixel Buds app being -absent.** Three independent sessions (`CAP-002`, `CAP-003`, `CAP-012`) show classic SSP under +`CAP-004`'s Cross-Transport Key Derivation path. This answers the question +`CAP-004-FINDINGS.md` §8 item 4 raised: **CTKD in `CAP-004` was not an effect of GMS being +disabled or the Pixel Buds app being absent** *(corrected 2026-10-15, `ai-sessions/0094`: the +attribution to nRF Connect's early BLE connection as such is withdrawn, see below)*. Three independent sessions (`CAP-002`, `CAP-003`, `CAP-012`) show classic SSP under three different conditions (official app; nRF Connect; GMS-disabled/no-app, no BLE tool at all) and `CAP-004` (and later `CAP-014`) shows CTKD. What separates them is **not** the mere existence of an LE link: `CAP-003` had an LE link before classic pairing (frame 1621 `LE Enhanced Connection Complete`, 1689 `Create Connection`, 1750 `Simple Pairing Complete`) and still used SSP. The CTKD sessions are the ones with an **SMP pairing on the LE link** (`CAP-004` frame 1856, @@ -211,7 +211,7 @@ Extraction command: tshark -r CAP-012-btsnooz_hci.log -Y "btrfcomm.dlci==0x08" -T fields -E separator='|' \ -e frame.number -e frame.time_epoch -e _ws.col.Info -e frame.len -e frame.cap_len -e data.data ``` -Header bytes recovered, Sequence 1 (frames 854–895, 15:31:19.850–.965 — 13 header pairs, in +Header bytes recovered, Sequence 1 (frames 854–895, 15:31:19.850–.965 — 18 header pairs, in order): `05 0c`, `05 0a`, `04 02`, `04 03`, `04 04`, `04 11`, `04 05`, `04 13`, `04 12`, `04 15`, `04 14`, `04 16`, `0e 04`, `09 03`, `03 01`, `0e 02`, `0e 01`, `09 02` (plus several zero-payload/header-only frames, e.g. 878/889/895, and one ambiguous 3-byte fragment at frame 882, @@ -265,9 +265,9 @@ taps the same row again at 15:32:08 ("Connecting…" shown by 15:32:10), produci `Create Connection` at 15:32:07.802 (frame 1001). This is a clean, on-screen-confirmed instance of `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s `PAIR-003` -("Disconnect and reconnect to an already-bonded device") — previously captured only on the -Pixel 9a (`TESTPLAN_BLUETOOTH_HCI_SNOOP.md` row for `PAIR-003`: "no dedicated Pixel 7a scenario -yet"). **`CAP-012` supplies that missing Pixel 7a data point, incidentally, not by design.** +("Disconnect and reconnect to an already-bonded device") — a further Pixel 7a data point, +incidentally, not by design; the first Pixel 7a occurrences are `CAP-006` (2026-08-15) and +`CAP-007` (`TESTPLAN_BLUETOOTH_HCI_SNOOP.md` `PAIR-003` row, corrected 2026-08-28). **Relevant to `PROTOCOL.md` §6's open HFP-recurrence question** (`CAP-008-FINDINGS.md` §3's narrowing: "does a reconnect that does *not* involve a full radio power-cycle also retrigger the @@ -323,7 +323,7 @@ against `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`: already established elsewhere, and no Fast Pair popup is expected or seen under this session's GMS-disabled condition (consistent with `CAP-004-FINDINGS.md` §7). - **`PAIR-003`** (disconnect/reconnect to an already-bonded device) — exercised incidentally, §6; - not part of Group S's own written procedure, but the first Pixel-7a occurrence of this Test-ID + not part of Group S's own written procedure (a further Pixel-7a occurrence; the first is `CAP-006`) (see §6 and §9). **No gap to flag**: every Test-ID this Group's own procedure calls for (`GFPS-001`) is clearly @@ -336,11 +336,11 @@ untracked behavior. §3 step 3's raw-log path, or a confirmed-larger snaplen if step 4 must be used) — the single highest-value follow-up, since §1's truncation is the only reason this session cannot give `GFPS-001` as clean an answer as `CAP-004` did for the nRF-Connect variant. -2. Fold this capture's §6 finding (a non-power-cycle reconnect retriggers the full HFP handshake) +2. (Obsolete: `CAP-002`'s "silent session" was a filter artefact, §6.) Fold this capture's §6 finding (a non-power-cycle reconnect retriggers the full HFP handshake) into a dedicated look at whether `CAP-002`'s original silent session ever tore down its ACL link at all — `CAP-002`'s own ~8h20m shared log likely already contains the answer and would not require a new capture. -3. `PAIR-003` now has a Pixel 7a data point (§6) — update `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s row +3. (Done 2026-08-28; the first Pixel 7a data point is `CAP-006`.) `PAIR-003` now has a Pixel 7a data point (§6) — update `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s row accordingly (see §10). ## 10. Hypothesis test record (`PROJECT_RULES.md` rule 10/11 template) @@ -362,9 +362,10 @@ untracked behavior. zero SMP frames anywhere in the 542s log, and zero LE Connection Complete/LE Enhanced Connection Complete events of any kind (only passive LE Extended Advertising Reports, which do not constitute a connection). -- **Conclusion:** 🟢 **FACT — hypothesis confirmed.** `CAP-004`'s CTKD bonding mechanism was - specifically caused by nRF Connect's early BLE connection existing before classic pairing began, - not by the GMS-disabled/no-app condition. With that confound removed, classic bonding under +- **Conclusion:** 🟢 **FACT — classic SSP without an LE SMP pairing; GMS-disabled/no-app is not + the cause of `CAP-004`'s CTKD** *(corrected 2026-10-15, `ai-sessions/0094`)*. That an SMP pairing + on the LE link (not merely an early LE link — `CAP-003` had one and used SSP) gates CTKD is 🟡 + HYPOTHESIS (§2, `DECISIONS.md` ADR-030 Update 2026-10-03). Without that SMP pairing, classic bonding under GMS-disabled/no-app conditions follows the same classic-SSP state machine as every other fresh pairing this project has captured (`PROTOCOL.md` §5.1). This is a genuine resolution, not an inconclusive result — unlike §4/§5's payload-content questions, which §1's truncation finding diff --git a/captures/CAP-013-2026-08-26_17-09-01_17-14-04-Group_A/CAP-013-EVENT-NOTES.md b/captures/CAP-013-2026-08-26_17-09-01_17-14-04-Group_A/CAP-013-EVENT-NOTES.md index 3a8f8df3..534907ac 100644 --- a/captures/CAP-013-2026-08-26_17-09-01_17-14-04-Group_A/CAP-013-EVENT-NOTES.md +++ b/captures/CAP-013-2026-08-26_17-09-01_17-14-04-Group_A/CAP-013-EVENT-NOTES.md @@ -26,8 +26,9 @@ directly against both the video and the log rather than accepted as given, per t (17:11:25), "Pixel Buds Pro 2 van Ted" appeared in the discovery list (17:11:27), and the device was tapped in that list (~17:11:31–45). **None of that discovery/inquiry activity is in the log at all.** The log's first frame lands almost exactly on the moment the phone begins actively connecting (a - passive BLE advertising report at 17:11:45.799, immediately followed by `Delete Stored Link Key` at - 17:11:46.737) — i.e. logging effectively started only once the user had already selected the device + `Sent Read By Type Request` on an LE/ATT link that already existed, 17:11:45.799, frame 1; frame 2 + is the first LE Extended Advertising Report; `Delete Stored Link Key` follows at 17:11:46.737, + frame 117) — i.e. logging effectively started only once the user had already selected the device and the phone-side connect sequence was already underway. 3. **The log is also severely ACL-truncated**, the same issue `CAP-012-FINDINGS.md` §1 documented for that session: `capinfos` reports an inferred 15-byte packet size limit, and every RFCOMM data frame @@ -97,7 +98,7 @@ timestamp below. | 17:12:52 | "Allow" tapped on the find-my-device permission screen; a further "allow the Pixel Buds app to access..." permission screen appears | User (App) | — | — | | 17:13:02 | "Allow" tapped | User (App) | — | — | | 17:13:27 | Device details screen shows firmware **`release_5.203`** | App (Auto) | — | Consistent with `ADR-012`'s wire-baseline firmware string; not independently re-verified on the wire this session (truncation limits DLCI 0x08 Group `0x03` Code `0x02` payload recovery here, see `CAP-013-FINDINGS.md` §4) | -| 17:13:47 | Device details panel fully shown: Right 100%, Case 13%, Left 100%, ANC controls (Transparency/Adaptive/Off/Noise cancellation), Forget/Disconnect | App (Auto) | incidental `BATT-004` | Screenshot `f_firmware.png`; DLCI `0x08` Group `0x0e` Code `0x01`/`0x02` battery-shaped traffic observed near this window (frames 1423–1425) but Length/Value bytes are truncated, so the 100/100/13 values cannot be cross-checked against the wire this session | +| 17:13:47 | Device details panel fully shown: Right 100%, Case 13%, Left 100%, ANC controls (Transparency/Adaptive/Off/Noise cancellation), Forget/Disconnect | App (Auto) | incidental `BATT-004` | Screenshot `f_firmware.png`; DLCI `0x08` Group `0x0e` Code `0x01`/`0x02` battery-shaped traffic observed at 17:14:20.26 (frames 1423–1425), ~33 s later and after the video ended but Length/Value bytes are truncated, so the 100/100/13 values cannot be cross-checked against the wire this session | | 17:14:04 | End of video recording | — | — | — | ## Corrections vs. the original draft of this file @@ -114,10 +115,10 @@ timestamp below. ## Traceability check (`AGENTS.md` §13 point 7, `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s Group-A Test-IDs) -Group A's associated Test-IDs are `PAIR-001` (primary) and incidental `BATT-004`; the Group-A-repeat note -additionally targets `PAIR-004`. All three are referenced in the timeline above. `APP-001`/`APP-002` -(Pixel Buds app onboarding permission taps) are incidentally exercised here too and referenced above, -though they are not Group A's own assigned Test-IDs. +Group A's associated Test-IDs (`TESTPLAN_BLUETOOTH_HCI_SNOOP.md`) are `PAIR-001` (primary), `BATT-004`, +`APP-001`, `APP-002` and, incidental, `GFPS-002`; the Group-A-repeat note additionally targets `PAIR-004`. +`PAIR-001`, `PAIR-004`, `BATT-004`, `APP-001` and `APP-002` are referenced in the timeline above; +`GFPS-002` is not (next section). ## Traceability check (2026-10-03, `ai-sessions/0069`, `A68-CAP-15`, `AGENTS.md` §13 step 7) diff --git a/captures/CAP-013-2026-08-26_17-09-01_17-14-04-Group_A/CAP-013-FINDINGS.md b/captures/CAP-013-2026-08-26_17-09-01_17-14-04-Group_A/CAP-013-FINDINGS.md index da3d41c8..ea1dc8ec 100644 --- a/captures/CAP-013-2026-08-26_17-09-01_17-14-04-Group_A/CAP-013-FINDINGS.md +++ b/captures/CAP-013-2026-08-26_17-09-01_17-14-04-Group_A/CAP-013-FINDINGS.md @@ -116,7 +116,7 @@ Change: | 0 | `0x00` | 539/549, 17:11:50.113/118 | RFCOMM multiplexer control (PN negotiation) | 🟢 FACT | | 6 | `0x0c` | 556/564, 17:11:50.124/152 | HFP AT-command traffic — ASCII `AT` (`0x41 0x54`) and `\r\n` (`0x0d 0x0a`) prefixes visible before truncation, e.g. frame 597 (`4154`) and frame 600 (`0d0a2b`, `\r\n+`, consistent with an unsolicited result code such as `+CIEV`/`+BIEV`) | 🟢 FACT that this is the HFP AT channel (per `PROTOCOL.md` §4.3 Option C's established pattern); full command/argument content not recoverable this session due to truncation (§1) | | 4 | `0x08` | 559/574, 17:11:50.143/165 | Private `[Group][Code][Length][Value]` envelope (`PROTOCOL.md` §2.3) — Groups `0x04`,`0x03`,`0x09`,`0x0e` observed by their first 2 bytes; see §4 below | 🟢 FACT for channel identity/topology; 🔴 most payload content truncated | -| 5 | `0x0a` | 596/605, 17:11:50.209/217 | Opens, no payload observed — consistent with `CAP-001-FINDINGS.md` §6's finding that this DLCI stays silent (SCO/eSCO ruled out there) | 🟢 FACT (silence reproduced a 5th time) | +| 5 | `0x0a` | 596/605, 17:11:50.209/217 | Opens, no payload observed — consistent with `CAP-001-FINDINGS.md` §6's finding that this DLCI stays silent (SCO/eSCO ruled out there) | 🟢 FACT (silence reproduced again; cf. the silent captures listed in `CAP-008-FINDINGS.md` §6) | | 2 | `0x04` | 785/787, 17:11:51.780/785 | Official Fast Pair Message Stream — Device Information (Group `0x03`) and Hearable Controls ANC Get (Group `0x08` Code `0x11`) observed; see §4 | 🟢 FACT for channel identity | | 1 | `0x02` | 1170/1172, 17:12:51.981/985 | `libmaestro` candidate Pigweed `pw_hdlc` channel — opens **~61s later** than the other four, not during the initial multiplexer burst; see §5 | 🟢 FACT for channel identity/framing (per `PROTOCOL.md` §2.2a); 🟡 timing observation below is new and single-sample | @@ -140,8 +140,8 @@ Representative hex (first bytes only, per §1's truncation): `Group 0x0e Code 0x01`/`0x02` traffic (per `ADR-014`, the confirmed per-earbud+case battery push) is **present** at connection time (frames 635/658/661) and again twice more later in the session (frames -1423/1425, 1450/1451, 1580/1582) — consistent with a battery push recurring around the 17:13:47 -on-screen "Right 100% / Case 13% / Left 100%" reading. **The Length and Value bytes are truncated in +1423/1425, 1450/1451, 1580/1582) — the first of these (1423/1425, 17:14:20.26) is ~33 s after the +17:13:47 on-screen "Right 100% / Case 13% / Left 100%" reading and after the video ended (17:14:04). **The Length and Value bytes are truncated in every one of these frames** (`frame.cap_len` = 15 throughout, per §1), so the specific `[value, flag, index]` triplets cannot be decoded and cross-checked against the on-screen percentages this session — recorded as 🔴 OPEN for the *specific values* this session, while the *presence* of this @@ -154,7 +154,7 @@ performed on screen this session). ## 5. DLCI 0x02 (`libmaestro` candidate) opens ~61s after the other channels (🟡 HYPOTHESIS, single sample) -Unlike `CAP-001`–`CAP-004`/`CAP-006` (where DLCI `0x02` opens in the same initial multiplexer burst as +Unlike `CAP-001`–`CAP-003`/`CAP-006` (where DLCI `0x02` opens in the same initial multiplexer burst as the other channels, `PROTOCOL.md` §2.2a), this session's DLCI `0x02` `SABM`/`UA` exchange (frames 1170/1172) happens at 17:12:51.981–985 — **~61 seconds after** the other four channels opened (17:11:50.1–51.8). This timing lines up closely with the on-screen "Allow" tap on the Pixel Buds app's @@ -217,7 +217,7 @@ not resolved by another session's absence of the phenomenon. ## 8. Other open questions raised by this capture - DLCI `0x02`'s delayed opening (§5) — is this driven by the companion app's setup-flow state, or - coincidental to this session? Needs a repeat with precise screen-to-SABM timing as the explicit goal. + coincidental to this session? Repeated by `CAP-031` (§5's 2026-08-27 note): not reproduced, read as a single-session artifact. - The second BLE link to a random address (§6) — is this the Buds' own GATT-side identity, or an unrelated device? Needs an explicit BLE-address-resolution check (e.g. via `Identity Address` in a Pairing/Bonding event, if one appears later in the log beyond this capture's own end, or a dedicated diff --git a/captures/CAP-014-2026-08-27_20-53-37_20-59-17-Group_W/CAP-014-EVENT-NOTES.md b/captures/CAP-014-2026-08-27_20-53-37_20-59-17-Group_W/CAP-014-EVENT-NOTES.md index f15622fc..88e94783 100644 --- a/captures/CAP-014-2026-08-27_20-53-37_20-59-17-Group_W/CAP-014-EVENT-NOTES.md +++ b/captures/CAP-014-2026-08-27_20-53-37_20-59-17-Group_W/CAP-014-EVENT-NOTES.md @@ -15,7 +15,7 @@ Accessory-Non-Owner half conflicted with `DECISIONS.md` ADR-008 and was correctl (§"Procedure" step 4 below and `CAP-014-FINDINGS.md` §2)**. **Outcome (see `CAP-014-FINDINGS.md` for full detail): (1) succeeded — the snaplen is confirmed -fixed (§0 there) — but the `0x0c0X`/`0x0f2X` handle↔UUID mapping is still 🔴 OPEN, for a newly +fixed (§0 there) — but the `0x0c0X`/`0x0f2X` handle↔UUID mapping stayed 🔴 OPEN in this session (resolved later by `CAP-034`, 🟢, `CAP-034-FINDINGS.md` §4), for a newly identified reason unrelated to snaplen: this session reused an already-bonded phone/cached GATT client, so Android served cached declarations for that handle range instead of re-transmitting them on the wire (`CAP-014-FINDINGS.md` §4).** `pm clear com.android.bluetooth` and the Pixel 9a path — @@ -127,8 +127,8 @@ source for this session, alongside the video and wire log): 3. **Consequence, evidenced in `CAP-014-FINDINGS.md` §4/§5:** because the phone/GATT-client pairing was reused rather than fresh, the connection served a cached GATT database for the `0x0c0X`/`0x0f2X` handle range instead of re-declaring it live on the wire (§4), and bonding - used Cross-Transport Key Derivation rather than classic SSP (§5, matching `CAP-004`'s already- - established "BLE-tool-first → CTKD" pattern). Neither is a failure of this session's snaplen fix + used Cross-Transport Key Derivation rather than classic SSP (§5; like `CAP-004`, this session has + an SMP pairing on the LE link, frame 2365 — the 🟡 candidate gate, `DECISIONS.md` ADR-030 Update). Neither is a failure of this session's snaplen fix (§0, confirmed working) — both trace directly to procedure choices, not instrumentation. ## Analysis checklist (per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §5 step 6) @@ -137,7 +137,7 @@ source for this session, alongside the video and wire log): confirm response frames are **not** truncated this time (`frame.cap_len == frame.len`). **Done — 0 mismatches across all 4,663 frames (`CAP-014-FINDINGS.md` §0).** - [x] Recover the `0x0c0X`/`0x0f2X` handle↔UUID mapping from the (now-untruncated) responses. - **Attempted — still 🔴 OPEN. No declaration response for this handle range exists on the + **Attempted — not recoverable from this session (resolved later by `CAP-034`, 🟢). No declaration response for this handle range exists on the wire this session (root cause identified, `CAP-014-FINDINGS.md` §4), not a truncation issue.** - [x] Confirm/refute the "Accessory Non-Owner Service" and "Unknown Service" (`109b862f-…`) identities against their on-screen characteristic reads. **Done — neither was actually read @@ -152,8 +152,8 @@ source for this session, alongside the video and wire log): annotations above.** - [x] Write `CAP-014-FINDINGS.md` per `PROJECT_RULES.md` §2, using this file's timeline as the evidence source, following the hex & script rule (§1 rule 4a). **Done.** -- [ ] Update this session's row in `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index — status - from `planned` to `analyzed`, fill in Android/firmware/app-version columns and the log path. +- [x] Update this session's row in `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index — status + from `planned` to `analyzed`, fill in Android/firmware/app-version columns and the log path. **Done.** - [x] ~~Rename this capture's folder from the `yyyy-MM-dd_HH-mm-ss_HH-mm-ss` placeholder to the actual session date/start-time/end-time.~~ **N/A — already named correctly:** `CAP-014-2026-08-27_20-53-37_20-59-17-Group_W`. diff --git a/captures/CAP-014-2026-08-27_20-53-37_20-59-17-Group_W/CAP-014-FINDINGS.md b/captures/CAP-014-2026-08-27_20-53-37_20-59-17-Group_W/CAP-014-FINDINGS.md index 8e2c23a3..0ef3f6ae 100644 --- a/captures/CAP-014-2026-08-27_20-53-37_20-59-17-Group_W/CAP-014-FINDINGS.md +++ b/captures/CAP-014-2026-08-27_20-53-37_20-59-17-Group_W/CAP-014-FINDINGS.md @@ -159,9 +159,9 @@ This connection (chandle `0x0003`) is active through the end of the log (no disc it appears anywhere in the file). **4a. CLI hygiene / cross-device contamination check (🟢 FACT — required before trusting any -handle-name resolution below):** three *other* LE connections exist in this same capture, to a +handle-name resolution below):** five *other* LE connections exist in this same capture *(corrected 2026-10-15, `ai-sessions/0094`)*, to a **different** peer (`67:6d:32:c1:30:07`, resolvable/random address, not the Buds), on chandles -`0x0002`/`0x0006`/`0x0009`: +`0x0002`/`0x0006`/`0x0007`/`0x0008`/`0x0009` (frames 500, 4006, 4483, 4531, 4570): ``` $ tshark -r CAP-014-btsnoop_hci.log -Y "bthci_evt.le_meta_subevent==0x0a or bthci_evt.le_meta_subevent==0x01" \ @@ -235,7 +235,7 @@ never sent. | `0x0007` | Read Resp (2262) | `8b447e7c056554c09c1c4ac90f6f771c` | Database Hash | matches nRF log 20:56:03.316 | | `0x0f32` | Read Resp (2615) | `64` | 1 byte, `100` decimal | **matches `CAP-017-FINDINGS.md` §6's `0x0f32 = 0x64`** exactly — see §6 below | | `0x0f33` | Write Req (2616) | `0100` | CCCD-shaped enable (handle = `0x0f32`+1) | new this session vs. `CAP-002`/`CAP-003`/`CAP-010` | -| `0x0f2a` | Read Resp (2973) | `5265766973696f6e2036` | ASCII `"Revision 6"` | **byte-for-byte identical to `CAP-002-FINDINGS.md` §4** (frame 1441 there) — 3rd session now confirming this exact string on this exact handle | +| `0x0f2a` | Read Resp (2973) | `5265766973696f6e2036` | ASCII `"Revision 6"` | **byte-for-byte identical to `CAP-002-FINDINGS.md` §4** (frame 1441 there) — 4th session (after `CAP-002`, `CAP-003`, `CAP-010`) confirming this exact string on this exact handle | | `0x0c04` | Write Req (2954) | `c916abcc85ee00fa1e6a9e5b448bc59686c8e978ff46d6a1a7616772aedf081f6effaf6d88bd9f99f90e2480f91c21941362b035170cd6804f3d7ef8377e2b44a140da5d8c447e71fa9aab6d3f5a0cae` | 80 bytes, opaque | reproduces the `0x0c04` write-then-notify shape from `CAP-002-FINDINGS.md` §4 | | `0x0c04` | Notify (2961) | `7dac8090ac7052714274f2ab09627cbd` | 16 bytes, opaque | — | | `0x0c0c` | Notify (2962) | `b76223856d8393eca0eb175f757eba157a3f9121fa0dd1684c19021d39fdcfb3e4999cbb7a11a427` | 40 bytes, opaque | reproduces `CAP-010-FINDINGS.md` §3's `0x0c0c` 40-byte notify exactly (`btatt.value` of 2962 = 40 bytes) | @@ -293,9 +293,10 @@ Request`/`Simple Pairing Complete` anywhere in this window) — the exact patter which `CAP-012-FINDINGS.md` §2 confirmed does **not** happen when only system Bluetooth Settings is used (no BLE tool). `CAP-014` uses nRF Connect (a BLE tool) to initiate bonding, and reproduces CTKD — this is the **2nd** independently confirmed CTKD instance (`CAP-004`, now `CAP-014`), -consistent with `CAP-012`'s already-established explanation ("CTKD when a BLE tool connects first, -classic SSP when only system settings is used") rather than new evidence toward a different -mechanism. No promotion proposed here — `CAP-012-FINDINGS.md` §2 already carries this as 🟢 FACT; +consistent with `CAP-012-FINDINGS.md` §2's reading (rewritten 2026-10-03): the CTKD sessions are +those with an SMP pairing on the LE link (this capture's frame 2365, `CAP-004`'s 1856), not merely +an early LE link (`CAP-003` had one and used SSP); that the SMP pairing gates CTKD is 🟡 HYPOTHESIS +(`DECISIONS.md` ADR-030 Update). No promotion proposed here; this section only records that `CAP-014` is a further reproducing data point, worth noting for future sessions choosing a bonding method. @@ -372,11 +373,10 @@ future sessions choosing a bonding method. shape now has a spec-verified name. See `captures/CAP-034-2026-09-01_06-46-31_06-52-45-Group_W/CAP-034-FINDINGS.md` §4 and `PROTOCOL.md` §6. -- 🔴 Handle `0x0034`'s real identity and the "missing first 3 bytes" of its device-name-like read - value (§4c) — reported as raw bytes, not explained. **Not resolved by `CAP-034`** — that capture's - own handle `0x0034` belonged to a different, legitimately-declared GAP Device Name characteristic - on the Buds' own attribute table, not this section's cross-connection-contaminated handle from a - different peer device; the two are unrelated, still open on its own terms. +- ~~Handle `0x0034`'s real identity and the "missing first 3 bytes" of its read value~~ — + **resolved (§4c, 2026-10-03, 🟢):** frame 3353 is a complete Device Name read; the "missing" + bytes were a dissector artefact (a handle→UUID mapping carried over from the other connection), + and handle `0x0034` is the GAP Device Name on the Buds' own attribute table, as in `CAP-034-FINDINGS.md` §4. - 🔴 `0x0f32`/`0x0f33`'s semantic meaning (value `100`) — reproduces exactly across `CAP-017` and `CAP-014`, still no UUID or meaning (§6). **Resolved 2026-09-01 (`CAP-034`):** `0x0f32` is the standard Battery Level characteristic (`0x2A19`) within the Battery Service (`0x0f30`–`0x0f33`); diff --git a/captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-EVENT-NOTES.md b/captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-EVENT-NOTES.md index c8e0e2f4..925113fc 100644 --- a/captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-EVENT-NOTES.md +++ b/captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-EVENT-NOTES.md @@ -28,7 +28,7 @@ command channel per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group T / `TESTPLAN_BLUETOO `EQP-002` (preset tap) and `EQS-004` (slider drag). In practice the maintainer ran a substantially richer procedure than the Group T minimum (one preset + one slider): **five distinct presets** were tapped in sequence, followed by **all five EQ sliders**, each dragged to its negative extreme, -its positive extreme, and back to near-zero, in three repeating passes — see +its positive extreme (first pass), and back to near-zero (second pass), in the same band order both times — see `CAP-015-FINDINGS.md` §4/§5 for why this turns out to directly resolve the field-to-band mapping question the earlier (2026-08-15) `CAP-005` capture left open. @@ -43,39 +43,39 @@ frame timestamps without offset correction. | 06:11:06 | Start video recording. Screen shows the system **Bluetooth** settings sheet, **"Bluetooth is off"**, `Use Bluetooth` toggled off | — | — | — | | 06:11:11 | `Use Bluetooth` toggled on; Bluetooth sheet now shows **"Pixel Buds Pro 2 van Ted"** already listed (`L: 100%, C: 100%, R: 100%`) — a previously-bonded device, not a fresh pair | User (App) | — | — | | 06:11:21 | Tap the gear icon next to the device row → **Device details** screen opens | User (App) | — | — | -| 06:11:36 | Device details fully shown: Left/Case/Right all 100%, `Forget`/`Connect` buttons, **Sound** row visible ("Active Noise Control and EQ") | App (Auto) | — | 1st `Create Connection` sent 06:11:45.417 (frame 1376) | +| 06:11:36 | Device details fully shown: Left/Case/Right all 100%, `Forget`/`Connect` buttons, **Sound** row visible ("Active Noise Control and EQ") | App (Auto) | — | 1st `Create Connection` sent 06:11:45.417 (frame 1376) — ≥0.6 s before the filmed Connect tap (06:11:46, next row), so the film clock runs behind the log clock here (as in `CAP-008`/`CAP-012`) | | 06:11:46 | Tap **Connect** | User (App) | — | — | | — | (not directly visible on screen) | — | — | 1st `Create Connection` → **Page Timeout** (status `0x04`, frame 1395, 06:11:50.547); 2nd `Create Connection` (frame 1396, 06:11:50.569) → succeeds (status `0x00`, frame 1402, 06:11:51.769) | | 06:11:54–58 | (not directly visible on screen — app UI mid-transition) | — | — | RFCOMM channels opened in order: DLCI `0x00` (mux, 06:11:54.627), `0x0c` (06:11:54.845), `0x04` (06:11:54.928), `0x0a` (06:11:55.372), `0x08` (06:11:56.274), `0x02` (06:11:58.186) | | 06:11:56 | **Sound** settings screen open: Conversation detection, Eartip seal check, **Equalizer**, Balance, Mono audio | App (Auto) | — | — | | 06:12:06 | (still on Sound screen) | — | — | — | -| 06:12:13 | Tap **Equalizer** → Equalizer screen opens; preset dropdown shows **`Last saved`** selected (baseline, all bands centered) | User (App) | — | Frame 2111 (06:12:13.279, DLCI 0x02): quintet `[0.0, 0.0, 0.0, 0.0, 0.0]` — see `CAP-015-FINDINGS.md` §3 | +| 06:12:13 | Tap **Equalizer** → Equalizer screen opens; preset dropdown shows **`Last saved`** selected (baseline, all bands centered) | User (App) | `EQP-007` (baseline shown on page open, not tapped) | Frame 2111 (06:12:13.279, DLCI 0x02): quintet `[0.0, 0.0, 0.0, 0.0, 0.0]` — see `CAP-015-FINDINGS.md` §3 | | 06:12:26 | Tap preset **Heavy bass** [`EQP-002`] | User (App) | `EQP-002` | Frame 2165 (06:12:26.370): `[5.0, 3.0, 0.0, 0.0, 0.0]` | -| 06:12:41 | Tap preset **Light bass** | User (App) | `EQP` (other) | Frame 2227 (06:12:41.340): `[-5.0, -1.5, 0.0, 0.0, 0.0]` | -| 06:12:57 | Tap preset **Balanced** | User (App) | `EQP` (other) | Frame 2303 (06:12:57.324): `[-3.5, 0.5, 1.0, -1.0, 2.5]` | -| 06:13:11 | Tap preset **Vocal boost** | User (App) | `EQP` (other) | Frame 2351 (06:13:11.028): `[-1.0, 0.0, 4.0, 2.0, 0.0]` | -| 06:13:26 | Tap preset **Clarity** | User (App) | `EQP` (other) | Frame 2400 (06:13:26.064): `[-2.0, 0.0, 2.0, 3.0, 5.0]` | +| 06:12:41 | Tap preset **Light bass** | User (App) | `EQP-003` | Frame 2227 (06:12:41.340): `[-5.0, -1.5, 0.0, 0.0, 0.0]` | +| 06:12:57 | Tap preset **Balanced** | User (App) | `EQP-004` | Frame 2303 (06:12:57.324): `[-3.5, 0.5, 1.0, -1.0, 2.5]` | +| 06:13:11 | Tap preset **Vocal boost** | User (App) | `EQP-005` | Frame 2351 (06:13:11.028): `[-1.0, 0.0, 4.0, 2.0, 0.0]` | +| 06:13:26 | Tap preset **Clarity** | User (App) | `EQP-006` | Frame 2400 (06:13:26.064): `[-2.0, 0.0, 2.0, 3.0, 5.0]` | | 06:13:34 | Clarity fully applied on screen; all 5 sliders visible top-to-bottom: **Upper treble, Treble, Mid, Bass, Low bass** — this on-screen order is the key needed to resolve the field↔band mapping (`CAP-015-FINDINGS.md` §5) | App (Auto) | — | — | | 06:13:39 | Finger briefly touches the **Upper treble** slider row (dropdown still closed, `Clarity` header shown) | User (App) | — | — | | 06:13:40 | Dropdown reopened, `Clarity` still shown checked in the list — sequencing vs. the row above is ambiguous at 1fps; see `CAP-015-FINDINGS.md` §6 | User (App) | — | — | -| 06:13:41 | (not clearly resolvable on screen at 1fps) — this is when the first wire-visible change appears | — | `EQS` (Upper treble) | Frame 2470 (06:13:40.998): `[4.5, -4.9, 4.5, 3.8, 4.1]` — bands 1–4 already differ from Clarity's clean values here, with no intervening `Sent` frame to explain the jump; see `CAP-015-FINDINGS.md` §6 open question | -| 06:13:52–53 | Upper treble dragged further down | User (App) | `EQS` (Upper treble) | Frames 2527→2573 (06:13:52.55–53.31): band 5 (Upper treble) steps `3.9 → 1.6 → -1.1 → -3.5 → -5.3 → -6.0` | -| 06:13:54 | Value tooltip over the slider reads **"-60"** (i.e. -6.0), `Save` button active (purple), drag settles at -6.0 | App (Auto) | `EQS` (Upper treble) | Frame 2586 (06:13:54.638, outer field 18/"save" shape): `[4.5, -4.9, 4.5, 3.8, -6.0]` | -| 06:14:06–07 | Upper treble dragged back up to its positive maximum | User (App) | `EQS` (Upper treble) | Frames 2616→2653 (06:14:05.92–07.67): band 5 steps `-5.8 → -2.4 → 1.2 → 4.3 → 6.0`, saved at 2653 | -| 06:14:22–23 | Finger moves to **Treble** slider, drags rapidly down to its negative maximum | User (App) | `EQS` (Treble) | Frames 2701→2715 (06:14:22.68–23.14): band 4 (Treble) steps `3.5 → -1.1 → -5.3 → -6.0` | -| 06:14:37–46 | Treble dragged back up | User (App) | `EQS` (Treble) | Frame 2785 (06:14:37.66): band 4 = 5.9; saved at 2819 (06:14:46.55) | -| 06:14:52–54 | Treble dragged back down again and saved | User (App) | `EQS` (Treble) | Frame 2848 (06:14:52.41): band 4 = -6.0; saved at 2863 (06:14:54.26) | -| 06:15:05 | Finger on **Mid** row (video: finger tip directly over the "Mid" label, slider already at/near its positive maximum) | User (App) | `EQS` (Mid) | Frames 2917→2920 (06:15:04.96–05.11): band 3 (Mid) steps `5.0 → 6.0`, saved at 2937 (06:15:06.75) | -| 06:15:17–18 | Mid dragged down to its negative maximum, saved | User (App) | `EQS` (Mid) | Frames 2969→2979 (06:15:16.88–17.34): band 3 steps `5.7 → 2.1 → -4.0 → -6.0`, saved at 3009 (06:15:18.51) | -| 06:15:31–32 | Finger on **Bass** row mid-drag; `Save` button purple/active (unsaved change pending); Bass bar near far left (minimum) | User (App) | `EQS` (Bass) | Frames 3064→3068 (06:15:31.07–31.23): band 2 (Bass) steps `-5.2 → -6.0`, saved at 3083 (06:15:33.01) | -| 06:15:44–46 | Bass dragged up to its positive maximum, saved | User (App) | `EQS` (Bass) | Frame 3130 (06:15:44.76): band 2 = 6.0, saved at 3142 (06:15:46.18) | -| 06:15:58–59 | Finger at the **Low bass** row (bottom of the slider list); Bass bar already full-right (max) from the previous step | User (App) | `EQS` (Low bass) | Frame 3178 (06:15:58.20): band 1 (Low bass) = -6.0, saved at 3187 (06:16:00.10) | -| 06:16:10–13 | Low bass dragged up, saved | User (App) | `EQS` (Low bass) | Frame 3215 (06:16:10.68): band 1 = 5.8, saved at 3228 (06:16:12.77) | -| 06:16:23–25 | **Second full pass begins** — Upper treble changed again, dragged to a small near-zero value and saved | User (App) | `EQS` (Upper treble) | Frame 3271 (06:16:23.31): band 5 = 0.2, saved at 3285 (06:16:24.83) | -| 06:16:33–35 | Treble → near-zero, saved | User (App) | `EQS` (Treble) | Frame 3318 (06:16:33.69): band 4 = 0.2, saved at 3328 (06:16:34.85) | -| 06:16:45–47 | Mid → near-zero, saved | User (App) | `EQS` (Mid) | Frame 3373 (06:16:45.35): band 3 = 0.3, saved at 3382 (06:16:46.56) | -| 06:16:56–58 | Bass → near-zero, saved | User (App) | `EQS` (Bass) | Frame 3403 (06:16:56.48): band 2 = 0.0, saved at 3428 (06:16:57.79) | -| 06:17:07–09 | Low bass → near-zero, saved. All 5 sliders now visually centered (near-zero); `Save` button still active | User (App) | `EQS` (Low bass) | Frames 3451→3458 (06:17:07.60–07.75): band 1 steps `0.2 → 0.1`, saved at 3468 (06:17:09.03) | +| 06:13:41 | (not clearly resolvable on screen at 1fps) — this is when the first wire-visible change appears | — | `EQS-001` | Frame 2470 (06:13:40.998): `[4.5, -4.9, 4.5, 3.8, 4.1]` — bands 1–4 already differ from Clarity's clean values here, with no intervening `Sent` frame to explain the jump; see `CAP-015-FINDINGS.md` §6 open question | +| 06:13:52–53 | Upper treble dragged further down | User (App) | `EQS-001` | Frames 2527→2573 (06:13:52.55–53.31): band 5 (Upper treble) steps `3.9 → 1.6 → -1.1 → -3.5 → -5.3 → -6.0` | +| 06:13:54 | Value tooltip over the slider reads **"-60"** (i.e. -6.0), `Save` button active (purple), drag settles at -6.0 | App (Auto) | `EQS-001` | Frame 2586 (06:13:54.638, outer field 18/"save" shape): `[4.5, -4.9, 4.5, 3.8, -6.0]` | +| 06:14:06–07 | Upper treble dragged back up to its positive maximum | User (App) | `EQS-001` | Frames 2616→2653 (06:14:05.92–07.67): band 5 steps `-5.8 → -2.4 → 1.2 → 4.3 → 6.0`, saved at 2653 | +| 06:14:22–23 | Finger moves to **Treble** slider, drags rapidly down to its negative maximum | User (App) | `EQS-002` | Frames 2701→2715 (06:14:22.68–23.14): band 4 (Treble) steps `3.5 → -1.1 → -5.3 → -6.0` | +| 06:14:37–46 | Treble dragged back up | User (App) | `EQS-002` | Frame 2785 (06:14:37.66): band 4 = 5.9; saved at 2819 (06:14:46.55) | +| 06:14:52–54 | Treble dragged back down again and saved | User (App) | `EQS-002` | Frame 2848 (06:14:52.41): band 4 = -6.0; saved at 2863 (06:14:54.26) | +| 06:15:05 | Finger on **Mid** row (video: finger tip directly over the "Mid" label, slider already at/near its positive maximum) | User (App) | `EQS-003` | Frames 2917→2920 (06:15:04.96–05.11): band 3 (Mid) steps `5.0 → 6.0`, saved at 2937 (06:15:06.75) | +| 06:15:17–18 | Mid dragged down to its negative maximum, saved | User (App) | `EQS-003` | Frames 2969→2979 (06:15:16.88–17.34): band 3 steps `5.7 → 2.1 → -4.0 → -6.0`, saved at 3009 (06:15:18.51) | +| 06:15:31–32 | Finger on **Bass** row mid-drag; `Save` button purple/active (unsaved change pending); Bass bar near far left (minimum) | User (App) | `EQS-004` | Frames 3064→3068 (06:15:31.07–31.23): band 2 (Bass) steps `-5.2 → -6.0`, saved at 3083 (06:15:33.01) | +| 06:15:44–46 | Bass dragged up to its positive maximum, saved | User (App) | `EQS-004` | Frame 3130 (06:15:44.76): band 2 = 6.0, saved at 3142 (06:15:46.18) | +| 06:15:58–59 | Finger at the **Low bass** row (bottom of the slider list); Bass bar already full-right (max) from the previous step | User (App) | `EQS-005` | Frame 3178 (06:15:58.20): band 1 (Low bass) = -6.0, saved at 3187 (06:16:00.10) | +| 06:16:10–13 | Low bass dragged up, saved | User (App) | `EQS-005` | Frame 3215 (06:16:10.68): band 1 = 5.8, saved at 3228 (06:16:12.77) | +| 06:16:23–25 | **Second full pass begins** — Upper treble changed again, dragged to a small near-zero value and saved | User (App) | `EQS-001` | Frame 3271 (06:16:23.31): band 5 = 0.2, saved at 3285 (06:16:24.83) | +| 06:16:33–35 | Treble → near-zero, saved | User (App) | `EQS-002` | Frame 3318 (06:16:33.69): band 4 = 0.2, saved at 3328 (06:16:34.85) | +| 06:16:45–47 | Mid → near-zero, saved | User (App) | `EQS-003` | Frame 3373 (06:16:45.35): band 3 = 0.3, saved at 3382 (06:16:46.56) | +| 06:16:56–58 | Bass → near-zero, saved | User (App) | `EQS-004` | Frame 3403 (06:16:56.48): band 2 = 0.0, saved at 3428 (06:16:57.79) | +| 06:17:07–09 | Low bass → near-zero, saved. All 5 sliders now visually centered (near-zero); `Save` button still active | User (App) | `EQS-005` | Frames 3451→3458 (06:17:07.60–07.75): band 1 steps `0.2 → 0.1`, saved at 3468 (06:17:09.03) | | 06:17:39 | Video ending; sliders unchanged since 06:17:09, `Save` button now greyed out again | App (Auto) | — | Two more DLCI 0x02 writes follow frame 3468, neither an EQ write: `WriteSetting 4:{15:0}` at **06:17:19.705** (frame 3487) and `4:{15:1}` at **06:17:29.417** (frame 3505) — field 15 = Volume EQ (`PROTOCOL.md` §4.5.6), off then on. Corrected 2026-10-03 (`ai-sessions/0069`, `A68-CAP-16`; `python3 scripts/pwrpc_decode.py CAP-015-btsnoop_hci.log`): this cell said "No further DLCI 0x02 Sent traffic after frame 3468". The matching action is not in this timeline (🔴 the film at 06:17:19 and 06:17:29 was not re-viewed) | | 06:17:40 | End video recording | — | — | — | @@ -84,7 +84,7 @@ frame timestamps without offset correction. `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group T only calls for one isolated preset tap (`EQP-002`) and one isolated slider drag (`EQS-004`, Bass). This session went well beyond that: five presets (Heavy bass, Light bass, Balanced, Vocal boost, Clarity) and all five sliders, each pushed to both -extremes and back to near-zero (three passes total, always in the same **Upper treble → Treble → +extremes and back to near-zero (two passes, three save cycles per band, always in the same **Upper treble → Treble → Mid → Bass → Low bass** order). This was not anticipated by the original test design but turns out to be exactly the data needed to confirm the field-to-band mapping that the earlier (2026-08-15) `CAP-005-FINDINGS.md` §5b/§6 left as an open question (inferred from a single band diff --git a/captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-FINDINGS.md b/captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-FINDINGS.md index d7bae552..191920eb 100644 --- a/captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-FINDINGS.md +++ b/captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-FINDINGS.md @@ -29,7 +29,7 @@ open question the earlier (2026-08-15) `CAP-005` capture left unresolved — **w in the 5-float quintet corresponds to which on-screen EQ band.** **Answer, per the evidence below: fully resolved.** This session's maintainer, apparently independently, ran a far more thorough procedure than the Group T minimum — 5 presets, then all 5 sliders each dragged to both extremes -and back near zero (3 passes) — which directly supplies the missing per-band isolation the earlier +and back near zero (two passes, 3 save cycles per band) — which directly supplies the missing per-band isolation the earlier capture called for. --- @@ -110,8 +110,8 @@ frame — excluded here as out of scope for the EQ analysis, not counted as a CR ## 3. Analysis: EQ presets (`EQP-002` family) -Six presets tapped in sequence, dropdown-list order (Default/`Last saved` baseline first, then -Heavy bass → Light bass → Balanced → Vocal boost → Clarity), each an isolated action with the app +Six preset frames: the `Last saved` baseline written when the Equalizer page opens (frame 2111, no +tap), then five presets tapped in dropdown-list order, Heavy bass → Light bass → Balanced → Vocal boost → Clarity, each an isolated action with the app settling before the next tap (`CAP-015-EVENT-NOTES.md`'s timeline). All six are `Sent`, HDLC address `0x0000`, control `0x3b`, outer field 16 (see §4 for what that field means): @@ -301,14 +301,14 @@ single value changed correlating with which single slider the user touched — n confirmed by decoding a second field's change, since only Bass was moved this session."* **This capture directly closes that gap.** §4's table shows the quintet's 5 elements change in a -strict, repeating order — index 4, then 3, then 2, then 1, then 0 — across three full passes, and +strict, repeating order — index 4, then 3, then 2, then 1, then 0 — in both passes (pass 1: two extreme cycles per band, pass 2: one near-zero cycle per band) *(corrected 2026-10-15, `ai-sessions/0094`)*, and §1's video review directly ties 4 of the 5 indices to a specific on-screen slider by finger position and (for index 4) an on-screen value tooltip: | Quintet index | Band | Evidence | |---|---|---| | 4 (last field, tag `0x2d`) | **Upper treble** | 🟢 Direct: video shows finger on the Upper treble slider with a `-60` tooltip exactly as this field drops to `-6.0` (frame 2573→2586) | -| 3 (tag `0x25`) | **Treble** | 🟢 By elimination + structural consistency: always the 2nd field to change in every one of the 3 passes, matching Treble's position (2nd from top) in the fixed on-screen slider order confirmed in §1; a supporting (not fully clean) video frame at 06:16:35 shows Treble's bar already at its just-changed value while Bass's is still untouched | +| 3 (tag `0x25`) | **Treble** | 🟢 By elimination + structural consistency: always the 2nd field to change in both passes, matching Treble's position (2nd from top) in the fixed on-screen slider order confirmed in §1; a supporting (not fully clean) video frame at 06:16:35 shows Treble's bar already at its just-changed value while Bass's is still untouched | | 2 (tag `0x1d`) | **Mid** | 🟢 Direct: video (t=239s/06:15:05) shows the finger directly over the "Mid" row exactly as this field starts changing | | 1 (tag `0x15`) | **Bass** | 🟢 Direct: video (t=266s/06:15:32) shows the finger on the "Bass" row mid-drag, `Save` button active, shortly before this field's cycle-1 save (frame 3083, 06:15:33.008) | | 0 (first field, tag `0x0d`) | **Low bass** | 🟢 Direct: video (t=293s/06:15:59) shows the finger at the "Low bass" row exactly as this field starts changing | @@ -316,7 +316,7 @@ position and (for index 4) an on-screen value tooltip: This also matches, exactly, the earlier `CAP-005-FINDINGS.md` §5b's inferred mapping (field 1↔Low bass … field 5↔Upper treble) — an independent capture, five days apart, landing on the identical field order. Combined with this capture's own internal repetition (the same order, -independently, 3 times) and the direct video ties for 4/5 fields, this clears +independently, in both passes) and the direct video ties for 4/5 fields, this clears `PROJECT_RULES.md` §1's promotion bar (byte-for-byte reproducible decode, cross-capture agreement, plus multiple independent within-capture confirmations) — **promoted to 🟢 FACT.** @@ -344,11 +344,11 @@ an explicit re-index. — Rcvd direction, structurally distinct from every other frame in this capture (different tag byte after the `2a`, nested small-int fields resembling `08 64 10 01`/`08 64 10 01` repeated, possibly a preset-list or capability response, not a quintet write). -- 🔴 **"Save" (outer field 18) trigger mechanism refined, not fully resolved.** The earlier capture +- ✅ **"Save" (outer field 18) trigger mechanism — answered by `CAP-053`: field 18 is written on Save** (status banner above; `CAP-053-FINDINGS.md`). The analysis below is kept as written; its slider-release hypothesis is not supported. The earlier capture hypothesized field 18 = an explicit tap of the on-screen `Save` button, roughly 5s after the matching field-16 preview. In **this** capture, field 18 fires **1.1–8.9 s** (corrected 2026-10-08, `ai-sessions/0081`; "0.05–1.9s" was a slip) after the last - field-16 preview in every one of the 15 save cycles (§4's table) — far too fast for a deliberate, - separate tap on a UI element in a different screen region, and no video frame shows a distinct + field-16 preview in every one of the 15 save cycles (§4's table) — a range that does not rule + out a separate tap (e.g. 8.9 s, 2785 → 2819), and no video frame shows a distinct `Save`-button tap between drag and save for any of the 15 cycles. **Revised 🟡 HYPOTHESIS:** field 18 fires on **slider-release** (finger lift), not on a separate `Save` button tap — the `Save` button's own purpose may be to persist the *whole custom profile* as the account's diff --git a/captures/CAP-016-2026-08-18_06-31-31_06-33-58-Group_U/CAP-016-EVENT-NOTES.md b/captures/CAP-016-2026-08-18_06-31-31_06-33-58-Group_U/CAP-016-EVENT-NOTES.md index 924c1349..b8ca3abe 100644 --- a/captures/CAP-016-2026-08-18_06-31-31_06-33-58-Group_U/CAP-016-EVENT-NOTES.md +++ b/captures/CAP-016-2026-08-18_06-31-31_06-33-58-Group_U/CAP-016-EVENT-NOTES.md @@ -64,7 +64,7 @@ extra `ffmpeg -vf fps=5`/`fps=10` passes are noted explicitly where used). `fram |---|---|---|---|---| | 06:31:31 | Start video recording. Screen: system **Bluetooth** settings sheet, "Bluetooth is off", `Use Bluetooth` toggled off. Case sits atop the phone, **lid already open**, both earbuds visible inside — case was opened before recording started (off-camera; not captured) | — | — | — | | 06:31:35 | Tap **Use Bluetooth** toggle | User (App) | — | — | -| 06:31:40 | Quick Settings tile & notification shade show "Pixel Buds Pro 2 van Ted — Left 100% Case 100% Right 100% battery" (BLE-only battery info; no classic profile connected yet) | App (Auto) | — | `LE Enhanced Connection Complete` at **06:31:40.983** (frame 691) to peer `4f:25:00:85:9a:b1`, connection handle `0x0002` — 🟡 HYPOTHESIS this is the Buds' BLE/GATT link (address differs from the classic BD_ADDR; consistent with a resolvable/random BLE address, common for Fast-Pair-capable peripherals — not independently confirmed to be the same physical device beyond the exact timing match) | +| 06:31:40 | Quick Settings tile & notification shade show "Pixel Buds Pro 2 van Ted — Left 100% Case 100% Right 100% battery" (BLE-only battery info; no classic profile connected yet) | App (Auto) | — | `LE Enhanced Connection Complete` at **06:31:40.983** (frame 691) to peer `4f:25:00:85:9a:b1`, connection handle `0x0002` — another device, not the Buds: its GATT table carries the Heart Rate service (frame 760), which the Buds' table lacks (`CAP-016-FINDINGS.md` §2, 🟢) | | 06:31:45 | Quick Settings panel shown, Bluetooth tile active (purple); Android build `17 (CP2A.260705.006)` visible on-screen | — | — | — | | 06:31:55 | **Device details** screen for "Pixel Buds Pro 2 van Ted": Left 100%/Case 100%/Right 100%, `Forget`/`Connect` buttons (classic profile **not yet connected**) | — | — | — | | 06:32:00–02 | Earbud in the slot **nearest the case hinge** lifted out by hand (held off-camera from here on) | User (Hardware) | `CASE-004`/`CASE-005` (see note below) | `Rcvd Connect Request` **06:32:02.531** (frame 1213, Buds-initiated) → `Sent Accept Connection Request` **06:32:02.545** (1214) → `Rcvd Connect Complete` **06:32:02.749** (1217, handle `0x0001`, `04:00:6e:cf:6e:07`, status `0x00`) — 🟢 FACT: this is a **Buds-initiated page**, not a phone-side `Create Connection` (contrast `CAP-001-FINDINGS.md` §1's three phone-initiated attempts) | @@ -95,7 +95,7 @@ by physical position only, per `PROJECT_RULES.md` §1's "no invented interpretat - The placeholder draft contained only the video's start (06:31:31) and end (06:33:58) lines and an incomplete metadata table (missing firmware/device/file-duration fields, both marked blank). This - revision fills in the full metadata table and the complete 20-row event timeline above, all + revision fills in the full metadata table and the complete 23-row event timeline above, all independently re-derived from the video and log. --- diff --git a/captures/CAP-016-2026-08-18_06-31-31_06-33-58-Group_U/CAP-016-FINDINGS.md b/captures/CAP-016-2026-08-18_06-31-31_06-33-58-Group_U/CAP-016-FINDINGS.md index afb5cd4f..aa971004 100644 --- a/captures/CAP-016-2026-08-18_06-31-31_06-33-58-Group_U/CAP-016-FINDINGS.md +++ b/captures/CAP-016-2026-08-18_06-31-31_06-33-58-Group_U/CAP-016-FINDINGS.md @@ -123,8 +123,8 @@ tshark -r CAP-016-btsnoop_hci.log -Y "btrfcomm && frame.time_relative>=596.0 && ``` 🟢 FACT (frame numbers independently re-checked): `DISC` (`0x43`) on DLCI `0x02`/`0x04`/`0x08`/`0x0a` -between 06:33:15.941–06:33:17.985 (frames 2770, 2772, 2784, 2785 — corrected 2026-10-03, `ai-sessions/0069`, `A68-CAP-16`: the DLCI `0x04` `DISC` is frame 2772, and the first two, on DLCI `0x02` and `0x04`, are **Received**: the Buds closed their own two channels; the phone then closed DLCI `0x08`/`0x0a`, frames 2784/2785 Sent, and re-opened all four with `SABM` 2815/2843/2904 Sent), followed by `SABM`(`0x2f`)/`UA` -(`0x63`) reopening each in turn, complete by **06:33:19.615** (frame 2906, DLCI `0x02` last). The +between 06:33:15.941–06:33:16.126 *(corrected 2026-10-15, `ai-sessions/0094`)* (frames 2770, 2772, 2784, 2785 — corrected 2026-10-03, `ai-sessions/0069`, `A68-CAP-16`: the DLCI `0x04` `DISC` is frame 2772, and the first two, on DLCI `0x02` and `0x04`, are **Received**: the Buds closed their own two channels; the phone then closed DLCI `0x08`/`0x0a`, frames 2784/2785 Sent, and re-opened all four with `SABM` 2815/2843/2904/2980 Sent — DLCI `0x0a`/`0x08`/`0x02`/`0x04`), followed by `SABM`(`0x2f`)/`UA` +(`0x63`) reopening each in turn, complete by **06:33:21.866** (frame 2982, DLCI `0x04` last) *(corrected 2026-10-15, `ai-sessions/0094`)*. The underlying ACL link (handle `0x0001`) is undisturbed throughout — no `Connect`/`Disconnect Complete` anywhere near this window (confirmed: the log's only such events are 06:32:02.749 and 06:33:45.152, both accounted for elsewhere). @@ -177,7 +177,7 @@ All 8 ANC Notify frames this session, in order: | Frame | Time | Settable-toggles | Current-state | Context | |---|---|---|---|---| -| 1521 | 06:32:03.567 | `0x00` | `0x20` (Off) | Right after classic connect, both buds still docked at this instant (first removed ~06:32:00–02, connect completes 06:32:02.749, this notify is part of the immediate post-connect RFCOMM-channel-open burst) | +| 1521 | 06:32:03.567 | `0x00` | `0x20` (Off) | Right after classic connect: the Left bud is already out (removed ~06:32:00–02, the trigger of the connect, which completes 06:32:02.749), the Right still docked (§6, frame 1515 `64 e4`); this notify is part of the immediate post-connect RFCOMM-channel-open burst | | 2128 | 06:32:18.631 | `0xe8` | `0x80` (Transparency) | ~0s before the *second* bud is lifted from the case (≈06:32:19–20) | | 2220 | 06:32:23.195 | `0xe8` | `0x80` | Idle, both buds out | | 2245 | 06:32:25.013 | `0xe8` | `0x80` | Idle | @@ -211,7 +211,7 @@ tshark -r CAP-016-btsnoop_hci.log -Y "btrfcomm.len>0 && frame.time_relative>=570 | Action | Video time | Wire result | |---|---|---| -| Case lid **closed** (empty) | 06:32:44–51 | Only routine periodic traffic in the surrounding window (device-info re-announce `google-pixel-buds-pro-v1`, `AT+BIEV`-shaped HFP heartbeat frames at their ordinary ~6–7s cadence) — **no frame timed to the lid closing specifically** | +| Case lid **closed** (empty) | 06:32:44–51 | Only routine periodic traffic in the surrounding window (device-info re-announce `google-pixel-buds-pro-v1`, `AT+BIEV`-shaped HFP frames at their ordinary irregular cadence, `CAP-009-FINDINGS.md` §2) — **no frame timed to the lid closing specifically** | | Case lid **reopened** (empty) | 06:33:02–04 | Same — no dedicated frame | This is a second, independent confirmation of `CAP-007`(old) §3.4's finding ("closing the case lid, @@ -296,7 +296,7 @@ encodes** — still 🔴 open, per `CAP-007`(old) §6. - 🔴 Why does the ANC Notify's settable-toggles byte revert from `0xe8` to `0x00` a second time at 06:33:23.456 (frame 3054), after already having re-announced `0xe8`/Transparency twice during the same bounce (frames 2768, 3012)? Not resolved by any video-visible action in this capture. -- 🔴 Does `4f:25:00:85:9a:b1` (the BLE link opened at 06:31:40.983, §2) actually belong to the same +- ✅ (answered in §2: no — handle `0x0002`'s peer exposes the Heart Rate service, frame 760; it is another device) Does `4f:25:00:85:9a:b1` (the BLE link opened at 06:31:40.983, §2) actually belong to the same physical Buds unit as classic peer `04:00:6e:cf:6e:07`? Time-coincident but not content-verified in this pass — a GATT-level read of that handle's advertised service data would settle it. - 🔴 What is the black, non-Pixel-Buds earbud/case visible in frame from ≈06:33:16–45 @@ -331,8 +331,8 @@ tshark -r CAP-016-btsnoop_hci.log -Y "frame.number==1991" -x 0030 6e 07 n. ``` After the HCI ACL (`02 01 20 2d 00`) and L2CAP (`29 00 4d 00`) headers, the HID payload is `a3 02` -(Transaction Type `DATA`, Report Type `Feature`, Report Id `0x02`) followed by 45 bytes that decode -byte-for-byte as: ASCII `#AndroidHeadTracker#1.0` (24 bytes) + **exactly 8** `0x00` zero-padding +(Transaction Type `DATA`, Report Type `Feature`, Report Id `0x02`) followed by 39 bytes (L2CAP length `0x0029` = 41 = 2 + 39) that decode +byte-for-byte as: ASCII `#AndroidHeadTracker#1.0` (23 bytes) + **exactly 8** `0x00` zero-padding bytes + ASCII `BT` (2 bytes) + `04 00 6e cf 6e 07` — the Buds' own classic BD_ADDR, in the same byte order the `bthci_acl` dissector reports it elsewhere in this log (`[Source BD_ADDR: Google_cf:6e:07 (04:00:6e:cf:6e:07)]`). Wireshark's own HID dissector misparses the leading bytes of this frame as @@ -390,7 +390,7 @@ handle, and the recurring `0xfea9` byte pair. burst's own timing (06:31:43–06:32:12) sits immediately after the BLE connection completes (06:31:40.983, §2) and entirely **before** the classic connection even forms (06:32:02.749, §1) — i.e. it overlaps the BLE-link startup window, not the classic RFCOMM channel-open burst. It does -**not** overlap the RFCOMM multiplexer channel bounce at 06:33:15.94–06:33:19.615 (§3) — that event +**not** overlap the RFCOMM multiplexer channel bounce at 06:33:15.94–06:33:21.87 (§3) — that event starts roughly **63 seconds after** this burst's last frame (2082, 06:32:12.689), with no handle- `0x0044` traffic anywhere in between. So of the two events this project might guess this burst is tied to, the timing evidence supports only the BLE-connect correlation — the channel-bounce diff --git a/captures/CAP-017-2026-08-16_18-30-12_18-37-12-Group_W/CAP-017-EVENT-NOTES.md b/captures/CAP-017-2026-08-16_18-30-12_18-37-12-Group_W/CAP-017-EVENT-NOTES.md index f0f1dc5f..276ac156 100644 --- a/captures/CAP-017-2026-08-16_18-30-12_18-37-12-Group_W/CAP-017-EVENT-NOTES.md +++ b/captures/CAP-017-2026-08-16_18-30-12_18-37-12-Group_W/CAP-017-EVENT-NOTES.md @@ -2,7 +2,7 @@ **Status:** Reviewed against `CAP-017-recording.mp4` frame-by-frame (sub-second resolution near key actions, using the video's burned-in wall-clock overlay) and cross-checked against -`CAP-017-btsnoop_hci.log` via `tshark`. This is a **second, independent `CAP-017` capture**, +`CAP-017-btsnoop_hci.log` via `tshark`. This is the **second, independent Group W session** (`CAP-017`, 18:30), distinct from `captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/` (the 11:42 attempt, which used the stock system Settings flow and produced zero discovery traffic — see that folder's `CAP-010-FINDINGS.md`). This 18:30 session instead drove **nRF Connect for Mobile** as a @@ -56,7 +56,7 @@ matched to a video-visible action. | 18:32:52–53 | User opens nRF Connect's overflow menu (`Bond`, `Refresh`, `Parse known characteristics`, `Clone device's services`, …) | User (App) | — | — | | 18:32:57 | System SSP dialog appears: **"Pair with Pixel Buds Pro 2 van Ted?"** (contacts/call-history toggle, Cancel button) | App (Auto) | `PAIR-001` | — | | 18:32:58 | User confirms pairing (off-screen tap, dialog dismisses) | User (App) | `PAIR-001` | Classic BR/EDR `Connect Complete`, status `0x00`, handle `0x0003` — frame 994, **18:32:58.425331** | -| 18:32:58.42–58.72 | *(not visible on screen)* A second short ATT burst runs immediately after classic pairing: `Read By Type`/`Find By Type Value` against low handles, a `Write Request` to handle `0x0005` (value `07`), and a `Find Information Response` identifying handle `0x0003` as the **Service Changed** characteristic | App (Auto) | `GATT-001` | Frames 960–1090, 18:32:58.150–58.719 | +| 18:32:58.15–58.72 | *(not visible on screen)* A second short ATT burst runs around classic pairing, starting 0.28 s before the classic `Connect Complete` (frame 994): `Read By Type`/`Find By Type Value` against low handles, a `Write Request` to handle `0x0005` (value `07`), and a `Find Information Response` identifying handle `0x0003` as the **Service Changed** characteristic | App (Auto) | `GATT-001` | Frames 960–1090, 18:32:58.150–58.719 | | 18:33:22–29 | User scrolls nRF Connect's **CLIENT** service list to the bottom, revealing the remaining 6 services: Telephony and Media Audio Service, **Google Fast Pair Service**, **Accessory Non-Owner Service** (128-bit UUID), Device Information, Battery Service, **Unknown Service** (128-bit UUID) — then scrolls back up | User (App) | `GATT-001` | Not present in the log payload (truncated) — read directly from video frames (18:33:22, 18:33:25, 18:33:29); see `CAP-017-FINDINGS.md` §3 | | 18:33:33 | User backgrounds nRF Connect; phone quick-settings panel shows a **"Pixel Buds Pro · 100% battery"** tile (classic profile now connected) | User (Phone) | — | — | | 18:33:39 | *(not visible on screen)* BLE link torn down | — | — | `Disconnect Complete`, handle `0x0002`, reason `0x16` (Terminated by Local Host) — frame 1690, **18:33:39.461955** | diff --git a/captures/CAP-017-2026-08-16_18-30-12_18-37-12-Group_W/CAP-017-FINDINGS.md b/captures/CAP-017-2026-08-16_18-30-12_18-37-12-Group_W/CAP-017-FINDINGS.md index e1168b41..b8e2abc8 100644 --- a/captures/CAP-017-2026-08-16_18-30-12_18-37-12-Group_W/CAP-017-FINDINGS.md +++ b/captures/CAP-017-2026-08-16_18-30-12_18-37-12-Group_W/CAP-017-FINDINGS.md @@ -3,8 +3,8 @@ > **Status as of 2026-09-30** (`ai-sessions/0059`, A58-CAP-04 — read this first; the body below is the analysis as written): `0x0f32` = Battery Level (Battery Service `0x180F`), `0x0f2a` = Firmware Revision String — 🟢 `CAP-034` (`PROTOCOL.md` §6, §4.3 Option D). Standardized, evidence-based extraction from `CAP-017-btsnoop_hci.log` + `CAP-017-recording.mp4`, -staged here for later promotion into `PROTOCOL.md` per `PROJECT_RULES.md` §2. This is a **second, -independent `CAP-017` capture**, distinct from +staged here for later promotion into `PROTOCOL.md` per `PROJECT_RULES.md` §2. This is the **second, +independent Group W session** (`CAP-017`, 18:30), distinct from `captures/CAP-010-2026-08-16_11-42-31_11-45-01-Group_W/CAP-010-FINDINGS.md` (the 11:42 attempt). Every claim below carries a status per `PROJECT_RULES.md` §1: @@ -73,7 +73,7 @@ discovery traffic: tshark -r CAP-017-btsnoop_hci.log -Y "btatt.opcode in {0x04,0x05,0x08,0x09,0x10,0x11}" \ -T fields -e frame.number -e btatt.opcode | wc -l ``` -→ **137 matches** (vs. zero in the 11:42 `CAP-010`, `CAP-002`, `CAP-003`). This alone is a real, +→ **137 matches** (0x04 ×28, 0x05 ×26, 0x08 ×52, 0x09 ×22, 0x10 ×5, 0x11 ×4); the same filter gives 6 in the 11:42 `CAP-010` and 2 in `CAP-003` — only `Read By Type` reads of known UUIDs — and none of the discovery opcodes `0x04`/`0x05`/`0x10`/`0x11` occur for the Buds in `CAP-010`, `CAP-002` or `CAP-003` *(corrected 2026-10-15, `ai-sessions/0094`)*. This alone is a real, positive result: a live `Read By Group Type`/`Read By Type`/`Find Information` walk of the full `0x0001–0xffff` handle range did happen on the wire, twice (once right after the BLE connection at 18:31:39, once again right after classic pairing completes at 18:32:58). @@ -159,7 +159,7 @@ at `t=189.0`, `189.6`, `190.0`, `190.5` (18:33:21–18:33:23), which agree byte- across five separate frames. **Cross-referenced against this project's own records:** `grep -rn "Accessory Non-Owner\|15190001\|109b862f\|FE2C" DESKRESEARCH_FINDINGS.md REVERSE_ENGINEERING.md PROTOCOL.md` returns **no -prior hits** — both 128-bit UUIDs are new to this project as of this capture. `0xFE2C` (Google +prior hits** — but both 128-bit UUIDs were already recorded from nRF Connect's UI in `CAP-004-FINDINGS.md` §6 (2026-08-11, a file the grep did not cover); this capture is their first session with live discovery on the wire. `0xFE2C` (Google Fast Pair Service) was already an ⚪ assumption/named hypothesis in `CAP-004-FINDINGS.md` §6; this is the first direct confirmation it's actually present on this device. @@ -188,7 +188,7 @@ tshark -r CAP-017-btsnoop_hci.log -Y "btatt.opcode in {0x0a,0x0b,0x12,0x13} and | Frame(s) | Time | Handle | Operation | Value (hex) | Note | |---|---|---|---|---|---| -| 911→913 | 18:31:41.958→41.990 | `0x0f28` | Read Req/Resp | `31` | Same value as `CAP-002`, `CAP-003`, the 11:42 `CAP-010` — 5th confirming session | +| 911→913 | 18:31:41.958→41.990 | `0x0f28` | Read Req/Resp | `31` | Same value as `CAP-003` and the 11:42 `CAP-010` — 3rd confirming session (`CAP-002` never reads `0x0f28`) | | 1570→1572 | 18:33:05.660→05.710 | `0x0f28` | Read Req/Resp | `31` | Re-read after classic pairing; identical value | | 1583→1604 | 18:33:07.650→07.689 | `0x0f32` | Read Req/Resp | `64` | **New handle, not seen in any prior capture of this device** | | 1605→1615 | 18:33:07.694→07.749 | `0x0f33` | Write Req/Resp | `0100` | CCCD-enable (Notify) immediately following the `0x0f32` read — standard "read once, then subscribe" pattern, consistent with `0x0f33` being `0x0f32`'s Client Characteristic Configuration Descriptor at handle+1 | @@ -365,8 +365,8 @@ Wireshark's BLE handle-aware dissector against an untruncated capture) that does identified as a candidate in `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`/`CAPTURE_BLUETOOTH_HCI_SNOOP.md`. Recommend updating both documents' `GATT-001`/`CAP-014` entries to record this as a third proven path, alongside the still-untried `pm clear`/Pixel-9a options. -- **The full 15-service primary/secondary GATT profile is now known (🟢 FACT, §3)** — five - standard LE Audio services (BASS/ASCS/PACS/VCS/MICS/AICS/CAS/TMAS-family), the standard +- **The full 15-service primary/secondary GATT profile is now known (🟢 FACT, §3)** — eight + standard LE Audio services (BASS/ASCS/PACS/VCS/MICS/AICS/CAS/TMAS) *(corrected 2026-10-15, `ai-sessions/0094`)*, the standard Device Information and Battery services, the Google Fast Pair Service (`0xFE2C`), and two previously-undocumented 128-bit proprietary services (`15190001-…` "Accessory Non-Owner Service", `109b862f-…` "Unknown Service"). @@ -390,7 +390,7 @@ Wireshark's BLE handle-aware dissector against an untruncated capture) that does response bytes can be parsed directly for handle-range + UUID data (§2). A tool that does expose handles on-screen (e.g. `gatttool`, `bluetoothctl`, or a raw `BluetoothGatt` script) would also work and doesn't require fixing the snaplen. -- Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md`'s Capture Index with this second `CAP-017` session +- (Done: the Capture Index has its own `CAP-017` row.) Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md`'s Capture Index with this second Group W session (folder `captures/CAP-017-2026-08-16_18-30-12_18-37-12-Group_W/`) — the existing `CAP-010` row currently only describes the 11:42 attempt. diff --git a/captures/CAP-018-2026-09-12_06-07-20_06-09-52-Group_Y/CAP-018-EVENT-NOTES.md b/captures/CAP-018-2026-09-12_06-07-20_06-09-52-Group_Y/CAP-018-EVENT-NOTES.md index 3e14e7b3..1902b3de 100644 --- a/captures/CAP-018-2026-09-12_06-07-20_06-09-52-Group_Y/CAP-018-EVENT-NOTES.md +++ b/captures/CAP-018-2026-09-12_06-07-20_06-09-52-Group_Y/CAP-018-EVENT-NOTES.md @@ -13,7 +13,9 @@ handle `0x0002`, 23 of the 73 frames containing a recurring `0xfea9` byte-pair m to a ~29s window right after the BLE link forms and before the classic link exists. Not yet isolated from a physical trigger — every capture that shows this burst to date also has a bud removal/insertion happening nearby in the same session. This Group isolates whether the burst is -triggered by the BLE connection forming alone, independent of any bud/case action. +triggered by the BLE connection forming alone, independent of any bud/case action. (Since +2026-10-03, `CAP-016-FINDINGS.md` §2/§11: `CAP-016`'s burst came from another device on handle +`0x0002`, a Heart-Rate wearable, not the Buds.) ## Log Metadata @@ -66,11 +68,11 @@ bud/case events. | 06:07:24–06:08:25 | Paired-device row shows cached "L:100%, C:95%, R:100%" info, **not yet marked Active** — a client-side cached display, not a live connection (see Findings §2) | System (UI) | — | Video frames `t=6`…`t=65` (`h_006.png`…`g_065.png`) all show the identical non-Active state; log shows only background `LE Extended Advertising Report`s in this window, no `Create Connection`. | | 06:08:27.773 | Phone sends classic `Create Connection` to the Buds (`04:00:6e:cf:6e:07`) | System (OS, phone-initiated) | `GATT-002` | Log frame 761. | | 06:08:28.615 | Classic `Connect Complete` (status 0x00) — stored link key reused, no SSP | System | `GATT-002` | Log frame 763. | -| 06:08:28.615–~06:08:30 | RFCOMM multiplexer + DLCIs `0x00/0x02/0x04/0x08/0x0a/0x0c` open; DLCI 0x04 `Get ANC state`(`08110000`)/`Notify`(`Settable=0x00`=docked, `Current=0x20`=Off) fires per ADR-021/022 | System (App/OS auto) | — | Log frames 1017–1041 (Get 1024, Notify 1041, `Settable=0x00` matches the buds visibly sitting in the open case on video). | +| 06:08:28.615–~06:08:30 | RFCOMM multiplexer + DLCIs `0x00/0x02/0x04/0x08/0x0a/0x0c` open; DLCI 0x04 `Get ANC state`(`08110000`)/`Notify`(`Settable=0x00` — per `DECISIONS.md` ADR-049 the Buds refuse a `Set` (🟢), 🟡 no bud worn; read as "docked" under ADR-024 at the time — `Current=0x20`=Off) fires per ADR-021/022 | System (App/OS auto) | — | Log frames 1017–1041 (Get 1024, Notify 1041, `Settable=0x00` matches the buds visibly sitting in the open case on video). | | ~06:08:50 | Device row turns purple/"Active. L:100%, R:100% batte…"; bottom notification shows full "Left 100% Case 95% Right 100%" | System (UI) | `GATT-002` | Video frame `t=90` (`j_090.png`). | | 06:07:20–06:09:52 | **Isolation check: buds/case never touched, entire video** — same static framing/shadow in every reviewed frame (`t=0,2,4,6,20,35,45,55,65,90,120,151`) | — | `GATT-002` | Video review, full duration. | | 06:09:52 | Video ends (152.16s after start) | System | `GATT-002` | Video frame `t=151` (`j_151.png`, overlay `06:09:51`). | -| 06:10:09.976–06:10:10.008 | **A *second*, unrelated LE connection forms** — `LE Extended Create Connection`/`Enhanced Connection Complete` to address `40:a8:ef:16:bb:35` (chandle `0x0004`) — **not** the Buds' own classic address, and **not video-covered** (20s after the video ends) | Buds/Case or unrelated device (Auto) | `GATT-002` | Log frames 1580/1589 — see Findings §3 for why this is attributed to an unrelated nearby device, not the Buds. | +| 06:10:09.976–06:10:10.008 | **A *second*, unrelated LE connection forms** — `LE Extended Create Connection`/`Enhanced Connection Complete` to address `40:a8:ef:16:bb:35` (chandle `0x0004`) — **not** the Buds' own classic address, and **not video-covered** (18s after the video ends) | Buds/Case or unrelated device (Auto) | `GATT-002` | Log frames 1580/1589 — see Findings §3 for why this is attributed to an unrelated nearby device, not the Buds. | | 06:10:10.034–06:10:10.409 | Full bidirectional GATT primary-service discovery on chandle `0x0004`: GAP/GATT/Device Information, two "Unknown" 128-bit-UUID services (handles `0x0040–0x0045` and `0x0050–0x0054`), and a standard **Heart Rate** service (`0x180D`) | System ↔ peer | — | Log frames 1607–1690 (full walk); Heart Rate response at frame 1663. | | 06:10:12.474–06:11:00.457 | The `0x0044` `Handle Value Notification` burst (23 frames, chandle `0x0004`) — same shape/marker (`a9fe`) as `CAP-016-FINDINGS.md` §11's original burst | Peer device (Auto) | `GATT-002` | Log frames 1929–2089 (full range: `tshark -Y "btatt.opcode==0x1b && btatt.handle==0x0044"`); see Findings §3. | diff --git a/captures/CAP-018-2026-09-12_06-07-20_06-09-52-Group_Y/CAP-018-FINDINGS.md b/captures/CAP-018-2026-09-12_06-07-20_06-09-52-Group_Y/CAP-018-FINDINGS.md index 521c311f..3ecb2da5 100644 --- a/captures/CAP-018-2026-09-12_06-07-20_06-09-52-Group_Y/CAP-018-FINDINGS.md +++ b/captures/CAP-018-2026-09-12_06-07-20_06-09-52-Group_Y/CAP-018-FINDINGS.md @@ -53,7 +53,7 @@ throughout, no hand ever approaches them. Bluetooth was toggled on at video over (frames `t=2`→`t=4`), confirmed to within ~1s of wall-clock by the log's own classic-controller bring-up sequence (`Write Extended Inquiry Response`/`Write Scan Enable`, frames 328–363, `06:07:23.926–06:07:24.023`) — i.e. video-overlay time and log wall-clock time agree closely in this -capture, consistent with every other capture in this project that has checked this. +capture (other captures show offsets of ~1–5 s, e.g. `CAP-016-FINDINGS.md` §6, `CAP-012`, `CAP-008`). **Step 2 — the classic (RFCOMM) reconnect is slow this session, but clean.** The phone does not actually send `Create Connection` to the Buds' classic address until `06:08:27.773` (frame 761) — @@ -104,8 +104,8 @@ $ tshark -r CAP-018-btsnoop_hci.log -Y 'bthci_acl.chandle==0x0004 and btatt.opco ``` Decoded: ATT opcode `0x11` (Read By Group Type Response), group length `0x14`=20B, **start handle `0x0040`, end handle `0x0045`**, 128-bit UUID (wire bytes reversed: -`abbaff00-e56a-484c-b8328b17cf6cbfe8`) — an unnamed/vendor UUID, same "Unknown Service" outcome -`CAP-016-FINDINGS.md` §11 itself reported for this handle range. **This service's handle range +`abbaff00-e56a-484c-b8328b17cf6cbfe8`) — an unnamed/vendor UUID (`CAP-016-FINDINGS.md` §11 captured +no discovery response for this handle range, so there is nothing there to compare). **This service's handle range (`0x0040`–`0x0045`) contains handle `0x0044`** — the exact handle the burst occurs on. The same discovery walk on chandle `0x0004` also finds GAP, GATT, Device Information, a second unnamed 128-bit service (`0x0050`–`0x0054`), and, critically, a **standard, SIG-assigned Heart Rate service @@ -163,10 +163,9 @@ procedure anticipated**: it isn't that the Buds' own BLE link independently trig regardless of physical action — in this capture, the burst isn't attributable to the Buds' link at all. -**Does not resolve:** whether `CAP-016`'s *original* burst (same session that also had genuine bud -removal/insertion activity nearby) was itself from the Buds or from a similarly-unrelated device — -this capture cannot retroactively re-examine `CAP-016`'s own log, and no cross-capture check of -`CAP-016-btsnoop_hci.log` was performed here (out of this task's per-phase scope). Also not resolved: +**Resolved since, by `CAP-016`'s own log** (`CAP-016-FINDINGS.md` §2/§11, corrected 2026-10-03): +`CAP-016`'s original burst was also another device's traffic — all 73 frames are on handle `0x0002`, +whose peer exposes the Heart Rate service (frame 760), not the Buds. Not resolved: what device `40:a8:ef:16:bb:35` actually is (no Device Name characteristic read was observed in the frame range checked), or why the classic reconnect took ~64s this session. @@ -179,7 +178,7 @@ happened off-camera in that window: ``` $ tshark -r CAP-018-btsnoop_hci.log -Y 'bthci_acl.chandle==0x0003 and frame.time>="2026-09-12 06:09:52" and frame.time<="2026-09-12 06:10:10"' \ -T fields -e frame.number -e _ws.col.Info -(no RFCOMM data frames — only background BLE advertising reports and periodic Number-of-Completed-Packets in this window) +(0 frames, exit 0; positive control: `bthci_acl.chandle==0x0003 and btrfcomm.len>0` without the time bound → 204 frames) ``` No DLCI activity of any kind on the Buds' own chandle in this gap — consistent with (not proof of) the buds/case remaining untouched through the unvideoed 18s tail, matching the pattern of every reviewed @@ -225,9 +224,8 @@ promotions):** ## 9. Open Questions -- 🔴 Was `CAP-016`'s *original* `0x0044` burst genuinely Buds-originated, or could it too have been - an unrelated nearby device coincidentally connecting around the same time as the bud - removal/insertion that session's own procedure involved? Not checked by this capture. +- ✅ Was `CAP-016`'s *original* `0x0044` burst genuinely Buds-originated? No — answered by + `CAP-016-FINDINGS.md` §2/§11 (2026-10-03): handle `0x0002`, a Heart-Rate wearable, not the Buds. - 🔴 What is `40:a8:ef:16:bb:35`'s actual identity (device name was not observed in the checked frame range)? - 🔴 Why did the classic reconnect take ~64s from Bluetooth-toggle to `Create Connection` this diff --git a/captures/CAP-019-2026-08-21_07-35-50_07-39-30-Group_C/CAP-019-EVENT-NOTES.md b/captures/CAP-019-2026-08-21_07-35-50_07-39-30-Group_C/CAP-019-EVENT-NOTES.md index 3f029877..d82714dd 100644 --- a/captures/CAP-019-2026-08-21_07-35-50_07-39-30-Group_C/CAP-019-EVENT-NOTES.md +++ b/captures/CAP-019-2026-08-21_07-35-50_07-39-30-Group_C/CAP-019-EVENT-NOTES.md @@ -32,7 +32,7 @@ adjacent controls. **Actual session shape (video-confirmed):** t=0–~28s is Bluetooth reconnect + app navigation (quick-settings → connect → open app → briefly detour into system audio volume, not part of either Test-ID) → "Device details". Conversation detection was found **ON**, switched **OFF** at 07:36:29 and back **ON** at 07:36:41 (both -taps on film, each with its own DLCI 0x02 write — frames 1720 and 1808); Multipoint was found **OFF** and switched **ON** once. +taps on film, each with its own DLCI 0x02 write — frames 1720 and 1808); Multipoint was found **OFF**, switched **ON** at 07:38:02 (frame 2293) and back **OFF** at 07:39:14 (frame 2482), both on film. ## Event Timeline @@ -45,6 +45,7 @@ taps on film, each with its own DLCI 0x02 write — frames 1720 and 1808); Multi | 07:36:5x–07:37:5x | Navigate back to Device details → "More settings" | User (App) | — | No RFCOMM data (navigation only) | | **~07:38:01.6** (wire write; on-screen toggle transition visible at t≈132s/07:38:02, confirmed ON by 07:38:03) | **Toggle 'Multipoint' OFF→ON**, under Device details → More settings | User (App) | `MULTI-001` | **Frame 2293** (DLCI 0x02, `Sent`/ctrl `0x4b`, 07:38:01.609130) — see §Decode. Rcvd echo: frame 2295 (07:38:01.640548). **Also triggers a Fast Pair Message Stream SASS burst** on DLCI 0x04 (Group `0x07`, frames 2296–2319, 07:38:01.641–07:38:02.047) — see §Decode. | | 07:38:06 | A push group: ANC `Notify` on DLCI 0x04 (2326 `08 13 00 04 01 e8 e8 08`), a settings-change stream packet on DLCI 0x02 (2329), DLCI 0x08 `0e 02` and `0e 01` (2330, 2332) — no Group `0x04` Code `0x12` among them (corrected 2026-10-03) | Buds (Auto) | — | Not attributable to either Test-ID — same already-documented periodic/channel-reopen pattern as `CAP-020-FINDINGS.md` §5 | +| **07:39:14** (film: ON at 07:39:11 and :13, finger on the switch 07:39:14, OFF at 07:39:16) | **Toggle 'Multipoint' ON→OFF**, More settings | User (App) | `MULTI-001` | **Frame 2482** (DLCI 0x02, phone → Buds, 07:39:13.782) `WriteSetting 4:{11:0}`, mirrored 2486/2489; followed by a DLCI 0x04 Group `0x07` (SASS) round, frames 2487–2496 (07:39:13.886–.927) — `CAP-019-FINDINGS.md` §4 | | 07:39:30 | Video end | — | — | Video last frame | ## Decode (DLCI 0x02, `libmaestro` Pigweed `pw_hdlc` channel, `PROTOCOL.md` §2.2a) @@ -104,7 +105,7 @@ MULTI-001: [off 13] field=5 wt=LD len=4 span=15..19 ``` Both match the same `field 5{ field 4{ = } }` outer wrapper first identified -in `CAP-020-FINDINGS.md` §5 for touch controls/head gestures — a fourth and fifth data point for +in `CAP-020-FINDINGS.md` §5 for touch controls/head gestures — a third and fourth data point for that wrapper being general-purpose, not feature-specific. Running field-number table so far (all 🟡 HYPOTHESIS, one capture each): @@ -124,10 +125,11 @@ that wrapper being general-purpose, not feature-specific. Running field-number t len 17, opaque), `07 34 00 0c 01 ...` (Code `0x34`, len 12, opaque — 15× across the whole log, a periodic SASS code, not Multipoint-specific; Codes `0x11`/`0x40`/`0x41`/`0x42` also run at connect (785–807) and at 07:39:13 (2487–2496), only `0x21` is unique to this burst — FINDINGS §4), ACKs `ff 01 00 03 07 40 00` / `ff 01 00 03 07 42 00` / -`ff 01 00 08 07 41 69 6e 2d 75 73 65`. **This directly confirms -`CAPTURE_BLUETOOTH_HCI_SNOOP.md`'s own Group C hint** ("Multipoint may trigger an SDP/connection -update, not just an RFCOMM command") — Multipoint activates SASS (Smart Audio Source Switching) -Message Stream traffic, previously only structurally identified (`PROTOCOL.md` §2.3) with no +`ff 01 00 08 07 41 69 6e 2d 75 73 65`. On `CAPTURE_BLUETOOTH_HCI_SNOOP.md`'s own Group C hint +("Multipoint may trigger an SDP/connection update, not just an RFCOMM command"): no SDP traffic +follows either Multipoint write (`-Y btsdp` in 07:38:00–10 and 07:39:13–20 → 0 frames; 20 elsewhere); +each write is followed by a SASS (Smart Audio Source Switching) round whose Code `0x21` is the only +Multipoint-specific code — Message Stream traffic previously only structurally identified (`PROTOCOL.md` §2.3) with no content correlated to any specific user action. **Not decoded further, flagged rather than guessed:** the remaining Group `0x07` codes' non-ASCII diff --git a/captures/CAP-019-2026-08-21_07-35-50_07-39-30-Group_C/CAP-019-FINDINGS.md b/captures/CAP-019-2026-08-21_07-35-50_07-39-30-Group_C/CAP-019-FINDINGS.md index 3b7242e0..50c91fee 100644 --- a/captures/CAP-019-2026-08-21_07-35-50_07-39-30-Group_C/CAP-019-FINDINGS.md +++ b/captures/CAP-019-2026-08-21_07-35-50_07-39-30-Group_C/CAP-019-FINDINGS.md @@ -206,22 +206,21 @@ Extends `CAP-020-FINDINGS.md` §5's table with two more settings on the same DLC | Multipoint ON | 11 | 1 | `CAP-019` | Four different settings, four different inner field numbers, three sharing value `1` and one using -value `2` — 🟡 HYPOTHESIS (strengthened by a 4th and 5th data point, still not 🟢 FACT): the outer +value `2` — 🟡 HYPOTHESIS (strengthened by a 3rd and 4th data point, still not 🟢 FACT): the outer wrapper is a general-purpose `libmaestro` settings-write envelope, with each setting owning its own inner field number. **Not confirmed:** whether the field number is a stable per-setting identifier across firmware versions/sessions, or whether value `2` for head gestures specifically means -something beyond "enabled" (e.g. a sub-mode). OFF writes were captured for conversation detection (this capture, frame 1720 `4:{22:0}`) -and touch controls (`CAP-020` frame 1995 `4:{4:0}`); not for Multipoint or head gestures. +something beyond "enabled" (e.g. a sub-mode). OFF writes were captured for all four: conversation detection (this capture, frame 1720 `4:{22:0}`), +Multipoint (this capture, frame 2482 `4:{11:0}`), touch controls (`CAP-020` frame 1995 `4:{4:0}`) and head gestures (`CAP-020` frame 2038 `4:{29:1}`) *(corrected 2026-10-15, `ai-sessions/0094`)*. ## 6. Conclusions & Next Steps - `CONV-001` isolates to one DLCI 0x02 write per tap (1720 OFF, 1808 ON), both on film and acknowledged — 🟢 FACT (`PROTOCOL.md` §4.5.1). `MULTI-001` isolates to a single write (frame 2293), verified CRC-32, within ~1s of the video-confirmed action — its field identity is 🟢 by ADR-019/025 (code); the OFF write is frame 2482, on film (§4). -- **New, higher-confidence finding:** Multipoint's DLCI 0x02 write is immediately followed by a - DLCI 0x04 Group `0x07` (SASS) negotiation burst containing an ASCII `"in-use"` string — the first - content-level, action-correlated data for this previously only structurally-identified Message - Stream group. +- Each Multipoint DLCI 0x02 write (ON 2293, OFF 2482) is immediately followed by a DLCI 0x04 + Group `0x07` (SASS) round; only Code `0x21` (2301, after ON) and the flags byte are + Multipoint-specific — Codes `0x11`/`0x40`/`0x41` ("in-use")/`0x42` also run at connect (785–807, §4). - Toggling Multipoint back OFF is in this capture (frame 2482): `field 11` flips to `0` and the SASS flags byte goes `b8` → `98`. A third sample of the flags byte is planned in `CAP-069`. diff --git a/captures/CAP-020-2026-08-21_07-46-14_07-47-49-Group_F/CAP-020-EVENT-NOTES.md b/captures/CAP-020-2026-08-21_07-46-14_07-47-49-Group_F/CAP-020-EVENT-NOTES.md index c7c7f43c..1994dba9 100644 --- a/captures/CAP-020-2026-08-21_07-46-14_07-47-49-Group_F/CAP-020-EVENT-NOTES.md +++ b/captures/CAP-020-2026-08-21_07-46-14_07-47-49-Group_F/CAP-020-EVENT-NOTES.md @@ -32,7 +32,7 @@ it **OFF** at the end (the OFF tap at 07:47:35, frame 2038; corrected 2026-10-03 video start) and app navigation (Bluetooth quick-settings → connect → open Pixel Buds app → "Device details" → "Controls and gestures"), not itself part of either Test-ID. Both toggles were found **already OFF** at the start of this session and were switched **ON** once each; "Use touch controls" was then switched **OFF** -again at 07:47:20 (on film, frame 1995 — re-checked `ai-sessions/0052`). +again at 07:47:20 (on film, frame 1995 — re-checked `ai-sessions/0052`), and "Use head gestures" **OFF** at 07:47:35 (on film, frame 2038). ## Event Timeline @@ -43,9 +43,9 @@ again at 07:47:20 (on film, frame 1995 — re-checked `ai-sessions/0052`). | 07:46:2x–07:46:44 | Navigate: connect device row → open Pixel Buds app → "Device details" → "Controls and gestures" | User (App) | — | No RFCOMM data traffic (navigation only, no query) | | **07:46:44** (video t=30s, tap seen mid-transition; confirmed ON by t=32s) | **Toggle 'Use touch controls' OFF→ON** | User (App) | `TOUCH-001` | **Frame 1741** (DLCI 0x02, Sent, 07:46:44.850477) — see §Decode below. ACK/Rcvd echo: frames 1749 (07:46:45.106282), 1750/1751 (DLCI 0x08, 07:46:45.140–.142, one-time capability strings — coincidental, part of a channel-reopen burst, not touch-controls-specific), 1753 (07:46:45.142917) | | 07:47:00 (video t=46s tap; "Optimize head gestures" info dialog shown 07:47:01–07:47:03; final ON confirmed on-screen 07:47:04) | **Toggle 'Use head gestures' OFF→ON** | User (App) | `HEAD-001` | **Frame 1935** (DLCI 0x02, Sent, 07:47:00.005060) — see §Decode below. ACK/Rcvd echo: frames 1939 (07:47:00.441100), 1942 (07:47:00.444725). The wire write precedes the on-screen "Optimize head gestures" explainer dialog by ~1s and has no separate wire action tied to the dialog's dismissal — the dialog is client-side-only. | -| 07:46:56–07:47:19 | Screen remains on "Controls and gestures" with both toggles ON | — | — | — | -| **07:47:35** (film: ON at 07:47:33–34, finger 07:47:35, OFF from 07:47:36) | **Toggle 'Use head gestures' ON→OFF** (row added 2026-10-03, `ai-sessions/0069`) | User (App) | `HEAD-001` | **Frame 2038** (DLCI 0x02, phone → Buds, 07:47:35.122421) `WriteSetting 4:{29:1}`, mirrored 2044 (07:47:35.541); DLCI 0x08 `04 16 00 02 08 02` 2045 — `CAP-020-FINDINGS.md` §4 | +| 07:47:04–07:47:19 | Screen remains on "Controls and gestures" with both toggles ON | — | — | — | | **07:47:20** (tap; ON at 07:47:17–19, OFF on-screen from 07:47:21) | **Toggle 'Use touch controls' ON→OFF** | User (App) | `TOUCH-001` | **Frame 1995** (DLCI 0x02, phone → Buds, 07:47:20.097043) `WriteSetting 4:{4:0}` → empty `RESPONSE` status OK + mirrored `4:{4:0}`, frame 2005 (07:47:20.629698) — see `CAP-020-FINDINGS.md` §3 | +| **07:47:35** (film: ON at 07:47:33–34, finger 07:47:35, OFF from 07:47:36) | **Toggle 'Use head gestures' ON→OFF** (row added 2026-10-03, `ai-sessions/0069`) | User (App) | `HEAD-001` | **Frame 2038** (DLCI 0x02, phone → Buds, 07:47:35.122421) `WriteSetting 4:{29:1}`, mirrored 2044 (07:47:35.541); DLCI 0x08 `04 16 00 02 08 02` 2045 — `CAP-020-FINDINGS.md` §4 | | 07:47:49 | Video end | — | — | Video last frame | ## Decode (DLCI 0x02, `libmaestro` Pigweed `pw_hdlc` channel, `PROTOCOL.md` §2.2a) diff --git a/captures/CAP-020-2026-08-21_07-46-14_07-47-49-Group_F/CAP-020-FINDINGS.md b/captures/CAP-020-2026-08-21_07-46-14_07-47-49-Group_F/CAP-020-FINDINGS.md index 38c94ce9..ce66ae93 100644 --- a/captures/CAP-020-2026-08-21_07-46-14_07-47-49-Group_F/CAP-020-FINDINGS.md +++ b/captures/CAP-020-2026-08-21_07-46-14_07-47-49-Group_F/CAP-020-FINDINGS.md @@ -190,9 +190,10 @@ it "fired again 35 s after `HEAD-001` with no visible action" — that was the O ## 6. Conclusions & Next Steps -- `TOUCH-001` and `HEAD-001` both isolate cleanly to a single `Sent` frame on DLCI 0x02, within - <1s of the video-confirmed action, with a verified CRC-32 and a consistent structural envelope — - 🟡 HYPOTHESIS level, matching this project's usual bar for a first single-capture attribution. +- `TOUCH-001` and `HEAD-001` each isolate to one DLCI 0x02 `Sent` write per filmed tap (touch + controls 1741 ON / 1995 OFF, head gestures 1935 ON / 2038 OFF), each within <1s of the action, + with a verified CRC-32: `qhr` field 4 = "Use touch controls" is 🟢 FACT (§3, ADR-019); field 29 + (head gestures, 1 = off, 2 = on) stays 🟡 HYPOTHESIS (§4, `PROTOCOL.md` §4.5.4). - New structural finding: the DLCI 0x02 outer `field 5{ field 4{...} }` wrapper is shared across at least 3 different settings (EQ, touch controls, head gestures), distinct from any one setting's inner content. **Promoted 2026-08-23** (`PROTOCOL.md` §4.5's shared preamble, `DECISIONS.md` @@ -207,8 +208,9 @@ it "fired again 35 s after `HEAD-001` with no visible action" — that was the O - 🔴 What do inner field numbers `4` (touch controls) and `29` (head gestures) represent inside the DLCI 0x02 `field 5{ field 4{...} }` wrapper — a per-setting message-type ID, a field-within-a- larger-schema position, or something else? → copied to `PROTOCOL.md` §6; answered for field 4 (and 7, 12) by the `qhr` schema, §8. -- 🔴 Does the same `field 5{ field 4{...} }` wrapper generalize to *every* `libmaestro` setting - (§4.5's whole remaining list), or only to some? → copied to `PROTOCOL.md` §6. +- ✅ Does the same `field 5{ field 4{...} }` wrapper generalize to *every* `libmaestro` setting + (§4.5's whole remaining list), or only to some? → copied to `PROTOCOL.md` §6; answered at the + schema level by §8 (it is `qhr`'s own oneof; only a subset of fields is wire-confirmed). ## 8. Addendum (2026-08-30) — inner field numbers resolved against a 2026-08-30 APK static-analysis pass (`REVERSE_ENGINEERING.md`'s `qjc`/`qja`/`qhr` entries); §7's first open question answered @@ -286,7 +288,7 @@ mechanical search, not evidence the field is unwritten). **Answers §7's first open question directly**: inner field numbers 4/29 are not an arbitrary per-setting ID scheme — they are `qhr`'s own oneof field numbers (the same 38-field schema `PROTOCOL.md` -§4.1's ANC command and §4.2's EQ quintet also live inside), addressed via standard protobuf wire-format +§4.2's EQ quintet also lives inside; §4.1's ANC command is on DLCI 0x04, not in `qhr`), addressed via standard protobuf wire-format tags. §7's second question (does the wrapper generalize to every setting) is now also answered at the schema level: `qhr` has 38 fields total, of which this project's various captures have wire-confirmed only a subset — the wrapper itself generalizes by construction (it's just `qhr`'s own oneof), but diff --git a/captures/CAP-021-2026-08-21_07-59-36_08-07-04-Group_G/CAP-021-EVENT-NOTES.md b/captures/CAP-021-2026-08-21_07-59-36_08-07-04-Group_G/CAP-021-EVENT-NOTES.md index a597029d..8f8afe0c 100644 --- a/captures/CAP-021-2026-08-21_07-59-36_08-07-04-Group_G/CAP-021-EVENT-NOTES.md +++ b/captures/CAP-021-2026-08-21_07-59-36_08-07-04-Group_G/CAP-021-EVENT-NOTES.md @@ -53,8 +53,8 @@ procedure's listed 1-2-3-4-5):** |---|---|---|---|---| | 07:59:36 | Video start | — | — | Video first frame | | 07:59:4x–08:00:5x | Bluetooth reconnect; navigate Device details → Controls and gestures; brief dead-end into "Touch controls" info screen (tap "Use touch controls" text, not the toggle) and back | User (App) | — | Connection handshake burst (DLCI 0x02/0x04/0x08); a `2001`→`2026` sequentially-incrementing DLCI 0x02 burst (38 frames, 07:59:42.9–07:59:44.5) matches the connection-time state-sync pattern, not a user action | -| ~08:00:10 | Incidental re-send of the `TOUCH-001`-shaped write (`field4{field4=1}`) while navigating in/out of "Touch controls"/"Controls and gestures" | User (App, incidental) | — | Frame 1626, `08:00:09.891252` — not a new toggle (touch controls was already ON, per `CAP-020`); a re-affirm, not attributed to any Group G Test-ID | -| **08:01:23.784** (video-confirmed: finger taps "Digital assistant" tab on "Customize left" at t≈108s/08:01:24) | **Set Left → Digital assistant** | User (App) | `HOLD-002` | **Frame 1895** — see §Decode. Rcvd echo: frame 1903 (08:01:24.230), which uniquely also echoes the *prior* value (`5` = Active noise control) alongside the new one (`6` = Digital assistant) | +| **08:00:10** (film: switch OFF at 08:00:07–09, finger on it 08:00:10, ON from 08:00:11) | **Toggle 'Use touch controls' OFF→ON** on "Controls and gestures" (incidental to Group G) | User (App) | `TOUCH-001` | Frame 1626, `08:00:09.891252`, `WriteSetting 4:{4:1}`, mirrored 1633/1637 — a real OFF→ON write: the connect-time read 1140 is `4:{4:0}` (`CAP-020` ended with touch controls OFF, frame 1995) | +| **08:01:23.784** (video-confirmed: finger taps "Digital assistant" tab on "Customize left" at t≈108s/08:01:24) | **Set Left → Digital assistant** | User (App) | `HOLD-002` | **Frame 1895** — see §Decode. Rcvd echo: frame 1903 (08:01:24.230), `4:{7:{1:{4:{1:6}} 2:{4:{1:5}}}}` — the mirror carries both sides' current values (Left `6` = Digital assistant, new; Right `5` = Active noise control, unchanged), as the later mirror 3623 does (`1:…6, 2:…6`) | | **08:03:16.151** (video-confirmed: finger taps "Digital assistant" tab on "Customize right" at t=220s) | **Set Right → Digital assistant** | User (App) | `HOLD-004` | **Frame 3619** — see §Decode | | **08:03:49.667** (video context: at t=253s, "Controls and gestures" shows Left/Right both "Digital assistant"; finger taps "Left" row, about to revert it) | **Set Left → Active noise control** (i.e. `HOLD-001`, executed *after* `HOLD-002` in this session, not before) | User (App) | `HOLD-001` | **Frame 4315** — see §Decode | | **08:04:09.920** | **Set Right → Active noise control** (`HOLD-003`, executed after `HOLD-004`) | User (App) | `HOLD-003` | **Frame 4976** — see §Decode | @@ -138,20 +138,20 @@ Decoding `field 5{ field 4{ ... } }`'s inner content mechanically (`field=tag>>3 `wiretype=tag&7`): ``` -1895: field7 wt=LD len=6 { field1 wt=LD len=4 { field4 wt=varint value=6 } } -3619: field7 wt=LD len=6 { field2 wt=LD len=4 { field4 wt=varint value=6 } } -4315: field7 wt=LD len=6 { field1 wt=LD len=4 { field4 wt=varint value=5 } } -4976: field7 wt=LD len=6 { field2 wt=LD len=4 { field4 wt=varint value=5 } } +1895: field7 wt=LD len=6 { field1 wt=LD len=4 { field4 wt=LD len=2 { field1 wt=varint value=6 } } } +3619: field7 wt=LD len=6 { field2 wt=LD len=4 { field4 wt=LD len=2 { field1 wt=varint value=6 } } } +4315: field7 wt=LD len=6 { field1 wt=LD len=4 { field4 wt=LD len=2 { field1 wt=varint value=5 } } } +4976: field7 wt=LD len=6 { field2 wt=LD len=4 { field4 wt=LD len=2 { field1 wt=varint value=5 } } } ``` **🟡 HYPOTHESIS, well-supported by a clean 2×2 cross-check:** `field 1` = Left, `field 2` = Right -(within the outer `field 7`), and the innermost `field 4` varint value encodes the selected action: +(within the outer `field 7`), and the innermost `field 4{ field 1 }` varint value encodes the selected action: `5` = Active noise control, `6` = Digital assistant. This is not a single-sample guess — all 4 combinations (Left/Right × ANC/Assistant) were exercised this session and each produced exactly the predicted field/value pair, with no exceptions. **Additional corroboration:** frame 1903 (the Rcvd echo for 1895, `08:01:24.230`) contains **both** `field1{field4=6}` (the new value) **and** -`field2{field4=5}` (a second, distinct sub-block) in the same message — read as an old/new or -Left/Right status pair, consistent with this reading without requiring it. +`field2{field4=5}` in the same message — Left's new value and Right's current value; the +settings mirror carries both sides (3623 likewise, `1:…6, 2:…6`). ### HOLD-005: ANC-mode rotation checklist diff --git a/captures/CAP-021-2026-08-21_07-59-36_08-07-04-Group_G/CAP-021-FINDINGS.md b/captures/CAP-021-2026-08-21_07-59-36_08-07-04-Group_G/CAP-021-FINDINGS.md index 4f3ea320..344c874e 100644 --- a/captures/CAP-021-2026-08-21_07-59-36_08-07-04-Group_G/CAP-021-FINDINGS.md +++ b/captures/CAP-021-2026-08-21_07-59-36_08-07-04-Group_G/CAP-021-FINDINGS.md @@ -108,7 +108,7 @@ field4(len2){ field1=varint(5|6) } } } } }` — e.g. 1895 `3a 06 0a 04 22 02 08 (08:03:49, "Controls and gestures" shows both Left/Right already "Digital assistant", finger tapping "Left" row to revert it). -**Status:** 🟡 **HYPOTHESIS**, but a comparatively strong one — all 4 of the 2×2 (earbud × action) +**Status:** 🟢 **FACT** since 2026-08-30 (`qhr` field 7, `DECISIONS.md` ADR-019, §8; the text below is the original 🟡 reasoning) *(corrected 2026-10-15, `ai-sessions/0094`)* — all 4 of the 2×2 (earbud × action) combinations were exercised and each produced exactly the field/value pair a consistent scheme predicts, with frame 1903 (Rcvd echo of 1895) independently containing *both* `field1{field4=6}` (new) and `field2{field4=5}` (a second value) in one message, consistent with — though not @@ -214,16 +214,16 @@ added from a 2026-08-23 external-audit re-analysis of all 1,123 frames — see ` decode was performed to support any specific payload semantics. Per `AGENTS.md` §13's zero-creativity rule, this is left as an open question rather than a guessed interpretation. - Whether this recurs in any other capture, or is specific to whatever incidental - app/OS/connection state was active in this one session (e.g. the earlier `TOUCH-001`-shaped - re-send noted in `CAP-021-EVENT-NOTES.md` around 08:00:10, or some other incidental state) is + app/OS/connection state was active in this one session (e.g. the earlier filmed `TOUCH-001` + OFF→ON write, frame 1626, noted in `CAP-021-EVENT-NOTES.md` at 08:00:10, or some other incidental state) is unknown — no other capture in this batch shows any DLCI 0x0a payload to compare against. -- `TODO.md`'s `CAP-008` item already earmarks DLCI 0x0a for a phone-call capture; this finding - shows the channel is not exclusively call-related, since nothing here involved a call. +- `CAP-008` (2026-08-26, a real phone call) found DLCI 0x0a silent during the call + (`CAP-008-FINDINGS.md` §6), so this burst is not call-related either. ## 5. Cross-command structural comparison -Extends `CAP-019-FINDINGS.md` §5's table — `HOLD-001`–`HOLD-004` are the first settings observed to -nest a **third** level (`field5{field4{field7{field1|2{field4=N}}}}`) rather than the two-level +Extends `CAP-019-FINDINGS.md` §5's table — `HOLD-001`–`HOLD-004` nest further levels +(`field5{field4{field7{field1|2{field4{field1=N}}}}}`; the EQ write of `CAP-005`/`CAP-015` already nests `field5{field4{field16{…}}}`) rather than the two-level `field5{field4{}}` seen for `TOUCH-001`/`HEAD-001`/`CONV-001`/`MULTI-001`. 🟡 HYPOTHESIS (new): the outer `field5{field4{...}}` wrapper's *inner* content structure scales with how much the setting needs to express — a plain boolean toggle uses a single varint; a per-earbud, multi-value @@ -234,8 +234,8 @@ earbud, before the value itself. - All 5 Test-IDs (`HOLD-001`–`HOLD-005`) isolate to DLCI 0x02 writes, cross-checked against video at each write's exact timestamp — the clean 2×2 combination coverage for `HOLD-001`–`HOLD-004` - is stronger evidence than a single-sample capture, though still 🟡 HYPOTHESIS overall (no - official spec). + is stronger evidence than a single-sample capture; `HOLD-001`–`HOLD-004`'s field 7 is 🟢 FACT + (ADR-019, §8), `HOLD-005`'s field 12 bit order is 🟢 per ADR-046 (status banner). - **Recommended next step:** an isolated single-checkbox-toggle-on-one-earbud-only capture would resolve `HOLD-005`'s open Left/Right attribution question. - **New, unattributed finding (§4a):** this session is the first to record any payload on DLCI diff --git a/captures/CAP-022-2026-08-21_08-15-24_08-17-27-Group_H/CAP-022-EVENT-NOTES.md b/captures/CAP-022-2026-08-21_08-15-24_08-17-27-Group_H/CAP-022-EVENT-NOTES.md index a2cee3bc..0806509b 100644 --- a/captures/CAP-022-2026-08-21_08-15-24_08-17-27-Group_H/CAP-022-EVENT-NOTES.md +++ b/captures/CAP-022-2026-08-21_08-15-24_08-17-27-Group_H/CAP-022-EVENT-NOTES.md @@ -41,7 +41,7 @@ sessions. |---|---|---|---|---| | 08:15:24 | Video start | — | — | Video first frame | | 08:15:2x–08:15:5x | Bluetooth reconnect; navigate Device details → Sound | User (App) | — | Connection handshake burst | -| **08:15:53.965** (video-confirmed tap ≈t=33s) | **Mono audio OFF→ON** | User (App) | `AUDIO-001` | Frame 1621 | +| **08:15:53.965** (video-confirmed: finger on the switch 08:15:53–54, t≈29–30s; ON from 08:15:55) | **Mono audio OFF→ON** | User (App) | `AUDIO-001` | Frame 1621 | | **08:16:04.605** (video-confirmed toggle OFF by t=41s/08:16:05) | **Mono audio ON→OFF** | User (App) | `AUDIO-001` | Frame 1823 (a second, opposite-direction sample of the same Test-ID) | | — | Navigate Sound → Equalizer (tap "Equalizer") | User (App) | — | No RFCOMM write (navigation only) | | **08:16:15.825** (video-confirmed toggle OFF by t=53s) | **Volume EQ ON→OFF** | User (App) | `AUDIO-002` | Frame 1871 | @@ -78,10 +78,10 @@ happening in this window). **Not confirmed, flagged rather than guessed (`AGENTS.md` §13):** `field 17`'s numeric scale, range, and which direction (Left/Right) increasing values represent. The 7 sampled values (199, 123, 49, 30, 150, 200, 10) do not monotonically track the video's own coarse left-right -description of the drag (center → right-extreme → back toward center) in an obvious way at 1fps -video-sampling resolution — a continuous drag emits many wire updates per second (the video's 1s -sampling only captures 3 checkpoints across 7 wire values), so this is most likely just -under-sampling on the video side, not a contradiction, but it is **not resolved** which specific +description of the drag (center → right-extreme → back toward center) in an obvious way — the 7 +writes are 6.6–8.8 s apart over 46 s (08:16:34.739 … 08:17:20.458), one per settle of repeated +touches on the slider (film: finger on it at 08:16:35, :43, :51, 08:17:07, :13, :19, :21), not a +burst from one continuous drag; it is **not resolved** here which specific wire value corresponds to which on-screen slider position. A future capture that pauses briefly at each slider extreme (rather than dragging continuously) would let a clean min/max/center range be read directly. diff --git a/captures/CAP-022-2026-08-21_08-15-24_08-17-27-Group_H/CAP-022-FINDINGS.md b/captures/CAP-022-2026-08-21_08-15-24_08-17-27-Group_H/CAP-022-FINDINGS.md index fc7c820e..45aa9252 100644 --- a/captures/CAP-022-2026-08-21_08-15-24_08-17-27-Group_H/CAP-022-FINDINGS.md +++ b/captures/CAP-022-2026-08-21_08-15-24_08-17-27-Group_H/CAP-022-FINDINGS.md @@ -79,8 +79,8 @@ for name, hx in frames.items(): Output: `ON -> ...2a052203980101` / `OFF -> ...2a0522039801 00`, decoding (offset 13) to `field5(len5){ field4(len3){ field19=1|0 } }`. -Video: toggle tapped ON at t≈33s (08:15:57), tapped OFF at t≈41s (08:16:05) — both within a few -seconds of frames 1621 (`08:15:53.965`) and 1823 (`08:16:04.605`) respectively. +Video: toggle tapped ON at t≈29–30s (finger on the switch 08:15:53–54, ON from 08:15:55), tapped OFF at t≈41s (08:16:05) — each within ~1 s +of frames 1621 (`08:15:53.965`) and 1823 (`08:16:04.605`) respectively *(corrected 2026-10-15, `ai-sessions/0094`)*. **Status:** 🟢 **FACT** — `field 19` = Mono audio, promoted 2026-09-03 (`DECISIONS.md` ADR-019 Update, maintainer-approved), based on this wire evidence (2 samples, ON and OFF, both within the @@ -101,7 +101,7 @@ Both CRC-32 verified. Video: toggle already ON at t=49s (08:16:13), OFF by t=53s matching frame 1871), ON again by the time the user leaves the Equalizer page (matching frame 1895 at 08:16:24). -**Status:** 🟡 **HYPOTHESIS** — `field 15` = Volume EQ, 2 samples (both directions), clean timing +**Status:** 🟢 **FACT** since 2026-09-08 (`DECISIONS.md` ADR-025 Update; status banner) *(corrected 2026-10-15, `ai-sessions/0094`)* — `field 15` = Volume EQ, 2 samples (both directions), clean timing match. ## 5. Analysis: `AUDIO-003` (Volume balance) @@ -171,9 +171,10 @@ types field 17 as `SINT32` (`REVERSE_ENGINEERING.md` line 856), so these raw var zigzag-decoded (`(n>>1) ^ -(n&1)`), not read as plain unsigned values as this section originally did — the `zigzag=` column in the script output above is the correct reading. -Video confirms this whole window (08:16:34–08:17:20) is a single continuous drag gesture on the -"Balance" slider, with no other action happening — centered at t=70s (08:16:34), toward the right -edge by t=78s (08:16:42), back toward center-right by t=86s (08:16:50). +Video shows this whole window (08:16:34–08:17:20) as repeated separate touches on the "Balance" +slider (finger on it at 08:16:35, 08:16:43, 08:16:51, 08:17:07, 08:17:13, 08:17:19, 08:17:21), with +no other action happening; the seven writes (1922–2099) are 6.6–8.8 s apart, one per settle, not a +stream from one continuous drag *(corrected 2026-10-15, `ai-sessions/0094`)*. **Status:** 🟢 **FACT** for the field-number identity and semantic name ("Volume balance") — promoted 2026-09-03 (`DECISIONS.md` ADR-019 Update), based on this wire evidence plus, @@ -210,14 +211,14 @@ finding first raised in `CAP-020-FINDINGS.md` §5 was **promoted to 🟢 FACT 20 (`DECISIONS.md` ADR-013) for the outer wrapper shape itself — this capture's 7 field numbers are part of the cross-capture evidence base for that promotion. Field 19's ("Mono audio") and field 17's ("Volume balance") own meanings were independently **promoted to 🟢 FACT 2026-09-03** -(`DECISIONS.md` ADR-019 Update, per §3/§5 above); field 15's ("Volume EQ") own meaning stays 🟡 -HYPOTHESIS, unaffected by this update. +(`DECISIONS.md` ADR-019 Update, per §3/§5 above); field 15's ("Volume EQ") own meaning was promoted +to 🟢 FACT on 2026-09-08 (ADR-025 Update). ## 7. Conclusions & Next Steps - All 3 Test-IDs (`AUDIO-001`–`AUDIO-003`) isolate cleanly to DLCI 0x02 writes, each cross-checked against video timing, each with at least 2 samples (except `AUDIO-003`, which has 7 samples from - one continuous gesture). + repeated touches on the Balance slider, 6.6–8.8 s apart). - **Recommended next step:** a capture that pauses at each Balance slider extreme (rather than a continuous drag) would let the value range/direction be read directly (done: `CAP-046`, ADR-026). Persistence is settled (§5). diff --git a/captures/CAP-023-2026-08-21_08-23-40_08-25-21-Group_I/CAP-023-EVENT-NOTES.md b/captures/CAP-023-2026-08-21_08-23-40_08-25-21-Group_I/CAP-023-EVENT-NOTES.md index 1a3326ab..af4069a5 100644 --- a/captures/CAP-023-2026-08-21_08-23-40_08-25-21-Group_I/CAP-023-EVENT-NOTES.md +++ b/captures/CAP-023-2026-08-21_08-23-40_08-25-21-Group_I/CAP-023-EVENT-NOTES.md @@ -46,7 +46,7 @@ numbers, and m[ore]"*). Only `FW-001` and `FW-002` were captured. | 08:23:40 | Video start | — | — | Video first frame | | 08:23:4x–08:24:1x | Bluetooth reconnect; navigate Device details → More settings → Firmware update | User (App) | — | Connection handshake burst (DLCI 0x02/0x04/0x08); DLCI 0x08 frame 849 (`08:23:46.038`) carries the ASCII string `release_5.203` — see §Decode | | **~08:24:17** (t=37s) | **Firmware update screen fully loaded** — "Left earbud: release_5.203", "Right earbud: release_5.203", "Case: release_5.203" all shown | — | `FW-002` | No new wire traffic accompanies this — the displayed strings match frame 849's connection-time value exactly (see §Decode); the screen reads from an already-cached value, not a live query | -| **~08:24:23** (t=43s) | **Tap 'Up to date' status row** (manual re-check) | User (App) | `FW-001` | **Zero RFCOMM traffic** in a ±15s window around this tap (checked explicitly, not assumed) — see §Decode | +| **~08:24:23** (t=43s) | **Tap 'Up to date' status row** (manual re-check) | User (App) | `FW-001` | **Zero RFCOMM traffic** in 08:24:02–08:24:45 (checked explicitly with a positive control) — see §Decode | | ~08:24:30 (t=50s) | Incidental tap on 'Automatic updates' toggle (off then back on) | User (App, incidental) | — | Zero RFCOMM traffic — not part of any Group I Test-ID | | 08:24:5x–08:25:1x | Navigate back to More settings, then back into Firmware update again | User (App) | — | No RFCOMM traffic | | 08:25:21 | Video end | — | — | Video last frame | @@ -88,8 +88,9 @@ confirms what the *app* calls "firmware version" for the *user-facing* display, **Firmware check is cached, not live-queried:** tapping "Up to date" (`FW-001`) and opening the Firmware update screen (`FW-002`) produced **zero** RFCOMM traffic on any DLCI, checked explicitly -across the whole ±15s window around each action (`tshark -Y "btrfcomm.len>0 and frame.time_epoch>X -and frame.time_epoch0 && frame.time>="2026-08-21 08:24:02" && +frame.time<="2026-08-21 08:24:45"'` → 0 rows, exit 0; the same filter without the time bound matches +212 frames, the last at 08:23:49.761). Per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group Q's three-way-outcome convention (applied here even though this isn't a Group Q item, since the same "confirmed local action, no wire traffic" shape applies): this is a **positive finding**, not a gap — the "Device firmware status: Up to date" and per-component version strings are read from diff --git a/captures/CAP-023-2026-08-21_08-23-40_08-25-21-Group_I/CAP-023-FINDINGS.md b/captures/CAP-023-2026-08-21_08-23-40_08-25-21-Group_I/CAP-023-FINDINGS.md index 3ea88a61..837e2796 100644 --- a/captures/CAP-023-2026-08-21_08-23-40_08-25-21-Group_I/CAP-023-FINDINGS.md +++ b/captures/CAP-023-2026-08-21_08-23-40_08-25-21-Group_I/CAP-023-FINDINGS.md @@ -74,10 +74,13 @@ Tapping "Up to date" (t≈43s/08:24:23) and opening the Firmware update screen ( produced **zero** RFCOMM traffic on any DLCI: ``` -$ tshark -r CAP-023-btsnoop_hci.log -Y "btrfcomm.len>0 and frame.time_epoch>1787293445 and frame.time_epoch<1787293475" \ +$ tshark -r CAP-023-btsnoop_hci.log -Y 'btrfcomm.len>0 && frame.time>="2026-08-21 08:24:02" && frame.time<="2026-08-21 08:24:45"' \ -T fields -e frame.number -(no output) +(no output; exit 0) ``` +Positive control: `-Y 'btrfcomm.len>0'` alone matches 212 frames, the last one 1132 at 08:23:49.761 +(e.g. frame 849, 08:23:46.038) — there is no RFCOMM data from 08:23:49.8 to the log's end. +(The earlier epoch window 1787293445–1787293475 was 08:24:05–08:24:35, not ±15 s around both actions.) **Status:** 🟢 **FACT** (a checked negative, not an absence of checking — per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group Q's three-way-outcome convention, applied here by analogy): @@ -112,8 +115,9 @@ traceability check, not silently left blank. - 🔴 What does DLCI 0x04's `"Revision 6"` string represent, if not the user-facing firmware version? → already tracked in `PROTOCOL.md` §0.1/§6, unchanged by this capture. -- 🔴 `FW-003` (serial numbers) and `FW-004` (connection status) remain unattributed — no capture - has visited the "About" page yet. → copied to `PROTOCOL.md` §6. +- 🔴 `FW-004` (connection status) remains unattributed — not exercised in any capture → copied to + `PROTOCOL.md` §6. `FW-003` (serial numbers) is covered since: OpenControl reads the serials with + one `GetHardwareInfo` per Connect (`DECISIONS.md` ADR-058; `CAP-072-FINDINGS.md` §2; TESTPLAN `FW-003`). --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/captures/CAP-023-2026-08-21_08-23-40_08-25-21-Group_I/CAP-023-FINDINGS.md - https://tedsluis.github.io/opencontrolpixelbudspro2/captures/CAP-023-2026-08-21_08-23-40_08-25-21-Group_I/CAP-023-FINDINGS diff --git a/captures/CAP-024-2026-08-21_08-31-27_08-33-24-Group_J/CAP-024-EVENT-NOTES.md b/captures/CAP-024-2026-08-21_08-31-27_08-33-24-Group_J/CAP-024-EVENT-NOTES.md index b1302231..1b2feda6 100644 --- a/captures/CAP-024-2026-08-21_08-31-27_08-33-24-Group_J/CAP-024-EVENT-NOTES.md +++ b/captures/CAP-024-2026-08-21_08-31-27_08-33-24-Group_J/CAP-024-EVENT-NOTES.md @@ -8,8 +8,9 @@ settings-write shape across the whole session, cross-checked against video timin detection and the two case-sound settings. **Location note:** In-ear detection lives on **Device details → More settings** (top item under -"Device settings", alongside Multipoint — confirming `CAP-019`'s Multipoint change persisted -across sessions, both still showing ON at this session's start). Case sounds is a separate +"Device settings", alongside Multipoint — both showing ON at this session's start; `CAP-019` had +left Multipoint OFF (frame 2482 `4:{11:0}`), so it was switched on again between the sessions +(this capture's connect read 1014: `4:{11:1}`). Case sounds is a separate sub-screen (**More settings → Case sounds**) with two toggles labeled **"Bud return"** (= `CASE-001`, "Earbuds replaced" in the skeleton's wording) and **"Other alerts"** (= `CASE-002`, "Other notifications"). diff --git a/captures/CAP-024-2026-08-21_08-31-27_08-33-24-Group_J/CAP-024-FINDINGS.md b/captures/CAP-024-2026-08-21_08-31-27_08-33-24-Group_J/CAP-024-FINDINGS.md index 5f3cec95..bbfa169b 100644 --- a/captures/CAP-024-2026-08-21_08-31-27_08-33-24-Group_J/CAP-024-FINDINGS.md +++ b/captures/CAP-024-2026-08-21_08-31-27_08-33-24-Group_J/CAP-024-FINDINGS.md @@ -76,7 +76,7 @@ Decodes to `field5(len4){ field4(len2){ field2=0|1 } }`. Video: In-ear detection start (persisted from a prior session), tapped OFF (t≈44–52s, matching frame 1850 at `08:32:10.980`), tapped back ON (t≈58s, matching frame 1912 at `08:32:23.091`). -**Status:** 🟡 **HYPOTHESIS** — `field 2` = In-ear detection, clean 2-sample ON/OFF match. +**Status:** 🟢 **FACT** since 2026-09-28 (`DECISIONS.md` ADR-019 Update; status banner) *(corrected 2026-10-15, `ai-sessions/0094`)* — `field 2` = In-ear detection, clean 2-sample ON/OFF match. ## 4. Analysis: `CASE-001` ("Bud return", labeled "Earbuds replaced" in the app's own settings list) @@ -179,11 +179,11 @@ Extends the running table (`CAP-022-FINDINGS.md` §6): No case-specific vs. bud-specific channel/address distinction was found — case sounds use the same DLCI 0x02 envelope as every bud-targeted setting in this batch, just their own field numbers. -Now 9 distinct settings across `CAP-019`–`CAP-024`, each with its own distinct inner field number +Now 12 distinct settings (`qhr` fields 2, 4, 7, 11, 12, 15, 17, 19, 22, 27, 28, 29) across `CAP-019`–`CAP-024`, each with its own distinct inner field number inside the shared `field5{field4{...}}` wrapper — the general-purpose-envelope finding (`CAP-020-FINDINGS.md` §5) held with no counter-examples across this whole batch and was **promoted to 🟢 FACT 2026-08-23** (`DECISIONS.md` ADR-013) for the outer wrapper shape. `field2` -(In-ear detection) remains its own, separately-labeled 🟡 HYPOTHESIS; `field28` ("Bud return") was +(In-ear detection) was promoted to 🟢 FACT 2026-09-28 (ADR-019 Update); `field28` ("Bud return") was **promoted to 🟢 FACT in full, and `field27` to 🟢 FACT at the category level, 2026-09-03** (`DECISIONS.md` ADR-019 Update, per §4/§5 above) — `field27`'s specific "Other alerts" label stays 🟡 HYPOTHESIS. @@ -191,8 +191,8 @@ inside the shared `field5{field4{...}}` wrapper — the general-purpose-envelope ## 7. Conclusions & Next Steps - All 3 Test-IDs isolate cleanly to DLCI 0x02 writes with video-timing correlation; `INEAR-001` - and `CASE-002` have unambiguous 2-sample (both-direction) evidence, `CASE-001` has one - unambiguous and one weakly-correlated sample. + and `CASE-002` have unambiguous 2-sample (both-direction) evidence, and so does `CASE-001` + (connect read 1096 `28:1` → 1988 `28:0` → 2023 `28:1`, both real changes, §4). - **Recommended next step:** none specific to this capture — the pattern is now well-established across 6 captures in this batch. diff --git a/captures/CAP-025-2026-08-21_08-40-52_08-45-26-Group_K/CAP-025-EVENT-NOTES.md b/captures/CAP-025-2026-08-21_08-40-52_08-45-26-Group_K/CAP-025-EVENT-NOTES.md index 33e32b61..4f6bef9a 100644 --- a/captures/CAP-025-2026-08-21_08-40-52_08-45-26-Group_K/CAP-025-EVENT-NOTES.md +++ b/captures/CAP-025-2026-08-21_08-40-52_08-45-26-Group_K/CAP-025-EVENT-NOTES.md @@ -33,7 +33,7 @@ app splits "Find My Buds" across **two distinct screens** with two distinct mech | Test device | Pixel 7a, Android 17 (⚪ ASSUMPTION), official Pixel Buds Companion App (version not visible on screen) | | Video file | `CAP-025-recording.mp4` (273.7s, 08:40:52–08:45:26 local time) | | Log file | `CAP-025-btsnoop_hci.log` (08:40:49.737–08:47:14.185, brackets the video) | -| Buds MAC (partial, per `AGENTS.md` §7/§9) | Not independently confirmed this session | +| Buds MAC (partial, per `AGENTS.md` §7/§9) | Confirmed on the wire: the only Connection Complete (`-Y 'bthci_evt.code==0x03'`, frame 349, 08:40:57.082, status `0x00`) has the Buds' classic address (`bthci_evt.bd_addr`, as in `CAP-025-FINDINGS.md` §1) | ## Procedure (per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group K) @@ -52,7 +52,7 @@ app splits "Find My Buds" across **two distinct screens** with two distinct mech | **08:41:41.243** (video: tap "Mute Right" ≈t=49s; status returns to "Connected") | Stop ringing Right (part of the `FIND-002` action's own lifecycle, not a separate Test-ID) | User (App) | `FIND-002` | Frame 2120 (+Buds' own message 2124); Buds' ACK 2123, phone's ACK 2127 | | **08:41:46.703** (video: tap "Ring Left" at t=54s; status → "Left earbud volume increasing…", button → "Mute Left") | **Play sound on Left earbud (start)** | User (App) | `FIND-001` | Frame 2131 (+Buds' own message 2134); Buds' ACK 2133, phone's ACK 2137 | | **08:41:58.882** (video: tap "Mute Left" at t=67s; status returns idle) | Stop ringing Left (part of `FIND-001`'s lifecycle) | User (App) | `FIND-001` | Frame 2180 (+Buds' own message 2183); Buds' ACK 2182, phone's ACK 2186 | -| 08:42:02.900 | A 5th, asymmetric (single send + single ACK) instance of the same stop-shaped frame — see §Decode caveat | — | `FIND-001` (likely a residual retry) | Frame 2202; ACK 2204 | +| 08:42:02.900 | A 5th, asymmetric (single send + single ACK) instance of the same stop-shaped frame — see §Decode caveat | — | `FIND-001` (no video-visible tap; the bud was not ringing) | Frame 2202; the Buds answer with their ACK only (2204), no status message — `CAP-025-FINDINGS.md` §4 | | **08:42:03–08:42:11** (video: t=71s tap "Most recent location" → t≈75s Find Hub map loads) | Navigate to Find Hub / Find My Device map view | User (App) | — | No DLCI 0x04 Group `0x04` traffic accompanies this navigation | | **~08:42:24–08:44:32** (video: "Connecting…"/"Stop sound" screen visible ≈t=95s/08:42:27 onward; map view with 3 per-target icons visible ≈t=150s/08:43:22 and ≈t=250s/08:45:02 — "Pixel Buds Pro 2 - Left" detail sheet with "Play sound"/"Share ownership") | **Attempted `FIND-003`(Case)/`FIND-004`(both) via Find Hub's own "Play sound" flow** | User (App) | `FIND-003`, `FIND-004` | **No `Group 0x04 Code 0x01` frame appears anywhere in this window** — instead, three full classic-RFCOMM reconnection bursts occur (08:43:42, 08:44:25, 08:45:04, ~40s apart, each matching the standard connection-open capability-handshake shape) — see §Decode | | 08:45:26 | Video end | — | — | Video last frame | @@ -121,8 +121,8 @@ correlate with, but are not proven to be caused by, the Find Hub flow's own conn confirmed; `FIND-003`/`FIND-004` attempted but resolve to a different (network-mediated) mechanism, not a wire-visible local command — explicitly flagged, not silently missing. - [x] Write `CAP-025-FINDINGS.md` per `PROJECT_RULES.md` §2. -- [ ] If confirmed, promote `PROTOCOL.md` §4.4 from 🟡 HYPOTHESIS to 🟢 FACT — **not done here**; - maintainer sign-off required per `AGENTS.md` §6. +- [x] If confirmed, promote `PROTOCOL.md` §4.4 from 🟡 HYPOTHESIS to 🟢 FACT — **done 2026-08-23** + for Left/Right Ring, with maintainer sign-off (`DECISIONS.md` ADR-011; `CAP-025-FINDINGS.md` §8). - [x] Update this session's row in `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index. - [x] Rename this capture's folder to the actual session date/start-time/end-time (video-derived). diff --git a/captures/CAP-025-2026-08-21_08-40-52_08-45-26-Group_K/CAP-025-FINDINGS.md b/captures/CAP-025-2026-08-21_08-40-52_08-45-26-Group_K/CAP-025-FINDINGS.md index 1ec9b78b..87acb365 100644 --- a/captures/CAP-025-2026-08-21_08-40-52_08-45-26-Group_K/CAP-025-FINDINGS.md +++ b/captures/CAP-025-2026-08-21_08-40-52_08-45-26-Group_K/CAP-025-FINDINGS.md @@ -23,7 +23,7 @@ legend per `PROJECT_RULES.md` §1: | Log file | [`CAP-025-btsnoop_hci.log`](./CAP-025-btsnoop_hci.log) — 384.4s, 2026-08-21 08:40:49.737–08:47:14.185 (+0200) | | Notes file | [`CAP-025-EVENT-NOTES.md`](./CAP-025-EVENT-NOTES.md) | | Video file | [`CAP-025-recording.mp4`](./CAP-025-recording.mp4) — 273.7s, 08:40:52–08:45:26 local time | -| Buds MAC (partial, per `AGENTS.md` §7/§9) | `04:00:6e:cf:6e:07` (confirmed via `bthci_evt.bd_addr` on the wire; DLCI-based filtering was used for the analysis below regardless) | +| Buds MAC (partial, per `AGENTS.md` §7/§9) | `04:00:6e:cf:6e:07` (confirmed via `bthci_evt.bd_addr` on the wire: `-Y 'bthci_evt.code==0x03'` → frame 349, 08:40:57.082, the only Connection Complete; DLCI-based filtering was used for the analysis below regardless) | ## 2. Methodology & a key structural finding @@ -109,7 +109,7 @@ Same envelope as `FIND-002`. `Value=0x02` = start-ring-**Left** (distinct from R | Value | Meaning | Evidence | |---|---|---| -| `0x00` | Stop / mute ringing (shared, not per-earbud) | 3 occurrences, all following a video-confirmed "Mute Left/Right" tap or immediately after | +| `0x00` | Stop / mute ringing (shared, not per-earbud) | 3 occurrences: two follow a video-confirmed "Mute Right"/"Mute Left" tap (2120, 2180); the third (2202) has no video-visible tap and is answered with an ACK only (§4) | | `0x01` | Start ringing **Right** | 1 start (2040; the Buds' own message 2045 carries the same value), video-confirmed | | `0x02` | Start ringing **Left** | 1 start (2131; the Buds' own message 2134), video-confirmed | @@ -157,8 +157,10 @@ resolved to the same level as, `FIND-001`/`FIND-002`'s confirmed local mechanism the Left/Right Ring mechanism specifically, 2026-08-23 (`DECISIONS.md` ADR-011, maintainer sign-off obtained per `AGENTS.md` §6). - **`FIND-003`/`FIND-004` are structurally different** — not a gap in this capture's coverage, but - a genuine architectural finding: Case/combined ringing goes through Find Hub's network-mediated - path, not the direct local Message Stream command. This should be recorded as its own open + a genuine architectural finding: the official app routes Case/combined ringing through Find Hub's + network-mediated path, not the direct local Message Stream command. The Device Action spec does + define a local Ring value `0x03` = both buds, untested on this firmware (`DECISIONS.md` ADR-027 + Update 2026-09-24; TESTPLAN `FIND-004`). This should be recorded as its own open question, not conflated with the Left/Right mechanism. - **Recommended next step:** if this project ever wants local, offline "ring the case" support (a Zero-GMS goal, `AGENTS.md` §1), the Find Hub/cloud path is out of scope by that same rule @@ -170,7 +172,8 @@ resolved to the same level as, `FIND-001`/`FIND-002`'s confirmed local mechanism - 🟡 What the Buds' ACK's state byte (`ff 01 00 03 04 01 00` — `00` in all five) and their own `04 01 00 01 xx` message mean beyond "the new ring state": a Ring stopped on the bud itself is the test (`CAP-068`). The "two ACK variants" question is answered (§3; `PROTOCOL.md` §6 ticked 2026-10-03). - 🔴 Does `FIND-003`(Case)/`FIND-004`(both) genuinely require Find My Device Network/GMS, with no - local-only fallback? Directly relevant to this project's Zero-GMS goal. → copied to + local-only fallback? For `FIND-004` the spec's local Ring value `0x03` is a candidate, untested + (ADR-027 Update 2026-09-24). Directly relevant to this project's Zero-GMS goal. → copied to `PROTOCOL.md` §6. - 🔴 What triggers the repeated classic-connection reopen bursts during the Find Hub flow (three times, ~40s apart)? → copied to `PROTOCOL.md` §6. diff --git a/captures/CAP-026-2026-09-12_06-49-53_06-52-49-Group_L/CAP-026-EVENT-NOTES.md b/captures/CAP-026-2026-09-12_06-49-53_06-52-49-Group_L/CAP-026-EVENT-NOTES.md index b1fc8ae6..3c366e9b 100644 --- a/captures/CAP-026-2026-09-12_06-49-53_06-52-49-Group_L/CAP-026-EVENT-NOTES.md +++ b/captures/CAP-026-2026-09-12_06-49-53_06-52-49-Group_L/CAP-026-EVENT-NOTES.md @@ -58,15 +58,15 @@ confused with genuinely spontaneous traffic during it. | Time | Action | Initiator | Test-ID | Wire evidence / Notes | |---|---|---|---|---| | 06:49:53 | Video starts. Bluetooth Settings sheet open, "Bluetooth is off." Buds/case sit on top of the phone. | User | — | Video `t=0`. | -| ~06:49:58 | Bluetooth toggled ON; row shows "Connecting…", battery notification already showing "Left 100% Case 93% Right 100%" at the bottom (cached) | User (Hardware) | — | Video `t=8` (overlay `06:50:01`). | +| ~06:49:58 | Bluetooth toggled ON; row shows "Connecting…", battery notification already showing "Left 100% Case 93% Right 100%" at the bottom (cached) | User (Hardware) | — | Video `t=5` (overlay `06:49:58`): toggle already ON, row "Connecting…"; the battery notification is visible from `t=7` (overlay `06:50:00`) and still at `t=8` (overlay `06:50:01`). | | 06:49:59.9–06:50:00.9 | Classic BR/EDR `Create Connection`→`Connect Complete` (stored key, no SSP) | System | — | Log frame 413, `06:50:00.878`. | -| 06:50:01.30 | DLCI 0x04 `Group 0x03 Code 0x03` (Option B candidate): `L=0x64(100) R=0x64(100)` | System (auto) | — | Log frame 702 — matches on-screen L/R. | +| 06:50:01.30 | DLCI 0x04 `Group 0x03 Code 0x03` ("Battery updated", ADR-031/033; was "Option B candidate" when written): `L=0x64(100) R=0x64(100)` | System (auto) | — | Log frame 702 — matches on-screen L/R. | | 06:50:01.36–.40 | DLCI 0x04 `Get ANC state`(`08110000`)/`Notify`(`Settable=0x00`=docked,`Current=0x20`=Off) | System (auto) | — | Log frames 731/739 — ADR-021/022 replication. | | 06:50:01.65 | DLCI 0x08 `Group 0x0e Code 0x01` (Option E battery triple): `Left=100(flag=1,idx=1), Right=100(flag=1,idx=2), Case=95(idx=3, no-flag/short form)` | System (auto) | — | Log frame 871 — **Case value (95) does not match the on-screen 93% shown throughout this video** (see Findings §3 — a further instance of ADR-014's "short/no-flag form = possibly stale" reading). | | ~06:50:07 | Device row shows "Active. 100% battery." (purple) | System (UI) | — | Video `t=14`. | | ~06:50:13 | Notification shade opened, shows "Devices • now, Pixel Buds Pro 2 van Ted, L:100% C:93% R:100%" | User | `BATT-001` | Video `t=20` — the battery-status-on-reconnect notification; all underlying wire data already delivered at `06:50:00–01`, ~12–13s earlier. | | ~06:50:19 | "Device details" (Pixel Buds app) screen opened; **idle observation window starts** | User (App) | `BATT-001` | Video `t=26`. | -| 06:50:19–06:51:19 | Idle wait, nothing touched, screen stays on "Device details" | — | `BATT-001` | Video spot-checked at `t=40,50,60,70,80` — unchanged. **Zero DLCI 0x02/0x04/0x08 traffic anywhere in this window** (log query, `06:51:00`→end, returns 0 rows) — no spontaneous re-query during the idle wait. | +| 06:50:19–06:51:19 | Idle wait, nothing touched, screen stays on "Device details" | — | `BATT-001` | Video spot-checked at `t=40,50,60,70,80` — unchanged. **Zero DLCI 0x02/0x04/0x08 payload frames anywhere in this window** (`tshark -Y '(btrfcomm.dlci==2 or btrfcomm.dlci==4 or btrfcomm.dlci==8) and btrfcomm.len>0 and frame.time>="2026-09-12 06:50:06"'` → 0 rows, exit 0; positive control: the same filter from `06:50:05` returns frames 1339/1340/1342/1343, 06:50:05.372–.380, the last payload frames of the connect burst) — no spontaneous re-query during the idle wait. | | ~06:51:19 | Recent-apps (multitasking) screen opened, showing the Settings→Bluetooth debugging page (used earlier to enable HCI snoop) as a background task — **idle window ends** | User | `BATT-001` | Video `t=86`. | | ~06:51:22–06:51:29 | Returns to home screen, opens app drawer | User | — | Video `t=96`. | | ~06:51:31 | Pixel Buds app tapped — "Device details" reloads (progress bar visible); **second observation window starts** | User (App) | `OBS-001` | Video `t=98`. | diff --git a/captures/CAP-026-2026-09-12_06-49-53_06-52-49-Group_L/CAP-026-FINDINGS.md b/captures/CAP-026-2026-09-12_06-49-53_06-52-49-Group_L/CAP-026-FINDINGS.md index 7bc3390f..6d0f9256 100644 --- a/captures/CAP-026-2026-09-12_06-49-53_06-52-49-Group_L/CAP-026-FINDINGS.md +++ b/captures/CAP-026-2026-09-12_06-49-53_06-52-49-Group_L/CAP-026-FINDINGS.md @@ -93,7 +93,9 @@ Complete` (frame 413) and **zero** `Disconnection Complete` events for the entir ``` $ tshark -r CAP-026-btsnoop_hci.log -Y "bthci_evt.code==0x05" -T fields -e frame.number -(0 rows) +(0 rows, exit 0) +$ tshark -r CAP-026-btsnoop_hci.log -Y "bthci_evt.code==0x03" -T fields -e frame.number # positive control +413 ``` The video-observed app force-close (recent-apps screen at `t=86`, overlay `06:51:19`) and reopen @@ -104,7 +106,12 @@ staying open throughout. Checked directly for any wire signal in and around this ``` $ tshark -r CAP-026-btsnoop_hci.log -Y '(btrfcomm.dlci==2 or btrfcomm.dlci==4 or btrfcomm.dlci==8) \ and btrfcomm.len>0 and frame.time>="2026-09-12 06:51:00"' -T fields -e frame.number -(0 rows) +(0 rows, exit 0) +$ # positive control: the same filter with frame.time>="2026-09-12 06:50:05" +1339 +1340 +1342 +1343 ``` **Zero DLCI 0x02/0x04/0x08 data frames anywhere from `06:51:00` (well before the multitask-switch diff --git a/captures/CAP-027-2026-08-30_15-45-14_15-49-07-Group_N/CAP-027-EVENT-NOTES.md b/captures/CAP-027-2026-08-30_15-45-14_15-49-07-Group_N/CAP-027-EVENT-NOTES.md index c139892a..03618aa2 100644 --- a/captures/CAP-027-2026-08-30_15-45-14_15-49-07-Group_N/CAP-027-EVENT-NOTES.md +++ b/captures/CAP-027-2026-08-30_15-45-14_15-49-07-Group_N/CAP-027-EVENT-NOTES.md @@ -40,7 +40,7 @@ phone) ## Event Timeline All wire-evidence timestamps below are `btsnoop_hci.log` frame times (local, +0200). Per-action -video-overlay timestamps in the leftmost column run a consistent **~1.5–2.3s ahead of** the +video-overlay timestamps in the leftmost column run a consistent **1.20–2.31s (one outlier, 3.15s, frame 1909) ahead of** the matching wire event across every action in this session (video shows the action slightly later than the wire event that caused it — encoding/registration lag, not a clock fault); this offset is noted once here rather than repeated per row. No frame of any kind is directly visible confirming @@ -56,7 +56,7 @@ following the noted action, within the established offset), not a visual gesture | 15:45:30 | spotify app: user selects play, music starts playing | — | — | — | | 15:45:39 | Tap once on right bud | User (Hardware) | `TOUCH-002` | AVRCP frame 1580 @15:45:36.892 `Rcvd Pass Through: Control - PAUSE (Pushed)` (offset 2.11s) | | 15:45:48 | swipe on right bud (unintentionaly) | User (Hardware) | — | AVRCP frame 1794 @15:45:46.515 `Rcvd Pass Through: Control - PLAY (Pushed)` (offset 1.49s) — see `CAP-027-FINDINGS.md` §3.2: the "unintentional" contact still produced a Pass Through command, but a **tap-shaped** one (PLAY), not the swipe/volume shape | -| 15:45:59 | Double-tap on right bud | User (Hardware) | `TOUCH-003` | AVRCP frame 1909 @15:45:55.855 `Rcvd Pass Through: Control - FORWARD (Pushed)` (offset 4.15s) | +| 15:45:59 | Double-tap on right bud | User (Hardware) | `TOUCH-003` | AVRCP frame 1909 @15:45:55.855 `Rcvd Pass Through: Control - FORWARD (Pushed)` (offset 3.15s) | | 15:46:09 | Triple-tap on right bud | User (Hardware) | `TOUCH-004` | AVRCP frame 1980 @15:46:07.368 `Rcvd Pass Through: Control - BACKWARD (Pushed)` (offset 1.63s) | | 15:46:24 | Triple-tap on right bud | User (Hardware) | `TOUCH-004` | AVRCP frame 2066 @15:46:22.800 `Rcvd Pass Through: Control - BACKWARD (Pushed)` (offset 1.20s) | | 15:46:35 | Tap once on left bud | User (Hardware) | `TOUCH-002` | AVRCP frame 2139 @15:46:32.692 `Rcvd Pass Through: Control - PAUSE (Pushed)` (offset 2.31s) | @@ -100,8 +100,8 @@ following the noted action, within the established offset), not a visual gesture evidence source, following the hex & script rule (§1 rule 4a). - [x] Update this session's row in `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index — status from `planned` to `analyzed`, fill in Android/firmware/app-version columns and the log path. -- [ ] Rename this capture's folder from the `yyyy-MM-dd_HH-mm-ss_HH-mm-ss` placeholder to the - actual session date/start-time/end-time. **Not done in this pass** — the placeholder folder +- [x] Rename this capture's folder from the `yyyy-MM-dd_HH-mm-ss_HH-mm-ss` placeholder to the + actual session date/start-time/end-time. **n/a — folder already named** — the folder name already embeds the correct actual date/start/end (`2026-08-30_15-45-14_15-49-07`), so no rename is needed; only the `-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-` skeleton pattern from the template was ever a placeholder, and this folder does not use it. diff --git a/captures/CAP-027-2026-08-30_15-45-14_15-49-07-Group_N/CAP-027-FINDINGS.md b/captures/CAP-027-2026-08-30_15-45-14_15-49-07-Group_N/CAP-027-FINDINGS.md index 42436cd9..80f3482d 100644 --- a/captures/CAP-027-2026-08-30_15-45-14_15-49-07-Group_N/CAP-027-FINDINGS.md +++ b/captures/CAP-027-2026-08-30_15-45-14_15-49-07-Group_N/CAP-027-FINDINGS.md @@ -16,8 +16,8 @@ packets, 2026-08-30 15:44:22.07–15:51:47.04 local/+0200 — wider than the vid `CAP-027-recording.mp4` (233.77s, 15:45:14–15:49:07 local, on-screen wall-clock overlay) · **Buds MAC (partial):** `04:00:6e:cf:6e:07` — same physical device as `CAP-021`/`CAP-033`. -**Scope note:** Group N covers `TOUCH-002`–`TOUCH-007` (`TESTPLAN_BLUETOOTH_HCI_SNOOP.md` line -165–170). Spotify was playing throughout (started 15:45:30), which turned out to be essential — +**Scope note:** Group N covers `TOUCH-002`–`TOUCH-007` (`TESTPLAN_BLUETOOTH_HCI_SNOOP.md`, rows +`TOUCH-002`–`TOUCH-007`). Spotify was playing throughout (started 15:45:30), which turned out to be essential — see §3. --- @@ -32,7 +32,7 @@ frame-level detail sought here — out of this Group's scope). Spotify playback **Timing-offset note (applies to every correlation in this file):** across all 15 timestamped actions in this session that have an unambiguous single wire match, the video-overlay timestamp -runs a consistent **1.20–2.31s ahead of** the wire event it corresponds to (one outlier at 4.15s, +runs a consistent **1.20–2.31s ahead of** the wire event it corresponds to (one outlier at 3.15s, frame 1909 @15:45:55.855, `TOUCH-003` @15:45:59). This is consistent with encoding/registration lag on the video side, not a clock fault, and matches the same-direction offset independently observed in `CAP-033` (this project's other 2026-08-30 session). All attributions below are **timing correlations** (nearest @@ -68,13 +68,13 @@ capture with the command below. ### 3.1 Mapping table -| Test-ID | Gesture | AVRCP event | Frame(s) (representative) | Semantics match `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` line | +| Test-ID | Gesture | AVRCP event | Frame(s) (representative) | Semantics match `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` row (same Test-ID) | |---|---|---|---|---| -| `TOUCH-002` | Tap once | `Pass Through: PAUSE` or `PLAY` (Pushed), toggling on current play state | 1580, 2139 (PAUSE); 2251, 2464, 2642 (PLAY) | 165: "Play/pause" ✅ | -| `TOUCH-003` | Double-tap | `Pass Through: FORWARD` (Pushed) | 1909, 2313 | 166: "Next track" ✅ | -| `TOUCH-004` | Triple-tap | `Pass Through: BACKWARD` (Pushed) | 1980, 2066, 2396, 2575, 2746 | 167: "Previous track" ✅ | -| `TOUCH-005` | Swipe forward | `RegisterNotification: VolumeChanged` (increase) | 2822 (59→65%), 2999 (59→65%) | 168: "Raise volume" ✅ | -| `TOUCH-006` | Swipe backward | `RegisterNotification: VolumeChanged` (decrease) | 2873 (65→59%), 3032 (65→59%) | 169: "Lower volume" ✅ | +| `TOUCH-002` | Tap once | `Pass Through: PAUSE` or `PLAY` (Pushed), toggling on current play state | 1580, 2139 (PAUSE); 2251, 2464, 2642 (PLAY) | "Play/pause" ✅ | +| `TOUCH-003` | Double-tap | `Pass Through: FORWARD` (Pushed) | 1909, 2313 | "Next track" ✅ | +| `TOUCH-004` | Triple-tap | `Pass Through: BACKWARD` (Pushed) | 1980, 2066, 2396, 2575, 2746 | "Previous track" ✅ | +| `TOUCH-005` | Swipe forward | `RegisterNotification: VolumeChanged` (increase) | 2822 (59→65%), 2999 (59→65%) | "Raise volume" ✅ | +| `TOUCH-006` | Swipe backward | `RegisterNotification: VolumeChanged` (decrease) | 2873 (65→59%), 3032 (65→59%) | "Lower volume" ✅ | Every occurrence, its exact frame number, and its video timestamp/offset is listed in `CAP-027-EVENT-NOTES.md`'s Event Timeline — not repeated here to avoid duplicating a table across @@ -145,13 +145,13 @@ touch-gesture signal. **Not decoded further in this file** — doing so is out o (no Test-ID this Group covers predicts DLCI 0x02 content), and `PROTOCOL.md` §2.2a's own open question about this channel's Sent-direction payload semantics is unaffected either way by this observation. (Decoded 2026-10-08, `ai-sessions/0081`: `scripts/pwrpc_decode.py` names the 30-byte frames `maestro_pw.Dosimeter`/`SubscribeToLiveDb` -server-stream packets — 416, 208 per channel 19/21, of 426 stream packets — the live hearing-wellness feed, `PROTOCOL.md` §2.2a; `ai-sessions/0068` +server-stream packets — 416, 208 per channel 19/21, of 426 stream packets (decoded pw_rpc packets; the "398 of 511" above counts RFCOMM payload frames of exactly 30 bytes, a different unit) — the live hearing-wellness feed, `PROTOCOL.md` §2.2a; `ai-sessions/0068` counted 420.) ## 4. `TOUCH-007` (press-and-hold): carried on DLCI 0x04, the *official* Fast Pair Message Stream — not `libmaestro` (🟢 FACT for the frame content; 🟡 HYPOTHESIS for the gesture attribution) Unlike `TOUCH-002`–`TOUCH-006`, `TOUCH-007`'s two occurrences (right bud, then left bud — both -un-timestamped `TBD`/`TDB` in the original procedure notes) show up as RFCOMM traffic, on **DLCI +`TBD`/`TDB` in the draft procedure notes, timestamps since recovered from the wire) show up as RFCOMM traffic, on **DLCI 0x04** — the same officially-documented Fast Pair Message Stream channel already confirmed 🟢 FACT for ANC control in `PROTOCOL.md` §4.1, **not** DLCI 0x02 (`libmaestro`/pigweed). diff --git a/captures/CAP-028-2026-09-12_07-16-15_07-17-18-Group_O/CAP-028-EVENT-NOTES.md b/captures/CAP-028-2026-09-12_07-16-15_07-17-18-Group_O/CAP-028-EVENT-NOTES.md index dff70974..bdf8a80d 100644 --- a/captures/CAP-028-2026-09-12_07-16-15_07-17-18-Group_O/CAP-028-EVENT-NOTES.md +++ b/captures/CAP-028-2026-09-12_07-16-15_07-17-18-Group_O/CAP-028-EVENT-NOTES.md @@ -7,7 +7,7 @@ camera (matching the draft's own "outside of video" note), so this session's evi for the gesture window itself; the surrounding connect/app-navigation sequence is video-confirmed. **Purpose (`CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group O):** main run-through group, never yet -captured. **Requires 'Head gestures' enabled first (Group F, `CAP-020`)** — do not run this +captured. **Requires 'Head gestures' enabled first (Group F, `CAP-020`; note: `CAP-020` left it off, frame 2038 there — it was on in this session per ReadSetting frame 1350, `CAP-028-FINDINGS.md` §2)** — do not run this session before `CAP-020` has toggled Head gestures on. Head gestures are a Pixel Buds Pro 2-exclusive feature, officially confirmed present (`TESTPLAN_BLUETOOTH_HCI_SNOOP.md` `HEAD-001`); this session tests whether the gestures themselves (Nod/Shake) generate any wire-visible traffic, @@ -20,7 +20,7 @@ or are purely on-device given they only act on an already-active call/notificati | Capture ID | `CAP-028` | | Group(s) | O | | Date | 2026-09-12 | -| Firmware version | ⚪ ASSUMPTION `release_5.203` (carried over; already known working for Head gestures since `CAP-020`, per `PROTOCOL.md` §0.1's `XC-03` note — not re-checked on-screen this session) | +| Firmware version | ⚪ ASSUMPTION `release_5.203` (carried over; Head gestures read back as on in this session, frame 1350, `CAP-028-FINDINGS.md` §2 — `CAP-020` left it off; `PROTOCOL.md` §0.1's `XC-03` note; not re-checked on-screen this session) | | Test device | Pixel 7a, Android 17, Pixel Buds Companion App `1.0.955078536`, Google Play services active | | Video file | `CAP-028-recording.mp4` — 62.68s, overlay `07:16:15`–`07:17:18`. **Camera points at the phone screen, not the user's head — the physical Nod/Shake gestures are not visible on camera.** | | Log file | `CAP-028-btsnoop_hci.log` — 2,187 packets, 227.71s, `07:16:14.148`–`07:20:01.855`, untruncated | @@ -35,8 +35,8 @@ or are purely on-device given they only act on an already-active call/notificati states Head Gestures require firmware ≥4.467, a version scheme distinct from this project's own confirmed wire-baseline `"release_5.203"` — `PROTOCOL.md` §0.1's `XC-03` note leaves the numeric relationship between the two schemes unreconciled, but also confirms this isn't currently - blocking anything: Head gestures are already known working on this project's own test device - since `CAP-020`. Record the firmware version shown on screen for this session regardless, per + blocking anything: Head gestures read back as on in this session (frame 1350, + `CAP-028-FINDINGS.md` §2; `CAP-020` left them off). Record the firmware version shown on screen for this session regardless, per `PROJECT_RULES.md` rule 11's reproducibility requirement. - Confirm HCI snoop logging is already enabled and running, and the Buds are connected, before either the Head-gestures-enable step (if needed) or the Nod/Shake actions begin. @@ -58,11 +58,11 @@ or are purely on-device given they only act on an already-active call/notificati | 07:16:15 | Video starts; Bluetooth Settings sheet open, "Bluetooth is off." | User | — | Video `t=0`. | | ~07:16:19–20 | Bluetooth toggled ON | User (Hardware) | — | (not individually re-checked at 1s resolution this pass; draft's `07:16:20` accepted, consistent with the log's own timing below). | | 07:16:20.912 | Classic `Connect Complete` (stored key, no SSP) | System | — | Log frame 241. | -| 07:16:21.719–.742 | DLCI 0x04 `Get`(`08110000`)/`Notify`(`Settable=0xe8`=**undocked**, `Current=0x40`=Adaptive) | System (auto) | — | Log frames 759/765 — confirms the Buds were **not** in the case at connect time (consistent with being worn for this Group's own test, though this could not be independently confirmed on video — see Findings §2). | | 07:16:21.689 | **A second, distinct LE connection forms** (chandle `0x0003`, address `7d:0a:16:e6:10:68` — not the Buds' classic address) | Peer device (Auto) | — | Log frame 736 — see Findings §3; this connection's own GATT discovery finds a standard Heart Rate service, matching `CAP-018`'s already-documented unrelated-device pattern. | +| 07:16:21.719–.742 | DLCI 0x04 `Get`(`08110000`)/`Notify`(`Settable=0xe8`=**undocked**, `Current=0x40`=Adaptive) | System (auto) | — | Log frames 759/765 — confirms the Buds were **not** in the case at connect time (consistent with being worn for this Group's own test, though this could not be independently confirmed on video — see Findings §2). | | ~07:16:27–29 | User navigates to home screen, reopens the Pixel Buds app ("Device details") | User | — | Video `t=12`–`t=14` (app screen shows "Device details," ANC "Adaptive" selected, `L:100% C:93% R:100%`). | | 07:16:25.29–07:17:02.87 | The second LE connection's own ATT traffic (`Write Command`→handle `0x0042`, `Handle Value Notification`←handle `0x0044`, the same handle `CAP-016`/`CAP-018` flagged) — dense, recurring | Peer device (Auto) | — | 62 Notify + 40 Write frames, chandle `0x0003` only — **not** the Buds' own classic connection (chandle `0x0002`), which carries zero ATT traffic throughout this session. | -| 07:16:29–07:17:15 | **Head-gesture testing window (physical Nod/Shake, per the draft) — not camera-visible; app screen stays on "Device details," unchanged** | User (Hardware) | `HEAD-002`, `HEAD-003` | Video spot-checked at `t=30,38,42,46,50,54,58` — identical static screen throughout, buds/case position ambiguous on video (see Findings §2). **Zero DLCI 0x02/0x04/0x08 traffic on the Buds' own chandle anywhere in this entire window** (log query `07:16:25`→`07:17:15` returns 0 rows) — see Findings §4. | +| 07:16:29–07:17:15 | **Head-gesture testing window (physical Nod/Shake, per the draft) — not camera-visible; app screen stays on "Device details," unchanged** | User (Hardware) | `HEAD-002`, `HEAD-003` | Video spot-checked at `t=30,38,42,46,50,54,58` — identical static screen throughout, buds/case position ambiguous on video (see Findings §2). **Zero DLCI 0x02/0x04/0x08 traffic on the Buds' own chandle anywhere in this entire window** (log query `07:16:25.5`→`07:17:15` returns 0 rows, exit 0; from `07:16:25` it returns the connect burst's last frames 1442–1448 as positive control) — see Findings §4. | | 07:17:15.550 | The periodic DLCI 0x02/0x04/0x08 cross-channel push fires (routine, not gesture-related — matches the already-documented `CAP-036`/`CAP-042` pattern) | System (auto) | — | Log frames 1834–1847. | | 07:17:18 | Video ends (62.68s) | System | — | Video `t=62`. | | 07:17:15–07:20:02 (log tail, beyond video) | Log continues past video end; two further periodic pushes (07:18:02, 07:19:35), no other Buds-attributable RFCOMM traffic | System (auto) | — | Log frames 1925–2120. | diff --git a/captures/CAP-028-2026-09-12_07-16-15_07-17-18-Group_O/CAP-028-FINDINGS.md b/captures/CAP-028-2026-09-12_07-16-15_07-17-18-Group_O/CAP-028-FINDINGS.md index 85ff5eae..70900546 100644 --- a/captures/CAP-028-2026-09-12_07-16-15_07-17-18-Group_O/CAP-028-FINDINGS.md +++ b/captures/CAP-028-2026-09-12_07-16-15_07-17-18-Group_O/CAP-028-FINDINGS.md @@ -15,7 +15,7 @@ staged here per `PROJECT_RULES.md` §2. Every claim below carries a status per ` | Field | Value | |---|---| | Capture ID | `CAP-028` | -| Purpose | Group O — physical head-gesture actions (Nod/Shake); requires Head gestures already enabled (`CAP-020`, Group F) | +| Purpose | Group O — physical head-gesture actions (Nod/Shake); requires Head gestures enabled (on in this session per frame 1350, §2; `CAP-020`, Group F, left them off) | | Date | 2026-09-12 | | Firmware | ⚪ ASSUMPTION `release_5.203` (carried over) | | Test device | Pixel 7a, Android 17, official Pixel Buds Companion App `1.0.955078536`, Google Play services active | @@ -24,7 +24,7 @@ staged here per `PROJECT_RULES.md` §2. Every claim below carries a status per ` | Notes file | [`CAP-028-EVENT-NOTES.md`](./CAP-028-EVENT-NOTES.md) | | Buds MAC (partial, per `AGENTS.md` §7/§9) | `04:00:6e:...:07` | -## 2. Precondition check — Head gestures already enabled (🟢 FACT, from `CAP-020`) +## 2. Precondition check — Head gestures already enabled (from this session's ReadSetting answer, frame 1350, and DLCI 0x08 frame 795; 🟡 for the value's meaning) This session's own connect-time read shows the setting **on**: `python3 scripts/pwrpc_decode.py CAP-028-btsnoop_hci.log | grep "29:"` → frame 1350 `ReadSetting` answer `4:{29:2}` (🟡 2 = on, `PROTOCOL.md` §4.5.4), and DLCI 0x08 `04 16 00 02 08 01` at 795. (Rewritten 2026-10-03, `ai-sessions/0069`: this used to rest on @@ -80,10 +80,16 @@ report. ``` $ tshark -r CAP-028-btsnoop_hci.log -Y '(btrfcomm.dlci==2 or btrfcomm.dlci==4 or btrfcomm.dlci==8) \ - and btrfcomm.len>0 and frame.time>="2026-09-12 07:16:25" and frame.time<="2026-09-12 07:17:15"' \ + and btrfcomm.len>0 and frame.time>="2026-09-12 07:16:25.5" and frame.time<="2026-09-12 07:17:15"' \ -T fields -e frame.number -(0 rows) +(0 rows, exit 0) +$ # positive control: the same filter with frame.time>="2026-09-12 07:16:25" (the bound quoted here before) +1442 +1443 +1444 +1448 ``` +(Corrected 2026-10-15, `ai-sessions/0094`: the lower bound quoted before, `07:16:25`, returns the tail of the connect burst, frames 1442–1448 at 07:16:25.477–.488; the "0 rows" holds only from `07:16:25.5`.) From the end of the connect-time settling burst (`07:16:25.49`, frame 1448) through `07:17:15.55` (frame 1834, the next routine periodic push — see §5), **zero** DLCI 0x02/0x04/0x08 frames appear on diff --git a/captures/CAP-029-2026-09-12_07-53-53_07-58-05-Group_P/CAP-029-EVENT-NOTES.md b/captures/CAP-029-2026-09-12_07-53-53_07-58-05-Group_P/CAP-029-EVENT-NOTES.md index ae284391..fe4fe687 100644 --- a/captures/CAP-029-2026-09-12_07-53-53_07-58-05-Group_P/CAP-029-EVENT-NOTES.md +++ b/captures/CAP-029-2026-09-12_07-53-53_07-58-05-Group_P/CAP-029-EVENT-NOTES.md @@ -27,7 +27,7 @@ without a full factory reset, since no officially documented duration exists for > fixed on 2026-08-20, see `CHANGELOG.md`). > > **Decide before capturing:** run items 15 and 17 only in this session (skip the destructive -> item 16), or run all three including the factory reset. Record the decision here: TBD. +> item 16), or run all three including the factory reset. Decision: **Run** — recorded in the Log Metadata table below ("Decision on item 16"). > > **Decision guidance (not a decision made on the maintainer's behalf):** items 15/17 alone are > non-destructive and sufficient to close this session's own open questions (`CONV-002` wire @@ -86,11 +86,11 @@ without a full factory reset, since no officially documented duration exists for | 07:54:02.767 | Classic `Connect Complete` (stored key, no SSP) | System | — | Log frame 370. | | 07:54:03.253–.269 | DLCI 0x04 `Get`(`08110000`)/`Notify`(`Settable=0xe8`=undocked,`Current=0x40`=Adaptive) | System (auto) | — | Log frames 756/777 — this is the **only** Group `0x08` (ANC) traffic anywhere before the reset (checked, see Findings §2). | | ~07:54:08–14 | Spotify opened, "Amy Winehouse Best Of" playing | User (App) | — | Video `t=20`, still playing. | -| ~07:54:14–16 | **Media pauses on screen** (play/pause icon flips from pause to play) | User (Hardware) → Buds (Auto)? | `CONV-002` | Video `t=20`→`t=22`: still playing at overlay `07:54:14`, already paused at `07:54:16`. **No DLCI 0x02/0x04/0x08 command accompanies this transition** (checked `07:54:00`–`07:54:30` in full); AVRCP shows only the phone's `PlaybackStatusChanged` → Paused (frame 1593, 07:54:17.670, phone → Buds) and no pass-through key from the Buds — see Findings §2. | +| ~07:54:14–16 | **Media pauses on screen** (play/pause icon flips from pause to play) | User (Hardware) → Buds (Auto)? | `CONV-002` | Video `t=20`→`t=22`: still playing at overlay `07:54:14`, already paused at `07:54:16`. **No known DLCI 0x02/0x04/0x08 command accompanies this transition** (DLCI 0x04 Group `0x08` checked `07:54:00`–`07:54:30` in full; no settings write); DLCI 0x02 carries only `SubscribeToLiveDb` stream packets, and DLCI 0x08 carries one unidentified Buds → phone frame, 1592 (07:54:17.547, `01 09 00 05 0a 03 0b 06 13`), 0.12 s before the AVRCP pause — Findings §2; AVRCP shows only the phone's `PlaybackStatusChanged` → Paused (frame 1593, 07:54:17.670, phone → Buds) and no pass-through key from the Buds — see Findings §2. | | ~07:54:55 | Buds "forgotten" in Bluetooth settings (in preparation for the reset) | User (Phone) | — | Video-inferred from the draft's own note; ACL disconnect (reason `0x16`, locally-terminated) at `07:54:54.532` (log frame 2034) matches closely. | | ~07:55:03–07:55:33 | Case button held for ~30s (case open, buds inside) | User (Hardware) | `CASE-007` | Video `t=68` (button being held, `07:55:02`) through `t=98` (case closed/lifted, `07:55:32`) — a full factory reset, per the app's already-confirmed behavior. | | 07:55:13.695 | First `Delete Stored Link Key` (during the hold) | System | — | Log frame 2091. | -| 07:55:44.765–07:56:34.348 | Several reconnect/disconnect cycles as the reset settles (4 total `Delete Stored Link Key` events: `07:55:13`, `07:56:12`, `07:56:19`, `07:56:34`) | System (auto) | — | Log frames 2091/2497/2860/3316 — see Findings §3. | +| 07:55:44.765–07:56:34.348 | Several reconnect/disconnect cycles as the reset settles (3 further `Delete Stored Link Key` events: `07:56:12`, `07:56:19`, `07:56:34` — 4 in total with `07:55:13` in the row above) | System (auto) | — | Log frames 2091/2497/2860/3316 — see Findings §3. | | ~07:56:01–07:56:09 | Reset "Pixel Buds Pro 2" reappears in "Pair new device" list | System/User | `PAIR-002` | Video `t=135` (`07:56:09`), user's finger tapping the entry. | | 07:56:34.035–07:56:34.653 | **Fresh classic SSP handshake**: `Link Key Request Negative Reply`→`IO Capability Request/Response`→`User Confirmation Request` | System | `PAIR-002` | Log frames 3316–3370 — genuine fresh pairing, not a key-reuse reconnect. | | ~07:56:45–07:57:09 | First-run onboarding: SDP browse, RFCOMM/HID/AVDTP connects, permission grants (Phone calls/Media audio/Input device) | System/User | `APP-001`/`APP-002` (incidental) | Video `t=195` (`07:57:09`). | @@ -103,10 +103,9 @@ without a full factory reset, since no officially documented duration exists for - [x] `CONV-002`: check whether the Conversation Detection trigger (pause media, switch to Transparency) produces a wire-visible command, and if so on which DLCI/channel. → **Clean - negative**: media visibly pauses, but zero DLCI 0x02/0x04/0x08 traffic of any kind accompanies - it, and no ANC mode change occurs anywhere in the pre-reset session. + negative** for known commands: media visibly pauses with no ANC Set/Notify, no settings write and no AVRCP pass-through, and no ANC mode change occurs anywhere in the pre-reset session; one unidentified DLCI 0x08 frame (1592, `01 09 … 13`) precedes the AVRCP pause by 0.12 s (🔴, Findings §2). - [x] `CASE-007` (if run): confirm this reproduces the previously-documented factory-reset - behavior. → Confirmed — matches the established pattern (bond removed, multiple + behavior. → Run and recorded — the project's first wire record of a factory reset (no earlier capture has one; `CAP-029-FINDINGS.md` §3): bond removed, multiple `Delete Stored Link Key`/reconnect cycles, followed by a fresh device advertisement). - [ ] `CASE-008`: record the exact press duration tried and whether pairing mode triggered. → **Not attempted this session** — no second case-button interaction occurs anywhere after the diff --git a/captures/CAP-029-2026-09-12_07-53-53_07-58-05-Group_P/CAP-029-FINDINGS.md b/captures/CAP-029-2026-09-12_07-53-53_07-58-05-Group_P/CAP-029-FINDINGS.md index fe021ed0..1eb4f90e 100644 --- a/captures/CAP-029-2026-09-12_07-53-53_07-58-05-Group_P/CAP-029-FINDINGS.md +++ b/captures/CAP-029-2026-09-12_07-53-53_07-58-05-Group_P/CAP-029-FINDINGS.md @@ -44,7 +44,9 @@ $ tshark -r CAP-029-btsnoop_hci.log -Y 'btrfcomm.dlci==4 and btrfcomm.len>0 and **No `0x12` Set / `0x13` Notify ANC-state frame occurs anywhere near the pause (checked the full `07:54:00`–`07:54:30` window, and separately the entire pre-reset session `07:53:51`–`07:54:55`) — the ANC mode never changes from the connect-time `Adaptive` reading.** Also checked and ruled out: -no AVRCP pass-through from the Buds and no DLCI 0x02 write (no `field5{field4{...}}` settings-write shape appears). AVRCP carries only the +no AVRCP pass-through from the Buds and no DLCI 0x02 write (`python3 -I scripts/pwrpc_decode.py CAP-029-btsnoop_hci.log | grep -c WriteSetting` → 0, whole log; +positive control: the same command on `CAP-046-btsnoop_hci.log` finds `WriteSetting` frame 1181). The pre-reset ANC check `tshark -Y 'btrfcomm.dlci==4 and btrfcomm.len>0 and +frame.time>="2026-09-12 07:53:51" and frame.time<="2026-09-12 07:54:55" and data.data[0:1]==08'` returns only 756/777 (the connect-time Get/Notify, exit 0). AVRCP carries only the phone's own play-status notifications to the Buds: ``` $ tshark -r CAP-029-btsnoop_hci.log -Y 'btavctp or btavrcp' -T fields -e frame.number -e frame.time -e frame.p2p_dir -e _ws.col.Info @@ -57,12 +59,14 @@ $ tshark -r CAP-029-btsnoop_hci.log -Y 'btavrcp.opcode==0x7c' | wc -l # AV/C 0 (exit 0; positive control: the same filter finds CAP-063 frame 5389, "Rcvd Pass Through: Control - PAUSE") ``` (The filter `avctp or avrcp` is not valid in tshark — it exits with status 4, *"avctp" is not a valid protocol or protocol field*; the dissector names -are `btavctp`/`btavrcp`.) The only RFCOMM activity in the exact -pause window (`07:54:14`–`07:54:17`) is routine zero-length DLCI-0x02 flow-control `UIH` frames at -roughly 1-per-second — background keepalive, not a data payload. - -**Conclusion:** no Buds-side command accompanies the pause — no ANC Set/Notify, no settings write, no AVRCP pass-through; the only wire signal is the -phone's own `PlaybackStatusChanged` → Paused (1593, phone → Buds), i.e. the player paused and told the Buds so. +are `btavctp`/`btavrcp`.) RFCOMM activity in the exact pause window (`07:54:14`–`07:54:18`): DLCI 0x02 `Dosimeter/SubscribeToLiveDb` stream packets +Buds → phone (1573…1590, `scripts/pwrpc_decode.py`) with empty flow-control `UIH` frames between them, and **one DLCI 0x08 frame, 1592 (07:54:17.547, Buds → phone) +`01 09 00 05 0a 03 0b 06 13`** — 0.12 s before the phone's `PlaybackStatusChanged` → Paused (1593). Its sibling 1711 (07:54:32.736, `… 06 12`) comes 0.07 s +before the Playing at 1713 (07:54:32.809); the Paused at 1910 (07:54:43.070) has no such frame (`tshark -Y 'btrfcomm.dlci==8 and btrfcomm.len>0 and +data.data[0:2]==01:09'` → 1592, 1711 only, exit 0). 🔴 Meaning of DLCI 0x08 Group `0x01` Code `0x09` open (`PROTOCOL.md` §6). *(corrected 2026-10-15, `ai-sessions/0094`: this said only zero-length flow-control frames occur in the window)* + +**Conclusion:** no known Buds-side command accompanies the pause — no ANC Set/Notify, no settings write, no AVRCP pass-through; besides the +phone's own `PlaybackStatusChanged` → Paused (1593, phone → Buds) the only Buds → phone frame near it is the unidentified DLCI 0x08 frame 1592 (above). *(corrected 2026-10-15, `ai-sessions/0094`)* Either (a) the "switch to Transparency" behavior `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s own note describes did not actually trigger this time (ANC was already Adaptive, which functionally already lets outside sound through — a plausible reason no mode switch was needed), or (b) the media-pause @@ -71,7 +75,7 @@ show (e.g. a phone-side/app-side decision from the Buds' own mic stream, not a d control message). Not resolved either way — recorded as a genuine open question, not force-fit into either explanation. -## 3. `CASE-007` — factory reset reproduces the established pattern (🟢 FACT) +## 3. `CASE-007` — factory reset, first wire record in this project (🟢 FACT for frames 2091/2497/2860/3316) ``` $ tshark -r CAP-029-btsnoop_hci.log -Y 'bthci_cmd.opcode==0x0c12' -T fields -e frame.number -e frame.time @@ -84,8 +88,7 @@ $ tshark -r CAP-029-btsnoop_hci.log -Y 'bthci_cmd.opcode==0x0c12' -T fields -e f Four `Delete Stored Link Key` events across the reset/re-pair sequence (video: button held `t=68`–`t=98`, overlay `07:55:02`–`07:55:32`), interleaved with reconnect attempts (Connection Complete at `07:55:44.76`, `07:56:13.37`, `07:56:22.37`, `07:56:34.35` — each followed by a fresh -disconnect except the last). This matches the already-documented factory-reset pattern from -`CAP-001`/`CAP-002`'s own Group P precedent — a third/fourth confirming instance, not a new finding. +disconnect except the last). No earlier capture records a factory reset (`CAP-001` is Group Z, `CAP-002` Group A; neither mentions one) — this is the project's first wire record of a factory reset, not a confirming instance. *(corrected 2026-10-15, `ai-sessions/0094`)* ## 4. `PAIR-002` — genuine fresh SSP handshake, not a key-reuse reconnect (🟢 FACT) diff --git a/captures/CAP-030-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_Q/CAP-030-EVENT-NOTES.md b/captures/CAP-030-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_Q/CAP-030-EVENT-NOTES.md index a6aa6d13..344777dd 100644 --- a/captures/CAP-030-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_Q/CAP-030-EVENT-NOTES.md +++ b/captures/CAP-030-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_Q/CAP-030-EVENT-NOTES.md @@ -7,7 +7,7 @@ the placeholder `CAP-030-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_Q` to the actual ses date/start-time/end-time, e.g. `CAP-030-2026-09-01_11-00-00_11-10-00-Group_Q`. **Purpose (`CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group Q, items #19–20 specifically):** item #18 -(passive BLE scan for the Battery Notification) is already planned separately as `CAP-011`. This +(passive BLE scan for the Battery Notification) is covered separately by `CAP-011` (analyzed) and its repeat `CAP-043`. This session covers the two remaining Group Q items: attempting to observe Loud Noise Protection and Adaptive Audio engaging. These are waiting periods to catch spontaneous hardware-initiated traffic — nothing to tap, just capture while the condition holds. Log explicit **observation @@ -57,7 +57,7 @@ start** and **observation end** boundaries, not just a single timestamp. the buds, or a door closing firmly nearby) — loud enough to plausibly trigger volume-limiting DSP, not loud enough to risk hearing damage or device/microphone damage. Do not use impulse sounds (gunshots, fireworks, air horns) — these are explicitly out of scope per Google's own - documentation (see Purpose above). + documentation (see Procedure step 19 below and `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` row `LOUD-001`). - `ADAPT-002`: a "distinctly different acoustic environment" means a clear, sustained ambient- noise-level change, not a momentary one — e.g. a quiet indoor room → a room with music/TV playing, or indoors → outdoors near light traffic. Stay in each environment long enough diff --git a/captures/CAP-031-2026-08-27_06-04-48_06-08-10-Group_A/CAP-031-EVENT-NOTES.md b/captures/CAP-031-2026-08-27_06-04-48_06-08-10-Group_A/CAP-031-EVENT-NOTES.md index e4ea4eaa..3af0883b 100644 --- a/captures/CAP-031-2026-08-27_06-04-48_06-08-10-Group_A/CAP-031-EVENT-NOTES.md +++ b/captures/CAP-031-2026-08-27_06-04-48_06-08-10-Group_A/CAP-031-EVENT-NOTES.md @@ -12,8 +12,8 @@ | Date | 2026-08-27 | | Firmware version | release_5.203 (screen-confirmed, see timeline) | | Test device | Pixel 7a, Android 17 (⚪ assumed — build number not screen-confirmed this session), system Bluetooth settings + Pixel Buds companion app for setup steps only | -| Video file | `CAP-031-recording.mp4` — | -| Log file | `CAP-031-btsnooz_hci.log` — | +| Video file | `CAP-031-recording.mp4` — 202.4s, overlay `06:04:48`–`06:08:10` (per `CAP-031-FINDINGS.md`) | +| Log file | `CAP-031-btsnooz_hci.log` — 1,747 packets, 227.98s, `06:06:37.160`–`06:10:25.142`, `btsnooz` format, truncated frames (per `CAP-031-FINDINGS.md`) | ## Event Timeline diff --git a/captures/CAP-031-2026-08-27_06-04-48_06-08-10-Group_A/CAP-031-FINDINGS.md b/captures/CAP-031-2026-08-27_06-04-48_06-08-10-Group_A/CAP-031-FINDINGS.md index e566ac9b..02e01096 100644 --- a/captures/CAP-031-2026-08-27_06-04-48_06-08-10-Group_A/CAP-031-FINDINGS.md +++ b/captures/CAP-031-2026-08-27_06-04-48_06-08-10-Group_A/CAP-031-FINDINGS.md @@ -184,7 +184,7 @@ permission-granting setup flow, rather than unconditionally during the initial R burst. This capture is a direct, higher-precision test of that hypothesis (the plan's Step 3 explicitly asked for it): **DLCI `0x02` opens at 06:07:19.202 (frames 1122/1124) — only ~1.64s after DLCI `0x00`'s own `SABM` at 06:07:17.561, and *before* DLCI `0x04` (06:07:24.342)**. This is -squarely within the initial multiplexer-open burst (all 5 DLCIs opened within ~6.85s of each +squarely within the initial multiplexer-open burst (all 6 DLCIs — 5 data channels plus the 0x00 mux control — opened within ~6.85s of each other), matching `CAP-001`/`CAP-002`/`CAP-004`/`CAP-006`'s pattern, not `CAP-013`'s outlier. The first app-permission "Allow" tap this session doesn't occur until 06:07:41 (per `CAP-031-EVENT-NOTES.md`), over 20 seconds after DLCI `0x02` already opened — there is no timing @@ -204,7 +204,8 @@ checked for the same pattern: - **Zero occurrences of `43:8a:82:03:4b:f2`** anywhere in this log (`tshark -r CAP-031-btsnooz_hci.log -Y "bthci_evt.bd_addr == 43:8a:82:03:4b:f2 or bthci_cmd.bd_addr == 43:8a:82:03:4b:f2"` → 0 matches). -- **Exactly one `LE Enhanced Connection Complete` event in the entire log** (frame 529, + (Exit 0; positive control: the same `bd_addr` filter with the Buds' own address, below, matches 84 frames, 417…760, including 529 and the classic Connection Complete 615.) +- **Exactly one `LE Enhanced Connection Complete` event in the entire log** (`tshark -r CAP-031-btsnooz_hci.log -Y "bthci_evt.le_meta_subevent==0x0a"` → 529 only, exit 0; `le_meta_subevent==0x01`, legacy LE Connection Complete → 0 rows, exit 0) (frame 529, 06:07:14.645649), and it resolves to the Buds' own address: `tshark -r CAP-031-btsnooz_hci.log -Y "frame.number==529" -V` shows `Peer Address Type: Public Device Address (0x00)`, `BD_ADDR: Google_cf:6e:07 (04:00:6e:cf:6e:07)` — the same public address @@ -263,7 +264,7 @@ capture simply didn't reproduce the phenomenon a third time. - **Hypothesis:** DLCI `0x02` opens later than the other RFCOMM channels, plausibly gated on an app-permission step. -- **Setup:** same as Test A; DLCI open times compared across all 5 channels. +- **Setup:** same as Test A; DLCI open times compared across all 6 channels (5 data channels plus the 0x00 mux control). - **Expected outcome (if hypothesis held):** DLCI `0x02`'s `SABM` tens of seconds after DLCI `0x00`'s, coinciding with an on-screen "Allow" tap. - **Actual outcome:** DLCI `0x02` opens 1.64s after DLCI `0x00`, within the initial multiplexer @@ -303,9 +304,8 @@ capture simply didn't reproduce the phenomenon a third time. available in the log for a future battery-tracking pass if needed. > **Update (2026-08-27), approved as recorded (maintainer, chat 2026-09-30, `ai-sessions/0059`):** the fourth attempt proposed -> above, `CAP-032`, succeeded — extracted via the raw BTSnoop file path (§8's proposed root-cause -> guess, "extraction path vs. session-specific," is now supported: `CAP-032`'s raw-path log is -> genuinely untruncated). Its log's first frame lands ~58s *before* the on-screen Forget tap, +> above, `CAP-032`, succeeded — extracted via the raw BTSnoop file path (`CAP-032`'s raw-path log is +> genuinely untruncated; §8's guesses (a)/(b) are superseded by this extraction-path explanation, see below). Its log's first frame lands ~56s *before* the on-screen Forget tap (~58s before its HCI effect, `CAP-032` frame 768), > finally covering the pre-clearing-action window. For that session: no BLE link and no valid > classic link key existed for the Buds before the Forget tap — the opposite of `CAP-001`'s original > finding, read as a clean counter-example rather than a resolution of `CAP-001`'s own puzzle. See diff --git a/captures/CAP-032-2026-08-27_18-30-15_18-32-33-Group_A/CAP-032-EVENT-NOTES.md b/captures/CAP-032-2026-08-27_18-30-15_18-32-33-Group_A/CAP-032-EVENT-NOTES.md index 42a0cf57..3614b855 100644 --- a/captures/CAP-032-2026-08-27_18-30-15_18-32-33-Group_A/CAP-032-EVENT-NOTES.md +++ b/captures/CAP-032-2026-08-27_18-30-15_18-32-33-Group_A/CAP-032-EVENT-NOTES.md @@ -34,7 +34,7 @@ around every timestamp below (video start = 18:30:15, so offset = wall-clock tim | 18:30:44 | "Connected devices" screen shown (post-Forget). Screenshot-confirmed at 18:30:43 the transition is already complete. | - | - | - | | 18:30:51 | user selects "See all" under "Saved devices" — section present but empty of the Buds entry (Forget succeeded). | User | `PAIR-004` | - | | 18:31:08 | user opens the Buds case, case visible open, phone still on "Connected devices". | User | `PAIR-001` | - | -| 18:31:11 | user presses the pair button on the case. | User | `PAIR-001` | LE Extended Advertising Reports from the Buds' public address (`04:00:6e:cf:6e:07`) begin at 18:31:14.998 (frame 991), ~3.6s later | +| 18:31:11 | user presses the pair button on the case. | User | `PAIR-001` | LE Extended Advertising Reports from the Buds' public address (`04:00:6e:cf:6e:07`) begin at 18:31:14.998 (frame 991), ~4.0s later | | 18:31:14 | Pop-up appears: "Pair with Pixel Buds Pro 2". | - | - | - | | 18:31:24 | user taps **"Connect"** on the pairing pop-up. | User | `PAIR-001`/`PAIR-004` | Matches `Sent Delete Stored Link Key` at 18:31:26.776 (frame 1090) — 0 keys deleted again (already deleted at the Forget tap) — full fresh-SSP sequence follows, frames 1090–1153, see `CAP-032-FINDINGS.md` §2 | | 18:31:36 | user taps "Start using the device" — Buds now paired. | User | `PAIR-001` | - | @@ -87,7 +87,7 @@ Group-A-repeat note: `PAIR-001`, `PAIR-004`, incidental `BATT-004`. - **`PAIR-004`** (does Forget clear a pre-existing BLE association/link-key completely, even one not established via the Pixel Buds app?) — ✅ **fully exercised for the first time in four attempts**: unlike `CAP-013`/`CAP-031`, this capture's log genuinely covers the window before the - Forget tap (log starts 18:29:45.722, ~58s before the 18:30:42 tap, and ~30s before the video + Forget tap (log starts 18:29:45.722, ~56s before the 18:30:42 tap — ~58s before its HCI effect, frame 768 — and ~30s before the video itself even starts) — see `CAP-032-FINDINGS.md` §0. Both the primary claim (state *before* the Forget tap) and the secondary claim (fresh SSP on re-pairing) are answered — see `CAP-032-FINDINGS.md` §0/§1/§2/§7. diff --git a/captures/CAP-032-2026-08-27_18-30-15_18-32-33-Group_A/CAP-032-FINDINGS.md b/captures/CAP-032-2026-08-27_18-30-15_18-32-33-Group_A/CAP-032-FINDINGS.md index bc91337b..5e4fa2a3 100644 --- a/captures/CAP-032-2026-08-27_18-30-15_18-32-33-Group_A/CAP-032-FINDINGS.md +++ b/captures/CAP-032-2026-08-27_18-30-15_18-32-33-Group_A/CAP-032-FINDINGS.md @@ -72,7 +72,7 @@ done. See §8 below for the proposed documentation update this motivates. "Forget device" button; the destination "Connected devices" screen is already showing by 18:30:43). The log's `Sent Delete Stored Link Key` for the Buds' address is at **18:30:43.927732** (frame 768) — within ~1–2s of the on-screen tap, matching the same tap-to-HCI-effect lag pattern. -- **Net coverage: the log's first frame precedes the Forget tap by ~58 seconds, and precedes the +- **Net coverage: the log's first frame precedes the Forget tap by ~56 seconds (~58 s before its HCI effect, frame 768), and precedes the video's own start by ~30 seconds.** This is the first of four attempts (`CAP-001`, `CAP-013`, `CAP-031`, `CAP-032`) where the pre-clearing-action window is actually inside the logged window, not before it. @@ -230,7 +230,7 @@ Forget tap — but that search only catches HCI opcodes Wireshark has a dissecto grep across every raw frame in the pre-Forget window found one match Wireshark's dissector doesn't tag as an address field at all: -**Frame 91, 18:29:45.827476 (+0200)** — 105ms after the log's very first frame, and ~58 seconds +**Frame 91, 18:29:45.827476 (+0200)** — 105ms after the log's very first frame, and ~56 seconds before the Forget tap: `tshark -r CAP-032-btsnoop_hci.log -Y "frame.number==91" -x` → `01 57 fd 0a 02 00 04 07 6e cf 6e 00 04 02` — HCI command opcode `0xFD57` (a Google/vendor-specific @@ -245,12 +245,13 @@ LE-only opening window), 213–278 (30, the 18:30:22 classic-radio-enable burst) 721–732 (6) (`tshark -r CAP-032-btsnoop_hci.log -Y "frame.number<=768 and bthci_cmd.opcode==0xfd57" -T fields -e frame.number`; corrected 2026-10-03, the text said "two bursts"). Decoding every occurrence's raw payload shows a repeating pattern: a `02 00 NN <6-byte-addr> 02` entry (assigning list index `NN` to an address) is followed by a `01 00 NN ` entry with HCI parameter length `0x12` (the `12` is the length byte, not part of the entry) -(apparently setting per-index flags/parameters), across a total of 22 distinct addresses and indices -`0x04`–`0x1a` — matching exactly the 22 distinct `bd_addr` values independently found elsewhere in -this log (§0.3). The Buds' address is assigned index `0x04` (frame 91) with what looks like a +(apparently setting per-index flags/parameters), across indices from `0x04` upward (the earlier "22 distinct addresses … matching exactly the 22 +distinct `bd_addr` values (§0.3)" is withdrawn 2026-10-15, `ai-sessions/0094`: §0.3 gives no such count; the whole log has 24 distinct dissected +`bd_addr` values, and only 5 `02 00 NN` entries exist, with 2 distinct addresses — the other entries are `07 00 NN …`, `01 00 NN …`, `01 01 …`, `03 00 …`, `00 01` shapes; +`tshark -Y "bthci_cmd.opcode==0xfd57" -T fields -e bthci_cmd.parameter | cut -c1-4 | sort | uniq -c`). The Buds' address is assigned index `0x04` (frame 91) with what looks like a flags/parameter sub-command immediately after (frame 93, all 18 parameter bytes: `01 00 04 01 00 11 11 01 80 01 f4 01 01 80 10 27 02 00`) that structurally differs from most other indices' own follow-up (e.g. index `0x06`, frame 113: -`01 00 06 40 00 11 11 01 80 00 00 00`) — indices `0x04`/`0x05` share one parameter pattern, `0x06` +all 18 parameter bytes: `01 00 06 40 00 11 11 01 80 00 00 00 00 00 00 00 00 00`) — indices `0x04`/`0x05` share one parameter pattern, `0x06` onward another. What this command actually configures (most plausibly a bulk push of all currently-bonded devices' identities into the controller's own address/resolving-list at Bluetooth-stack bring-up, given the timing and the one-entry-per-bonded-device shape) is **not** @@ -299,7 +300,7 @@ recurrence of either of theirs. (version not screen-confirmed). Capture method: raw `btsnoop_hci.log` (untruncated, §0.1). - **Expected outcome:** log's first frame timestamp ≤ the video's own Forget-tap timestamp (18:30:42). -- **Actual outcome:** log's first frame is 18:29:45.722826 — **~58 seconds *before*** the Forget +- **Actual outcome:** log's first frame is 18:29:45.722826 — **~56 seconds *before*** (~58 s before its HCI effect, frame 768) *(corrected 2026-10-15, `ai-sessions/0094`)* the Forget tap, and ~30 seconds before the video itself starts (§0.2). - **Conclusion:** 🟢 **CONFIRMED** — the pre-clearing-action window is captured for the first time in four attempts. Given that coverage, the primary question itself is answerable for this session: diff --git a/captures/CAP-033-2026-08-30_15-17-03_15-19-52-Group_AA/CAP-033-EVENT-NOTES.md b/captures/CAP-033-2026-08-30_15-17-03_15-19-52-Group_AA/CAP-033-EVENT-NOTES.md index ef4c0812..8f90b4b9 100644 --- a/captures/CAP-033-2026-08-30_15-17-03_15-19-52-Group_AA/CAP-033-EVENT-NOTES.md +++ b/captures/CAP-033-2026-08-30_15-17-03_15-19-52-Group_AA/CAP-033-EVENT-NOTES.md @@ -79,7 +79,7 @@ session (same-direction offset as independently observed in `CAP-027`, this proj |---|---|---|---|---| | 15:17:03 | start of the video | — | — | — | | 15:17:10 | user enables bluetooth | — | — | — | -| 15:17:23 | "Forget" via system Bluetooth settings | User (Hardware) | `SDP-001` | Video-confirmed: "Forget device?" dialog shown 15:17:22, "Forget" tapped 15:17:24 (`CAP-033-FINDINGS.md` §1.1) — **before** Force-stop below, contrary to procedure | +| 15:17:24 | "Forget" via system Bluetooth settings | User (Hardware) | `SDP-001` | Video-confirmed: "Forget device?" dialog shown 15:17:22, "Forget" tapped 15:17:24 (`CAP-033-FINDINGS.md` §1.1) — **before** Force-stop below, contrary to procedure | | 15:17:34 | Companion app force-stopped | User (Hardware) | `SDP-001` | Video-confirmed: Settings → Apps → Pixel Buds → App info → "Force stop" tapped 15:17:34, confirmed 15:17:36 (`CAP-033-FINDINGS.md` §1.1) | | 15:17:52 | user selects "pair new device" | — | — | — | | 15:17:53 | user opens case | — | — | — | diff --git a/captures/CAP-033-2026-08-30_15-17-03_15-19-52-Group_AA/CAP-033-FINDINGS.md b/captures/CAP-033-2026-08-30_15-17-03_15-19-52-Group_AA/CAP-033-FINDINGS.md index 63850aa3..fa59424c 100644 --- a/captures/CAP-033-2026-08-30_15-17-03_15-19-52-Group_AA/CAP-033-FINDINGS.md +++ b/captures/CAP-033-2026-08-30_15-17-03_15-19-52-Group_AA/CAP-033-FINDINGS.md @@ -143,18 +143,18 @@ that 1232-byte blob): (Full 1232-byte blob available in the raw capture; the excerpt above is the tail containing the two records most relevant to `DECISIONS.md` ADR-018/this Group's stated purpose. The complete record -set, decoded the same way — service-name string + UUID + `[Group:1][Code:1][Length:2][RFCOMM -channel:1]` protocol descriptor — across the full response:) +set, decoded the same way — service-name string + UUID + SDP ProtocolDescriptorList (L2CAP UUID16 `0x0100`, RFCOMM +UUID16 `0x0003` with a uint8 channel number, e.g. `35 0c 35 03 19 01 00 35 05 19 00 03 08 03` = RFCOMM channel 3) — across the full response:) | SDP service name (wire string) | UUID | RFCOMM channel | Corresponding DLCI (`channel×2`) | Already documented as | |---|---|---|---|---| | **"MAESTRO APP"** | `25e97ff7-24ce-4c4c-8951-f764a708f7b5` | 1 | `0x02` | `libmaestro`/Pigweed channel — the "pigweed" UUID from `DECISIONS.md` ADR-018, byte-for-byte | | **"GFPS RFCOMM"** | `df21fe2c-2515-4fdb-8886-f12c4d67927c` | 2 | `0x04` | Official Fast Pair Message Stream (`PROTOCOL.md` §2.1/§4.1) | -| **"DEBUG APP"** | `25e97ff7-24ce-4c4c-8951-f764a708f7b4` (last byte `b4`, one bit different from "pigweed"'s `b5`) | 3 | `0x06` | Not previously documented — no traffic on this DLCI observed in this session | +| **"DEBUG APP"** | `25e97ff7-24ce-4c4c-8951-f764a708f7b4` (last byte `b4`, one bit different from "pigweed"'s `b5`) | 3 | `0x06` | Not previously documented — no traffic on this DLCI observed in this session (`tshark -Y 'btrfcomm.frame_type==0x2f'` → SABM only on 0x00/0x0c/0x08/0x0a/0x04; `-Y 'btrfcomm.len>0'` per DLCI → 0x00 30, 0x04 53, 0x08 41, 0x0c 52; exit 0) | | **"GSND CONTROL"** | `f8d1fbe4-7966-4334-8024-ff96c9330e15` | 4 | **`0x08`** | The private, 🔴-open-identity envelope (`PROTOCOL.md` §2.3) | | **"GSND AUDIO"** | `81c2e72a-0591-443e-a1ff-05f988593351` | 5 | `0x0a` | The unattributed DLCI 0x0a burst from `CAP-021-FINDINGS.md` §4a | | "Handsfree" (standard UUID16 `0x111E`) | — | 6 | `0x0c` | HFP AT-command channel (`PROTOCOL.md` §4.3 Option C) — traffic on this DLCI in this session is ordinary HFP SLC setup | -| **"BTIS"** | `e7ab2241-ca64-4a69-ac02-05f5c6fe2d62` | 9 | `0x12` | Not previously documented — no traffic on this DLCI observed in this session | +| **"BTIS"** | `e7ab2241-ca64-4a69-ac02-05f5c6fe2d62` | 9 | `0x12` | Not previously documented — no traffic on this DLCI observed in this session (same two commands as DLCI 0x06 above) | **Raw-byte scan confirms the "pigweed"/"MAESTRO APP" UUID is genuinely present** (not just in the dissected SDP record): `python3` scan of the raw log file finds `25e97ff724ce4c4c8951f764a708f7b5` @@ -238,7 +238,7 @@ recorded here as `not attempted` rather than forced or left silently blank. `AGENTS.md` §6/`DECISIONS.md` ADR-017 mechanical-assistance boundary already used for the "pigweed"/"default" UUID discovery itself) — this is the natural next step to turn §3's lead into something promotable. -- Decode DLCI 0x0a in a session that actually opens it (this one didn't) using the SDP-confirmed +- Decode DLCI 0x0a in a session where it carries payload (this one opened it — SABM 1536 / UA 1548 — but sent nothing on it) using the SDP-confirmed "GSND AUDIO" identity as a naming anchor for `CAP-021-FINDINGS.md` §4a's still-unattributed 1123-frame burst. diff --git a/captures/CAP-034-2026-09-01_06-46-31_06-52-45-Group_W/CAP-034-EVENT-NOTES.md b/captures/CAP-034-2026-09-01_06-46-31_06-52-45-Group_W/CAP-034-EVENT-NOTES.md index 66031f15..60a49017 100644 --- a/captures/CAP-034-2026-09-01_06-46-31_06-52-45-Group_W/CAP-034-EVENT-NOTES.md +++ b/captures/CAP-034-2026-09-01_06-46-31_06-52-45-Group_W/CAP-034-EVENT-NOTES.md @@ -13,8 +13,7 @@ unit before — so Option (b)'s own precondition doesn't hold as-is. This sessio Option (a)'s cache-clearing action (`pm clear com.android.bluetooth`) **applied to the Pixel 9a** (GrapheneOS) rather than the Pixel 7a used throughout the rest of this project, using nRF Connect as the GATT client (the same third-party-client approach that got `CAP-017` a genuine, if -truncated, discovery walk). Fill in each `[ ]`/`___` below as the session happens — do not -pre-fill any log-derived value (frame numbers, exact timestamps) before the capture exists. +truncated, discovery walk). ## Log Metadata @@ -118,12 +117,12 @@ frame numbers, the way `CAP-017-EVENT-NOTES.md` did.)* | `06:47:06` | Open case, buds in pairing mode (visible on scanner) | User (Hardware) | `PAIR-001` | not independently isolable — Buds appear in the ongoing LE Extended Advertising Report stream (frames from 3191 onward), no discrete "case opened" HCI event exists | | `06:47:41.966` | Tap CONNECT in nRF Connect scanner (video-confirmed, `f_70.9.jpg`) | User (App) | `GATT-001` | frame 3241 (`Sent LE Extended Create Connection [v1]`, 06:47:42.050644 — ~0.08s app-processing delay after the tap) | | `06:47:42.073` | LE connection established | App/OS (Auto) | `GATT-001` | frame 3247 (`Rcvd LE Meta — LE Enhanced Connection Complete [v1]`, connection handle `0x0040`) | -| `06:47:42.147` - `06:47:45.490` | MTU exchange + **full, genuine discovery burst** (Read By Group Type / Read By Type / Find Information, handles `0x0001`–`0xffff`) — this is the primary evidence source, see `CAP-034-FINDINGS.md` §3–§4 | App (Auto) | `GATT-001` | frames 3264–3469 | +| `06:47:42.142` - `06:47:45.471` | MTU exchange + **full, genuine discovery burst** (Read By Group Type / Read By Type / Find Information, handles `0x0001`–`0xffff`) — this is the primary evidence source, see `CAP-034-FINDINGS.md` §3–§4 | App (Auto) | `GATT-001` | frames 3264–3469 | | `06:47:45` - `06:48:13` | CLIENT tab populated — full service list visible on screen (scrolling down) | User (App) | `GATT-001` | — (no ATT traffic; passive video-only, list already populated from the discovery burst above) | | `06:48:13.798` - `06:48:23.489` | **Correction — not "read individual characteristics" broadly:** only 5 standard GATT/GAP reads succeed on the wire (Client Supported Features, Database Hash, Server Supported Features, Device Name, Appearance). No Fast Pair/Device-Information/Battery/Unknown-Service value was read in this window. | User (App) | `GATT-001` | frames 3473, 3474, 3476, 3477, 3479, 3481, 3482, 3483, 3485, 3486 | | `06:49:56.741` - `06:50:00.860` | **New finding, not in the original plan:** nRF Connect's debug log (`f_274.1.jpg`) shows attempted reads of Model ID (`fe2c1233…`) and a Passkey (`fe2c1235…`) notify-subscribe — every attempt throws `Exception occurred (Reading characteristic/descriptor failed)` **1ms** after the call, and **zero** matching frames exist on the wire in this window. Consistent with a pre-bond local/API-level rejection (see `CAP-034-FINDINGS.md` §6b), not a wire-level ATT error. | User (App) | `GATT-001` | none (0 frames, confirmed exhaustively — see `CAP-034-FINDINGS.md` §6b) | -| `06:51:05.069` - `06:51:09.000` | **Correction — this is LE Security Manager (SMP) pairing, not classic SSP**, despite the on-screen dialog text; Cross-Transport Key Derivation (CTKD) pattern, matching `CAP-004`/`CAP-014` (both nRF-Connect-initiated bonds) | App/OS (Auto) | `PAIR-001` | frames 3492 (Pairing Request) – 3518 (Pairing DHKey Check, Rcvd) | -| `06:51:08.000` (approx., unpaused by the 5.5s on-screen dialog gap) | Confirmed system pairing dialog (tapped "Koppelen") | User (App) | `PAIR-001` | inferred from the 3509→3517 gap (`Pairing Confirm` at 05.396 → `Pairing DHKey Check` at 08.900); no separate on-the-wire event marks the tap itself | +| `06:51:05.084` - `06:51:09.691` | **Correction — this is LE Security Manager (SMP) pairing, not classic SSP**, despite the on-screen dialog text; Cross-Transport Key Derivation (CTKD) pattern, matching `CAP-004`/`CAP-014` (both nRF-Connect-initiated bonds) | App/OS (Auto) | `PAIR-001` | frames 3492 (Pairing Request) – 3527 (Identity Address Information; Pairing DHKey Check Rcvd at 3518) | +| `06:51:08.000` (approx., inside the 3.50s wire gap while the dialog was shown) | Confirmed system pairing dialog (tapped "Koppelen") | User (App) | `PAIR-001` | inferred from the 3509→3517 gap (`Pairing Confirm` at 05.396 → `Pairing DHKey Check` at 08.900); no separate on-the-wire event marks the tap itself | | `06:51:10.091` - `06:51:10.332` | Classic BR/EDR link forms via CTKD (no SSP exchange) and device bonds | App/OS (Auto) | `PAIR-001` | frame 3559 (`Rcvd Connect Complete`, chandle `0x000b`) through `Command Complete (Link Key Request Reply)` ~3610 | | `06:51:13.439` | Classic ACL disconnect (LE link `0x0040` stays up throughout — not a full reconnect, see `CAP-034-FINDINGS.md` §5) | OS (Auto) | `PAIR-003` | frame 3684 (`Disconnection Complete`, chandle `0x000b`) | | `06:51:22.158` | **Correction — not a full "reconnect + service discovery":** nRF Connect's UI shows `DISCONNECTED`/offers `CONNECT` again (`f_291.1.jpg`), but no new LE connection event occurs anywhere in the log (same chandle `0x0040` throughout) — this tap's only wire-level effect is a single Database Hash re-check (cache hit) | User (App) | `PAIR-003`/`GATT-001` | frames 3690 (Sent) – 3691 (Rcvd) | diff --git a/captures/CAP-034-2026-09-01_06-46-31_06-52-45-Group_W/CAP-034-FINDINGS.md b/captures/CAP-034-2026-09-01_06-46-31_06-52-45-Group_W/CAP-034-FINDINGS.md index 091ceccf..036213fb 100644 --- a/captures/CAP-034-2026-09-01_06-46-31_06-52-45-Group_W/CAP-034-FINDINGS.md +++ b/captures/CAP-034-2026-09-01_06-46-31_06-52-45-Group_W/CAP-034-FINDINGS.md @@ -2,7 +2,7 @@ > **Status as of 2026-09-30** (`ai-sessions/0059`, A58-CAP-04 — read this first; the body below is the analysis as written): this capture's GATT map was promoted to 🟢 on 2026-09-01 (`PROTOCOL.md` §6); `FE2C1238…` = Find Hub "Beacon actions" (🟢, `PROTOCOL.md` §6 2026-09-26). -**✅ Maintainer sign-off obtained 2026-09-01, per `AGENTS.md` §6/§15.** All four promotions proposed +**✅ Maintainer sign-off obtained 2026-09-01, per `AGENTS.md` §6/§15.** All four promotions proposed (§9 items 1–4; item 5 = no ADR) in §9 (`PROTOCOL.md` §6/§4.3 Option D, `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s `GATT-001` row, `CAPTURE_BLUETOOTH_HCI_SNOOP.md`'s Capture Index + Group W section, and the cross-reference notes in `CAP-010`/`CAP-017`/`CAP-014-FINDINGS.md`) have been applied. @@ -138,7 +138,7 @@ never went away. 3691 2026-09-01T06:51:22.212250+0200 0x09 Rcvd Read By Type Response, Attribute List Length: 1 ``` This is the standard GATT-caching hash check (`CAP-003-FINDINGS.md` §1) — it confirms the client - still trusts its already-resolved database (the one just declared 20s earlier in the same LE + still trusts its already-resolved database (the one declared ~3.5 minutes earlier, discovery burst ending at frame 3469, 06:47:45.471, in the same LE session) and explicitly does **not** re-walk it. **This capture therefore has no second, independent confirmation of the structure — only one discovery pass exists in the whole log.** This is a stronger, more precise version of the guardrail's prediction: not "treat window 2 as @@ -447,15 +447,17 @@ characteristics, **before** bonding: Cross-checked against the wire — **zero** matching frames exist: ``` $ tshark -r CAP-034-btsnoop_hci.log -Y "bthci_acl.chandle==0x0040 and frame.time>=\"2026-09-01 06:49:55\" and frame.time<=\"2026-09-01 06:50:02\"" -(no output) +(no output, exit 0) +$ # positive control: the same filter over 06:47:42–06:47:46 +3264 … 3469 (the discovery burst, §4) ``` Every attempt throws its exception **~1ms** after the call — far too fast for a real BLE round trip (the discovery burst's own request/response pairs took 30–65ms each, §4). 🟡 HYPOTHESIS, not confirmed: this is consistent with a local Android-API-level rejection (e.g. firing a new GATT operation before a prior one's callback returns, a well-known `BluetoothGatt` pitfall) rather than any wire-level ATT error or encryption requirement — the official Fast Pair spec's own -characteristics table (§4.2) lists these as "Encrypted: No," so an encryption-gate explanation is -not supported by the cited spec text either. Reported as an open, unresolved observation (§8), not a +characteristics table is said to list these as "Encrypted: No" — ⚪ not quoted in this file (§4.2 quotes names and properties only; its one quoted "No" is the +Find Hub `FE2C1238` row), so an encryption-gate explanation is neither supported nor ruled out here. Reported as an open, unresolved observation (§8), not a guessed mechanism. ### 6c. The "reconnect" / "drill into Unknown Service" steps — already covered in §3 @@ -508,7 +510,7 @@ second confirmation — this capture's resolution rests on one discovery pass, n is judged sufficient because it is a full, genuine `0x0001..0xffff` walk with an untruncated log and independent video corroboration. -**Maintainer sign-off obtained 2026-09-01 — all four applied:** +**Maintainer sign-off obtained 2026-09-01 — items 1–4 applied (item 5: no ADR):** 1. **`PROTOCOL.md` §6**'s `0x0c0X`/`0x0f2X` open item — closed, citing this capture (§4.1–§4.7) as 🟢 FACT, with the full handle/UUID/name table added in place of the "🔴 OPEN QUESTION" framing. diff --git a/captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-EVENT-NOTES.md b/captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-EVENT-NOTES.md index f27f8475..e989fdb6 100644 --- a/captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-EVENT-NOTES.md +++ b/captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-EVENT-NOTES.md @@ -13,9 +13,7 @@ conclusive "genuinely absent" confirmation this Group was originally designed to `CAP-035-FINDINGS.md` §2 for the full reasoning. **Method for this session:** Pixel 9a (GrapheneOS), system Bluetooth settings only — no Pixel Buds -Companion App, no nRF Connect, no other BLE/GATT tool. Fill in each `[ ]`/`___` below as the session -happens — do not pre-fill any log-derived value (frame numbers, exact timestamps) before the capture -exists. +Companion App, no nRF Connect, no other BLE/GATT tool. ## Log Metadata @@ -120,7 +118,7 @@ the `btsnooz.py` fallback. package:com.google.android.apps.adm package:com.google.android.overlay.glanceablehubsettings2022 ``` - Record the exact output here, verbatim, even if empty: `___` + Recorded output: the package list above (`com.google.android.gms` present, disabled — `CAP-035-FINDINGS.md` §2). If any GMS-related package **is** present, also record its enabled/disabled state (`adb shell dumpsys package | grep -i enabled`) — "absent" and "present but disabled" are different conditions and change how strong this session's result is. Do not @@ -452,7 +450,7 @@ video 1's guessed rows or video 2's own original (unverified, and as it turned o | `06:51:40` | Buds no longer listed as "Opgeslagen" in the Bluetooth panel — Forget confirmed complete | User (OS) | Pre-condition | — | | — | **Not shown on camera in either video** — no Settings→Apps→Google Play Services (or any GMS app-info) screen appears anywhere in video 1 (checked exhaustively, 5s-interval contact sheet, full 67.69s) or at any of 10 sampled points across video 2's 317.57s. **Since independently confirmed off-camera via `dumpsys`** (added to the Preparation checklist above): `com.google.android.gms` shows `enabled=3` (`COMPONENT_ENABLED_STATE_DISABLED_USER`) — a verified disabled state, not merely a handwritten claim. See `CAP-035-FINDINGS.md` §2. | User (OS) | Pre-condition | — | | ~`06:51:50`–`06:51:53` | Buds appear in scan list; tap to connect | User (OS) | `PAIR-001`, `GSND-001` | frame 552 (`LE Enhanced Connection Complete`, 06:51:53.610, handle `0x0040`) | -| `06:52:00` | "Koppelen met Pixel Buds Pro 2 van Ted?" dialog appears on screen — **left unconfirmed**; video 1 stops recording ~1s later while it is still showing | User (OS) | `PAIR-001` | — | +| `06:51:55`–`06:51:57` | "Koppelen met Pixel Buds Pro 2 van Ted?" dialog appears on screen (not yet at video 1 `t=62`, overlay 06:51:54; open at `t=65`, overlay 06:51:57) — **left unconfirmed**; video 1 stops recording ~4s later (06:52:00.7) while it is still showing | User (OS) | `PAIR-001` | — | | `06:52:00.69` (approx.) | **End video 1 recording** (67.69s duration) | — | — | — | | — | *(~3 s gap — video 1 stopped, video 2 started; dialog remains on screen, untouched, throughout)* | — | — | — | | **`06:52:04`** | **Start video 2 recording.** Same still-unconfirmed pairing dialog visible (confirmed identical to video 1's last frame). | — | — | — | @@ -475,9 +473,7 @@ confirmed via a full-file DLCI census (`tshark -Y btrfcomm -T fields -e btrfcomm ## Decode / Analysis -*(Fill in after capture — isolate the Buds' own `chandle`/RFCOMM traffic first per `AGENTS.md` §13's -CLI-hygiene rule. Then check each of the four named channels — command + raw hex per finding, per -`PROJECT_RULES.md` §1 rule 4a.)* +Done — results in `CAP-035-FINDINGS.md` §3–§8; the commands below are the ones used. ``` tshark -r CAP-035-btsnoop_hci.log -Y "bthci_acl.chandle==0x000b and btrfcomm.dlci in {6,8,10,18}" \ # 96 frames; handle 0x000b = the Buds (Connection Complete 792, 1455). Corrected 2026-10-03: the address filter matched nothing. @@ -507,7 +503,7 @@ directly. ## Open Questions -- 🔴 *(carry forward any that remain unresolved after this session)* +- See `CAP-035-FINDINGS.md` §11. --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-EVENT-NOTES.md - https://tedsluis.github.io/opencontrolpixelbudspro2/captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-EVENT-NOTES diff --git a/captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-FINDINGS.md b/captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-FINDINGS.md index 83d289cd..c682721f 100644 --- a/captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-FINDINGS.md +++ b/captures/CAP-035-2026-09-02_06-50-53_06-57-24-Group_AB/CAP-035-FINDINGS.md @@ -71,7 +71,7 @@ video 1 (16 frames covering its entire 67.69s) and 10 sample points across video **Conclusion — interpretation, stated plainly:** this is **case (a) refined, not case (c)**: video 1 and video 2 are two **sequential** recordings of the same real-time sequence, separated by only a -short (~3–7s) recording-stop/restart gap — the user apparently stopped recording while the dialog +short (~3 s) *(corrected 2026-10-15, `ai-sessions/0094`)* recording-stop/restart gap — the user apparently stopped recording while the dialog sat open and unconfirmed, then started a second recording ~3 s later (06:50:53 + 67.69 s = 06:52:00.7; video 2 starts 06:52:04 — corrected 2026-10-03, `ai-sessions/0069`, `A68-CAP-23`: this document said "~7s" here and in its §1 title) from the same still-pending state. It is *not* a "redo from the pairing dialog" (case (c)) in the sense of discarding and re-attempting the pairing flow — nothing was cancelled, re-tapped, or reset between the two videos; @@ -282,8 +282,9 @@ CLI-hygiene artifact (§0, §3 rule out both). $ tshark -r CAP-035-btsnoop_hci.log -Y "btatt" -T fields -e frame.number -e btatt.handle -e _ws.col.Info ``` 38 ATT frames total, all incidental to the OS's own LE/CTKD bonding housekeeping (Model Number -String, Device Name, Server/Client Supported Features, Database Hash — all standard GAP/GATT -service handles `0x0001`–`0x0009` — plus `0x0f32`/`0x0f33`, the Battery Level characteristic and its +String at `0x0f26` (frames 577/728, Device Information), Device Name at `0x0034` (711) and the phone-side `0x0016` (590/729), +Server/Client Supported Features and Database Hash in the GATT service `0x0001`–`0x0009` (`0x000a` only in an Error Response, 780) *(corrected 2026-10-15, `ai-sessions/0094`: this said all were in `0x0001`–`0x0009`)* +— plus `0x0f32`/`0x0f33`, the Battery Level characteristic and its CCCD per `CAP-034-FINDINGS.md` §4.5). **No handle in the `0x0c00`–`0x0c18` range (the Fast Pair Service or the Accessory Non-Owner Service, per `CAP-034-FINDINGS.md` §4) is ever touched** — no discovery walk, no read, no write. This session used system Bluetooth settings only, with no Fast @@ -319,8 +320,8 @@ guardrail. - DLCI 0x0a opens in lockstep with DLCI 0x08 both times but never carries payload (🟢 FACT, §6) — consistent with the established multi-capture baseline, now confirmed once more under this Group's specific (verified-disabled-GMS) conditions. -- DLCI 0x06 and 0x12 never open at all (🟢 FACT, §7) — first check of their existence under any - condition; clean negative. +- DLCI 0x06 and 0x12 never open at all (🟢 FACT, §7) — first check of their existence under this + Group's conditions (`CAP-033-FINDINGS.md` §3 already saw no traffic on either, 2026-08-30) *(corrected 2026-10-15, `ai-sessions/0094`)*; clean negative. - ADR-008 compliant (🟢 FACT, §8). **Maintainer sign-off obtained 2026-09-02 — applied:** diff --git a/captures/CAP-036-2026-09-04_06-35-58_06-41-18-Group_AC/CAP-036-EVENT-NOTES.md b/captures/CAP-036-2026-09-04_06-35-58_06-41-18-Group_AC/CAP-036-EVENT-NOTES.md index 380c1f35..4b0a4bfa 100644 --- a/captures/CAP-036-2026-09-04_06-35-58_06-41-18-Group_AC/CAP-036-EVENT-NOTES.md +++ b/captures/CAP-036-2026-09-04_06-35-58_06-41-18-Group_AC/CAP-036-EVENT-NOTES.md @@ -1,5 +1,7 @@ # Event Notes: Pixel Buds Pro 2 (`libmaestro` / `libgfps`) — Group AC, settings-state read-back on (re)connect and on settings-screen open (`CAP-036`) +> **Status as of 2026-09-30** (as in `CAP-036-FINDINGS.md`): the DLCI 0x02 connect burst is decoded (`PROTOCOL.md` §6 2026-09-24); the "2-field sub-message" is Option F (ADR-043); Settable: ADR-049. The text below is as written. + **Status:** ✅ **Captured and analyzed 2026-09-04.** Full decode/analysis in `CAP-036-FINDINGS.md` — this file carries the corrected Event Timeline (verified via a frame-by-frame video re-pass) and a summary pointer to that decode, per @@ -97,7 +99,7 @@ frames). Not truncated. See `CAP-036-FINDINGS.md` §0 for the full command/outpu **CLI-hygiene correction found this session:** the `bluetooth.addr == 04:00:6e:cf:6e:07` filter this file's own §8 command (below) and `AGENTS.md` §13 both specify **returns zero matches** in this log for RFCOMM/`btrfcomm` frames — Wireshark's `bluetooth.addr` aggregate field is not -populated for ACL/RFCOMM traffic in this particular btsnoop capture, only for the HCI event layer +populated for any frame in this particular btsnoop capture (`-Y "bluetooth.addr"` alone → 0 frames, HCI events included); the HCI event layer's own field (`bthci_evt.bd_addr`). Verified workaround: `bthci_evt.bd_addr == 04:00:6e:cf:6e:07` **does** match (3 frames, including the Connection Complete event, frame 906), which identifies `bthci_acl.chandle == 0x0005` as the Buds' connection handle for the rest of this session — used @@ -332,7 +334,7 @@ another paired device, not the Buds, and structurally never touches DLCI 0x02/0x android.permission.CHANGE_COMPONENT_ENABLED_STATE: granted=true android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE: granted=true ``` - Record the exact output verbatim, even if unexpected: `___` + Recorded output: above (`enabled=0` for both users). (`enabled=0`/`COMPONENT_ENABLED_STATE_DEFAULT` = enabled; `enabled=3` = disabled-by-user, which would mean this session is *not* the intended baseline — say so rather than proceeding as if it were.) @@ -400,15 +402,15 @@ verified ones.** | Time (local) | Action / Event | Initiator | Test-ID | Evidence in `CAP-036-btsnoop_hci.log` | |---|---|---|---|---| | `06:35:47.35` | btsnoop log starts | — | — | frame 1 | -| `06:35:58` | Start video recording (Bluetooth off, Buds already out of case) | — | — | — | +| `06:35:58` | Start video recording (Bluetooth off; both buds seated in the open case, on camera from `t=0` through the end — corrected 2026-10-15, `ai-sessions/0094`: this said "already out of case") | — | — | Wire agrees: every DLCI 0x04 "Battery updated" frame reads `03 03 00 03 e4 e4 ff` (100 %, charging bit set — frames 1165 … 2355) | | `06:36:05` | **Window 1 start** — "Use Bluetooth" toggled ON | User (App) | `OBS-004`, `PAIR-003` | — (no wire event yet) | | `06:36:28.61` | Phone sends HCI `Create Connection` to the Buds (reconnect actually begins — ~23s after the toggle, not immediately) | OS (Auto) | `PAIR-003` | frame 898 | | `06:36:30.96` | HCI `Connection Complete`, BD_ADDR `04:00:6e:cf:6e:07`, `chandle 0x0005` | OS (Auto) | `PAIR-003` | frame 906 | | `06:36:31.30`–`31.33` | DLCI 0x04 (Fast Pair Message Stream) opens: `SABM`→`UA` | OS/App (Auto) | `OBS-004` | frames 1109, 1125 | | `06:36:31.359` | **`08 11 00 00` — "Get ANC state" query, Sent, DLCI 0x04** (documented-but-never-before-observed opcode, `PROTOCOL.md` §4.1) | App (Auto) | `OBS-004` | frame 1169 | | `06:36:31.369` | **`08 13 00 04 01 e8 00 20` — "Notify ANC state" response, Rcvd, DLCI 0x04** (current_state=`0x20`=Off, ~10.7ms after the query) | Buds (Auto) | `OBS-004` | frame 1182 | -| `06:36:31.37`–`31.81` | DLCI 0x08 opens and exchanges its connect-time capability/firmware/battery burst (Option E, `PROTOCOL.md` §4.3) | OS/App/Buds (Auto) | — | frames 1186–1372 | -| `06:36:32.57`–`35.66` | DLCI 0x02 (`libmaestro`) opens and exchanges a dense RPC-shaped burst (not decoded further this session — connection-settling traffic, see `CAP-036-FINDINGS.md` §4) | OS/App/Buds (Auto) | — | frames 1392–1591 | +| `06:36:31.37`–`31.81` | DLCI 0x08 opens and exchanges its connect-time capability/firmware/battery burst (Option E, `PROTOCOL.md` §4.3) | OS/App/Buds (Auto) | — | frames 1186–1372 (SABM 1186; payload 1273–1372) | +| `06:36:32.57`–`35.66` | DLCI 0x02 (`libmaestro`) opens and exchanges a dense RPC-shaped burst (not decoded further this session — connection-settling traffic, see `CAP-036-FINDINGS.md` §4) | OS/App/Buds (Auto) | — | frames 1392–1591 (SABM 1392; payload 1404–1591) | | `~06:36:40`–`06:36:41` | Connection established (on-screen confirmation: "Active", L:100% C:100% R:100%) — note the ~9s gap between wire-level completion (above) and this UI confirmation | App/OS (Auto) | `OBS-004`, `PAIR-003` | — (UI-only; wire side already complete) | | `06:36:40`–`~06:37:03` | Idle, nothing touched, nothing navigated — **zero DLCI 2/4/8 frames in this span** | — | `OBS-004` | none (checked) | | `~06:37:04` | **Window 1 end** — user taps gear icon → Device details screen (ANC row shows "Off" selected — matches the wire decode above exactly) | User (App) | `OBS-004` | — | @@ -423,11 +425,11 @@ verified ones.** | `~06:38:46` | **Window 3 end** — navigated back to "Controls and gestures", then (~06:38:53–58) back to Device details | User (App) | `OBS-004` | — | | `~06:39:08`–`06:39:16` | Navigated to "More settings" list (In-ear detection=ON, Multipoint=ON already visible here, before either sub-screen opened) | User (App) | `OBS-004` | — | | `06:39:16`–`~06:39:43` | Idle on "More settings", nothing touched — **zero DLCI 2/4/8 frames in this span** | — | `OBS-004` | none (checked) | -| `06:39:51` | First periodic DLCI 0x02/0x04/0x08 push burst begins (~10s-ish cadence thereafter; same known content as the connect-time pushes, all Rcvd-direction) — **begins ~3s before the Multipoint screen even renders, i.e. time-triggered, not screen-open-triggered** | Buds (Auto) | — | frames 1999–2007 (DLCI 0x08), 2009 (DLCI 0x02) | +| `06:39:51` | First periodic DLCI 0x02/0x04/0x08 push burst begins (irregular thereafter — gaps 10.1, 70.1, 10.1, 9.6, 50.4, 9.6 s, `CAP-036-FINDINGS.md` §12.1; same known content as the connect-time pushes, all Rcvd-direction) — **begins ~3s before the Multipoint screen even renders, i.e. time-triggered, not screen-open-triggered** | Buds (Auto) | — | frames 1999–2007 (DLCI 0x08), 2009 (DLCI 0x02) | | `~06:39:53`–`06:39:54` | **Window 5 start** — Multipoint screen opened (Use multipoint=ON) | User (App) | `OBS-004` | — | | `06:39:54`–`06:41:16` | Idle on Multipoint screen, nothing touched — **only the already-known periodic push (above) recurs (frames through 2361, continuing to 06:42:31, well past this video); no new Sent/query frame anywhere in this span** | — | `OBS-004` | frames 1999–2361 (periodic push only) | | `06:41:16` | **Window 5 end** — video ends here, screen still untouched | User (App) | `OBS-004` | — | -| `06:41:18` | End video recording | — | — | — | +| `06:41:16` | End video recording (`ffprobe` 317.95 s from 06:35:58; the folder name's `06-41-18` is the draft value) | — | — | — | | `not run` | *(optional)* Window 4 — in-ear detection screen — skipped this session (time) | — | `OBS-004` | — | **Contamination log:** None on the Buds' own connection. Windows were clean and no sliders or @@ -473,7 +475,7 @@ tshark -r CAP-036-btsnoop_hci.log -Y "bluetooth.addr == and btrfcomm. 35.66, frames 1404–1591) — none inside Window 1/2/3/5's stated idle spans. That burst itself is not decoded further this session (RPC-shaped, not matching the write envelope's specific inner-field shape on inspection) — see `CAP-036-FINDINGS.md` §4. A separate, periodic - Rcvd-only push (frames 2009/2048/2164/2204/2237/2334/2360, ~10s-ish cadence from 06:39:51 + Rcvd-only push (frames 2009/2048/2164/2204/2237/2334/2360, irregular 10–70 s gaps from 06:39:51 onward) recurs during Window 5 but is never `Sent`-direction and is not write-shaped.** - [x] **DLCI 0x08 — do not mistake a known push for a query.** This channel already pushes unsolicited content on connect (battery, the `google-pixel-buds-pro-v1` capability blob, @@ -486,7 +488,7 @@ tshark -r CAP-036-btsnoop_hci.log -Y "bluetooth.addr == and btrfcomm. Group/Code pairs are not mapped to any known setting (`PROTOCOL.md` §6 already lists DLCI 0x08's Groups as largely unresolved) — flagged as a new 🔴 open question, not claimed as a settings query. The same connect-time content (Option E) recurs verbatim during Window 5's - idle period (~10s-ish cadence from 06:39:51), always `Rcvd`, never a new `Sent` frame.** + idle period (irregular 10–70 s gaps from 06:39:51), always `Rcvd`, never a new `Sent` frame.** - [x] **Per-window attribution:** for each frame found, state which window it falls in and how far it sits from that window's start — a frame arriving 12s into an idle window means something different from one arriving 200ms after the screen opened. **Result: the only query/response @@ -525,12 +527,12 @@ if unresolved, or it becomes invisible to future sessions.)* `04 04`, `04 11`, `04 13`, `04 15`, `0e 04`) are structurally identical to DLCI 0x04's confirmed "Get" pattern, but none of these Group/Code pairs is mapped to any known setting — genuinely open, not a settings query claim (`CAP-036-FINDINGS.md` §5). -- 🔴 The dense RPC-shaped burst on DLCI 0x02 immediately after channel establishment (frames +- 🟢 *(answered: decoded — `PROTOCOL.md` §6 2026-09-24, `CAP-036-FINDINGS.md` status banner)* The dense RPC-shaped burst on DLCI 0x02 immediately after channel establishment (frames 1404–1591, 06:36:32.57–35.66) is not decoded this session beyond noting it shares a partial constant prefix with §4.5's documented correlation-ID pattern (`03 10 XX 1d ea 71 de 7e 25`) — whether it carries any settings-state read-back via `libmaestro`'s own channel (as opposed to the DLCI 0x04 mechanism this session did confirm) is unresolved (`CAP-036-FINDINGS.md` §4). -- 🔴 The "Notify ANC state" frame found this session (`08 13 00 04 01 e8 00 20`) decodes its +- *(answered: ADR-049)* The "Notify ANC state" frame found this session (`08 13 00 04 01 e8 00 20`) decodes its "Settable toggles" byte as `0x00`, whereas every previously-documented "Set ANC state" frame (`PROTOCOL.md` §4.1, `CAP-001`) shows `settable=0xe8` in the same byte position — not reconciled; either this byte means something different in a `Notify` triggered by an explicit `Get` versus one diff --git a/captures/CAP-036-2026-09-04_06-35-58_06-41-18-Group_AC/CAP-036-FINDINGS.md b/captures/CAP-036-2026-09-04_06-35-58_06-41-18-Group_AC/CAP-036-FINDINGS.md index b62a2fd9..e77de41f 100644 --- a/captures/CAP-036-2026-09-04_06-35-58_06-41-18-Group_AC/CAP-036-FINDINGS.md +++ b/captures/CAP-036-2026-09-04_06-35-58_06-41-18-Group_AC/CAP-036-FINDINGS.md @@ -75,7 +75,7 @@ $ tshark -r CAP-036-btsnoop_hci.log -Y "bluetooth.addr == 04:00:6e:cf:6e:07" -T ``` Wireshark's `bluetooth.addr` aggregate field is not populated for ACL/RFCOMM frames in this -particular btsnoop capture — only for the HCI event layer. The working substitute: +particular btsnoop capture — nor for any other frame (`-Y "bluetooth.addr"` alone → 0 frames, exit 0); `bthci_evt.bd_addr` is. The working substitute: ``` $ tshark -r CAP-036-btsnoop_hci.log -T fields -e bthci_evt.bd_addr 2>/dev/null | grep -v "^$" | sort -u @@ -136,7 +136,7 @@ two separate, both-clean sub-windows — "Controls and gestures" (parent, `06:38 starting at `06:38:46` as originally guessed. Both sub-windows are unaffected by this correction (nothing was toggled in either), so the DLCI 2/4/8 decode in §§3–5 covers both. -**On-screen values captured this pass** (satisfying `CAP-036-EVENT-NOTES.md` line ~329's +**On-screen values captured this pass** (satisfying `CAP-036-EVENT-NOTES.md` Preparation checklist's "on-screen current values" item's requirement, previously blank): - ANC mode: **Off** (the "Off" tile alone shows a tinted/selected background at `06:37:04`, cropped and confirmed pixel-for-pixel against the other three tiles' flat grey). @@ -217,7 +217,7 @@ $ tshark -r CAP-036-btsnoop_hci.log -Y "bthci_acl.chandle==0x0005 and btrfcomm.d All 99 DLCI 0x02 frames in this session fall into exactly two clusters: -1. **Connection-settling burst, frames 1392–1591 (06:36:32.570–35.655), ~3.1s** — `SABM`/`UA` +1. **Connection-settling burst, frames 1392–1591 (06:36:32.570–35.655; SABM 1392, payload 1404–1591), ~3.1s** — `SABM`/`UA` followed by a dense back-and-forth of small `Sent`/`Rcvd` payloads, including three ASCII `"release_5.203"` firmware-version strings (frame 1405) and many short request/response pairs sharing a partial constant prefix (`03 10 15 1d ea 71 de 7e 25...`, matching — up to that @@ -281,7 +281,7 @@ $ tshark -r CAP-036-btsnoop_hci.log -Y "bthci_acl.chandle==0x0005 and btrfcomm.d ``` The same connect-time content (capability blob + firmware string + battery-like value) recurs -verbatim during Window 5 (frames 1999–2357, ~10s-ish cadence from `06:39:51` onward, matching +verbatim during Window 5 (frames 1999–2357, irregular 10–70 s gaps from `06:39:51` onward, §12.1, *(corrected 2026-10-15, `ai-sessions/0094`)* matching DLCI 0x02/0x04's periodic push documented in §4/§3) — always `Rcvd`, always immediately acknowledged by an empty `Sent` frame, never a new query. @@ -373,10 +373,10 @@ conclusions. - 🔴 DLCI 0x08's connect-time zero-length `[Group][Code][00 00]`-shaped `Sent` frames (`04 02`, `04 04`, `04 11`, `04 13`, `04 15`, `0e 04`) are structurally identical to DLCI 0x04's confirmed "Get" pattern, but none of these Group/Code pairs is mapped to any known setting (§5). -- 🔴 The dense RPC-shaped burst on DLCI 0x02 immediately after channel establishment (frames +- 🟢 *(answered: decoded — `GetSoftwareInfo`, `SubscribeRuntimeInfo`, `GetHardwareInfo`, `SetWallclock`, `ReadSetting` sweep; `PROTOCOL.md` §6 2026-09-24)* The dense RPC-shaped burst on DLCI 0x02 immediately after channel establishment (frames 1404–1591) is not decoded beyond noting its partial match to §4.5's correlation-ID prefix — whether it carries any settings-state read-back via `libmaestro`'s own channel is unresolved (§4). -- 🔴 This session's "Notify ANC state" frame (`08 13 00 04 01 e8 00 20`) decodes "Settable +- *(answered: ADR-049)* This session's "Notify ANC state" frame (`08 13 00 04 01 e8 00 20`) decodes "Settable toggles" as `0x00`, versus `0xe8` in every previously-documented "Set ANC state" frame in the same byte position — not reconciled (§3). - 🟢 DLCI 0x02's periodic push (§12.6) is the `SubscribeRuntimeInfo` stream — Case / Left / Right @@ -552,7 +552,7 @@ the structure was left as an open question because it has two fields per entry w triple has three, and the trailing numbers were misread as "01, 01, 02". Read from the bytes above, the three entries are `08 64 10 01`, `08 64 10 02`, `08 64 10 02`: field 1 = 100 in each, field 2 = **1, 2, 2**. With the Option F reading that is Case 100 % (field 2 = 1), Left 100 % charging and -Right 100 % charging (field 2 = 2) — both buds were in the case when this frame was sent. The same +Right 100 % charging (field 2 = 2) — both buds were in the case when this frame was sent (the video shows both buds seated in the open case for the whole recording, and every DLCI 0x04 `03 03` frame, 1165 … 2355, carries the charging bit; `CAP-036-EVENT-NOTES.md`'s "already out of case" was corrected 2026-10-15). The same frame carries entry 7 (`3a 06 08 01 10 01 18 00`) and the stream's method id. Nothing in this section is an open question any more; the wire evidence that settled the entry order is in `CAP-050`/`CAP-056` (ADR-043), not in this capture. diff --git a/captures/CAP-037-2026-09-06_06-11-46_06-29-38-Group_AD/CAP-037-EVENT-NOTES.md b/captures/CAP-037-2026-09-06_06-11-46_06-29-38-Group_AD/CAP-037-EVENT-NOTES.md index c6f5132b..de2daba1 100644 --- a/captures/CAP-037-2026-09-06_06-11-46_06-29-38-Group_AD/CAP-037-EVENT-NOTES.md +++ b/captures/CAP-037-2026-09-06_06-11-46_06-29-38-Group_AD/CAP-037-EVENT-NOTES.md @@ -141,7 +141,7 @@ that all agree, is the authoritative record for this correction). | `06:14:14.04` | Reconnect 5 — Get/Notify (chandle `0x0008`) | docked (`0x00`, video-confirmed 06:14:13) — **all 5 "planned" repeats were docked, not alternating as written** | `OBS-004`, `PAIR-003` | frames 5989/6027 | | `~06:14:14`–`06:15:34` | Buds physically removed from case (undock event; exact moment not pinned to the second) | transition | — | — | | `06:15:34.39`–`06:18:05.75` | Reconnects 6–10 (chandles `0x0005`,`0x0006`,`0x0007`,`0x0008`,`0x0009`) | undocked (`0xe8`, video-confirmed 06:16:48) | `OBS-004`, `PAIR-003` | frames 8117–11507 (5 Get/Notify pairs) | -| `06:18:24.27` | Extra Notify on chandle `0x0009` (no new Get) 18 s after its own Get (frame 11483, 06:18:05.75), Settable `0x00`; the link drops at 06:18:28.09 (frame 12065) | Settable flip within one connection — explained by `CAP-048` §4 as a docking in progress | 🟢 (corrected 2026-10-03: the time given here was frame 11507's) | frame 12008 | +| `06:18:24.27` | Extra Notify on chandle `0x0009` (no new Get) 18 s after its own Get (frame 11483, 06:18:05.75), Settable `0x00`; the link drops at 06:18:28.09 (frame 12065) | Settable flip within one connection — explained by `CAP-048` §4 as a docking in progress | `OBS-004` | frame 12008 (🟢; corrected 2026-10-03: the time given here was frame 11507's) | | `06:18:51.72`–`06:20:02.03` | Reconnects 11–13 (chandles `0x000a`,`0x000b`,`0x000c`) | docked (`0x00`) | `OBS-004`, `PAIR-003` | frames 12700–14457 | | `06:21:13.95`–`06:23:45.96` | Reconnects 14–18 (chandles `0x0007`,`0x0008`,`0x000a`,`0x000b`,`0x000c`) | undocked (`0xe8`, video-confirmed 06:23:42) | `OBS-004`, `PAIR-003` | frames 16300–19778 | | `06:24:24.86`–`06:26:08.78` | Reconnects 19–23 (chandles `0x000d`,`0x000e`,`0x000f`,`0x0010`,`0x0011`) | docked (`0x00`) | `OBS-004`, `PAIR-003` | frames 20776–24400 | @@ -180,7 +180,7 @@ tshark -r CAP-037-btsnoop_hci.log -Y "bthci_acl.chandle== and btrf planned 5 repeats (see corrected Event Timeline above; full per-reconnect table in CAP-037-FINDINGS.md §3). This is the largest single-session replication of ADR-022's trigger condition on file to date. -- [x] **Does the `Settable-toggles` byte match dock state on every repeat**, per ADR-024? Result: +- [x] **Does the `Settable-toggles` byte match dock state on every repeat**, per ADR-024 (since superseded by ADR-049 — see `CAP-037-FINDINGS.md` status banner)? Result: **Yes, 26/26** — every docked-block reconnect reads `0x00`, every undocked-block reconnect reads `0xe8`, cross-validated against 6 independent video spot-checks (all agree). One anomaly: chandle `0x0009` shows a second Notify 18s after its own Get, with no new Get frame, @@ -203,12 +203,12 @@ tshark -r CAP-037-btsnoop_hci.log -Y "bthci_acl.chandle== and btrf *(Add every new 🔴 OPEN QUESTION found here — and copy each into `PROTOCOL.md` §6's matching subsection per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §8's mandatory rule.)* -- 🔴 Chandle `0x0009`'s second "Notify ANC state" frame (frame 12008, 06:18:24.27 — corrected 2026-10-03; answered by `CAP-048` §4) fires ~18s after +- 🟢 *(answered by `CAP-048` §4: a docking in progress; the link drops 3.8 s later — `CAP-037-FINDINGS.md` §8)* Chandle `0x0009`'s second "Notify ANC state" frame (frame 12008, 06:18:24.27 — corrected 2026-10-03) fires ~18s after its own Get (frame 11483/11507) with no new Get in between, and its `Settable-toggles` value flips from `0xe8` to `0x00` — implying either a dock-state change mid-connection without a disconnect (in tension with `DECISIONS.md` ADR-016's "disconnect fires the instant both buds are re-docked" finding) or a spontaneous, unprompted second Notify on this specific connection. Not - resolved by this capture (CAP-037-FINDINGS.md §5). + resolved by this capture itself; answered by `CAP-048` §4 (CAP-037-FINDINGS.md §5/§8). - 🔴 The planned Docked→Undocked→Docked→Undocked→Docked alternation did not happen on schedule — all 5 "planned" repeats were docked, and the real dock-state alternation began organically ~1–1.5 minutes later than planned, continuing for many more cycles. Not itself a protocol open question, @@ -217,22 +217,22 @@ subsection per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §8's mandatory rule.)* ## Next steps after filling this in -- [ ] Cross-reference every Test-ID this Group is supposed to exercise (`AGENTS.md` §13) — +- [x] Cross-reference every Test-ID this Group is supposed to exercise (`AGENTS.md` §13) — confirm `OBS-004`/`PAIR-003` are clearly referenced above. -- [ ] Write `CAP-037-FINDINGS.md` per `PROJECT_RULES.md` §2, using this file's timeline as the +- [x] Write `CAP-037-FINDINGS.md` per `PROJECT_RULES.md` §2, using this file's timeline as the evidence source, following the hex & script rule (§1 rule 4a). -- [ ] Update this session's row in `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index — status from +- [x] Update this session's row in `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index — status from `planned` to `captured`/`analyzed`, and fill in the Android/firmware/app-version columns and the log path. Update `id_registry.csv`'s `CAP-037` row to match. -- [ ] Update `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s `OBS-004` row with a **pointer only**, never a +- [x] Update `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s `OBS-004` row with a **pointer only**, never a restated finding. -- [ ] Copy every new 🔴 OPEN QUESTION into `PROTOCOL.md` §6 (mandatory, §8). -- [ ] **Do not** promote anything in `PROTOCOL.md` to 🟢 FACT, and **do not** write or amend a +- [x] Copy every new 🔴 OPEN QUESTION into `PROTOCOL.md` §6 (mandatory, §8). (None remains — `CAP-037-FINDINGS.md` §8.) +- [x] **Do not** promote anything in `PROTOCOL.md` to 🟢 FACT, and **do not** write or amend a `DECISIONS.md` ADR, without explicit maintainer sign-off — propose it, clearly labelled as a proposal (`AGENTS.md` §6, §15). Even a clean 5/5 reproduction of ADR-022/ADR-024 is *additional evidence for an already-FACT claim*, not itself grounds for the agent to write a new ADR unprompted. -- [ ] Rename this capture's folder from the `yyyy-MM-dd_HH-mm-ss_HH-mm-ss` placeholder to the +- [x] Rename this capture's folder from the `yyyy-MM-dd_HH-mm-ss_HH-mm-ss` placeholder to the actual session date/start-time/end-time. --- diff --git a/captures/CAP-037-2026-09-06_06-11-46_06-29-38-Group_AD/CAP-037-FINDINGS.md b/captures/CAP-037-2026-09-06_06-11-46_06-29-38-Group_AD/CAP-037-FINDINGS.md index 76cc3aed..c6fe223a 100644 --- a/captures/CAP-037-2026-09-06_06-11-46_06-29-38-Group_AD/CAP-037-FINDINGS.md +++ b/captures/CAP-037-2026-09-06_06-11-46_06-29-38-Group_AD/CAP-037-FINDINGS.md @@ -125,13 +125,15 @@ both earbuds are seated in the case, so "Current ANC state" trivially reads "Off `Settable-toggles` also reads "docked" — but this capture only shows correlation, not the underlying mechanism, so it stays HYPOTHESIS. -## 4. DLCI 0x02 — silent throughout (🟢 FACT, clean negative, consistent with "nothing else touched") +## 4. DLCI 0x02 — no `Sent` frame outside each connection's settling window (🟢 FACT, clean negative, consistent with "nothing else touched") *(heading corrected 2026-10-15, `ai-sessions/0094`: it said "silent throughout")* ``` $ tshark -r CAP-037-btsnoop_hci.log -Y "btrfcomm.dlci==2 and frame.p2p_dir==0 and btrfcomm.len>0" \ -T fields -e frame.number +(1,266 frames, exit 0, in per-connection clusters: 1376–1558, 1743–1745, 3443–3637, 3756–3758, …, 29264–29520; + every one ≤ 28.0 s after the preceding Connection Complete from -Y "bthci_evt.code==0x03 and bthci_evt.status==0" — 31 events, 863 … 28715) ``` -No `Sent`-direction DLCI 0x02 traffic outside each reconnect's own brief connection-settling burst +No `Sent`-direction DLCI 0x02 traffic outside each reconnect's own connection-settling window (main burst plus a 2–5-frame follow-up ~20–28 s after connect) (matching `CAP-036-FINDINGS.md` §4's already-documented pattern) — confirms no setting was touched by accident anywhere in this 20-minute session, consistent with the Contamination log's "no settings screens opened" note. @@ -144,6 +146,7 @@ $ tshark -r CAP-037-btsnoop_hci.log -Y "btrfcomm.dlci==4 and bthci_acl.chandle== 11483 06:18:05.748367 Sent 08110000 11507 06:18:06.031360 Rcvd 0813000401e8e880 <- Settable=0xe8 12008 06:18:24.272960 Rcvd 0813000401e80020 <- Settable=0x00, NO preceding Get +$ tshark -r CAP-037-btsnoop_hci.log -Y "bthci_evt.code==0x05 and bthci_evt.connection_handle==0x0009" # separate query 12065 06:18:28.089732 Rcvd Disconnect Complete, reason 0x13 ``` Rewritten 2026-10-03 (`ai-sessions/0069`, `A68-CAP-23`). On this one handle a **second** "Notify ANC diff --git a/captures/CAP-038-2026-09-06_06-49-50_06-53-57-Group_AE/CAP-038-EVENT-NOTES.md b/captures/CAP-038-2026-09-06_06-49-50_06-53-57-Group_AE/CAP-038-EVENT-NOTES.md index f66a0fa8..3bfb085c 100644 --- a/captures/CAP-038-2026-09-06_06-49-50_06-53-57-Group_AE/CAP-038-EVENT-NOTES.md +++ b/captures/CAP-038-2026-09-06_06-49-50_06-53-57-Group_AE/CAP-038-EVENT-NOTES.md @@ -1,5 +1,7 @@ # Event Notes: Pixel Buds Pro 2 (`libmaestro` / `libgfps`) — Group AE, realistic physical reconnect trigger vs. a system-Bluetooth-toggle reconnect (`CAP-038`) +> **Status as of 2026-09-30** (as in `CAP-038-FINDINGS.md`): the Settable byte's current reading is ADR-049 (supersedes ADR-024's "dock state"); "DOCKED"/"reconfirms ADR-024" below is the reading as written. The press-and-hold reading of 3695/3847 was confirmed by `CAP-051`. + **Status:** ✅ **Captured and analyzed.** All blanks below are filled in from `CAP-038-btsnoop_hci.log` and `CAP-038-recording.mp4` per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §5 (analysis) and §8 (what to update), and `PROJECT_RULES.md` rule 11/14 (reproducibility metadata). @@ -176,7 +178,7 @@ tshark -r CAP-038-btsnoop_hci.log -Y "bthci_acl.chandle==" \ (`CAP-014-FINDINGS.md` §4a) — a clean negative once correctly attributed. 5. **DLCI `0x0b`** opens on chandle `0x0001` (6 frames): server channel 5 ("GSND AUDIO") with the direction bit set — control frames only. Result: item 1 is the session's central tension (§3); item 2 is a new HYPOTHESIS-level - observation; items 3–4 are clean negatives (once correctly attributed away from the unrelated + observation; item 3 is an A2DP stream set up but never started (no media, `bta2dp` → 0); item 4 is a clean negative (once correctly attributed away from the unrelated BLE device). - [ ] **Window 2 (if run):** **not verifiably executed** — see Event Timeline correction. No video coverage (video ends ~06:53:57, before Window 2's documented 06:54:14 start) and no wire @@ -190,12 +192,12 @@ tshark -r CAP-038-btsnoop_hci.log -Y "bthci_acl.chandle==" \ ## Open Questions -- 🔴 Why does the first reconnect's "Notify ANC state" frame read `Settable-toggles=0x00` (docked), +- *(answered: part of ADR-049 — `CAP-038-FINDINGS.md` §9 status)* Why does the first reconnect's "Notify ANC state" frame read `Settable-toggles=0x00` (docked), immediately after the Buds were reported physically removed from the case for this Group's "realistic worn reconnect" test? Two explanations are equally consistent with this one data point and this capture cannot distinguish them (query timing vs. sensor lag) — see `CAP-038-FINDINGS.md` §3. -- 🔴 What produced the two ANC-mode Notify frames (3695, 3847) on DLCI 0x04 with no preceding Get +- *(answered: press-and-hold, confirmed by `CAP-051` — `CAP-038-FINDINGS.md` §9 status)* What produced the two ANC-mode Notify frames (3695, 3847) on DLCI 0x04 with no preceding Get and no Set(`0x12`) frame anywhere in the log? Plausibly a hardware press-and-hold gesture (matches `CAP-027-FINDINGS.md` §4's mechanism) but not video-confirmed. - 🔴 Window 2 (case-lid-only reconnect trigger) remains untested — needs a re-run with video @@ -206,13 +208,12 @@ tshark -r CAP-038-btsnoop_hci.log -Y "bthci_acl.chandle==" \ - [x] Cross-reference every Test-ID this Group is supposed to exercise (`AGENTS.md` §13) — see `CAP-038-FINDINGS.md` §8. - [x] Write `CAP-038-FINDINGS.md` per `PROJECT_RULES.md` §2, hex & script rule (§1 rule 4a). -- [ ] Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index (status → `captured`/`analyzed`) and - `id_registry.csv`'s `CAP-038` row. **(Owned centrally — not done by this pass.)** -- [ ] Add `OBS-005`'s Evidence column pointer in `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` (pointer only). - **(Owned centrally — not done by this pass.)** -- [ ] Copy every new 🔴 OPEN QUESTION into `PROTOCOL.md` §6 (mandatory, §8). **(Owned centrally — - not done by this pass; the three open questions are recorded above and in - `CAP-038-FINDINGS.md` §9 ready to be copied in.)** +- [x] Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index (status → `captured`/`analyzed`) and + `id_registry.csv`'s `CAP-038` row. (Done centrally: both read `analyzed`.) +- [x] Add `OBS-005`'s Evidence column pointer in `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` (pointer only). + (Done centrally.) +- [x] Copy every new 🔴 OPEN QUESTION into `PROTOCOL.md` §6 (mandatory, §8). (Done centrally — + `CAP-038-FINDINGS.md` §9 status, 2026-09-30.) - [ ] **Do not** promote anything to 🟢 FACT or write a `DECISIONS.md` ADR without explicit maintainer sign-off (`AGENTS.md` §6, §15) — propose only. (Honored: nothing in this file or `CAP-038-FINDINGS.md` promotes a FACT beyond what was already an approved ADR/FACT before diff --git a/captures/CAP-038-2026-09-06_06-49-50_06-53-57-Group_AE/CAP-038-FINDINGS.md b/captures/CAP-038-2026-09-06_06-49-50_06-53-57-Group_AE/CAP-038-FINDINGS.md index ed070650..c3cd7d1a 100644 --- a/captures/CAP-038-2026-09-06_06-49-50_06-53-57-Group_AE/CAP-038-FINDINGS.md +++ b/captures/CAP-038-2026-09-06_06-49-50_06-53-57-Group_AE/CAP-038-FINDINGS.md @@ -133,7 +133,7 @@ Per `AGENTS.md` §13.6, no forced reconciliation is offered — this is reported future capture (one that holds the connection open longer before checking, or that independently video-confirms the dock sensor's own physical state at the query's exact timestamp) to resolve. -`libmaestro`'s own channel (DLCI `0x03`, 155 non-empty frames total) is not decoded field-by-field +`libmaestro`'s own channel (DLCI `0x03`, 155 frames total, 132 non-empty, 61 of them `Sent`) is not decoded field-by-field this pass — see §7 for its role in the mid-session settings contamination. DLCI `0x0b` (6 frames) is server channel 5, "GSND AUDIO" — the channel this project otherwise sees as DLCI `0x0a`; it carries control frames only here. @@ -170,10 +170,13 @@ Frames 3695 (`Current=0x08`) and 3847 (`Current=0x80`) are DLCI 0x04 "Notify ANC with **no immediately-preceding `08 11` Get** and — checked exhaustively — **no `08 12` "Set ANC state" frame anywhere in this entire log**: ``` -$ tshark -r CAP-038-btsnoop_hci.log -Y "bthci_acl.chandle==0x0004 and data.data contains 08:12" \ - -T fields -e frame.number -(0 rows) +$ tshark -r CAP-038-btsnoop_hci.log -Y "data.data contains 08:12" -T fields -e frame.number # whole log, both connections +(0 rows, exit 0) +$ tshark -r CAP-038-btsnoop_hci.log -Y "btrfcomm.len>0 and data.data[0:2]==08:13" -T fields -e frame.number # positive control +1154 (chandle 0x0001, DLCI 0x05) +3282, 3695, 3847, 4122, 4158 (chandle 0x0004, DLCI 0x04) ``` +(Corrected 2026-10-15, `ai-sessions/0094`: the command quoted here before was restricted to chandle `0x0004`, so it did not cover the first connection's Message Stream on DLCI 0x05; the whole-log result is the same, 0 rows.) Also confirmed DLCI 0x04 never bounces (no SABM/UA) between the two clear Get/Notify pairs (3261/3282 and 4116/4122) — these two frames appear on an already-open, continuously-open channel. This matches `CAP-027-FINDINGS.md` §4's already-established mechanism exactly: a @@ -209,7 +212,7 @@ $ tshark -r CAP-038-btsnoop_hci.log -Y "bthci_acl.chandle==0x0001 and btrfcomm.d ``` All 63 `Sent`-direction DLCI `0x02`-*numbered* frames in this log occur on chandle `0x0004` (06:52:53.26 onward) — zero on chandle `0x0001` (which, per §3's correction, uses DLCI `0x03` for -the equivalent `libmaestro` channel instead, carrying 155 non-empty frames of its own). This means +the equivalent `libmaestro` channel instead, carrying 155 frames — 132 non-empty, 61 `Sent` — of its own). This means the EQ/In-Ear/Multipoint/Touch-Controls settings-browsing that spans both connections likely produced writes on **both** chandles' respective `libmaestro` DLCI (`0x03` on the first, `0x02` on the second) — not, as an earlier pass concluded, exclusively on the second. **Not individually @@ -222,8 +225,8 @@ deskresearch pass could decode these frames against the known `field5{field4{... - **`OBS-005`** (primary): exercised via both reconnects. The first (chandle `0x0001`, immediately after physical removal from the case — the more "realistic" trigger) reads - **`Settable-toggles=0x00` (docked)**, a genuine tension with the intended test condition (§3); no - A2DP/GATT-battery traffic found on either connection (clean negative, §6). The second (chandle + **`Settable-toggles=0x00` (docked)**, a genuine tension with the intended test condition (§3); an + A2DP stream is set up (AVDTP SetConfiguration/Open) but never started on either connection, and no Buds GATT battery traffic (§6, §1). The second (chandle `0x0004`) reads undocked as expected, then correctly flips to docked (§4). Window 2 (case-lid-only trigger) not verifiably executed (§2). - **`PAIR-003`** (incidental): two reconnects to an already-bonded device, exercised cleanly (§1). diff --git a/captures/CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF/CAP-039-EVENT-NOTES.md b/captures/CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF/CAP-039-EVENT-NOTES.md index e4b18dff..9d6b3bed 100644 --- a/captures/CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF/CAP-039-EVENT-NOTES.md +++ b/captures/CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF/CAP-039-EVENT-NOTES.md @@ -1,5 +1,7 @@ # Event Notes: Pixel Buds Pro 2 (`libmaestro` / `libgfps`) — Group AF, `Settable-toggles` byte: Set-tap vs. reconnect-Get, same session/dock-state (`CAP-039`) +> **Status as of 2026-09-30** (as in `CAP-039-FINDINGS.md`): `03 03 …` is 🟢 "Battery updated" (ADR-031/033); the DLCI 0x02 burst is the official app's `ReadSetting` sweep (`PROTOCOL.md` §6 2026-09-24); the Settable byte's reading is ADR-049 (supersedes ADR-024's "dock state"). The text below is as written. + **Status:** ✅ **Captured, video-reviewed, and decoded — see `CAP-039-FINDINGS.md`.** The session ran 6 reconnects and 4 ANC taps, not the planned 1 Get + 1 Set. Folder renamed to `CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF`. @@ -37,7 +39,7 @@ in the case at any point, or the dock-state variable stops being held constant. | Date | 2026-09-06 | | Firmware version | ⚪ ASSUMPTION `release_5.203` | | Test device | Pixel 7a, Android version ⚪ not recorded in this session (this file said "14"; the same phone is recorded as 17 in the captures before and after — unreconciled, `ai-sessions/0069` `A68-CAP-23`). **Official Pixel Buds Companion App, Google Play Services enabled** | -| Video file | `CAP-039-recording.mp4` — 05:03s, `07:07:17`–`07:12:20` local time | +| Video file | `CAP-039-recording.mp4` — 265.62s (`ffprobe`), `07:07:17`–~`07:11:43` local time | | Log file | `CAP-039-btsnoop_hci.log` — 372.04s, 6,184 packets, 2026-09-06 07:07:28.040–07:13:40.082 local time | | Buds MAC (partial, per `AGENTS.md` §7/§9) | `04:00:6e:cf:6e:07` | @@ -98,11 +100,11 @@ not silently reconciled.** | `07:07:17` | Start video recording | — | — | — | | `07:07:22` | Phone shown in system Bluetooth quick-panel, Buds already "Active" (L:98%/R:100%); user's finger near the Buds row (video-confirmed, `t6.png`) — the log has not started yet (first log frame is `07:07:28.040`, a full `Sent Reset` HCI re-init sequence) | User (OS) | — | — (predates log) | | `07:07:28.040` | Log starts — HCI controller Reset/init sequence (consistent with the "phone rebooted" prep step) | — | — | frame 1 | -| `07:07:33`–`34` | "Pixel Buds Pro 2 van Ted — Connecting…" shown on screen (video-confirmed, `t17.png`); wire Connection Complete chandle `0x0002` (BLE) @`07:07:34.091`, chandle `0x0005` (classic) @`07:07:35.111` | App/OS (Auto) | `PAIR-003` | frames 653, 683 | +| `07:07:33`–`34` | "Pixel Buds Pro 2 van Ted — Connecting…" shown on screen (video-confirmed, `t17.png`); wire: failed classic page @`07:07:34.091` (frame 653, Connection Complete status `0x04` Page Timeout, link type ACL — no connection), then the classic connection chandle `0x0005` @`07:07:35.111` | App/OS (Auto) | `PAIR-003` | frames 653, 683 | | `07:07:39.230`/`.412` | DLCI 0x04 Get (`08 11 00 00`) / Notify (`08 13 00 04 01 e8 08 08`) immediately after reconnect — Current state `0x08` = Noise cancellation (pre-existing mode, matches on-screen "Noise cancellation" selected at `t40.png`) | App/OS (Auto) | `OBS-006` (Get #1) | frames 890/909 | +| `~07:07:43`–`46` | 47-frame (Sent) DLCI 0x02 burst, 07:07:43.76–07:07:47.40 — the official app's `ReadSetting` sweep, not an EQ write (`CAP-039-FINDINGS.md` §4 and status banner) | User (App) | — | frames 1203–1362 (representative) | | `07:07:56`–`57` | **Real "Window 1" tap** — Device details screen, user taps **"Adaptive"** tile (video-confirmed, `t40.png` — finger on 3rd tile; mode was Noise cancellation, NOT "Off" as originally documented) | User (App) | `ANC`-family | Set frame 1411 @`07:07:57.588` | | `07:07:57.896` | Notify: Current `0x40` = Adaptive | App (Auto) | — | frame 1416 | -| `~07:07:43`–`46` | Rapid ~300-frame DLCI 0x02 write burst (consistent with an EQ slider drag — see Decode/Analysis §, bonus) | User (App) | — | frames 1203–1362 (representative) | | `07:08:11.861` | Disconnection Complete, chandle `0x0005`, reason `0x16` (Terminated by Local Host) | User (OS)/App | `PAIR-003` | frame 1628 | | `07:08:25.794`; `07:08:29.785`/`.980` | Reconnect #2 — chandle `0x0006` (Connect Complete 1711); DLCI 0x04 Get/Notify, Current `0x40` (still Adaptive) | App/OS (Auto) | `OBS-006` (Get #2) | frames 1951/1967 | | `07:08:51.455` | Disconnection Complete, chandle `0x0006`, reason `0x16` | User (OS)/App | `PAIR-003` | frame 2461 | @@ -120,8 +122,8 @@ not silently reconciled.** | `~07:11:43` | Video ends (ffprobe duration 265.62s; 07:07:17+265.62s) — log continues to `07:13:40.082` with idle BLE advertising only, nothing further of interest | — | — | frame 6184 (log end) | **Contamination log:** The originally-documented "one Set, one Get" plan was not followed. The -user instead performed **6 reconnects** (5 of them locally-initiated disconnect/reconnect cycles, -reason `0x16`, matching the same button-mashing pattern `CAP-040`'s own procedure used +user instead performed **6 reconnects** (the initial one plus 5 disconnect/reconnect cycles — 4 locally-initiated, +reason `0x16`, and 1 remote, `0x13`, frame 5184 — matching the same button-mashing pattern `CAP-040`'s own procedure used deliberately) and **4 ANC taps** alternating Noise cancellation ↔ Adaptive (never "Off"), all interleaved with a mid-session EQ-slider-drag-shaped DLCI 0x02 burst and other settings navigation (In-Ear Detection, Touch Controls, Multipoint — not individually decoded, see Decode/Analysis). @@ -154,7 +156,7 @@ tshark -r CAP-039-btsnoop_hci.log -Y "bthci_acl.chandle== and btrf ## Open Questions - 🔴 What causes the app/OS to disconnect-and-immediately-reconnect 5 times in ~3.5 minutes with no - visible on-screen trigger for most of them (reason `0x16`, locally-initiated)? Not resolved by + visible on-screen trigger for most of them (reason `0x16`, locally-initiated, on 4; `0x13`, remote, on frame 5184)? Not resolved by this capture — flagged for a future session with continuous full-screen video coverage of the Bluetooth quick-panel/app, since this session's camera was mostly on the Device details screen and did not show whichever UI action triggered each disconnect. @@ -167,16 +169,16 @@ tshark -r CAP-039-btsnoop_hci.log -Y "bthci_acl.chandle== and btrf ## Next steps after filling this in -- [ ] Cross-reference every Test-ID this Group is supposed to exercise (`AGENTS.md` §13). -- [ ] Write `CAP-039-FINDINGS.md` per `PROJECT_RULES.md` §2, hex & script rule (§1 rule 4a). -- [ ] Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index (status → `captured`/`analyzed`) and +- [x] Cross-reference every Test-ID this Group is supposed to exercise (`AGENTS.md` §13). +- [x] Write `CAP-039-FINDINGS.md` per `PROJECT_RULES.md` §2, hex & script rule (§1 rule 4a). +- [x] Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index (status → `captured`/`analyzed`) and `id_registry.csv`'s `CAP-039` row. -- [ ] Add `OBS-006`'s Evidence column pointer in `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` (pointer only). -- [ ] Copy every new 🔴 OPEN QUESTION into `PROTOCOL.md` §6 (mandatory, §8). -- [ ] **Do not** promote anything to 🟢 FACT or write/amend a `DECISIONS.md` ADR without explicit +- [x] Add `OBS-006`'s Evidence column pointer in `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` (pointer only). +- [x] Copy every new 🔴 OPEN QUESTION into `PROTOCOL.md` §6 (mandatory, §8). +- [x] **Do not** promote anything to 🟢 FACT or write/amend a `DECISIONS.md` ADR without explicit maintainer sign-off (`AGENTS.md` §6, §15) — propose only, even if this session's result would revise ADR-024. -- [ ] Rename this capture's folder to the actual session date/start-time/end-time. +- [x] Rename this capture's folder to the actual session date/start-time/end-time. --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/captures/CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF/CAP-039-EVENT-NOTES.md - https://tedsluis.github.io/opencontrolpixelbudspro2/captures/CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF/CAP-039-EVENT-NOTES diff --git a/captures/CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF/CAP-039-FINDINGS.md b/captures/CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF/CAP-039-FINDINGS.md index c77cc657..60b5b531 100644 --- a/captures/CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF/CAP-039-FINDINGS.md +++ b/captures/CAP-039-2026-09-06_07-07-17_07-11-43-Group_AF/CAP-039-FINDINGS.md @@ -1,6 +1,6 @@ # Findings: `CAP-039` (Group AF — `Settable-toggles` byte: Set-tap vs. reconnect-Get, fixed dock state, `OBS-006`) -> **Status as of 2026-09-30** (`ai-sessions/0059`, A58-CAP-04 — read this first; the body below is the analysis as written): `03 03 …` is 🟢 "Battery updated" (ADR-031/033); the ~300-frame DLCI 0x02 burst is the official app's `ReadSetting` sweep (`PROTOCOL.md` §6 2026-09-24); Settable: ADR-049. +> **Status as of 2026-09-30** (`ai-sessions/0059`, A58-CAP-04 — read this first; the body below is the analysis as written): `03 03 …` is 🟢 "Battery updated" (ADR-031/033); the 47-frame (Sent) DLCI 0x02 burst is the official app's `ReadSetting` sweep (`PROTOCOL.md` §6 2026-09-24); Settable: ADR-049. Standardized, evidence-based extraction from `CAP-039-btsnoop_hci.log` + `CAP-039-recording.mp4`, staged here for later promotion into `PROTOCOL.md` per `PROJECT_RULES.md` §2. Every claim below @@ -197,13 +197,13 @@ duration. Strengthens, does not promote, the existing HYPOTHESIS. ## 7. Conclusions — awaiting maintainer sign-off for anything beyond factual record -> **Status of the proposals below (2026-09-30, `ai-sessions/0059`, maintainer's choice "Pointer per item"):** the 10-sample Settable fold is superseded by ADR-049; the Option B data point is superseded (Option B 🟢, ADR-031/033); both 🔴 items are in `PROTOCOL.md` §6 (the "~300-frame burst" is the official app's connect-time `ReadSetting` sweep, §6 2026-09-24). +> **Status of the proposals below (2026-09-30, `ai-sessions/0059`, maintainer's choice "Pointer per item"):** the 10-sample Settable fold is superseded by ADR-049; the Option B data point is superseded (Option B 🟢, ADR-031/033); both 🔴 items are in `PROTOCOL.md` §6 (the 47-frame Sent burst is the official app's connect-time `ReadSetting` sweep, §6 2026-09-24). **Confirmed by this session's own evidence (factual record, no promotion needed — restates already-FACT `DECISIONS.md` ADR-024 with new same-session evidence):** - `Settable-toggles=0xe8` on all 10 Set/Get Notify occurrences in a single session with dock state held constant (undocked) — supports ADR-024's dock-state reading as trigger-independent, per - §3. No counter-example found; ADR-024 does not need revisiting. + §3. No counter-example found; ADR-024 did not need revisiting on this session's evidence (ADR-024's "dock state" Decision was later superseded by ADR-049 — status note above). - The originally-documented Event Timeline (single Set at `07:07:23` "Off→Noise cancellation", single Get at `07:08:16`) is factually incorrect on the mode, the trigger count, and (partially) the timestamps — corrected in `CAP-039-EVENT-NOTES.md` with video+wire evidence, per §2. @@ -218,7 +218,7 @@ already-FACT `DECISIONS.md` ADR-024 with new same-session evidence):** **Recorded as open, not resolved (§6 items, copy into `PROTOCOL.md` §6):** - 🔴 What triggers the 5 mid-session disconnect/reconnect cycles (reason `0x16`/`0x13`) with no clearly camera-visible cause for most of them? -- 🔴 The ~300-frame DLCI 0x02 burst's exact purpose remains undecoded (same open item as +- *(answered: the official app's connect-time `ReadSetting` sweep, `PROTOCOL.md` §6 2026-09-24)* The 47-frame (Sent) DLCI 0x02 burst's exact purpose remained undecoded here (same open item as `CAP-036-FINDINGS.md` §4/§11, reconfirmed not-EQ here). --- diff --git a/captures/CAP-040-2026-09-06_07-28-00_07-57-05-Group_AG/CAP-040-EVENT-NOTES.md b/captures/CAP-040-2026-09-06_07-28-00_07-57-05-Group_AG/CAP-040-EVENT-NOTES.md index 5014cb21..fb07c75d 100644 --- a/captures/CAP-040-2026-09-06_07-28-00_07-57-05-Group_AG/CAP-040-EVENT-NOTES.md +++ b/captures/CAP-040-2026-09-06_07-28-00_07-57-05-Group_AG/CAP-040-EVENT-NOTES.md @@ -84,7 +84,7 @@ traffic to CAP-040). - [x] Bluetooth HCI snoop logging enabled and the phone rebooted. - [x] Video recording with a visible wall-clock overlay ready, framed so the on-screen Left/Right/Case battery percentages are legible at every reconnect. -- [x] **Record the starting on-screen battery values** (Left/Right/Case) before the session: Left: 95%, Case: 85%, Right: 100%. *(Note: These values remain exactly the same throughout the entire 24-minute video).* +- [x] **Record the starting on-screen battery values** (Left/Right/Case) before the session: Left: 95%, Case: 85%, Right: 100%. *(Note: These values remain the same through 07:52:10, the first 24 minutes of the 29-minute (1745 s) video; in the tail Left goes to 100% and Case to 84% — `CAP-040-FINDINGS.md` §2.)* - [x] Decide which known-changing value to bracket: Chosen option: **Option B (case dock count/state)**. *Note: User execution deviated from the protocol's 4-state sequence, opting for a 2-state (Both Docked vs Both Undocked) high-repetition loop instead.* @@ -111,13 +111,13 @@ traffic to CAP-040). | Time (local) | Action / Event | On-screen L/R/Case | Test-ID | Evidence in `CAP-040-btsnoop_hci.log` | |---|---|---|---|---| -*Note: Because battery percentages remained static (95/85/100) for the entire duration, the only bracketed variable is the physical Dock State. The user repeated a specific cycle approximately 15 times. The cycle consists of: Auto-Connect via Case Open (Docked) -> App Connect (Undocked) -> App Connect (Docked) -> Case Close.* +*Note: Because battery percentages remained static (95/85/100) through 07:52:10 (not in the video's tail — `CAP-040-FINDINGS.md` §2), the only bracketed variable is the physical Dock State. The user repeated a specific cycle approximately 15 times. The cycle consists of: Auto-Connect via Case Open (Docked) -> App Connect (Undocked) -> App Connect (Docked) -> Case Close.* -| Time (local) | Action / Event | Dock State | Test-ID | Evidence in `CAP-040-btsnoop_hci.log` | +| Time (local) | Action / Event | Dock State (hand-noted — unverified, contradicted by film and wire, see the correction below the table) | Test-ID | Evidence in `CAP-040-btsnoop_hci.log` | |---|---|---|---|---| | `07:28:00` | Start video recording | Unknown | — | — | | `07:28:16` | User navigates to Device Details | — | — | — | -| `07:28:28` | Initial connection established | Undocked | `PRIV-001` | no discrete frame (channel stayed open, see Contamination log correction) | +| `07:28:12` | Initial connection established | Undocked | `PRIV-001` | frame 646 (Connection Complete, 07:28:12.077) | | `07:28:36` | Buds placed in case | Docked | — | — | | `07:28:40` | Tapped 'Disconnect' in App | Docked | — | no discrete frame (channel stayed open, see Contamination log correction) | | `07:28:44` | Tapped 'Connect' in App | Docked | `PRIV-001` | no discrete frame (channel stayed open, see Contamination log correction) | @@ -228,9 +228,16 @@ traffic to CAP-040). | `07:52:09` | Case closed (auto disconnect) | Docked | — | no discrete frame (channel stayed open, see Contamination log correction) | | `07:52:10` | End video recording | — | — | — | +**Correction (2026-10-15, `ai-sessions/0094`) — the buds were not docked between 07:28:17 and 07:53:53.** The "Docked" rows above are hand-noted +and do not hold: (1) DLCI 0x04 "Battery updated" reads `03 03 00 03 5f 64 ff` (Left 95, Right 100, **no charging bit**, ADR-031/033) 36 times, frames +1337 (07:28:17.210) … 3987 (07:43:57.975); the first frame with a charging bit is 5729 (07:53:53.100, `df 64`, Left charging), Right only from 7266 +(07:55:25.504, `64 e4`) (`tshark -Y 'btrfcomm.len>0 and data.data[0:2]==03:03'`); (2) the only Disconnection Complete is 7784 (07:55:41.465) — +no "auto disconnect" on any "Case closed" row (ADR-016); (3) film stills at 07:28:40, 07:39:40 and 07:52:09 show the case empty (open, closed, open) +with both buds lying beside it. The first real docking is ~07:53:53 (Left), see `CAP-040-FINDINGS.md` §2/§4. + **Contamination log:** The execution deviated from the suggested "Option B" protocol in two ways. First, the user did not test intermediate states (e.g., "only Left docked" or "only Right docked"); the test alternated strictly between "both docked" and "both out". Second, the user utilized the Pixel Buds app's "Connect/Disconnect" UI buttons for many of the reconnects, rather than the OS-level Bluetooth toggle. While this provides a huge volume of "Both Docked" vs. "Both Undocked" samples, the reliance on the App UI for reconnection is a slight methodological deviation from earlier baselines like `CAP-036`. -**Wire-evidence correction to the above (found during Phase 1/3 analysis, see `CAP-040-FINDINGS.md` §1 for full evidence):** the deviation turned out to matter more than "slight." The app's own "Connect"/"Disconnect" button taps produce **zero** HCI/RFCOMM-visible traffic of any kind — checked directly (e.g. the `07:29:46`/`07:29:50` Disconnect/Connect pair: no frame at all on `chandle 0x0002` in that window). The classic ACL connection (`chandle 0x0002`) and DLCI 0x08 both stay open continuously from `07:28:12` onward; only DLCI 0x02/0x04 show occasional independent channel bounces, and only **one** genuine HCI `Disconnection Complete` occurs in the entire session — at `07:55:41.465` (reason `0x13`), inside a burst of real DLCI 0x04 `SABM`/`DISC` cycling from `07:54:33`–`07:55:41` that falls **after** this table's documented end (`07:52:10`) and inside the video's own previously-unreviewed tail (video runs to ~`07:57:05`, not `07:52:10` — see Log Metadata). Video frames at `07:52:28`/`07:56:20`/`07:57:00` confirm: still connected and undocked-then-redocked through most of the documented table, then genuinely disconnected (app shows "Connect", not "Disconnect") from `07:56:20` onward, matching the real `07:55:41` teardown. **Practical consequence: almost none of this table's ~30 individual "Tapped Disconnect/Connect"/"Case closed/opened" rows correspond to a distinguishable wire event** — the per-row frame-number blanks below are intentionally left as "no discrete frame (channel stayed open)" rather than filled with a misleading frame number; the one section of this session that *does* carry real, wire-confirmed reconnect activity is the `07:54:28`–`07:55:41` cluster near the end, addressed in full in `CAP-040-FINDINGS.md`. +**Wire-evidence correction to the above (found during Phase 1/3 analysis, see `CAP-040-FINDINGS.md` §1 for full evidence):** the deviation turned out to matter more than "slight." The app's own "Connect"/"Disconnect" button taps produce **zero** HCI/RFCOMM-visible traffic of any kind — checked directly (e.g. the `07:29:46`/`07:29:50` Disconnect/Connect pair: no frame at all on `chandle 0x0002` in that window). The classic ACL connection (`chandle 0x0002`) and DLCI 0x08 both stay open continuously from `07:28:12` onward; only DLCI 0x02/0x04 show occasional independent channel bounces, and only **one** genuine HCI `Disconnection Complete` occurs in the entire session — at `07:55:41.465` (reason `0x13`), inside a burst of real DLCI 0x04 `SABM`/`DISC` cycling from `07:54:33`–`07:55:41` that falls **after** this table's documented end (`07:52:10`) and inside the video's own previously-unreviewed tail (video runs to ~`07:57:05`, not `07:52:10` — see Log Metadata). Video frames at `07:52:28`/`07:56:20`/`07:57:00` confirm: still connected and undocked-then-redocked through most of the documented table, then genuinely disconnected (app shows "Connect", not "Disconnect") from `07:56:20` onward, matching the real `07:55:41` teardown. **Practical consequence: almost none of this table's ~80 (81) individual "Tapped Disconnect/Connect"/"Case closed/opened" rows correspond to a distinguishable wire event** — the per-row frame-number blanks below are intentionally left as "no discrete frame (channel stayed open)" rather than filled with a misleading frame number; the one section of this session that *does* carry real, wire-confirmed reconnect activity is the `07:54:28`–`07:55:41` cluster near the end, addressed in full in `CAP-040-FINDINGS.md`. ## Decode / Analysis @@ -239,7 +246,7 @@ tshark -r CAP-040-btsnoop_hci.log -Y "bthci_acl.chandle== and btrf -T fields -e frame.number -e frame.time -e frame.p2p_dir -e data.data ``` -- [ ] **For each of the 6 unmapped codes (`05 0c`, `04 02`, `04 04`, `04 11`, `04 13`, `04 15`, +- [x] **For each of the 7 unmapped codes (`05 0c`, `04 02`, `04 04`, `04 11`, `04 13`, `04 15`, `0e 04`), find the `Sent` Get frame and the immediately-following `Rcvd` frame(s) on the same Group** (matching Group number, not necessarily the same Code — the response Code may differ, as it does for DLCI 0x04's own Get/Notify pair using Codes `0x11`/`0x13`). Record the @@ -251,7 +258,7 @@ tshark -r CAP-040-btsnoop_hci.log -Y "bthci_acl.chandle== and btrf opens/closes in the entire 30-minute session (confirmed via `rfcomm.frametype`/`_ws.col.Info` filtered to `Channel=4`: one `SABM`→`UA` at session start, no `DISC` until the log ends) — see `CAP-040-FINDINGS.md` §1/§3 for the full command+hex.** -- [ ] **Per repeat/bracket, decode each response's numeric fields** (protobuf-shaped tag/varint +- [x] **Per repeat/bracket, decode each response's numeric fields** (protobuf-shaped tag/varint parsing, per `PROTOCOL.md` §2.2a's method) and check whether any field's value **changes** in step with the known bracketed value (on-screen battery, or which component is docked). A field that stays constant across all repeats is not this value; a field that tracks the @@ -266,7 +273,7 @@ tshark -r CAP-040-btsnoop_hci.log -Y "bthci_acl.chandle== and btrf `100` from `07:54:29` onward — exactly coinciding with the video-confirmed real docking event in the previously-unreviewed tail. See `CAP-040-FINDINGS.md` §4 for the full table/hex — this is a new 🟡 HYPOTHESIS, not a FACT promotion.** -- [ ] **Outcome classification: (c) inconclusive for the originally-designed bracketing method** — +- [x] **Outcome classification: (c) inconclusive for the originally-designed bracketing method** — the procedure's own deviation (App Connect/Disconnect buttons instead of a real disconnect) meant the "~15 repeats" never actually re-triggered DLCI 0x08's channel at all, so the 7 flagged codes could not be correlated against the dock-state bracket as intended. This is a @@ -293,15 +300,15 @@ tshark -r CAP-040-btsnoop_hci.log -Y "bthci_acl.chandle== and btrf ## Next steps after filling this in -- [ ] Cross-reference every Test-ID this Group is supposed to exercise (`AGENTS.md` §13). -- [ ] Write `CAP-040-FINDINGS.md` per `PROJECT_RULES.md` §2, hex & script rule (§1 rule 4a). -- [ ] Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index (status → `captured`/`analyzed`) and +- [x] Cross-reference every Test-ID this Group is supposed to exercise (`AGENTS.md` §13). +- [x] Write `CAP-040-FINDINGS.md` per `PROJECT_RULES.md` §2, hex & script rule (§1 rule 4a). +- [x] Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index (status → `captured`/`analyzed`) and `id_registry.csv`'s `CAP-040` row. -- [ ] Add `PRIV-001`'s Evidence column pointer in `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` (pointer only). -- [ ] Copy every new 🔴 OPEN QUESTION into `PROTOCOL.md` §6 (mandatory, §8). -- [ ] **Do not** promote anything to 🟢 FACT or write a `DECISIONS.md` ADR without explicit +- [x] Add `PRIV-001`'s Evidence column pointer in `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` (pointer only). +- [x] Copy every new 🔴 OPEN QUESTION into `PROTOCOL.md` §6 (mandatory, §8). +- [x] **Do not** promote anything to 🟢 FACT or write a `DECISIONS.md` ADR without explicit maintainer sign-off (`AGENTS.md` §6, §15) — propose only. -- [ ] Rename this capture's folder to the actual session date/start-time/end-time. +- [x] Rename this capture's folder to the actual session date/start-time/end-time. ## Traceability check (2026-10-03, `ai-sessions/0069`, `A68-CAP-15`, `AGENTS.md` §13 step 7) diff --git a/captures/CAP-040-2026-09-06_07-28-00_07-57-05-Group_AG/CAP-040-FINDINGS.md b/captures/CAP-040-2026-09-06_07-28-00_07-57-05-Group_AG/CAP-040-FINDINGS.md index d1365d7f..0af65234 100644 --- a/captures/CAP-040-2026-09-06_07-28-00_07-57-05-Group_AG/CAP-040-FINDINGS.md +++ b/captures/CAP-040-2026-09-06_07-28-00_07-57-05-Group_AG/CAP-040-FINDINGS.md @@ -123,7 +123,9 @@ $ tshark -r CAP-040-btsnoop_hci.log -Y "bthci_acl.chandle==0x0002" -T fields -e ``` $ tshark -r CAP-040-btsnoop_hci.log -Y "bthci_acl.chandle==0x0002 and frame.time >= \"2026-09-06 07:29:44\" and frame.time <= \"2026-09-06 07:29:53\"" \ -T fields -e frame.number -e frame.time -e frame.p2p_dir -e _ws.col.Info -(0 rows) +(0 rows, exit 0) +$ # positive control: the same filter over 07:28:10–07:28:14 +648 … 1109 ``` **Conclusion:** the official app's own in-app "Connect"/"Disconnect" buttons, as tapped in this @@ -132,7 +134,7 @@ link and DLCI 0x08 stay open continuously from `07:28:12` through `07:55:41`. Wh "Disconnect"/"Connect" button pair actually does (a local UI-state toggle, or a change scoped to a higher layer this project hasn't identified), it is not visible at the RFCOMM/HCI level in this session. **This invalidates the ~15-repeat bracketing this session's own procedure intended**: the -documented Event Timeline's ~30 "Tapped Disconnect/Connect in App" and "Case closed/opened (auto)" +documented Event Timeline's ~80 (81) "Tapped Disconnect/Connect in App" and "Case closed/opened (auto)" rows between `07:28:44` and `07:52:09` correspond to **zero** distinguishable wire events for the vast majority of them — see `CAP-040-EVENT-NOTES.md`'s corrected Contamination log for the full detail. The one place in this session with genuine, wire-confirmed channel/connection activity is @@ -161,6 +163,14 @@ same throughout the entire 24-minute video"): true for the documented 24-minute not for the video as a whole — Left and Case both change in the final, previously-unreviewed ~5 minutes. +**Correction (2026-10-15, `ai-sessions/0094`) — the buds were not docked before ~07:53:53, so the missing wire signal is not only the app buttons.** +DLCI 0x04 "Battery updated" reads `03 03 00 03 5f 64 ff` (no charging bit) 36 times, frames 1337 (07:28:17.210) … 3987 (07:43:57.975); the +first charging bit is frame 5729 (07:53:53.100, `df 64`, Left), Right from 7266 (07:55:25.504) (`tshark -r CAP-040-btsnoop_hci.log -Y +'btrfcomm.len>0 and data.data[0:2]==03:03'`); the only Disconnection Complete is 7784 (07:55:41.465). Film stills at 07:28:40, 07:39:40 and +07:52:09 show the case empty with both buds beside it. The Event Timeline's "Buds placed in case | Docked" / "Case closed (auto disconnect)" +rows (07:28:36–07:52:09) are therefore not real docking events: with the buds outside the case, case open/close produces no disconnect. +*(corrected 2026-10-15, `ai-sessions/0094`)* + ## 3. `PRIV-001`'s central question: the 7 flagged unmapped codes fire exactly once (🟢 FACT for the observation; inconclusive for the correlation test) ``` @@ -211,7 +221,7 @@ $ awk -F'|' '{print substr($4,1,8)}' dlci08_all.tsv | sort | uniq -c | sort -rn 1 050c0000 <- (one of the 7 flagged codes, bundled with the other 6 in frame 980) ``` -## 4. Bonus finding: DLCI 0x08's Left-earbud battery field reports the `0xff` sentinel at the exact moment of physical docking (🟡 HYPOTHESIS, new, single occurrence) +## 4. Bonus finding: the Left-earbud battery field reports the `0xff` sentinel during the physical docking (🟡 HYPOTHESIS; on DLCI 0x04 and DLCI 0x08, not a single occurrence — re-timed 2026-10-15, see the note at the end of this section) Decoding every `Group 0x0e Code 0x01` occurrence (`ADR-014`'s already-FACT `[value, flag, index]` triple, `index=1`→Left/`2`→Right/`3`→Case) across the whole session: @@ -246,6 +256,12 @@ the point of physical case contact (sensor/contact-read gap), before settling to like a charging-state placeholder (`100`) rather than a fresh percentage reading. Single occurrence, one session — not proposed for promotion. +**Re-timing (2026-10-15, `ai-sessions/0094`):** the docking starts earlier and the sentinel is not single-channel. DLCI 0x04 "Battery updated" +(`tshark -Y 'btrfcomm.len>0 and data.data[0:2]==03:03'`) already reports Left charging at 07:53:53.100 (frames 5729…5750, `03 03 00 03 df 64 ff`), +Left `0xff` with the charging bit at 07:54:14.214 (6078/6079/6083, `ff 64`) — 14 s before frame 6243 — again `df 64` at 07:54:28.307 (6224–6226) and +`ff 64` at 07:54:28.337 (6240/6246), then `e4 64` (100 %, charging) from 07:54:29.614 (6271). The physical docking of the Left bud is therefore +~07:53:53 (first charging bit), and the `0xff` reading occurs on both channels and more than once. 🟡 unchanged as a reading; the timing above is 🟢 FACT for the frames. + ## 5. Test-ID traceability (`AGENTS.md` §13) - **`PRIV-001`** (primary): exercised, but the session's own procedure deviation (§1) limited the diff --git a/captures/CAP-041-2026-09-06_17-10-39_17-17-48-Group_AH/CAP-041-EVENT-NOTES.md b/captures/CAP-041-2026-09-06_17-10-39_17-17-48-Group_AH/CAP-041-EVENT-NOTES.md index b1ee9163..07277d71 100644 --- a/captures/CAP-041-2026-09-06_17-10-39_17-17-48-Group_AH/CAP-041-EVENT-NOTES.md +++ b/captures/CAP-041-2026-09-06_17-10-39_17-17-48-Group_AH/CAP-041-EVENT-NOTES.md @@ -1,5 +1,7 @@ # Event Notes: Pixel Buds Pro 2 (`libmaestro` / `libgfps`) — Group AH, DLCI 0x02's connect-time RPC burst vs. non-default settings state (`CAP-041`) +> **Status as of 2026-10-03** (as in `CAP-041-FINDINGS.md` — read this first; the text below is as written): the phone's **requests** in the DLCI 0x02 connect burst do not vary with the settings, but the Buds' **answers** in the same burst carry every setting (the official app's `ReadSetting` sweep — `CAP-041-FINDINGS.md` §3, frames 799 … 3279; the content diff is §8). The 79-valued sub-message is `SubscribeRuntimeInfo` entry 6.1 = Case % (Option F, ADR-043). The "clean negative" and the two related 🔴 items below are answered by that. + **Status:** ✅ **Captured, video-reviewed, and decoded — see `CAP-041-FINDINGS.md`.** The session produced 3 wire-confirmed reconnects (not the 4 originally planned) against 3 genuinely distinct, non-default settings states; the DLCI 0x02 connect-time burst's length/shape signature was found @@ -44,7 +46,7 @@ touch nothing else. The reconnect itself is the only in-session action. | Date | 2026-09-06 | | Firmware version | 🟢 **FACT** `release_5.203` (Confirmed on screen at 17:11:32) | | Test device | Pixel 7a, Android version ⚪ not recorded in this session (this file said "14"; the same phone is recorded as 17 in the captures before and after — unreconciled, `ai-sessions/0069` `A68-CAP-23`). **Official Pixel Buds Companion App, Google Play Services enabled** | -| Video file | `CAP-041-recording.mp4` — `17:10:39`–`17:17:48` local time | +| Video file | `CAP-041-recording.mp4` — 421.23s (`ffprobe`), `17:10:39`–`17:17:40` local time (last frame's overlay; the folder name's `17-17-48` is the draft value) | | Log file | `CAP-041-btsnoop_hci.log` | | Buds MAC (partial, per `AGENTS.md` §7/§9) | `04:00:6e:cf:6e:07` | @@ -119,7 +121,7 @@ Untruncated, raw-path extraction. Video (`ffprobe`): 421.23s, wall-clock overlay | `17:16:55` | **Window 4 start** — Bluetooth toggled OFF | User (OS) | `OBS-007` | — | | `17:17:00` | Bluetooth toggled ON | User (OS) | `OBS-007` | — | | `17:17:05` | Connection established (Window 4 burst) | App/OS | `OBS-007` | — | -| `17:17:48` | End video recording | — | — | — | +| `17:17:40` | End video recording (last frame's overlay; `ffprobe` 421.23 s) | — | — | — | **🟢 FACT — video re-pass correction (`ffmpeg` frame extraction at t=0,46,66,72,80,95,101,163,224,277,286,300s):** the claimed per-window BT-toggle/connect timestamps above are placeholders and do **not** match @@ -150,7 +152,7 @@ independently-known settings states from the Event Timeline's own settings-chang claimed window count. **Contamination log:** The ⚠️ Rule was completely violated. The user did not set the non-default values *before* the recording. Instead, the user actively navigated the app and changed settings *during* the recording. Furthermore, instead of one single idle reconnect window, the user performed four separate Bluetooth toggles (reconnects), changing the Equalizer, Touch Controls, Mono Audio, and Usage & Diagnostics settings between each reconnect. -**Positive aspect:** While this violates the single-variable test protocol, it provides an excellent multi-bracket dataset: we now have four distinct connect-time bursts, each with a known, different settings state. App navigation during the burst window might generate concurrent DLCI 0x02 traffic, which will need to be carefully separated from the automatic read-back burst during decoding. +**Positive aspect:** While this violates the single-variable test protocol, it provides an excellent multi-bracket dataset: we now have three distinct connect-time bursts (three wire-confirmed reconnects, see the correction above), each with a known, different settings state. App navigation during the burst window might generate concurrent DLCI 0x02 traffic, which will need to be carefully separated from the automatic read-back burst during decoding. ## Decode / Analysis @@ -192,7 +194,7 @@ into one RFCOMM I-frame — `DESKRESEARCH_FINDINGS.md`'s 2026-08-28 entry, `CAP- value never changes across this session, this is *consistent with* but does **not confirm** the field tracking Case battery — recorded as a strengthening data point for `CAP-036-FINDINGS.md` §12.6's existing 🔴 OPEN QUESTION, not a resolution. -- [x] **Outcome classification: (b) — clean negative** at the length/shape level: this session's +- [x] **Outcome classification (as written): (b) — clean negative** at the length/shape level *(superseded 2026-10-03: the Buds' answers in the same burst carry every setting — `CAP-041-FINDINGS.md` §3/§6)*: this session's three genuinely different settings states (touch controls off + 3 different EQ configs + mono on; +per-earbud touch off + different EQ; +per-earbud touch=Digital assistant/Adaptive+different EQ+diagnostics off) all produce a connect-time DLCI 0x02 burst @@ -207,29 +209,29 @@ into one RFCOMM I-frame — `DESKRESEARCH_FINDINGS.md`'s 2026-08-28 entry, `CAP- - 🔴 Which of the four claimed BT-toggle actions in this session's procedure notes did **not** produce a distinct new ACL connection on the wire (only 3 Connection-Complete events exist for 4 claimed windows) — not resolved by the available video frames this pass. -- 🔴 `CAP-036-FINDINGS.md` §12.6's open question (does the DLCI 0x02 periodic push's 2-field, +- 🟢 *(answered: `SubscribeRuntimeInfo` entry 6.1 = Case %, Option F, ADR-043 — `CAP-041-FINDINGS.md` §6)* `CAP-036-FINDINGS.md` §12.6's open question (does the DLCI 0x02 periodic push's 2-field, non-1/2/3-indexed sub-message track battery state?) is neither resolved nor contradicted by this session — the candidate field stayed constant at a value matching on-screen Case% (79) the whole session, which is consistent with, but doesn't prove, that reading. -- 🔴 Whether the connect-time burst's payload *content* (not just its length signature) differs +- 🟢 *(answered: it does — the Buds' `ReadSetting` answers differ per window, `CAP-041-FINDINGS.md` §3/§8)* Whether the connect-time burst's payload *content* (not just its length signature) differs with settings state remains untested — a full byte-for-byte HDLC-unescape+CRC-verify decode of every subframe across this session's 3 windows and `CAP-036`'s baseline was out of this pass's budget; see `CAP-041-FINDINGS.md`'s Recommended next steps. ## Next steps after filling this in -- [ ] Cross-reference every Test-ID this Group is supposed to exercise (`AGENTS.md` §13). -- [ ] Write `CAP-041-FINDINGS.md` per `PROJECT_RULES.md` §2, hex & script rule (§1 rule 4a). -- [ ] Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index (status → `captured`/`analyzed`) and +- [x] Cross-reference every Test-ID this Group is supposed to exercise (`AGENTS.md` §13). +- [x] Write `CAP-041-FINDINGS.md` per `PROJECT_RULES.md` §2, hex & script rule (§1 rule 4a). +- [x] Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index (status → `captured`/`analyzed`) and `id_registry.csv`'s `CAP-041` row. -- [ ] Add `OBS-007`'s Evidence column pointer in `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` (pointer only). -- [ ] Copy every new 🔴 OPEN QUESTION into `PROTOCOL.md` §6 (mandatory, §8). -- [ ] **Do not** promote anything to 🟢 FACT or write a `DECISIONS.md` ADR without explicit +- [x] Add `OBS-007`'s Evidence column pointer in `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` (pointer only). +- [x] Copy every new 🔴 OPEN QUESTION into `PROTOCOL.md` §6 (mandatory, §8). +- [x] **Do not** promote anything to 🟢 FACT or write a `DECISIONS.md` ADR without explicit maintainer sign-off (`AGENTS.md` §6, §15) — propose only, even for a positive finding this directly relevant to `ARCHITECTURE.md` §3.1. - [ ] Remember to set EQ/touch-controls back to their normal values after this session, if desired — this is a deliberate one-off non-default state for this capture only. -- [ ] Rename this capture's folder to the actual session date/start-time/end-time. +- [x] Rename this capture's folder to the actual session date/start-time/end-time. ## Traceability check (2026-10-03, `ai-sessions/0069`, `A68-CAP-15`, `AGENTS.md` §13 step 7) diff --git a/captures/CAP-041-2026-09-06_17-10-39_17-17-48-Group_AH/CAP-041-FINDINGS.md b/captures/CAP-041-2026-09-06_17-10-39_17-17-48-Group_AH/CAP-041-FINDINGS.md index 4034f23b..98483db3 100644 --- a/captures/CAP-041-2026-09-06_17-10-39_17-17-48-Group_AH/CAP-041-FINDINGS.md +++ b/captures/CAP-041-2026-09-06_17-10-39_17-17-48-Group_AH/CAP-041-FINDINGS.md @@ -133,8 +133,11 @@ across all four. The only positional differences are in the early header frames' don't reproduce at the same position) — none of these differences are large enough, or shaped like a known settings payload (see §3), to read as settings-content. -**Settings states compared** (from the Event Timeline, all four genuinely different, none matching -`CAP-036`'s all-defaults baseline): +**Settings states compared** (from the Event Timeline as hand-noted, all four genuinely different, none matching +`CAP-036`'s all-defaults baseline). *Note 2026-10-15 (`ai-sessions/0094`): the per-window states below are the hand-noted ones and are not +confirmed by the wire — §3's decoded answers are the record: Windows A and B return the same EQ `[6.00 × 5]` (871/2010) although an EQ change is +logged between them, A's all-equal EQ does not match "Bass high, Treble low", and Window C returns mono `0` (3252) and Volume EQ `0` (3239) though +neither change is logged. Treat the timeline's settings rows as unverified.* - `CAP-036` (baseline): every setting at default (EQ centered, touch controls on). - Window A: touch controls fully off, one non-default EQ config, mono audio on. - Window B: + both earbuds' touch customization off, a different EQ config. @@ -161,7 +164,7 @@ CAP-041-btsnoop_hci.log` (pw_hdlc + pw_rpc, CRC-32 checked; 96 `ReadSetting` req asking, not by the Buds volunteering. (Rewritten in place 2026-10-03, `ai-sessions/0069`, A68-CAP-18: this section and §8 used to conclude "no settings read-back of any kind" from the request side alone.) -## 4. Bonus/incidental finding: the DLCI 0x02 periodic push's constant field matches on-screen Case% (🟡 HYPOTHESIS, strengthens but does not resolve `CAP-036-FINDINGS.md` §12.6's open question) +## 4. Bonus/incidental finding: the DLCI 0x02 periodic push's constant field matches on-screen Case% (🟡 HYPOTHESIS as written, strengthens but does not resolve `CAP-036-FINDINGS.md` §12.6's open question — answered since: Option F, ADR-043, see §6) Within each window's connect-time burst (not a separate later push — this occurs ~4s after channel-open, still inside the burst window itself), a recurring sub-message structurally identical diff --git a/captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-EVENT-NOTES.md b/captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-EVENT-NOTES.md index 93ea116c..90c75b6d 100644 --- a/captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-EVENT-NOTES.md +++ b/captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-EVENT-NOTES.md @@ -1,5 +1,7 @@ # Event Notes: Pixel Buds Pro 2 (`libmaestro` / `libgfps`) — Group AI, long pure-idle bracket for the periodic DLCI 0x02/0x04/0x08/HFP push cadence (`CAP-042`) +> **Status as of 2026-09-30** (as in `CAP-042-FINDINGS.md`): Settable: ADR-049; the two idle DLCI 0x02 pushes are `maestro_pw.Dosimeter/SubscribeToLiveDb` stream packets (4518 …, 7627 …), not the runtime-info stream of `CAP-036`'s push (its only packet here is in the connect burst, frame 800). The text below is as written. + **Status:** ✅ **Captured and decoded — see `CAP-042-FINDINGS.md`.** Central result: after the initial connect-settling burst, the DLCI 0x02/0x04/0x08 push recurs only twice in the whole ~37m39s session (~16m and ~19m apart), far sparser than `CAP-036`'s short-session sample, and @@ -46,7 +48,7 @@ genuinely idle — a single accidental tap invalidates the whole point. | Group(s) | AI (`OBS-002` — long idle bracket for background/battery-polling behavior) | | Date | 2026-09-06 | | Firmware version | ⚪ ASSUMPTION `release_5.203` | -| Test device | Pixel 7a, Android ⚪ ASSUMPTION (carried from `CAP-036`, not visible this session — screen off throughout). **Official Pixel Buds Companion App (backgrounded), Google Play Services enabled (⚪ ASSUMPTION, not re-verified)** | +| Test device | Pixel 7a, Android ⚪ not recorded (17 assumed, as in neighbouring captures; `CAP-036` did not record it either — not visible this session, screen off throughout). **Official Pixel Buds Companion App (backgrounded), Google Play Services enabled (⚪ ASSUMPTION, not re-verified)** | | Video file | `CAP-042-recording.mp4` — 2119.65s (~35m20s), 17:29:53–~18:05:13 local (wall-clock overlay); two spot-check frames (start ~17:29:53, mid ~18:04:48) confirm an undisturbed scene: buds resting loose beside the open, empty case, phone screen off | | Log file | `CAP-042-btsnoop_hci.log` — 2258.87s (~37m39s), 8,560 packets, 2026-09-06 17:30:02.871–18:07:41.746 local time (see §0 of `CAP-042-FINDINGS.md` — this is the MAIN log only; `.log.last` is leftover `CAP-041`-session content, not part of this session — do not merge) | | Buds MAC (partial, per `AGENTS.md` §7/§9) | `04:00:6e:cf:6e:07` | @@ -67,7 +69,7 @@ is fully and cleanly contained in the main log alone (starts fresh at frame 1, 1 - [x] Google Play Services **enabled** — carried `CAP-036`'s verification forward as ⚪ ASSUMPTION. - [x] No third-party BLE/GATT tool used. - [x] Bluetooth HCI snoop logging enabled and the phone rebooted. -- [x] **Fixed dock state: undocked/worn** (both earbuds resting loose beside the open, empty case — video-confirmed at start and mid-session; wire-confirmed via `Settable-toggles=0xe8` per `DECISIONS.md` ADR-024, see `CAP-042-FINDINGS.md` §1). Held constant for the entire session. +- [x] **Fixed dock state: undocked, not worn** (both earbuds resting loose beside the open, empty case — video-confirmed at start and mid-session; wire-confirmed via `Settable-toggles=0xe8` per `DECISIONS.md` ADR-024, see `CAP-042-FINDINGS.md` §1). Held constant for the entire session. - [x] Video recording with a visible wall-clock overlay ready — a static framing with periodic implicit confirmation (long single take, no edits) rather than per-minute stills. - [x] Session ran ~37m39s of idle time after the initial connect settled — well over the 15-minute minimum. @@ -154,17 +156,17 @@ tshark -r CAP-042-btsnoop_hci.log -Y "btrfcomm.len>0" -T fields -e frame.number ## Next steps after filling this in -- [ ] Cross-reference every Test-ID this Group is supposed to exercise (`AGENTS.md` §13). -- [ ] Write `CAP-042-FINDINGS.md` per `PROJECT_RULES.md` §2, hex & script rule (§1 rule 4a). -- [ ] Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index (status → `captured`/`analyzed`) and +- [x] Cross-reference every Test-ID this Group is supposed to exercise (`AGENTS.md` §13). +- [x] Write `CAP-042-FINDINGS.md` per `PROJECT_RULES.md` §2, hex & script rule (§1 rule 4a). +- [x] Update `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9 Capture Index (status → `captured`/`analyzed`) and `id_registry.csv`'s `CAP-042` row. -- [ ] Update `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s `OBS-002` row — this is its first dedicated +- [x] Update `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`'s `OBS-002` row — this is its first dedicated capture scenario; update the "Capture scenario(s)" column accordingly (pointer only, no restated finding). -- [ ] Copy every new 🔴 OPEN QUESTION into `PROTOCOL.md` §6 (mandatory, §8). -- [ ] **Do not** promote anything to 🟢 FACT or write a `DECISIONS.md` ADR without explicit +- [x] Copy every new 🔴 OPEN QUESTION into `PROTOCOL.md` §6 (mandatory, §8). +- [x] **Do not** promote anything to 🟢 FACT or write a `DECISIONS.md` ADR without explicit maintainer sign-off (`AGENTS.md` §6, §15) — propose only. -- [ ] Rename this capture's folder to the actual session date/start-time/end-time. +- [x] Rename this capture's folder to the actual session date/start-time/end-time. --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-EVENT-NOTES.md - https://tedsluis.github.io/opencontrolpixelbudspro2/captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-EVENT-NOTES diff --git a/captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-FINDINGS.md b/captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-FINDINGS.md index 140831d8..2de93861 100644 --- a/captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-FINDINGS.md +++ b/captures/CAP-042-2026-09-06_17-30-02_18-07-42-Group_AI/CAP-042-FINDINGS.md @@ -12,7 +12,7 @@ staged here for later promotion into `PROTOCOL.md` per `PROJECT_RULES.md` §2. S **Capture ID:** `CAP-042` · **Date:** 2026-09-06 · **Firmware:** ⚪ ASSUMPTION `release_5.203` (carried over, not re-checked on screen this session — screen stayed off). **Phone:** Pixel 7a, -Android ⚪ ASSUMPTION (carried from `CAP-036`, not visible this session). **App:** official Pixel +Android ⚪ not recorded (17 assumed, as in neighbouring captures; `CAP-036` did not record it either — not visible this session). **App:** official Pixel Buds Companion App, connected but **backgrounded** (not kept open — this is the deliberate difference from `CAP-036`'s open-app baseline). **Google Play Services:** ⚪ ASSUMPTION enabled (carried forward, not re-verified). **Buds MAC (partial):** `04:00:6e:cf:6e:07`. @@ -164,9 +164,11 @@ absence, not a filtering artifact (re-checked with a plain `contains "BIEV"` gre ## 3. Cross-channel timing — near-lockstep holds *within* each push cluster, but HFP drops out entirely (🟡 HYPOTHESIS, single session) +> *Note (2026-10-15, `ai-sessions/0094`): the DLCI 0x02 packets in Push #1/#2 are `Dosimeter/SubscribeToLiveDb` stream packets (4518 … 4582, 7627 … 7646, `scripts/pwrpc_decode.py`), not the `SubscribeRuntimeInfo` stream that `CAP-036`'s push is (`CAP-036-FINDINGS.md` §12.6) — so the DLCI 0x02 column below is not the same push as `CAP-036` §12.5's; see the status banner.* + | Event | DLCI 0x02 | DLCI 0x04 | DLCI 0x08 | HFP `AT+BIEV` | |---|---|---|---|---| -| Connect burst | 3.403–6.526s | 2.596–6.53s (approx, spans SLC+Info) | 2.680–4.346s | 2.264s, 2.343s | +| Connect burst | 3.403–6.526s | 2.345–6.53s (approx, spans SLC+Info; Get frame 598 at 2.345s) | 2.680–4.346s | 2.264s, 2.343s | | Push #1 | 974.056–977.146s | 974.086s, 977.378s | 974.084s, 977.350s | **absent** | | Push #2 | 2101.353–2103.412s | 2101.036s, 2104.154s | 2101.036s, 2104.301s | **absent** | @@ -204,6 +206,8 @@ substantially longer than 14.6 minutes without firing. This does **not** contrad ## 5. Candidate explanation, flagged not claimed: app-foreground state, not connection-supervision (🔴 OPEN QUESTION) +> *Note (2026-10-15, `ai-sessions/0094`): the comparison below treats this session's idle DLCI 0x02 packets as `CAP-036`'s push; they are `Dosimeter/SubscribeToLiveDb` packets, not `SubscribeRuntimeInfo` (§3 note, status banner), so the DLCI 0x02 part of the comparison does not hold as written.* + `CAP-036-FINDINGS.md` §12.5 speculated the near-lockstep push's shared trigger is "plausibly Buds-side, e.g. a periodic connection-supervision tick." That session ran with the official app **open on Device details for the entire session**. This session deliberately ran with the app diff --git a/captures/CAP-043-2026-09-13_09-50-31_09-53-41-Group_Q/CAP-043-EVENT-NOTES.md b/captures/CAP-043-2026-09-13_09-50-31_09-53-41-Group_Q/CAP-043-EVENT-NOTES.md index 3d86b25f..8c157bd1 100644 --- a/captures/CAP-043-2026-09-13_09-50-31_09-53-41-Group_Q/CAP-043-EVENT-NOTES.md +++ b/captures/CAP-043-2026-09-13_09-50-31_09-53-41-Group_Q/CAP-043-EVENT-NOTES.md @@ -24,7 +24,7 @@ non-match worth re-examining the hypothesis itself). | Group(s) | Q (repeat) | | Date | 2026-09-13 | | Firmware version | `release_5.203` (⚪ ASSUMPTION, maintainer-supplied session context — not wire-verifiable this session, zero DLCI 0x08/classic traffic exists) | -| Test device | Pixel 7a, Android 17 (🟢 FACT, MP4 container tag); official Pixel Buds Companion App force-stopped; app version `1.0.955078536`, Google Play services active (⚪ ASSUMPTION, maintainer-supplied context) | +| Test device | Pixel 7a, Android 17 (⚪ ASSUMPTION — the MP4 container tag is the camera phone's, not the test phone's; `CAP-043-FINDINGS.md` §1, corrected 2026-10-03) *(corrected 2026-10-15, `ai-sessions/0094`)*; official Pixel Buds Companion App force-stopped; app version `1.0.955078536`, Google Play services active (⚪ ASSUMPTION, maintainer-supplied context) | | Video file | `CAP-043-recording.mp4` — 189.897s, 09:50:31–09:53:41 local (burned-in overlay + `ffprobe`-cross-checked) | | Log file | `CAP-043-btsnoop_hci.log` — 1,572 packets, 306.895s, 09:50:27.996–09:55:34.891 local | | Buds MAC (partial, per `AGENTS.md` §7/§9) | Once, inside a controller vendor command (frame 91); never in a connection (see Isolation check below) | @@ -62,7 +62,8 @@ Step D/E. |---|---|---|---|---| | 09:50:27.996 | Log's only Bluetooth-adapter `Reset` (log t=0). Not necessarily the same moment as the video's own displayed toggle transition — see `CAP-043-FINDINGS.md` §2's timing note (unresolved, non-blocking, ~3s earlier than the video's own t=0). | System | — | Log frame 1 | | 09:50:31 (video t=0) | Video starts. Both buds are in the closed case (visible on-camera, resting on the phone). Bluetooth settings screen shows "Bluetooth is off," toggle in off position. App confirmed force-stopped off-camera prior to recording. | User | `BATT-002`, `BATT-003` | Video frame at t=0 | -| ~09:50:29–09:50:36 (video t≈5s) | Bluetooth toggled ON; bonded "Pixel Buds Pro 2 van Ted" entry appears in the settings list showing a static `L: 100%, C: 84%, R: 100%` battery line (present already at first appearance — see `CAP-043-FINDINGS.md` §4 on this being a cached, not live, reading). First own-Buds Fast Pair (`0xFE2C`) BLE advertisement in the log at frame 133 (log t=1.06s, absolute 09:50:29.06). | User (OS) / Buds (auto) | `BATT-002`, `BATT-003` | Video ~t=5s frame; log frame 133 | +| 09:50:29.06 (log; before video t=0) | First own-Buds Fast Pair (`0xFE2C`) BLE advertisement in the log, frame 133 (log t=1.06s) — 2 s before the video starts, while the video's first frame still shows "Bluetooth is off" (see `CAP-043-FINDINGS.md` §2's video/log timing note) | Buds (auto) | `BATT-002`, `BATT-003` | log frame 133 | +| ~09:50:36 (video t≈5s) | Bluetooth toggled ON; bonded "Pixel Buds Pro 2 van Ted" entry appears in the settings list showing a static `L: 100%, C: 84%, R: 100%` battery line (present already at first appearance — see `CAP-043-FINDINGS.md` §4 on this being a cached, not live, reading). | User (OS) | `BATT-002`, `BATT-003` | Video ~t=5s frame | | 09:50:31–09:53:41 | Passive observation window. The case remains closed and untouched; the system Bluetooth settings panel stays open and static the entire time (screen never navigates away, no app opens); the "2 apps are active" indicator stays constant throughout (a single 5s-sampled thumbnail at t=115s misread as "3" on first pass — a denser 2fps re-check confirms "2" at every sample in that window, not a real transition). **Isolation confirmed:** zero classic RFCOMM/SDP/connection traffic to the Buds' address anywhere in the log (see Isolation check above). | — | `BATT-002`, `BATT-003` | Window duration: 189.9s (video); 60 own-Buds `0xFE2C` advertisements logged across the full 306.9s log, frames 133–1548, byte-identical payload throughout — see `CAP-043-FINDINGS.md` §3 | | 09:53:41 | Video ends. | — | — | Video's final frame | | 09:55:34.891 | Log ends (continues ~113s past the video's own end, with no further notable activity — logging simply stopped later than recording). | — | — | Log's last frame | diff --git a/captures/CAP-043-2026-09-13_09-50-31_09-53-41-Group_Q/CAP-043-FINDINGS.md b/captures/CAP-043-2026-09-13_09-50-31_09-53-41-Group_Q/CAP-043-FINDINGS.md index dd518e1f..48444b36 100644 --- a/captures/CAP-043-2026-09-13_09-50-31_09-53-41-Group_Q/CAP-043-FINDINGS.md +++ b/captures/CAP-043-2026-09-13_09-50-31_09-53-41-Group_Q/CAP-043-FINDINGS.md @@ -51,7 +51,11 @@ check used to rest on `bluetooth.addr == ` → 0 and a forward-byt `CAP-031` and `CAP-044`, where the Buds are connected, and a BD_ADDR is little-endian on the wire.) `tshark`'s own protocol-hierarchy stats (`-z io,phs`) confirm the **entire** log's non-HCI-command/ event content is exactly `bthci_acl → btl2cap → btatt` (BLE GATT only) — there is no `btrfcomm` -branch in the hierarchy at all. **This is a genuinely clean, connection-free capture** — the specific +branch in the hierarchy at all. That GATT traffic is one LE connection, not the Buds' (added 2026-10-15, `ai-sessions/0094`): `tshark -Y +"bthci_evt.le_meta_subevent==0x0a or bthci_evt.le_meta_subevent==0x01"` → frame 317 (09:50:36.133, handle `0x0002`, random peer address), carrying all +345 ATT frames; its primary services (`-Y "btatt.opcode==0x11"`) are GAP, GATT, Device Information, **Heart Rate (`0x180D`)** and six 128-bit vendor +services, and no Fast Pair Service (`-Y "btatt.uuid16==0xfe2c"` → 0) — unlike the Buds' map (`CAP-034-FINDINGS.md` §4, `0xFE2C` at `0x0c00`–`0x0c14`); the same +Heart-Rate signature as the unrelated device in `CAP-018`/`CAP-028`. **This is a genuinely clean, connection-free capture** — the specific procedure deviation that capped `CAP-011` (an active classic RFCOMM+GATT connection present throughout) does not reproduce here. @@ -159,7 +163,7 @@ scope), flagged only so it isn't silently lost. **A separate observation, worth surfacing since the system UI itself is informative here:** the system Bluetooth settings panel displays a live-looking `L/C/R` battery line for the bonded device for the *entire* session, despite this session's own isolation check (§2/§3) confirming **no** -classic connection, **no** RFCOMM, and **no** GATT session to the Buds ever formed, and the one BLE +classic connection, **no** RFCOMM, and **no** GATT session to the Buds ever formed (the log's one LE connection, handle `0x0002`, is an unrelated Heart-Rate device — §2), and the one BLE advertisement actually observed from the Buds does not structurally carry a battery value at all (§3). This value must therefore be a **cached, last-known reading** carried over from a previous session, not a value refreshed by anything observed on the wire in this capture — 🟡 HYPOTHESIS, not diff --git a/captures/CAP-044-2026-09-13_13-05-51_13-11-07-Group_AA/CAP-044-EVENT-NOTES.md b/captures/CAP-044-2026-09-13_13-05-51_13-11-07-Group_AA/CAP-044-EVENT-NOTES.md index 03a29c1b..9189bc88 100644 --- a/captures/CAP-044-2026-09-13_13-05-51_13-11-07-Group_AA/CAP-044-EVENT-NOTES.md +++ b/captures/CAP-044-2026-09-13_13-05-51_13-11-07-Group_AA/CAP-044-EVENT-NOTES.md @@ -23,15 +23,15 @@ FACT either way. This repeat corrects both deviations in one session. | Group(s) | AA (repeat, 2nd attempt) | | Date | 2026-09-13 | | Firmware version | `release_5.203` (🟢 confirmed on-wire, frame 4416, DLCI 0x08 Group `0x03` Code `0x02`) | -| Test device | Pixel 7a, Android 17 (🟢 FACT, MP4 container tag); app version `1.0.955078536`, Google Play services active (⚪ ASSUMPTION, maintainer-supplied context) | +| Test device | Pixel 7a, Android 17 (⚪ ASSUMPTION — the MP4 container tag is the camera phone's, as established in `CAP-043-FINDINGS.md` §1) *(corrected 2026-10-15, `ai-sessions/0094`)*; app version `1.0.955078536`, Google Play services active (⚪ ASSUMPTION, maintainer-supplied context) | | Video file | `CAP-044-recording.mp4` — 316.42s, 13:05:51–13:11:07 local (burned-in overlay + `ffprobe`-cross-checked) | | Log file | `CAP-044-btsnoop_hci.log` — 6,363 packets, 850.35s, 12:59:08.43–13:13:18.78 local | | Buds MAC (partial, per `AGENTS.md` §7/§9) | `04:00:6e:cf:6e:07` (same unit as `CAP-001`/`CAP-002`/`CAP-032`/`CAP-033`) | **Isolation check (required — this is the whole point of the repeat) — 🟡 PARTIALLY ACHIEVED, for a -different reason than `CAP-033`.** Force-stop *does* precede Forget in wall-clock order this time -(the very first, off-camera Force-stop, then the in-app Forget at 13:05:59) — that specific ordering -issue from `CAP-033` §1.1 does not reproduce. **However, no Force-stop action of any kind is +different reason than `CAP-033`.** Force-stop nominally precedes Forget in wall-clock order this time +(the very first, off-camera Force-stop, then the in-app Forget at 13:05:59) — but the app was relaunched from recents at 13:05:55, so it was running +at the Forget, which nullifies that order: `CAP-033` §1.1's problem (app alive at the Forget) recurs (corrected 2026-10-15, `ai-sessions/0094`). **Moreover, no Force-stop action of any kind is observed anywhere in the video between the in-app Forget (13:05:59) and the actual "Pair" tap that triggers bonding + the SDP browse (13:08:07, not 13:07:00 as originally logged below — see `CAP-044-FINDINGS.md` §1)** — a 2m8s window during which the app may have remained resident in the diff --git a/captures/CAP-044-2026-09-13_13-05-51_13-11-07-Group_AA/CAP-044-FINDINGS.md b/captures/CAP-044-2026-09-13_13-05-51_13-11-07-Group_AA/CAP-044-FINDINGS.md index b5e4e796..9371398a 100644 --- a/captures/CAP-044-2026-09-13_13-05-51_13-11-07-Group_AA/CAP-044-FINDINGS.md +++ b/captures/CAP-044-2026-09-13_13-05-51_13-11-07-Group_AA/CAP-044-FINDINGS.md @@ -9,8 +9,8 @@ staged here per `PROJECT_RULES.md` §2. Every claim below carries a status per ` - 🔴 **OPEN QUESTION** — genuinely unresolved by this capture. **Capture ID:** `CAP-044` · **Date:** 2026-09-13 · **Firmware:** `release_5.203` (🟢 confirmed -on-wire, frame 4416, DLCI 0x08 Group `0x03` Code `0x02`) · **Phone:** Pixel 7a, Android 17 (🟢 -FACT, MP4 container tag) · **App version:** `1.0.955078536` (⚪ ASSUMPTION, maintainer-supplied +on-wire, frame 4416, DLCI 0x08 Group `0x03` Code `0x02`) · **Phone:** Pixel 7a, Android 17 (⚪ +ASSUMPTION — the MP4 container tag is the camera phone's, `CAP-043-FINDINGS.md` §1) *(corrected 2026-10-15, `ai-sessions/0094`)* · **App version:** `1.0.955078536` (⚪ ASSUMPTION, maintainer-supplied context) · **Google Play services:** active (⚪ ASSUMPTION, maintainer-supplied context) · **Log file:** `CAP-044-btsnoop_hci.log` (6,363 packets, 850.35s, 12:59:08.43–13:13:18.78 local) · **Video:** `CAP-044-recording.mp4` (316.42s, 13:05:51–13:11:07 local) · **Buds MAC (partial):** @@ -70,9 +70,9 @@ the picture: - This means `SDP-001`'s isolation requirement ("force-stop the app first, so it cannot react to the pairing at all") is **not cleanly satisfied in this session either**, for a different, more precise reason than `CAP-033`'s violation (`CAP-033-FINDINGS.md` §1.1's Forget-before-Force-stop ordering - issue): here the *order* was correct (Force-stop before Forget, at least for the very first - force-stop before the video started), but there is no confirmed force-stop covering the actual - window in which the pairing and SDP browse happened. + issue): here the *order* was nominally correct (Force-stop before Forget, for the very first + force-stop before the video started), but the relaunch at 13:05:55 put the app back in the foreground for the Forget at 13:05:59, so the order + was nullified, and there is no confirmed force-stop covering the actual window in which the pairing and SDP browse happened. *(corrected 2026-10-15, `ai-sessions/0094`)* - **This is directly relevant to interpreting §3's positive finding below** — see §5's discussion of why the SDP query *pattern* observed in this session is unusually informative regardless of this gap. @@ -134,7 +134,7 @@ Service Attribute: Service Name (0x100), value = MAESTRO APP byte-for-byte identically in this independent session, 14 days later** — a second, independent wire-level confirmation of "MAESTRO APP"=DLCI 0x02, "GSND CONTROL"=DLCI 0x08, "GSND AUDIO"=DLCI 0x0a, and "GFPS RFCOMM" (official Fast Pair Message Stream)=DLCI 0x04. This further strengthens -`DECISIONS.md` ADR-018's DLCI-0x02-channel-ownership FACT (now a 5th/6th confirming instance, +`DECISIONS.md` ADR-018's DLCI-0x02-channel-ownership FACT (now a 5th confirming instance, counting `CAP-001`/`CAP-002`/`CAP-032`/`CAP-033`/this capture) and adds a second wire-level data point for the `CAP-033`-introduced "GSND CONTROL"/"GSND AUDIO" naming lead. @@ -198,8 +198,8 @@ independently-comparable SDP transaction exists in this log to check against. - **What actually happened, per §1/§2:** the isolation gap in this session is that no Force-stop is confirmed to cover the ~2m8s window between the in-app "Forget" and the actual "Pair" tap that triggers the SDP browse — not (as `CAP-033` had) a Forget-before-Force-stop ordering violation, and - not (as `CAP-033` had) a never-executed step 3. This session fixes both of `CAP-033`'s specific - gaps (§1.1/§1.3 there) but introduces a different one of its own. + not (as `CAP-033` had) a never-executed step 3. This session fixes `CAP-033`'s step-3 gap (§1.3 there); the ordering gap (§1.1) is only + nominally fixed — the app was relaunched before the Forget (13:05:55) — and a different gap of its own is added. *(corrected 2026-10-15, `ai-sessions/0094`)* - **What the SDP browse itself looked like, per §3, is a genuinely new and informative data point, independent of the isolation question**: this session's SDP transaction is a set of *targeted*, one-UUID-at-a-time queries (GSND CONTROL, GSND AUDIO, GFPS RFCOMM ×2, MAESTRO APP) rather than diff --git a/captures/CAP-045-2026-09-12_08-22-51_08-24-32-Group_AJ/CAP-045-EVENT-NOTES.md b/captures/CAP-045-2026-09-12_08-22-51_08-24-32-Group_AJ/CAP-045-EVENT-NOTES.md index efefd09b..500e5ab8 100644 --- a/captures/CAP-045-2026-09-12_08-22-51_08-24-32-Group_AJ/CAP-045-EVENT-NOTES.md +++ b/captures/CAP-045-2026-09-12_08-22-51_08-24-32-Group_AJ/CAP-045-EVENT-NOTES.md @@ -50,6 +50,7 @@ own rotation list. | 08:22:56.997 | Classic `Connect Complete` (stored key) | System | — | Log frame 294. | | 08:22:57.421–.471 | DLCI 0x04 `Get`/`Notify` (`Settable=0xe8`=undocked, `Current=0x40`=Adaptive) | System (auto) | — | Log frames 609/612. | | ~08:23:01–04 | Pixel Buds app opened, "Device details" screen — **the main ANC-toggle row, not the "Controls and gestures" rotation-checklist screen** | User (App) | — | Video `t=30` confirms: the top-level "Noise cancellation/Off/Adaptive/Transparency" row is visible, "Transparency" selected black. | +| before 08:23:21.949 | Left earbud taken from the case and put in — **not visible on video** (the camera angle hides the case slots and the user's ears; stills at 08:22:52–08:23:19 show only the loose bud beside the case) — no row recorded it | User (Hardware) | — | no discrete frame identified | | **08:23:21.949** | **Physical long-press, Left earbud: Adaptive→Transparency** | User (Hardware) | `TOUCH-007` (not `HOLD-005`) | Log frame 1583, `Notify`-only (`0813000401e8e880`), **no preceding `08 12` Set frame** — matches `CAP-027-FINDINGS.md` §4's press-and-hold mechanism exactly. | | **08:23:32.507** | Physical long-press, Left: Transparency→Noise cancellation | User (Hardware) | `TOUCH-007` | Log frame 1755, `Notify`-only (`...e808`). | | **08:23:46.899** | Physical long-press, Left: Noise cancellation→Adaptive | User (Hardware) | `TOUCH-007` | Log frame 1818, `Notify`-only (`...e840`). | diff --git a/captures/CAP-045-2026-09-12_08-22-51_08-24-32-Group_AJ/CAP-045-FINDINGS.md b/captures/CAP-045-2026-09-12_08-22-51_08-24-32-Group_AJ/CAP-045-FINDINGS.md index 2d3133b1..2ab85a69 100644 --- a/captures/CAP-045-2026-09-12_08-22-51_08-24-32-Group_AJ/CAP-045-FINDINGS.md +++ b/captures/CAP-045-2026-09-12_08-22-51_08-24-32-Group_AJ/CAP-045-FINDINGS.md @@ -70,9 +70,10 @@ $ tshark -r CAP-045-btsnoop_hci.log -Y 'btrfcomm.dlci==4 and btrfcomm.len>0' -T **Every state transition after the connect-time pair is a bare `Notify` (`08 13`) with no preceding `08 12` Set frame** — exactly `CAP-027-FINDINGS.md` §4's already-documented press-and-hold mechanism (a hardware gesture drives the Buds to report a Notify directly, bypassing the app-initiated Set -path). All 10 transitions correlate to a video-confirmed action within ≤1s (see the corrected Event -Timeline), 8 of them clean ANC-mode-cycle transitions (Adaptive→Transparency→Noise -cancellation→Adaptive, repeating) matching the physical long-press gestures, and 2 of them +path). 9 of the 10 transitions correlate to a video-confirmed action within ≤1s (see the corrected Event +Timeline): 7 clean ANC-mode-cycle transitions (Adaptive→Transparency→Noise +cancellation→Adaptive, repeating) matching the physical long-press gestures (3 Left: 1583/1755/1818; 4 Right: 2168/2218/2237/2254); one further +cycle transition, 2122 (08:24:02.089, →Adaptive), has no video-visible gesture; and 2 of them *(corrected 2026-10-15, `ai-sessions/0094`)* (`08:23:54`, `08:24:30`) matching in-ear-removal moments (`Current` reads `Off`, the same value a genuine "ANC off" selection would produce — no distinct removal-specific value exists in this message). @@ -83,7 +84,7 @@ carries no side field (🟢 FACT, structural):** [Group:1][Code:1][Len:2BE=0004][Version:1][UI toggles:1][Settable toggles:1][Current state:1] ``` Per `PROTOCOL.md` §4.1's already-confirmed layout, this 8-byte frame has no field capable of -encoding which earbud triggered it. All 5 Left-attributed and 5 Right-attributed Notify frames in +encoding which earbud triggered it. All 4 Left-cluster (1583–1849) and 6 Right-cluster (2122–2273) Notify frames in this session are structurally identical in shape — differing only in the `Current state` byte value, which reflects the *new ANC mode*, not the *triggering side*. **The only way to attribute a frame to Left vs. Right is timing/video correlation** — which this session's own corrected Event Timeline @@ -103,11 +104,11 @@ re-run of Group AJ's own procedure is still needed.** ## 5. Test-ID traceability - **`HOLD-005`**: **not exercised** — the checklist screen was never opened. -- **`TOUCH-007`** (incidental, not this Group's own target): exercised 8 times (4 per earbud), +- **`TOUCH-007`** (incidental, not this Group's own target): exercised 7 times (3 Left, 4 Right; plus one gesture-less transition, 2122), cleanly video/wire-correlated — a further replication of `CAP-027-FINDINGS.md` §4's finding, and - the first to show it holds for the Right earbud specifically (`CAP-027`'s own Right-side sample - was ambiguous between "one long hold" and "two presses," per that file's own open item — this - session's 4 clean Right-side samples don't resolve that ambiguity either, since this session's own + the first multi-sample replication on the Left earbud (`CAP-027`'s own Left-side sample, frames 3056/3091, + was ambiguous between "one long hold" and "two presses," per that file's own open item; its Right-side sample, 2930, was a single clean frame — this + session's 3 clean Left-side samples don't resolve that ambiguity either, since this session's own gestures weren't individually video-timed to sub-second precision). ## 6. Conclusions & proposed downstream updates diff --git a/captures/CAP-046-2026-09-12_17-02-59_17-05-49-Group_AK/CAP-046-EVENT-NOTES.md b/captures/CAP-046-2026-09-12_17-02-59_17-05-49-Group_AK/CAP-046-EVENT-NOTES.md index 9d890dbb..b809957e 100644 --- a/captures/CAP-046-2026-09-12_17-02-59_17-05-49-Group_AK/CAP-046-EVENT-NOTES.md +++ b/captures/CAP-046-2026-09-12_17-02-59_17-05-49-Group_AK/CAP-046-EVENT-NOTES.md @@ -60,11 +60,11 @@ frame extracted at that exact offset. | 17:04:15.680 | `field19` fires `=1` (ON) | System (auto?) | — | Log frame 1754. | | **17:04:38.261** | Slider returned to center — wire `field17=1` | User (App) | `AUDIO-003` | Log frame 1786. | | 17:04:38.712 | `field19` fires `=0` (OFF) | System (auto?) | — | Log frame 1806. | -| **17:04:51.320** | Slider dragged to full extreme (2nd cycle) — wire `field17=-100` | User (App) | `AUDIO-003` | Log frame 1834. **Video (`w_112.3.png`) confirms RIGHT extreme again** — 2nd confirmation. | +| **17:04:51.320** | Slider dragged to full extreme (2nd cycle) — wire `field17=-100` | User (App) | `AUDIO-003` | Log frame 1834. **Video (`w_112.3.png`) confirms RIGHT extreme again** — 3rd confirmation. | | 17:04:51.613 | `field19` fires `=1` (ON) | System (auto?) | — | Log frame 1843. | | **17:05:09.216** | Slider returned to center — wire `field17=0` | User (App) | `AUDIO-003` | Log frame 1873. | | 17:05:09.362 | `field19` fires `=0` (OFF) | System (auto?) | — | Log frame 1880. | -| **17:05:22.959** | Slider dragged to full extreme (2nd cycle) — wire `field17=100` | User (App) | `AUDIO-003` | Log frame 1908. **Video (`w_144.0.png`) confirms LEFT extreme again** — 3rd confirmation. | +| **17:05:22.959** | Slider dragged to full extreme (2nd cycle) — wire `field17=100` | User (App) | `AUDIO-003` | Log frame 1908. **Video (`w_144.0.png`) confirms LEFT extreme again** — 4th confirmation. | | 17:05:23.290 | `field19` fires `=1` (ON) | System (auto?) | — | Log frame 1916. | | ~17:05:39 | **No slider change** — handle remains at the Left extreme, unchanged | — | — | Video `t=160` (`f_160.png`) — the draft's claimed "Sample 4: intermediate -31" action **did not happen**; the slider is untouched here. | | **17:05:46.275** | Slider returned to center — wire `field17=1` (final write of the session) | User (App) | `AUDIO-003` | Log frame 1957. | @@ -81,7 +81,7 @@ occurrences exist). No intermediate-position drag appears anywhere in the video - [x] Zigzag-decode (`(n>>1) ^ -(n&1)`) each isolated sample's `field 17` value. → Done, 8 samples. - [x] Match each decoded value against the video-confirmed slider position/label at that exact - moment. → 3 extreme-position video confirmations (+ a 4th confirming the "no change" gap). + moment. → 4 extreme-position video confirmations (+ one more confirming the "no change" gap). - [x] Determine the field's numeric scale/range and which direction maps to negative vs. positive. → **Range ±100. Positive = Left, negative = Right — opposite of the draft's own labeling.** - [x] Record the result plainly either way. → Recorded, with the draft's polarity error corrected. diff --git a/captures/CAP-046-2026-09-12_17-02-59_17-05-49-Group_AK/CAP-046-FINDINGS.md b/captures/CAP-046-2026-09-12_17-02-59_17-05-49-Group_AK/CAP-046-FINDINGS.md index 7cf79713..ab39fc8e 100644 --- a/captures/CAP-046-2026-09-12_17-02-59_17-05-49-Group_AK/CAP-046-FINDINGS.md +++ b/captures/CAP-046-2026-09-12_17-02-59_17-05-49-Group_AK/CAP-046-FINDINGS.md @@ -24,7 +24,7 @@ staged here per `PROJECT_RULES.md` §2. Every claim below carries a status per ` | Notes file | [`CAP-046-EVENT-NOTES.md`](./CAP-046-EVENT-NOTES.md) | | Buds MAC (partial, per `AGENTS.md` §7/§9) | `04:00:6e:...:07` | -## 2. Central finding — polarity resolved, opposite of the draft's own assumption (🟢 FACT, 3× video-confirmed) +## 2. Central finding — polarity resolved, opposite of the draft's own assumption (🟢 FACT, 4× video-confirmed) *(corrected 2026-10-15, `ai-sessions/0094`: said 3×)* **Method:** every DLCI 0x02 `Sent` frame (61 total) was HDLC-unescaped, CRC-32-verified, and searched for the `qhr` field-17 tag (`0x88 0x01`, per `PROTOCOL.md` §4.5.7): @@ -59,7 +59,7 @@ identical to the structured decode. **8 `field17` writes total, zigzag-decoded:* | 1908 | 17:05:22.959 | 200 | **+100** | **Left** extreme again (`w_144.0.png`) | | 1957 | 17:05:46.275 | 2 | 1 | center | -**Result, 3/3 extreme-position video confirmations, zero counter-examples:** +**Result, 4/4 extreme-position video confirmations (1181, 1746, 1834, 1908), zero counter-examples:** *(corrected 2026-10-15, `ai-sessions/0094`)* - `field17 = -100` (negative extreme) → slider at the **Right** extreme. - `field17 = +100` (positive extreme) → slider at the **Left** extreme. - Center-return values cluster near zero (`0`, `1`, `1`, `2`) — consistent with a true center of `0`, @@ -119,7 +119,7 @@ this question (does the field scale linearly at non-extreme positions?) remains ## 6. Conclusions & proposed downstream updates **Recorded as this session's own factual result (no sign-off needed, purely descriptive):** -- 8 `field17` writes, zigzag-decoded, 3 extreme positions video-confirmed. +- 8 `field17` writes, zigzag-decoded, 4 extreme positions video-confirmed. - `field19` (Mono audio) fires in exact lockstep with every Balance-extreme/center transition. - Only 2 of the draft's claimed 4 samples actually occurred. diff --git a/captures/CAP-047-2026-09-14_05-51-38_06-35-30-Group_AL/CAP-047-EVENT-NOTES.md b/captures/CAP-047-2026-09-14_05-51-38_06-35-30-Group_AL/CAP-047-EVENT-NOTES.md index 3fd238fd..ffe4b778 100644 --- a/captures/CAP-047-2026-09-14_05-51-38_06-35-30-Group_AL/CAP-047-EVENT-NOTES.md +++ b/captures/CAP-047-2026-09-14_05-51-38_06-35-30-Group_AL/CAP-047-EVENT-NOTES.md @@ -1,6 +1,6 @@ # Event Notes: Pixel Buds Pro 2 (`libmaestro` / `libgfps`) — Group AL, DLCI 0x0a burst trigger, purpose-built hypothesis test (`CAP-047`) -**Status:** ⚪ **Captured, video-analyzed (incl. a targeted dense re-check of Recording 2's tail), and wire-level analyzed.** See `CAP-047-FINDINGS.md` for the full log analysis and hypothesis-test conclusion. +**Status:** ✅ **Captured, video-analyzed (incl. a targeted dense re-check of Recording 2's tail), and wire-level analyzed.** See `CAP-047-FINDINGS.md` for the full log analysis and hypothesis-test conclusion. **Purpose (`CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group AL, added 2026-09-09):** `CAP-021`'s 1123-frame DLCI 0x0a burst (`PROTOCOL.md` §6) has recurred in exactly 1 of 16+ sessions checked — passively diff --git a/captures/CAP-047-2026-09-14_05-51-38_06-35-30-Group_AL/CAP-047-FINDINGS.md b/captures/CAP-047-2026-09-14_05-51-38_06-35-30-Group_AL/CAP-047-FINDINGS.md index b5fc3862..dc46242e 100644 --- a/captures/CAP-047-2026-09-14_05-51-38_06-35-30-Group_AL/CAP-047-FINDINGS.md +++ b/captures/CAP-047-2026-09-14_05-51-38_06-35-30-Group_AL/CAP-047-FINDINGS.md @@ -74,7 +74,7 @@ Buds Connection Complete (frame 393, 05:49:56.45, chandle `0x0002`) and ends wit chandle's own locally-terminated Disconnect Complete (frame 1746, 05:51:28.776, reason `0x16`), 97.2s after its own start. The **main log begins independently** with its own fresh `Sent Reset` (frame 1, 05:51:30.682) — only 1.8s after `.log.last`'s last frame — and its own fresh Buds -Connection Complete (frame 611, 05:51:39.91) lands 11s after Recording 1's own documented start +Connection Complete (frame 611, 05:51:39.91) lands ~2s after Recording 1's own documented start (9.2s after the main log's first frame) (05:51:38, both Buds resting on the table, case closed, phone screen black/standby). Applying the `CAP-040`/`CAP-042`/`CAP-049` precedent's own diagnostic (`CAP-049-FINDINGS.md` §0): @@ -296,17 +296,19 @@ artifact:** cycle. - Recording 2: **two** genuine ACL Disconnection Complete events, reason `0x13` (remote user terminated — i.e. Buds-initiated, matching `DECISIONS.md` ADR-016's own "Disconnect-on-redock" - mechanism), at `06:33:59.19` (frame 2033, ~2s after swap #2's both-docked moment) and + mechanism), at `06:33:59.19` (frame 2033, ~8–18 s after swap #2's both-docked moment, 06:33:41–51, and ~2 s *after* the screen already showed + "+ Connect" at 06:33:57 — corrected 2026-10-15, `ai-sessions/0094`) and `06:34:50.92` (frame 3443, essentially simultaneous with swap #3's second placement at `06:34:51`). `DECISIONS.md` ADR-016 (`PROTOCOL.md` §5.1/§7) documents disconnect-on-redock as firing "the instant the *second* bud is placed in the case" — with no qualification for slot correctness, since its own evidence base never included a swapped-slot seating. This session's own dock-state-byte -evidence (§5 below) confirms the physical-presence sensor reads "both docked" identically in **all -three** swapped-dock instances (`06:10:20.78`, and both of Recording 2's) — yet only two of the -three produce a disconnect. **🔴 OPEN QUESTION, not resolved here:** why does Recording 1's -swapped dock (`06:10:21`) not disconnect, when both Recording 2 swaps do, given the dock-sensor -itself reports the identical "both docked" state in all three? No video-visible or wire-visible +evidence (§5 below) shows a both-seated `0x00` reading only for Recording 1's swapped dock (frame 2803, `06:10:20.78`); Recording 2 has +**no** Notify at all during swap #2 (06:33:41–06:33:59; `tshark -r CAP-047-btsnoop_hci-2.log -Y "btrfcomm.len>0 and data.data[0:2]==08:13"` → 952, 2673, +3048, 3364, 3834, 3996, 4339), and its `0x00` readings around swap #3 (3048 empty case, 3364 one bud) are §5's counter-examples, not both-seated +readings *(corrected 2026-10-15, `ai-sessions/0094`: this said the byte reads "both docked" identically in all three instances)*. Two of the +three swapped docks produce a disconnect. **🔴 OPEN QUESTION, not resolved here:** why does Recording 1's +swapped dock (`06:10:21`) not disconnect, when both Recording 2 swaps do? The dock-state byte cannot answer it — it was read while both buds were seated only in Recording 1. No video-visible or wire-visible difference between the three docking actions themselves was found that would explain this (all three are direct physical placements, not the CAP-016-style "both removed then case closed" sequence ADR-016's own evidence was originally built from) — reported plainly, not force-fit into a @@ -331,7 +333,7 @@ essentially simultaneous with the video's own documented `06:10:21` "both buds a in swapped configuration" moment. (Frame 1048, at Recording 1's own start with both Buds resting outside, correctly reads `0xe8`.) -**Secondary result — two new, unreconciled counter-examples to ADR-024 in Recording 2, additional to +**Secondary result — four new, unreconciled counter-examples to ADR-024 in Recording 2 (952 below, 3364, 3834, 3048), additional to the two `CAP-048-FINDINGS.md` §5 already documents, of the same general character (a fresh channel-(re)establishment's Get/Notify returning a stale value):** ``` @@ -343,6 +345,10 @@ $ tshark -r CAP-047-btsnoop_hci-2.log -Y "btrfcomm.dlci==0x05 and btrfcomm.len>0 3996 2026-09-14T06:35:02.195231+0200 1 0813000401e8e880 4339 2026-09-14T06:35:15.170599+0200 1 0813000401e8e840 ``` +- **Frame 952 (`06:33:12.44`, Connection 1, chandle `0x0005`, DLCI 0x04 — not covered by the DLCI 0x05 command above; `tshark -r CAP-047-btsnoop_hci-2.log + -Y "btrfcomm.len>0 and data.data[0:2]==08:13"` lists it) — Settable=`0x00`, counter-example under ADR-024's reading.** It is the connect-time answer + to Get 931 (06:33:12.25); per `CAP-047-EVENT-NOTES.md` the case is closed with both buds outside at this time (lid opened on an empty case at 06:33:31). + (Under ADR-049's 🟡 "`00` ⇒ no bud worn" it is consistent.) *(added 2026-10-15, `ai-sessions/0094`)* - Frame 2673 (`06:34:19.72`, Settable=`0xe8`) — correct: both buds are outside (removed `06:34:13`–15). - **Frame 3364 (`06:34:46.79`, Settable=`0x00`) — counter-example.** This Notify follows a DLCI 0x05 channel-bounce reopen at `06:34:45.78`–`45.92` (SABM/UA, immediately preceding it). Per @@ -376,8 +382,8 @@ $ tshark -r CAP-047-btsnoop_hci-2.log -Y "btrfcomm.dlci==0x05 and btrfcomm.len>0 None of this contradicts ADR-024's own core finding (dock-state-vs-not, confirmed correct in the large majority of readings, including every genuinely-both-docked and genuinely-neither-docked -sample in this session) — it adds three more single-session counter-examples (one video-time-clean, -two with a plausible settling explanation, one without) to the same still-open reliability question +sample in this session) — it adds four more single-session counter-examples (three with a plausible settling explanation — 952, 3364, +3834 — and one without, 3048) to the same still-open reliability question `CAP-048-FINDINGS.md` §5 already flagged, now reproduced independently a second time. ## 6. Cross-checks @@ -457,8 +463,8 @@ promotions of new protocol semantics):** ## 10. Open Questions - Why does Recording 1's swapped-slot dock (`06:10:21`) never trigger a disconnect, when both of - Recording 2's swapped-slot docks do, given the dock-sensor byte reads identically "both docked" in - all three (§4)? No video- or wire-visible procedural difference found. + Recording 2's swapped-slot docks do (§4)? The dock-state byte was read with both buds seated only in Recording 1 (frame 2803), so it cannot + be compared across the three. No video- or wire-visible procedural difference found. - What causes frame 3048's spontaneous "both docked" Notify (`06:34:39.51`) on an empty case, not preceded by any channel reopen (§5)? Possibly related to the immediately-following channel bounce (`06:34:39.92`), possibly not — not established. diff --git a/captures/CAP-048-2026-09-12_17-41-41_17-52-44-Group_AD/CAP-048-EVENT-NOTES.md b/captures/CAP-048-2026-09-12_17-41-41_17-52-44-Group_AD/CAP-048-EVENT-NOTES.md index 1fb595f6..49bdf69e 100644 --- a/captures/CAP-048-2026-09-12_17-41-41_17-52-44-Group_AD/CAP-048-EVENT-NOTES.md +++ b/captures/CAP-048-2026-09-12_17-41-41_17-52-44-Group_AD/CAP-048-EVENT-NOTES.md @@ -55,7 +55,7 @@ state. dock state throughout (not just the phone screen) — the anomaly needs sub-second dock-state correlation that `CAP-037`'s own pass didn't have. -## Event Timeline — corrected, wire-derived (the draft's per-reconnect table above did not survive verification) +## Event Timeline — corrected, wire-derived (replaces the draft's per-reconnect table, which did not survive verification) **The draft's claimed 25-reconnect, rapid docked/undocked-alternating-every-~15s table does not match the wire or the video.** The actual classic-ACL reconnect cadence and dock-state changes were @@ -78,7 +78,7 @@ events, not re-derived from the draft's own (unreliable) per-row claims. | 17:45:43.751–17:45:44.513 | Reconnect (chandle `0x0008`) | `0x00` Docked | Not independently video-checked this pass | | 17:46:49.025–.830 | Reconnect (chandle `0x0009`) | `0x00` Docked | Not independently video-checked this pass | | 17:47:41.535–17:47:42.136 | Reconnect (chandle `0x000a`) | `0x00` Docked | ❌ **video confirms both buds loose outside the case** (`t=358`–`362`, `pre742_358.png`/`mid_362.png`) — **second stale-reading counter-example, see Findings §5** | -| 17:48:51.024–17:49:46.910 | **Connection-retry burst**: 7 `Create Connection`/`Connect Complete` cycles on the same chandle (`0x000b`), roughly every 5–20s, culminating in a stable connection | `0x00` Docked (final `Get`/`Notify`, `17:49:47.27`) | Video (`t≈450`, `17:49:11`) shows the app stuck on "Connecting…" during this window, case appears closed — plausible link between a closed case and repeated failed connection attempts, not confirmed further (see Findings §6, new open item) | +| 17:48:51.024–17:49:46.910 | **Connection-retry burst**: 6 failed pages (Connection Complete status `0x04` Page Timeout, frames 7705/7712/7721/7741/7747/7753), roughly every 5–20s, then one connection (7781, handle `0x000b`) — `CAP-048-FINDINGS.md` §6 | `0x00` Docked (final `Get`/`Notify`, `17:49:47.27`) | Video (`t≈450`, `17:49:11`) shows the app stuck on "Connecting…" during this window, case appears closed — plausible link between a closed case and repeated failed connection attempts, not confirmed further (see Findings §6, new open item) | | 17:50:16.387–.934 | Reconnect (chandle `0x000c`) | `0x00` Docked | ✅ confirmed (`late_516.png`, `17:50:17`) | | 17:50:42.151–.164 | Same-chandle (`0x000c`) DLCI reopen | `0xe8` Undocked | ✅ confirmed — buds visibly removed by `17:50:43` (`late_542.png`) — **correctly, live-updated reading** | | 17:50:55.642–17:50:56.290 | Reconnect (chandle `0x000e`) | `0xe8` Undocked | Not independently video-checked this pass (consistent with the still-undocked state confirmed just before and after) | @@ -97,7 +97,7 @@ video-cross-checked** against a specific extracted frame (✅/❌ above); the re not each individually re-verified against a fresh video extraction this pass, though every one falls inside a dock-state window already established by an adjacent, video-confirmed reading (i.e., no reading was left completely uncorroborated). A future pass could individually verify the remaining -~5 reconnects for full exhaustiveness; this was not completed here given this capture's own size +7 readings (0x0006 reopen, 0x0008, 0x0009, 0x000b, 0x000e, 0x000f, 0x0012) for full exhaustiveness; this was not completed here given this capture's own size (663.58s video, 13,301-packet log) relative to the rest of this task's scope. ## Analysis checklist (per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group AD) diff --git a/captures/CAP-048-2026-09-12_17-41-41_17-52-44-Group_AD/CAP-048-FINDINGS.md b/captures/CAP-048-2026-09-12_17-41-41_17-52-44-Group_AD/CAP-048-FINDINGS.md index 5cd984c0..7b4b178b 100644 --- a/captures/CAP-048-2026-09-12_17-41-41_17-52-44-Group_AD/CAP-048-FINDINGS.md +++ b/captures/CAP-048-2026-09-12_17-41-41_17-52-44-Group_AD/CAP-048-FINDINGS.md @@ -54,7 +54,7 @@ this session's evidence below.** ``` $ tshark -r CAP-048-btsnoop_hci.log -Y 'btrfcomm.dlci==4 and btrfcomm.len>0' -T fields \ -e frame.number -e frame.time -e bthci_acl.chandle -e frame.p2p_dir -e data.data | grep -E "\s08(11|13)" -(16 rows — 15 distinct Get(`08110000`)/Notify(`0813...`) pairs across 13 chandles, plus 1 extra +(31 rows — 15 distinct Get(`08110000`)/Notify(`0813...`) pairs across 13 chandles, plus 1 extra Notify-only anomaly at 17:52:25.140, see §4) $ tshark -r CAP-048-btsnoop_hci.log -Y 'btrfcomm.dlci==4 and btrfcomm.len>0' -T fields -e bthci_acl.chandle | sort -u @@ -157,14 +157,13 @@ instances from the four correct ones is not established. ## 6. New open item — a connection-retry burst, plausibly tied to a closed case (🔴 OPEN QUESTION, new) ``` -$ tshark -r CAP-048-btsnoop_hci.log -Y 'bthci_acl.chandle==0x000b' -T fields -e frame.number -e frame.time -e frame.p2p_dir -e _ws.col.Info \ - | grep -E "Connect Complete|Create Connection" -(7 "Rcvd Connect Complete" events, from 17:48:51.024 to 17:49:46.910, each preceded by its own "Sent Create Connection") $ tshark -r CAP-048-btsnoop_hci.log -Y 'bthci_evt.code==0x03 && frame.number>7700 && frame.number<7790' -T fields -e frame.number -e bthci_evt.status 7705 0x04 7712 0x04 7721 0x04 7741 0x04 7747 0x04 7753 0x04 7781 0x00 ``` **Six of the seven are failed pages** (status `0x04`, Page Timeout); only the seventh (7781) is a connection — which is why no Disconnection Complete lies -between them. So this is the phone paging Buds that do not answer, six times in a row, not seven connect/complete cycles. (Rewritten in place 2026-10-03, +between them. So this is the phone paging Buds that do not answer, six times in a row, not seven connect/complete cycles. (2026-10-15, `ai-sessions/0094`: the +`bthci_acl.chandle==0x000b` query quoted here before, with the output "7 'Rcvd Connect Complete' events", was removed — that filter matches no HCI event; it +returns 0 Connect Complete/Create Connection lines.) (Rewritten in place 2026-10-03, `ai-sessions/0069`, A68-CAP-11.) Video at `t≈450` (`17:49:11`, mid-burst) shows the app UI stuck on "Connecting…", and the case appears closed (only the case's outer shell is visible, no loose buds in frame). 🟡 **HYPOTHESIS, diff --git a/captures/CAP-049-2026-09-12_18-13-54_18-21-49-Group_AF/CAP-049-EVENT-NOTES.md b/captures/CAP-049-2026-09-12_18-13-54_18-21-49-Group_AF/CAP-049-EVENT-NOTES.md index 791f9a78..9bb55dcc 100644 --- a/captures/CAP-049-2026-09-12_18-13-54_18-21-49-Group_AF/CAP-049-EVENT-NOTES.md +++ b/captures/CAP-049-2026-09-12_18-13-54_18-21-49-Group_AF/CAP-049-EVENT-NOTES.md @@ -50,7 +50,7 @@ session with no clearly camera-visible trigger for most of them (4 of 5 locally | 18:13:54 | Video starts. Case visible (buds not in it — worn/off-frame). Bluetooth off. | User | `OBS-006` | Video `t=0`. | | ~18:13:56 | Bluetooth toggled on | User (OS) | `OBS-006` | — | | 18:13:55.218 | `.log.last`'s own fresh `Sent Reset` | System | — | Matches the toggle-on step to within ~2s. | -| 18:14:05.361 / 18:14:09.395 | Classic `Connect Complete` (two chandles, `0x0002` then `0x0005` — a brief reconnect settling, not individually investigated further) | System | `OBS-006` | — | +| 18:14:05.361 / 18:14:09.395 | Classic `Connect Complete` ×2: frame 691 is a failed page (status `0x04` Page Timeout, no connection), frame 736 the connection (handle `0x0005`) — `CAP-049-FINDINGS.md` §1 | System | `OBS-006` | — | | 18:14:10.186–.247 | DLCI 0x04 `Get`/`Notify` — `Settable=0xe8`(undocked), `Current=0x08`(Noise cancellation) | System (auto) | — | Matches on-screen "Noise cancellation" selected. | | **18:14:24.543–.987** | **ANC `Set`→`Notify` — user taps 'Adaptive'** | User (App) | `OBS-006` | Video `t=30` (`f_030.png`) shows "Device details," Adaptive selected — matches exactly. `Settable=0xe8` (Set-triggered sample). | | ~18:14:48 | Bluetooth toggled off (quick panel) | User (OS) | `OBS-006` | — | diff --git a/captures/CAP-049-2026-09-12_18-13-54_18-21-49-Group_AF/CAP-049-FINDINGS.md b/captures/CAP-049-2026-09-12_18-13-54_18-21-49-Group_AF/CAP-049-FINDINGS.md index 4f9e118a..f8755d1a 100644 --- a/captures/CAP-049-2026-09-12_18-13-54_18-21-49-Group_AF/CAP-049-FINDINGS.md +++ b/captures/CAP-049-2026-09-12_18-13-54_18-21-49-Group_AF/CAP-049-FINDINGS.md @@ -38,7 +38,7 @@ $ xxd -l 16 CAP-049-btsnoop_hci.log ``` $ capinfos CAP-049-btsnoop_hci.log.last | grep -E "Earliest|Latest" -Earliest: 2026-09-12 18:13:55.523844 Latest: 2026-09-12 18:14:56.767281 +Earliest: 2026-09-12 18:13:55.218251 Latest: 2026-09-12 18:14:56.767281 $ tshark -r CAP-049-btsnoop_hci.log.last -T fields -e frame.number -e frame.time -e _ws.col.Info | head -1 1 2026-09-12T18:13:55.218251+0200 Sent Reset $ tshark -r CAP-049-btsnoop_hci.log.last -Y "bthci_evt.code==0x05" -T fields -e frame.number -e frame.time -e bthci_evt.reason diff --git a/captures/CAP-050-2026-09-14_21-01-01_21-13-30-Group_AG/CAP-050-EVENT-NOTES.md b/captures/CAP-050-2026-09-14_21-01-01_21-13-30-Group_AG/CAP-050-EVENT-NOTES.md index c2f09768..82bd1f0d 100644 --- a/captures/CAP-050-2026-09-14_21-01-01_21-13-30-Group_AG/CAP-050-EVENT-NOTES.md +++ b/captures/CAP-050-2026-09-14_21-01-01_21-13-30-Group_AG/CAP-050-EVENT-NOTES.md @@ -21,7 +21,7 @@ actually reopen DLCI 0x08 (OS-level Bluetooth toggle, or physical case/bud docki | Capture ID | `CAP-050` | | Group(s) | AG (repeat) | | Date | 2026-09-14 | -| Firmware version | `release_5.203` | +| Firmware version | ⚪ ASSUMPTION `release_5.203` (carried over, not re-confirmed on-screen this session — `CAP-050-FINDINGS.md` §1) | | Test device | Pixel 7a, official Pixel Buds Companion App + GMS enabled. The app was open on the Device details screen for the entire recording (video-confirmed continuously, `00:00:00`–`00:12:29`) — no deviation observed, except one brief interval (`00:11:41`–`00:11:50`, wall-clock `21:12:44`–`21:12:53`) where the phone's screen showed the lock screen / home screen instead of the app (the app was reopened to Device details afterward). | | Video file | `CAP-050-recording.mp4`, duration `748.80s` (`ffprobe`), 1280×720, 30fps. Burned-in wall-clock overlay: start `14 sep 2026 21:01:01`, end `14 sep 2026 21:13:30`. | | Log file | `CAP-050-btsnoop_hci.log` (871.57s, 20,060 packets, 0/20,060 `cap_len≠len` mismatches — untruncated, raw path; see `CAP-050-FINDINGS.md` §0) | @@ -38,9 +38,8 @@ distinct trigger types used across the session: removes them, visibly triggering the official app's "Disconnect"/"Connect" button label to flip between the two states. The app's own in-app Connect/Disconnect buttons are **not** tapped anywhere in this video — every state change traces to a physical dock/undock action, consistent with this - repeat's purpose. Whether each of these physical actions actually reopened DLCI 0x08 on the wire is - explicitly deferred to the separate, out-of-scope log-analysis session; this session can only - report the physical/UI-observed action and the resulting in-app Connect/Disconnect label. + repeat's purpose. Whether each of these physical actions actually reopened DLCI 0x08 on the wire was + deferred to the separate log-analysis pass, since done: `CAP-050-FINDINGS.md` §2. ## Procedure — planned (skeleton, unchanged below) vs. what the video actually shows @@ -91,10 +90,10 @@ is the app's own Connect/Disconnect button label, read literally from the screen | 21:02:21 – 21:02:37 | Continued handling of the case/buds (hand repeatedly touching case and buds); screen remains "Disconnect" (connected) throughout this window — no full both-docked state is sustained long enough to produce a disconnect here. | User (Hardware) | `PRIV-001` | no distinct wire event isolated for this handling-only window | | 21:02:37 – 21:02:39 | Screen shows **"Connect"** (disconnected). **Resolved (`CAP-050-FINDINGS.md` §6):** dense `ffmpeg` re-extraction at `t=96/97/98s` shows both earbuds clearly, normally seated in the case's two slots (no wrong-orientation) — **dock state at this disconnect: both docked.** | User (Hardware) | `PRIV-001` | Wire: chandle `0x0001` disconnect, frame 4948, `21:02:37.728` (reason `0x13`); preceding Notify (frame 4141, `21:02:04.012`) read `Settable-toggles=0x00` (docked), consistent with the video read here — `CAP-050-FINDINGS.md` §5/§6 | | 21:02:58 – 21:03:00 | Screen shows **"Disconnect"** again (reconnected), notification banner "Left 39% Case 74% Right 46%". **Resolved (`CAP-050-FINDINGS.md` §4/§6):** dense `ffmpeg` re-extraction at `t=117/119s` shows **Left earbud docked, Right earbud held in hand (out)** — not "neither docked" as originally guessed. | User (Hardware) | `PRIV-001` | Wire: chandle `0x0002` (2nd instance) Connect Complete frame 5145, `21:03:00.091`; the following Notify (frame 5684, `21:03:04.173`) reads `Settable-toggles=0x00` (docked) — a genuine mismatch against the video-confirmed Left-only dock state, consistent with `CAP-048-FINDINGS.md` §5's "stale pre-transition reading" pattern, not a contradiction of `DECISIONS.md` ADR-024 — `CAP-050-FINDINGS.md` §4/§5 | -| 21:04:16 – 21:04:20 | Both earbuds again placed into the case (lid stays open); by `21:04:20` area screen shows "Connect" (disconnected). **Dock state: both docked.** (Exact transition second not pinned closer than a ~4s window at this review density.) | User (Hardware) | `PRIV-001` | not individually wire-correlated this pass — falls between chandle `0x0001` (ends `21:02:37.728`) and chandle `0x0002`'s 2nd instance (starts `21:03:00.091`); consistent with a `~21:02:37`–`21:04:03` handling span, not separately isolated | -| 21:04:48 – 21:04:52 | Right earbud removed again; screen returns to "Disconnect" (reconnected). **Dock state at reconnect: Left docked, Right out**. | User (Hardware) | `PRIV-001` | Wire: `Connection Complete` chandle `0x0004`, frame 6659, `21:04:03.465`; `Settable-toggles=0x00` at `21:04:07.485` — `CAP-050-FINDINGS.md` §5 (Left-only-docked configuration confirmed by video at `t=183s`, §4) | -| 21:05:01 – 21:12:44 | Maintainer continues a long series of similar dock/undock actions — earbuds repeatedly placed into and picked out of the case's Left/Right slots, individually and in close succession, with the in-app Connect/Disconnect label flipping in step on multiple further occasions (case battery percentages climb steadily from Case 74%→71% and Left 39%→63%, Right 46%→65% over this window, consistent with real charging while docked between cycles). **Partially resolved (`CAP-050-FINDINGS.md` §1/§5):** the wire log shows 10 further `Connection Complete` events inside this window (chandles `0x0006`,`0x0007`,`0x0008`,`0x0009`,`0x000a`,`0x000b`/`0x000c`,`0x000d`,`0x000c`-2nd — far fewer than "many more than 15" implied by the in-app label alone), each with a confirmed `Settable-toggles` reading (`CAP-050-FINDINGS.md` §5's table) — only the `21:08:19` reconnect (Left docked/Right out, video-confirmed, `CAP-050-FINDINGS.md` §4) was individually video-cross-checked this pass; the remaining reconnects' exact Left/Right/both sub-state was not separately re-verified against video beyond the already-tabulated `Settable-toggles` binary read. A 20-frame supplementary scan of this whole window (`t=260`–`660s`) found no wrong-orientation docking event (`CAP-050-FINDINGS.md` §6). | User (Hardware) | `PRIV-001` | Wire: 10 `Connection Complete` events at `21:05:38.815`/`21:06:31.275`/`21:08:18.776`/`21:09:31.775`/`21:10:05.065`/`21:10:30.036`/`21:10:39.643`/`21:10:59.584`/`21:11:26.330`/`21:12:29.017` (chandles per `CAP-050-FINDINGS.md` §1); dock-state bracket per reconnect in `CAP-050-FINDINGS.md` §5 | -| 21:11:41 – 21:12:44... 21:11:41 - 21:12:53 | Phone screen goes to the lock screen / home screen briefly (app backgrounded or screen timeout) while case/bud handling continues off the Device-details screen; app is reopened to Device details by `21:12:53`, showing "Connect" (disconnected), Left 63%, Case 71%, Right 65%. | User | `PRIV-001` | falls within the large 21:05:01–21:12:44 window above; not separately wire-isolated | +| 21:03:31 – 21:03:37 | Both earbuds again placed into the case (lid stays open; second bud going in at `21:03:31`); by `21:03:37` the screen shows "Connect" (disconnected). **Dock state: both docked.** (Re-timed 2026-10-15, `ai-sessions/0094`, from film stills at `t=150/156`; this row said `21:04:16`–`21:04:20`, when the film shows both buds outside the case.) | User (Hardware) | `PRIV-001` | Wire: chandle `0x0002` (2nd instance) `Disconnection Complete`, frame 6495, `21:03:35.295` (reason `0x13`) | +| ~21:04:03 – 21:04:04 | Right earbud removed again; screen returns to "Disconnect" (reconnected). **Dock state at reconnect: Left docked, Right out** (film `t=183s`, `21:04:04`; by `21:04:16` both buds are outside the case, still connected). (Re-timed 2026-10-15, `ai-sessions/0094`: this row said `21:04:48`–`21:04:52`.) | User (Hardware) | `PRIV-001` | Wire: `Connection Complete` chandle `0x0004`, frame 6659, `21:04:03.465`; `Settable-toggles=0x00` at `21:04:07.485` — `CAP-050-FINDINGS.md` §5 (Left-only-docked configuration confirmed by video at `t=183s`, §4) | +| 21:05:01 – 21:12:44 | Maintainer continues a long series of similar dock/undock actions — earbuds repeatedly placed into and picked out of the case's Left/Right slots, individually and in close succession, with the in-app Connect/Disconnect label flipping in step on multiple further occasions (case battery percentages climb steadily from Case 74%→71% and Left 39%→63%, Right 46%→65% over this window, consistent with real charging while docked between cycles). **Partially resolved (`CAP-050-FINDINGS.md` §1/§5):** the wire log shows 10 further `Connection Complete` events inside this window (chandles `0x0006`,`0x0007`,`0x0008`,`0x0009`,`0x000a`,`0x000c`/`0x000b`,`0x000d`,`0x000c`-2nd,`0x000d`-2nd — far fewer than "many more than 15" implied by the in-app label alone), each with a confirmed `Settable-toggles` reading (`CAP-050-FINDINGS.md` §5's table) — only the `21:08:19` reconnect (Left docked/Right out, video-confirmed, `CAP-050-FINDINGS.md` §4) was individually video-cross-checked this pass; the remaining reconnects' exact Left/Right/both sub-state was not separately re-verified against video beyond the already-tabulated `Settable-toggles` binary read. A 20-frame supplementary scan of this whole window (`t=260`–`660s`) found no wrong-orientation docking event (`CAP-050-FINDINGS.md` §6). | User (Hardware) | `PRIV-001` | Wire: 10 `Connection Complete` events at `21:05:38.815`/`21:06:31.275`/`21:08:18.776`/`21:09:31.775`/`21:10:05.065`/`21:10:30.036`/`21:10:39.643`/`21:10:59.584`/`21:11:26.330`/`21:12:29.017` (chandles per `CAP-050-FINDINGS.md` §1); dock-state bracket per reconnect in `CAP-050-FINDINGS.md` §5 | +| ~21:12:24 – 21:12:47 (film: screen black ~21:12:27–21:12:39, lock screen ~21:12:43; video-relative `00:11:41` = 21:12:42) | Phone screen goes to the lock screen / home screen briefly (app backgrounded or screen timeout) while case/bud handling continues off the Device-details screen; app is reopened to Device details by `21:12:53`, showing "Connect" (disconnected), Left 63%, Case 71%, Right 65%. | User | `PRIV-001` | falls within the large 21:05:01–21:12:44 window above; not separately wire-isolated | | 21:12:59 – 21:13:09 | Right earbud removed from the case; screen returns to "Disconnect" (reconnected). **Dock state at reconnect: Left docked, Right out** (same pattern as the two earlier cycles | User (Hardware) | `PRIV-001` | Wire: `Connection Complete` chandle `0x000f`, frame 19073, `21:12:57.847`; `Settable-toggles=0x00` at `21:13:01.776` — `CAP-050-FINDINGS.md` §5 | | 21:13:30 | Video ends. Screen shows "Connect" (disconnected), Left 63%, Case 71%, Right 65%; no earbuds visible outside the case (both apparently docked). | — | `PRIV-001` | Wire: last `Connection Complete` chandle `0x0011`, frame 19868, `21:13:20.208` — private-envelope DLCI not confirmed for this final reconnect, `CAP-050-FINDINGS.md` §2 | diff --git a/captures/CAP-050-2026-09-14_21-01-01_21-13-30-Group_AG/CAP-050-FINDINGS.md b/captures/CAP-050-2026-09-14_21-01-01_21-13-30-Group_AG/CAP-050-FINDINGS.md index 0a7be080..82a78535 100644 --- a/captures/CAP-050-2026-09-14_21-01-01_21-13-30-Group_AG/CAP-050-FINDINGS.md +++ b/captures/CAP-050-2026-09-14_21-01-01_21-13-30-Group_AG/CAP-050-FINDINGS.md @@ -144,8 +144,8 @@ zero-length ones (`02`, `04`, `11`, `13`, `15`), in the same burst, every time** | `04.12` | `08 02 10 01` — **byte-identical in all 14 bursts** | **Constant — not a match** | | `04.14` | `08 01` — **byte-identical in all 14 bursts** | **Constant — not a match** | | `09.02` | `08 00` — **byte-identical in all 14 bursts** | **Constant — not a match** | -| `04.05` | `08 03` in 11 of 14 bursts; `08 04` once (chandle `0x0002`-second, mid-burst, see §4); `08 05` once (chandle `0x0008`) | **Fluctuates, but does not cleanly track the dock-state bracket — see §4. Reported as 🔴 OPEN QUESTION, not a confirmed semantic reading** | -| `04.16` | `08 02` in 11 of 14 bursts; `08 01` twice (chandle `0x0002`-second mid-burst, and chandle `0x0008`) | Same pattern/verdict as `04.05` — see §4 | +| `04.05` | whole log, 42 `Rcvd` frames: `08 03` ×37; `08 04` ×3 (5966/6082/6350, chandle `0x0002`-second, 21:03:07–21:03:24, see §4); `08 05` ×2 (11017/11438, chandle `0x0008`, 21:08:19–21:08:30) *(corrected 2026-10-15, `ai-sessions/0094`: the per-burst counts "11 of 14 + once + once" summed to 13)* | **Fluctuates, but does not cleanly track the dock-state bracket — see §4. Reported as 🔴 OPEN QUESTION, not a confirmed semantic reading** | +| `04.16` | whole log, 21 `Rcvd` frames: `08 02` ×18; `08 01` ×3 (5973/6090, chandle `0x0002`-second mid-burst, and 11051, chandle `0x0008`) *(corrected 2026-10-15, `ai-sessions/0094`: "11 of 14 + twice" summed to 13)* | Same pattern/verdict as `04.05` — see §4 | **Outcome for `PRIV-001`'s own 7 flagged codes, stated per the three-way framework this Test-ID's own Analysis checklist requires:** @@ -201,13 +201,13 @@ not* distinction, confirmed correct at every *other* reconnect this session — the same "reconnects can catch a stale in-flight reading" caveat ADR-024's own 2026-09-13 Update already documents. -## 5. `Settable-toggles` cross-check against `DECISIONS.md` ADR-022/ADR-024 — 16/16 `Get`/`Notify` pairs, zero misses; dock-state reading correct at every other reconnect (🟢 FACT, reconfirms both ADRs) +## 5. `Settable-toggles` cross-check against `DECISIONS.md` ADR-022/ADR-024 — 16 reconnects, 19 `Get`s each answered, 23 `Notify` frames, zero misses (🟢 FACT for ADR-022); under ADR-024's both-vs-not-both reading five `0x00` readings with not-both docked are counter-examples (21:01:14, 21:02:04, 21:03:04, 21:04:07, 21:13:01) *(corrected 2026-10-15, `ai-sessions/0094`: said "16/16 pairs … dock-state reading correct at every other reconnect")* ``` $ tshark -r CAP-050-btsnoop_hci.log -Y 'btrfcomm.len>0 and data.data contains 08:11:00:00' \ -T fields -e frame.number -e frame.time -e bthci_acl.chandle -e btrfcomm.dlci ``` -19 `08 11 00 00` "Get ANC state" `Sent` frames total (across all 16 reconnects — the same +19 `08 11 00 00` "Get ANC state" `Sent` frames total (23 `08 13` Notify frames in all — 19 answers plus 4 without a Get) (across all 16 reconnects — the same session-local-DLCI caveat applies here too: `btrfcomm.dlci` alone is not `4` for every occurrence, matching `CAP-038-FINDINGS.md` §3's own finding for the *official* Fast Pair Message Stream, distinct from the private envelope checked above), each answered by a matching `08 13 00 04 ...` Notify — @@ -217,12 +217,12 @@ reconnect: | Reconnect time | `Settable-toggles` | Video-confirmed dock state (where checked, §4/§6) | |---|---|---| -| 21:01:14, 21:01:40 | `0x00` (docked) | not individually checked this pass | -| 21:02:04 | `0x00` (docked) | `21:02:37` (disconnect moment, same window): **both docked**, confirmed §6 | +| 21:01:14, 21:01:40 | `0x00` (docked) | 21:01:13: **both buds outside the case** (`CAP-050-EVENT-NOTES.md`) — 21:01:14 is a **counter-example** under ADR-024's binary; 21:01:40: both being seated (21:01:38–44, disconnect 21:01:41) | +| 21:02:04 | `0x00` (docked) | 21:01:49–21:01:58: Right removed, **Left docked, Right out** at this reconnect (3219, 21:01:59.937; `CAP-050-EVENT-NOTES.md`) — a **counter-example** under ADR-024's binary (the both-docked moment cited here before, 21:02:37, is the later disconnect) | | 21:03:04 | `0x00` (docked) | `21:03:00`: **Left docked, Right out — a counter-example, see §4** | | 21:03:08, 21:03:15 | `0xe8` (undocked) | `21:03:08`: **both out**, confirmed §4 | | 21:03:34 | `0x00` (docked) | not individually checked | -| 21:04:07, 21:05:01 | `0x00` (docked) | 21:04:04: **Left docked, Right out** — also `Settable=00`, i.e. this reconnect's reading is internally consistent with itself even though it doesn't distinguish "one docked" from "both docked" (ADR-024 only ever claimed a both-vs-not-both binary, not a 3-way split — this is expected, not a counter-example) | +| 21:04:07, 21:05:01 | `0x00` (docked) | 21:04:04: **Left docked, Right out** — `Settable=00` with not-both docked is a **counter-example** under ADR-024's both-vs-not-both binary (as 21:03:04 above; corrected 2026-10-15, `ai-sessions/0094`: this said "expected, not a counter-example") | | 21:05:42 | `0x00` (docked) | not individually checked | | 21:06:35 | `0x00` (docked) | not individually checked | | 21:08:19 | `0xe8` (undocked) | `21:08:19`: **Left docked, Right out — consistent with ADR-024's own not-both-docked reading (only "both docked" maps to 0x00; "Left only" correctly reads as not-both, 0xe8)** | @@ -234,16 +234,16 @@ reconnect: | 21:11:30, 21:12:12 | `0x00` (docked) | not individually checked | | 21:12:38 | `0xe8` (undocked) | not individually checked | | 21:12:41 | `0x00` (docked) | not individually checked | -| 21:13:01 | `0x00` (docked) | not individually checked | +| 21:13:01 | `0x00` (docked) | 21:12:59–21:13:09: **Left docked, Right out** (`CAP-050-EVENT-NOTES.md`) — a **counter-example** under ADR-024's binary | **Consistency with the wider `CAP-036`–`CAP-042`/`CAP-048`/`CAP-049` lineage (per this phase's own cross-check instruction):** the pattern here — mostly `0x00`, with `0xe8` appearing exactly at the sessions' genuine not-both-docked moments — matches `CAP-037`'s 26/26 and `CAP-039`'s 10/10 -same-session confirmations, and the one flagged discrepancy (§4's `21:03:04` stale-reading case) -matches, rather than contradicts, `CAP-048-FINDINGS.md` §5's own two counter-examples of the same -"fresh reconnect catches a pre-transition value" shape — **no contradiction with the established -lineage is found; this session's own single anomaly fits an already-documented, still-🟡-HYPOTHESIS -pattern rather than opening a new one.** +same-session confirmations; but under ADR-024's both-vs-not-both binary this session has **five** counter-examples, not one — `0x00` while +not both buds were seated at 21:01:14 (both outside), 21:02:04, 21:03:04, 21:04:07 and 21:13:01 (Left docked, Right out) — against one consistent +not-both reading (21:08:19, `0xe8`). The five are many more than a "stale reading after a fresh reconnect" (`CAP-048-FINDINGS.md` §5) explains; +they fit ADR-049 (which superseded ADR-024's Decision): 🟡 `0x00` ⇔ no bud worn — the out-of-case bud was in hand or on the table each time. +*(corrected 2026-10-15, `ai-sessions/0094`: this said "this session's own single anomaly")* ## 6. Recalled mis-docking (wrong-orientation) event — **no clear video evidence found; a confirmed absence, not a guess** (🟡 targeted re-check, not exhaustive) diff --git a/captures/CAP-051-2026-09-14_21-42-55_21-44-09-Group_AM/CAP-051-EVENT-NOTES.md b/captures/CAP-051-2026-09-14_21-42-55_21-44-09-Group_AM/CAP-051-EVENT-NOTES.md index 412f6ed1..7d190318 100644 --- a/captures/CAP-051-2026-09-14_21-42-55_21-44-09-Group_AM/CAP-051-EVENT-NOTES.md +++ b/captures/CAP-051-2026-09-14_21-42-55_21-44-09-Group_AM/CAP-051-EVENT-NOTES.md @@ -19,7 +19,8 @@ DLCI 0x04). Test-ID would be a `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` addition, out of this skeleton-creation prompt's own scope (see `ai-sessions/0005_MAINTENANCE_PROMPT_2026_09_09.md` Task 4); this is flagged here as a follow-up, not silently assigned one, and stays flagged (not actioned) after this -video-only pass too, per `ai-sessions/0020_CAPTURE_PROMPT_2026_09_14.md`'s own scope boundary. The +video-only pass too, per `ai-sessions/0020_CAPTURE_PROMPT_2026_09_14.md`'s own scope boundary (the +candidate `ANC-005` was declined 2026-09-18, `ai-sessions/0031`; `CAP-051-FINDINGS.md` §5 item 4). The existing `ANC-*` Test-IDs cover the UI-level ANC mode change itself; this session's own question (which channel(s) carry the write) is distinct from those. @@ -31,7 +32,7 @@ existing `ANC-*` Test-IDs cover the UI-level ANC mode change itself; this sessio | Group(s) | AM (new) | | Date | 2026-09-14 | | Firmware version | `release_5.203` | -| Test device | Pixel 7a, official Pixel Buds Companion App; full DLCI 0x02 traffic retained, raw-path extraction — **note:** whether raw-path/non-`btsnooz.py` logging was actually used is a log-file property this video-only session cannot confirm from the recording alone; that specific sub-claim is left open for the separate log-analysis session, not marked confirmed just because this cell (inherited from the skeleton) states it | +| Test device | Pixel 7a, official Pixel Buds Companion App; full DLCI 0x02 traffic retained, raw-path extraction — confirmed by the log analysis (raw path, untruncated, `CAP-051-FINDINGS.md` §0; see the Log file row) | | Video file | `CAP-051-recording.mp4`, duration 73.758489s (`ffprobe`), 1280×720 @ 30fps, wall-clock overlay 21:42:55 → 21:44:09 (2026-09-14). Watched in full via dense `ffmpeg -ss -frames:v 1` extraction (1s spacing across the full duration, tightened to 0.5s around each action) — clearly shows the in-app tap and each physical press-and-hold gesture, each isolated by a visible gap with no contact/tap in between. | | Log file | `CAP-051-btsnoop_hci.log` (209.66s, 2,457 packets, 0/2,457 `cap_len≠len` mismatches — **confirmed raw path, untruncated**, `CAP-051-FINDINGS.md` §0) | | Buds MAC (partial, per `AGENTS.md` §7/§9) | `04:00:6e:cf:6e:07` | @@ -74,7 +75,7 @@ not itself confirmed by anything visible in this recording. | ~21:43:01–21:43:06 | "Use Bluetooth" toggle switched on; Bluetooth quick-settings panel shows "Pixel Buds Pro 2 van Ted — Connecting…" | User | — | Wire: `Connection Complete` chandle `0x0002`, frame 786, `21:43:07.657` — `CAP-051-FINDINGS.md` §1 | | ~21:43:16 | Device details screen open (`Pixel Buds Pro 2 van Ted`, Active). On-screen: Left 100%, Case 54%, Right 100%. Active noise control row visible, **"Adaptive" already selected** — this is the screen's resting state when it was opened, not an action performed in this session. | User (App) | — | Wire: initial `Notify ANC state`, frame 1093, `21:43:11.971`, Current=`0x40`=Adaptive — matches on-screen state — `CAP-051-FINDINGS.md` §2 | | 21:43:26 (tap ~21:43:26, confirmed on-screen by 21:43:27) | **In-app ANC-mode tap** (`QuickActionsFragment` toggle group, Active noise control row): user's finger taps "Transparency". Mode changes on-screen from "Adaptive" to "Transparency". Single isolated tap — no other in-app tap observed anywhere else in the video. | User (App) | `ANC`-family | Wire: `Set ANC state` frame 1527, `21:43:28.107`, new_mode=`0x80`=Transparency; ACK frame 1529; `Notify` frame 1530 confirms — genuine Set+ACK+Notify sequence, the official DLCI 0x04 path. **No DLCI 0x02 write found at this moment (confirmed absence)** — `CAP-051-FINDINGS.md` §2/§3 | -| 21:43:38–~21:43:41/42 (contact visible continuously in this window; mode-change confirmed on-screen by 21:43:42) | **Physical press-and-hold gesture #1**: finger visibly pressed against the earbud (right side of frame; Left/Right identity not independently confirmed from video, see Procedure note above) for ~3–4s. Mode changes on-screen from "Transparency" to "Noise cancellation". | User (Hardware) | `TOUCH-007` | Wire: `Notify ANC state` frame 1707, `21:43:43.336`, Current=`0x08`=Noise cancellation — **no preceding `Set`(`0x12`) or `Get`(`0x11`) anywhere in the log**, matching `CAP-038-FINDINGS.md` §5's "Notify without Set" pattern. **No DLCI 0x02 write found (confirmed absence)** — `CAP-051-FINDINGS.md` §2/§3 | +| 21:43:38–~21:43:41/42 (contact visible continuously in this window; mode-change confirmed on-screen by 21:43:42) | **Physical press-and-hold gesture #1**: finger visibly pressed against the earbud (right side of frame; Left/Right identity not independently confirmed from video, see Procedure note above) for ~3–4s. Mode changes on-screen from "Transparency" to "Noise cancellation". | User (Hardware) | `TOUCH-007` | Wire: `Notify ANC state` frame 1707, `21:43:43.336`, Current=`0x08`=Noise cancellation — **no `Set`(`0x12`) or `Get`(`0x11`) since the previous Notify (1533)** — the log's only `08 12` is the tap's 1527, its only `08 11` the connect read 1072, matching `CAP-038-FINDINGS.md` §5's "Notify without Set" pattern. **No DLCI 0x02 write found (confirmed absence)** — `CAP-051-FINDINGS.md` §2/§3 | | ~21:43:51/52–~21:43:54 (contact visible continuously in this window; mode-change confirmed on-screen by 21:43:55) | **Physical press-and-hold gesture #2**: same earbud, finger pressed again for ~3s. Mode changes on-screen from "Noise cancellation" to "Adaptive". Gap since gesture #1's release (~21:43:42) to this gesture's contact start (~21:43:51/52) is **~9–10s — at or just under** the planned ≥10s isolation window. | User (Hardware) | `TOUCH-007` | Wire: `Notify ANC state` frame 1754, `21:43:56.002`, Current=`0x40`=Adaptive — again no `Set`/`Get`. **No DLCI 0x02 write found (confirmed absence)** — `CAP-051-FINDINGS.md` §2/§3 | | ~21:44:02–~21:44:03 (contact visible continuously in this window; mode-change confirmed on-screen by 21:44:04) | **Physical press-and-hold gesture #3**: same earbud, finger pressed a third time for ~2s. Mode changes on-screen from "Adaptive" to "Transparency". Gap since gesture #2's release (~21:43:55) to this gesture's contact start (~21:44:02) is **~7s — clearly under** the planned ≥10s isolation window. | User (Hardware) | `TOUCH-007` | Wire: `Notify ANC state` frame 1801, `21:44:05.102`, Current=`0x80`=Transparency — again no `Set`/`Get`. **No DLCI 0x02 write found (confirmed absence)** — `CAP-051-FINDINGS.md` §2/§3 | | 21:44:04–21:44:09 | No further action observed. Device details screen remains open, ANC mode remains "Transparency" through video end. Session/video end at 21:44:09. | — | — | Wire: only the already-documented ~10s periodic DLCI 0x02/0x04/0x08 push recurs (e.g. frame 1796, `21:44:03.087`) — `CAP-051-FINDINGS.md` §3 | @@ -120,8 +121,8 @@ write exists. done. - [x] Flag, as a follow-up outside this prompt's own scope, whether a dedicated Test-ID for this question should be added to `TESTPLAN_BLUETOOTH_HCI_SNOOP.md` (see the Purpose section - above). — a candidate ID (`ANC-005`) is proposed in `CAP-051-FINDINGS.md` §5 item 4, awaiting - maintainer sign-off — not unilaterally added. + above). — a candidate ID (`ANC-005`) was proposed in `CAP-051-FINDINGS.md` §5 item 4 and + declined 2026-09-18 (`ai-sessions/0031`): `TOUCH-007`'s row covers it. - [x] Rename this capture's folder from the `yyyy-MM-dd_HH-mm-ss_HH-mm-ss` placeholder to the actual session date/start-time/end-time — done, via `git mv`, to `CAP-051-2026-09-14_21-42-55_21-44-09-Group_AM` (video overlay start/end times). diff --git a/captures/CAP-051-2026-09-14_21-42-55_21-44-09-Group_AM/CAP-051-FINDINGS.md b/captures/CAP-051-2026-09-14_21-42-55_21-44-09-Group_AM/CAP-051-FINDINGS.md index 5f0c7fe4..488e121d 100644 --- a/captures/CAP-051-2026-09-14_21-42-55_21-44-09-Group_AM/CAP-051-FINDINGS.md +++ b/captures/CAP-051-2026-09-14_21-42-55_21-44-09-Group_AM/CAP-051-FINDINGS.md @@ -69,6 +69,7 @@ $ tshark -r CAP-051-btsnoop_hci.log -Y "btrfcomm.dlci==4 and btrfcomm.len>0" \ | **1527** | **21:43:28.107** | **Sent** | `08 12 00 14 01 e8 e8 80 <16 reserved bytes>` | **Set ANC state, new_mode=`0x80`=Transparency** | | 1529 | 21:43:28.219 | Rcvd | `ff 01 00 06 08 12 01 e8 e8 80` | ACK for the Set above | | 1530 | 21:43:28.299 | Rcvd | `08 13 00 04 01 e8 e8 80` | Notify, Current=`0x80`=Transparency | +| 1533 | 21:43:28.375 | Rcvd | `08 13 00 04 01 e8 e8 80` | Notify repeated, same bytes as 1530 *(row added 2026-10-15, `ai-sessions/0094`)* | | **1707** | **21:43:43.336** | Rcvd | `08 13 00 04 01 e8 e8 08` | Notify, Current=`0x08`=**Noise cancellation** — **no `08 12` Set and no `08 11` Get anywhere nearby** | | **1754** | **21:43:56.002** | Rcvd | `08 13 00 04 01 e8 e8 40` | Notify, Current=`0x40`=**Adaptive** — again no Set/Get nearby | | **1801** | **21:44:05.102** | Rcvd | `08 13 00 04 01 e8 e8 80` | Notify, Current=`0x80`=**Transparency** — again no Set/Get nearby | @@ -95,7 +96,7 @@ tolerance, `PROTOCOL.md` §4.1):** reproduced here for the first time with a positively-identified, video-confirmed trigger**: the in-app tap produces a genuine `Set`(`0x12`)+ACK+`Notify` sequence on DLCI 0x04 (the confirmed, official path, `PROTOCOL.md` §4.1), while all three physical press-and-hold gestures produce only a -spontaneous `Notify`(`0x13`) with no preceding `Set` or `Get` anywhere in the log — matching +spontaneous `Notify`(`0x13`) with no `Set` or `Get` between the previous Notify and it (the log's only `08 12` is the tap's 1527, its only `08 11` the connect read 1072) *(corrected 2026-10-15, `ai-sessions/0094`)* — matching `CAP-027-FINDINGS.md` §4's established mechanism (the Buds decide the mode change on-device and only report it out). **This resolves `CAP-038-FINDINGS.md` §5's own open item as a confirmed positive**: its "plausibly a hardware press-and-hold gesture" reading (offered without video evidence, since that @@ -126,10 +127,10 @@ acknowledgement, not a data-carrying UIH frame: ``` **The only non-empty traffic in this window is `Rcvd`-direction** (Buds→phone), and it is of two kinds (`python3 scripts/pwrpc_decode.py CAP-051-btsnoop_hci.log`, frames 1520–1811): -- the runtime-info stream (`SubscribeRuntimeInfo`, every ~10 s: 1517/1565/1711/1796 at `21:43:23`/`33`/`43`/`44:03`, not tied to the actions), e.g. 1565 +- the runtime-info stream (`SubscribeRuntimeInfo`, every ~10 s with one 20 s gap — no push decoded at `21:43:53`: 1517/1565/1711/1796 at `21:43:23`/`33`/`43`/`44:03`, not tied to the actions), e.g. 1565 `7e00a5032a1f10e2f3c88b8a341800320c1204086410011a04086410013a06080010001800080710151dea71de7d5e2590821ee64863cfb07e`; - the **settings stream** (`SubscribeToSettingsChanges`, subscribed at 1177): one `SERVER_STREAM` `4:{13:n}` right after each of the four Notifies — 1534 - `13:3` (the tap, 0.001 s after Notify 1533), **1717 `13:2`** (gesture #1, +0.144 s after 1707), **1755 `13:4`** (gesture #2, +0.002 s after 1754), **1802 + `13:3` (the tap, 0.077 s after Notify 1530 and 0.001 s after its byte-identical repeat 1533), **1717 `13:2`** (gesture #1, +0.144 s after 1707), **1755 `13:4`** (gesture #2, +0.002 s after 1754), **1802 `13:3`** (gesture #3, +0.048 s after 1801); raw 1755 `7e 00 a5 03 2a 04 22 02 68 04 08 07 10 15 1d ea 71 de 7d 5e 25 f5 ad 21 28 39 67 a2 f7 7e` (`2a 04 22 02 68 04` = `4:{13:4}`, `08 07` = `SERVER_STREAM`, `10 15` = channel 21). 🟢 FACT (this capture): each hold is reported twice — on the Message Stream and on MAESTRO setting 13 (`PROTOCOL.md` §4.1, Update of 2026-10-14; `DESKRESEARCH_FINDINGS.md` 2026-10-14). The Notify at 1993 (21:45:45.603) has no stream @@ -183,7 +184,7 @@ Per `ai-sessions/0023_CROSSCHECK_PROMPT_2026_09_15.md` Phase 1: traced `fye.a(qh - **`ANC`-family / `TOUCH-007`**: all four transitions individually confirmed and time-correlated against DLCI 0x04's already-FACT ANC path (§2) — the in-app tap is the first video-confirmed, wire-correlated `Set` frame for a `QuickActionsFragment` tap specifically (as opposed to the - Device-details-screen ANC row used by `CAP-001`/`CAP-006`); `TOUCH-007` gains a third confirming + Device-details-screen ANC row used by `CAP-001`/`CAP-006`); `TOUCH-007` gains a second confirming session (after `CAP-027`) with, for the first time, both physical-gesture video *and* a positively matched "Notify without Set" wire signature in the same session. - **This Group's own primary question (no assigned Test-ID, per `CAP-051-EVENT-NOTES.md`'s own diff --git a/captures/CAP-053-2026-10-05_17-46-34_17-49-39-Group_AO/CAP-053-EVENT-NOTES.md b/captures/CAP-053-2026-10-05_17-46-34_17-49-39-Group_AO/CAP-053-EVENT-NOTES.md index 41dd6dd9..031ac8b1 100644 --- a/captures/CAP-053-2026-10-05_17-46-34_17-49-39-Group_AO/CAP-053-EVENT-NOTES.md +++ b/captures/CAP-053-2026-10-05_17-46-34_17-49-39-Group_AO/CAP-053-EVENT-NOTES.md @@ -2,7 +2,7 @@ **Status:** ✅ **Captured 2026-10-05 and analysed 2026-10-05** (`ai-sessions/0072`). Film and both HCI logs reviewed in full; the analysis is in `CAP-053-FINDINGS.md`. **Central result:** field 18 was written **once**, 0.2–0.5 s after the filmed **Save** tap (frame 1838); the two release-only -windows (12.8 s and 18.7 s) and the leave-the-screen condition wrote **no** field 18. The "navigate away" step was done differently from the plan: the +windows (12.8 s and 18.5 s) and the leave-the-screen condition wrote **no** field 18. The "navigate away" step was done differently from the plan: the maintainer left the EQ screen with the **Home** gesture and came back through **Recents**, not with Back. The planned skeleton is kept unchanged as Appendix A (with the maintainer's app-version block). @@ -116,7 +116,7 @@ Test-IDs named by the Group: `EQS-004` (Bass slider) — exercised (slider 4); ` **Status:** 🔲 **Not yet captured — skeleton only.** Fill in every `TBD` below after recording, per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §5 (analysis) and §8 (what to update), and `PROJECT_RULES.md` rule 11/14 (reproducibility metadata). Once reviewed, rename this folder from -the placeholder `CAP-053-2026-10-05_17-46-34_17-49-39-Group_AO` to the actual session +the placeholder `CAP-053-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_AO` to the actual session date/start-time/end-time, e.g. `CAP-053-2026-09-15_08-30-00_08-40-00-Group_AO`. **Purpose (`CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group AO, added 2026-09-13, diff --git a/captures/CAP-053-2026-10-05_17-46-34_17-49-39-Group_AO/CAP-053-FINDINGS.md b/captures/CAP-053-2026-10-05_17-46-34_17-49-39-Group_AO/CAP-053-FINDINGS.md index ec265ed9..d8fd4d9c 100644 --- a/captures/CAP-053-2026-10-05_17-46-34_17-49-39-Group_AO/CAP-053-FINDINGS.md +++ b/captures/CAP-053-2026-10-05_17-46-34_17-49-39-Group_AO/CAP-053-FINDINGS.md @@ -30,10 +30,10 @@ grep WriteSetting`; exit 0; raw frames and CRC below): |---|---|---|---|---|---|---| | 1577 | 17:47:35.998 | **16** | −1.0, 0.0, 4.0, 2.0, **5.0** | Upper treble drag 62.6–63.62 | during the drag | 1583 / OK 1584 | | 1639 | 17:47:50.268 | **16** | −1.0, 0.0, 4.0, **−4.8**, 5.0 | Treble drag 76.6–77.85 | during the drag | 1643 / 1644 | -| 1727 | 17:48:10.180 | **16** | −1.0, 0.0, **0.3**, −4.8, 5.0 | Mid drag 96.6–97.59 | 0.2 s | 1733 / 1734 | +| 1727 | 17:48:10.180 | **16** | −1.0, 0.0, **0.3**, −4.8, 5.0 | Mid drag 96.6–97.59 | ≈ 0.1 s (film 97.68 ± 0.13 s) *(corrected 2026-10-15, `ai-sessions/0094`)* | 1733 / 1734 | | 1799 | 17:48:29.824 | **16** | −1.0, 0.0, **4.2**, −4.8, 5.0 | second Mid drag 116.36–117.36 | during the drag | 1804 / 1805 | | 1838 | 17:48:40.823 | **18** | −1.0, 0.0, 4.2, −4.8, 5.0 | **Save tap** 127.86–128.10 (toast "EQ saved" 128.36) | **0.2–0.5 s** | 1844 / 1845 | -| 1914 | 17:48:52.515 | **16** | −1.0, **4.3**, 4.2, −4.8, 5.0 | Bass drag 138.87–139.87 | during the drag | 1916 / 1917 | +| 1914 | 17:48:52.515 | **16** | −1.0, **4.3**, 4.2, −4.8, 5.0 | Bass drag 138.87–139.87 | ≈ 0.15 s after the release (film 140.02 ± 0.13 s) *(corrected 2026-10-15, `ai-sessions/0094`)* | 1916 / 1917 | | Condition (Group AO) | Window | Field-18 writes | Result | |---|---|---|---| @@ -71,6 +71,8 @@ Command: `python3 scripts/pwrpc_decode.py CAP-053-btsnoop_hci.log` (119 lines; s Opening Device details, Sound and the EQ screen sent **nothing** on any channel (no RFCOMM data frame on handle 0x0002 between 17:47:08.512 — frame 1083, the end of the connect sweep — and 1577 at 17:47:35.998; the screens were opened at 17:47:09.4, 17:47:16.7, 17:47:18.5) — 🟢, as `CAP-036`'s screen-open negative. +Command: `tshark -r CAP-053-btsnoop_hci.log -Y "bthci_acl.chandle==0x0002 && btrfcomm.len>0 && frame.number>1083 && frame.number<1577"` → 0 frames, +exit 0; positive control: the same filter with `frame.number>=1083 && frame.number<=1577` lists 1083 and 1577 *(command added 2026-10-15, `ai-sessions/0094`)*. ## 3. Wire against the code (`v1.0.955078536-10253511`, the run's version) @@ -97,7 +99,8 @@ the repeated drag in step 4. `[Group][Code][Len BE][Value]`; 0 leftover bytes): Play-services claim `03 08 00 02 01 25` (702), session nonce `03 0a` (704), Model ID `03 01 … da 2d b1`, BLE address `03 02 … 56 30 04 c9 fd b5`, `03 09` "Revision 6", `03 0b` (25 bytes, FHN, out of scope), battery `03 03 00 03 64 64 ff` ×3 per push, `08 11` → `08 13 00 04 01 e8 e8 40` (Adaptive, Settable `e8`), SASS `07 10`/`07 11`/`07 21`/`07 40`/`07 41 "in-use"`/`07 42`/`07 34`, ACKs. 🟢 - No `08 12` (no ANC change). The Buds repeat battery every push (17:47:51, 17:48:11, 17:48:41, 17:48:51, 17:49:21, 17:49:51, 17:50:01) together with + No `08 12` (no ANC change; `tshark -r CAP-053-btsnoop_hci.log -Y "bthci_acl.chandle==0x0002 && btrfcomm.dlci==0x04 && btrfcomm.len>0 && data.data + contains 08:12"` → 0 frames, exit 0; the same filter with `08:11` finds 720 `08110000` — command added 2026-10-15, `ai-sessions/0094`). The Buds repeat battery every push (17:47:51, 17:48:11, 17:48:41, 17:48:51, 17:49:21, 17:49:51, 17:50:01) together with GSND `0e 02`/`0e 01`/`04 03` and `AT+BIEV=2,100` within 10 ms — not tied to any filmed action. - **GSND CONTROL (DLCI 0x08):** the phone's opening burst (`05 0c`, `04 02`, `04 04`, `04 11`, `04 13`, `04 15`, `0e 04`, `09 03`, `03 01` with the time zone "Europe/Amsterdam", `01 07`, `02 05`, `02 0b`, `01 08`, `09 02`) and the Buds' answers incl. `03 02 … release_5.203` (822); `0e 01` entry 3 = 0x37 = diff --git a/captures/CAP-054-2026-10-05_18-02-33_18-08-29-Group_AP/CAP-054-EVENT-NOTES.md b/captures/CAP-054-2026-10-05_18-02-33_18-08-29-Group_AP/CAP-054-EVENT-NOTES.md index 3b55513e..0ea80416 100644 --- a/captures/CAP-054-2026-10-05_18-02-33_18-08-29-Group_AP/CAP-054-EVENT-NOTES.md +++ b/captures/CAP-054-2026-10-05_18-02-33_18-08-29-Group_AP/CAP-054-EVENT-NOTES.md @@ -28,7 +28,7 @@ classic connection. The planned skeleton is kept unchanged as Appendix A. ## Capture-integrity pre-flight -- The log starts 18:02:45.507 with a `Reset` (13.7 s after the film starts at 18:02:31.4) and LE scanning on (`LE Set Extended Scan Enable` frames +- The log starts 18:02:45.507 with a `Reset` (14.1 s after the film starts at 18:02:31.4) and LE scanning on (`LE Set Extended Scan Enable` frames 101–107); the film shows Bluetooth **off** until the tap at 18:02:47.9–48.2 — the same LE-only restart as in `CAP-053`. Nothing in the film's first 14 s needs the log (App info screen, notification shade, Quick Settings). - **The phone's controller was scanning throughout** (scan enables at 18:02:45, :48, :54, 18:04:20, 18:04:50, 18:05:37, 18:06:07, 18:06:37, 18:06:43, @@ -54,7 +54,7 @@ Phone time = film + 18:02:31.4. Frames are `CAP-054-btsnoop_hci.log` (= `CAP-058 | 18:02:47.9 – 18:02:48.2 | 16.59 – 16.86 | **Bluetooth on**; row "Pixel Buds Pro 2 van Ted · L: 100%, C: 54%, R: 100%" (not connected) | User | 1 | — | first `0xFE2C` report 18:02:47.764 (frame 129) | | 18:02:58.9 – 18:02:59.2 | 27.59 – 27.86 | **Force stop → OK** (Force stop greyed at 29.99 s) | User | 1 | — | none on the wire | | 18:03:05 – 18:03:13 | 31.99 – 41.99 | Recents → **Connected devices**: "Saved devices · …van Ted · L: 100%, C: 54%, R: 100%" (not connected) | User | 1/2 | — | — | -| 18:02:47.8 – 18:04:13.5 | 16 – 102 | **Baseline, lid closed** (≈ 92 s from Bluetooth on to the opening) | — | 3 | `BATT-002` | only the closed-case advertiser `36:91:c4:7d:54:e4`: 25 reports, flags/filter type 2 (hide UI), salt, then byte **`0x2a`** (129–907) | +| 18:02:47.8 – 18:04:13.5 | 16 – 102 | **Baseline, lid closed** (≈ 90 s from Bluetooth on to the opening) | — | 3 | `BATT-002` | only the closed-case advertiser `36:91:c4:7d:54:e4`: 25 reports, flags/filter type 2 (hide UI), salt, then byte **`0x2a`** (129–907) | | 18:04:16.511 | — | That advertiser's payload changes (new salt) | Buds | — | — | frame 917 | | 18:04:16.7 – 18:04:17.0 | 105.35 – 105.62 | **Settings: "C: 54%" → "C: 53%"** with the lid still closed | Android | not in the plan | `BATT-002` | no connection; nearest Buds frame 917 (0.2–0.5 s before) | | 18:04:18.2 – 18:04:19.5 | 106.85 – 108.12 | **Lid opened (#1)**; case LED lit | User | 4 | `BATT-007`, `CASE-003` | — | @@ -63,7 +63,7 @@ Phone time = film + 18:02:31.4. Frames are `CAP-054-btsnoop_hci.log` (= `CAP-058 | 18:04:21.0 – 18:04:21.2 | 109.62 – 109.85 | **Heads-up "Pixel Buds Pro 2 van Ted · Left 100% Case 53% Right 100%"**, headphone icon in the status bar | Android | 4 | `BATT-007` | **no connection** (no ACL/LE link to the Buds until 18:06:18) | | 18:04:21.512 → 18:05:03.919 | — | A second Buds advertiser `59:c2:44:e0:c7:4e`: filter type **0 (show UI)**, salt, Random Resolvable Data `0x46` + 4 bytes; **no battery field** | Buds | 4 | `BATT-007` | 78 reports, frames 967–1331 | | 18:04:22 – 18:05:03 | 110 – 152 | Lid open ≈ 44 s, nothing touched; panel stays "Saved devices … L 100 C 53 R 100" | — | 4 | `BATT-007` | — | -| 18:05:03.5 – 18:05:04.0 | 152.12 – 152.62 | **Lid closed (#1)** (audio click 152.25 s); headphone icon gone | User | 4 | `CASE-006` (lid part) | closed-case advertiser back to `0x2a` from 18:05:05.320 (1336) | +| 18:05:03.5 – 18:05:04.0 | 152.12 – 152.62 | **Lid closed (#1)** (audio click 152.25 s); headphone icon gone | User | 4 | — (buds never left the case, so not `CASE-006`) | closed-case advertiser back to `0x2a` from 18:05:05.320 (1336) | | 18:05:04 – 18:05:36.5 | 152.6 – 185 | Lid closed ≈ 33 s | — | 4 (wait 30 s) | — | 36:91 `0x2a` ×19+ | | 18:05:36.5 – 18:05:37.0 | 185.10 – 185.62 | **Lid opened (#2)** | User | 4 (repeat) | `BATT-007`, `CASE-003` | 36:91 → `0x29` at 18:05:37.278 (1414); scan restart 18:05:37.655 | | 18:05:37.949 → 18:06:15.226 | — | Show-UI advertiser `6e:e9:9a:a6:b7:26` (type 0, salt, RRD `0x46`), no battery field | Buds | 4 | `BATT-007` | 77 reports, 1451–1768 | @@ -86,8 +86,8 @@ Phone time = film + 18:02:31.4. Frames are `CAP-054-btsnoop_hci.log` (= `CAP-058 | Step | Plan | Done | Status | |---|---|---|---| | 1 | Bluetooth on, Buds bonded not connected, app force-stopped, panel on film | Bluetooth turned on, force stop on film, Connected devices "Saved devices" (not connected) | done (order: Bluetooth on before the force stop) | -| 2 | Start log and film | film first, log 13.7 s later (LE-only restart) | done | -| 3 | Lid closed ≥ 60 s | ≈ 92 s after Bluetooth on (≈ 80 s after the force stop) | done | +| 2 | Start log and film | film first, log 14.1 s later (LE-only restart) | done | +| 3 | Lid closed ≥ 60 s | ≈ 90 s after Bluetooth on (≈ 80 s after the force stop) | done | | 4 | Lid open ≥ 30 s, close, wait 30 s; repeat once | open 44 s, closed 33 s, open again — **never closed again** | done once; the repeat's close **skipped** | | 5 | One bud out ≥ 15 s, back, ≥ 15 s | Left out 20 s, back 26 s | done (the phone connected — see FINDINGS §3) | | 6 | The other bud | Right out 34 s, back | done (connected) | @@ -115,7 +115,7 @@ Test-IDs named by the Group: `BATT-007` — exercised twice (lid openings); `BAT **Status:** 🔲 **Not yet captured — skeleton only.** Fill in every `TBD` below after recording, per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §5 (analysis) and §8 (what to update), and `PROJECT_RULES.md` rule 11/14 (reproducibility metadata). Once reviewed, rename this folder from -the placeholder `CAP-054-2026-10-05_18-02-33_18-08-29-Group_AP` to the actual session +the placeholder `CAP-054-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_AP` to the actual session date/start-time/end-time, e.g. `CAP-054-2026-09-15_08-30-00_08-40-00-Group_AP`. **Purpose (`CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group AP; redesigned 2026-10-03, `ai-sessions/0069`, lead L68-6):** the Fast Pair diff --git a/captures/CAP-054-2026-10-05_18-02-33_18-08-29-Group_AP/CAP-054-FINDINGS.md b/captures/CAP-054-2026-10-05_18-02-33_18-08-29-Group_AP/CAP-054-FINDINGS.md index a3713a7d..4d6ce3aa 100644 --- a/captures/CAP-054-2026-10-05_18-02-33_18-08-29-Group_AP/CAP-054-FINDINGS.md +++ b/captures/CAP-054-2026-10-05_18-02-33_18-08-29-Group_AP/CAP-054-FINDINGS.md @@ -52,7 +52,7 @@ closed-case form (`52:39:01:d3:49:1e`: `10 52 13d3802844 21 54a6 2a b029be65f6a0 removal. 🟡 `36:91…`: the closed-case form of `CAP-043`, RSSI −13 to −28 dBm next to the phone, its post-salt byte follows the filmed lid state in 225 of 225 reports, its payload changed at 18:04:16.511 — 0.2–0.5 s before Settings changed "C: 54%" to "C: 53%". -**Answer.** 🟢 With this firmware the Buds send **no** Battery Notification field in clear — not with the lid closed, not in the ≈ 44 s and ≈ 50 s after +**Answer.** 🟢 With this firmware the Buds send **no** Battery Notification field in clear — not with the lid closed, not in the ≈ 44 s and ≈ 39 s *(corrected 2026-10-15, `ai-sessions/0094`)* after each lid opening, not with one bud out — and on lid open they switch to a **show-UI** account-key advertisement (filter type 0) with Random Resolvable Data. The spec's "use case" sentence is not followed in the documented form. diff --git a/captures/CAP-056-2026-09-28_17-30-53_17-35-58-Group_AR/CAP-056-EVENT-NOTES.md b/captures/CAP-056-2026-09-28_17-30-53_17-35-58-Group_AR/CAP-056-EVENT-NOTES.md index 5a53f296..0ff4c449 100644 --- a/captures/CAP-056-2026-09-28_17-30-53_17-35-58-Group_AR/CAP-056-EVENT-NOTES.md +++ b/captures/CAP-056-2026-09-28_17-30-53_17-35-58-Group_AR/CAP-056-EVENT-NOTES.md @@ -130,7 +130,7 @@ Frame numbers: `CAP-056-btsnoop_hci.log`. Checklist state written as `{NC Off Ad 1815 = 1959, 1843 = 1991); only the film tells them apart (FINDINGS §2). - [x] (A) which bit clears: Adaptive → boolean **4**, Transparency → boolean **3** (FINDINGS §1). - [ ] (A) whether "Customize right" shows a change made on "Customize left" — **not tested** (A2 skipped) (FINDINGS §2). -- [x] (B) W2/W5: every field-2 write against the filmed switch labelled "In-ear detection" — 5 writes, 5 filmed taps (FINDINGS §3). +- [x] (B) W2/W5: every field-2 write against the filmed switch labelled "In-ear detection" — 5 filmed taps, 6 writes (the fifth tap's 4344 was cancelled by the phone, 4360, and re-sent as 4361) (FINDINGS §3). - [x] (B) W1 vs W3: pause, Buds `DISC`, Settable, other pushes with OHD on vs off (FINDINGS §4). ## Open questions — carried into `CAP-056-FINDINGS.md` @@ -146,7 +146,8 @@ Frame numbers: `CAP-056-btsnoop_hci.log`. Checklist state written as `{NC Off Ad `INEAR-003`, `INEAR-004` and `PAIR-003` are referenced. - [x] `CAP-056-FINDINGS.md` written (rule 4a: commands + raw hex). - [x] Folder renamed to `CAP-056-2026-09-28_17-30-53_17-35-58-Group_AR` (overlay clock, maintainer-approved in chat 2026-09-28). -- [ ] Capture Index / `id_registry.csv` / `TESTPLAN` rows — after the checkpoint (`ai-sessions/0055` Phase D). +- [x] Capture Index / `id_registry.csv` / `TESTPLAN` rows — after the checkpoint (`ai-sessions/0055` Phase D) — done: Capture Index row `analyzed`, + `id_registry.csv` row `analyzed`, `TESTPLAN` rows `HOLD-005`, `INEAR-001`, `INEAR-002`, `INEAR-004` cite `CAP-056` (checked 2026-10-15, `ai-sessions/0094`). --- diff --git a/captures/CAP-056-2026-09-28_17-30-53_17-35-58-Group_AR/CAP-056-FINDINGS.md b/captures/CAP-056-2026-09-28_17-30-53_17-35-58-Group_AR/CAP-056-FINDINGS.md index 1bdab418..3dcbae10 100644 --- a/captures/CAP-056-2026-09-28_17-30-53_17-35-58-Group_AR/CAP-056-FINDINGS.md +++ b/captures/CAP-056-2026-09-28_17-30-53_17-35-58-Group_AR/CAP-056-FINDINGS.md @@ -134,7 +134,8 @@ Raw bytes: ``` - 🟢 Five filmed taps on the switch labelled **"In-ear detection — Earbuds automatically play audio when in and pause audio when out"** (label read - at t = 283 s) produced five `WriteSetting 4:{2:…}` with the value of the new switch position, each acknowledged OK. Together with `CAP-024` + at t = 283 s) produced six `WriteSetting 4:{2:…}` with the value of the new switch position — the fifth tap's 4344 was cancelled by the phone + (`CLIENT_ERROR CANCELLED` 4360) and re-sent as 4361 — each acknowledged OK *(corrected 2026-10-15, `ai-sessions/0094`)*. Together with `CAP-024` 1850/1912 (byte-identical) the UI switch writes field 2 in both directions. - 🟢 **Independent confirmation from the Buds themselves:** after every field-2 write the Buds send the Fast Pair Audio-switch (SASS) "Notify capability" `07 11` with flags `0xb8`/`0xb0`, unprompted (no phone `07 10` before 2176, 2852, 3630, 4052). The official SASS page @@ -194,8 +195,8 @@ Raw examples: 2253 `040500020804`, 2318 `040500020806`, 2453 `040500020805`, 368 reported `0x40`), and with none worn the Buds report `13:0` / `Notify … 00 20`. - 🟢 **Settable `00` with no bud worn, in-ear detection on:** 3940 (17:35:28.123) `01 e8 00 20` while both buds were in the hands (film 17:35:22–28) — one more sample for ADR-024's 🟡 "`0x00` = no bud worn", no counter-example. Every other `Notify` of the session reads `e8` with at least one bud - worn. 🔴 **Settable with in-ear detection off and no bud worn — no sample** (no `Get` was sent in rows 11/12's window; the re-open `Get`s 3137 - (17:34:37.601) and 3476 (17:34:56.721) fall during a re-insertion and read `e8 08`). + worn. 🔴 **Settable with in-ear detection off and no bud worn — no sample** (no `Get` was sent in rows 11/12's window; the re-open `Get`s 3133 + (17:34:37.553) and 3472 (17:34:56.644) fall during a re-insertion and read `e8 08` — answers 3137 (17:34:37.601) and 3476 (17:34:56.721)). - 🟡 **DLCI 0x08 `04 05` (the `CAP-050` §4 "fluctuates near dock changes" code) tracks wear while in-ear detection is on:** `06` both worn, `04` / `05` one bud out (a different value for each side of the head — rows 1 vs 3, 9 first step), `03` both out, and it sits at `01` whenever in-ear detection is off (2854, 2920, 3187, 3259, 3518, 4054, 4099, 4166) and at connect with it off (1133). `01 09` `0a 03 0b 06 13|12` accompanies the changes (2254, 2320, diff --git a/captures/CAP-058-2026-10-05_21-39-51_21-46-22-Group_AT/CAP-058-EVENT-NOTES.md b/captures/CAP-058-2026-10-05_21-39-51_21-46-22-Group_AT/CAP-058-EVENT-NOTES.md index 7622cbe0..2cf58800 100644 --- a/captures/CAP-058-2026-10-05_21-39-51_21-46-22-Group_AT/CAP-058-EVENT-NOTES.md +++ b/captures/CAP-058-2026-10-05_21-39-51_21-46-22-Group_AT/CAP-058-EVENT-NOTES.md @@ -160,7 +160,7 @@ scenario) — X4/X5, X11/X12. registered `planned` on 2026-09-18 by `ai-sessions/0031` but, unlike `CAP-052`–`CAP-057`, had no placeholder folder). Fill in every `TBD` below after recording, per `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §5 (analysis) and §8 (what to update), and `PROJECT_RULES.md` rule 11/14 (reproducibility metadata). Once reviewed, rename this folder from the placeholder -`CAP-058-2026-10-05_21-39-51_21-46-22-Group_AT` to the actual session date/start-time/end-time. +`CAP-058-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_AT` to the actual session date/start-time/end-time. **Purpose (`CAPTURE_BLUETOOTH_HCI_SNOOP.md` Group AT, maintainer-approved 2026-09-18, `ai-sessions/0031`):** two prior attempts at `SDP-001` (`CAP-033`, `CAP-044`) were each capped at 🟡 HYPOTHESIS by a procedural gap (`CAP-033`: Forget before Force-stop; `CAP-044`: diff --git a/captures/CAP-058-2026-10-05_21-39-51_21-46-22-Group_AT/CAP-058-FINDINGS.md b/captures/CAP-058-2026-10-05_21-39-51_21-46-22-Group_AT/CAP-058-FINDINGS.md index a6a95f15..846aac94 100644 --- a/captures/CAP-058-2026-10-05_21-39-51_21-46-22-Group_AT/CAP-058-FINDINGS.md +++ b/captures/CAP-058-2026-10-05_21-39-51_21-46-22-Group_AT/CAP-058-FINDINGS.md @@ -45,7 +45,7 @@ state by a "force stop"."* — so the stopped state does not block a bind; and ` (`ActiveServices.java` ≈ lines 4313–4361 and 5726–5910 of that revision). 🟡 for this phone's build (the device may differ from `main`). **The revised Group AT's rule, applied literally:** outcome **(a)** — "no `am_proc_start` between the force stop and the SDP browse" — is **not met**: -the process was present again 16 s after the force stop. Outcome **(b)** — "a restart with its cause" — is met **in part**: the restart is on record +the process was present again at the next `dumpsys` run (21:40:51 laptop clock; laptop ↔ phone offset not measured, so its delay after the force stop is not known). Outcome **(b)** — "a restart with its cause" — is met **in part**: the restart is on record (two PIDs), its cause is not. 🟢 What can be concluded: through this path (force stop in Settings, then Settings' device pages, Forget, pairing) the companion app does **not** stay dead; "app never running" was not reached in this run. With the `CAP-054` observation (the app ran again after its force stop and opened MAESTRO at 18:06:22) and the context observation of the prompt (`am_proc_start … bound-service … @@ -86,11 +86,11 @@ firmware does not publish `3a046f6d-…` at all; the "default internal rfcomm so | Pairing | LE (Fast Pair) | Classic SSP | Link key | Outcome | |---|---|---|---|---| -| A, 21:36:28 (before the film) | key-based pairing `0x0c04`, passkey `0x0c07`, account key `0x0c0a` | Buds IO DisplayYesNo, phone confirms | type **0x05** (authenticated P-256) | SDP, RFCOMM 0x0c/0x0a/0x08/0x04, no MAESTRO; local disconnect, Forget at 21:36:42.545 | +| A, 21:36:28 (before the film) | key-based pairing `0x0c04`, passkey `0x0c07`, account key `0x0c0a` | Buds IO DisplayYesNo, phone confirms | type **0x05** (Authenticated Combination Key, P-192; `.log.last` 10333) *(corrected 2026-10-15, `ai-sessions/0094`)* | SDP, RFCOMM 0x0c/0x0a/0x08/0x04, no MAESTRO; local disconnect, Forget at 21:36:42.545 | | B, 21:37:06 (before the film) | as A | as A | 0x05 | SDP, MAESTRO opened 21:37:10.627; ended 0x13 21:37:25.348 | | 1, 21:41:17.6 (film: half-sheet *Connect*) | KBP 2156–2170, account key 2334 | DisplayYesNo 2204, confirmation 2223 → reply **2232** (no dialog on film) | 0x05 (2235) | SDP, MAESTRO 2823; "Ready to use — connected to someone else's account"; ACL ended 0x13 21:41:25.421 | | (failed), 21:41:38.4 (film: *Connect* again) | KBP 3161–3173, LE dropped 3211 | three attempts, each `User Confirmation Request Negative Reply` from the phone (3341, 3410, 3478) | — | "Couldn't connect" 21:41:45.6 | -| 2, 21:42:05.6 (film: Settings → Pair new device → *Pair*) | none before; KBP + account key `0x0c0a` on LE 0x000f afterwards (4627–4650, 21:42:30–32) | Buds IO **NoInputNoOutput** (3789), confirmation 3797 → reply **3801 (21:42:07.643)** = the filmed *Pair* tap (21:42:07.1–07.6) | type **0x04** (unauthenticated P-256) (3804) | SDP, MAESTRO 4284, connected to the end | +| 2, 21:42:05.6 (film: Settings → Pair new device → *Pair*) | none before; KBP + account key `0x0c0a` on LE 0x000f afterwards (4627–4650, 21:42:30–32) | Buds IO **NoInputNoOutput** (3789), confirmation 3797 → reply **3801 (21:42:07.643)** = the filmed *Pair* tap (21:42:07.1–07.6) | type **0x04** (Unauthenticated Combination Key, P-192) (3804) *(corrected 2026-10-15, `ai-sessions/0094`)* | SDP, MAESTRO 4284, connected to the end | 🟢 The Buds answer with a different IO capability in the two classic paths: DisplayYesNo after a Fast Pair key-based pairing, NoInputNoOutput for a Settings pairing — so Android's link key is authenticated (0x05) after Fast Pair and unauthenticated (0x04) after the Settings pairing. `PROTOCOL.md` diff --git a/captures/CAP-059-2026-09-20_17-16-59_17-22-29-Group_AU/CAP-059-EVENT-NOTES.md b/captures/CAP-059-2026-09-20_17-16-59_17-22-29-Group_AU/CAP-059-EVENT-NOTES.md index 5e557ff4..ec085d04 100644 --- a/captures/CAP-059-2026-09-20_17-16-59_17-22-29-Group_AU/CAP-059-EVENT-NOTES.md +++ b/captures/CAP-059-2026-09-20_17-16-59_17-22-29-Group_AU/CAP-059-EVENT-NOTES.md @@ -70,6 +70,7 @@ unassigned rather than guessed. | 17:17:49.6–50.76 | HFP connects (state 1→2); A2DP comes up. | Hardware | `PAIR-001` | §2/§3 | | 17:17:50.299–17:18:53.44 | DLCI 0x04 (Fast Pair Message Stream) contention window: 4 app claim attempts collide with Google Play services (attempt 1 fails, attempt 2 succeeds each time); after 17:18:53.44, Play services never re-claims again this session (11 further app claims all succeed on attempt 1). | Hardware/GMS | — | §4 | | 17:17:50.895 onward | DLCI 0x08 pushes `Group 0x0e Code 0x01` (Left/Right/Case battery) 19 times across the session, identical value (Left 100, Right 100, Case 97%) every time. | Hardware | — | §6, frame 1211 first instance | +| 17:18:18.957–17:18:27.583 | Five EQ writes `WriteSetting 4:{16:{…}}` on pw_rpc channel 21 (frames 2169, 2181, 2188, 2203, 2214). Row added 2026-10-03 (`ai-sessions/0069`, `A68-CAP-15`; `python3 scripts/pwrpc_decode.py CAP-059-btsnoop_hci.log`); the tap on film was not isolated. | User (app) | `EQP-006` (partial) | frames 2169–2214 | | 17:18:33.046 | App logs `Pairing: bond timed out` — contradicts the already-successful 17:17:49.121 bond; no `bond state` line logged at all. | App (defect) | `PAIR-001` | §3 | | 17:18:35.842 / :50.48 / :51.90 / 17:19:03.219 | Find My Buds, in wire order: **Left** ring (`04 01 00 01 02`, 2301), **Stop** (`… 00`, 2482), **Right** ring (`… 01`, 2504), **Stop** (`… 00`, 2589) — corrected 2026-10-03 (`ai-sessions/0069`, `A68-CAP-15`): this row read "Left ring start, Right ring start, (a repeat), Stop". Each command is answered by the Buds' ACK and followed by the Buds' own copy of the same message (2312, 2494, 2507, 2604, Received — the ring-status sync, `PROTOCOL.md` §4.4); ringing audible on the camera mic (3.0s-interval tone bursts) starting ≈3.1s after each command and continuing past the Message Stream socket's own release. | User (app) | `FIND-001`, `FIND-002` | §6, frames 2310/2491/2506/2600 | | ~17:19:03–22 | Both earbuds removed from the case (video-confirmed); battery-claim field on DLCI 4 transitions from `e4 e4 ff` (both seated/charging) to `64 64 ff` (both out, unplugged). | User (hardware) | — | §6, frames 1056–2595 vs. 2776–4882 | @@ -79,7 +80,6 @@ unassigned rather than guessed. | ~17:20:13–15 | User taps the Buds' row in Android's own Bluetooth settings panel (off-camera tap, on-camera state flip "Actief"→"Opgeslagen"); Android tears down HFP then the ACL. | User (Android Settings) | — | §1 row 2, frame 3390 | | 17:20:35–36 | Buds reconnect; app's Android-link mirror is re-created on resume and reads correctly this time (`CONNECTED via profiles [2]`→`[1, 2]`). | Hardware / App | — | §2 | | EQ screen viewed at some point in this window (exact tap not separately isolated this pass) | App's first `ReadSetting 4:16` returns `Low bass −5, Bass −1.5`, matching the film's EQ screen; a later read returns `[-2, 0, 2, 3, 5]`, consistent with the Clarity preset having been applied and persisted. | User (app) | `EQP-006` (🟡 — preset value pattern, not independently confirmed against film for the exact tap) | §6, frames 1455/3202/3558/4855 | -| 17:18:18.957–17:18:27.583 | Five EQ writes `WriteSetting 4:{16:{…}}` on pw_rpc channel 21 (frames 2169, 2181, 2188, 2203, 2214). Row added 2026-10-03 (`ai-sessions/0069`, `A68-CAP-15`; `python3 scripts/pwrpc_decode.py CAP-059-btsnoop_hci.log`); the tap on film was not isolated. | User (app) | `EQP-006` (partial) | frames 2169–2214 | | 17:20:54.242–17:21:15.565 | **Second ANC round: 8 `Set`s** — `80`, `40`, `20`, `20` (17:20:54–55, four within 1.4 s) then `80`, `40`, `08`, `20` (17:21:09–15). Row added 2026-10-03; counted in `PROTOCOL.md` §4.1's tally. | User (app) | `ANC-001`–`004` | frames 4250, 4269, 4282, 4287, 4462, 4490, 4512, 4581 | | 17:21:13.900–17:21:14.460 | App releases and re-claims DLCI 4 within 0.56s (unusually fast re-open for this session). | App | — | §1 | | 17:21:16.023 | **Buds themselves** send `DISC` on DLCI 2, then DLCI 4 five ms later — the phenomenon this capture was made to investigate. ACL/audio unaffected (Spotify keeps playing). | Hardware (Buds) | — | §1 row 3, frames 4595/4597 | diff --git a/captures/CAP-059-2026-09-20_17-16-59_17-22-29-Group_AU/CAP-059-FINDINGS.md b/captures/CAP-059-2026-09-20_17-16-59_17-22-29-Group_AU/CAP-059-FINDINGS.md index f98fa8f2..b29ecb61 100644 --- a/captures/CAP-059-2026-09-20_17-16-59_17-22-29-Group_AU/CAP-059-FINDINGS.md +++ b/captures/CAP-059-2026-09-20_17-16-59_17-22-29-Group_AU/CAP-059-FINDINGS.md @@ -68,7 +68,7 @@ host (multipoint); (3) does the Buds' periodic `07 34` (SASS) message on DLCI 4 | `RFCOMM channel 0x02 lost (bt socket closed, read return: -1)` | 17:20:15.897 | frame 3358 `Sent DISC` on the HFP DLCI 0x0c 15:20:14.517Z, **Disconnect Complete reason 0x13** (remote user terminated) frame 3390 15:20:15.877Z; system log: `input_interaction … SystemUIDialog` 15:20:14.392Z, Settings unfrozen .535, Telecom HFP state 3(.547)→0(.689); film: Android's Bluetooth panel "Actief"→"Opgeslagen" at overlay 17:20:13→15 | **The user tapped the Buds' row in Android's own Bluetooth panel** (⚪ the tap itself is off-camera); Android tore down HFP then the ACL — a full disconnect. | | `RFCOMM channel 0x02 lost` | 17:21:16.028 | frame 4595 `Rcvd DISC Channel=1` (Buds→phone, DLCI 0x02) 15:21:16.023Z, frame 4597 `Rcvd DISC Channel=2` (DLCI 0x04) **+5ms**, then phone `DISC`+`DM` on DLCI 4 (4598/4599); **no** `Disconnect Complete` — ACL stays up (Spotify keeps playing, Android panel still "connected" at 17:21:29) | **The Buds closed both RFCOMM channels while the ACL/audio stayed up** — the phenomenon this capture was made to investigate. | -Frames 4586–4595 (`tshark -Y "frame.number>=4562 && frame.number<=4600"`): app claim `SABM` DLCI 4 +Frames 4552–4597 (`tshark -Y "frame.number>=4552 && frame.number<=4600"`): app claim `SABM` DLCI 4 frame 4552 (17:21:14.460), Buds' `07 34` at 4567 (+118ms, `01 2f 2e 65 82 90 4f a2 1d 6d d6 c8`), the app's `Set ANC` frame 4581 (17:21:15.565, `08 12 … 01 e8 e8 20`), Buds' ACK + Notify 4586–4589 (`08 13 … 01 e8 e8 20`, 15.644–15.665), a Case-battery push on DLCI 8 (4593, 15.683), **0.34s of @@ -84,7 +84,7 @@ lies between 4593 and 4595. originally planned were not performed this session (one continuous tour instead). 3. See §5 below. -**Same phenomenon as `LOG-001`'s originally-reported "89s deny-mode drop"?** 🟡 HYPOTHESIS yes (both +**Same phenomenon as the "89s deny-mode drop" of `ai-sessions/0040` §2.1?** 🟡 HYPOTHESIS yes (both RFCOMM channels closed by the Buds within ms, audio link up); 🔴 OPEN why — here it came 1.56s after an app claim that had itself been re-opened only 0.56s after the previous release (`Sent DISC` 4528 at 17:21:13.900, `SABM` 4552 at 17:21:14.460); no other claim in this capture was re-opened within <1.26s @@ -220,7 +220,9 @@ payloads are `10 | 50 <5B account-key filter> | 21 <2B salt> | 46 <4B>` (e.g. `1 73 ec 46 e2 dd f1 fb`) — a field of type `0x46`, where the official Fast Pair Battery Notification would carry `0x33`/`0x34` then three `0bSVVVVVVV` bytes; no payload here contains that marker at the expected field position. `PROTOCOL.md` §4.3 Option A's `CAP-011` result (inconclusive) stands and this -is a second, cleaner non-match. GATT (Option D): LE discovery on handle `0x0041` +is a second, cleaner non-match. (Later reading, `CAP-054-FINDINGS.md` §1/§6, 🟡: the leading `0x10` is the SASS "Version and flags" +byte and `46 <4B>` is the SASS Random Resolvable Data field, `0bLLLL0110` with length 4 — the payload is the SASS form without the optional +Battery Data; pointer added 2026-10-15, `ai-sessions/0094`.) GATT (Option D): LE discovery on handle `0x0041` (`tshark -Y "btatt.opcode==0x11" -T fields -e frame.number -e btatt.uuid16"`, frames 704, 768, 5275) lists services `0x1800 0x1801 0x1849 0x184c 0x1855 0x1858 0x185b 0xfcf1 0xfef3`, and on the second connection additionally `0x180a` — **no Battery Service `0x180f`**, zero `0x2a19` matches anywhere in @@ -230,7 +232,7 @@ the log: 🟢 FACT for this firmware/capture. While the session was open the battery card showed "Left/Right 100%" from the last claim only, with nothing indicating its age — the film shows values from 17:17:55 still displayed at 17:19:37, after the -buds had already left the case at 17:19:06 (the value shown happened to still be correct, from the +buds had already left the case at ≈ 17:19:06 and ≈ 17:19:11 (`AT+BIEV` 2624/2631 and 2685/2694) (the value shown happened to still be correct, from the 17:19:22 claim, but the card gave the viewer no way to know that at the time). Acceptable per `DECISIONS.md` ADR-032 ("only as fresh as the last claim"), but the card did not say *when* that claim happened — see `ai-sessions/0043` Phase H task 28 (replacing "(last known)" with an actual timestamp), diff --git a/captures/CAP-060-2026-09-21_17-54-01_18-00-49-Group_AV/CAP-060-EVENT-NOTES.md b/captures/CAP-060-2026-09-21_17-54-01_18-00-49-Group_AV/CAP-060-EVENT-NOTES.md index 624d69f0..f0cbb437 100644 --- a/captures/CAP-060-2026-09-21_17-54-01_18-00-49-Group_AV/CAP-060-EVENT-NOTES.md +++ b/captures/CAP-060-2026-09-21_17-54-01_18-00-49-Group_AV/CAP-060-EVENT-NOTES.md @@ -79,9 +79,9 @@ video/log evidence together. | 17:55:47.287–17:56:24.269 | Find My Buds exercised: 4 ring/stop commands cycling Left/Right/Stop; video confirms the Find screen and finger taps at `t=139s` ("Ringing: Right earbud"). | User (app) | `FIND-001`, `FIND-002` | debug export lines 81–113; frame `t=139` | | 17:56:36.558–17:56:58.494 | ANC cycled 6 times: Transparency(`80`)→Adaptive(`40`)→Off(`20`)→Active(`08`)→Transparency(`80`)→Adaptive(`40`) — each `Set`/`Notify` pair on DLCI 4. | User (app) | `ANC-001`, `ANC-002`, `ANC-003`, `ANC-004` | debug export lines 121–180 | | 17:57:11 | Maintainer switches to Spotify. | User | — | video `t=190s` | -| 17:57:38.283–17:57:41.658 | Reconnect after `PAIR-003`-style drop (see Drop 2 below is later — this is a routine app-triggered reconnect cycle continuing the session); DLCI 0x08 opened and released cleanly this time (no Timeout logged for this cycle alone, but a further Timeout follows immediately at 17:57:40.645). | App | — | debug export lines 201–221 | -| 17:57:45.421–17:58:06.476 | EQ screen used: 13 `WriteSetting` calls in ~21s (video `t=228s` shows the Presets row, finger over Vocal Boost/Clarity) — a preset-tapping sequence, not isolated single-slider drags (the film does not show which preset(s) were tapped, so individual `EQS-*`/`EQP-*` Test-IDs are not claimed with confidence). | User (app) | `EQP-005`/`EQP-006` (🟡 — preset area confirmed on video, exact preset(s) not disambiguated) | debug export lines 222–253; frame `t=228.5s` | | 17:57:29.912 | **Drop 2 (non-user):** Buds `DISC` on DLCI 0x02 only (DLCI 4 already on-demand-released at the time); ACL stays up. | Hardware (Buds) | — | frames 3651/3652 | +| 17:57:38.283–17:57:41.658 | Reconnect after Drop 2 (debug export 17:57:37.926 `Disconnected -> Connecting`, 17:57:38.283 attempt 1/3); DLCI 0x08 opened and released cleanly this time (no Timeout logged for this cycle alone, but a further Timeout follows immediately at 17:57:40.645). | App | `PAIR-003` | debug export lines 201–221 | +| 17:57:45.421–17:58:06.476 | EQ screen used: 13 `WriteSetting` calls in ~21s (video `t=228s` shows the Presets row, finger over Vocal Boost/Clarity) — a preset-tapping sequence, not isolated single-slider drags (the film does not show which preset(s) were tapped, so individual `EQS-*`/`EQP-*` Test-IDs are not claimed with confidence). | User (app) | `EQP-005`/`EQP-006` (🟡 — preset area confirmed on video, exact preset(s) not disambiguated) | debug export lines 222–253; frame `t=228.5s` | | 17:58:37.339 | **User's own Disconnect tap** — phone-initiated `DISC` on DLCI 0x02, confirmed on the wire (dir=0). | User (app) | — | frame 4116 (dir=0, `DISC`); debug export line 281 | | 17:58:41.172 | Reconnect. | App | `PAIR-003` | debug export lines 283–286 | | 17:58:53.164–53.417 | **User taps the Buds' row in Android's own Bluetooth settings panel** — video-confirmed directly: `t=292s` frame shows the panel open, "Pixel Buds Pro 2 van Ted — Actief. Batterijniveau 100%" highlighted, finger touching that row. System log confirms `input_interaction: … SystemUIDialog` and a `com.android.settings` unfreeze at the same instant. Android tears down HFP first (phone-initiated `DISC` on DLCI 0x0c, frame 4263). | User (Android Settings) | — | frame `t=292s` (visual confirmation); system log 15:58:53.164/.275; HCI frames 4263/4276 | @@ -105,7 +105,7 @@ video/log evidence together. done, `CAP-060-FINDINGS.md`. - [x] Answer `ai-sessions/0043`'s four specific questions — done, `CAP-060-FINDINGS.md` §1 (Case battery/drops), §2 (per-earbud dock state), §3 (ANC tile). -- [x] Cross-reference Test-IDs actually exercised — `PAIR-001`, `PAIR-003` (four reconnect cycles), +- [x] Cross-reference Test-IDs actually exercised — `PAIR-001`, `PAIR-003` (six reconnect cycles: 17:55:29, 17:57:38, 17:58:41, 17:59:00, 17:59:40, 18:00:29), `ANC-001`–`004`, `FIND-001`/`002` are 🟢-confirmed. `EQP-005`/`EQP-006` are 🟡 (preset-area video confirmation, not individually disambiguated). No `EQS-*` Test-ID is claimed — the 13-write burst is more consistent with preset taps than slider drags, unconfirmed either way. diff --git a/captures/CAP-060-2026-09-21_17-54-01_18-00-49-Group_AV/CAP-060-FINDINGS.md b/captures/CAP-060-2026-09-21_17-54-01_18-00-49-Group_AV/CAP-060-FINDINGS.md index 3552e37f..9a513d97 100644 --- a/captures/CAP-060-2026-09-21_17-54-01_18-00-49-Group_AV/CAP-060-FINDINGS.md +++ b/captures/CAP-060-2026-09-21_17-54-01_18-00-49-Group_AV/CAP-060-FINDINGS.md @@ -22,8 +22,8 @@ present and active · **App under test:** OpenControl for Pixel Buds, `ai-sessio ## 1. Why did the session sometimes lose its connection? (`ai-sessions/0043` question 1) -This capture shows **seven non-user session or link ends** (three Buds-side closes of the session channel with the ACL up, four ACL drops) plus **one deliberate -user disconnect** and **two failed +This capture shows **six non-user session or link ends** (three Buds-side closes of the session channel with the ACL up, three ACL drops #4–#6) plus **one ACL +drop the user caused from Android's Bluetooth panel** (#3), **one deliberate in-app user disconnect** *(corrected 2026-10-15, `ai-sessions/0094`)* and **two failed reconnect attempts** — substantially more activity than `CAP-059`'s three drops, letting this capture distinguish several genuinely different mechanisms rather than one. @@ -121,7 +121,7 @@ shows a clean, video-corroborated transition: | Time window | Value | Video correlation | |---|---|---| | 17:54:56.047–17:55:41.708 | `00` (both docked) | Session start — buds shown seated in the case | -| 17:56:36.662–17:59:00.627 | `e8` (not both docked) | Spans the entire ANC-cycling window (17:56:36–17:58) and the EQ-preset window (17:57:45–18:06) — consistent with the buds being out/worn during active use | +| 17:56:36.662–17:59:00.627 | `e8` (not both docked) | Spans the entire ANC-cycling window (17:56:36–17:58) and the EQ-preset window (17:57:45–17:58:06) *(corrected 2026-10-15, `ai-sessions/0094`)* — consistent with the buds being out/worn during active use | | 17:59:41.231–18:00:29.610 | `00` (both docked) again | Trailing part of the session | This is 🟢 FACT, directly matching the video's own "Both earbuds are in the case (as of the last diff --git a/captures/CAP-061-2026-09-24_17-24-59_17-31-14-Group_AW/CAP-061-EVENT-NOTES.md b/captures/CAP-061-2026-09-24_17-24-59_17-31-14-Group_AW/CAP-061-EVENT-NOTES.md index 646cdf92..3978924f 100644 --- a/captures/CAP-061-2026-09-24_17-24-59_17-31-14-Group_AW/CAP-061-EVENT-NOTES.md +++ b/captures/CAP-061-2026-09-24_17-24-59_17-31-14-Group_AW/CAP-061-EVENT-NOTES.md @@ -103,7 +103,7 @@ on-demand channel. Frame numbers are HCI frames in `CAP-061-btsnoop_hci.log`. | 17:29:40–17:30:06 | Find: Ring Left/Right taps, refused (17:29:45.250, 17:29:56.747, 17:30:03.253, 17:30:06.266). Disconnect 17:29:48.4 (4889) / Connect 17:29:49.6 (4915; announcement 4931; EQ read; 0x04 snapshot 4948; Case claim 4993 + `0e 04` 5001, no answer). | User (app) | `FIND-001`, `FIND-002` (refused), `PAIR-003` | film t=281–307; export lines 237–284 | | 17:30:16 / 17:30:26 / 17:30:29 / 17:30:38 | Refresh battery (0x04 5164, Case 5214 + `0e 04` 5222, no answer), ANC Refresh (5256), **Transparent** tap refused (17:30:32.847), Refresh battery (5372; Case 5419 + `0e 04` 5427, no answer). | User (app) | `BATT-004`, `ANC-004` (refused) | film t=317–340; export lines 285–322 | | 17:30:32–17:30:48 | Lid opened; the maintainer picks up the buds and starts putting them back. At 17:30:48.339 the **Buds send `DISC` on DLCI 0x02** (5456); the app: "Session lost: channel 0x02 closed … last inbound frame … 8448 ms earlier; not a user disconnect"; the screen: "The Maestro channel (equalizer) was closed while Android still shows the Buds connected …". ACL stays up. | User (hardware) / Buds | — | film t=333–349; export lines 323–325 | -| ≈ 17:30:52.4–17:30:53 | **Right bud seated** (film 2 fps: t=352.5–353.0); case LED on; `AT+BIEV=2,100` 17:30:53.145 (5464). | User (hardware) | `CASE-006` (part) | film t=353–355; HCI 5464 | +| ≈ 17:30:51.5–17:30:52.2 | **Right bud seated** (film 2 fps: t=352.5–353.0; overlay = 17:24:59.15 + t, phone 0–0.2 s behind); case LED on; `AT+BIEV=2,100` 17:30:53.145 (5464). | User (hardware) | `CASE-006` (part) | film t=353–355; HCI 5464 | | 17:30:56–17:30:58 | Tap **Connect** (with the Left bud still in the hand): 0x02 (5507), announcement **channel 19** (5524); snapshot claim 0x04 (5541): battery **`64 e4 ff`** (Left 100 % not charging, Right 100 % charging, 5557) and **`Notify 01 e8 00 20`** (5560, 17:30:58.806) → screen "**Both earbuds seem to be in the case** … (updated 17:30:58) — provisional", "Left: 100% (updated 17:30:58)", "Right: 100% (charging)". **Wrong:** the Left bud is in the hand beside the case at that instant (30 fps: in hand t=359.4–360.4, seated t≈360.45 = ≈ 17:30:59.4–59.6 phone time). Case claim 5585 + `0e 04` 5593. | User (app) / Buds | `PAIR-003` | film t=357–361; export lines 326–344 | | 17:30:59.97 | **Left bud seated** → ACL `Disconnection Complete` reason `0x13` (5597), 0.4–0.6 s after the seating (ADR-016). App: session lost; Android link NOT_CONNECTED. | Buds | `CASE-006` | HCI 5597; export lines 345–348 | | 17:31:05.5 | Tap **Connect** with both buds seated, lid open: Create Connection (5605) → Connection Complete (5608); 0x02 (5685), announcement ch 19 (5697); 0x04 snapshot (5719): battery `e4 e4 ff`, `Notify e8 00 20` → "Both earbuds seem to be in the case" — **correct**; Case claim (5766) + `0e 04` (5776), no answer → Timeout; 0x08 released (6026). HFP opened by the Buds on DLCI 0x09 (5887). | User (app) | `PAIR-003`, `BATT-004` | film t=367–369; export lines 349–370 | diff --git a/captures/CAP-061-2026-09-24_17-24-59_17-31-14-Group_AW/CAP-061-FINDINGS.md b/captures/CAP-061-2026-09-24_17-24-59_17-31-14-Group_AW/CAP-061-FINDINGS.md index 0a888ae2..a8c70ec9 100644 --- a/captures/CAP-061-2026-09-24_17-24-59_17-31-14-Group_AW/CAP-061-FINDINGS.md +++ b/captures/CAP-061-2026-09-24_17-24-59_17-31-14-Group_AW/CAP-061-FINDINGS.md @@ -90,9 +90,10 @@ input, not in the gate. The gate behaved as designed for the input it had. A sec - 🟢 FACT: **20 of 20** app opens received **no** Buds frame. **12** were closed by a Buds-side `DISC` 10–198 ms after the `SABM` — each right after the app's attempt 1 had failed and the stack had closed the other owner's 0x08 (phone `DISC` 1621, 2116, 2528, 2916, 3368, 3626: ADR-032's mechanism) — so the request was never sent (export: "RFCOMM on-demand channel 0x08 closed (IOException …)" then - "Case battery not read: ChannelLost", lines 30–33 … 90–93). **8** were held 2.1–5.2 s after the other owner had gone (from 17:27:24), - sent **exactly** `0e 04 00 00` (ADR-039) and received nothing (export "Case battery not read: Timeout", lines 169, 180, 195, 266, 294, - 321, 369). + "Case battery not read: ChannelLost", lines 30–33 … 90–93). **8** were held after the other owner had gone (from 17:27:24), + sent **exactly** `0e 04 00 00` (ADR-039) and received nothing — 7 held 2.1–5.2 s until the app's Timeout (export "Case battery not read: + Timeout", lines 169, 180, 195, 266, 294, 321, 369), and 5585 held 0.69 s until the ACL ended (5597; export lines 345–347, session lost) + *(corrected 2026-10-15, `ai-sessions/0094`)*. - **Therefore ADR-039's 🟡 HYPOTHESIS "sending `0e 04 00 00` is sufficient" is refuted by this capture**: 8/8 app claims that sent it got no answer on an open no other client held. 🟡 HYPOTHESIS for why: the Buds answer on this channel only after (or as part of) the other opener's burst, whose first message is `05 0c` (answered by `05 0a`); which of the burst's messages is needed is 🔴 open, and @@ -143,8 +144,8 @@ All 20 `Notify ANC state` frames (`08 13 00 04 01 e8 `) against |---|---|---|---|---| | 1594, 2085, 2502, 2896 | 17:25:46–17:26:31 | `00` / `20` (OFF) | `e4 e4` (both charging) | both seated ✓ | | 3197 (Play-services channel) | 17:26:45.720 | `e8` / `40` | `e4 64` (L charging, R not) | Right out since 17:26:36–37 ✓ | -| 3272, 3346, 3604, 3996, 4309 … 5392 (12) | 17:26:57–17:30:39 | `e8` / `08` | `64 64` | both out ✓ | -| **5560** | **17:30:58.806** | **`00` / `20`** | **`64 e4`** (5557, 2 ms earlier: **Left not charging**, Right charging) | **Right seated (17:30:52.4–53.0), Left in the hand beside the case** (30 fps: seated only at t ≈ 360.45 s = 17:30:59.4–59.6 phone time) ✗ | +| 3272, 3346, 3604, 3996, 4309 … 5392 (13) *(corrected 2026-10-15, `ai-sessions/0094`)* | 17:26:57–17:30:39 | `e8` / `08` | `64 64` | both out ✓ | +| **5560** | **17:30:58.806** | **`00` / `20`** | **`64 e4`** (5557, 2 ms earlier: **Left not charging**, Right charging) | **Right seated (17:30:51.5–52.2; film t = 352.5–353.0) *(corrected 2026-10-15, `ai-sessions/0094`)*, Left in the hand beside the case** (30 fps: seated only at t ≈ 360.45 s = 17:30:59.4–59.6 phone time) ✗ | | 5741 | 17:31:06.765 | `00` / `20` | `e4 e4` | both seated ✓ | - 🟢 FACT: **one counter-example to the derived reading "`0x00` = both earbuds in the case"**: frame 5560 reads `0x00` 0.6–0.8 s before the diff --git a/captures/CAP-062-2026-09-25_06-38-36_06-57-10-Group_AX/CAP-062-EVENT-NOTES.md b/captures/CAP-062-2026-09-25_06-38-36_06-57-10-Group_AX/CAP-062-EVENT-NOTES.md index 211b6b74..46550796 100644 --- a/captures/CAP-062-2026-09-25_06-38-36_06-57-10-Group_AX/CAP-062-EVENT-NOTES.md +++ b/captures/CAP-062-2026-09-25_06-38-36_06-57-10-Group_AX/CAP-062-EVENT-NOTES.md @@ -151,7 +151,7 @@ Times are phone local time (HCI / debug export). "Claim" = the app's on-demand D wording), C9 ✓, C10 ✓ (bottom bar; swipe not distinguishable on film); D1/D2 ✓; E1–E7 ✓; F1–F4 done with the buds **not worn** (NAK) and once worn (F4 ACK), **F5 not run** as written (no press-and-hold), **F6/F7 not run**, F8 ✓; G1–G4 ✓; H1 ✓, H2 partly (Clarity only), H3/H4 ✓, **H5 not run**, H6 ✓; I1–I3 ✓, I4 ✗; J1/J2 ✓, J3 ✓ (system log), **J4 not run**; **K1–K5 not run**; L1 ✓, L2 ✓ (off film), - **L3 not run**. Registry Test-IDs: `PAIR-001` ✓, `PAIR-003` ✓ (×14 app sessions), `CASE-004`/`CASE-005`/`CASE-006` ✓, `BATT-004` ✓, + **L3 not run**. Registry Test-IDs: `PAIR-001` ✓, `PAIR-003` ✓ (×15 app sessions), `CASE-004`/`CASE-005`/`CASE-006` ✓, `BATT-004` ✓, `ANC-001`–`ANC-004` ✓ (ACK worn, NAK not worn), `FIND-001`/`FIND-002` ✓, `EQS-001`/`EQP-*` ✓ (Clarity only). - [x] `ai-sessions/0046` §9: R1 ✓, R2 ✓, R3 ✓ (audibility not established), R4 ✓ (ringing heard until Stop), R5 ✓, R6 ✓, R7 ✓, R8 (E)/(F) **expected but not observed** (not run). diff --git a/captures/CAP-062-2026-09-25_06-38-36_06-57-10-Group_AX/CAP-062-FINDINGS.md b/captures/CAP-062-2026-09-25_06-38-36_06-57-10-Group_AX/CAP-062-FINDINGS.md index 10811df5..de313503 100644 --- a/captures/CAP-062-2026-09-25_06-38-36_06-57-10-Group_AX/CAP-062-FINDINGS.md +++ b/captures/CAP-062-2026-09-25_06-38-36_06-57-10-Group_AX/CAP-062-FINDINGS.md @@ -77,8 +77,8 @@ The build is the `0046` one: "Runtime info requested (channel 19)" (export line ears; "worn" is inferred from the buds leaving the table/case during the test plan's in-ear steps. **Settling experiment:** one bud in an ear, the other on the table, filmed; then swap; read `08 11` → `08 13` each time (no `Set` needed). - 🟢 FACT (all captures, same command over every `*btsnoop_hci.log`): every official-app and earlier app `Set` (31 in `CAP-001`, `-006`, `-039`, - `-051`, `-059`, `-060` — `CAP-002` holds none; the full tally is 70 `Set`s in 13 captures, 59 ACKed, 11 NAKed, all NAKs in `CAP-062`: `PROTOCOL.md` - §4.1 Update of 2026-10-03, `scripts/message_stream_tally.py`; list corrected 2026-10-08, `ai-sessions/0081`) was ACKed and each was answered by a `Notify` with Settable `e8`; `CAP-062` holds the only NAKs. `CAP-036` (buds in + `-051`, `-059`, `-060` — `CAP-002` holds none; the full tally is 70 `Set`s in 13 captures, 59 ACKed, 11 NAKed — 10 in `CAP-062`, 1 in `CAP-064` (3440): `PROTOCOL.md` + §4.1 Update of 2026-10-03, `scripts/message_stream_tally.py`; list corrected 2026-10-08, `ai-sessions/0081`) was ACKed and each was answered by a `Notify` with Settable `e8`; `CAP-062` holds 10 of the 11 NAKs, `CAP-064` 3440 the eleventh *(corrected 2026-10-15, `ai-sessions/0094`)*. `CAP-036` (buds in the case for the whole session) contains **no** `Set` at all — only a `Get`/`Notify 01 e8 00 20` pair (frame 1182). 🟡 HYPOTHESIS: the official app does not offer ANC while the Buds report no settable mode (`CAP-001-EVENT-NOTES.md` records the ANC row "greyed out" until shortly before the first real `Set`). @@ -141,7 +141,7 @@ The `SubscribeRuntimeInfo` stream the app has subscribed to since ADR-043 (paylo | Stream frame (time) | `6.2.2` | `6.3.2` | `7.1` | `7.2` | 6.1 (Case) | Battery frame (L, R) | Film | |---|---|---|---|---|---|---|---| | 2782, 2927, 3236, 3488, 5508, 7118, 8612 … 11447 | 1 | 1 | 0 | 0 | absent | `64 64` (neither charging) | both out (table or ears) | -| 3760 (06:42:28.851) | 1 | **2** | **1** | 0 | 60 | `64 e4` (4520) | Right seated, Left out | +| 3760 (06:42:28.851) | 1 | **2** | **1** | 0 | 60 | `64 e4` (4520, 06:43:04.000 — the next claim, 35 s later; no battery frame within ±3 s) | Right seated, Left out | | 4845, 6429, 7447, 7649 | **2** | **2** | **1** | **1** | 60 | `e4 e4` | both seated | | 7033 (06:46:25.732) | **2** | 1 | 0 | **1** | 60 | `e4 64` (7058) | Right taken out, Left seated | @@ -222,7 +222,7 @@ consent (I-5). ## 8. What works (evidence as above) - 🟢 Pairing through the app (second attempt; the first, with the lid closed, found nothing and was cancelled cleanly), permissions, the - Android-link mirror, 14 Connect/Disconnect cycles, the foreground service start/stop at every Connect/Disconnect/loss (system log + Android-link mirror, 15 Connects (14 sessions ended, §3; the 15th ran to the end), the foreground service start/stop at every Connect/Disconnect/loss (system log `am_foreground_service_start/stop`), the notification text following the ANC mode (film t=940, 1009, 1022). - 🟢 The firmware line and no Safe Mode (R1); ANC write with ACK (6/6 worn) and honest NAK reporting (10/10); the Quick Settings tile (G1–G4); EQ read on channels 19 and 21, 7/7 writes OK, one write per release, persistence across a reconnect (H6). @@ -234,7 +234,7 @@ consent (I-5). | Channel / protocol | What the app does | What the Buds answer | Goes well | Goes wrong / to improve | |---|---|---|---|---| -| **DLCI 0x02/0x03 pw_rpc** (`maestro_pw.Maestro`) | per Connect: waits for `GetSoftwareInfo`, `ReadSetting 4:16`, `SubscribeRuntimeInfo`; EQ `WriteSetting` | announcement 22–250 ms after `UA`; RESPONSE OK; stream on dock changes | 14/14 announcements, 14/14 reads, 7/7 writes, stream (§4) | the Buds `DISC` it on every wear/dock change (§3); only 6.1 is used — 6.2/6.3/7 are dropped (§4) | +| **DLCI 0x02/0x03 pw_rpc** (`maestro_pw.Maestro`) | per Connect: waits for `GetSoftwareInfo`, `ReadSetting 4:16`, `SubscribeRuntimeInfo`; EQ `WriteSetting` | announcement 22–250 ms after `UA`; RESPONSE OK; stream on dock changes | 15/15 announcements, 15/15 reads, 7/7 writes, stream (§4) | the Buds `DISC` it on every wear/dock change (§3); only 6.1 is used — 6.2/6.3/7 are dropped (§4) | | **DLCI 0x04/0x05 Message Stream** | on-demand claims: snapshot (`08 11`), ANC `Set`, Ring/Stop, Refresh | Device Info (`03 0a`, `03 01 da 2d b1`, `03 02`, `03 09`, `03 0b`), `07 34`, battery ×3, `Notify`, ACK/NAK, Ring echo | 40 claims, all on attempt 1 after the first collision; every answer decoded | ANC `Set` sent while the claim's own `Notify` says no mode is settable (§2) | | **DLCI 0x08/0x0a** (GSND) | nothing (ADR-043) | Case push to the Google app | no contention with the app | — (owner lead §6) | | **HFP** (DLCI 0x0c/0x09) | nothing (ADR-040) | `AT+BIEV` in 65 frames (15 dissected as `bthfp` + 50 raw on the undissected DLCI; "×14" corrected 2026-10-03, A68-CAP-08) | — | — | diff --git a/captures/CAP-063-2026-09-27_15-57-33_16-25-03-Group_AY/CAP-063-EVENT-NOTES.md b/captures/CAP-063-2026-09-27_15-57-33_16-25-03-Group_AY/CAP-063-EVENT-NOTES.md index 74623ed8..cd63aad4 100644 --- a/captures/CAP-063-2026-09-27_15-57-33_16-25-03-Group_AY/CAP-063-EVENT-NOTES.md +++ b/captures/CAP-063-2026-09-27_15-57-33_16-25-03-Group_AY/CAP-063-EVENT-NOTES.md @@ -87,7 +87,7 @@ Times are **phone local time** (HCI / debug export); film-only events are overla | 15:58:14.54–15:58:15.4 | **Opened by itself:** link CONNECTED → "Automatic re-open (ADR-044, trigger: LINK_BACK)" → app `SABM` DLCI 0x02 (1226) → announcement ch 21 (1261) → `ReadSetting 4:16` (1267 → 1279 `[-2.83,-5.89,2,3,5]`) → `4:2 4:4 4:7 4:17 4:19 4:22` (1293…1339: 1, 1, L/R 5, 7 = Right 4, 1, 1) → `SubscribeRuntimeInfo` (1340 → 1342: Case 37, L/R charging) | App | AY-0 var. (buds in the open case, right after pairing) [`PAIR-003`] | export 11–21; film "ready" 15:58:15 | | 15:58:15.0–15:58:17.4 | Snapshot claim: attempt 1 collides with Play services' fresh DLCI 0x04 open (1275; phone `DISC` 1320), attempt 2 (1369): `08 11` 1379 → `03 03 00 03 e4 e4 ff` ×3, `Notify 01 e8 00 20` (1389); released 1548 | App / Play services | D1 | export 19, 22, 23 | | 15:58:16 | Connection: "Firmware: release_5.203", no Safe Mode card; Left/Right "100% (updated 15:58:15) — charging in the case", "Case: 37% (updated 15:58:15)" | App | D1, E1, E2 [`BATT-004`] | film 15:58:15 | -| 15:58:16.6–16.7 | The Google app opens DLCI 0x0a/0x08 (1441/1470); Play services re-opens DLCI 0x04 (1708: `03 08`, `07 10`, `06 01`) and holds it until 16:00:16.8 | Google app / Play services | — | HCI; system log 13:58:30.336 UTC "Waited long enough for … BistoRealService" | +| 15:58:16.6–16.7 | The Google app opens DLCI 0x0a/0x08 (1441/1470); Play services re-opens DLCI 0x04 at 15:58:20.32 (1708: `03 08`, `07 10`, `06 01`) and holds it until 16:00:16.8 | Google app / Play services | — | HCI; system log 13:58:30.336 UTC "Waited long enough for … BistoRealService" | | 15:58:23 | Debug tab: **Debug mode on** | User | S3, L1 | film 15:58:21–22 | | 15:58:39.9 | The phone closes the LE link (Disconnect 2178 → 2180, `0x16`) after 58 Fast Pair Key-based Pairing writes, each "Error 0x81" | Play services | — | HCI (out of scope, ADR-008) | | 15:58:46–47 | ANC tab: "ANC mode: OFF (updated 15:58:15)", buttons disabled, "ANC can only be changed while you wear the Buds. Tap Refresh to check again." | App | F8 | film | @@ -179,7 +179,7 @@ Times are **phone local time** (HCI / debug export); film-only events are overla | 16:17:05.83 | **Re-open by itself** (LINK_BACK, 7810); stream 8082 both charging, Case 34; `Notify 00 20` (8032) | App | ADR-044 (b) ✓ | export 700–735 | | 16:17:10–14 | Android's device-details screen (shows the Bluetooth address) | User | — | film (ADR-010) | | 16:17:18 / 28, 16:18:18 / 29 | Stream pushes while docked and idle, each with `AT+BIEV` (8165/8169, 8200/8201, 8306/8307, 8380/8381) | Buds | E6 ✓ | film (times re-stamped) | -| 16:17:21–16:18:40 | Idle, both docked, lid open (≈ 93 s, not 2 min) | User | AY-14 var. | film | +| 16:17:21–16:18:40 | Idle, both docked, lid open (79 s; ≈ 93–95 s counted from the re-open at 16:17:05.8 — not 2 min) | User | AY-14 var. | film | | 16:18:40.33 | **Refresh battery** → re-subscription 8417 → **answered** 8426 (+194 ms, Case 34) → "Case: 34% (updated 16:18:40)"; claim 8439: `e4 e4`, `Notify 00 20` | User / App | **AY-14 ✓ (answered)**, E9 | film 16:18:38–40 | | 16:18:48.22 | Find: **Ring Left**, both docked → `04 01 00 01 02` (8518) → ACK `ff 01 00 03 04 01 00` (8520) + echo (8521); "Ringing: Left earbud …" | User / App | **AY-1** [`FIND-001`] | film 16:18:45–47 | | 16:19:09.49 | **Stop** → `04 01 00 01 00` (8598) → ACK (8600) | User | AY-1 | film 16:19:07–08 | @@ -236,7 +236,7 @@ Times are **phone local time** (HCI / debug export); film-only events are overla | H2 | partly (Heavy bass; Light bass and Balanced at other times) | 6009, 2374, 8633 | | H3, H4 | partly (+5.23, −5.71; no ±6 pair) | 6017, 6026 | | H5 | not run (button not shown) | film | -| AY-13a | done, repeated (5×) ✓ | export 206, 257, 272, 535, 546 | +| AY-13a | done, repeated (3× on its own; 8 Refreshes in all with AY-13b's 535/546/557/567 and AY-14's 16:18:40, FINDINGS §1) ✓ | export 206, 257, 272 | | AY-13b | done ✓ (16:14:49.9/50.7, 0.79 s) | export 557–568 | | AY-7 | done differently (both at once, 16:15:08–21), then per bud (16:15:48–16:16:34) ✓ | stream 6338 … 7555 | | AY-6 | done ✓ (16:15:22, 16:16:35) | 6378, 7566 | diff --git a/captures/CAP-064-2026-10-01_10-04-14_10-29-28-Group_AZ/CAP-064-FINDINGS.md b/captures/CAP-064-2026-10-01_10-04-14_10-29-28-Group_AZ/CAP-064-FINDINGS.md index f1eb6092..12fb9ace 100644 --- a/captures/CAP-064-2026-10-01_10-04-14_10-29-28-Group_AZ/CAP-064-FINDINGS.md +++ b/captures/CAP-064-2026-10-01_10-04-14_10-29-28-Group_AZ/CAP-064-FINDINGS.md @@ -91,7 +91,7 @@ Command: `tshark -r CAP-064-btsnoop_hci.log -Y "bthci_evt.code==0x03 || bthci_ev -T fields -e frame.number -e frame.time -e frame.p2p_dir -e bthci_evt.code -e bthci_cmd.opcode -e bthci_evt.status -e bthci_evt.connection_handle -e bthci_evt.reason` → exactly the rows above (plus the LE device's frame 284). -- 🟢 **Positive control (`AGENTS.md` §13 step 8):** C, D, E and G — four automatic re-opens by LINK_BACK, each 6–8 ms after the reading turned `CONNECTED`; +- 🟢 **Positive control (`AGENTS.md` §13 step 8):** C, D, E and G — four automatic re-opens by LINK_BACK, each 3–7 ms after the reading turned `CONNECTED` *(corrected 2026-10-15, `ai-sessions/0094`)*; plus 8 AFTER_LOSS re-opens (§6). The thing that differs between C/D/E/G and B/F: in C/D/E the **Buds** paged the phone the moment a bud left the case (HCI Connection Request event), in G **Android** re-paged the Buds 3.4 ms after an ACL drop; in B and F nobody paged. - 🟢 **Docked Buds accept a page when the lid is open** (F: Create Connection → Connection Complete in 0.96 s) and **not when it is closed** (A: Page Timeout diff --git a/captures/CAP-065-2026-10-01_11-07-30_11-28-23-Group_BA/CAP-065-EVENT-NOTES.md b/captures/CAP-065-2026-10-01_11-07-30_11-28-23-Group_BA/CAP-065-EVENT-NOTES.md index f97fac3a..3a919459 100644 --- a/captures/CAP-065-2026-10-01_11-07-30_11-28-23-Group_BA/CAP-065-EVENT-NOTES.md +++ b/captures/CAP-065-2026-10-01_11-07-30_11-28-23-Group_BA/CAP-065-EVENT-NOTES.md @@ -86,7 +86,7 @@ app's claims start with `08 11` or `08 12` (FINDINGS §3). | 11:07:38–11:08:26 | Bluetooth list: "Charge 6 Verbonden", "Pixel Buds Pro 2 va Opgeslagen", "Niro Opgeslagen"; case closed above the phone; OpenControl stays resumed underneath (no `wm_on_*` between 09:00:31 and 09:10:41 UTC) | OS | — | film; LE Connection Complete 290; logcat | | 11:07:59.5 | Status bar 11:07 → 11:08 (overlay :59 + 0.5 s) | — | S0 var. (nothing spoken) | 5 fps crops | | 11:08:20.5 | **Lid opened, both buds docked**; no HCI event for the Buds until 11:08:27.10 | User | BA-1 var. | film; HCI (no event 290 → 596) | -| 11:08:22–28 | Left-slot bud taken out (Right stays docked); head on the **right** of the frame ⇒ **Left** into the ear (≈ 11:08:28–30) | User | BA-1 [`CASE-005`, `INEAR-002`] | film; stream 1089 6.3.2 = 2 (Right charging), 6.2.2 = 1 | +| 11:08:22–28 | Left-slot bud taken out (Right stays docked); head on the **right** of the frame ⇒ **Left** into the ear (≈ 11:08:28–30) | User | BA-1 [`CASE-004`, `INEAR-002`] | film; stream 1089 6.3.2 = 2 (Right charging), 6.2.2 = 1 | | 11:08:27.10 | **Buds' Connection Request** (596) → ACL (600) | Buds | — | HCI | | 11:08:27.8–11:08:28.8 | **Opened by itself:** link `CONNECTED` → "Automatic re-open (ADR-044, trigger: LINK_BACK)" → app `SABM` 0x02 (863) → announcement **ch 19** (884) → reads 16, 2, 4, 7, 12, 17, 19, 22 (923…1049) → `SubscribeRuntimeInfo` (1066; Case 89, 1089); snapshot claim `08 11` (973) → `Notify 01 e8 00 20` (990, Left in the hand) | App | BA-1 [`PAIR-003`, `EQS-001`] | export 11–58 | | 11:08:31.45 | Play services' claim: `Notify 01 e8 e8 40` — the Left bud is in the ear | Buds | [`INEAR-002`] | 1352 | @@ -113,13 +113,13 @@ app's claims start with `08 11` or `08 12` (FINDINGS §3). | 11:12:34–38 | Right bud from the ear into the right slot ("Right 100 % ⚡") | User | (not planned) [`CASE-006`] | film; stream 3611 6.3.2 = 2 | | 11:12:42–46 | Left bud from the ear into the left slot (both docked, lid open) → `Notify 01 e8 00 20` (PS, 3641, 11:12:43.04) → ACL `0x13` (3646, 11:12:45.965); card: "Connected to this phone (Android) / App control: not open yet … IOException: bt socket closed, read return: -1", then "Paired — not connected …" | User / Buds / App | BA-5 pre-state | export 197–203; film | | 11:12:52–54 | **Right**-slot bud out onto the table right of the case (Left docked) → Connection Request (3689) → re-open (LINK_BACK; attempt 1 failed 84 ms) → announcement **ch 21** (4074) → ready; snapshot `Notify 01 e8 00 20` (4120); "Left 100 % ⚡" | User / Buds / App | **BA-11 by accident** (only the Right out) [`CASE-005`, `PAIR-003`] | export 204–245; stream 4162 6.2.2 = 2 | -| 11:12:56–58 | **Left**-slot bud out onto the table left of the case; **both on the table**, case open and empty | User | **BA-5** [`CASE-005`] | film; stream 4321 none charging | +| 11:12:56–58 | **Left**-slot bud out onto the table left of the case; **both on the table**, case open and empty | User | **BA-5** [`CASE-004`] | film; stream 4321 none charging | | 11:13:20–28 | Battery (i): "Left: 100% (updated 11:12:56) — not charging (out of the case) (11:13:07) … Case: 89% — last seen 11:12:56 (no bud charging in the case) / The Buds report the Case level only while a bud is in the case. / Firmware: release_5.203" | User / App | — [`BATT-004`] | film | | 11:13:30–34 | **Left** bud into the left slot; Right stays on the table | User | **BA-6** (BA-5 not yet refreshed) [`CASE-006`] | film; stream 4518 6.2.2 = 2 | | 11:13:46–52 | Battery (i): "Left: 100% (updated 11:12:56) — charging in the case (11:13:34) / Right: … not charging (out of the case) (11:13:34) / Case: 89% (updated 11:13:34)" | User / App | — | film | | 11:13:56–11:14:06 | ANC tab: "ANC can only be changed while you wear the Buds. Tapping a mode checks again first.", Off | App | BA-6 | film | | 11:14:12–14 | Right bud into the right slot (both docked) → ACL `0x13` (4653, 11:14:14.830); "Not connected to the Buds …" | User / Buds | (not planned) [`CASE-006`] | export 261–266 | -| 11:14:20–22 | Right bud out onto the table, then the Left onto the table → Connection Request (4698) → re-open, announcement **ch 21** (4962) → snapshot `Notify 01 e8 00 20` (5280); the ANC note again | User / App | **BA-5 rep.** [`CASE-005`] | export 267–296; film | +| 11:14:20–22 | Right bud out onto the table, then the Left onto the table → Connection Request (4698) → re-open, announcement **ch 21** (4962) → snapshot `Notify 01 e8 00 20` (5280); the ANC note again | User / App | **BA-5 rep.** [`CASE-005`, `CASE-004`] | export 267–296; film | | 11:14:31.6 | **ANC Refresh, both buds on the table** → claim 5443 → `Get` (5453) → **`Notify 01 e8 00 20`** (5465, 11:14:33.437) | User / App | **BA-5 ✓** [`ANC-001`] | export 306–314; film (finger on Refresh 11:14:30) | | 11:14:42–46 | **Left** bud into the left slot; Right stays on the table | User | **BA-6 rep.** | film; stream 5596 6.2.2 = 2 | | 11:14:58.4 | **Refresh** → `Get` (5694) → **`Notify 01 e8 00 20`** (5707, 11:15:00.485); (i) "ANC mode: OFF (updated 11:15:00) / ANC can only be changed while you wear the Buds (checked 11:15:00)…" | User / App | **BA-6 ✓** | export 319–329; film 11:15:04–10 | @@ -127,7 +127,7 @@ app's claims start with `08 11` or `08 12` (FINDINGS §3). | 11:15:51.3 | **Refresh** (lid closed) → `Get` (6007) → battery **`03 03 00 03 ff 64 ff`** (6015 — Left unknown) → **`Notify 01 e8 00 20`** (6019, 11:15:53.518); (i) "(checked 11:15:53)" | User / App | **BA-7 ✓** | export 332–340; film 11:15:50–12:02 | | 11:16:14–16 | Lid opened (Left docked) | User | BA-7 → BA-8 | film; stream 6174 (11:16:19.21) 6.2.2 = 2, Case 89 again | | 11:16:18–22 | Right bud from the table, head on the **left** ⇒ **Right** into the ear → PS `Notify 01 e8 e8 40` (6224, 11:16:23.618) | User / Buds | BA-8 [`INEAR-003`] | film | -| 11:16:26–30 | Left bud out of the slot, head on the **right** ⇒ **Left** into the ear → PS `Notify 01 e8 e8 20` (6257, 11:16:31.526); both worn | User / Buds | BA-8 [`INEAR-002`, `CASE-005`] | film; stream 6240 none charging | +| 11:16:26–30 | Left bud out of the slot, head on the **right** ⇒ **Left** into the ear → PS `Notify 01 e8 e8 20` (6257, 11:16:31.526); both worn | User / Buds | BA-8 [`INEAR-002`, `CASE-004`] | film; stream 6240 none charging | | 11:16:40.2 | **Refresh, both worn** → `Get` (6322) → **`Notify 01 e8 e8 20`** (6334, 11:16:41.787) → the note disappears; (i) "ANC mode: OFF (updated 11:16:41) / Connection: Ready …" | User / App | **BA-8 ✓** | export 351–359; film 11:16:38–50 | | 11:16:52–54 | Lid closed (empty case) | User | — | film | | 11:17:00.6 | **Refresh** again → `Notify 01 e8 e8 20` (6532); (i) "updated 11:17:02" | User / App | BA-8 rep. | export 360–368; film 11:17:06 | @@ -135,7 +135,7 @@ app's claims start with `08 11` or `08 12` (FINDINGS §3). | 11:18:30.4 | **Disconnect** (both worn) | User | **BA-9** | export 415 (11:18:31.908); film 11:18:30 | | 11:18:32–48 | Lid opened; a bud from an ear into the right slot (11:18:38–40), the other into the left slot (11:18:42–46); lid closed (11:18:46–48) → PS `Notify … 40` (7011) and `… 00 20` (7091) → ACL `0x13` (7094, 11:18:46.496); "Paired — not connected …" | User / Buds | BA-9 ✓ [`CASE-006`] | export 417; film | | 11:18:48–11:19:15 | Lid closed, both docked, 27 s | User | BA-9 | film | -| 11:19:16–20 | Lid opened; **only the Left**-slot bud out, onto the table; Right docked | User | **BA-10** [`CASE-005`] | film; stream 8256 6.3.2 = 2 | +| 11:19:16–20 | Lid opened; **only the Left**-slot bud out, onto the table; Right docked | User | **BA-10** [`CASE-004`] | film; stream 8256 6.3.2 = 2 | | 11:19:23.0–23.9 | Connection Request (7346) → ACL (7350) → link `CONNECTED`; **no re-open** (the Disconnect tap switched ADR-044 off): "App control: not open yet — tap Connect" | Buds / App | ADR-044 item 2 ✓ | export 418; film 11:19:24 | | 11:19:52.25 | **Connect** → ready → announcement **ch 19** (8190); snapshot `Notify 01 e8 00 20` (8269) | User / App | **BA-10 ✓** [`PAIR-003`] | export 419–455 | | 11:19:58.4 | **Disconnect** (Left still out) | User | BA-11 var. | export 456 (11:20:00.934) | @@ -214,8 +214,9 @@ app's claims start with `08 11` or `08 12` (FINDINGS §3). **Traceability (`AGENTS.md` §13.7):** every skeleton step (S0–S2, BA-1 … BA-17, F7, K1–K5, L2/L3, A5, (E), B4, Z1/Z2) appears above with a result. Registry Test-IDs the skeleton names: exercised — `ANC-001` (tile 11:10:00, 11:11:24; BA-5 Refresh), `ANC-002` (tile 11:10:09, F7), `ANC-003` (tile 11:10:20), -`ANC-004` (tile 11:10:14), `PAIR-003` (every automatic re-open and the Connect taps 11:19:52, 11:20:14, BA-16), `CASE-004` (lid closed with a bud inside, -BA-7, BA-9), `CASE-005` (every bud out of the case), `CASE-006` (every bud into the case), `BATT-004` (every claim's `03 03` burst; BA-14), `EQS-001` +`ANC-004` (tile 11:10:14), `PAIR-003` (every automatic re-open and the Connect taps 11:19:52, 11:20:14, BA-16), `CASE-004` (every Left bud out of the case), +`CASE-005` (every Right bud out of the case), `CASE-006` (every bud into the case; lid closed with buds inside, BA-7, BA-9) (Left/Right tags corrected +2026-10-15, `ai-sessions/0094`, to TESTPLAN's titles), `BATT-004` (every claim's `03 03` burst; BA-14), `EQS-001` (every Connect's `ReadSetting 4:16`; BA-13), `INEAR-001` (field 2 read at every Connect and in BA-12/13; no write), `HOLD-001` (field 7 read, Noise control both buds; no write), `AUDIO-003` (field 17 read `0` = Centre; no write). **Not exercised:** `PAIR-001` (B4/Z1 skipped — no pairing), `EQP-002` (no preset tapped), `AUDIO-001` (no music played: AVRCP "Stopped" in all seven ACLs). diff --git a/captures/CAP-065-2026-10-01_11-07-30_11-28-23-Group_BA/CAP-065-FINDINGS.md b/captures/CAP-065-2026-10-01_11-07-30_11-28-23-Group_BA/CAP-065-FINDINGS.md index 7dd3c99c..c06498d1 100644 --- a/captures/CAP-065-2026-10-01_11-07-30_11-28-23-Group_BA/CAP-065-FINDINGS.md +++ b/captures/CAP-065-2026-10-01_11-07-30_11-28-23-Group_BA/CAP-065-FINDINGS.md @@ -92,8 +92,8 @@ Whenever that link came up while the app was visible and ADR-044 was on, the app Command: as in the header (HCI connection events) → exactly the seven Connection Request / Connection Complete pairs and six `0x13` (1498, 3646, 4653, 7094, 8613, 9683) / one LE `0x08` (12282) — `tshark -r CAP-065-btsnoop_hci.log -Y 'bthci_evt.code==0x05' -T fields -e frame.number -e bthci_evt.reason`; "five" corrected 2026-10-03, `ai-sessions/0069` Disconnection Complete events; `-Y "bthci_cmd.opcode==0x0405"` → 0 frames (positive control: the same filter matches `CAP-064` frames 673, 7201 and 8399). -- 🟢 Opening the lid with both buds docked produced **no HCI event** in A, E (after the lid had been closed), F and G; the link came 3.5–7 s after the lid - opened, each time 1–4 s after a bud left its slot. In `CAP-064` the same held (B, F there). No case in this run of Android re-paging the Buds after an ACL +- 🟢 Opening the lid with both buds docked produced **no HCI event** in A, E (after the lid had been closed), F and G; the link came 5.4–7.4 s after the lid + opened *(corrected 2026-10-15, `ai-sessions/0094`)*, each time 1–4 s after a bud left its slot. In `CAP-064` the same held (B, F there). No case in this run of Android re-paging the Buds after an ACL drop (all six drops were the Buds' `0x13` on docking; `CAP-064`'s 🔴 G-vs-F question gets no new sample). - 🟢 The ACL drops when the **last bud is docked**, also with the lid open (11:08:53.56 lid open, 11:12:45.97 lid open, 11:14:14.83 lid open), and does **not** drop when the lid is closed with one bud outside (BA-7, 11:15:30–11:16:14: ACL `0x000b` stayed up). 🟢 With the lid closed and the Left bud inside, the @@ -179,8 +179,8 @@ Command: the scratch decoder's message list filtered to `08 13`; raw example 546 | 9 | 10123, 11:21:34.62 | **19** | only the **Left** | 10405: Right charging | ✓ | | 10 | 12062, 11:27:35.99 (BA-16) | **19** | both worn (same ACL as #9) | 12124: none charging | ✓ (unchanged) | -- 🟢 FACT (this capture): with only one bud out of the case the Buds announced **19 when it was the Left and 21 when it was the Right — 7 of 7** (incl. #4's - first-out); the request addresses were the tabulated pairs (19 ↔ `00 3b`/`80 a3`, 21 ↔ `00 4b`/`00 a5`), matching `fux.java:90–103` (19 = `LEFT_BT_CORE`, +- 🟢 FACT (this capture): with only one bud out of the case the Buds announced **19 when it was the Left and 21 when it was the Right — 8 of 8** (#1, #2, #6, #7, #9 Left; #3, #4's + first-out, #8 Right) *(corrected 2026-10-15, `ai-sessions/0094`)*; the request addresses were the tabulated pairs (19 ↔ `00 3b`/`80 a3`, 21 ↔ `00 4b`/`00 a5`), matching `fux.java:90–103` (19 = `LEFT_BT_CORE`, 21 = `RIGHT_BT_CORE` on `MAESTRO_A`). 24/26 (`MAESTRO_B`) never occurred. - 🟡 HYPOTHESIS (strong): the announced channel names the bud that **currently hosts the phone's link** (the "primary"), not merely the first bud out — #5 shows the channel changing 21 → 19 within one ACL after a Buds-side close with both buds worn. The Message Stream's Device Information `03 02` ("BLE diff --git a/captures/CAP-066-2026-10-01_16-12-57_16-34-50-Group_BB/CAP-066-EVENT-NOTES.md b/captures/CAP-066-2026-10-01_16-12-57_16-34-50-Group_BB/CAP-066-EVENT-NOTES.md index 0928d31b..03b7ba3e 100644 --- a/captures/CAP-066-2026-10-01_16-12-57_16-34-50-Group_BB/CAP-066-EVENT-NOTES.md +++ b/captures/CAP-066-2026-10-01_16-12-57_16-34-50-Group_BB/CAP-066-EVENT-NOTES.md @@ -176,7 +176,7 @@ services (its first message is `03 08 00 02 01 25`); the app's claims start with | BB-8 Transparency, Off in the app | **done** → two claims, `08 11` first, ACK `80`, ACK `20` | A4855, A5007 | | BB-9 Disconnect / Connect, (i) at once | **done differently**: no Disconnect or Connect tap in the whole run; the session re-opened by itself when a bud left the case (16:22:21); the (i) was opened ≈ 6 s after ready — the "last connection" words were not seen | E1 620–629; film 16:22:28 | | BB-12 Left out with both worn on 19 | **not done** — on ch 19 with both worn (16:24:52 onwards) no bud was taken out on film | film; HCI (no `DISC` 16:24:52–16:32:17) | -| BB-12m Right out with both worn on 21 | **done, repeated** (16:16:46 → 19, 16:24:10 → 19), plus a variant (16:21:36, the Right from the ear into the case, on 19 → 19) and a new case (16:22:50, the Right **into** the ear with the Left on the table: 19 → 21) | FINDINGS §4 | +| BB-12m Right out with both worn on 21 | **done, repeated** (16:16:46 → 19, 16:24:10 → 19), plus a variant (16:21:36, the Right from the ear into the case, on 21 → 19: announcements A3267 ch 21, A5268 ch 19 — a switch) and a new case (16:22:50, the Right **into** the ear with the Left on the table: 19 → 21) | FINDINGS §4 | | BB-13 tile large | **done** (before the film, and again on film 16:25:06–30) | film | | BB-14 force-stop, tile within 3 s | **done differently**: real force-stop ✓ (16:26:54); the tile read ≈ 9 s after ready: "ANC / Off" — never "Open the app" while ready | film; system log | | BB-15 cut-off watch | **watched — no cut-off occurred**: 0 of 23 app claims had an answer after the claim's close; no "answer cut off" line | FINDINGS §2 | diff --git a/captures/CAP-067-2026-10-03_07-57-35_08-18-29-Group_BC/CAP-067-EVENT-NOTES.md b/captures/CAP-067-2026-10-03_07-57-35_08-18-29-Group_BC/CAP-067-EVENT-NOTES.md index 9d14b368..e3c925e4 100644 --- a/captures/CAP-067-2026-10-03_07-57-35_08-18-29-Group_BC/CAP-067-EVENT-NOTES.md +++ b/captures/CAP-067-2026-10-03_07-57-35_08-18-29-Group_BC/CAP-067-EVENT-NOTES.md @@ -18,7 +18,7 @@ Info tab's licence and links, dark mode Off/System, an export with Debug mode of | Capture ID | `CAP-067` | | Group(s) | BC | | Date | 2026-10-03 | -| Firmware version | 🟢 `release_5.203` — all 7 MAESTRO announcements of the HCI logs (e.g. `.log.last` frame 253) and the Info tab on film ("Case / Left bud / Right bud: release_5.203", 07:59:04–10, 08:09:30, 08:10:50) | +| Firmware version | 🟢 `release_5.203` — all 6 MAESTRO announcements of the HCI logs (e.g. `.log.last` frame 253) and the Info tab on film ("Case / Left bud / Right bud: release_5.203", 07:59:04–10, 08:09:30, 08:10:50) | | Test device | Pixel 9a, GrapheneOS, Android 17 `CP3A.260905.009` (logcat header `google/tegu/tegu:17/CP3A.260905.009/2026092501:user/release-keys`), **`userType: full.secondary`** (logcat header) — a secondary user. **No Google Play in that user:** the app drawer on film (07:58:00) lists App Store, Auditor, Bellen, Berichten, Bestanden, Camera, Contacten, Galerij, Info, Instellingen, Klok, OpenControl, PDF Viewer, Rekenmachine, Vanadium — no Play Store; the HCI logs carry **no** Play-services Message Stream claim (FINDINGS §2: 0 of 6 claims; positive control `CAP-066` 26). P0's `pm list packages` output was not saved (no file) | | App build | **OpenControl for Pixel Buds Pro 2 1.0.0, build `8d8af4b` (2026-10-02), not "-dirty"** — the Info tab on film (07:58:25–31, full-resolution crop t = 52 s: "App: 1.0.0, build 8d8af4b (2026-10-02)") and the logcat header `package: io.github.tedsluis.opencontrolpixelbuds:10000, targetSdk 34` (versionCode 10000 = 1.0.0). `8d8af4b` = "docs(capture): adapt the CAP-067 skeleton …" (2026-10-02 08:33:08 +0200). `dist/1.0.0/opencontrol-pixelbudspro2-1.0.0.apk` (read only): SHA-256 `107d49b6…f3467a2`, signing certificate SHA-256 `a7530f5c…79d8dcb`, `versionCode='10000' versionName='1.0.0'`, label "OpenControl for Pixel Buds Pro 2", no `INTERNET` permission (`apksigner verify --print-certs`, `aapt2 dump badging`) — equal to the values in the prompt. That the phone ran **this** file is consistent (version code, hash, label, notification title) but not proven (the APK was not pulled from the phone). Launcher label "OpenControl" on film (07:58:00–11); notification title "OpenControl for Pixel Buds Pro 2" (08:10:21, 08:12:15, 08:13:33, 08:15:21) | | Other devices | Bluetooth list on film: "Charge 6" and "Niro" (saved, not connected). Classic handle `0x000b` = the Buds, the only handle in both files. LE: only advertising reports during the CDM picker's scan (08:16:51.5–08:16:58.6, 18 addresses incl. the Fitbit `c8:cc:a8:e7:48:93`), no LE link | @@ -113,10 +113,10 @@ frame N of `.log.last` / `.log` (handle `0x000b`); "E1 N" / "E2 N" = line N of t | 08:03:26 | Tap **Sound**; scroll to "Balance and audio" (Centre) | User | BC-3 | film | | 08:03:52.7 – 08:04:43.1 | **17 balance drags**, one `WriteSetting 4:{17:n}` each, all `RESPONSE` OK: Right 8, Right 7, Centre, Centre, Right 5, Centre, Centre, Right 12, Centre, Centre, Right 13, Right 13, Left 7, Left 27, Left 52, Centre, **Right 4** (A876 08:04:43.079, `17:7`); label after each OK on film ("Right 8" 08:03:52, "Right 7" 08:04:04, "Right 5" 08:04:12, "Right 12"/"13" 08:04:26–32, "Left 27" 08:04:34, "Left 52" 08:04:36–38, "Right 4" 08:04:44–58) | User / App | BC-3 ✓ (17th drag) [`AUDIO-003`] | A818–A878; E1 162–212 | | 08:05:00.0 | Drag to the centre → `17:0` (A882 08:05:01.136) → "Centre" | User / App | BC-5 ✓ [`AUDIO-003`] | A882/A885; E1 213–215; film | -| 08:05:12 | The **right-hand-slot** bud out of the case; head enters from frame-**left** → **Right** bud into the Right ear | User | (BC-6 prep) [`INEAR-002`] | film; A889 08:05:12.666 6.3.2 = 1 | -| 08:05:24–08:05:31 | The **left-hand-slot** bud out (head out of view) → Left bud into the Left ear; case empty | User | (BC-6 prep) | film; A906 08:05:32.340 (no 6.1, 6.2.2 = 1) | +| 08:05:12 | The **right-hand-slot** bud out of the case; head enters from frame-**left** → **Right** bud into the Right ear | User | (BC-6 prep) [`INEAR-003`] | film; A889 08:05:12.666 6.3.2 = 1 | +| 08:05:24–08:05:31 | The **left-hand-slot** bud out (head out of view) → Left bud into the Left ear; case empty | User | (BC-6 prep) [`INEAR-002`] | film; A906 08:05:32.340 (no 6.1, 6.2.2 = 1) | | 08:05:36–08:06:02 | Both worn (off frame), Sound tab; session on **21** | — | — | film | -| 08:06:04–08:06:10 | **Head on frame-left → the Right bud out of the ear** (both had been worn on 21), laid right of the case | User | **BC-6a ✓** [`INEAR-003`] | film (2 fps) | +| 08:06:04–08:06:10 | **Head on frame-left → the Right bud out of the ear** (both had been worn on 21), laid right of the case | User | **BC-6a ✓** [`INEAR-004`] | film (2 fps) | | 08:06:06.805 | **Buds `DISC` MAESTRO** (A945) with the ACL up → E1 "Session lost … Buds closed the channel (provisional …)" → **AFTER_LOSS** re-open 08:06:08.340 → ready 08:06:08.906 → announcement **ch 19** (A982); claim A1001: `Notify 01 e8 e8 80` (Transparent), battery `64 64 ff` | Buds / App | BC-6a ✓ (21 → 19) | A945–A1049; E1 228–269; film (Sound dimmed 08:06:08) | | 08:06:55–08:06:58 | The Right bud picked up from the table and put back into the Right ear (head on frame-left) | User | BC-6a ✓ | film | | 08:07:29–08:07:34 | **A bud taken out of an ear** (head **not** in view; the hand enters from the bottom-left) and laid left of the case | User | **BC-6 (bud not identifiable)** [`INEAR-004`] | film (3 fps) | @@ -199,7 +199,7 @@ frame N of `.log.last` / `.log` (handle `0x000b`); "E1 N" / "E2 N" = line N of t | BC-end | done | film end | | Extra | the Connect tap with the lid closed (07:58:39); a tab tour (08:08:42); dark mode On (08:09:24, 08:10:52); the ANC mode change by the Buds (Transparent → Active) | timeline | -Registry Test-IDs: [`AUDIO-003`] BC-3/BC-5/BC-R ✓; [`INEAR-002`] 08:05:12 ✓; [`INEAR-003`] BC-6a ✓; [`INEAR-004`] BC-6 (bud not identifiable); [`PAIR-003`] +Registry Test-IDs: [`AUDIO-003`] BC-3/BC-5/BC-R ✓; [`INEAR-003`] 08:05:12 ✓; [`INEAR-002`] 08:05:24–31 ✓; [`INEAR-004`] BC-6a ✓ (Right) and BC-6 (bud not identifiable); [`PAIR-003`] BC-11 ✓ (wire only), also after A5 and Z1; [`BATT-004`] every connect ✓; [`PAIR-001`] Z1 ✓. ## Analysis checklist diff --git a/captures/CAP-067-2026-10-03_07-57-35_08-18-29-Group_BC/CAP-067-FINDINGS.md b/captures/CAP-067-2026-10-03_07-57-35_08-18-29-Group_BC/CAP-067-FINDINGS.md index f315e696..4b0f2ed3 100644 --- a/captures/CAP-067-2026-10-03_07-57-35_08-18-29-Group_BC/CAP-067-FINDINGS.md +++ b/captures/CAP-067-2026-10-03_07-57-35_08-18-29-Group_BC/CAP-067-FINDINGS.md @@ -11,8 +11,8 @@ system log. The timeline these findings refer to is `CAP-067-EVENT-NOTES.md`. - ⚪ **ASSUMPTION** — not tested here. - 🔴 **OPEN QUESTION** — genuinely unresolved by this capture. -**Capture ID:** `CAP-067` · **Date:** 2026-10-03, film overlay 07:57:35–08:18:29 (phone 07:57:33.9–08:18:27.9) · **Firmware:** 🟢 `release_5.203` (7 of 7 -announcements, Info tab on film) · **Phone:** Pixel 9a, GrapheneOS, Android 17 `CP3A.260905.009`, **secondary user (`full.secondary`), no Google Play** · +**Capture ID:** `CAP-067` · **Date:** 2026-10-03, film overlay 07:57:35–08:18:29 (phone 07:57:33.9–08:18:27.9) · **Firmware:** 🟢 `release_5.203` (6 of 6 +announcements *(corrected 2026-10-15, `ai-sessions/0094`)*, Info tab on film) · **Phone:** Pixel 9a, GrapheneOS, Android 17 `CP3A.260905.009`, **secondary user (`full.secondary`), no Google Play** · **App under test:** OpenControl for Pixel Buds Pro 2 **1.0.0, build `8d8af4b`** (release-signed, versionCode 10000) · **HCI logs:** "A" = `.log.last` (1,563 packets, 07:57:42.429–08:12:24.089), "B" = `.log` (1,810 packets, 08:12:38.178–08:20:05.132), 0 `cap_len≠len` · **Buds:** `04:00:6e:cf:6e:07`, classic handle `0x000b` for every ACL, the only handle in both files; no LE link. `bluetooth.addr` is empty with this `H4 with linux header` encapsulation, so every command diff --git a/captures/CAP-068-2026-10-04_07-22-57_07-54-10-Group_BD/CAP-068-EVENT-NOTES.md b/captures/CAP-068-2026-10-04_07-22-57_07-54-10-Group_BD/CAP-068-EVENT-NOTES.md index 45a96be4..f9370c9b 100644 --- a/captures/CAP-068-2026-10-04_07-22-57_07-54-10-Group_BD/CAP-068-EVENT-NOTES.md +++ b/captures/CAP-068-2026-10-04_07-22-57_07-54-10-Group_BD/CAP-068-EVENT-NOTES.md @@ -84,16 +84,16 @@ Phone time. "A"/"B"/"Z" frame numbers as in Log Metadata; E5 line numbers; L = l | 07:25:09–07:25:13 | **A bud out of the case** → Buds `DISC` MAESTRO (A749 07:25:11.294) → card "The app's channel was closed while Android still shows the Buds connected — the Buds do this when a bud goes in or out of the case or an ear …" → automatic re-open 07:25:12.8, announcement **21** (A797). Which bud: the **Right** (A860 runtime info 6.3 field 2 = 1, 6.2 = 2; film 07:25:19 "Left 100 % ⚡ … Right 100 %") | User → Buds → App | (BD-1 pre) | — | `CASE-005` | A749, A797, A860; E5 45–74; film 1 | | 07:25:27–07:25:37 | **The other (Left) bud out** → Buds `DISC` (A883 07:25:34.474) → re-open, announcement **19** (A919) | User → Buds → App | (BD-1 pre) | — | `CASE-004` | A883, A919, A983 (no 6.1, 6.2/6.3 field 2 = 1); E5 93–120 | | 07:25:41 | ANC tab: Noise cancellation ✓ (Notify A971 `01 e8 e8 08`) | User | — | — | — | film 1 | -| 07:25:50.5 | Tab **Transparency** → claim 4: Get → Notify `e8 e8 08` → `Set … 80` (A1039) → ACK `ff 01 00 06 08 12 01 e8 e8 80` (A1042) → Notify `… 80` | User | BD-1 | — | `ANC-002` | A1017–A1049; film 1 07:25:51 ✓ Transparency | +| 07:25:50.5 | Tab **Transparency** → claim 4: Get → Notify `e8 e8 08` → `Set … 80` (A1039) → ACK `ff 01 00 06 08 12 01 e8 e8 80` (A1042) → Notify `… 80` | User | BD-1 | — | `ANC-004` | A1017–A1049; film 1 07:25:51 ✓ Transparency | | — | **Adaptive not tapped on the tab** | — | BD-2 skipped | — | `ANC-003` (tile only) | film 1; no `Set … 40` before 07:26:45 | | 07:25:59.9 | Tab **Off** → `Set … 20` (A1090) → ACK (A1093) | User | BD-3 | — | `ANC-001` | film 1 07:26:01 | -| 07:26:15.8 | Tab **Noise cancellation** → `Set … 08` (A1146) → ACK (A1149); at the release the phone sent three `DM` on DLCI 4 (A1157/60/61) while the Buds' battery frames crossed its `DISC` (A1154/55) | User | BD-4 | — | `ANC-004` | A1124–A1164; film 1 07:26:17 | +| 07:26:15.8 | Tab **Noise cancellation** → `Set … 08` (A1146) → ACK (A1149); at the release the phone sent three `DM` on DLCI 4 (A1157/60/61) while the Buds' battery frames crossed its `DISC` (A1154/55) | User | BD-4 | — | `ANC-002` | A1124–A1164; film 1 07:26:17 | | 07:26:28–29 | Shade: "Connected — ANC: ACTIVE" | — | — | — | — | film 1 | -| 07:26:37.1 | **Tile** (shown ACTIVE) → Get → Notify `08` → `Set 80` (A1215) → ACK (A1218) → tile "Transparent" | User | BD-5 (1) | — | `ANC-002` | E5 171; A1193–A1225; film 1 07:26:39 | +| 07:26:37.1 | **Tile** (shown ACTIVE) → Get → Notify `08` → `Set 80` (A1215) → ACK (A1218) → tile "Transparent" | User | BD-5 (1) | — | `ANC-004` | E5 171; A1193–A1225; film 1 07:26:39 | | 07:26:44.7 | Tile → Notify `80` → `Set 40` (A1266) → ACK (A1269) → "Adaptive" | User | BD-5 (2) | — | `ANC-003` | E5 183; film 1 07:26:45 | | 07:26:51.4 | Tile → Notify `40` → `Set 20` (A1323) → ACK → "Off" | User | BD-5 (3) | — | `ANC-001` | E5 195; film 1 07:26:53 | -| 07:26:57.8 | Tile → Notify `20` → `Set 08` (A1375) → ACK → "Active" | User | BD-5 (4) | — | `ANC-004` | E5 209; film 1 07:26:59 | -| 07:27:06.5 | Tile → Notify `08` → `Set 80` (A1426) → ACK → "Transparent" — **a fifth tile tap** (repeat) | User | BD-5 (5, repeated) | — | `ANC-002` | E5 221; film 1 07:27:07 | +| 07:26:57.8 | Tile → Notify `20` → `Set 08` (A1375) → ACK → "Active" | User | BD-5 (4) | — | `ANC-002` | E5 209; film 1 07:26:59 | +| 07:27:06.5 | Tile → Notify `08` → `Set 80` (A1426) → ACK → "Transparent" — **a fifth tile tap** (repeat) | User | BD-5 (5, repeated) | — | `ANC-004` | E5 221; film 1 07:27:07 | | 07:27:19–07:27:37 | **Both buds out of the ears onto the table** (head in view 07:27:19–21, the ear covered by the hand; no head-side assignment). No Buds `DISC` on this change | User | BD-6 | — | `INEAR-004` | film 1; A (no `DISC` 07:27:08–07:28:38) | | 07:27:51.1 | **Tile** → claim 12: Notify **`01 e8 00 20`** (A1508) → **no `Set`**; toast "The Buds don't allow changing noise control right now (usually because no b…"; tile "Not allowed now" | User | BD-6 | S11 | `INEAR-004` | E5 237, 245; A1486–A1511; film 1 07:27:53–55 | | 07:27:58–07:28:05 | Home, app reopened; ANC tab shows **Off** ✓ (the claim's Notify) with the "don't allow" note | User | BD-6 | — | — | film 1; L 67–72 | @@ -101,7 +101,7 @@ Phone time. "A"/"B"/"Z" frame numbers as in Log Metadata; E5 line numbers; L = l | 07:28:33–07:28:41 | First bud into an ear (head not in view) → Buds `DISC` (A1635 07:28:38.960) → re-open, announcement **21** (A1673); claim 14 Notify `01 e8 e8 80` (A1719) — Transparent, the Buds' own mode | User → Buds → App | BD-7 pre | — | `INEAR-002`/`003` (bud not identifiable) | E5 265–303; film 1 07:28:39–43 | | 07:28:45–07:28:51 | Second bud into an ear → Buds `DISC` (A1744 07:28:50.960) → **"Automatic re-open skipped: the session was lost 9939 ms after an automatic re-open"** (ADR-044's 10-s guard); ANC tab "Not connected to the Buds …" | User → Buds → App | — | — | — | E5 307–311; film 1 07:28:51–07:29:21 | | 07:29:22–23 | **Pull-to-refresh on the ANC tab** (not in the plan) → Connect → ready on **19** (A1791) | User | extra | — | `PAIR-003` | film 1 4 fps 07:29:22.5; E5 312 | -| 07:29:30.4 | Tab **Noise cancellation** (shown Transparency) → Notify `80` → `Set 08` (A1913) → ACK | User | BD-7 done differently (the shown mode was Transparency, not Active) | — | `ANC-004` | A1887–A1922; film 1 07:29:31 | +| 07:29:30.4 | Tab **Noise cancellation** (shown Transparency) → Notify `80` → `Set 08` (A1913) → ACK | User | BD-7 done differently (the shown mode was Transparency, not Active) | — | `ANC-002` | A1887–A1922; film 1 07:29:31 | | 07:29:40.4 | Tab **Off** → Notify `08` → `Set 20` (A1967) → ACK (A1970) | User | BD-8 (1st) | — | `ANC-001` | A1943–A1990; film 1 07:29:41 | | 07:29:46.0 | Tab **Off again** (the current mode) → Notify **`01 e8 e8 20`** (A2033) → **`Set … 20` sent** (A2034) → **ACK** `ff 01 00 06 08 12 01 e8 e8 20` (A2036) → Notify `… 20` (A2037) | User | BD-8 (2nd) | — | **`ANC-006`** | A2009–A2042; screen unchanged | | 07:30:22–07:30:31 | Connection tab: Left 100 % / Case 80 % (dimmed, dot) / Right 98 % | — | — | — | — | film 1 | @@ -144,7 +144,7 @@ Phone time. "A"/"B"/"Z" frame numbers as in Log Metadata; E5 line numbers; L = l | 07:49:06.6 | Buds `DISC` MAESTRO (B2434) → "Still connecting…" → re-open (ch 19) | Buds → App | extra loss | — | — | E5 822–841 | | 07:49:19–26 | Export (Debug mode on) → "Debug log saved (862 lines)." (E3) | User | (BD-25 pre) | — | — | film 2; E5 866 | | 07:49:28 | **Debug mode off** | User | BD-25 | — | — | film 2 | -| 07:49:42–44 | ANC tab **Transparency** → Notify `08` → `Set 80` (B2629) → ACK (B2632) | User | BD-25 | — | `ANC-002` | B2605–B2639; film 2 | +| 07:49:42–44 | ANC tab **Transparency** → Notify `08` → `Set 80` (B2629) → ACK (B2632) | User | BD-25 | — | `ANC-004` | B2605–B2639; film 2 | | 07:49:47–54 | Export → "Debug log saved (869 lines)." (E4): **no hex line** after the switch (E4 = E3 + 7 state lines; the claim's 12 inbound messages are not dumped) | User | BD-25 | — | — | E4 vs E3; film 2 | | 07:50:12–07:50:28 | Both buds out of the ears onto the table | User | BD-26 pre | — | — | film 2 | | 07:50:29–30 | **Debug mode on** again | User | BD-26 pre | — | — | film 2 4 fps | @@ -210,14 +210,14 @@ Phone time. "A"/"B"/"Z" frame numbers as in Log Metadata; E5 line numbers; L = l - [x] Every filter scoped to handle `0x000b`; DLCIs identified by content (MAESTRO announcements on 0x02; `03 0a`/`03 01` on 0x04; AT commands on 0x0c/0x09). - [x] P0 and Info: user 10, no `com.google.android.gms`/`com.android.vending` (P0 exit status not recorded — the wire negative carries it); 1.0.1 `e1fc886`; APK and certificate SHA-256. - [x] Zero Play-services claims (0 in four logs; `CAP-066` 26). -- [x] I: 11 `Set`s on film with ACK and Notify (tab 4, tile 5, BD-25 1, BD-7′ 1); the Definition-of-done note — the maintainer's decision (`ai-sessions/0070` checkpoint). +- [x] I: 12 `Set`s on film with ACK and Notify (tab 5 — BD-1, BD-3, BD-4, BD-8 ×2 —, tile 5, BD-25 1, BD-7′ 1); the Definition-of-done note — the maintainer's decision (`ai-sessions/0070` checkpoint). - [x] II: a tap on the current mode sends a `Set` and the Buds ACK it (BD-8). - [x] III: the two exports against the app's adapter lines and the HCI logs (no system log). - [x] IV: BD-13/14 (S1 tile, S2 dimmed, S4 text), BD-15, BD-17, BD-19; BD-18 not identifiable. - [x] V: one row per step (above; `APP_TESTPLAN.md` Summary updated). - [x] VI: every `04 01`/`ff 01` frame by direction (`CAP-068-FINDINGS.md` §6). - [x] VII: page timeout `0x04` and the text on film. -- [x] Traceability (`AGENTS.md` §13.7): `ANC-001` (BD-3, BD-5, BD-8), `ANC-002` (BD-1, BD-5, BD-25), `ANC-003` (tile only — tab skipped), `ANC-004` (BD-4, BD-5, BD-7′), `ANC-006` (BD-8), `PAIR-003` (BD-9…11, BD-13/14, BD-20, BD-29/30 and the losses), `BATT-004` (every connect), `EQS-001` (BD-15/16, BD-21), `FIND-001` (BD-24, BD-26, BD-28), `FIND-005` (BD-27 — no wire evidence, see FINDINGS §6), `INEAR-004` (BD-6) — each in the timeline. +- [x] Traceability (`AGENTS.md` §13.7): `ANC-001` (BD-3, BD-5, BD-8), `ANC-002` (BD-4, BD-5, BD-7′), `ANC-003` (tile only — tab skipped), `ANC-004` (BD-1, BD-5, BD-25), `ANC-006` (BD-8), `PAIR-003` (BD-9…11, BD-13/14, BD-20, BD-29/30 and the losses), `BATT-004` (every connect), `EQS-001` (BD-15/16, BD-21), `FIND-001` (BD-24, BD-26, BD-28), `FIND-005` (BD-27 — no wire evidence, see FINDINGS §6), `INEAR-004` (BD-6) — each in the timeline. - [x] `CAPTURE_BLUETOOTH_HCI_SNOOP.md` §9, `id_registry.csv`, `APP_TESTPLAN.md`, `CAP-068-FINDINGS.md`. ## Appendix A — the planned procedure (skeleton as committed in `e1fc886`; text unchanged, heading levels lowered by one) diff --git a/captures/CAP-068-2026-10-04_07-22-57_07-54-10-Group_BD/CAP-068-FINDINGS.md b/captures/CAP-068-2026-10-04_07-22-57_07-54-10-Group_BD/CAP-068-FINDINGS.md index 2b2345e3..1aba35eb 100644 --- a/captures/CAP-068-2026-10-04_07-22-57_07-54-10-Group_BD/CAP-068-FINDINGS.md +++ b/captures/CAP-068-2026-10-04_07-22-57_07-54-10-Group_BD/CAP-068-FINDINGS.md @@ -16,7 +16,7 @@ exports and the one app logcat, per `ai-sessions/0070`. There is no system log. **HCI logs:** "Z" = `CAP-068-btsnoop_hci.log.last` (1,413 packets, 07:11:25.133–07:23:02.020, before the film), "A" = `CAP-068-btsnoop_hci2.log.last` (2,314, 07:23:07.514–07:34:58.304), "B" = `CAP-068-btsnoop_hci2.log` (3,563, 07:35:44.243–07:56:06.298); `CAP-068-btsnoop_hci.log` is a byte-for-byte prefix of A (2,219 packets) and is not counted · **Exports:** E1…E5, each a prefix of the next; E5 = `…-075355.txt` · **Buds:** `04:00:6e:cf:6e:07`, classic handle -`0x000b` for all six ACLs; no LE link. +`0x000b` for all five ACLs (plus one page timeout, §1); no LE link. Commands used throughout (rule 4a; all on handle `0x000b`, shown matching the Connection Complete events A160, B159, B1092, B2844): the control-frame inventory `tshark -r -Y "bthci_acl.chandle==0x000b && btrfcomm && (btrfcomm.frame_type==0x2f || btrfcomm.frame_type==0x63 || btrfcomm.frame_type==0x43 @@ -162,7 +162,7 @@ scripts/message_stream_tally.py -v` (`CAP-068-btsnoop_hci2.log.last {'get': 19, recovered on the next tap; no session was lost without a message. Criterion 3 ("stable across multiple connect/disconnect cycles") holds over 15 ends. - 🟢 **Lead L-1 (the hosting bud):** with both docked on 19, the **Right** out ⇒ `DISC` + **21**; then the Left out ⇒ `DISC` + **19** (#1, #2). Fits the 🟡 reading of `PROTOCOL.md` §2.2a ("the channel names the bud hosting the link") but does not test the open case (the Left out with both **worn** on 19). No status change. -- 🟡 #11 and #13: no action on film; the ring tone became audible 13 s after the Ring (07:48:44.8) — consistent with a bud being taken out of an ear while ringing +- 🟡 #11 and #13: no action on film; the ring tone became audible at 07:48:44.8, 28 s after the Ring (07:48:16.9) and 13 s after loss #11 — consistent with a bud being taken out of an ear while ringing (#11) and put back (#13). Head not in view; not settled. - 🟢 **J4 (BD-23):** app paused 07:45:47.552 → resumed 07:47:59.548 (logcat); MAESTRO stayed open (runtime info B2051, B2059, B2065 received), no `DISC`; on return "Noise cancellation", no message. diff --git a/captures/CAP-069-2026-10-04_16-05-29_16-27-07-Group_BE/CAP-069-EVENT-NOTES.md b/captures/CAP-069-2026-10-04_16-05-29_16-27-07-Group_BE/CAP-069-EVENT-NOTES.md index 01dd6616..3c40e297 100644 --- a/captures/CAP-069-2026-10-04_16-05-29_16-27-07-Group_BE/CAP-069-EVENT-NOTES.md +++ b/captures/CAP-069-2026-10-04_16-05-29_16-27-07-Group_BE/CAP-069-EVENT-NOTES.md @@ -149,7 +149,7 @@ Phone time = overlay + 0.43 s. Frames: `CAP-069-btsnoop_hci.log`. "MS" = Message | BE-14 | **done** (Adaptive → Noise cancellation) | | BE-15 | **done** (tap on Adaptive after NC → Adaptive) | | BE-16 | **done** (Transparency), and **repeated** (Off) | -| BE-17 | **done early** (16:07:17, from Balanced) and **repeated** four more times (16:17:36, 16:18:43, 16:19:07; from Heavy bass, Heavy bass, Last saved) | +| BE-17 | **done early** (16:07:17, from Balanced) and **repeated** three more times (16:17:36, 16:18:43, 16:19:07; from Heavy bass, Heavy bass, Last saved) | | BE-18 | **done differently** — "Last saved" (a new custom curve saved at 16:18:59) instead of Balanced; the run ends with the EQ **not** restored | | BE-19 | **done differently** — both buds docked at 16:19:42 dropped the link (expected, ADR-016); the "connected, both in the case" state did not exist | | BE-20 … BE-27 | **done** in order (BE-26/27 also once out of order at 16:19:37–42, before BE-19) | diff --git a/captures/CAP-069-2026-10-04_16-05-29_16-27-07-Group_BE/CAP-069-FINDINGS.md b/captures/CAP-069-2026-10-04_16-05-29_16-27-07-Group_BE/CAP-069-FINDINGS.md index 7a11d77d..054c99ae 100644 --- a/captures/CAP-069-2026-10-04_16-05-29_16-27-07-Group_BE/CAP-069-FINDINGS.md +++ b/captures/CAP-069-2026-10-04_16-05-29_16-27-07-Group_BE/CAP-069-FINDINGS.md @@ -60,8 +60,8 @@ the OFF tap on film) and the smali write site (`PROTOCOL.md` §4.5.4, 2026-10-03 filmed samples on the current firmware and app, zero counter-examples. 🟢 FACT for this capture. **GSND CONTROL Code `0x16` (🟡, refines lead L68-4):** its value is **not** a copy of field 29 — it is `08 01` after every ON and `08 02` after every OFF -(6/6), i.e. **inverted** against the skeleton's prediction (`BE-2`'s "Expected": `08 01` after ON — that matched; `BE-3`'s `08 02` after OFF — matched; the -skeleton therefore predicted it right, field 29 and Code `0x16` simply use opposite numbers). It also changes without a write: `08 02` with the setting ON when no +(6/6), i.e. **inverted** against field 29's numbering (1 = off, 2 = on), as the skeleton predicted (`BE-2`'s "Expected": `08 01` after ON — matched; `BE-3`'s +`08 02` after OFF — matched; field 29 and Code `0x16` simply use opposite numbers). It also changes without a write: `08 02` with the setting ON when no bud is worn (6329 16:19:40 both docked; 6880 and 8359 at connect; 7521 both buds on the table) and `08 01` when a bud goes into an ear (2262, 7320). 🟡 HYPOTHESIS: Code `0x16` = "head gestures active" (1 = active: setting on and worn; 2 = inactive). Test: a head-gesture toggle with no bud worn (predict no `04 16` change, or `02` both ways). @@ -132,7 +132,7 @@ ACK it. The decision is the maintainer's (§12 item 3, checkpoint). ## 5. `EQP-001` — the official "Default" preset writes 0.0 × 5 = OpenControl's "Flat" (🟢 FACT for this capture) -Five Default taps, five writes `4:{16:{0.0, 0.0, 0.0, 0.0, 0.0}}`, raw RpcPacket payload +Five Default selections, four writes `4:{16:{0.0, 0.0, 0.0, 0.0, 0.0}}` *(corrected 2026-10-15, `ai-sessions/0094`)*, raw RpcPacket payload `2a 1e 22 1c 82 01 19 0d 00 00 00 00 15 00 00 00 00 1d 00 00 00 00 25 00 00 00 00 2d 00 00 00 00` in 1990 (16:07:18.544, from Balanced), 5803 (16:17:36.77, from Heavy bass), 5965 (16:18:44.02, from Heavy bass), 6066 (16:19:07.80, from Last saved) — every one OK — byte-identical in payload to OpenControl 1.0.1's "Flat" (`CAP-015` frame 2111, `PROTOCOL.md` §4.2 2026-10-03 Update). (Four writes for five filmed selections: the menu opened at 16:19:10 and 16:19:14 with Default @@ -215,7 +215,7 @@ Readings (each a 🟡 HYPOTHESIS unless marked): charging and the MS battery flipped only at 16:20:46.06 (7404), 0.26 s before `04 05 06` — when the bud went into the ear, 11.7 s later. Elsewhere the bit changed within 0.5 s of the movement (2217, 2306). 🔴 why; relevant to OpenControl's "charging in the case" line (§12 item 7). - **Field 13 on the settings stream (🟡, `PROTOCOL.md` §6 item of 2026-09-28):** `13:2` NC, `13:4` Adaptive, `13:3` Transparency, `13:1` Off — 4/4 with the four `Set`s - (5528, 5611, 5656, 5701) and 6/6 with the unsolicited Notifies (2270, 2367, 7327, 7419, 7459), `13:0` with Settable `00` (7530). Strengthened; field 13 is + (5528, 5611, 5656, 5701) and 5/5 with the unsolicited Notifies that kept MAESTRO open (2270, 2367, 7327, 7419, 7459; the sixth, 6156, had no push — the Buds `DISC`ed MAESTRO with it, 6157), `13:0` with Settable `00` (7530). Strengthened; field 13 is readable (`ReadSetting 4:13` → `13:0`, 933). - **The MAESTRO channel follows the hosting bud (🟡, `PROTOCOL.md` §2.2a):** announcement ch 21 with both buds in the case (907), ch 19 when only the Left was out (6336, 7089), ch 21 with both taken out together (8139) — consistent with "the channel names the bud that hosts the link". diff --git a/captures/CAP-070-2026-10-07_06-10-41_06-40-59-Group_BF/CAP-070-EVENT-NOTES.md b/captures/CAP-070-2026-10-07_06-10-41_06-40-59-Group_BF/CAP-070-EVENT-NOTES.md index 4233c226..ccf53e40 100644 --- a/captures/CAP-070-2026-10-07_06-10-41_06-40-59-Group_BF/CAP-070-EVENT-NOTES.md +++ b/captures/CAP-070-2026-10-07_06-10-41_06-40-59-Group_BF/CAP-070-EVENT-NOTES.md @@ -90,7 +90,7 @@ Times: phone clock (log times exact; film-only events = overlay + 0.5 s, rounded | 06:14:40.35 | **Other alerts off** → A777 `4:{27:0}` → A780 | maintainer / app | BF-6 (differently) | T6 | `CASE-002` | A777/A780; E1 116 | | 06:14:46–51 | Sound tab; **Volume EQ off** → A785 `4:{15:0}` → A788 | maintainer / app | BF-7 (differently) | T7 | `AUDIO-002` | A785/A788; E1 119 | | 06:14:57.79 / 06:15:02.94 | **Disconnect** (A790) then **Connect** → ch 21 (A824): reads answer `11:0` A880, `15:0` A886, `27:0` A898, `28:0` A901, `29:1` A904 | maintainer / app | BF-8 | T8 | `PAIR-003`, `MULTI-001`, `HEAD-001`, `CASE-001`, `CASE-002`, `AUDIO-002` | A790–A905; E1 120–164 | -| 06:15:16 → 06:15:18.23 | **Left** (upper slot) out of the case onto the table → Buds `DISC` DLCI 2 (A913), ACL up | maintainer / Buds | BF-9 (differently: Left out of the case, not of an ear) | — | `CASE-004`, `INEAR-002` | film; A913; E1 168–172 | +| 06:15:16 → 06:15:18.23 | **Left** (upper slot) out of the case onto the table → Buds `DISC` DLCI 2 (A913), ACL up | maintainer / Buds | BF-9 (differently: Left out of the case, not of an ear) | — | `CASE-004` | film; A913; E1 168–172 | | 06:15:19.73–06:15:19.86 | automatic re-open (AFTER_LOSS) → announcement **19** (A961) — only the Left out | app / Buds | BF-9 | — | `CASE-004`, `PAIR-003` | A945–A1038; E1 173–217 | | 06:15:22 → 06:15:25.10 | Left back into the case → ACL dropped `0x13` (A1044); "Automatic re-open skipped: the session was lost 5296 ms after an automatic re-open" (ADR-044 chain guard). Observation "did the case sound" — not recordable (no audio, nothing said) | maintainer / Buds / app | BF-9 | — | `CASE-001` (observation: none) | A1044; E1 221–228 | | 06:15:36–06:15:41.52 | Left out again → Buds open the ACL (A1049, no phone page) and the RFCOMM multiplexer (A1266 Buds `SABM` DLCI 0); LINK_BACK re-open (first attempt fails, A1252 collision; second A1383 DLCI **3**) → ch 19 (A1396); Buds-opened DLCI 0x08 = HFP (A1272, `AT+BRSF` A1293) | Buds / app | — (not in plan) | — | `CASE-004` | A1049–A1480; E1 229–275 | diff --git a/captures/CAP-070-2026-10-07_06-10-41_06-40-59-Group_BF/CAP-070-FINDINGS.md b/captures/CAP-070-2026-10-07_06-10-41_06-40-59-Group_BF/CAP-070-FINDINGS.md index 83c84903..23125ba0 100644 --- a/captures/CAP-070-2026-10-07_06-10-41_06-40-59-Group_BF/CAP-070-FINDINGS.md +++ b/captures/CAP-070-2026-10-07_06-10-41_06-40-59-Group_BF/CAP-070-FINDINGS.md @@ -16,7 +16,7 @@ announcements, 66 entries; Info tab on film) · **Phone:** Pixel 9a, GrapheneOS, Play** · **App under test:** OpenControl for Pixel Buds Pro 2 **1.1.0, build `0323849`** (release-signed, versionCode 10100) · **HCI logs:** Z `CAP-070-btsnoop_hci1.log.last` (4,664 packets, 05:44:02.521–06:10:46.252, the unfilmed rehearsal), A `CAP-070-btsnoop_hci2.log.last` (5,285, 06:10:52.690–06:37:28.403), B `CAP-070-btsnoop_hci2.log` (1,430, 06:39:36.576–06:42:06.705); `CAP-070-btsnoop_hci1.log` = the first 222,764 bytes of A, not -counted · **Buds:** `04:00:6e:…:07`, classic handle `0x000b` in all 19 ACLs; no LE link to the Buds. +counted · **Buds:** `04:00:6e:…:07`, classic handle `0x000b` in all 15 ACLs (Z 6, A 7, B 2); no LE link to the Buds. Commands used throughout (rule 4a; all on handle `0x000b`, shown matching the Connection Complete events Z161, A159, B159 — `tshark -r -Y "bthci_evt.code==0x03 || bthci_evt.code==0x05 || bthci_cmd.opcode==0x0405" -T fields -e frame.number -e frame.time -e bthci_evt.bd_addr -e bthci_cmd.bd_addr -e @@ -72,7 +72,7 @@ are in the session scratchpad (`ai-sessions/0075` RESULT). | ACL | Start | End | Note | |---|---|---|---| -| Z1–Z8 | Z159 05:44:40 … Z3617 05:58:08 | Z4078 05:59:36 `0x13` | the rehearsal (8 MAESTRO sessions) | +| Z1–Z6 | Z159 05:44:40 … Z3617 05:58:08 | Z4078 05:59:36 `0x13` | the rehearsal (6 ACLs — Connection Complete Z161, Z1086, Z1598, Z2086, Z2576, Z3619 — with 8 MAESTRO sessions) *(corrected 2026-10-15, `ai-sessions/0094`)* | | A1 | A157 phone page 06:11:26.924 (Connect) → A159 | A1044 06:15:25.096 `0x13` | Left back into the case | | A2 | **A1049 06:15:41.516, Buds-initiated** (no `Create Connection`) | A1526 06:15:58.630 `0x13` | the Buds opened the multiplexer (A1266) and HFP on DLCI 0x08 (A1272, `AT+BRSF` A1293) | | A3 | A1529 page 06:16:11.964 (pull) → A1531 | A2022 06:16:52.709 `0x13` | | @@ -227,7 +227,7 @@ per-bud field 2 (2 = charging): - 🟢 Every loss was classified and recovered: Buds-side closes with the ACL up re-opened after 1.5 s (#3, #9); one loss within 10 s of an automatic re-open was not re-opened (#4, ADR-044's chain guard, `SessionReopener.kt:101–104`, `CHAIN_GUARD_MS = 10_000L` `:137`) — the next Buds ACL re-opened it; every ACL loss came back by LINK_BACK when the Buds reconnected, or by a pull/Connect while both stayed docked. No session was lost without a message. -- 🟢 **The first LINK_BACK attempt fails on a multiplexer collision** (#4, #6, #8, #11 is clean): E3 232/371/532 "RFCOMM channel 0x02 connect failed after 84–106 +- 🟢 **The first LINK_BACK attempt fails on a multiplexer collision** (3 times: #4, #6, #8; #11 is clean): E3 232/371/532 "RFCOMM channel 0x02 connect failed after 84–106 ms (attempt 1/3)", and on the wire the phone's `SABM` DLCI 0 crossed the Buds' own (A1252/A1266) or was repeated (A2241/A2274, A3351/A3385); attempt 2 succeeded 400 ms later each time (`RfcommBudsTransport` retry, `DEFAULT_RETRY_DELAY_MS = 400L`, `RfcommBudsTransport.kt:386`). As designed (`ai-sessions/0039`). - 🟢 **Bluetooth off/on (BF-24/25):** final cause line, no "provisional"; the new process re-opened by itself on Bluetooth on (L2 478, trigger `LINK_BACK`) and read @@ -257,7 +257,7 @@ per-bud field 2 (2 = charging): | 1 | The screen-reader text of "—" is unverified on a phone (no dump) | **open test item**, not a defect shown | §5 | | 2 | C12 (a tap during a re-open) not exercised | open test item | §5 | | 3 | Case-sound and Volume-EQ audibility not observable (no audio track) | open test item (observations, not app claims) | §0 | -| 4 | The first LINK_BACK attempt collides with the Buds' own multiplexer `SABM` (4 times), the retry succeeds | observation — as designed | §6 | +| 4 | The first LINK_BACK attempt collides with the Buds' own multiplexer `SABM` (3 times), the retry succeeds | observation — as designed | §6 | | 5 | One loss within 10 s of a re-open is not re-opened; the Buds' next ACL re-opened it | minor — ADR-044 as designed (already a 1.0.x known behaviour) | §6 #4 | | 6 | Two `SIGQUIT` stack dumps, cause unknown | 🔴 not an app defect shown (the process continued) | §0 | | 7 | P1: 1.1.0 was a first install in user 10, not the planned update over 1.0.1 | test-procedure gap; the update path stays untested on hardware | §0 | diff --git a/captures/CAP-071-2026-10-08_17-51-29_18-27-28-Group_BG/CAP-071-EVENT-NOTES.md b/captures/CAP-071-2026-10-08_17-51-29_18-27-28-Group_BG/CAP-071-EVENT-NOTES.md index afc97c1b..20907400 100644 --- a/captures/CAP-071-2026-10-08_17-51-29_18-27-28-Group_BG/CAP-071-EVENT-NOTES.md +++ b/captures/CAP-071-2026-10-08_17-51-29_18-27-28-Group_BG/CAP-071-EVENT-NOTES.md @@ -250,7 +250,7 @@ Times: phone clock (log times exact; film-only events = overlay + 1.25 s, rounde | Step | Result | Where | |---|---|---| -| P0 | done (outputs below, exit statuses recorded) | before the film | +| P0 | done (outputs above, "The maintainer's P0/P1 outputs"; exit statuses recorded) | before the film | | P1 | done differently: 1.1.0 newly installed in user 10 at 17:40 and used off film; Dark mode On on film; Debug mode on not shown in 1.1.0 (on in 1.1.1 without a tap); Info 1.1.0 on film; `dumpsys` before/after without `lastUpdateTime`; installed from `dist/1.1.1/` | 17:40–17:54 | | P2 | done | 17:51:33/39 | | P3 | done differently: sound yes; the head in view once (18:06:57) | — | diff --git a/captures/CAP-071-2026-10-08_17-51-29_18-27-28-Group_BG/CAP-071-FINDINGS.md b/captures/CAP-071-2026-10-08_17-51-29_18-27-28-Group_BG/CAP-071-FINDINGS.md index a848755e..5980f35c 100644 --- a/captures/CAP-071-2026-10-08_17-51-29_18-27-28-Group_BG/CAP-071-FINDINGS.md +++ b/captures/CAP-071-2026-10-08_17-51-29_18-27-28-Group_BG/CAP-071-FINDINGS.md @@ -14,7 +14,7 @@ announcements, 33 entries; Info tab on film) · **Phone:** Pixel 9a, GrapheneOS, Play** · **App under test:** OpenControl for Pixel Buds Pro 2 **1.1.1, build `86a6fb3`** (release-signed, versionCode 10101), installed over **1.1.0, build `0323849`** · **HCI logs:** Z `CAP-071-btsnoop_hci1.log.last` (854 packets, 17:37:32.758–17:51:34.615, before the film), A `CAP-071-btsnoop_hci2.log.last` (6,996, 17:51:39.365–18:26:23.881), B `CAP-071-btsnoop_hci2.log` (760, 18:26:32.311–18:28:44.260); `CAP-071-btsnoop_hci1.log` = the first 319,218 bytes of A, not counted · -**Buds:** `04:00:6e:…:07`, classic handle `0x000b` in all 7 ACLs; no LE link to the Buds. +**Buds:** `04:00:6e:…:07`, classic handle `0x000b` in all 6 ACLs; no LE link to the Buds. Commands used throughout (rule 4a; all on handle `0x000b`, shown matching the Connection Complete events Z187, A171, A5025, A5626, A6419, B159 — `tshark -r -Y "bthci_evt.code==0x03 || bthci_evt.code==0x05 || bthci_cmd.opcode==0x0405 || bthci_evt.code==0x04" -T fields -e frame.number -e frame.time -e bthci_evt.bd_addr diff --git a/captures/CAP-072-2026-10-09_17-27-26_18-23-39-Group_BH/CAP-072-EVENT-NOTES.md b/captures/CAP-072-2026-10-09_17-27-26_18-23-39-Group_BH/CAP-072-EVENT-NOTES.md index d2045410..c41b6aa1 100644 --- a/captures/CAP-072-2026-10-09_17-27-26_18-23-39-Group_BH/CAP-072-EVENT-NOTES.md +++ b/captures/CAP-072-2026-10-09_17-27-26_18-23-39-Group_BH/CAP-072-EVENT-NOTES.md @@ -116,7 +116,7 @@ Columns: phone time · what happened (actor) · step / V / test-plan / Test-ID | Phone time | Event | Step · V · IDs | Evidence | |---|---|---|---| -| 08:43–15:04 (before) | the app (uid 10353) runs 7 sessions with the thirteen requests incl. `GetHardwareInfo`; 13:26:24–29 writes `28:0`, `27:0`, `28:1`, `27:1` (the release APK tried before the run) | not in the plan (P9) | Z 11373 … 26365, Z 21478–21569 | +| 08:43–15:04 (before) | the app (uid 10353) runs 7 sessions with the fifteen requests (13 `ReadSetting` incl. 4:16, `SubscribeRuntimeInfo`, `GetHardwareInfo`); 13:26:24–29 writes `28:0`, `27:0`, `28:1`, `27:1` (the release APK tried before the run) | not in the plan (P9) | Z 11373 … 26365, Z 21478–21569 | | 15:40:24 | user switch 0 → 10; Bluetooth restarts; Z ends | — | system log `am_switch_user` | | 17:19:38 | a Connect of the uid-10353 app (12 reads, subscription, `GetHardwareInfo`) | P9 deviation | A 5589–5667 | | 17:24:06 | `cmd package uninstall` of the app (all users); PID 6994 killed | P1 done differently | X 817–826 | @@ -143,19 +143,19 @@ Columns: phone time · what happened (actor) · step / V / test-plan / Test-ID | 17:37:26 | pull → "Probably worn (checked 17:37:26)" | BH-6 | A 9103; S s600–601 | | 17:37:3x–17:38:0x | Settings and Info (serials again at 17:37:16 on ch 21) | BH-6/7 · V16 | S s607–s638 | | 17:38:19–23 | **Right** into the case (from the ear); Buds `DISC` 17:38:19.078 → re-open ch **19** (only the Left out); "Probably worn (17:38:22)" (Left worn); a short tone on the audio at the seating | not in the plan; `INEAR-006`, `CASE-001` | A 9334–9418; S s653–657; C | -| 17:38:29 | **Left** into the case → both in; ACL `0x13` 17:38:29.17 ("Android no longer shows the Buds connected …", S s663) | BH-20 (early) · `CASE-004` | A 9492; E4 322–329 | -| 17:38:49–17:39:01 | Left out (17:38:49), ACL back by the Buds (A 9506), re-open ch 19 "**Not worn (checked 17:38:50)**" (`00`, Left in the hand); Right out 17:39:01; Buds `DISC` 17:39:05 → ch 21 "Probably worn (17:39:08)" | BH-20 · `CASE-005`, `INEAR-006` | A 9506–10132; E4 330–443; S s683–702 | +| 17:38:29 | **Left** into the case → both in; ACL `0x13` 17:38:29.17 ("Android no longer shows the Buds connected …", S s663) | BH-20 (early) · `CASE-006` | A 9492; E4 322–329 | +| 17:38:49–17:39:01 | Left out (17:38:49), ACL back by the Buds (A 9506), re-open ch 19 "**Not worn (checked 17:38:50)**" (`00`, Left in the hand); Right out 17:39:01; Buds `DISC` 17:39:05 → ch 21 "Probably worn (17:39:08)" | BH-20 · `CASE-004`, `CASE-005`, `INEAR-006` | A 9506–10132; E4 330–443; S s683–702 | | 17:39:31, 17:40:31 | pulls → "Probably worn" (both worn, no music) | BH-6/7 | A 10266, 10531; S s723–785 | | 17:40:49–17:41:09 | both buds laid **on the table** (C); Buds `DISC` 17:41:06.482 → re-open ch 19, `DISC` again 17:41:09.690 (1.25 s later: re-open skipped, ADR-044 guard) | BH-8 · `INEAR-004` | A 10627–10728; E4 473–515 | | 17:41:28 | Connect tap → "**Not worn (checked 17:41:30)**" (`00`) | BH-8 · V7 · `INEAR-006` | A 10777–10842; S s842–844 | | 17:41:57–17:43:26 | ANC tab: **seven mode taps refused**, "The Buds don't allow changing noise control right now …"; each claim `Get` → `Notify` `00` → no `Set`; the (i) "checked HH:MM:SS" follows each | BH-8 (×7) · V7 · `ANC-004` | E4 565–631; A 10980 … 11705; S s853–s968 | | 17:44:03–13 | both buds from the table into the ears (C: hair at 17:44:13) | BH-10 | C | -| 17:44:32 | ANC tap **Noise cancellation** → `Get` (TRN, `e8`) → **`Set` 08** → ACK → `Notify` NC; "Probably worn (17:44:32)" | BH-10 · V8 · `ANC-001` | A 11915–11949; S s1025–1026 | +| 17:44:32 | ANC tap **Noise cancellation** → `Get` (TRN, `e8`) → **`Set` 08** → ACK → `Notify` NC; "Probably worn (17:44:32)" | BH-10 · V8 · `ANC-002` | A 11915–11949; S s1025–1026 | | 17:44:34–17:48:46 | **no Message Stream claim open** (DISC A 11947 … SABM A 12490); ANC tab on screen, (i) opened 4× (always "ACTIVE (updated 17:44:32)", no "Changed by the Buds"); AVRCP volume changes from the Buds 17:45:27, 17:45:55, 17:46:11, 17:46:30 and a PLAY 17:46:22 (gestures on a bud); speech 17:45:28–17:46:31, 17:48:01–11 — **the maintainer's press-and-holds** (the maintainer's statement; not visible on either film) | BH-11 done differently · V10 · `ANC-001`…`004` | A 11947, 12490, 12027–12132; S s1082–s1229 | -| 17:48:46 | ANC tap **Off** → `Get` (NC) → `Set` 20 → ACK; (i) "OFF (updated 17:48:46)" | BH-12 · V11 | A 12490–12524; S s1280–1284 | +| 17:48:46 | ANC tap **Off** → `Get` (NC) → `Set` 20 → ACK; (i) "OFF (updated 17:48:46)" | BH-12 · V11 · `ANC-001` | A 12490–12524; S s1280–1284 | | 17:49:12 | pull on the ANC tab → `Get` → `Notify` OFF; (i) "OFF (updated 17:49:12)" | BH-12 · V11 | A 12666; S s1305–1313 | | 17:49:2x–17:50:0x | Quick Settings: edit tiles, **the ANC tile added** to user 10 (not in QS before) | not in the plan · J-tile | S s1326–s1352 | -| 17:50:03 / 17:50:23 | **tile** taps: OFF → Active (`Set` 08), Active → Transparent (`Set` 80); the app's (i) "updated 17:50:04 / 17:50:24", no line; notification "Connected — ANC: …" follows | BH-12 · V11 · `ANC-001`…`003` | E4 684–706; A 12845–13014; S s1354–1385 | +| 17:50:03 / 17:50:23 | **tile** taps: OFF → Active (`Set` 08), Active → Transparent (`Set` 80); the app's (i) "updated 17:50:04 / 17:50:24", no line; notification "Connected — ANC: …" follows | BH-12 · V11 · `ANC-002`, `ANC-004` | E4 684–706; A 12845–13014; S s1354–1385 | | 17:50:55–17:51:03 | **Right** then **Left** into the case → ACL `0x13` 17:51:03.25 (both in); short tone at the Right's seating | BH-13 (first half) · `CASE-001` | A 13098; C 17:50:57–17:51:03 | | 17:51:08–10 | Android's link back (the Buds' ACL A 13104) → automatic re-open ch 21: `Get` → `Notify` OFF (`00`) → **unprovoked `Notify` TRN (`e8`) 63 ms later** while both buds are taken out of the case → the (i) shows **"Changed by the Buds at 17:51:10 (a press-and-hold on a bud, or the Buds' own change)."** | BH-11's line (seen once) · V10 | A 13484–13519; S s1424–1425; C 17:51:07–17 | | 17:51:13–19 | both buds laid **on the table** (C) | BH-13 · `INEAR-006` | C 17:51:13–19 | @@ -190,33 +190,33 @@ Columns: phone time · what happened (actor) · step / V / test-plan / Test-ID | 18:05:10 / 18:05:17 | **Other alerts OFF / ON** again → OK; (i) "Earbuds replaced: changed 18:01:53 / Other alerts: changed 18:05:10" | BH-14 (repeated) | A 18708–18763; S s2261–2271 | | 18:05:4x | music restarted in the browser | — | S s2294–2299 | | 18:06:08 / 18:06:20 | Controls: **Conversation detection OFF / ON** → `4:{22:0}` / `{22:1}` → OK; AVRCP PAUSE/PLAY 18:06:22–47 | BH-15 · V14 · `CONV-001` | A 18921–18947; S s2321–2334 | -| 18:06:35 | ANC tap **Noise cancellation** (from TRN) → `Set` 08 → ACK (during music) | BH-10/16 · `ANC-001` | A 19008–19043; S s2348–2349 | +| 18:06:35 | ANC tap **Noise cancellation** (from TRN) → `Set` 08 → ACK (during music) | BH-10/16 · `ANC-002` | A 19008–19043; S s2348–2349 | | 18:07:04 | Sound: preset **Balanced** → `4:{16:[-3.5,0.5,1.0,-1.0,2.5]}` → OK | BH-16 · V15 · `EQP-001` | A 19113/19115; S s2378 | | 18:07:14 / 18:07:21 | Upper treble dragged: writes `…4.66` and `…6.0` → OK; (i) "EQ updated: 18:07:21" | BH-16 · `EQS-001` | A 19133–19181; S s2387–2401 | | 18:07:49–18:08:57 | **balance**: 30 writes (`17:11, 0, 18, 0, 0, 15, 0, 0, 15, 0, 0, 0, 25, 105, 15, 0, 9, 0, 18, 18, 20, 0, 26, 20, 9, 24, 21, 0, 13, 0` — zigzag), labels on screen incl. "Right 6", "Left 9", "Right 53", "Right 8"; ends **Centre** (`17:0`) | BH-16 done differently · `AUDIO-003` | A 19287–19510; E4 1672–1765; S s2416–s2483 | | 18:09:03 / 18:09:06 | **Mono ON / OFF** → OK | BH-16 · `AUDIO-001` | A 19520, 19529 | | — | **Volume EQ** off/on and **Read EQ again** (H5) — not done | BH-16 partly skipped | no `4:{15:…}` write, no `ReadSetting 4:16` between connects | -| 18:09:24 / 18:09:29 | **Use touch controls OFF / ON** → OK | BH-17 · `HOLD-005` | A 19573, 19583 | -| 18:09:33 | mode list: **Adaptive ticked** → `4:{12:{1:1 2:0 3:1 4:1}}` → OK | BH-17 done differently | A 19595 | -| 18:09:45–18:10:01 | press and hold: **Left** Digital assistant → Noise control, **Right** Digital assistant → Noise control → OK ×4 | BH-17 (both buds) · `HOLD-005` | A 19622–19659 | -| 18:10:11 / 18:10:13 | mode list: Off box tapped twice (`4:{12:…}` ×2) → ends `{1:1 2:0 3:1 4:1}` (Off unticked, Adaptive ticked) | BH-17 | A 19682, 19690 | +| 18:09:24 / 18:09:29 | **Use touch controls OFF / ON** → OK | BH-17 · `TOUCH-001` | A 19573, 19583 | +| 18:09:33 | mode list: **Adaptive ticked** → `4:{12:{1:1 2:0 3:1 4:1}}` → OK | BH-17 done differently · `HOLD-005` | A 19595 | +| 18:09:45–18:10:01 | press and hold: **Left** Digital assistant → Noise control, **Right** Digital assistant → Noise control → OK ×4 | BH-17 (both buds) · `HOLD-001`–`HOLD-004` | A 19622–19659 | +| 18:10:11 / 18:10:13 | mode list: Off box tapped twice (`4:{12:…}` ×2) → ends `{1:1 2:0 3:1 4:1}` (Off unticked, Adaptive ticked) | BH-17 · `HOLD-005` | A 19682, 19690 | | 18:10:27 / 18:10:32 | **head gestures OFF / ON** → `29:1` / `29:2` → OK | BH-17 · `HEAD-001` | A 19716, 19726 | | 18:10:52 / 18:10:58 | **Multipoint OFF / ON** → OK | BH-17 · `MULTI-001` | A 19764, 19779 | | 18:11:18 / 18:11:19 | **Disconnect, Connect** → ch 19; every value read back; Info: build `ec6d163`, firmware 18:11:20, **serials 18:11:20 identical** | BH-18 · V16, V17 · `PAIR-003`, `FW-003` | A 19816–19942; S s2631–s2659 | | 18:12:04–11 | both buds from the ears onto the table; Buds `DISC` 18:12:04.334 → ch 21 (Info "Serial numbers: Not read from the Buds yet" for < 1 s, then 18:12:07) → `DISC` 18:12:09.737 (skipped) | BH-19 setup · V16 | A 20019–20160; S s2680–2691 | | 18:12:22 | re-open (a tap) ch 19, `Notify` `00` | BH-19 | A 20217–20348 | | 18:12:26 / 18:12:45 / 18:12:46 / 18:13:09 | **Ring Left** → ACK; **Stop** → ACK; **Ring Right** → ACK; **Stop** → ACK; ring on the audio both times | BH-19 · `FIND-001`, `FIND-002` | A 20379–20683; S s2699–2744 | -| 18:13:13–17 | both buds from the table into the case → ACL `0x13` 18:13:15.94 | BH-20 · C8 · `CASE-004` | A 20717; C | -| 18:13:31–37 | both taken out onto the table; the Buds' ACL back 18:13:34.9 → re-open ch 21 "**Not worn (checked 18:13:37)**" (`00`) → `DISC` 18:13:38.134 (skipped, 1.95 s) | BH-20 · C9 · `CASE-005` | A 20735–21181; S s2769–2772 | +| 18:13:13–17 | both buds from the table into the case → ACL `0x13` 18:13:15.94 | BH-20 · C8 · `CASE-006` | A 20717; C | +| 18:13:31–37 | both taken out onto the table; the Buds' ACL back 18:13:34.9 → re-open ch 21 "**Not worn (checked 18:13:37)**" (`00`) → `DISC` 18:13:38.134 (skipped, 1.95 s) | BH-20 · C9 · `CASE-004`, `CASE-005` | A 20735–21181; S s2769–2772 | | 18:13:44–18:15:20 | **Home** for 1 min 36 s (link observer stopped/started) → re-open on return ch 19 "Not worn (18:15:21)" | BH-21 · J4, K1, K2 | E4 2067–2119; S s2777–s2875 | | 18:15:3x | **Export 1** (2,121 lines) → E1 | BH-22 export | E4 2125; S s2886–2891 | | 18:16:0x–18:18:18 | bugreport 1 (pulls `…hci1.log`); SIGQUIT to 12137 at 18:16:33 | P6 note | X; L | | 18:18:32 | **Bluetooth off** (Quick Settings): "Bluetooth adapter: ON -> TURNING_OFF", "Session loss cause: Bluetooth was switched off on this phone" | BH-22 · V18 · `PAIR-003` | E4 2135–2141; A 21841–21859; S s3061–3080 | -| 18:18:45 | **Bluetooth on** → the Buds' link 18:18:47.3 → automatic re-open ch 19, thirteen requests, serials 18:18:48 | BH-22 | E4 2142–2197; B 159–546 | +| 18:18:45 | **Bluetooth on** → the Buds' link 18:18:47.3 → automatic re-open ch 19, fifteen requests, serials 18:18:48 | BH-22 | E4 2142–2197; B 159–546 | | 18:19:01 | **Export 2** (2,197 lines) → E2 | BH-end1 export (early) | E4 2201; S s3082–3098 | | 18:19:35–41 | both buds from the table to the ears → Buds `DISC` 18:19:37.17 → ch 21: `Get` → `Notify` TRN, **unprovoked `Notify` NC 18:19:41.644**, Buds `DISC` 3 ms later (skipped) | not in the plan | B 703–829 | | 18:20:03 | Connect tap → ch 19 | — | B 864 | -| 18:20:09 / 18:20:13 | **In-ear detection OFF / ON** (Controls; buds worn; no pull, the Worn line not read) | BH-9 done differently · `INEAR-004` | B 980, 983; S s3161–3169 | +| 18:20:09 / 18:20:13 | **In-ear detection OFF / ON** (Controls; buds worn; no pull, the Worn line not read) | BH-9 done differently · `INEAR-001` | B 980, 983; S s3161–3169 | | 18:20:44–18:20:49 | phone **rotated** (landscape) on the Debug tab and back (two Activity re-creations) | S12 part · K4 | E4 2322–2331; S s3198 | | 18:21:10 | **Export 3** (2,335 lines) → E3 (portrait, no rotation during the save dialog) | BH-24 done differently · S12 | E4 2340; S s3203–3226 | | 18:21:31–18:23:36 | bugreport 2 (pulls B); SIGQUIT 18:21:56 | P6 note | L 648 | @@ -237,7 +237,7 @@ Columns: phone time · what happened (actor) · step / V / test-plan / Test-ID | P7 ★ | partly | music from 17:54 (internet radio); **Do Not Disturb not on** (the call came through; Quick Settings "Modi" tile inactive on S 17:49) | | P8 ★ | done | lid open, Buds connected before 1.2.0 started | | P9 ★ | not kept | the release APK used today before the run (Z, A 5589) and 1.1.1 opened at 17:25 before the film | -| BH-1 | done differently | session by itself, thirteen requests; Controls not opened within the first second (S6 caught at 17:31:28 instead) | +| BH-1 | done differently | session by itself, fifteen requests; Controls not opened within the first second (S6 caught at 17:31:28 instead) | | BH-2 | done | build `ec6d163`, serials, Case sounds card and its (i), Debug on, Dark On | | BH-3 | done | "Both buds in the case" | | BH-4 | done differently | System → light at once, then Off, then **On** (not left on System) | @@ -269,11 +269,12 @@ ANC tile added to QS; the internet radio; the lid closed/opened at 18:02:09–21 pair (18:05); the unprovoked `Notify` at 18:19:41 when the buds were put in. **Test-IDs (`AGENTS.md` §13.7):** `PAIR-003` (17:29:26, 18:11:19, 17:38:49, 18:13:34, 18:18:47), `BATT-004` (17:29:27), `FW-003` (17:31:5x, 18:11:2x), -`INEAR-006` (25 readings, FINDINGS §4), `INEAR-005` (17:35:55, 17:38:21, 17:38:50, 17:56:07 … 18:00:17), `INEAR-004` (17:41:06–17:43:26, 18:20:09/13), -`ANC-001` (17:44:32, 18:06:35), `ANC-002`/`ANC-003` (17:48:46, 17:50:03/23 via tap and tile), `ANC-004` (17:41:57–17:43:26), `CASE-001` (17:55:50–18:01:53), +`INEAR-006` (25 readings, FINDINGS §4), `INEAR-005` (17:35:55, 17:38:21, 17:38:50, 17:56:07 … 18:00:17), `INEAR-004` (17:41:06–17:43:26), `INEAR-001` (18:20:09/13), +`ANC-001` (17:48:46), `ANC-002` (17:44:32, 17:50:03 tile, 18:06:35), `ANC-003` (**not exercised** — no Adaptive `Set`), `ANC-004` (17:41:57–17:43:26 refused, 17:50:23 tile), `CASE-001` (17:55:50–18:01:53), `CASE-002` (18:02:03/17, 18:05:10/17), `CONV-001` (18:06:08/20), `EQP-001` (18:07:04), `EQS-001` (18:07:14/21), `AUDIO-001` (18:09:03/06), `AUDIO-002` -(**expected but not observed** — no Volume EQ write), `AUDIO-003` (18:07:49–18:08:57), `HOLD-005` (18:09:24–18:10:14), `HEAD-001` (18:10:27/32), `MULTI-001` -(18:10:52/58), `CASE-004` (17:38:29, 17:51:03, 17:59:23, 18:13:15), `CASE-005` (17:38:49, 17:51:08, 17:59:29, 18:13:34), `FIND-001`/`FIND-002` (18:12:26–18:13:10). +(**expected but not observed** — no Volume EQ write), `AUDIO-003` (18:07:49–18:08:57), `TOUCH-001` (18:09:24/29), `HOLD-001`–`HOLD-004` (18:09:45–18:10:01), `HOLD-005` (18:09:33, 18:10:11/13), `HEAD-001` (18:10:27/32), `MULTI-001` +(18:10:52/58), `CASE-004`/`CASE-005` (bud removals: Left 17:38:49 / Right 17:39:01, both 17:51:08, Right 17:59:23 / Left 17:59:29, both 18:13:31–34), `CASE-006` (into the +case, lid open: 17:38:29, 17:51:03, 17:59:23, 18:13:15), `FIND-001`/`FIND-002` (18:12:26–18:13:10). APP_TESTPLAN V1–V18 are mapped in FINDINGS §10; V19 (C12, S12, T11) is partly done (S12 differently); T11 moves to the rebuilt 1.2.0's run. ## Analysis checklist diff --git a/captures/CAP-072-2026-10-09_17-27-26_18-23-39-Group_BH/CAP-072-FINDINGS.md b/captures/CAP-072-2026-10-09_17-27-26_18-23-39-Group_BH/CAP-072-FINDINGS.md index 25f677e7..bb73d2de 100644 --- a/captures/CAP-072-2026-10-09_17-27-26_18-23-39-Group_BH/CAP-072-FINDINGS.md +++ b/captures/CAP-072-2026-10-09_17-27-26_18-23-39-Group_BH/CAP-072-FINDINGS.md @@ -105,7 +105,7 @@ positive control: `python3 -I scripts/pwrpc_decode.py ` decodes `CA (Owner user, the Google app) — not the run. - 🟢 **ANC `Notify` Settable** (the third value byte, `01 e8 [S] mode`): `00` in 18 claims (both buds in the case, in the hand or on the table — §4; at 17:51:10 followed by `e8` 63 ms later), `e8` in the other 31 claims with a `Notify` (43 `e8` messages). -## 2. The Connect read — thirteen requests (BH-1, BH-18, BH-20, BH-22; V1, V2, V16, V17; ADR-058) +## 2. The Connect read — fifteen requests (BH-1, BH-18, BH-20, BH-22; V1, V2, V16, V17; ADR-058) 🟢 **31 sessions** (A 28 from 17:29:26, B 3; plus the release APK's at 17:19 and 1.1.1's at 17:25): the Buds' unsolicited `GetSoftwareInfo` → `ReadSetting 4:16` → `ReadSetting` **2, 4, 7, 11, 12, 15, 17, 19, 22, 27, 28, 29** → one `SubscribeRuntimeInfo` → **one `GetHardwareInfo`**, in that order, in **30 of 30 complete @@ -281,7 +281,7 @@ by one reviewer): above). A dropped session cannot undo an ACKed write. **Refuted as stated**; the Buds' session drop is real but harmless to the setting. - (b) The write not acknowledged / sent on a closing channel: 🟢 refuted (all ACKed, 6–35 s before). - (c) Field 28 does not mean "a chime at every return": 🟡 **supported** — with the switch on, a tone was found at 2 of 2 clear Right entries (E1, E9) and faint at - E3, E11, but at none of the five Left entries (E4, E7, E8, E10 and E2 faint); with it off, at none of 7 except a faint candidate at E5. 🟡 The chime may depend on + E3, E11, but clearly at none of the five Left entries (E2, E4, E7, E8, E10; faint at E2); with it off, at none of 7 except a faint candidate at E5. 🟡 The chime may depend on which bud is replaced, on the lid, on both buds, or play from the bud rather than the case. - (d) Another sound (field 27, a charging or low-battery sound): 🔴 not distinguishable here; "Other alerts" was on throughout the entries. - (e) A stale switch on screen: 🟢 refuted — the switch on S matched every read (e.g. S s1722 "off" after the 17:56:08 re-open read `28:0`). @@ -299,15 +299,16 @@ by one reviewer): (cause "the Buds closed the channel"), 4 with the ACL dropped by the Buds (17:38:29, 17:51:03, 17:59:23, 18:13:15 — both buds into the case; cause "Android's link to the Buds went down"), 1 while the app was hidden (17:55:15), 1 Bluetooth off (final at once). Every automatic re-open after a loss came **1.498–1.503 s after "Ready -> Disconnected"** (AFTER_LOSS, 15 times, E4; `SessionReopener.kt` `LOSS_REOPEN_DELAY_MS = 1_500L`) or when Android's link came back (LINK_BACK, 8 times, the first attempt failing on -the multiplexer 4 times and the 400-ms retry succeeding — as `CAP-070`/`071`); the 10-s chain guard skipped 8 re-opens (losses 1.25–9.9 s after a re-open), after -which a Connect tap followed (17:41:28, 17:51:40, 18:00:16, 18:01:47, 18:12:22, 18:20:03). 🟢 No session stayed lost without the documented reason. +the multiplexer 4 times and the 400-ms retry succeeding — as `CAP-070`/`071`); the 10-s chain guard skipped 8 re-opens (losses 1.25–9.9 s after a re-open); six were +followed by a Connect tap (17:41:28, 17:51:40, 18:00:16, 18:01:47, 18:12:22, 18:20:03), two by Android's link coming back (LINK_BACK 17:38:49.6 after the skip of +17:38:29, 18:15:20.4 after the skip of 18:13:38; E4). 🟢 No session stayed lost without the documented reason. - 🟢 **BH-20 (both into the case and out):** the ACL dropped (`0x13`), "Paired — not connected to this phone / Open the case or put the Buds in your ears …" (S s665), the Buds' own ACL came back when the buds left the case (A 9506, 13104, 16151, 20735) and the app re-opened by itself 0.27–0.52 s later — **C9 "ready again by itself" ×4**. - 🟢 **BH-21:** Home 18:13:44–18:15:20 (1 min 36 s), the session was lost at 18:13:38 just before; on return LINK_BACK re-opened it (E4 2069–2119). - 🟢 **BH-22:** export E1 at 18:15:3x before the off; "Bluetooth adapter: ON -> TURNING_OFF" 18:18:32.433, cause "Bluetooth was switched off on this phone" (final); - on 18:18:45.967 → the Buds' link 18:18:47.3 → automatic re-open, thirteen requests, serials 18:18:48 (E4 2135–2197). "Bluetooth is disabled." is not on S (Android's + on 18:18:45.967 → the Buds' link 18:18:47.3 → automatic re-open, fifteen requests, serials 18:18:48 (E4 2135–2197). "Bluetooth is disabled." is not on S (Android's Bluetooth dialog covered the app). Info's "Not read …" while off: not looked at. - **The exports:** E1 (18:15:3x, 2,121 lines) before the off; E2 (18:19:01) after the on; E3 (18:21:10) after the rotation; E4 (18:23:3x). 🟢 **No serial and no MAC as text** in any export or in L: `grep -c ""` for each of the three full strings and `grep -c -i "04:00:6e:cf:6e:07"` → 0 each (exit 1); positive control diff --git a/captures/CAP-073-2026-10-10_08-23-35_08-33-24-Group_BI/CAP-073-EVENT-NOTES.md b/captures/CAP-073-2026-10-10_08-23-35_08-33-24-Group_BI/CAP-073-EVENT-NOTES.md index 2cd90a97..721e0002 100644 --- a/captures/CAP-073-2026-10-10_08-23-35_08-33-24-Group_BI/CAP-073-EVENT-NOTES.md +++ b/captures/CAP-073-2026-10-10_08-23-35_08-33-24-Group_BI/CAP-073-EVENT-NOTES.md @@ -72,8 +72,8 @@ The two `firstInstallTime` lines are per user. A full `dumpsys package` taken by - **B and A are consecutive:** A ends 2026-10-10 08:22:48.796, B starts 08:22:51.771 (`capinfos -a -e`); the user switch 0 → 10 is at 08:22:46.7–47.9 (system log `uc_switch_user` / `am_switch_user`). No Buds ACL spans the gap: A's last Buds ACL ended at 07:43:06 (A 26567, `0x13`), B's first starts at 08:23:01.706 (B 159). -- **A is before the run:** OpenControl sessions on 2026-10-09 18:35, 19:29 and 2026-10-10 06:43–06:44, 07:07–07:08, 07:13, each with the thirteen requests and - `GetHardwareInfo` (a 1.2.0 build), plus EQ and Volume EQ writes (07:07:40–07:08:44, A 21952–22180) and Earbuds-replaced writes (06:43–06:44); the system log +- **A is before the run:** OpenControl sessions on 2026-10-09 18:35, 19:29 and 2026-10-10 06:43–06:44, 07:07–07:08, 07:13, each with the fifteen requests + (13 `ReadSetting` incl. 4:16, `SubscribeRuntimeInfo`, `GetHardwareInfo`; a 1.2.0 build), plus EQ and Volume EQ writes (07:07:40–07:08:44, A 21952–22180) and Earbuds-replaced writes (06:43–06:44); the system log shows the app resumed **in user 0** at 07:07:08 (`wm_set_resumed_activity: [0,…]`), process 30161 died 07:57:45. Play services (the Owner user) claimed the Message Stream 14 times in A. **Not the run;** it explains the Buds' starting state (EQ = −2, 0, 2, 3, 5). - **The skeleton's P9 ("no app use before the film"):** the app was opened in user 10 at 08:22:59 and connected at 08:23:01 — 34 s before the film starts. @@ -100,7 +100,7 @@ Columns: phone time · what happened (actor) · step / W / Test-ID · evidence. | 08:22:46.7–47.9 | user switch 0 → 10; the app's tile service bound in user 10 (process 15696, `ec6d163`) | — | X 2, X 4 | | 08:22:59.2 | the app opened (MainActivity resumed in user 10); "Android link: PENDING -> NOT_CONNECTED" | P9 deviation | X 32; system log 15696 lines | | 08:23:01.113 | `ConnectionState: Disconnected -> Connecting` with no "Automatic re-open" line (Android's link read NOT_CONNECTED, so ADR-044 could not fire) — 🟡 a Connect tap; the phone pages the Buds (Create Connection B 157, 17 ms later) | P9 deviation | system log 15696; B 157, 159 | -| 08:23:01.9–08:23:03.5 | session 1 (ch 21): announcement B 258, the thirteen requests, `GetHardwareInfo` B 333 → 336 (46 ms); snapshot claim `SABM` B 270 → `08 11` B 280 → `Notify` `e8 00 20` (B 291, OFF, no bud worn) → the phone's `DISC` B 450 at 08:23:03.530 (**1.513 s** after B 291) | `PAIR-003`, `BATT-004` (old build) | B 236–469 | +| 08:23:01.9–08:23:03.5 | session 1 (ch 21): announcement B 258, the thirteen `ReadSetting`s, `SubscribeRuntimeInfo`, `GetHardwareInfo` B 333 → 336 (46 ms); snapshot claim `SABM` B 270 → `08 11` B 280 → `Notify` `e8 00 20` (B 291, OFF, no bud worn) → the phone's `DISC` B 450 at 08:23:03.530 (**1.513 s** after B 291) | `PAIR-003`, `BATT-004` (old build) | B 236–469 | | 08:23:04.067 | the phone sets AVRCP absolute volume **0 %**; play status Stopped | — | B 575, 577 | | **08:23:35.3** | **screen recording starts** (t=0): Connection tab, dark, "App control: ready", Left 100 % ⚡, Case 100 %, Right 100 % ⚡, "Both buds in the case" | P4 · `INEAR-006` | S t=0 | | 08:23:44–08:24:01 | gear → Settings: Dark mode **On**, Case sounds both on; **Info: "App: 1.2.0, build ec6d163 (2026-10-09)"**, firmware ×3 `release_5.203` (08:23:01), control channel 21, serials (08:23:02) Case / Right bud / Left bud; Debug: **Debug mode on**, Unidentified frames (5); Settings; back to Connection | P1 (on film) · W1 (before) · `FW-003` | S t=9–26 | @@ -133,7 +133,7 @@ Columns: phone time · what happened (actor) · step / W / Test-ID · evidence. | 08:28:33–08:28:50 | **Sound → Balance:** release 1 → `4:{17:9}` B 3679 → OK B 3681, "Right 5"; release 2 → `4:{17:27}` B 3682 → OK, "Right 14"; drag "Right 6 — release to set", "Right 3 — …", "Right 4 — …", release 3 → **`4:{17:7}`** B 3685 → OK B 3687, **"Right 4"**; drag "Right 2 — …", "Left 6 — …", "Left 43 — …", "Left 83 — …", "Left 77 — …", "Left 9 — …", "Left 3 — …", release 4 → `4:{17:8}` B 3691 → OK B 3694 (08:28:49.068), "Right 4" until the OK, then **"Left 4"** | **BI-20** · W12 · `AUDIO-003` | S t=298–314; E 167–178 | | 08:28:54–08:29:00 | Controls (touch on, both holds Noise control, the mode list Noise cancellation + Adaptive + Transparency, Off unticked); Find | — | S t=319–321 | | 08:29:46.094 | **Disconnect** tap (phone `DISC` DLCI 2 B 3761) | BI-3 · W2 · `PAIR-003` | E 183–184; S t=371 | -| 08:29:48.237 | **Connect** tap; session 4 (ch 19), thirteen requests, `GetHardwareInfo` B 3878 → 3882 (57 ms); snapshot claim B 3823 … `DISC` B 3883 (**1.508 s**); "Probably worn (checked 08:29:48)" | BI-3 · W2 · `PAIR-003` | E 185–231; S t=373 | +| 08:29:48.237 | **Connect** tap; session 4 (ch 19), thirteen `ReadSetting`s, `SubscribeRuntimeInfo`, `GetHardwareInfo` B 3878 → 3882 (57 ms); snapshot claim B 3823 … `DISC` B 3883 (**1.508 s**); "Probably worn (checked 08:29:48)" | BI-3 · W2 · `PAIR-003` | E 185–231; S t=373 | | 08:29:51–08:29:56 | battery (i): Left/Right "not charging (08:29:49)", Case "last seen 08:27:46 (last connection)", the Worn text | — | S t=376–380 | | 08:30:23.5 | the Right bud into the case (6.3 = 2) — Right ⚡ on screen | — | B 3953; S t=408 | | 08:30:30.238 | **ACL dropped by the Buds (`0x13`)**; "the Buds closed the channel" → "Android's link to the Buds went down around the loss"; screen "Android no longer shows the Buds connected …" | — | B 3968; E 238–244; S t=415 | @@ -164,12 +164,12 @@ Columns: phone time · what happened (actor) · step / W / Test-ID · evidence. | P7 | not checked | — | no call came | | P8 | done | S t=0 | both buds in the case at the start | | P9 | **not done** | E/X 08:22:59–08:23:03 | the app opened and connected 34 s before the film | -| BI-1 | done (old build at 08:23:01; new build at 08:27:07 by LINK_BACK) | E 1–52; B 3078–3145 | thirteen requests, one `GetHardwareInfo`; the snapshot claim released 1.511 s after its answer | +| BI-1 | done (old build at 08:23:01; new build at 08:27:07 by LINK_BACK) | E 1–52; B 3078–3145 | fifteen requests (13 `ReadSetting` incl. 4:16, `SubscribeRuntimeInfo`, one `GetHardwareInfo`); the snapshot claim released 1.511 s after its answer | | BI-2 | done | S t=215–225 | build `5b4d5db`, no "-dirty"; firmware ×3; serials Case / Right / Left | | BI-3 | done | E 183–231 | Disconnect 08:29:46, Connect 08:29:48 | | BI-4 | **not done** | — | no Bluetooth off/on | | BI-5 | done once (at the end) | S t=555–560 | "Debug log saved (681 lines)." | -| BI-6 | done (without camera; both buds out of the case, worn per the maintainer — not filmed) | B 3484–3504; E 131 | one `SABM`, `08 11`, the hold logged; **no `DISC` for 30.5 s** (08:27:59.8–08:28:30.9) while the tab was shown; the ANC (i) not opened | +| BI-6 | done (without camera; both buds out of the case, worn per the maintainer — not filmed) | B 3484–3504; E 131 | one `SABM`, `08 11`, the hold logged; **no `DISC` for 31.1 s** (08:27:59.8–08:28:30.9) while the tab was shown; the ANC (i) not opened | | BI-7, BI-8 | done (four holds, the bud not identifiable) | B 3531, 3543, 3546, 3547 | the mode changed on screen without a tap each time; the (i) line **not looked at** | | BI-9 | done ×4 | B 3550–3602 | `08 11` → `Notify` → `Set` → ACK on the held channel, no `SABM`; the (i) not opened | | BI-10 | **not done** | — | the tile not used | @@ -187,14 +187,14 @@ the skeleton's "Not in this run"); a Connect by pull-to-refresh on Controls (08: (08:23:02, 08:27:07), `FW-003` (S t=10–19, 217–225), `ANC-001` Off (set 08:28:20), `ANC-002` NC (read 08:27:59, set 08:28:22), `ANC-003` Adaptive (08:28:05 by the Buds, set 08:28:23), `ANC-004` Transparency (08:28:08 by the Buds, set 08:28:19), `TOUCH-007` (08:28:05, 08:28:08, 08:28:11, 08:28:15 — four unprovoked `Notify`; the press-and-holds themselves are the maintainer's statement, not filmed), `CASE-004` Left out of the case (08:27:40, 08:30:47, 08:31:52, 08:32:17 — runtime info 6.2 = 1), `CASE-005` Right out (08:27:57, 08:30:33, 08:31:45, 08:32:06 — 6.3 = 1), `INEAR-006` (the Worn line at 08:23:35, 08:27:44, 08:29:48, 08:30:33/34), `AUDIO-003` -(08:28:34–08:28:49). **Expected but not observed:** `AUDIO-002` (BI-18, Volume EQ not run), `INEAR-004` (BI-19, in-ear detection not switched). +(08:28:34–08:28:49). **Expected but not observed:** `AUDIO-002` (BI-18, Volume EQ not run), `INEAR-001` (BI-19, in-ear detection not switched). ## Analysis checklist - [x] Every filter scoped to the Buds' handle `0x000b` (Connection Complete B 159, 3973, 4483, 4953, 5589, 6069, 6581 for the Buds' address). - [x] P0, P1, the Info frame's hash = B1's; **the installed APK = B1's file** (pulled, SHA-256 equal). - [x] Zero Play-services claims in B (positive control `CAP-066`). -- [x] I: every session's thirteen requests (11 of 11), one `GetHardwareInfo` per session; every snapshot claim released 1.507–1.513 s after its answer. +- [x] I: every session's fifteen requests (11 of 11: 13 `ReadSetting` incl. 4:16, `SubscribeRuntimeInfo`, one `GetHardwareInfo`); every snapshot claim released 1.507–1.513 s after its answer. - [x] II (ADR-061) as far as run: the hold's start, no `DISC` while the tab was shown, the `DISC` 1.509 s after leaving; four unprovoked `Notify`, nothing from the app after them; the taps without `SABM`. Not run: the tile, Home, the re-open with the tab shown, the (i) line. - [x] III: BI-20 against the references; the others not run. diff --git a/captures/CAP-073-2026-10-10_08-23-35_08-33-24-Group_BI/CAP-073-FINDINGS.md b/captures/CAP-073-2026-10-10_08-23-35_08-33-24-Group_BI/CAP-073-FINDINGS.md index c778267c..713520bc 100644 --- a/captures/CAP-073-2026-10-10_08-23-35_08-33-24-Group_BI/CAP-073-FINDINGS.md +++ b/captures/CAP-073-2026-10-10_08-23-35_08-33-24-Group_BI/CAP-073-FINDINGS.md @@ -42,7 +42,7 @@ trailing 4 bytes little-endian, the RpcPacket read with `scripts/pwrpc_decode.py itself 0.15 s after the start (E 4, LINK_BACK; ADR-044). - **The 2026-10-09 18:33:54 install:** `dumpsys package` (2026-10-10, phone attached) prints `firstInstallTime=2026-10-09 18:33:54` under **User 0** and `17:24:41` under User 10 — `ec6d163` was installed into the **Owner user** after `CAP-072` ended; one APK serves both users. A (the Owner's day) shows that app - in use at 18:35, 19:29, 06:43–06:44, 07:07–07:08 and 07:13 (each session with the thirteen requests and `GetHardwareInfo`; EQ and Volume-EQ writes at 07:07–07:08, + in use at 18:35, 19:29, 06:43–06:44, 07:07–07:08 and 07:13 (each session with the fifteen requests — 13 `ReadSetting` incl. 4:16, `SubscribeRuntimeInfo`, `GetHardwareInfo`; EQ and Volume-EQ writes at 07:07–07:08, A 21952–22180; `wm_set_resumed_activity: [0,…]` at 07:07:08). Not the run. - **No Google Play in user 10:** P0 lists only `app.grapheneos.gmscompat.config`, `…lib`, `gmscompat`, `exit=0` (as `CAP-072-FINDINGS.md` §0). Not made: `am get-current-user` and the P0 positive control; the user is in X 2 (`am_switch_user: 10`, 08:22:47.871). @@ -98,7 +98,7 @@ trailing 4 bytes little-endian, the RpcPacket read with `scripts/pwrpc_decode.py "disconnected mode" response; no source found that names this case, `ai-sessions/0085` search); harmless — the app had released the channel. - 🟢 **Another LE device** ("Charge 6", handle `0x0040`, 08:23:51–08:26:06, ATT only) — excluded by the handle filter. -## 2. The Connect read — thirteen requests (BI-1, BI-3; W1–W3; ADR-058) +## 2. The Connect read — fifteen requests (BI-1, BI-3; W1–W3; ADR-058) 🟢 **11 sessions** (1 by `ec6d163`, 10 by `5b4d5db`): the Buds' unsolicited `GetSoftwareInfo` → `ReadSetting` **16, 2, 4, 7, 11, 12, 15, 17, 19, 22, 27, 28, 29** → one `SubscribeRuntimeInfo` → **one `GetHardwareInfo`**, in that order, **in 11 of 11 sessions**; every request answered `status=OK` (E "Settings read" ×10 + X ×1). @@ -151,7 +151,7 @@ Command for an "Equal" check: `tshark -r -Y "frame contains =3480 && frame.number<=3660"` → B 3484 `0x2f`, 3486 `0x63`, 3654 `0x43`, 3656 `0x63` (exit 0) — the positive control is the claim's own `SABM`/`UA`). The channel stayed open - **30.97 s** (08:27:59.763–08:28:30.890) — `ec6d163` released every claim 1.5 s after its answer (B 450; `CAP-072` A 11947). + **31.13 s** (08:27:59.763–08:28:30.890, `SABM` 3484 → `DISC` 3654) *(corrected 2026-10-15, `ai-sessions/0094`)* — `ec6d163` released every claim 1.5 s after its answer (B 450; `CAP-072` A 11947). - **Four unprovoked `Notify`** on the held channel: B 3531 08:28:05.455 `e8 e8 40` (Adaptive), B 3543 08:28:08.072 `e8 e8 80` (Transparency), B 3546 08:28:11.877 `e8 e8 08` (NC), B 3547 08:28:15.749 `e8 e8 40` (Adaptive). **No `08 11` in the 2 s before any of them** (the app's last `08 11` before them is B 3492, 5.6 s earlier) and **nothing from the app after them** (the phone's only frames on DLCI 4 are zero-length UIH credit frames, `btrfcomm.len==0`, B 3532, 3544, 3548). @@ -228,7 +228,7 @@ Connection screen's own button"; the phone paged the Buds, B 4951). 🟢 No sess | Aspect | `ec6d163` (08:22:59–08:27:05) | `5b4d5db` (08:27:07 →) | Same? | |---|---|---|---| -| Connect read | the thirteen, one `GetHardwareInfo` (session 1) | the same (sessions 2–11) | ✓ byte-identical (§2) | +| Connect read | the fifteen (13 `ReadSetting` incl. 4:16, `SubscribeRuntimeInfo`, one `GetHardwareInfo`) (session 1) | the same (sessions 2–11) | ✓ byte-identical (§2) | | Snapshot claim | released 1.513 s after its answer (B 450) | 1.504–1.511 s (10 claims) | ✓ | | Noise-control tab | not opened on film; no claim between 08:23:03.5 and 08:27:05 | **held 31 s** while shown | the change of ADR-061 (`CAP-072` §5 is the old behaviour) | | Settings, permissions, association | Dark mode On, Debug mode on | kept; no dialog | ✓ | @@ -239,12 +239,12 @@ Connection screen's own button"; the phone paged the Buds, B 4951). 🟢 No sess ## 8. The skeleton's "Refuted if", applied literally per step that was run -- **BI-1** (crash; light theme; a request missing or out of order; DLCI 0x04 open 3 s after the snapshot): not refuted — dark, 13 requests in order in 11/11 +- **BI-1** (crash; light theme; a request missing or out of order; DLCI 0x04 open 3 s after the snapshot): not refuted — dark, 15 requests in order in 11/11 sessions, every snapshot released ≤ 1.513 s after its answer. - **BI-2** ("-dirty"; another hash; no serials): not refuted — `5b4d5db`, serials Case/Right/Left. - **BI-3**: not refuted. **BI-4**: not run. - **BI-5** (an empty file): not refuted — 681 lines, 66.75 kB (S t=584). -- **BI-6** (a `DISC` while the tab is shown): **not refuted** — none in 30.5 s. +- **BI-6** (a `DISC` while the tab is shown): **not refuted** — none in 31.1 s. - **BI-7/BI-8** (no `Notify`; the `Notify` but no line; the app sending anything in reaction): no `Notify` — not refuted (four); the app sending — not refuted; **"the line" — not checked** (the (i) not opened). - **BI-9** (a second `SABM`; the line still shown): `SABM` — not refuted; the line — not checked. @@ -270,7 +270,7 @@ Connection screen's own button"; the phone paged the Buds, B 4951). 🟢 No sess | 10 | P9: the app opened and connected 34 s before the film; the Owner user used the app before the run | test-procedure deviation | §0 | **No heavy defect:** no crash or ANR; every write answered and byte-identical where a reference exists; no write before its answer; no Safe Mode on `release_5.203`; -every session end explained and recovered; no setting lost by the update; no request outside the thirteen + the user's writes; nothing on DLCI 0x08/0x0a from the app. +every session end explained and recovered; no setting lost by the update; no request outside the fifteen + the user's writes; nothing on DLCI 0x08/0x0a from the app. **The analysing session's view (the decision is the maintainer's):** what ADR-061 was built for — a press-and-hold shown on the noise-control tab — is on the wire and on screen; the untested branches are release-and-close paths whose failure mode is a channel released too late or early (the ADR-032 behaviour), not a wrong diff --git a/captures/CAP-074-2026-10-12_06-40-16_06-45-31-Group_BJ/CAP-074-EVENT-NOTES.md b/captures/CAP-074-2026-10-12_06-40-16_06-45-31-Group_BJ/CAP-074-EVENT-NOTES.md index 22ce7fed..ca7c0377 100644 --- a/captures/CAP-074-2026-10-12_06-40-16_06-45-31-Group_BJ/CAP-074-EVENT-NOTES.md +++ b/captures/CAP-074-2026-10-12_06-40-16_06-45-31-Group_BJ/CAP-074-EVENT-NOTES.md @@ -224,8 +224,8 @@ Connect) · BJ-27 ANC tab; holds (06:45:18 shown — then 06:45:27 not) · BJ-28 read" ×10, 130 `ReadSetting` answers). - `ANC-001`…`ANC-004` — four taps in the app (Transparency 06:40:54, Off :59, Noise cancellation 06:41:06, Adaptive :12), 4 `Set` → 4 ACK, 0 NAK; every mode also shown from a `Notify` (Off 06:44:27, 06:45:18; NC 06:43:54, 06:44:24, 06:45:14; Adaptive 06:44:57; Transparency 06:42:33, 06:43:00, 06:43:56). -- `TOUCH-007` — **47 mode changes the app did not request** in the run: 9 arrived on the app's claims and are on screen; 35 are unprovoked `Notify` frames on Play - services' claims; 3 are Play's own `Get` answers that already carry a changed mode. The press-and-holds themselves are the maintainer's statement (no camera; the +- `TOUCH-007` — **49 mode changes the app did not request** in the run: 9 arrived on the app's claims and are on screen; 35 are unprovoked `Notify` frames on Play + services' claims; 5 are Play's own `Get` answers that already carry a changed mode (15618, 15957, 16270, 16999, 17940; FINDINGS §4 item 6). The press-and-holds themselves are the maintainer's statement (no camera; the cue words are 0.1–1.5 s after a `Notify` in 33 of 33 cases). - `BATT-004` — "Battery updated" ×3 on every claim's open (E 39, 58–59, 73–74, …; 100 %/100 %/`ff`). - `FW-003` — the Info tab on film (t=6): firmware ×3, the three serials with their labels (06:38:11). diff --git a/captures/CAP-074-2026-10-12_06-40-16_06-45-31-Group_BJ/CAP-074-FINDINGS.md b/captures/CAP-074-2026-10-12_06-40-16_06-45-31-Group_BJ/CAP-074-FINDINGS.md index a607eb49..cfd1f815 100644 --- a/captures/CAP-074-2026-10-12_06-40-16_06-45-31-Group_BJ/CAP-074-FINDINGS.md +++ b/captures/CAP-074-2026-10-12_06-40-16_06-45-31-Group_BJ/CAP-074-FINDINGS.md @@ -139,8 +139,8 @@ a9 7e` = `CAP-073` B 3375); the two strings not in `CAP-073` are this run's own answer (E 49, 152, 254, …), the Info tab labels them Case / Right bud / Left bud (S t=6). - 🟢 **The snapshot claim of every Connect collided with Play services:** in **10 of 10** sessions the DLCI 0x04 attempt 1 failed after 56–363 ms with `IOException: - read failed, socket might closed or timeout, read ret: -1` (E 15, 84, 98, 119, 163, 191, 218, 280, 335, 396, 456, 513, 571, 626 — 14 such lines in the run incl. the - tap and tab claims) and attempt 2 connected 0.6–0.7 s later — the `ai-sessions/0039` collision shape: the app's failed attempt closes Play's port (the phone's `DISC` + read failed, socket might closed or timeout, read ret: -1` (E 15, 84, 98, 119, 163, 191, 218, 280, 335, 396, 456, 513, 571, 626 — 13 such lines in the run, E 84 … 626, incl. the + tap and tab claims; E 15 is 06:38:10, before the film) and attempt 2 connected 0.6–0.7 s later — the `ai-sessions/0039` collision shape: the app's failed attempt closes Play's port (the phone's `DISC` of Play's claim within 2 ms of E's "connect failed" line, e.g. B 14450 06:40:50.698 ↔ E 119 06:40:50.698), the retry finds the port free. **`CAP-073` (no Play): 0 of 12 first attempts failed.** @@ -162,8 +162,8 @@ first hold. **What the evidence shows (🟢 unless marked):** 1. **Every entry of the noise-control tab started a hold, and every hold ended by a close the app did not make.** E logs "Message Stream hold started: the - noise-control tab is on screen (ADR-061)" 12 times in the run (06:40:38.936, 06:40:51.921, 06:40:59.801, 06:41:06.430, 06:41:12.441, 06:41:57.867, 06:42:31.044, - 06:42:58.043, 06:43:27.434, 06:43:51.743, 06:44:24.476, 06:44:53.944, 06:45:14.634 — thirteen with the pre-film 06:38:24.617) and "hold ended" 13 times: **once "the + noise-control tab is on screen (ADR-061)" 13 times in the run (06:40:38.936, 06:40:51.921, 06:40:59.801, 06:41:06.430, 06:41:12.441, 06:41:57.867, 06:42:31.044, + 06:42:58.043, 06:43:27.434, 06:43:51.743, 06:44:24.476, 06:44:53.944, 06:45:14.634 — fourteen with the pre-film 06:38:24.617) *(count corrected 2026-10-15, `ai-sessions/0094`)* and "hold ended" 13 times: **once "the noise-control tab was left"** (06:40:40.392 — the tab visit of t=22–25) and **twelve times "the channel was closed, not by this app"**, each 2–3 ms after "RFCOMM on-demand channel 0x04 closed (IOException: bt socket closed, read return: -1) — session channels untouched" (E 161/162, 176/177, 189/190, 204/205, 266/267, 321/322, 380/381, 440/441, 497/498, 555/556, 610/611, 667/668). In `CAP-073` the one hold of the run ended by the tab (31 s), never by a close. @@ -176,8 +176,8 @@ first hold. came 2.18 s after the previous cut — Play's *retry*, see 5). Measured from the app's own `SABM`: 4.68, 4.92, 4.82, 5.13, 4.66, 4.85, 4.66, 5.04, 4.88 s for the nine claims that followed a collision directly, and **1.03 s (15500), 1.61 s (16578), 0.38 s (14845)** for the three claims opened on attempt 1 into a free channel — their life is whatever was left of Play's 5.2–5.7 s. The same ≈ 5.3 s shows where the app had already released by itself: Play's re-open came 5.30, 5.32, 5.33, 5.30, 5.28 s - after it lost its socket (06:38:10.631 → 13047 06:38:15.937; 13168 → 13251; 13373 → 13450; 14072 → 14152; 14263 → 14346). **20 of 20 recoveries of the run fall in - 5.2–5.7 s after the loss**; `ai-sessions/0040` measured 2.7–5.0 s on 2026-09-19 and `CAP-059` 1.7–3.1 s (a different Play version or state — ⚪ not resolved here). + after it lost its socket (06:38:10.631 → 13047 06:38:15.937; 13168 → 13251; 13373 → 13450; 14072 → 14152; 14263 → 14346). **16 of 16 measured recoveries of the run fall in + 5.2–5.7 s after the loss** *(count corrected 2026-10-15, `ai-sessions/0094`)*; `ai-sessions/0040` measured 2.7–5.0 s on 2026-09-19 and `CAP-059` 1.7–3.1 s (a different Play version or state — ⚪ not resolved here). 4. **What a recovery does to the app's claim:** no Play `SABM` is on the wire at the moment of a cut — the stack refuses Play's connect before anything reaches the Buds and, on that failure path, removes the **existing** port, which is the app's (§8: AOSP `port_api.cc` returns `PORT_ALREADY_OPENED` with the incumbent's handle, `bta_jv_act.cc` then calls `RFCOMM_RemoveConnection(handle)`). The wire shows only the resulting `DISC` of the app's DLCI; the stack's own log lines are not retained @@ -194,7 +194,9 @@ first hold. 06:45:17.550** — and **each of the nine is on screen** (S t=138 Transparency, 164 Transparency, 218 NC, 220 Transparency, 249 NC, 251 Off, 281 Adaptive, 299 NC, 302 Off) with no touch indicator. The (i) line "Changed by the Buds …" was not opened (not on film). - on Play's claims: 13 answer Play's own `08 11` (one per claim; three of them already carry a changed mode — 15618 `80`, 15957 `08`, 16270 `08`, each 0.4–0.5 s - after Play's `SABM` with a cue word 0.2–0.4 s later: a hold at the moment of Play's open), **35 are unprovoked** (the bold frames of §1's table) — **none reached the + after Play's `SABM` with a cue word 0.2–0.4 s later: a hold at the moment of Play's open; by the same rule — a mode other than the channel's previous + `Notify` — two more do: 16999 `08` after the app's 16937 `80`, 17940 `40` after the app's 17848 `20`, cue words not checked; count added 2026-10-15, + `ai-sessions/0094`), **35 are unprovoked** (the bold frames of §1's table) — **none reached the app, none is on screen:** the screen keeps the last mode the app saw (Adaptive t=57–94 and t=102–129, Transparency t=138–156 and t=164–182 and t=220–242, Adaptive t=191–210, Off t=251–274, Adaptive t=281–295) while the Buds cycled through 5–7 modes each time. - **The order of the Buds' changes follows the press-and-hold list** written at 06:38:41 (all four: `40` → `80` → `08` → `20` → `40` …, e.g. 15204–15256; `CAP-073`'s @@ -257,9 +259,9 @@ held the channel again — attempt 2 OK, hold 0.7 s, released by the tab) is the | Connect reads | the thirteen + `GetHardwareInfo`, byte-identical to `CAP-072` (11 of 11) | **byte-identical to `CAP-073`** (15 of 15 strings, 10 of 10 sessions; §2) | | Play-services marker | 0 in the run (`.last`: 14, the Owner user) | **22** (A: 7) | | DLCI 0x04 claim owner over time | the app only; between its claims nobody | **alternating app / Play**: Play holds it whenever the app does not (116 s, 34 s, 29 s, 18–24 s, 12 s, > 408 s); the app's claims last 0.4–5.1 s before the cut | -| The app's first connect attempt on 0x04 | 0 of 12 failed | **14 of 16 failed** (56–363 ms), 2 connected into a free channel and were cut within 0.4–1.6 s | +| The app's first connect attempt on 0x04 | 0 of 12 failed | **13 of 16 failed** (95–363 ms), 3 connected into a free channel and were cut within 0.4–1.6 s *(corrected 2026-10-15, `ai-sessions/0094`)* | | The hold's lifetime and end | 31 s, ended by leaving the tab (`DISC` 1.509 s later) | **0.4–5.1 s, ended twelve of twelve times by a foreign close** ("not by this app"), once by leaving the tab | -| Play's recovery after losing the channel | n/a | **5.2–5.7 s, 20 of 20**; retry 2.2–2.6 s after a failed recovery | +| Play's recovery after losing the channel | n/a | **5.2–5.7 s, 16 of 16**; retry 2.2–2.6 s after a failed recovery | | Unprovoked `Notify` received by the app | 4 of 4 of the run (B 3531, 3543, 3546, 3547 — on the 31-s hold) | **9 of 44** (the 35 on Play's claims + 3 at Play's opens unseen) | | Unprovoked `Notify` received by Play services | 0 (no Play) | 35 + 3 (one logged by `HearableControlManager`, 06:45:26.6) | | Unprovoked `Notify` received by nobody | 0 (the Buds send only to a connected Seeker: no claim ⇒ no frame — `CAP-072` §5) | 0 — Play is on the channel whenever the app is not; every hold is on the wire | diff --git a/captures/CAP-075-2026-10-12_17-22-31_2026-10-13_06-34-32-Group_BK/CAP-075-EVENT-NOTES.md b/captures/CAP-075-2026-10-12_17-22-31_2026-10-13_06-34-32-Group_BK/CAP-075-EVENT-NOTES.md index 47219500..8225f9be 100644 --- a/captures/CAP-075-2026-10-12_17-22-31_2026-10-13_06-34-32-Group_BK/CAP-075-EVENT-NOTES.md +++ b/captures/CAP-075-2026-10-12_17-22-31_2026-10-13_06-34-32-Group_BK/CAP-075-EVENT-NOTES.md @@ -20,7 +20,7 @@ shown. With Play (set 2) the behaviour of `CAP-074` is back: Play's Fast Pair ta and showed none of the 9 holds made while it was selected as they happened. **"Take it back"** ran three chains of exactly three re-claims (3 000 / 6 000 / 12 000 ms in the export), every re-claim colliding with Play's retry (attempt 1 refused by the stack, attempt 2 connecting ≈ 0.9 s later and killing Play's socket), every re-claimed hold cut again 5.3–5.8 s after Play's loss; of the 31 holds made in set 2's film, 8 were on screen within a second (5 on the app's own -claim, 3 by a re-claim's own read), 2 only after the maintainer tapped, and 16 never. The stack path `CAP-074-FINDINGS.md` §8/§10 left 🟡 is on the system log +claim, 3 by a re-claim's own read), 2 late by a re-claim, 3 for < 0.3 s or overdrawn, 2 only after the maintainer tapped, and 16 never. The stack path `CAP-074-FINDINGS.md` §8/§10 left 🟡 is on the system log for 15 of 15 cuts in the log's reach. **Not in this run** (as the skeleton allowed): BK-11 … BK-13 (a tab change, Home or the switch *within 2 s of a cut* — the one "re-claim cancelled" of the run @@ -39,7 +39,7 @@ version are **not** on film (P0's "on film" item not done). | Phone | Pixel 9a, GrapheneOS, Android 17 (`CP3A.261005.005` in `CAP-074`; not re-read here). **Set 1: the secondary user** (the app logcat's header `userType: full.secondary`; the 1.2.0 process of 17:22 and the 1.2.1 process of 17:25 both `package … :10201`). **Set 2: the Owner user 0** (`am_proc_start: [0,17838,10354,io.github.tedsluis.opencontrolpixelbuds,…]` at 06:18:58.248 in CAP-075-logcat-all.txt and the same line, in UTC, in the System log; the app logcat's header has no `userType` line) | | App under test | OpenControl for Pixel Buds Pro 2 **1.2.1** (versionCode 10201) — **Info tab on film in both users: "App: 1.2.1, build 31ee183 (2026-10-12)"**, no "-dirty" (film 1 s187–189 = 17:25:38.9–40.9; film 2 s23–36 = 06:29:27–40). Installed over 1.2.0 `5b4d5db` during set 1's film (Android's "Updaten…" overlay film 1 s166–168 = 17:25:17.9–19.9; the 1.2.0 process 11917 lost the foreground 17:25:17.843, the 1.2.1 process 14361 was created 17:25:20.128 — app logcat 1, UTC 15:25:17.843 / 15:25:20.128). The installed APK's hash: **not pulled** (⚪, Checkpoint 0) | | Previous version in each user | 1.2.0 build `5b4d5db` — on film in set 1 (s0–4, "App: 1.2.0, build 5b4d5db (2026-10-10)", Settings without the "Noise-control channel" card, s7–10); the update kept the app's data (Debug mode and Dark mode as before; `firstInstallTime` not read: P1 not run) | -| Google Play services | **set 2: present and active** — uid 10205, process 7277, 19 Message Stream claims of its own in the live HCI log and 33 `cleanup_rfc_slot … app_uid: 10205` lines in the system log; its notification "Google Play-services needs to sho…" on film 2 s3–5. **Set 1: absent** — 0 frames with Play's first message `03 08 00 02 01 25` in both set-1 HCI logs (positive control: 22 in `CAP-074-btsnoop_hci.log`, 6 + 18 in set 2's two logs), 0 "closed, not by this app" lines in export 1. *Nearby devices* permission and Play's version: **not on film** | +| Google Play services | **set 2: present and active** — uid 10205, process 7277, 18 Message Stream claims of its own in the live HCI log and 33 `cleanup_rfc_slot … app_uid: 10205` lines in the system log; its notification "Google Play-services needs to sho…" on film 2 s3–5. **Set 1: absent** — 0 frames with Play's first message `03 08 00 02 01 25` in both set-1 HCI logs (positive control: 22 in `CAP-074-btsnoop_hci.log`, 6 + 18 in set 2's two logs), 0 "closed, not by this app" lines in export 1. *Nearby devices* permission and Play's version: **not on film** | | Official Pixel Buds app | not used | | Buds | Pixel Buds Pro 2, firmware **`release_5.203` ×3** (film 1 s16–22 and s187; film 2 s24–36; `GetSoftwareInfo` answers hci1.log 7xx / hci2.log 1190); the three serials on film (not written here). **Worn** for every hold of set 1 from 17:26:04 (Settable byte `e8` from claim #5; `00` at 17:22:45, 17:23:10, 17:23:23, 17:25:20 and 17:25:24 — the buds were in the case until after 17:25:24, "Not worn (checked 17:25:24)" film 1 s209) and for every hold of set 2's film (`e8` from 06:28:55; `00` at 06:19:01 — in the case) | | Video files | **set 1:** CAP-075-recording1.mp4 (Android's name carried the stamp 20261012-172842 = the end) — 370.84 s, 4 203 frames at 11.33 fps (variable), 1080×2424, **with sound** (AAC); **set 2:** CAP-075-recording2.mp4 (Android's stamp 20261013-063431 = the end; its epoch stamp 1791865743.758 = the start 06:29:03.758) — 327.89 s, **with sound**. No camera. Both **committed as is** (Checkpoint 0) | @@ -178,7 +178,7 @@ and the Play alternation as set 2's film and is **not** analysed further (no scr - **Before the recording.** 06:18:58.248 the app process 17838 starts in user 0 (L/X `am_proc_start`); E2 06:18:58.764 "start", 06:18:59.018 the Android link observer: NOT_CONNECTED. **06:19:00.638 Connect** (no "automatic re-open" line: a tap — not on film): the phone pages the Buds (H2L 18117 06:19:00.677; the ACL - 18119 06:19:00.784), **channel 21**, the thirteen requests + `GetHardwareInfo` (H2L 18253–18326, byte-identical to set 1's 17:25:20 sequence), the snapshot + 18119 06:19:00.784), **channel 21**, the thirteen `ReadSetting`s + `SubscribeRuntimeInfo` + `GetHardwareInfo` (H2L 18253–18326, byte-identical to set 1's 17:25:20 sequence), the snapshot claim #10 of H2L (`SABM` 18257 06:19:01.110, attempt 1 OK — the channel was free; `Notify` 18288 `…e8 00 20`: Off, **in the case**). **06:19:02.593 "hold started"** (the ANC tab was opened within 1.5 s: the snapshot claim became the hold — BK-4's path) → **06:19:03.666 the phone's `DISC`** (18576; E2 03.674 "closed (IOException: bt socket closed, read return: -1)" + 03.675 "hold ended: the channel was closed, not by this app") → **06:19:04.522 "Message Stream @@ -243,7 +243,7 @@ and the Play alternation as set 2's film and is **not** analysed further (no scr after 777 ms (E2 06:31:24.949; Play's #11 `DISC` 3273 06:31:24.948), **claim #12** `SABM` 3297 06:31:25.871, `Notify` 3321 `…20` (Off — the hold of 06:31:06, read now); **the tab left at once** (Connection s141, 06:31:25: "Probably worn (checked 06:30:58)" → s142 "(checked 06:31:26)") — no "hold started" line (the tab was left before the claim's answer); **06:31:26.627 Disconnect** (s143 "not open yet — tap Connect"): the app's `DISC` 3325 06:31:26.633 and of - DLCI 0x02. **06:31:28.158 Connect** (s144 "connecting…"; channel 19; the thirteen requests): **the snapshot claim connects at attempt 1** (claim #13 `SABM` + DLCI 0x02. **06:31:28.158 Connect** (s144 "connecting…"; channel 19; the fifteen requests): **the snapshot claim connects at attempt 1** (claim #13 `SABM` 3392 06:31:29.508 — the channel was free: Play's socket had died at 06:31:24.948 and its recovery timer was running; `Notify` 3423 `…20`; s145 ready, s146 "Probably worn (checked 06:31:29)") → **cut by Play's recovery 06:31:30.508** (`DISC` 3463; E2 "closed not by this app" 06:31:30.576; **5.560 s after Play's loss** — the Connection tab was shown, so no hold, no hint, no re-claim: the snapshot claim was simply gone; BK-14's path with the tab not shown). → Play's @@ -257,7 +257,7 @@ and the Play alternation as set 2's film and is **not** analysed further (no scr its `Get` answered `40` (3646: a hold in the gap 33.0–37.6), then `80` 3688 39.801, `08` 3711 43.527, `80` 3731 45.847, `08` 3801 51.482 — **none shown** (Off s150–159; the volume panel s151). Cue 06:31:33.72. BK-5/BK-6's family, `TOUCH-007`. - **06:31:50–55 Disconnect / Connect (BK-14, hint only):** Connection s166 ("Probably worn (checked 06:31:32)"), **Disconnect 06:31:51.824** (s168–169; the app's - `DISC` of 0x02), **Connect 06:31:55.535** (s171 "connecting…"; channel 19; the thirteen requests): the snapshot claim — attempt 1 fails 408 ms (Play's #15 + `DISC` of 0x02), **Connect 06:31:55.535** (s171 "connecting…"; channel 19; the fifteen requests): the snapshot claim — attempt 1 fails 408 ms (Play's #15 killed: `DISC` 3879 06:31:56.779), attempt 2 = claim #16 `SABM` 3924 06:31:57.639, `Notify` 3964 `…08` (NC — the hold of 06:31:51.48, read now), released `DISC` 3992 06:31:59.647 (the Connection tab: s172 ready, s174 "Probably worn (checked 06:31:58)"). **The hint was gone at the Connect** (s171–176) ✓. **06:32:00.2 the ANC tab** (s176, NC): claim #17 `SABM` 4025 06:32:00.579 (attempt 1 OK — Play's recovery timer running), `Notify` 4046 `…08`, "hold started" @@ -270,7 +270,7 @@ and the Play alternation as set 2's film and is **not** analysed further (no scr 981 ms (E2 06:32:32.136; Play's #18 `DISC` 4374 06:32:32.135), claim #19 `SABM` 4408 06:32:33.157, `Notify` 4434 `…08`; **the tab left** (Connection s209, 06:32:33.2; s210 "checked 06:32:33") → released `DISC` 4446 06:32:35.246 (no hold line: the tab was left before the answer); **06:32:35.405 Disconnect** (s211). Play's recovery #20 `SABM` 4489 06:32:37.707 (5.572 s after 32.135). -- **06:32:38.747 Connect (s214; BK-3/BK-4's path under "Take it back"):** the thirteen requests; the snapshot claim — attempt 1 fails 501 ms (Play's #20 killed: +- **06:32:38.747 Connect (s214; BK-3/BK-4's path under "Take it back"):** the fifteen requests; the snapshot claim — attempt 1 fails 501 ms (Play's #20 killed: `DISC` 4600 06:32:39.714), attempt 2 = **claim #21** `SABM` 4639 06:32:40.490, `Notify` 4673 `…08` 06:32:41.002 (s215 ready, s217 "Probably worn (checked 06:32:41)"); **the ANC tab within 1.5 s (s218, 06:32:42.2) → "hold started" 06:32:41.966 on the snapshot claim** (BK-4 ✓: no second `SABM`) → **the cut 4738 06:32:45.240** (5.526 s after Play's loss) → **chain 2:** Play #22 4768 47.774 (2.534 s) → "re-claim 1/3 … 3000 ms" 06:32:48.332 → fails 792 ms (Play's `DISC` @@ -317,7 +317,7 @@ and the Play alternation as set 2's film and is **not** analysed further (no scr | P1 | partly | the update over 1.2.0 on film 1 (17:25:17.9–19.9, no uninstall: the app's data kept); no `dumpsys` saved; **no APK pull** (Checkpoint 0) | | P2–P5, P7–P9 | ✓ (P7 not verifiable) | HCI snoop on across both sets (the rotation at each Bluetooth off/on); screen recordings with sound; the status bar across minute changes in both films; buds charged (100 %/100 %); **P9 not kept in set 2** (the app was opened at 06:18:58, ten minutes before the film) and in set 1 the 1.2.0 app was open before the update by design | | P6 | ✓ (set 2) | `bugreportz` at 06:35:41 (the main buffer from 04:34:02 UTC — too late for the cuts) **and** the GrapheneOS log viewer's export whose main buffer starts 06:26:54 and covers **all 15 cuts of the film** (the stack lines are present for 15 of 15) | -| BK-1 (Play: open the app) | ✓ differently | the session of 06:19:00 (a Connect tap; the thirteen requests; the snapshot claim at attempt 1 — Play was not on the channel yet) and the re-open of 06:29:27 (attempt 1 fails 433 ms, attempt 2 connects) | +| BK-1 (Play: open the app) | ✓ differently | the session of 06:19:00 (a Connect tap; the fifteen requests; the snapshot claim at attempt 1 — Play was not on the channel yet) and the re-open of 06:29:27 (attempt 1 fails 433 ms, attempt 2 connects) | | BK-2 (Play: Info, Settings card) | ✓ | film 2 s24–36 Info ("App: 1.2.1, build 31ee183 (2026-10-12)", `release_5.203` ×3, the serials); the card on s41–52 — **"Take it back" already selected** (not the fresh default: set before the film) | | BK-3 (Disconnect / Connect) | ✓ ×3 | 06:31:26.6/06:31:28.2, 06:31:51.8/06:31:55.5, 06:32:35.4/06:32:38.7 | | BK-4 (the ANC tab within 1.5 s of ready) | ✓ ×2 | 06:19:02.593 and 06:32:41.966: "hold started" on the snapshot claim, no second `SABM` | @@ -332,16 +332,16 @@ and the Play alternation as set 2's film and is **not** analysed further (no scr | BK-end1 | ✓ | the export 06:35:33 (513 lines), `bugreportz` 06:35:41, the status bar across minutes; the switch to the other user was the evening before (set 1 came first) | | BK-15 (no Play: open the app; Info; the card) | ✓ | film 1 s168–189 (the LINK_BACK re-open after the update; Info; the card with "Show a hint" — the default on the update) | | BK-16 (hint only: the ANC tab, holds for 30 s) | ✓ ×3 | H2 (4 holds), H3 (9), H5 (12): every hold shown, **no `DISC` while the tab was shown**, no hint, 0 Play claims | -| BK-17 ("Take it back"; holds for 30 s) | ✓ ×2 | the switch 17:26:58; H4 (12 holds, 34 s, no `DISC`), H6 (4 holds, 21 s) — no re-claim line, no extra `SABM`, no hint | +| BK-17 ("Take it back"; holds for 30 s) | ✓ ×2 | the switch 17:26:58; H4 (12 holds; held 34.1 s, 17:27:04.9–38.99; claim `SABM` → `DISC` 35.72 s, no foreign `DISC`), H6 (4 holds; held 21 s; claim 22.62 s) — no re-claim line, no extra `SABM`, no hint | | BK-18 (leave the tab) | ✓ ×5 | every hold ended "the noise-control tab was left" → the phone's `DISC` 1.50–1.51 s later (H1 … H5); H6 "the app is not visible" → 1.51 s | | BK-19 / BK-20 / BK-21 (optional) | ✗ | not run in either set | | BK-end2 | partly | the export 17:31:58 (228 lines); **the switch not set back** to "Show a hint" (set 1 ends on "Take it back" since 17:28:19); no APK pull | ## Test-IDs (`AGENTS.md` §13 step 7; `TESTPLAN_BLUETOOTH_HCI_SNOOP.md`) -- `PAIR-003` — set 1: the Connect 17:22:44, the ADR-044 re-open 17:23:10, the re-open after the update 17:25:20 (three sessions, each with the thirteen requests - and one `GetHardwareInfo` answered); set 2: the Connect 06:19:00, the LINK_BACK re-open 06:29:27, the Disconnect/Connect pairs 06:31:26/28, 06:31:51/55, - 06:32:35/38 (five sessions, the same thirteen requests, byte-identical to `CAP-073`/`CAP-074`). +- `PAIR-003` — set 1: the Connect 17:22:44, the ADR-044 re-open 17:23:10, the re-open after the update 17:25:20 (three sessions, each with the thirteen `ReadSetting`s, + `SubscribeRuntimeInfo` and one `GetHardwareInfo` answered); set 2: the Connect 06:19:00, the LINK_BACK re-open 06:29:27, the Disconnect/Connect pairs 06:31:26/28, 06:31:51/55, + 06:32:35/38 (five sessions, the same fifteen requests, byte-identical to `CAP-073`/`CAP-074`). - `BATT-004` — "Battery updated" (`03 03 00 03 …`) on every claim's open in both sets (E1 lines 51–52 at 17:25:20.5; E2 55–56 at 06:19:03.6); the Case from the runtime-info stream (74 → 73 % set 1; 71 → 69 % set 2). - `FW-003` — the Info tab on film in both users: `release_5.203` ×3, the serials with their labels, the 1.2.1 build line (film 1 s187–189; film 2 s24–36); the 1.2.0 @@ -361,7 +361,7 @@ and the Play alternation as set 2's film and is **not** analysed further (no scr - [x] Every filter scoped to the Buds' handle `0x000b` (shown matching hci2.log 2298); the Play marker's positive control (`CAP-074` 22); DLCIs by content. - [x] P0 by the logs (set 1 `full.secondary` + 0 Play; set 2 user 0 + Play uid 10205) — the shell outputs and the permission/version film **not** made; P1 the update on film, **no APK pull** (⚪ — Checkpoint 0); the Info frame's hash = B1's `31ee183` in both users. -- [x] The claim inventory of DLCI 0x04 with both clients (`CAP-075-FINDINGS.md` §1): 1 + 10 (set 1, all the app's), 12 + 38 (set 2: 6 + 19 the app's, 3 + 19 Play's), +- [x] The claim inventory of DLCI 0x04 with both clients (`CAP-075-FINDINGS.md` §1): 1 + 10 (set 1, all the app's), 12 + 38 (set 2: 6 + 20 the app's, 6 + 18 Play's), every `SABM`/`DISC` with its time and closer, every `Notify` attributed (§4). - [x] Part 1 hint only: the cuts at Play's recovery **5.33–5.82 s after Play's loss** (not a fixed time after the app's `SABM` — 0.42 s and 1.2 s for #14 and #17); no app `SABM` after a cut without a tap, re-entry or Connect; the hint line with the cut's time (06:31:33, 06:32:02); cleared by the Connect and by Refresh; diff --git a/captures/CAP-075-2026-10-12_17-22-31_2026-10-13_06-34-32-Group_BK/CAP-075-FINDINGS.md b/captures/CAP-075-2026-10-12_17-22-31_2026-10-13_06-34-32-Group_BK/CAP-075-FINDINGS.md index 0108f52f..617ab91a 100644 --- a/captures/CAP-075-2026-10-12_17-22-31_2026-10-13_06-34-32-Group_BK/CAP-075-FINDINGS.md +++ b/captures/CAP-075-2026-10-12_17-22-31_2026-10-13_06-34-32-Group_BK/CAP-075-FINDINGS.md @@ -15,7 +15,7 @@ app's own claim lives only until Play's next connect, which is **5.33–5.82 s a 1.2 s for two of them). "Take it back" ran three chains of exactly three re-claims (3 000 / 6 000 / 12 000 ms after the cut, as scheduled); **every re-claim collided**: Play's retry came 2.2–2.8 s after each cut, so attempt 1 was refused by the Bluetooth stack ("already at opened state") and attempt 2, 0.9–1.5 s later, connected by killing Play's socket; every re-claimed hold was cut again 5.3–5.8 s after that. The app held the channel **≈ 43 % of each 44-s chain**, -then nothing until a tap. Of the 31 holds made in set 2's film, 8 were on screen within a second, 16 never. The back-off buys nothing: Play's timers did not +then nothing until a tap. Of the 31 holds made in set 2's film, 8 were on screen within a second, 2 late by a re-claim, 3 for < 0.3 s or overdrawn, 2 only after a user action, 16 never (§4). The back-off buys nothing: Play's timers did not change over 15 cycles. The two best next steps for the Play user are a constant 3-s step with a longer chain (§9 P1) and a way to restart the chain from the hint (§9 P2). No crash, no write, no Safe Mode, no session lost by the app (§11). @@ -31,8 +31,8 @@ hint (§9 P2). No crash, no write, no Safe Mode, no session lost by the app (§1 2026-10-13). Set 2 = the Owner (user 0): `am_proc_start: [0,17838,10354,io.github.tedsluis.opencontrolpixelbuds,…]` (L line for 06:18:58.248; X the same at 04:18:58.248 UTC); 0 `am_switch_user` lines in X (`grep -c am_switch_user` → 0, exit 1; positive control `grep -c am_proc_start` → 123, exit 0). The maintainer can still confirm with `adb shell am get-current-user` / `pm list users` (Checkpoint 0). -- **Play.** Set 2: present and active — uid 10205 / process 7277 (`NearbyDiscovery`, `HearableControl` lines in L), 19 claims of its own on DLCI 0x04 in H2 and - 3 + 2 in H2L, 33 `cleanup_rfc_slot … scn: 2, app_uid: 10205` lines; `frame contains 03:08:00:02:01:25` → **6 (H2L) + 18 (H2)**, exit 0. Set 1: **0** in H1L +- **Play.** Set 2: present and active — uid 10205 / process 7277 (`NearbyDiscovery`, `HearableControl` lines in L), 18 claims of its own on DLCI 0x04 in H2 and + 4 + 2 in H2L *(counts corrected 2026-10-15, `ai-sessions/0094`)*, 33 `cleanup_rfc_slot … scn: 2, app_uid: 10205` lines; `frame contains 03:08:00:02:01:25` → **6 (H2L) + 18 (H2)**, exit 0. Set 1: **0** in H1L and 0 in H1 (the same filter, exit 0; positive control `CAP-074-btsnoop_hci.log` → 22). Play's *Nearby devices* permission and version: not on film (P0's item not done — `TODO.md` §3's standing item stays open). - **Other clients on the Buds' ACL in set 2.** The Google app (uid 10302, `com.google.android.googlequicksearchbox` per L's `am_pss` lines) opened RFCOMM @@ -69,8 +69,8 @@ the non-initiating device are reachable on DLCIs 2, 4, 6, …" (RFCOMM §5.2, qu |---|---|---|---|---|---| | H1L | 501 17:22:45.301 → 1097 17:23:04.162 `0x16` | 1 | 1 (#1 `SABM` 617 → `DISC` 841, 1.55 s) | 0 | the phone (the app's release) | | H1 | 151 17:23:09.631 → 1814 17:28:47.531 `0x13` | 10 | 10 (#1 … #10, below) | 0 | the phone, 1.50–1.51 s after the hold's end or 1.54–1.64 s after a snapshot/Refresh answer | -| H2L | 18:13:17 → 18:14:11 `0x13`; 18:14:11 → 18:14:12 `0x13`; 18119 06:19:00.784 → 21818 06:29:13.490 `0x16` | 12 | 7 (#1, #2, #4, #6, #8 on 2026-10-12; #10 06:19:01) | 5 (#3, #5, #7, #9; #11 06:19:05–06:28:49, 583 s, closed **by the Buds** 21491; #12 06:28:54–06:29:13) | the phone; #10 cut by Play (18576 06:19:03.666) | -| H2 | 469 06:29:21.136 → open at the end | 38 | 19 | 19 (#38 open at the end) | the phone: 15 cut by Play's connect, 4 released by the app (#2, #16, #19, #37) or its Disconnect (#12) | +| H2L | 18:13:17 → 18:14:11 `0x13`; 18:14:11 → 18:14:12 `0x13`; 18119 06:19:00.784 → 21818 06:29:13.490 `0x16` | 12 | 6 (#1, #2, #4, #6, #8 on 2026-10-12; #10 06:19:01) | 6 (#3, #5, #7, #9; #11 06:19:05–06:28:49, 583 s, closed **by the Buds** 21491; #12 06:28:54–06:29:13) | the phone; #10 cut by Play (18576 06:19:03.666) | +| H2 | 469 06:29:21.136 → open at the end | 38 | 20 | 18 (#38 open at the end) | the phone: 15 cut by Play's connect, 4 released by the app (#2, #16, #19, #37) or its Disconnect (#12) | **Set 1's ten claims (H1, all the app's, every `Notify` on them):** #1 460 17:23:10.055 → 546 (1.54 s, snapshot, `…00 20`), #2 615 17:23:23.003 → 640 (4.07 s, the 1.2.0 ANC tab), #3 782 17:25:20.575 → 845 (1.55 s, the 1.2.1 snapshot), #4 868 17:25:24.563 → 954 (5.33 s, H1 hold, Settable `00`), #5 1111 17:26:03.977 → @@ -122,7 +122,7 @@ The 1.2.1 sequences were compared request by request (`tshark … -Y 'btrfcomm.d ## 3. Every write — byte for byte The app wrote, on DLCI 0x02, only the sequences of §2 (3 + 5 sessions × 15 requests; nothing else: `pwrpc_decode` lists no other method — GetSoftwareInfo 1/2/2/4 -are the Buds' announcements); on DLCI 0x04 only `SABM`, `08 11 00 00` (one per claim: 1 / 10 / 7 / 19 — the `data.data[0:2]==08:11` count per log 1 / 10 / 12 / 38 +are the Buds' announcements); on DLCI 0x04 only `SABM`, `08 11 00 00` (one per claim: 1 / 10 / 6 / 20 — the `data.data[0:2]==08:11` count per log 1 / 10 / 12 / 38 includes Play's own `Get`s) and the phone's `DISC`/`UA`. **0 `Set` frames** (`bthci_acl.chandle==0x000b && btrfcomm.dlci==4 && frame.p2p_dir==0 && data.data[0:2]==08:12` → 0 / 0 / 0 / 0, `tshark` exit 0 each; positive control `CAP-074-btsnoop_hci.log` → 5). Nothing from the app on DLCI 0x08/0x0a (the Google app's channels, §0). 🟢 FACT: 1.2.1 wrote nothing new to the Buds; `ANC-001`…`ANC-004`'s `Set` path was not exercised in this run (no mode tap). @@ -299,7 +299,7 @@ change, Home, the switch within 2 s of a cut) are untested. Play's timers equal | Step | Refuted if … | Outcome | |---|---|---| -| BK-1 | a crash; a request missing or out of order; Safe Mode | **not refuted**: 0 crashes; the thirteen requests in order in all 8 sessions; `release_5.203` | +| BK-1 | a crash; a request missing or out of order; Safe Mode | **not refuted**: 0 crashes; the fifteen requests (13 `ReadSetting` incl. 4:16, `SubscribeRuntimeInfo`, `GetHardwareInfo`) in order in all 8 sessions; `release_5.203` | | BK-2 | "-dirty"; another hash; the card missing or the other row selected on a fresh update | **not refuted** for set 1 (the fresh update: "Show a hint" selected, s181); set 2's card showed "Take it back" — not a fresh update but the Owner's stored choice from before the film (E2 06:19:04 "re-claim cancelled" proves the mode was set by then) | | BK-5 | an app `SABM` after the cut without a tap (hint only); no hint line; the hint without a time | **not refuted**: 0 app `SABM` between the cuts 06:31:33.02 / 06:32:02.20 and the next user action (claims #16 = the Connect 06:31:55, #19 = the tab re-entry 06:32:33); the hint with "06:31:33" / "06:32:02" | | BK-6 | the hint not cleared by the tap; the old (i) sentence | partly tested: the hint cleared by Refresh and by a Connect (no mode tap in the run); the corrected (i) sentence on film ×2 | @@ -319,7 +319,7 @@ change, Home, the switch within 2 s of a cut) are untested. Play's timers equal | C4 category | Found? | Evidence | |---|---|---| -| Crash | no | `am_crash` 0 / `am_anr` 0 (X, L); both app logcats without an exception; 8 sessions, 49 app claims | +| Crash | no | `am_crash` 0 / `am_anr` 0 (X, L); both app logcats without an exception; 8 sessions, 32 app claims (37 in the four logs incl. the Owner's 2026-10-12 connection in H2L; §1) | | Wrong write | no | 0 `Set` frames; the Connect reads byte-identical to `CAP-073`/`CAP-074`; nothing on other DLCIs | | Safe Mode on the verified firmware | no | `release_5.203` ×3 in both users; no Safe Mode screen | | Lost session | **not by the app**: two Buds-initiated closes with the app in the background (17:28:47, 06:28:49) and the ADR-044 re-open on return; no loss while the app was in use | §7 | diff --git a/captures/CAP-077-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_BM/CAP-077-EVENT-NOTES.md b/captures/CAP-077-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_BM/CAP-077-EVENT-NOTES.md new file mode 100644 index 00000000..676d0cd5 --- /dev/null +++ b/captures/CAP-077-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_BM/CAP-077-EVENT-NOTES.md @@ -0,0 +1,74 @@ +# Event Notes: OpenControl for Pixel Buds Pro 2 on Pixel 9a (GrapheneOS, the secondary user without Google Play services) — Group BM, 1.3.1: the four `CAP-076` / `ai-sessions/0093` fixes, the screen reader and the single mode label (`CAP-077`) + +**Status:** 🔲 **Not yet captured — skeleton only** (written by `ai-sessions/0094`, 2026-10-15). The hardware run of **1.3.1** (versionCode 10301), the fix release +the maintainer chose in chat 2026-10-15 ("1.3.1 met APP-01…06"): "Use different Buds" now acts on the pairing the repository connects with (A93-APP-01) and +asks first (A93-APP-08); a re-open's "Changed since the app last saw it" is kept when the same session's `ReadSetting 4:13` answers the same mode (A93-APP-02, +`CAP-076` §6.2); a tap's `Set` no longer takes the `Get`'s own `Notify` as its answer (A93-APP-03, `CAP-076` §5.3); a settings-stream push no longer ends a +pending read (A93-APP-04); the switch rows and EQ sliders are read with their names by TalkBack (A93-APP-07); one name for each mode on the buttons, the (i), the +tile and the notification (A93-APP-08). **Nothing new on the wire:** the app's requests are the 1.3.0 ones (the `transport.send` call sites are unchanged, +`ai-sessions/0094` RESULT). **One part, ≤ 15 minutes on film with sound**, in the **secondary user (user 10) without Google Play** (`ai-sessions/0093` §7 D4 — no +collision with Play on DLCI 0x04). The maintainer films; a later CAPTURE session analyses it and gives the release verdict (`RELEASING.md` C3/C4). After the run: +rename this folder to the screen recording's first/last phone times and the recording to CAP-077-recording1.mp4. + +**Not in this run** (say so if asked): the user with Google Play (the per-tap collision is unchanged — `CAP-076` §5); a second pair of Buds (A93-APP-01 is +tested with the same pair chosen again in Android's picker); the case-sound experiment; the wear sequences. + +## Log Metadata + +| Field | Value | +|---|---| +| Capture ID | `CAP-077` | +| Group(s) | BM (new) | +| Date | TBD | +| Phone | Pixel 9a, GrapheneOS (Android 17) — **the secondary user (user 10) without Google Play** (`CAP-076` part 2's user) | +| App under test | OpenControl for Pixel Buds Pro 2 **1.3.1** (versionCode 10301), the release APK of `scripts/release.sh 1.3.1` (B1), kept as `~/opencontrol-1.3.1-tested` (B3) — **Info tab on film**: "App: 1.3.1, build ()", no "-dirty" | +| Previous version | 1.3.0 build `ff27ee1` (installed 2026-10-15, `CAP-076`) | +| Official Pixel Buds app | not used | +| Buds | Pixel Buds Pro 2, firmware `release_5.203` expected (any other ⇒ Safe Mode: say so, stop) — both buds **worn** for the hold and tap steps | +| Video files | TBD — the screen recording **with sound** (say each action aloud); no camera | +| Log files | TBD — the HCI snoop log, the app's debug export (Debug mode on), the app logcat, the P0/P1 outputs | + +## Preparation + +| # | Check | Done | +|---|---|---| +| P0 | `adb shell am get-current-user` (10); `adb shell pm list packages --user 10 \| grep -i -E "gms\|vending"; echo "exit=$?"` (nothing, exit 1); positive control `grep opencontrol` (exit 0) | ☐ | +| P1 | In the installed 1.3.0: Debug mode **on** (on film). `adb shell dumpsys package io.github.tedsluis.opencontrolpixelbuds \| grep -E "firstInstallTime\|lastUpdateTime\|versionCode"` → save. `adb install -r` the tested 1.3.1 APK; the same `dumpsys` again (versionCode 10301) | ☐ | +| P2 | HCI snoop log on | ☐ | +| P3 | Android's **screen recorder with sound** on; the status bar across a minute change at the start and the end | ☐ | +| P4 | **Do Not Disturb on**; TalkBack **off** at the start (BM-3 turns it on and off again) | ☐ | +| P5 | Buds charged, both worn; Android shows them connected before the app is opened; **no app use before the film** | ☐ | + +**Rhythm:** one action, then 5–10 s; say what you do. Export the debug log at the end. Serials: never unredacted in any note (first 4 + last 2 characters). + +## Steps + +DLCIs are session-local (MAESTRO 0x02 or 0x03, Message Stream 0x04 or 0x05). Every MAESTRO request starts `7e 00 3b 03 10 13 1d ea 71 de 7d 5e 25` on +**channel 19** or `7e 00 4b 03 10 15 1d ea 71 de 7d 5e 25` on **channel 21** (Info → "Control channel"). **Reference frames** — "H1" = +`CAP-076-btsnoop_hci1.log` (1.3.0 `ff27ee1`, handle `0x000b`), re-derived by `ai-sessions/0094` with `tshark -r CAP-076-btsnoop_hci1.log -Y "frame.number==N" +-T fields -e frame.number -e frame.time -e bthci_acl.chandle -e btrfcomm.dlci -e data.data` (exit 0): + +| Request / event | Reference | +|---|---| +| The Connect's seventeen MAESTRO requests, with `SubscribeToSettingsChanges` and `ReadSetting 4:13` after `SubscribeRuntimeInfo` | `CAP-076` §2 (H1 4129 → 4130 on channel 21, byte-identical to `CAP-051` 1177/1178); unchanged in 1.3.1 | +| **The snapshot `Get`'s answer, then the equal `4:13` answer** (A93-APP-02) | H1 **4094** 06:41:33.418 `0813000401e8e808` (DLCI 0x04, Noise cancellation) → **4135** 06:41:34.165 `7e00a5032a0422026802080110151dea71de7d5e2551aed0aef4aa8ebc7e` (`4:{13:2}`, +747 ms) — in 1.3.0 the export said "Noise-control mode from the mode read (read)"; in 1.3.1 the (i) keeps "Changed since the app last saw it" and the export says "(unchanged)" | +| **A tile tap right after the `Get`'s answer** (A93-APP-03) | `CAP-076` §5.3 (06:46:08.96: no `Set` on the wire, nothing said) — in 1.3.1 every tap ends in a `Set` (`08 12 …`) with its ACK, or in a failure message | + +Anything else from the app on the wire (a request not in this table or in `CAP-076` §2, a claim of DLCI 0x04 that is not a tap, the tile, Refresh, a pull or a +Connect's snapshot, a `WriteSetting 4:{13:n}`) is a finding. + +| Step | Action (say it aloud) | Expected on screen | Expected on the wire | Test-ID | Result | +|---|---|---|---|---|---| +| BM-1 | P1's update; open the app; Info; gear → Settings | "App: 1.3.1, build …"; Settings = Dark mode, Case sounds, Use different Buds | the seventeen requests | `PAIR-003`, `FW-003` | | +| BM-2 | The labels: the ANC tab and its (i); pull the notification shade; the tile; the Sound tab's presets | the (i) "ANC mode: Noise cancellation …" (not "ACTIVE"), "Connection: connected"; the notification "Connected — noise control: …"; the tile's subtitle the same word; the presets "Heavy bass", not "HEAVY BASS" | — | | | +| BM-3 | TalkBack on: Settings → Case sounds (touch each switch row); Controls → a switch row; Sound → the Bass slider; the bottom tabs; TalkBack off | each row read with its name and state ("Earbuds replaced, switch, on"); the slider "Bass, … dB"; each tab named once | — | | | +| BM-4 | Disconnect; two press-and-holds while disconnected (the mode changes); Connect; the ANC (i) | after the Connect: **"Changed since the app last saw it (read at HH:MM:SS) …"** stays (A93-APP-02) | the snapshot `Get`'s `Notify` (new mode), then the `4:13` answer with the same mode, as H1 4094 → 4135; the export "(unchanged)" for the second | `PAIR-003`, `TOUCH-007`, `ANC-001`…`ANC-004` | | +| BM-5 | Right after a Connect (within ≈ 2 s of "Ready"): ten taps on the tile, ≈ 3 s apart | every tap changes the mode or says why not — **none silent** (A93-APP-03) | per tap: `08 11`, its `Notify`, then `08 12 …` and the ACK (or the failure) | `ANC-001`…`ANC-004` | | +| BM-6 | A press-and-hold during a Connect (tap Connect, hold within ≈ 1 s) | the held mode shown | a `4:{13:n}` stream packet before the `4:13` answer is possible; the export's "Noise-control mode read (channel N)" follows the RESPONSE, not the push (A93-APP-04) | `TOUCH-007`, `PAIR-003` | | +| BM-7 | Settings → **Use different Buds** → **Cancel** | the dialog closes; nothing changes | — | | | +| BM-8 | **Use different Buds** → **Forget these Buds**; rotate the phone; Connect | not connected to the Buds by their name (A93-APP-01): the app asks to pair (*Pair a device*) | **no `SABM`** on DLCI 0x02 after the Forget until BM-9 | `PAIR-003` | | +| BM-9 | *Pair a device* → choose the same Buds in Android's picker | connected; the seventeen requests | the Connect's requests | `PAIR-003` | | +| BM-end | Export the debug log; the app logcat; the APK pull | "Debug log saved (N lines)." | — | | | + +--- +https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/captures/CAP-077-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_BM/CAP-077-EVENT-NOTES.md - https://tedsluis.github.io/opencontrolpixelbudspro2/captures/CAP-077-yyyy-MM-dd_HH-mm-ss_HH-mm-ss-Group_BM/CAP-077-EVENT-NOTES diff --git a/id_registry.csv b/id_registry.csv index 01d37f3f..5d172457 100644 --- a/id_registry.csv +++ b/id_registry.csv @@ -242,5 +242,6 @@ CAP-073,capture,analyzed,"2026-10-10 08:23:35-08:33:24, Group BI (new) — the r CAP-074,capture,analyzed,"2026-10-12 06:40:16-06:45:31, Group BJ (new) — the released 1.2.0 (5b4d5db, versionCode 10200; Info tab on film; APK not pulled) in the GrapheneOS Owner user with Google Play services: one screen recording with audio (as is), two HCI logs, one debug export, the app logcat, a system-log extract; the maintainer's ad-hoc run (nine Disconnect/Connect pairs, the ANC tab, press-and-holds). 10 sessions byte-identical to CAP-073; every ADR-061 hold cut 4.7-5.1 s after its SABM by Play services' Fast Pair recovery (5.2-5.7 s after it lost the channel, 20 of 20) — 9 holds shown, 35 reached Play services; the root cause of the first-hold-only pattern; fix: a Settings switch (hint only / re-claim with back-off), the maintainer's choice (ai-sessions/0086)",CAPTURE_BLUETOOTH_HCI_SNOOP.md §9 CAP-075,capture,analyzed,"2026-10-12 17:22:31 – 2026-10-13 06:34:32 (two sets), Group BK (new) — 1.2.1 (31ee183, versionCode 10201; Info tab on film in both users; APK not pulled), the Noise-control channel switch (ADR-062): set 1 the secondary user without Google Play (41 of 41 press-and-holds shown, both positions identical, no foreign close), set 2 the Owner with Google Play (hint only: the hint with the cut time, 0 of 9 holds shown; take it back: three chains of exactly three re-claims at 3/6/12 s, every re-claim colliding with Play's retry 2.1–2.8 s after the cut, every hold cut 5.3–5.8 s after Play's loss, 43 % coverage then nothing, 8 of 19 holds shown; the stack path of the cut observed 15 of 15 in the system log); verdict: no heavy issue, 1.2.1 released as built; analysed by ai-sessions/0088",CAPTURE_BLUETOOTH_HCI_SNOOP.md §9 CAP-076,capture,analyzed,"2026-10-15 06:35:26 – 07:08:20 (two parts), Group BL — 1.3.0 (ff27ee1, versionCode 10300; installed over 1.2.2 bfd7891 on film 1; the pulled APK = the tested build, SHA-256 03272c6f…a96c), the settings stream on the app's own channel (ADR-064/065): part 1 the Owner with Google Play, part 2 the secondary user without; one SubscribeToSettingsChanges and one ReadSetting 4:13 per MAESTRO session, byte-identical to CAP-051 1177/1178 and CAP-056 1451/1450 (12 of 12 full sessions); press-and-holds on the noise-control tab shown within 0.1 s, 29 of 29 with Play holding DLCI 0x04 and 12 of 12 without; 15 own Sets ACKed and classed as the app's; nothing held or re-claimed (33 app claims, all taps/tile/Refresh/Find/Connect snapshots); every change pushed twice (channels 21 and 19) with both buds out (🟡 one per bud core); the re-open's ""changed since"" cause overwritten by the equal 4:13 answer; a tile tap cut after the Get's answer silent; per-tap collisions with Play measured (8 of 12 claims at attempt 2, 4 cut); analyzed by ai-sessions/0092 — verdict release as built with two Known issues (the maintainer, chat 2026-10-15)",CAPTURE_BLUETOOTH_HCI_SNOOP.md §9 +CAP-077,capture,planned,"planned — Group BM (new): the hardware run of 1.3.1 (versionCode 10301) in the secondary user without Google Play — the kept ""changed since"" cause across the equal ReadSetting 4:13 answer, ten tile taps right after a Connect (none silent), a hold during a Connect, Use different Buds with its dialog (no connection by name, then Pair a device with the same Buds), TalkBack on the switch rows and an EQ slider, one name per mode; nothing new on the wire; skeleton by ai-sessions/0094",CAPTURE_BLUETOOTH_HCI_SNOOP.md §9 INEAR-006,test,catalogued,"The app's 'probably worn' indicator (ADR-059) read against the ears on film: both out, one in, both in the case, in-ear detection off, buds straight from the case to the table watched 60 s — added 2026-10-09, ai-sessions/0082",TESTPLAN_BLUETOOTH_HCI_SNOOP.md WELL-002,test,catalogued,"maestro_pw.Dosimeter SubscribeToLiveDb values correlated with a sound-level meter on film (the 🟡 that blocks ADR-060) — candidate, no capture scenario yet (added 2026-10-09, ai-sessions/0082)",TESTPLAN_BLUETOOTH_HCI_SNOOP.md diff --git a/reverse-engineering/APK_VERSIONS.md b/reverse-engineering/APK_VERSIONS.md index 02af5c3c..f4be9627 100644 --- a/reverse-engineering/APK_VERSIONS.md +++ b/reverse-engineering/APK_VERSIONS.md @@ -31,6 +31,9 @@ the same version subfolder — record each file's own SHA-256 in the table below | `v1.0.955078536-10253511/` | `base.apk` | `b12d75d07f7743b83ed0d507a9d6b7b2abf61d8f1e7c3e63aadc442c097d6c86` | `1.0.955078536` | `10253511` | 2026-08-30 | Pixel 7a (`lynx`, serial `38021JEHN07835`), maintainer's own device | Play Store, maintainer's own Google account, installed on the maintainer's own device | `1.5.1` | `3.0.3` | `pbtk 1.1.3` (pipx) | | `v1.0.955078536-10253511/` | `split_config.arm64_v8a.apk` | `ae63526672528d4fbed3086c2cd8280966bdd9ce9e764d2f00456f5b7698f3ba` | `1.0.955078536` | `10253511` | 2026-08-30 | Pixel 7a (`lynx`, serial `38021JEHN07835`), maintainer's own device | Play Store, maintainer's own Google account, installed on the maintainer's own device | `1.5.1` | `3.0.3` | `pbtk 1.1.3` (pipx) | | `v1.0.955078536-10253511/` | `split_config.xxhdpi.apk` | `a759ef5bdf17aa431fa58f64c27ea5e2801b114b7436ca785834012126286c46` | `1.0.955078536` | `10253511` | 2026-08-30 | Pixel 7a (`lynx`, serial `38021JEHN07835`), maintainer's own device | Play Store, maintainer's own Google account, installed on the maintainer's own device | `1.5.1` | `3.0.3` | `pbtk 1.1.3` (pipx) | +| `v1.0.990706425-10260911/` | `base.apk` | `16b7e8f77e6e9a473f31b7636b5b9ee1021f921e439fe48d1491d0a6b64b3aa5` | `1.0.990706425` | `10260911` | 2026-10-05 | Pixel 7a (`lynx`), maintainer's own device | Play Store, maintainer's own Google account, installed on the maintainer's own device (`adb pull` of base + 2 splits, same signing certificate as 1.0.955078536) | — (not decompiled) | — | — | +| `v1.0.990706425-10260911/` | `split_config.arm64_v8a.apk` | `4b493b23a6cafbb3de9d492b122815974c69e7ab6a8763ed1764a86c6ee80d5f` | `1.0.990706425` | `10260911` | 2026-10-05 | Pixel 7a (`lynx`), maintainer's own device | Play Store, maintainer's own Google account, installed on the maintainer's own device (`adb pull` of base + 2 splits, same signing certificate as 1.0.955078536) | — (not decompiled) | — | — | +| `v1.0.990706425-10260911/` | `split_config.xxhdpi.apk` | `f0d065232de7eefc05ae2315983762b2f94a4e7098fa51289f54f63fce9b4952` | `1.0.990706425` | `10260911` | 2026-10-05 | Pixel 7a (`lynx`), maintainer's own device | Play Store, maintainer's own Google account, installed on the maintainer's own device (`adb pull` of base + 2 splits, same signing certificate as 1.0.955078536) | — (not decompiled) | — | — | minSdk=32, targetSdk=36 (from `dumpsys package`, recorded here for reference; not part of the table's own columns). @@ -48,6 +51,9 @@ not finished — further passes remain valuable (see `TODO.md` Phase 2). **Used in a capture, not pulled (2026-10-04, `ai-sessions/0071`, maintainer-approved in chat, `AskUserQuestion` "New APK"):** the official app **1.0.990706425** (Play Store "Updated on Sep 30, 2026", on film) ran `CAP-069`. It has no "Find device" screen, calls `maestro_pw.JitterBuffer` and an unnamed MAESTRO service `0xbf6c9399` on the wire (`CAP-069-FINDINGS.md` §9). Not pulled or decompiled; a follow-up session is planned (`TODO.md` §4). +*(Update 2026-10-15, `ai-sessions/0094`, maintainer-approved in chat — `ai-sessions/0093` A93-HK-03, D8: it **was** pulled on 2026-10-05 into +`reverse-engineering/apk/v1.0.990706425-10260911/` and is now registered in the table above — `sha256sum *.apk` and `aapt2 dump badging base.apk` +(`versionCode='10260911' versionName='1.0.990706425'`); not decompiled — that is a later CROSSCHECK session, `TODO.md` §4.)* ## Why provenance matters here diff --git a/reverse-engineering/tools/BACKLOG.md b/reverse-engineering/tools/BACKLOG.md index 88a44a6c..308e02e2 100644 --- a/reverse-engineering/tools/BACKLOG.md +++ b/reverse-engineering/tools/BACKLOG.md @@ -258,7 +258,5 @@ with every build, so the diff must key on what survives R8: Output: JSON per step, names and shapes only; the governance block at the top of this file applies. ---- - --- https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/reverse-engineering/tools/BACKLOG.md - https://tedsluis.github.io/opencontrolpixelbudspro2/reverse-engineering/tools/BACKLOG diff --git a/scripts/lint_docs.py b/scripts/lint_docs.py index 6d5185fb..99ea756f 100644 --- a/scripts/lint_docs.py +++ b/scripts/lint_docs.py @@ -409,7 +409,7 @@ def check_table_cells(files: list[Path]) -> list[str]: return errors -CAPTURE_INDEX_STATUSES = {"planned", "captured", "analyzed", "promoted", "discarded", "withdrawn"} +CAPTURE_INDEX_STATUSES = {"planned", "captured", "analyzed", "discarded", "withdrawn"} # "promoted" retired 2026-10-15 (ai-sessions/0094) REGISTRY_STATUSES = { "capture": {"planned", "captured", "analyzed", "promoted", "discarded", "withdrawn"}, "adr": {"accepted", "superseded", "proposed"}, @@ -448,6 +448,61 @@ def check_status_values() -> list[str]: return errors +ADR_STATUS_LINE_RE = re.compile(r"^## (ADR-\d{3})\b.*?^- \*\*Status\*\*:?\s*\**([A-Za-z]+)", re.MULTILINE | re.DOTALL) +INDEX_ROW_RE = re.compile(r"^\| (\d{4}) \| [A-Z]+ \|.*\| ([^|]*?) \|\s*$", re.MULTILINE) +INDEX_STATUSES = SESSION_STATUSES + ("planned",) + + +def check_governance() -> list[str]: + """(h)–(l), added 2026-10-15 (ai-sessions/0094, maintainer-approved in chat; ai-sessions/0093 A93-HK-01/05, A93-GOV-07, + A93-DEC-03, A93-SES-07): the INDEX status cells, finished items left in TODO.md, the README's ADR counts, the first word of + every ADR Status line, and an unfilled "(this commit)" row in a session record older than the newest session.""" + errors = [] + with (REPO_ROOT / "id_registry.csv").open(newline="", encoding="utf-8") as f: + adr_status = {row["id"]: row["status_or_date"] for row in csv.DictReader(f) if row["type"] == "adr"} + sessions = sorted(p.name for p in (REPO_ROOT / "ai-sessions").glob("[0-9][0-9][0-9][0-9]_*.md")) + newest = sessions[-1][:4] if sessions else None + # (h) INDEX: the status cell starts with a legend value; a session with a RESULT agrees with the RESULT's first value. + index = REPO_ROOT / "ai-sessions" / "INDEX.md" + for m in INDEX_ROW_RE.finditer(index.read_text(encoding="utf-8")): + num, cell = m.group(1), m.group(2).strip() + if not cell.startswith(INDEX_STATUSES): + errors.append(f"ai-sessions/INDEX.md: {num}: status \"{cell[:40]}\" is not one of: " + " | ".join(INDEX_STATUSES)) + continue + results = list((REPO_ROOT / "ai-sessions").glob(f"{num}_*_RESULT_*.md")) + if results and num != newest: + rm = SESSION_STATUS_RE.search(results[0].read_text(encoding="utf-8", errors="replace")) + if rm: + first = next((v for v in SESSION_STATUSES if rm.group(1).startswith(v)), None) + if first and not cell.startswith(first): + errors.append(f"ai-sessions/INDEX.md: {num}: status cell does not start with its RESULT's \"{first}\"") + # (i) TODO.md holds open items only. + for n, line in enumerate((REPO_ROOT / "TODO.md").read_text(encoding="utf-8").splitlines(), 1): + if re.match(r"\s*- \[x\]", line): + errors.append(f"TODO.md:{n}: a finished item ([x]) — delete it; the work is recorded where it was done") + # (j) README's ADR counts against the registry. + rm = re.search(r"(\d+) accepted, (\d+) superseded", (REPO_ROOT / "README.md").read_text(encoding="utf-8")) + if rm: + acc = sum(1 for v in adr_status.values() if v == "accepted") + sup = sum(1 for v in adr_status.values() if v == "superseded") + if (int(rm.group(1)), int(rm.group(2))) != (acc, sup): + errors.append(f"README.md: \"{rm.group(0)}\" but id_registry.csv has {acc} accepted, {sup} superseded") + # (k) the first word of each ADR's Status line matches its registry status. + for m in ADR_STATUS_LINE_RE.finditer((REPO_ROOT / "DECISIONS.md").read_text(encoding="utf-8")): + adr, word = m.group(1), m.group(2).lower() + want = adr_status.get(adr) + if want and word != want: + errors.append(f"DECISIONS.md: {adr} Status starts with \"{m.group(2)}\" but id_registry.csv says \"{want}\"") + # (l) an unfilled "(this commit)" row in a session record older than the newest session. + for path in sorted((REPO_ROOT / "ai-sessions").glob("[0-9][0-9][0-9][0-9]_*_RESULT_*.md")): + if path.name[:4] == newest: + continue + for n, line in enumerate(path.read_text(encoding="utf-8", errors="replace").splitlines(), 1): + if line.startswith("| (this commit) |"): + errors.append(f"ai-sessions/{path.name}:{n}: \"(this commit)\" not back-filled (AI_SESSION_LOG_PROCEDURE.md §4b)") + return errors + + def main() -> int: known_ids = load_registry() files = all_markdown_files() @@ -469,7 +524,7 @@ def is_history(err: str) -> bool: all_table_problems = check_table_cells(files) table_notes = [e for e in all_table_problems if is_history(e)] table_errors = [e for e in all_table_problems if not is_history(e)] - status_errors = check_status_values() + status_errors = check_status_values() + check_governance() if filename_errors: print("=== Dead filename references ===")