diff --git a/AGENTS.md b/AGENTS.md index 641fdf9..52acb7d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -129,6 +129,10 @@ Keys: - `autoBlackoutOnExternalConnect` (BOOL, default YES) - `blackoutActive` (BOOL) — persisted blackout intent - `verbosityLevel` (Integer, default 1; 2 enables `[verbose=2]`-tagged log lines) +- `recoveryStrategy` (String, default `displaysleep`; `none` disables the + post-wake cursor-on-black auto-recovery — set via + `blackoutd recovery `; see P20/P29 and + `docs/debug/cursor-on-black-matrix.md`) ## Key technical details @@ -341,8 +345,10 @@ For non-trivial work, an agent should also read: 1. `docs/technical-debt.md` — current priorities and known issues 2. `ROADMAP.md` — milestone plan (cross-references the P-numbers above) -3. `docs/decisions/` — accepted ADRs (4 today: Trojan Source, daemon - presence, wake-settle timer, merge strategy) +3. `docs/decisions/` — ADRs 0001–0010 (0009 is `proposed`, the rest + accepted). For display work start with 0003 wake-settle timer and + 0010 cursor-on-black detection/recovery; 0009 is the SP2309W color + quirk (repo-foundation has its own, unrelated 0009 on logging) 4. `CONTRIBUTING.md` — encoding policy, REUSE/SPDX conventions, commit and PR conventions diff --git a/docs/architecture.md b/docs/architecture.md index ada66de..efd73a9 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -110,6 +110,18 @@ fires reconfiguration callbacks as side effects. A 2-second window connect/disconnect events. A safety check at window close catches any real events that arrived during the window. +**Detector-gated wake recovery** — Some wakes leave the external display +black (cursor only) with every CG/WindowServer-visible field healthy — a +DCP/scanout stall below WindowServer. The one host-visible signal is a +WindowServer log line (SkyLight coalescing a pending hotplug "out" with +the wake's "in"; 56/56 black vs 0/33 clean across the empirical matrix). +At wake-settle, with an external present, the daemon queries the unified +log for that marker and, when present, runs a display-sleep cycle +(`pmset displaysleepnow` + `caffeinate -u`) — the recovery validated +locked and unlocked. Gated by the `recoveryStrategy` preference +(`blackoutd recovery `). See ADR 0010 and +technical-debt P20/P29. + ### Companion Project [displayrecommitd](https://github.com/toobuntu/displayrecommitd/) is a diff --git a/docs/debug/cursor-on-black-matrix.md b/docs/debug/cursor-on-black-matrix.md index 456b31b..0548648 100644 --- a/docs/debug/cursor-on-black-matrix.md +++ b/docs/debug/cursor-on-black-matrix.md @@ -33,17 +33,41 @@ time; their bundles' `version.txt` is authoritative). From a terminal with the panels visible (before sleep): ```sh -sudo --validate # primes sudo so the schedule-wake step never prompts - # (repro falls back to one pre-sleep prompt if it must) - # Baseline (detection): wake, capture, no recovery. Group A of the # coverage table below. ./build/blackoutd repro --wake 15 --group A # With recovery: wake, capture, display-sleep cycle, capture again. ./build/blackoutd repro --wake 15 --recover displaysleep --group B + +# Locked (group C): --lock issues the ctrl-cmd-q lock keystroke just +# before sleep (keep the Apple Watch off/out of range). +./build/blackoutd repro --wake 15 --recover displaysleep --group C --lock ``` +`repro` primes sudo itself (`sudo --validate` as its first step — one +interactive prompt at most, pre-sleep; builds before 2026-07-19 +evening needed a manual `sudo --validate &&` prefix and a separate +osascript lock command). + +> **Daemon auto-recovery vs. data collection.** As of 2026-07-20 the +> daemon itself runs the displaysleep recovery at wake-settle when the +> WindowServer marker is present (`recoveryStrategy` pref, default +> `displaysleep`). **Before collecting matrix data, run +> `blackoutd recovery none`** — otherwise the daemon clears the black +> a few seconds after wake and the post-wake capture and eyewitness +> "settled" states record the recovered panel, not the failure. +> Restore with `blackoutd recovery displaysleep` afterward. + +To emulate the C1 cable trigger in software (no cable handling), add +`--trigger extcycle`: after scheduling the wake and before sleeping, +`repro` disables the external display via CG, waits 5 s for the +built-in to restore and redraw (the daemon's safety invariant does the +restore), re-enables it, and waits 5 s for the re-attach — then sleeps +as usual. The sheet's `trigger` field records it, so soft-trigger runs +are machine-distinguished from physical `C1` runs (which stay a Notes +code). + `repro` schedules the wake (the only step that needs `sudo`), sleeps, and on wake **speaks each step** (`say`) so you can follow it with the screen black. Each step also posts a Notification Center banner stamped `HH:mm:ss` — @@ -51,6 +75,11 @@ invisible during the black, but retained, so after recovery the notification log shows when each stage ran and pairs with the bundles' timestamps. Watch the **panels**, not the terminal. When you hear: +- **"awake"** — spoken within ~1 s of the wake instant (repro detects + the wake rather than sleeping a fixed interval). Anchor the + *immediate* observation to this cue; the settle countdown also + starts here, so the capture is a consistent `--settle` seconds + post-wake. - **"capturing post wake"** — look at both panels, fill the *post-wake* rows. - **"recovering"** then **"capturing post recover"** — look again, fill the *post-recover* rows. @@ -129,6 +158,37 @@ open B2/E2 and settle to B0/E1 within seconds): - `B1` — lit, cursor-on-black (role reversal — wrongly active) - `B2` — lit, showing desktop/content (blackout not holding) +**Notes short codes** (used in the sheets' Notes field from run 46 on): + +- `R0` — no recovery needed (the run settled clean; the group B/C + displaysleep cycle ran anyway but had nothing to clear) +- `M1` — Music.app playing during the run (`M0` — explicitly not + playing) +- `C1` — pre-run cable trigger: the external's **USB-C end** of its + USB-C → HDMI cable was unplugged from the MacBook and replugged + *before* the run, leaving it unplugged until the built-in fully + redrew. Mostly (not completely) reliably provokes cursor-on-black + on the following wake — the first on-demand repro lever (found run + 49; missed on run 55). +- `W1` — scheduled wake failed; the maintainer woke the machine + manually (Space bar or trackpad press). Applies to every + `--trigger` run 61–80: a repro bug scheduled the wake *before* the + trigger, whose ~15 s of cueing and settling left the wake time in + the past by the time the machine slept. Fixed the same evening + (trigger now runs first; the wake time is computed after it). The + W1 runs stay valid — they additionally show the black reproduces + on manual wakes, so the failure is not scheduled-wake-specific. + +> **Built-in immediate-state caveat (2026-07-19).** The built-in +> panel is small and glare-prone, and a cursor is easy to miss — it +> is possible that all role reversals are *full* inversions +> (immediate `E2/B1`) and that some earlier records under-report the +> built-in as `B0`/`B2` because the cursor went unseen. From run 54 +> the maintainer pre-positions the cursor near the top of the screen +> and observed the full `E2/B1` inversion directly on runs 54, 56, +> and 57. Treat pre-54 *immediate built-in* states as approximate; +> settled states and Outcome lines are unaffected. + ## Per-run record `repro` generates this block prefilled (the `` fields) in the run @@ -146,7 +206,8 @@ Conditions (pre-sleep -> at capture): session ................ locked|unlocked> power .................. battery|AC> wake ................... - recovery applied ....... + trigger ................ + recovery applied ....... settle ................. daemon settled ......... @@ -239,6 +300,264 @@ current build (spoken cues, at-the-moment prompts, boundary-timed condition reads, single-sample settle marker). **Cohort 2 starts at run 011 and owes its own ≥3 black / ≥3 clean settled captures before analysis begins.** + +### Group A — cohort 2 (runs 011+, v0.4.0 build) + +Collected 2026-07-19 on the merged v0.4.0 build with the full +workflow. The cohort satisfies the capture gate: 3 on-condition black +(runs 14, 22, 24) and 9 clean settled captures. + +| run | sheet | immediate E/B | settled E/B | black? | +|-----|--------------------------------------------------------|---------------|-------------|--------| +| 11 | [run011](repro-matrix/run011-grp-a-20260719-000924.md) | E1/B0 | E1/B0 | yes¹ | +| 12 | [run012](repro-matrix/run012-grp-a-20260719-082315.md) | E2/B2 | E0/B0 | no | +| 13 | [run013](repro-matrix/run013-grp-a-20260719-110044.md) | E2/B2 | E0/B0 | no | +| 14 | [run014](repro-matrix/run014-grp-a-20260719-110205.md) | E2/B1 | E1/B0 | yes | +| 15 | [run015](repro-matrix/run015-grp-a-20260719-110325.md) | E2/B2 | E0/B0 | no | +| 16 | [run016](repro-matrix/run016-grp-a-20260719-110439.md) | E2/B2 | E0/B0 | no | +| 17 | [run017](repro-matrix/run017-grp-a-20260719-120552.md) | E2/B1 | E2/B0 | no² | +| 18 | [run018](repro-matrix/run018-grp-a-20260719-120742.md) | E2/B2 | E0/B0 | no | +| 19 | [run019](repro-matrix/run019-grp-a-20260719-120843.md) | *disregarded* | *disregarded* | — | +| 20 | [run020](repro-matrix/run020-grp-a-20260719-120955.md) | E2/B2 | E0/B0 | no | +| 21 | [run021](repro-matrix/run021-grp-a-20260719-121755.md) | E2/B2 | E0/B0 | no | +| 22 | [run022](repro-matrix/run022-grp-a-20260719-121916.md) | E2/B1 | E1/B0 | yes | +| 23 | [run023](repro-matrix/run023-grp-a-20260719-122131.md) | E2/B2 | E0/B0 | no | +| 24 | [run024](repro-matrix/run024-grp-a-20260719-122252.md) | E2/B1 | E1/B0 | yes | +| 36 | [run036](repro-matrix/run036-grp-a-20260719-141127.md) | E2/B2 | E0/B0 | no | + +¹ Run 11 ran on AC (group A specifies battery): a black capture kept +as corroboration, off-condition for the matched diff. +² Run 17 settled **E2** (black, no cursor) — a black panel that the +E1-keyed Outcome line scores "no". Out of both pools for the matched +diff; corroboration for the detector (its bundle sides with the +blacks). +Run 19 is disregarded (maintainer: possibly misreported); its window +is excluded from every tally. + +### Group B (runs 025+) + +| run | sheet | settled E/B | black? | recovery cleared? | +|-----|--------------------------------------------------------|-------------|--------|-------------------| +| 25 | [run025](repro-matrix/run025-grp-b-20260719-122426.md) | E0/B0 | no | n/a (was clean) | +| 26 | [run026](repro-matrix/run026-grp-b-20260719-122726.md) | E0/B0 | no | n/a (was clean) | +| 27 | [run027](repro-matrix/run027-grp-b-20260719-122934.md) | E0/B0 | no | n/a (was clean) | +| 28 | [run028](repro-matrix/run028-grp-b-20260719-131053.md) | E0/B0 | no | n/a (was clean) | +| 29 | [run029](repro-matrix/run029-grp-b-20260719-131252.md) | E0/B0 | no | n/a (was clean) | +| 30 | [run030](repro-matrix/run030-grp-b-20260719-131452.md) | E0/B0 | no | n/a (was clean) | +| 31 | [run031](repro-matrix/run031-grp-b-20260719-131825.md) | E0/B0 | no | n/a (was clean) | +| 32 | [run032](repro-matrix/run032-grp-b-20260719-132525.md) | E0/B0 | no | n/a (was clean) | +| 33 | [run033](repro-matrix/run033-grp-b-20260719-132720.md) | E0/B0 | no | n/a (was clean) | +| 34 | [run034](repro-matrix/run034-grp-b-20260719-132947.md) | E0/B0 | no | n/a (was clean) | +| 35 | [run035](repro-matrix/run035-grp-b-20260719-140116.md) | E0/B0 | no | n/a (was clean) | +| 37 | [run037](repro-matrix/run037-grp-b-20260719-141825.md) | E1/B0 | yes | **yes** | +| 38 | [run038](repro-matrix/run038-grp-b-20260719-142134.md) | E0/B0 | no | n/a (was clean) | +| 39 | [run039](repro-matrix/run039-grp-b-20260719-142353.md) | E0/B0 | no | n/a (was clean) | +| 40 | [run040](repro-matrix/run040-grp-b-20260719-142621.md) | E0/B0 | no | n/a (was clean) | +| 41 | [run041](repro-matrix/run041-grp-b-20260719-145713.md) | E0/B0 | no | n/a (R0) | +| 42 | [run042](repro-matrix/run042-grp-b-20260719-150121.md) | E0/B0 | no | n/a (R0) | +| 43 | [run043](repro-matrix/run043-grp-b-20260719-150356.md) | E0/B0 | no | n/a (R0) | +| 44 | [run044](repro-matrix/run044-grp-b-20260719-150606.md) | E0/B0 | no | n/a (R0) | +| 45 | [run045](repro-matrix/run045-grp-b-20260719-150805.md) | E0/B0 | no | n/a (R0) | +| 46 | [run046](repro-matrix/run046-grp-b-20260719-151024.md) | E0/B0 | no | n/a (R0) | +| 47 | [run047](repro-matrix/run047-grp-b-20260719-151223.md) | E0/B0 | no | n/a (R0) | +| 48 | [run048](repro-matrix/run048-grp-b-20260719-151427.md) | E0/B0 | no | n/a (R0) | +| 49 | [run049](repro-matrix/run049-grp-b-20260719-151715.md) | E1/B0 | yes | **yes** | +| 50 | [run050](repro-matrix/run050-grp-b-20260719-152024.md) | E0/B0 | no | n/a (R0) | +| 51 | [run051](repro-matrix/run051-grp-b-20260719-152300.md) | E1/B0 | yes | **yes** | +| 52 | [run052](repro-matrix/run052-grp-b-20260719-152718.md) | E1/B0 | yes | **yes** | +| 53 | [run053](repro-matrix/run053-grp-b-20260719-153031.md) | E1/B0 | yes | **yes** | +| 54 | [run054](repro-matrix/run054-grp-b-20260719-153227.md) | E1/B0 | yes | **yes** | +| 55 | [run055](repro-matrix/run055-grp-b-20260719-153508.md) | E0/B0 | no | n/a (R0) | +| 56 | [run056](repro-matrix/run056-grp-b-20260719-153711.md) | E1/B0 | yes | **yes** | +| 57 | [run057](repro-matrix/run057-grp-b-20260719-153916.md) | E1/B0 | yes | **yes** | + +Codes on the extension runs (see legend): `M1` on runs 37–57; `C1` on +runs 49 and 51–57 (49 is where the cable trigger was discovered — its +sheet describes the unplug/replug in prose; run 55 is the C1 miss — +cable trigger applied, wake settled clean). Run 47 ran on AC; run 48's +lid was closed during sleep (no visible or audible wake until the +clamshell reopened). + +Group B verdict: 8 blacks (runs 37, 49, 51–54, 56–57), and the +`displaysleep` recovery cleared **every one** end-to-end (post-recover +E0/B0) — well past the n ≥ 2 consistency bar; cohort 1's four manual +clears corroborate. The group's question is answered: yes, the +display-sleep cycle clears it. + +### Group B — soft-trigger series (runs 061–080) + +All twenty runs used `--trigger extcycle` (the software C1 analog) and +carry `W1` (see the legend; wake was manual — Space bar or trackpad). +Runs 61–68 recovered with `displaysleep`, 69–80 with `extcycle`. + +| run | sheet | recovery | settled E/B | black? | cleared? | +|-----|--------------------------------------------------------|--------------|-------------|--------|----------| +| 61 | [run061](repro-matrix/run061-grp-b-20260719-202401.md) | displaysleep | E1/B0 | yes | yes | +| 62 | [run062](repro-matrix/run062-grp-b-20260719-202947.md) | displaysleep | E1/B0 | yes | yes | +| 63 | [run063](repro-matrix/run063-grp-b-20260719-203423.md) | displaysleep | E1/B0 | yes | yes | +| 64 | [run064](repro-matrix/run064-grp-b-20260719-203715.md) | displaysleep | E1/B0 | yes | yes | +| 65 | [run065](repro-matrix/run065-grp-b-20260719-203953.md) | displaysleep | E1/B0 | yes | yes | +| 66 | [run066](repro-matrix/run066-grp-b-20260719-204340.md) | displaysleep | E1/B0 | yes | yes | +| 67 | [run067](repro-matrix/run067-grp-b-20260719-204849.md) | displaysleep | E1/B0 | yes | yes | +| 68 | [run068](repro-matrix/run068-grp-b-20260719-205256.md) | displaysleep | E1/B0 | yes | yes | +| 69 | [run069](repro-matrix/run069-grp-b-20260719-205745.md) | extcycle | E1/B0 | yes | yes | +| 70 | [run070](repro-matrix/run070-grp-b-20260719-210158.md) | extcycle | E1/B0 | yes¹ | yes | +| 71 | [run071](repro-matrix/run071-grp-b-20260719-210655.md) | extcycle | E0/B0 | no | n/a | +| 72 | [run072](repro-matrix/run072-grp-b-20260719-211053.md) | extcycle | E0/B0 | no | n/a | +| 73 | [run073](repro-matrix/run073-grp-b-20260719-211335.md) | extcycle | E1/B0 | yes² | yes | +| 74 | [run074](repro-matrix/run074-grp-b-20260719-211608.md) | extcycle | E1/B0 | yes | yes | +| 75 | [run075](repro-matrix/run075-grp-b-20260719-212006.md) | extcycle | E1/B0 | yes² | yes | +| 76 | [run076](repro-matrix/run076-grp-b-20260719-212233.md) | extcycle | E1/B0 | yes | yes | +| 77 | [run077](repro-matrix/run077-grp-b-20260719-212714.md) | extcycle | E1/B0 | yes | yes | +| 78 | [run078](repro-matrix/run078-grp-b-20260719-212937.md) | extcycle | E1/B0 | yes | yes | +| 79 | [run079](repro-matrix/run079-grp-b-20260719-213221.md) | extcycle | E1/B0 | yes | yes | +| 80 | [run080](repro-matrix/run080-grp-b-20260719-214926.md) | extcycle | E1/B0 | yes | yes | + +¹ Run 70: the maintainer pressed the Space bar just as the "capturing +post wake" cue played (the press was already in motion) — treat the +settled record as possibly contaminated; the session was also locked +by capture time. +² Runs 73 and 75: the maintainer flags the *immediate* observation as +possibly incomplete (run 73 likely missed a built-in cursor-on-black +preceding the external's). Runs 76, 77, and 80 were carefully +observed; run 76's sequence was E2/B1 → brief E1/B1 → E1/B0 → +recovery to E0/B0. + +**Soft-trigger verdict**: `--trigger extcycle` provoked cursor-on-black +in **18 of 20** runs (misses 71, 72) — the repro is now fully +software-driven; no cable handling, no physical HPD event. `M1` on +none of 61–80 (run 61 notes `M0`). Codes: `W1` on all twenty. + +**extcycle-as-recovery verdict (split by lock state)**: while +**unlocked**, extcycle cleared 10/10 blacks (69–70, 73–80). While +**locked** (group C runs 81–85 below), it failed on both blacks and +*induced* a black external (or both panels black) even on clean wakes. +`displaysleep` remains the recovery of record; extcycle is usable only +in an unlocked session. + +### Group C (runs 058+) — recovery while locked + +Working invocation (run 59 first; lock the session **before** +starting `repro` — run 58 chained the `osascript` *after* `repro`, so +the lock never took effect and the run executed unlocked): + +```sh +sudo --validate && \ + osascript -l JavaScript -e 'Application("System Events").keystroke("q", {using:["control down","command down"]})' && \ + ./build/blackoutd repro --wake 15 --recover displaysleep --group C +``` + +(`ctrl-cmd-q` locks the screen; needs Accessibility permission for the +terminal app. Keep the Apple Watch off/out of range per the LOCKED +note above.) + +| run | sheet | session | settled E/B | black? | recovery cleared? | +|-----|--------------------------------------------------------|---------|-------------|--------|-------------------| +| 58 | [run058](repro-matrix/run058-grp-c-20260719-154916.md) | unlocked¹ | E1/B0 | yes | **yes** | +| 59 | [run059](repro-matrix/run059-grp-c-20260719-155209.md) | locked | E0/B0 | no | n/a (R0) | +| 60 | [run060](repro-matrix/run060-grp-c-20260719-155510.md) | locked | E1/B0 | yes | **yes** | + +¹ Run 58's lock keystroke ran after `repro` (see above); the sheet's +machine-read session field correctly records unlocked → unlocked, so +it is effectively a group B data point. Codes: `M1` on 58–60; `C1` on +60 — and **possibly on 58**: its Notes do not record C1, but the +maintainer later recalled the cable trigger may have been applied +there too, so do not cite run 58 as a trigger-free natural repro. Run +60 is the first **locked** black: the displaysleep recovery cleared +it while the session stayed locked (n=1 for displaysleep-locked). + +### Group C — extcycle recovery while locked (runs 081–085) + +Same locked invocation (osascript lock, then repro), `--recover +extcycle`, no trigger, Apple Watch off. These runs answer a different +question than planned: extcycle **must not be used while locked**. + +| run | sheet | settled E/B | black? | recovery outcome | +|-----|--------------------------------------------------------|-------------|--------|----------------------------------------------------| +| 81 | [run081](repro-matrix/run081-grp-c-20260719-215853.md) | E1/B0 | yes | cleared, then **both panels black**¹ | +| 82 | [run082](repro-matrix/run082-grp-c-20260719-220650.md) | E0/B0 | no | n/a (R0) | +| 83 | [run083](repro-matrix/run083-grp-c-20260719-220914.md) | E1/B0 | yes | **both panels black**; manual Space + unlock fixed | +| 84 | [run084](repro-matrix/run084-grp-c-20260719-221345.md) | E0/B0 | no | **induced** black external; left it CG-offline² | +| 85 | [run085](repro-matrix/run085-grp-c-20260719-222052.md) | E0/B0 | no | **induced** transient both-black; Space restored | + +¹ Run 81: after the both-black state, Space restored the built-in but +the external stayed black; four `blackoutd off`/`on` cycles did not +restore it; the next run's sleep/wake (run 82) did. +² Run 84's aftermath: a subsequent `--trigger extcycle` attempt failed +with `extcycle: no external display online` — the external was not +merely dark but absent from CGGetOnlineDisplayList. Run 85's +sleep/wake re-onlined it. An extcycle failure mode can therefore +drop the external below CG visibility entirely. + +### Runs 086–104 — shakedowns, MCL controls, groups C/D/E + +All on the notification-based wake build, all `--trigger extcycle`, +all `--recover displaysleep`, no W1 (the scheduled wake fired on every +lid-open run). MonitorControlLite was quit for runs 90–104 (`MCL?` = +running/quit). Runs 87–97 on AC are group **D** (relabeled per the +maintainer); locked AC runs 98–99 stay group C (D is defined +unlocked) with AC on record here and in the sheets' power field. + +| run | sheet | grp | power | session | settled E/B | black? | cleared? | MCL? | +|-----|--------------------------------------------------------|-----|-------|----------|-------------|--------|----------|---------| +| 86 | [run086](repro-matrix/run086-grp-b-20260719-233431.md) | B | batt | unlocked | E1/B0 | yes | yes | running | +| 87 | [run087](repro-matrix/run087-grp-d-20260719-235246.md) | D | AC | unlocked | E1/B0 | yes | yes | running | +| 88 | [run088](repro-matrix/run088-grp-b-20260719-235545.md) | B | batt | unlocked | E1/B0 | yes | yes | running | +| 89 | [run089](repro-matrix/run089-grp-d-20260719-235755.md) | D | AC | unlocked | E1/B0 | yes | yes | running | +| 90 | [run090](repro-matrix/run090-grp-d-20260720-000202.md) | D | AC | unlocked | E1/B0 | yes | yes | quit | +| 91 | [run091](repro-matrix/run091-grp-d-20260720-000746.md) | D | AC | unlocked | E1/B0 | yes | yes | quit | +| 92 | [run092](repro-matrix/run092-grp-b-20260720-001020.md) | B | batt | unlocked | E1/B0 | yes | yes | quit | +| 93 | [run093](repro-matrix/run093-grp-b-20260720-001334.md) | B | batt | unlocked | E1/B0 | yes | yes | quit | +| 94 | [run094](repro-matrix/run094-grp-b-20260720-001600.md) | B | batt | unlocked | E1/B0 | yes | yes | quit | +| 95 | [run095](repro-matrix/run095-grp-b-20260720-002033.md) | B | batt | unlocked | E1/B0 | yes | yes | quit | +| 96 | [run096](repro-matrix/run096-grp-d-20260720-002246.md) | D | AC | unlocked | E1/B0 | yes | yes | quit | +| 97 | [run097](repro-matrix/run097-grp-d-20260720-002550.md) | D | AC | unlocked | E1/B0 | yes | yes | quit | +| 98 | [run098](repro-matrix/run098-grp-c-20260720-002935.md) | C | AC | locked | E1/B0 | yes | yes | quit | +| 99 | [run099](repro-matrix/run099-grp-c-20260720-003235.md) | C | AC | locked | E1/B0 | yes | yes | quit | +| 100 | [run100](repro-matrix/run100-grp-c-20260720-003513.md) | C | batt | locked | E1/B0 | yes | yes | quit | +| 101 | [run101](repro-matrix/run101-grp-c-20260720-003736.md) | C | batt | locked | E1/B0 | yes | yes | quit | +| 102 | [run102](repro-matrix/run102-grp-c-20260720-004031.md) | C | batt | locked | E0/B0 | no | n/a (R0) | quit | +| 103 | [run103](repro-matrix/run103-grp-e-20260720-004529.md) | E | AC | unlocked | E0/B0 | no | n/a (R0) | quit | +| 104 | [run104](repro-matrix/run104-grp-e-20260720-004902.md) | E | batt | unlocked | E1/B0 | yes | yes | quit | + +Verdicts from this block: + +- **MonitorControlLite is retired as a confound**: 15 runs with MCL + quit produced 13 blacks, indistinguishable from the MCL-running + runs. The host-side macOS attribution stands. +- **Locked displaysleep is n=5** (runs 60, 98, 99, 100, 101 — every + locked black cleared). Group C's question is answered: yes, + recovery works while locked. +- **Group D has its answer** (runs 87, 89, 90, 91, 96, 97 — six AC + blacks): the failure is not power-dependent, now at n=6 under the + scripted trigger (retiring the P29 "AC" open question for good). +- **Group E**: run 103 (AC, clamshell closed through the scheduled + wake) woke *clean* — the external, as the sole active display in + clamshell mode, came up rendering; the lid was reopened after wake. + Run 104 (battery): the scheduled wake did not fire with the lid + closed (expected without AC), and the manual lid-open wake went + black (E1), cleared by displaysleep — the lived scenario reproduced + and recovered. +- The **"awake" cue is intermittently inaudible** (heard: 89, 101, + 102, 103, 104; not heard: 87, 92–95, 100 — both power sources on + both lists). Wake detection is fine in every case (captures and + sheets landed on schedule); the loss is in post-wake audio-device + bring-up racing the `say` call. The Notification Center banner + carries the timestamp either way. Known quirk, not queued for a + fix. + +**Detection result (2026-07-19)**: the field-by-field diff over this +cohort found a WindowServer log marker that splits black from clean — +10/10 black wakes vs 0/15 clean across both cohorts, extended to +**19/19 vs 0/26** by the run 41–60 extension (including the C1-miss +run 55 and the locked runs), and to **39/39 vs 0/31** by the +soft-trigger series 61–85 — where the two trigger-but-clean runs (71, +72) show the trigger's own pre-sleep hotplug pair does **not** plant +the marker, and every marker timestamp sits at the wake, not at the +trigger. Runs 86–104 bring the all-time tally to **56/56 black vs +0/33 clean**. See `docs/technical-debt.md` P29 (2026-07-19/20 +updates) for the diff record, mechanism reading, and caveats. + ## What we do with the data - **Detection** — diff `dcp.txt` / `connection-mode.txt` (and the raw diff --git a/docs/debug/repro-matrix/run011-grp-a-20260719-000924.md b/docs/debug/repro-matrix/run011-grp-a-20260719-000924.md new file mode 100644 index 0000000..6a2bf01 --- /dev/null +++ b/docs/debug/repro-matrix/run011-grp-a-20260719-000924.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-000924 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 11 started: 2026-07-19 00:09:24 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 00:09:42.726 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-000953 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run012-grp-a-20260719-082315.md b/docs/debug/repro-matrix/run012-grp-a-20260719-082315.md new file mode 100644 index 0000000..af5d6ed --- /dev/null +++ b/docs/debug/repro-matrix/run012-grp-a-20260719-082315.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-082315 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 12 started: 2026-07-19 08:23:15 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 08:23:33.415 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-082343 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run013-grp-a-20260719-110044.md b/docs/debug/repro-matrix/run013-grp-a-20260719-110044.md new file mode 100644 index 0000000..6271b8d --- /dev/null +++ b/docs/debug/repro-matrix/run013-grp-a-20260719-110044.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-110044 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 13 started: 2026-07-19 11:00:44 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 11:01:03.321 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-110117 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run014-grp-a-20260719-110205.md b/docs/debug/repro-matrix/run014-grp-a-20260719-110205.md new file mode 100644 index 0000000..79ba8db --- /dev/null +++ b/docs/debug/repro-matrix/run014-grp-a-20260719-110205.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-110205 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 14 started: 2026-07-19 11:02:05 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 11:02:23.897 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-110237 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run015-grp-a-20260719-110325.md b/docs/debug/repro-matrix/run015-grp-a-20260719-110325.md new file mode 100644 index 0000000..3bf8a53 --- /dev/null +++ b/docs/debug/repro-matrix/run015-grp-a-20260719-110325.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-110325 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 15 started: 2026-07-19 11:03:25 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 11:03:43.719 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-110357 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run016-grp-a-20260719-110439.md b/docs/debug/repro-matrix/run016-grp-a-20260719-110439.md new file mode 100644 index 0000000..6d039b8 --- /dev/null +++ b/docs/debug/repro-matrix/run016-grp-a-20260719-110439.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-110439 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 16 started: 2026-07-19 11:04:39 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 11:04:57.798 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-110511 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run017-grp-a-20260719-120552.md b/docs/debug/repro-matrix/run017-grp-a-20260719-120552.md new file mode 100644 index 0000000..da88292 --- /dev/null +++ b/docs/debug/repro-matrix/run017-grp-a-20260719-120552.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-120552 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 17 started: 2026-07-19 12:05:52 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 12:06:10.853 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-120624 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run018-grp-a-20260719-120742.md b/docs/debug/repro-matrix/run018-grp-a-20260719-120742.md new file mode 100644 index 0000000..83551dd --- /dev/null +++ b/docs/debug/repro-matrix/run018-grp-a-20260719-120742.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-120742 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 18 started: 2026-07-19 12:07:42 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 12:07:54.184 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-120810 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run019-grp-a-20260719-120843.md b/docs/debug/repro-matrix/run019-grp-a-20260719-120843.md new file mode 100644 index 0000000..b3fd74e --- /dev/null +++ b/docs/debug/repro-matrix/run019-grp-a-20260719-120843.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-120843 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 19 started: 2026-07-19 12:08:43 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 12:09:01.770 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-120915 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run020-grp-a-20260719-120955.md b/docs/debug/repro-matrix/run020-grp-a-20260719-120955.md new file mode 100644 index 0000000..6d87177 --- /dev/null +++ b/docs/debug/repro-matrix/run020-grp-a-20260719-120955.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-120955 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 20 started: 2026-07-19 12:09:55 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 12:10:14.335 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-121027 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run021-grp-a-20260719-121755.md b/docs/debug/repro-matrix/run021-grp-a-20260719-121755.md new file mode 100644 index 0000000..2acf7e7 --- /dev/null +++ b/docs/debug/repro-matrix/run021-grp-a-20260719-121755.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-121755 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 21 started: 2026-07-19 12:17:55 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 12:18:14.155 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-121827 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run022-grp-a-20260719-121916.md b/docs/debug/repro-matrix/run022-grp-a-20260719-121916.md new file mode 100644 index 0000000..e5b0a11 --- /dev/null +++ b/docs/debug/repro-matrix/run022-grp-a-20260719-121916.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-121916 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 22 started: 2026-07-19 12:19:16 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 12:19:35.597 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-121948 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: There were a few brief moments during the blackout and switchover where both the built-in and external were cursor-on-black. +``` diff --git a/docs/debug/repro-matrix/run023-grp-a-20260719-122131.md b/docs/debug/repro-matrix/run023-grp-a-20260719-122131.md new file mode 100644 index 0000000..edb964e --- /dev/null +++ b/docs/debug/repro-matrix/run023-grp-a-20260719-122131.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-122131 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 23 started: 2026-07-19 12:21:31 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 12:21:50.180 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-122203 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run024-grp-a-20260719-122252.md b/docs/debug/repro-matrix/run024-grp-a-20260719-122252.md new file mode 100644 index 0000000..d788fe2 --- /dev/null +++ b/docs/debug/repro-matrix/run024-grp-a-20260719-122252.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-122252 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 24 started: 2026-07-19 12:22:52 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 12:23:12.237 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-122324 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run025-grp-b-20260719-122426.md b/docs/debug/repro-matrix/run025-grp-b-20260719-122426.md new file mode 100644 index 0000000..7a60889 --- /dev/null +++ b/docs/debug/repro-matrix/run025-grp-b-20260719-122426.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-122426 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 25 started: 2026-07-19 12:24:26 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 12:24:45.144 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-122458 + post-recover = docs/debug/blackoutd-diag-20260719-122541 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Displays active; recovery was not necessary but occurred anyway after some delay. +``` diff --git a/docs/debug/repro-matrix/run026-grp-b-20260719-122726.md b/docs/debug/repro-matrix/run026-grp-b-20260719-122726.md new file mode 100644 index 0000000..62fbe23 --- /dev/null +++ b/docs/debug/repro-matrix/run026-grp-b-20260719-122726.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-122726 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 26 started: 2026-07-19 12:27:26 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 12:27:45.247 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-122759 + post-recover = docs/debug/blackoutd-diag-20260719-122843 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Displays active, recovery unnecessary +``` diff --git a/docs/debug/repro-matrix/run027-grp-b-20260719-122934.md b/docs/debug/repro-matrix/run027-grp-b-20260719-122934.md new file mode 100644 index 0000000..a4df590 --- /dev/null +++ b/docs/debug/repro-matrix/run027-grp-b-20260719-122934.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-122934 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 27 started: 2026-07-19 12:29:34 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 12:29:53.101 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-123006 + post-recover = docs/debug/blackoutd-diag-20260719-123051 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Displays active, recovery not needed. It took a few moments for the external display to be lit, showing desktop after the built-in blacked out. +``` diff --git a/docs/debug/repro-matrix/run028-grp-b-20260719-131053.md b/docs/debug/repro-matrix/run028-grp-b-20260719-131053.md new file mode 100644 index 0000000..07c1573 --- /dev/null +++ b/docs/debug/repro-matrix/run028-grp-b-20260719-131053.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-131053 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 28 started: 2026-07-19 13:10:53 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 13:11:12.273 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-131126 + post-recover = docs/debug/blackoutd-diag-20260719-131205 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Displays active, no recovery needed +``` diff --git a/docs/debug/repro-matrix/run029-grp-b-20260719-131252.md b/docs/debug/repro-matrix/run029-grp-b-20260719-131252.md new file mode 100644 index 0000000..91b12d9 --- /dev/null +++ b/docs/debug/repro-matrix/run029-grp-b-20260719-131252.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-131252 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 29 started: 2026-07-19 13:12:52 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 13:13:10.635 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-131324 + post-recover = docs/debug/blackoutd-diag-20260719-131404 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Displays active, no recovery needed +``` diff --git a/docs/debug/repro-matrix/run030-grp-b-20260719-131452.md b/docs/debug/repro-matrix/run030-grp-b-20260719-131452.md new file mode 100644 index 0000000..3345342 --- /dev/null +++ b/docs/debug/repro-matrix/run030-grp-b-20260719-131452.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-131452 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 30 started: 2026-07-19 13:14:52 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 13:15:11.265 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-131524 + post-recover = docs/debug/blackoutd-diag-20260719-131605 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Recovery not needed +``` diff --git a/docs/debug/repro-matrix/run031-grp-b-20260719-131825.md b/docs/debug/repro-matrix/run031-grp-b-20260719-131825.md new file mode 100644 index 0000000..62cf75e --- /dev/null +++ b/docs/debug/repro-matrix/run031-grp-b-20260719-131825.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-131825 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 31 started: 2026-07-19 13:18:25 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 13:18:44.123 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-131857 + post-recover = docs/debug/blackoutd-diag-20260719-131940 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Recovery not needed +``` diff --git a/docs/debug/repro-matrix/run032-grp-b-20260719-132525.md b/docs/debug/repro-matrix/run032-grp-b-20260719-132525.md new file mode 100644 index 0000000..e07d854 --- /dev/null +++ b/docs/debug/repro-matrix/run032-grp-b-20260719-132525.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-132525 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 32 started: 2026-07-19 13:25:25 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 13:25:44.481 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-132559 + post-recover = docs/debug/blackoutd-diag-20260719-132641 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: No recovery needed +``` diff --git a/docs/debug/repro-matrix/run033-grp-b-20260719-132720.md b/docs/debug/repro-matrix/run033-grp-b-20260719-132720.md new file mode 100644 index 0000000..9d3fc3a --- /dev/null +++ b/docs/debug/repro-matrix/run033-grp-b-20260719-132720.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-132720 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 33 started: 2026-07-19 13:27:20 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 13:27:39.193 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-132753 + post-recover = docs/debug/blackoutd-diag-20260719-132834 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: No recovery needed +``` diff --git a/docs/debug/repro-matrix/run034-grp-b-20260719-132947.md b/docs/debug/repro-matrix/run034-grp-b-20260719-132947.md new file mode 100644 index 0000000..4b674bf --- /dev/null +++ b/docs/debug/repro-matrix/run034-grp-b-20260719-132947.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-132947 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 34 started: 2026-07-19 13:29:47 +Build: v0.4.0 built: 2026-07-19T04:08:56+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 13:30:06.059 blackoutd[80240:1872712] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-133019 + post-recover = docs/debug/blackoutd-diag-20260719-133100 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: No recovery needed +``` diff --git a/docs/debug/repro-matrix/run035-grp-b-20260719-140116.md b/docs/debug/repro-matrix/run035-grp-b-20260719-140116.md new file mode 100644 index 0000000..932582e --- /dev/null +++ b/docs/debug/repro-matrix/run035-grp-b-20260719-140116.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-140116 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 35 started: 2026-07-19 14:01:16 +Build: v0.4.0 built: 2026-07-19T17:36:33+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 14:01:36.596 blackoutd[87639:2064839] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-140149 + post-recover = docs/debug/blackoutd-diag-20260719-140226 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: No recovery needed +``` diff --git a/docs/debug/repro-matrix/run036-grp-a-20260719-141127.md b/docs/debug/repro-matrix/run036-grp-a-20260719-141127.md new file mode 100644 index 0000000..c86117c --- /dev/null +++ b/docs/debug/repro-matrix/run036-grp-a-20260719-141127.md @@ -0,0 +1,46 @@ + + +# repro run sheet — 20260719-141127 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 36 started: 2026-07-19 14:11:27 +Build: v0.4.0 built: 2026-07-19T17:36:33+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. A + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-19 14:11:46.975 blackoutd[87639:2064839] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-141200 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run037-grp-b-20260719-141825.md b/docs/debug/repro-matrix/run037-grp-b-20260719-141825.md new file mode 100644 index 0000000..c0706b8 --- /dev/null +++ b/docs/debug/repro-matrix/run037-grp-b-20260719-141825.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-141825 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 37 started: 2026-07-19 14:18:25 +Build: v0.4.0 built: 2026-07-19T17:36:33+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 14:18:45.191 blackoutd[87639:2064839] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-141857 + post-recover = docs/debug/blackoutd-diag-20260719-141935 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Music.app playing pre-sleep; went silent at sleep and resumed playing music prior to display wake. +``` diff --git a/docs/debug/repro-matrix/run038-grp-b-20260719-142134.md b/docs/debug/repro-matrix/run038-grp-b-20260719-142134.md new file mode 100644 index 0000000..7c55e4b --- /dev/null +++ b/docs/debug/repro-matrix/run038-grp-b-20260719-142134.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-142134 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 38 started: 2026-07-19 14:21:34 +Build: v0.4.0 built: 2026-07-19T17:36:33+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 14:21:53.662 blackoutd[87639:2064839] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-142207 + post-recover = docs/debug/blackoutd-diag-20260719-142245 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Music playing +``` diff --git a/docs/debug/repro-matrix/run039-grp-b-20260719-142353.md b/docs/debug/repro-matrix/run039-grp-b-20260719-142353.md new file mode 100644 index 0000000..8b0831f --- /dev/null +++ b/docs/debug/repro-matrix/run039-grp-b-20260719-142353.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-142353 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 39 started: 2026-07-19 14:23:53 +Build: v0.4.0 built: 2026-07-19T17:36:33+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 14:24:11.746 blackoutd[87639:2064839] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-142425 + post-recover = docs/debug/blackoutd-diag-20260719-142503 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: No recovery needed, music playing +``` diff --git a/docs/debug/repro-matrix/run040-grp-b-20260719-142621.md b/docs/debug/repro-matrix/run040-grp-b-20260719-142621.md new file mode 100644 index 0000000..5ead5a6 --- /dev/null +++ b/docs/debug/repro-matrix/run040-grp-b-20260719-142621.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-142621 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 40 started: 2026-07-19 14:26:21 +Build: v0.4.0 built: 2026-07-19T17:36:33+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 14:26:40.915 blackoutd[87639:2064839] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-142653 + post-recover = docs/debug/blackoutd-diag-20260719-142733 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: No recovery needed, music on +``` diff --git a/docs/debug/repro-matrix/run041-grp-b-20260719-145713.md b/docs/debug/repro-matrix/run041-grp-b-20260719-145713.md new file mode 100644 index 0000000..973578a --- /dev/null +++ b/docs/debug/repro-matrix/run041-grp-b-20260719-145713.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-145713 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 41 started: 2026-07-19 14:57:13 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 14:57:32.389 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-145745 + post-recover = docs/debug/blackoutd-diag-20260719-145823 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: No recovery needed, music on +``` diff --git a/docs/debug/repro-matrix/run042-grp-b-20260719-150121.md b/docs/debug/repro-matrix/run042-grp-b-20260719-150121.md new file mode 100644 index 0000000..3943c58 --- /dev/null +++ b/docs/debug/repro-matrix/run042-grp-b-20260719-150121.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-150121 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 42 started: 2026-07-19 15:01:21 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:01:40.057 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-150153 + post-recover = docs/debug/blackoutd-diag-20260719-150230 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: No recovery needed, music on +``` diff --git a/docs/debug/repro-matrix/run043-grp-b-20260719-150356.md b/docs/debug/repro-matrix/run043-grp-b-20260719-150356.md new file mode 100644 index 0000000..4ee9e34 --- /dev/null +++ b/docs/debug/repro-matrix/run043-grp-b-20260719-150356.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-150356 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 43 started: 2026-07-19 15:03:56 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:04:15.022 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-150428 + post-recover = docs/debug/blackoutd-diag-20260719-150506 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: No recovery needed, music on +``` diff --git a/docs/debug/repro-matrix/run044-grp-b-20260719-150606.md b/docs/debug/repro-matrix/run044-grp-b-20260719-150606.md new file mode 100644 index 0000000..e38883d --- /dev/null +++ b/docs/debug/repro-matrix/run044-grp-b-20260719-150606.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-150606 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 44 started: 2026-07-19 15:06:06 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:06:25.215 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-150638 + post-recover = docs/debug/blackoutd-diag-20260719-150716 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: No recovery needed, music on +``` diff --git a/docs/debug/repro-matrix/run045-grp-b-20260719-150805.md b/docs/debug/repro-matrix/run045-grp-b-20260719-150805.md new file mode 100644 index 0000000..027cb08 --- /dev/null +++ b/docs/debug/repro-matrix/run045-grp-b-20260719-150805.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-150805 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 45 started: 2026-07-19 15:08:05 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:08:24.438 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-150837 + post-recover = docs/debug/blackoutd-diag-20260719-150915 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: No recovery needed, music on +``` diff --git a/docs/debug/repro-matrix/run046-grp-b-20260719-151024.md b/docs/debug/repro-matrix/run046-grp-b-20260719-151024.md new file mode 100644 index 0000000..cbf6e48 --- /dev/null +++ b/docs/debug/repro-matrix/run046-grp-b-20260719-151024.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-151024 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 46 started: 2026-07-19 15:10:24 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:10:43.280 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-151056 + post-recover = docs/debug/blackoutd-diag-20260719-151134 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: No recovery needed (R0), music on (M1) +``` diff --git a/docs/debug/repro-matrix/run047-grp-b-20260719-151223.md b/docs/debug/repro-matrix/run047-grp-b-20260719-151223.md new file mode 100644 index 0000000..77a53bf --- /dev/null +++ b/docs/debug/repro-matrix/run047-grp-b-20260719-151223.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-151223 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 47 started: 2026-07-19 15:12:23 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:12:42.962 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-151255 + post-recover = docs/debug/blackoutd-diag-20260719-151335 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: R0 M1, on AC not battery +``` diff --git a/docs/debug/repro-matrix/run048-grp-b-20260719-151427.md b/docs/debug/repro-matrix/run048-grp-b-20260719-151427.md new file mode 100644 index 0000000..7423826 --- /dev/null +++ b/docs/debug/repro-matrix/run048-grp-b-20260719-151427.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-151427 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 48 started: 2026-07-19 15:14:27 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [ ] no [x] yes: closed + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:15:02.022 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-151501 + post-recover = docs/debug/blackoutd-diag-20260719-151540 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: R0 M1, wno visible or audible wake until clamshell reopened +``` diff --git a/docs/debug/repro-matrix/run049-grp-b-20260719-151715.md b/docs/debug/repro-matrix/run049-grp-b-20260719-151715.md new file mode 100644 index 0000000..1e93070 --- /dev/null +++ b/docs/debug/repro-matrix/run049-grp-b-20260719-151715.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-151715 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 49 started: 2026-07-19 15:17:15 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:17:34.145 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-151747 + post-recover = docs/debug/blackoutd-diag-20260719-151825 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: M1. Growing frustrated without a cursor-on-black repro, I physically unplugged/replugged the external display's USB-C (-> HDMI) cable from the Macbook prior to this run. +``` diff --git a/docs/debug/repro-matrix/run050-grp-b-20260719-152024.md b/docs/debug/repro-matrix/run050-grp-b-20260719-152024.md new file mode 100644 index 0000000..09e5a51 --- /dev/null +++ b/docs/debug/repro-matrix/run050-grp-b-20260719-152024.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-152024 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 50 started: 2026-07-19 15:20:24 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:20:43.503 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-152056 + post-recover = docs/debug/blackoutd-diag-20260719-152134 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: R0 M1 +``` diff --git a/docs/debug/repro-matrix/run051-grp-b-20260719-152300.md b/docs/debug/repro-matrix/run051-grp-b-20260719-152300.md new file mode 100644 index 0000000..82329f5 --- /dev/null +++ b/docs/debug/repro-matrix/run051-grp-b-20260719-152300.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-152300 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 51 started: 2026-07-19 15:23:00 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:23:19.909 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-152332 + post-recover = docs/debug/blackoutd-diag-20260719-152410 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: M1. Physically unplug/replug the external display's USB-C side of its USB-C -> HDMI cable from the Macbook prior to the run (C1) +``` diff --git a/docs/debug/repro-matrix/run052-grp-b-20260719-152718.md b/docs/debug/repro-matrix/run052-grp-b-20260719-152718.md new file mode 100644 index 0000000..d592f65 --- /dev/null +++ b/docs/debug/repro-matrix/run052-grp-b-20260719-152718.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-152718 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 52 started: 2026-07-19 15:27:18 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:27:37.737 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-152751 + post-recover = docs/debug/blackoutd-diag-20260719-152829 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: C1 M1 +``` diff --git a/docs/debug/repro-matrix/run053-grp-b-20260719-153031.md b/docs/debug/repro-matrix/run053-grp-b-20260719-153031.md new file mode 100644 index 0000000..102a315 --- /dev/null +++ b/docs/debug/repro-matrix/run053-grp-b-20260719-153031.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-153031 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 53 started: 2026-07-19 15:30:31 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:30:50.731 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-153103 + post-recover = docs/debug/blackoutd-diag-20260719-153141 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: C1 M1 +``` diff --git a/docs/debug/repro-matrix/run054-grp-b-20260719-153227.md b/docs/debug/repro-matrix/run054-grp-b-20260719-153227.md new file mode 100644 index 0000000..893f4a8 --- /dev/null +++ b/docs/debug/repro-matrix/run054-grp-b-20260719-153227.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-153227 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 54 started: 2026-07-19 15:32:27 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:32:46.032 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-153259 + post-recover = docs/debug/blackoutd-diag-20260719-153337 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: C1 M1 +``` diff --git a/docs/debug/repro-matrix/run055-grp-b-20260719-153508.md b/docs/debug/repro-matrix/run055-grp-b-20260719-153508.md new file mode 100644 index 0000000..694e52f --- /dev/null +++ b/docs/debug/repro-matrix/run055-grp-b-20260719-153508.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-153508 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 55 started: 2026-07-19 15:35:08 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:35:27.476 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-153541 + post-recover = docs/debug/blackoutd-diag-20260719-153620 + +@ wake (immediate — first look as the panels light): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: C1 M1 R0 +``` diff --git a/docs/debug/repro-matrix/run056-grp-b-20260719-153711.md b/docs/debug/repro-matrix/run056-grp-b-20260719-153711.md new file mode 100644 index 0000000..c9976b3 --- /dev/null +++ b/docs/debug/repro-matrix/run056-grp-b-20260719-153711.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-153711 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 56 started: 2026-07-19 15:37:11 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T18:56:53+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:37:30.616 blackoutd[92781:2163480] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-153743 + post-recover = docs/debug/blackoutd-diag-20260719-153821 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: C1 M1 +``` diff --git a/docs/debug/repro-matrix/run057-grp-b-20260719-153916.md b/docs/debug/repro-matrix/run057-grp-b-20260719-153916.md new file mode 100644 index 0000000..7110a6b --- /dev/null +++ b/docs/debug/repro-matrix/run057-grp-b-20260719-153916.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-153916 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 57 started: 2026-07-19 15:39:16 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T19:38:58+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:39:35.193 blackoutd[98648:2250223] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-153948 + post-recover = docs/debug/blackoutd-diag-20260719-154026 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: C1 M1 +``` diff --git a/docs/debug/repro-matrix/run058-grp-c-20260719-154916.md b/docs/debug/repro-matrix/run058-grp-c-20260719-154916.md new file mode 100644 index 0000000..137e056 --- /dev/null +++ b/docs/debug/repro-matrix/run058-grp-c-20260719-154916.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-154916 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 58 started: 2026-07-19 15:49:16 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T19:38:58+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:49:37.037 blackoutd[98648:2250223] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-154948 + post-recover = docs/debug/blackoutd-diag-20260719-155027 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: M1. Apple Watch was off, but the lock screen command seemed not to work as post-recovery was to an unlocked screen: sudo --validate && ./build/blackoutd repro --wake 15 --recover displaysleep --group C && osascript -l JavaScript -e 'Application("System Events").keystroke("q", {using:["control down","command down"]})' +``` diff --git a/docs/debug/repro-matrix/run059-grp-c-20260719-155209.md b/docs/debug/repro-matrix/run059-grp-c-20260719-155209.md new file mode 100644 index 0000000..6b4a9a0 --- /dev/null +++ b/docs/debug/repro-matrix/run059-grp-c-20260719-155209.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-155209 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 59 started: 2026-07-19 15:52:09 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T19:38:58+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ locked -> locked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:52:28.625 blackoutd[98648:2250223] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-155241 + post-recover = docs/debug/blackoutd-diag-20260719-155319 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: M1. sudo --validate && osascript -l JavaScript -e 'Application("System Events").keystroke("q", {using:["control down","command down"]})' && ./build/blackoutd repro --wake 15 --recover displaysleep --group C +``` diff --git a/docs/debug/repro-matrix/run060-grp-c-20260719-155510.md b/docs/debug/repro-matrix/run060-grp-c-20260719-155510.md new file mode 100644 index 0000000..6841cde --- /dev/null +++ b/docs/debug/repro-matrix/run060-grp-c-20260719-155510.md @@ -0,0 +1,51 @@ + + +# repro run sheet — 20260719-155510 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 60 started: 2026-07-19 15:55:10 +Build: v0.4.0-3-g460ad9d built: 2026-07-19T19:38:58+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ locked -> locked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 15:55:28.698 blackoutd[98648:2250223] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-155542 + post-recover = docs/debug/blackoutd-diag-20260719-155621 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: C1 M1. sudo --validate && osascript -l JavaScript -e 'Application("System Events").keystroke("q", {using:["control down","command down"]})' && ./build/blackoutd repro --wake 15 --recover displaysleep --group C +``` diff --git a/docs/debug/repro-matrix/run061-grp-b-20260719-202401.md b/docs/debug/repro-matrix/run061-grp-b-20260719-202401.md new file mode 100644 index 0000000..a53bab7 --- /dev/null +++ b/docs/debug/repro-matrix/run061-grp-b-20260719-202401.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-202401 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 61 started: 2026-07-19 20:24:01 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:18:39+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 20:25:25.305 blackoutd[8558:2493339] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-202538 + post-recover = docs/debug/blackoutd-diag-20260719-202616 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: M0. NOTE: Scheduled wake failed; I had to depress the Space bar to wake. +``` diff --git a/docs/debug/repro-matrix/run062-grp-b-20260719-202947.md b/docs/debug/repro-matrix/run062-grp-b-20260719-202947.md new file mode 100644 index 0000000..122e240 --- /dev/null +++ b/docs/debug/repro-matrix/run062-grp-b-20260719-202947.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-202947 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 62 started: 2026-07-19 20:29:47 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:18:39+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 20:30:46.023 blackoutd[8558:2493339] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-203059 + post-recover = docs/debug/blackoutd-diag-20260719-203137 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Depressed trackpad to trigger wake; scheduled wake failed. +``` diff --git a/docs/debug/repro-matrix/run063-grp-b-20260719-203423.md b/docs/debug/repro-matrix/run063-grp-b-20260719-203423.md new file mode 100644 index 0000000..a633a45 --- /dev/null +++ b/docs/debug/repro-matrix/run063-grp-b-20260719-203423.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-203423 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 63 started: 2026-07-19 20:34:23 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:18:39+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 20:35:28.255 blackoutd[8558:2493339] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-203541 + post-recover = docs/debug/blackoutd-diag-20260719-203619 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Scheduled wake failed; depressed Space bar to trigger wake. +``` diff --git a/docs/debug/repro-matrix/run064-grp-b-20260719-203715.md b/docs/debug/repro-matrix/run064-grp-b-20260719-203715.md new file mode 100644 index 0000000..bab44db --- /dev/null +++ b/docs/debug/repro-matrix/run064-grp-b-20260719-203715.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-203715 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 64 started: 2026-07-19 20:37:15 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:18:39+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 20:38:09.271 blackoutd[8558:2493339] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-203822 + post-recover = docs/debug/blackoutd-diag-20260719-203900 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Scheduled wake failed; manual wake via depressing Space bar. +``` diff --git a/docs/debug/repro-matrix/run065-grp-b-20260719-203953.md b/docs/debug/repro-matrix/run065-grp-b-20260719-203953.md new file mode 100644 index 0000000..a9b556b --- /dev/null +++ b/docs/debug/repro-matrix/run065-grp-b-20260719-203953.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-203953 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 65 started: 2026-07-19 20:39:53 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:18:39+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 20:40:43.411 blackoutd[8558:2493339] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-204057 + post-recover = docs/debug/blackoutd-diag-20260719-204135 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Scheduled wake failed; manual wake triggered by depressing Space bar. +``` diff --git a/docs/debug/repro-matrix/run066-grp-b-20260719-204340.md b/docs/debug/repro-matrix/run066-grp-b-20260719-204340.md new file mode 100644 index 0000000..e983ac6 --- /dev/null +++ b/docs/debug/repro-matrix/run066-grp-b-20260719-204340.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-204340 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 66 started: 2026-07-19 20:43:40 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:18:39+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 20:45:13.803 blackoutd[8558:2493339] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-204527 + post-recover = docs/debug/blackoutd-diag-20260719-204605 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Just because I saw a black built-in on wake doesn't mean it didn't have a cursor-on-black, only that I didn't see it. I could have missed it. Scheduled wake failed even when run as `sudo --validate && ./build/blackoutd repro --wake 15 --trigger extcycle --recover displaysleep --group B`, which was surprising. Manual wake triggered by depressing Space bar. +``` diff --git a/docs/debug/repro-matrix/run067-grp-b-20260719-204849.md b/docs/debug/repro-matrix/run067-grp-b-20260719-204849.md new file mode 100644 index 0000000..65923cf --- /dev/null +++ b/docs/debug/repro-matrix/run067-grp-b-20260719-204849.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-204849 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 67 started: 2026-07-19 20:48:49 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:18:39+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 20:49:47.640 blackoutd[8558:2493339] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-205001 + post-recover = docs/debug/blackoutd-diag-20260719-205040 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Confirmed by careful observation, preceding repro with sudo --validate results in failed wake and cursor-on-black only on external display; built-in remains off throughout. Wake triggered by depressing space bar. +``` diff --git a/docs/debug/repro-matrix/run068-grp-b-20260719-205256.md b/docs/debug/repro-matrix/run068-grp-b-20260719-205256.md new file mode 100644 index 0000000..bd3260b --- /dev/null +++ b/docs/debug/repro-matrix/run068-grp-b-20260719-205256.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-205256 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 68 started: 2026-07-19 20:52:56 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:18:39+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 20:53:50.045 blackoutd[8558:2493339] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-205403 + post-recover = docs/debug/blackoutd-diag-20260719-205442 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Scheduled wake failed; triggered by depressing touchpad. +``` diff --git a/docs/debug/repro-matrix/run069-grp-b-20260719-205745.md b/docs/debug/repro-matrix/run069-grp-b-20260719-205745.md new file mode 100644 index 0000000..c54b974 --- /dev/null +++ b/docs/debug/repro-matrix/run069-grp-b-20260719-205745.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-205745 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 69 started: 2026-07-19 20:57:45 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:57:37+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 20:58:36.390 blackoutd[12629:2559119] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-205849 + post-recover = docs/debug/blackoutd-diag-20260719-205935 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Scheduled wake failed, wake manually triggered (W1). +``` diff --git a/docs/debug/repro-matrix/run070-grp-b-20260719-210158.md b/docs/debug/repro-matrix/run070-grp-b-20260719-210158.md new file mode 100644 index 0000000..a59ea63 --- /dev/null +++ b/docs/debug/repro-matrix/run070-grp-b-20260719-210158.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-210158 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 70 started: 2026-07-19 21:01:58 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:57:37+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> locked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 21:02:57.108 blackoutd[12629:2559119] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-210256 + post-recover = docs/debug/blackoutd-diag-20260719-210342 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: W1 but I depressed Space bar as repro announced capturing post-wake +``` diff --git a/docs/debug/repro-matrix/run071-grp-b-20260719-210655.md b/docs/debug/repro-matrix/run071-grp-b-20260719-210655.md new file mode 100644 index 0000000..359462c --- /dev/null +++ b/docs/debug/repro-matrix/run071-grp-b-20260719-210655.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-210655 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 71 started: 2026-07-19 21:06:55 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:57:37+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 21:07:57.235 blackoutd[12629:2559119] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-210810 + post-recover = docs/debug/blackoutd-diag-20260719-210855 + +@ wake (immediate — first look as the panels light): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: W1, surprising result with external active on wake. +``` diff --git a/docs/debug/repro-matrix/run072-grp-b-20260719-211053.md b/docs/debug/repro-matrix/run072-grp-b-20260719-211053.md new file mode 100644 index 0000000..b7fb110 --- /dev/null +++ b/docs/debug/repro-matrix/run072-grp-b-20260719-211053.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-211053 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 72 started: 2026-07-19 21:10:53 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:57:37+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 21:11:51.827 blackoutd[12629:2559119] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-211205 + post-recover = docs/debug/blackoutd-diag-20260719-211251 + +@ wake (immediate — first look as the panels light): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: W1 +``` diff --git a/docs/debug/repro-matrix/run073-grp-b-20260719-211335.md b/docs/debug/repro-matrix/run073-grp-b-20260719-211335.md new file mode 100644 index 0000000..9ea74eb --- /dev/null +++ b/docs/debug/repro-matrix/run073-grp-b-20260719-211335.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-211335 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 73 started: 2026-07-19 21:13:35 +Build: v0.4.0-8-g2187621 built: 2026-07-20T00:57:37+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 21:14:19.569 blackoutd[12629:2559119] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-211433 + post-recover = docs/debug/blackoutd-diag-20260719-211520 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: W1 +``` diff --git a/docs/debug/repro-matrix/run074-grp-b-20260719-211608.md b/docs/debug/repro-matrix/run074-grp-b-20260719-211608.md new file mode 100644 index 0000000..3b1e264 --- /dev/null +++ b/docs/debug/repro-matrix/run074-grp-b-20260719-211608.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-211608 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 74 started: 2026-07-19 21:16:08 +Build: v0.4.0-8-g2187621 built: 2026-07-20T01:16:03+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> locked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 21:17:11.398 blackoutd[15374:2596027] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-211710 + post-recover = docs/debug/blackoutd-diag-20260719-211757 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: W1 +``` diff --git a/docs/debug/repro-matrix/run075-grp-b-20260719-212006.md b/docs/debug/repro-matrix/run075-grp-b-20260719-212006.md new file mode 100644 index 0000000..6562c18 --- /dev/null +++ b/docs/debug/repro-matrix/run075-grp-b-20260719-212006.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-212006 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 75 started: 2026-07-19 21:20:06 +Build: v0.4.0-8-g2187621 built: 2026-07-20T01:16:03+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 21:21:02.585 blackoutd[15374:2596027] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-212109 + post-recover = docs/debug/blackoutd-diag-20260719-212154 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: W1 +``` diff --git a/docs/debug/repro-matrix/run076-grp-b-20260719-212233.md b/docs/debug/repro-matrix/run076-grp-b-20260719-212233.md new file mode 100644 index 0000000..2220e45 --- /dev/null +++ b/docs/debug/repro-matrix/run076-grp-b-20260719-212233.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-212233 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 76 started: 2026-07-19 21:22:33 +Build: v0.4.0-8-g2187621 built: 2026-07-20T01:16:03+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 21:23:34.354 blackoutd[15374:2596027] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-212347 + post-recover = docs/debug/blackoutd-diag-20260719-212434 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: W1 +``` diff --git a/docs/debug/repro-matrix/run077-grp-b-20260719-212714.md b/docs/debug/repro-matrix/run077-grp-b-20260719-212714.md new file mode 100644 index 0000000..142703d --- /dev/null +++ b/docs/debug/repro-matrix/run077-grp-b-20260719-212714.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-212714 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 77 started: 2026-07-19 21:27:14 +Build: v0.4.0-8-g2187621 built: 2026-07-20T01:16:03+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 21:28:04.835 blackoutd[15374:2596027] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-212818 + post-recover = docs/debug/blackoutd-diag-20260719-212904 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: W1 +``` diff --git a/docs/debug/repro-matrix/run078-grp-b-20260719-212937.md b/docs/debug/repro-matrix/run078-grp-b-20260719-212937.md new file mode 100644 index 0000000..e717be2 --- /dev/null +++ b/docs/debug/repro-matrix/run078-grp-b-20260719-212937.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-212937 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 78 started: 2026-07-19 21:29:37 +Build: v0.4.0-8-g2187621 built: 2026-07-20T01:16:03+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 21:30:32.962 blackoutd[15374:2596027] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-213046 + post-recover = docs/debug/blackoutd-diag-20260719-213131 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: W1 +``` diff --git a/docs/debug/repro-matrix/run079-grp-b-20260719-213221.md b/docs/debug/repro-matrix/run079-grp-b-20260719-213221.md new file mode 100644 index 0000000..91fa61b --- /dev/null +++ b/docs/debug/repro-matrix/run079-grp-b-20260719-213221.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-213221 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 79 started: 2026-07-19 21:32:21 +Build: v0.4.0-8-g2187621 built: 2026-07-20T01:16:03+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 21:33:42.023 blackoutd[15374:2596027] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-213355 + post-recover = docs/debug/blackoutd-diag-20260719-213441 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: W1 +``` diff --git a/docs/debug/repro-matrix/run080-grp-b-20260719-214926.md b/docs/debug/repro-matrix/run080-grp-b-20260719-214926.md new file mode 100644 index 0000000..cd9019d --- /dev/null +++ b/docs/debug/repro-matrix/run080-grp-b-20260719-214926.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-214926 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 80 started: 2026-07-19 21:49:26 +Build: v0.4.0-8-g2187621 built: 2026-07-20T01:16:03+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 21:50:20.456 blackoutd[18563:2652232] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-215032 + post-recover = docs/debug/blackoutd-diag-20260719-215117 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: W1 +``` diff --git a/docs/debug/repro-matrix/run081-grp-c-20260719-215853.md b/docs/debug/repro-matrix/run081-grp-c-20260719-215853.md new file mode 100644 index 0000000..feae664 --- /dev/null +++ b/docs/debug/repro-matrix/run081-grp-c-20260719-215853.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-215853 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 81 started: 2026-07-19 21:58:53 +Build: v0.4.0-8-g2187621 built: 2026-07-20T01:16:03+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ locked -> locked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ none + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 21:59:11.789 blackoutd[18563:2652232] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-215925 + post-recover = docs/debug/blackoutd-diag-20260719-220015 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [x] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: I should clarify "Did the recovery clear the external panel? n" because it did clear it but then both displays went black. The system was not asleep because I heard the announcements. I triggered display wake by depressing Space bar after hearing "Recovery complete", then unlocked via TouchID, and answered the prompts while built-in was active and external black. +``` diff --git a/docs/debug/repro-matrix/run082-grp-c-20260719-220650.md b/docs/debug/repro-matrix/run082-grp-c-20260719-220650.md new file mode 100644 index 0000000..a9fd481 --- /dev/null +++ b/docs/debug/repro-matrix/run082-grp-c-20260719-220650.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-220650 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 82 started: 2026-07-19 22:06:50 +Build: v0.4.0-8-g2187621 built: 2026-07-20T01:16:03+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ locked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ none + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 22:07:11.061 blackoutd[18563:2652232] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-220723 + post-recover = docs/debug/blackoutd-diag-20260719-220807 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run083-grp-c-20260719-220914.md b/docs/debug/repro-matrix/run083-grp-c-20260719-220914.md new file mode 100644 index 0000000..914599f --- /dev/null +++ b/docs/debug/repro-matrix/run083-grp-c-20260719-220914.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-220914 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 83 started: 2026-07-19 22:09:14 +Build: v0.4.0-8-g2187621 built: 2026-07-20T01:16:03+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ locked -> locked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ none + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 22:10:26.608 blackoutd[18563:2652232] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-220942 + post-recover = docs/debug/blackoutd-diag-20260719-221028 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: I might have recorded built-in at wake wrong; it might have been B0 then. Cursor-on-black at settled. Recovery caused both displays to go black. I recovered them by depressing Space bar after hearing the "Repro complete" announcement, then unlocked via TouchID. Both displays are active. +``` diff --git a/docs/debug/repro-matrix/run084-grp-c-20260719-221345.md b/docs/debug/repro-matrix/run084-grp-c-20260719-221345.md new file mode 100644 index 0000000..d66e9c6 --- /dev/null +++ b/docs/debug/repro-matrix/run084-grp-c-20260719-221345.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-221345 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 84 started: 2026-07-19 22:13:45 +Build: v0.4.0-8-g2187621 built: 2026-07-20T01:16:03+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ locked -> locked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ none + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 22:14:03.991 blackoutd[18563:2652232] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-221418 + post-recover = docs/debug/blackoutd-diag-20260719-221507 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: On recovery the external went black -> built-in active -> both black. Recovered after hearing "Repro complete" announcement via depressing Space bar and unlocking via TouchID to active built-in and black external. +``` diff --git a/docs/debug/repro-matrix/run085-grp-c-20260719-222052.md b/docs/debug/repro-matrix/run085-grp-c-20260719-222052.md new file mode 100644 index 0000000..f07861f --- /dev/null +++ b/docs/debug/repro-matrix/run085-grp-c-20260719-222052.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-222052 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 85 started: 2026-07-19 22:20:52 +Build: v0.4.0-8-g2187621 built: 2026-07-20T01:16:03+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ locked -> locked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ none + recovery applied ....... extcycle + settle ................. 20 s + daemon settled ......... 2026-07-19 22:21:11.712 blackoutd[18563:2652232] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-222125 + post-recover = docs/debug/blackoutd-diag-20260719-222222 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Recovery: active built-in -> black external -> black built-in -> manual display wake after the "Repro complete" announcement -> unlock via TouchID to E0/B0. +``` diff --git a/docs/debug/repro-matrix/run086-grp-b-20260719-233431.md b/docs/debug/repro-matrix/run086-grp-b-20260719-233431.md new file mode 100644 index 0000000..89096fe --- /dev/null +++ b/docs/debug/repro-matrix/run086-grp-b-20260719-233431.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-233431 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 86 started: 2026-07-19 23:34:31 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 23:35:09.810 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-233537 + post-recover = docs/debug/blackoutd-diag-20260719-233615 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run087-grp-d-20260719-235246.md b/docs/debug/repro-matrix/run087-grp-d-20260719-235246.md new file mode 100644 index 0000000..c9487b1 --- /dev/null +++ b/docs/debug/repro-matrix/run087-grp-d-20260719-235246.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-235246 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 87 started: 2026-07-19 23:52:46 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. D + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 23:53:23.686 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-235346 + post-recover = docs/debug/blackoutd-diag-20260719-235424 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: On AC power, did not hear the "Awake" announcement. +``` diff --git a/docs/debug/repro-matrix/run088-grp-b-20260719-235545.md b/docs/debug/repro-matrix/run088-grp-b-20260719-235545.md new file mode 100644 index 0000000..6fae406 --- /dev/null +++ b/docs/debug/repro-matrix/run088-grp-b-20260719-235545.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-235545 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 88 started: 2026-07-19 23:55:45 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 23:56:20.805 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-235645 + post-recover = docs/debug/blackoutd-diag-20260719-235723 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run089-grp-d-20260719-235755.md b/docs/debug/repro-matrix/run089-grp-d-20260719-235755.md new file mode 100644 index 0000000..5b9da60 --- /dev/null +++ b/docs/debug/repro-matrix/run089-grp-d-20260719-235755.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260719-235755 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 89 started: 2026-07-19 23:57:55 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. D + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-19 23:58:29.720 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260719-235855 + post-recover = docs/debug/blackoutd-diag-20260719-235933 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Wake: Very brief E2/B1 -> E1/B1 -> E1/B0. On AC power. +``` diff --git a/docs/debug/repro-matrix/run090-grp-d-20260720-000202.md b/docs/debug/repro-matrix/run090-grp-d-20260720-000202.md new file mode 100644 index 0000000..faa85c6 --- /dev/null +++ b/docs/debug/repro-matrix/run090-grp-d-20260720-000202.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-000202 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 90 started: 2026-07-20 00:02:02 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. D + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:02:36.536 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-000301 + post-recover = docs/debug/blackoutd-diag-20260720-000339 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: On AC power, MonitorControlLite not running. +``` diff --git a/docs/debug/repro-matrix/run091-grp-d-20260720-000746.md b/docs/debug/repro-matrix/run091-grp-d-20260720-000746.md new file mode 100644 index 0000000..b641305 --- /dev/null +++ b/docs/debug/repro-matrix/run091-grp-d-20260720-000746.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-000746 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 91 started: 2026-07-20 00:07:46 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. D + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:08:23.489 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-000848 + post-recover = docs/debug/blackoutd-diag-20260720-000926 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: On AP power, MCL not running. +``` diff --git a/docs/debug/repro-matrix/run092-grp-b-20260720-001020.md b/docs/debug/repro-matrix/run092-grp-b-20260720-001020.md new file mode 100644 index 0000000..3de0d8d --- /dev/null +++ b/docs/debug/repro-matrix/run092-grp-b-20260720-001020.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-001020 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 92 started: 2026-07-20 00:10:20 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:10:56.427 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-001120 + post-recover = docs/debug/blackoutd-diag-20260720-001158 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Did not hear the "Awake" cue. MCL not running. +``` diff --git a/docs/debug/repro-matrix/run093-grp-b-20260720-001334.md b/docs/debug/repro-matrix/run093-grp-b-20260720-001334.md new file mode 100644 index 0000000..0a596f3 --- /dev/null +++ b/docs/debug/repro-matrix/run093-grp-b-20260720-001334.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-001334 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 93 started: 2026-07-20 00:13:34 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:14:08.892 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-001433 + post-recover = docs/debug/blackoutd-diag-20260720-001512 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Did not hear the awake cue. MCL not running. +``` diff --git a/docs/debug/repro-matrix/run094-grp-b-20260720-001600.md b/docs/debug/repro-matrix/run094-grp-b-20260720-001600.md new file mode 100644 index 0000000..56c6e29 --- /dev/null +++ b/docs/debug/repro-matrix/run094-grp-b-20260720-001600.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-001600 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 94 started: 2026-07-20 00:16:00 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:16:36.162 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-001700 + post-recover = docs/debug/blackoutd-diag-20260720-001739 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Awake cue not heard. MCL not running. +``` diff --git a/docs/debug/repro-matrix/run095-grp-b-20260720-002033.md b/docs/debug/repro-matrix/run095-grp-b-20260720-002033.md new file mode 100644 index 0000000..d467859 --- /dev/null +++ b/docs/debug/repro-matrix/run095-grp-b-20260720-002033.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-002033 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 95 started: 2026-07-20 00:20:33 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. B + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:21:08.506 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-002132 + post-recover = docs/debug/blackoutd-diag-20260720-002211 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Awake cue not heard. MCL not running. +``` diff --git a/docs/debug/repro-matrix/run096-grp-d-20260720-002246.md b/docs/debug/repro-matrix/run096-grp-d-20260720-002246.md new file mode 100644 index 0000000..9659af7 --- /dev/null +++ b/docs/debug/repro-matrix/run096-grp-d-20260720-002246.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-002246 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 96 started: 2026-07-20 00:22:46 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. D + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:23:22.300 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-002349 + post-recover = docs/debug/blackoutd-diag-20260720-002428 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: On AC power. MCL not running. +``` diff --git a/docs/debug/repro-matrix/run097-grp-d-20260720-002550.md b/docs/debug/repro-matrix/run097-grp-d-20260720-002550.md new file mode 100644 index 0000000..0396f32 --- /dev/null +++ b/docs/debug/repro-matrix/run097-grp-d-20260720-002550.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-002550 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 97 started: 2026-07-20 00:25:50 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. D + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:26:26.458 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-002650 + post-recover = docs/debug/blackoutd-diag-20260720-002729 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: On AC power. MCL not running. +``` diff --git a/docs/debug/repro-matrix/run098-grp-c-20260720-002935.md b/docs/debug/repro-matrix/run098-grp-c-20260720-002935.md new file mode 100644 index 0000000..d52dbb4 --- /dev/null +++ b/docs/debug/repro-matrix/run098-grp-c-20260720-002935.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-002935 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 98 started: 2026-07-20 00:29:35 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ locked -> locked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:30:19.955 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-003045 + post-recover = docs/debug/blackoutd-diag-20260720-003124 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: On AC power. MCL not running. +``` diff --git a/docs/debug/repro-matrix/run099-grp-c-20260720-003235.md b/docs/debug/repro-matrix/run099-grp-c-20260720-003235.md new file mode 100644 index 0000000..62373ee --- /dev/null +++ b/docs/debug/repro-matrix/run099-grp-c-20260720-003235.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-003235 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 99 started: 2026-07-20 00:32:35 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ locked -> locked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:33:19.447 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-003345 + post-recover = docs/debug/blackoutd-diag-20260720-003424 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: On AC power. MCL not running. +``` diff --git a/docs/debug/repro-matrix/run100-grp-c-20260720-003513.md b/docs/debug/repro-matrix/run100-grp-c-20260720-003513.md new file mode 100644 index 0000000..722d7b6 --- /dev/null +++ b/docs/debug/repro-matrix/run100-grp-c-20260720-003513.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-003513 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 100 started: 2026-07-20 00:35:13 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ locked -> locked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:35:55.528 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-003619 + post-recover = docs/debug/blackoutd-diag-20260720-003659 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Awake cue not heard. MCL not running. +``` diff --git a/docs/debug/repro-matrix/run101-grp-c-20260720-003736.md b/docs/debug/repro-matrix/run101-grp-c-20260720-003736.md new file mode 100644 index 0000000..770b62a --- /dev/null +++ b/docs/debug/repro-matrix/run101-grp-c-20260720-003736.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-003736 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 101 started: 2026-07-20 00:37:36 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ locked -> locked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:38:18.824 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-003844 + post-recover = docs/debug/blackoutd-diag-20260720-003923 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Awake cue heard. MCL not running. +``` diff --git a/docs/debug/repro-matrix/run102-grp-c-20260720-004031.md b/docs/debug/repro-matrix/run102-grp-c-20260720-004031.md new file mode 100644 index 0000000..2548b15 --- /dev/null +++ b/docs/debug/repro-matrix/run102-grp-c-20260720-004031.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-004031 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 102 started: 2026-07-20 00:40:31 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. C + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ locked -> locked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:41:13.871 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-004140 + post-recover = docs/debug/blackoutd-diag-20260720-004219 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [x] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Awake cue heard. MCL not running. +``` diff --git a/docs/debug/repro-matrix/run103-grp-e-20260720-004529.md b/docs/debug/repro-matrix/run103-grp-e-20260720-004529.md new file mode 100644 index 0000000..d4d81fa --- /dev/null +++ b/docs/debug/repro-matrix/run103-grp-e-20260720-004529.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-004529 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 103 started: 2026-07-20 00:45:29 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. E + lid .................... open -> open + moved during sleep? ... [ ] no [x] yes: closed + session ................ unlocked -> unlocked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:46:05.164 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-004631 + post-recover = docs/debug/blackoutd-diag-20260720-004711 + +@ wake (immediate — first look as the panels light): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [ ] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: On AC power. MCL not running. Awake cue heard. +``` diff --git a/docs/debug/repro-matrix/run104-grp-e-20260720-004902.md b/docs/debug/repro-matrix/run104-grp-e-20260720-004902.md new file mode 100644 index 0000000..84d4022 --- /dev/null +++ b/docs/debug/repro-matrix/run104-grp-e-20260720-004902.md @@ -0,0 +1,52 @@ + + +# repro run sheet — 20260720-004902 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 104 started: 2026-07-20 00:49:02 +Build: v0.4.0-13-g2287185 built: 2026-07-20T03:34:23+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. E + lid .................... open -> open + moved during sleep? ... [ ] no [x] yes: opened + session ................ unlocked -> unlocked + power .................. battery -> battery + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... displaysleep + settle ................. 20 s + daemon settled ......... 2026-07-20 00:50:11.325 blackoutd[26174:2796319] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-005039 + post-recover = docs/debug/blackoutd-diag-20260720-005119 + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-recover capture: + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + recovery cleared the external? ... [x] yes [ ] no [ ] partial + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: Scheduled wake did not occur with clamshell closed on battery power. MCL not running. I closed the lid during sleep, waited, and only after I reopened it did the external display wake (to E1). Awake cue heard. +``` diff --git a/docs/debug/repro-matrix/run105-20260720-020909.md b/docs/debug/repro-matrix/run105-20260720-020909.md new file mode 100644 index 0000000..4de6607 --- /dev/null +++ b/docs/debug/repro-matrix/run105-20260720-020909.md @@ -0,0 +1,47 @@ + + +# repro run sheet — 20260720-020909 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 105 started: 2026-07-20 02:09:09 +Build: v0.4.0-19-gb271787 built: 2026-07-20T06:06:47+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. ____ (not given; pass --group a..e) + lid .................... open -> open + moved during sleep? ... [ ] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-20 02:09:37.684 blackoutd[38840:3040108] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-021005 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [ ] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: ____________________________________________________________ +``` diff --git a/docs/debug/repro-matrix/run106-20260720-021026.md b/docs/debug/repro-matrix/run106-20260720-021026.md new file mode 100644 index 0000000..a9a4a94 --- /dev/null +++ b/docs/debug/repro-matrix/run106-20260720-021026.md @@ -0,0 +1,47 @@ + + +# repro run sheet — 20260720-021026 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 106 started: 2026-07-20 02:10:26 +Build: v0.4.0-19-gb271787 built: 2026-07-20T06:06:47+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. ____ (not given; pass --group a..e) + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-20 02:11:03.989 blackoutd[38840:3040108] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-021130 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [ ] E1 [x] E2 [ ] E3 + built-in ... [ ] B0 [x] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [x] yes [ ] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: displaysleep auto-recovery validation run: ./build/blackoutd recovery displaysleep && ./build/blackoutd repro --wake 15 --trigger extcycle. Wake E2/B1 -> E1/B0 -> E0/B0 with no flicker prior to "capturing post wake" cue. +``` diff --git a/docs/debug/repro-matrix/run107-20260720-021403.md b/docs/debug/repro-matrix/run107-20260720-021403.md new file mode 100644 index 0000000..d8c82b0 --- /dev/null +++ b/docs/debug/repro-matrix/run107-20260720-021403.md @@ -0,0 +1,47 @@ + + +# repro run sheet — 20260720-021403 + +Machine-filled by `blackoutd repro`. Fill in the eyewitness +checkboxes and notes; the matrix +(`docs/debug/cursor-on-black-matrix.md`) indexes these sheets +per group. + +```text +Run #: 107 started: 2026-07-20 02:14:03 +Build: v0.4.0-19-gb271787 built: 2026-07-20T06:06:47+00:00 (UTC) + +Conditions (pre-sleep -> at capture): + group .................. ____ (not given; pass --group a..e) + lid .................... open -> open + moved during sleep? ... [x] no [ ] yes: ______ + session ................ unlocked -> unlocked + power .................. AC -> AC + wake ................... scheduled (--wake 15) + trigger ................ extcycle + recovery applied ....... none + settle ................. 20 s + daemon settled ......... 2026-07-20 02:14:36.913 blackoutd[38840:3040108] [wake] — display pipeline settled + +Bundles: + post-wake = docs/debug/blackoutd-diag-20260720-021500 + post-recover = n/a + +@ wake (immediate — first look as the panels light): + external ... [ ] E0 [x] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +@ post-wake capture (settled — at the "capturing post wake" + cue; the state the bundle records): + external ... [x] E0 [ ] E1 [ ] E2 [ ] E3 + built-in ... [x] B0 [ ] B1 [ ] B2 + +Outcome: + cursor-on-black occurred this run? ... [ ] yes [x] no + (yes = settled external E1; note E2/E3 variants in Notes) +Notes: displaysleep auto-recovery validation: ./build/blackoutd recovery displaysleep && ./build/blackoutd repro --wake 15 --trigger extcycle. Wake E1/B0 -> E0/B0 with no flicker prior to "capturing post wake cue". Awake cue not heard. +``` diff --git a/docs/decisions/0009-sp2309w-color-quirk.md b/docs/decisions/0009-sp2309w-color-quirk.md new file mode 100644 index 0000000..149e25b --- /dev/null +++ b/docs/decisions/0009-sp2309w-color-quirk.md @@ -0,0 +1,168 @@ +--- +# SPDX-FileCopyrightText: Copyright 2026 Todd Schulman +# +# SPDX-License-Identifier: GPL-3.0-or-later + +number: 9 +title: SP2309W color-cast correction as an opt-in quirk module +status: proposed +date: 2026-07-20 +decision-makers: + - toobuntu +--- + +# SP2309W color-cast correction as an opt-in quirk module + +> Recorded retroactively. The decision was sketched 2026-05-21/26 in +> technical-debt P26, the 2026-05-25 session handoff, and the +> `inject_edid` project's ADRs 0001–0002, all of which already cite +> "ADR 0009"; this document writes down what those sources agreed on. +> Status stays `proposed` until the module lands (or the standalone +> alternative is chosen instead) — P26's first acceptance criterion. + +## Context and Problem Statement + +The maintainer's Dell SP2309W (2008, 8-bit RGB TN, native 2048×1152) +ships a *defective* EDID: block 0 declares RGB, but the CTA-861 +extension (byte 131, bits 5–4) wrongly advertises YCbCr 4:4:4/4:2:2. +macOS trusts the extension and negotiates YCbCr — which the panel has +no decode path for — producing the pink cast (green when the OSD is +set to YPbPr). A 2010 production run shipped a corrected RGB-only +EDID; no firmware update exists for this unit, so correction is +host-side forever. + +The `inject_edid` project settled the mechanism and the host: + +- **ADR 0001 (inject_edid)** — runtime virtual-EDID injection via the + private `IOAVServiceSetVirtualEDIDMode`, patching only CEA byte 131 + (clear bits 5–4, fix the extension checksum). The filesystem + override mechanism is non-functional on Apple Silicon; DDC-driven + re-set is reactive and proprietary; a hardware EDID emulator fails + the no-spend constraint. +- **ADR 0002 (inject_edid)** — the correction is a non-resident + one-shot (the mapping survives process exit and display sleep, + reverts on full system sleep) fired from **blackoutd** on external + connect and after the wake-settle, because blackoutd is the single + actor on the reconfiguration callback and renegotiation is driven + by the same CG recommit blackoutd already owns; a second daemon + reintroduces the two-actor race behind the 2026-05-19 12:10 + incident. + +That leaves the blackoutd-side question this ADR answers: **how does a +general-purpose daemon carry a correction that applies to exactly one +monitor model, without burdening every other user with dead code and +extra private-API surface?** + +Orthogonality note (P29 insists): this is the *color-encoding* defect. +It is unrelated to cursor-on-black, which is the absence of scanout — +the two must not be entangled in code or triggers. + +## Decision Drivers + +* Other users must carry no Dell-specific dead code and no additional + private-API surface in their binaries. +* One display actor: the correction must ride blackoutd's existing + callback and wake-settle timing (inject_edid ADR 0002). +* The single-monitor scope argues against any generality beyond one + guarded hook (YAGNI). +* The unconditional safety invariant must be untouched. + +## Considered Options + +* **Opt-in, isolated quirk module compiled under `make QUIRKS=sp2309w`** + (chosen, proposed). +* Compile the quirk in unconditionally, gate at runtime only. +* A generic quirk framework (registry, per-display hooks). +* Standalone resident daemon. +* Do nothing (keep BetterDisplay + watchdog). + +## Decision Outcome + +Chosen: host the correction as an **opt-in, isolated quirk module**, +because it keeps every non-Dell build free of the quirk's code and +private-API fragility while giving the quirk build blackoutd's +single-actor timing: + +* Its own translation unit, `src/quirks/DisplayColorController.{h,m}` + — a sibling to `DisplayController`, not code woven into it. +* Compiled **only** under `make QUIRKS=sp2309w` + (`-DBLACKOUTD_QUIRK_SP2309W`). A default `make` produces a binary + with zero quirk symbols and no reference to + `IOAVServiceSetVirtualEDIDMode`. +* Runtime-keyed to vendor `0x10AC` / product `0xD01D` even when + compiled in — a quirk build attached to any other monitor does + nothing. +* Attached through **one narrow hook** in the core (external-connect + and post-wake-settle, before the blackout commit — never during + pipeline churn), not a generic quirk framework: no other quirks are + planned, and a framework for n=1 is YAGNI. +* The module re-derives the external `DCPAVServiceProxy` on each + event and holds no resident `IOAVServiceRef`; the induced + reconfiguration is marked self-originated (an + `_actionInProgress`-style suppression) **without weakening the + unconditional safety-invariant check**. + +### Consequences + +* Good, because other users' builds carry no Dell-specific code, no + extra private-API dependency, and no behavioral difference — the + quirk cannot regress the core for anyone who does not opt in. +* Good, because the quirk user gets a preventive fix (YCbCr never + negotiated) applied at exactly the right moments by the actor that + already owns the timing, retiring the BetterDisplay + + `restore_rgb.sh` watchdog. +* Bad, because `IOAVServiceSetVirtualEDIDMode` is private and fragile + across macOS updates — but that surface exists only in quirk builds. +* Bad, because a compile-time flag means no single released binary + serves both audiences, and the `QUIRKS=sp2309w` configuration needs + its own build check (or knowingly stays maintainer-built-only). +* Neutral, because accepting this ADR is P26's first acceptance + criterion; if the standalone-daemon alternative is chosen instead, + this ADR moves to `rejected` and inject_edid ADR 0002 gets a + superseding note. + +### Confirmation + +Pending — the module has not landed. The underlying mechanism is +confirmed in the standalone tool (inject_edid ADR 0002: cast cleared, +`YCbCr 4:4:4 Limited` → `RGB Full`, CEA byte 131 `0xF1` → `0xC1`); +what remains is the hosted form: no competing actor, no safety- +invariant interaction, re-application on each wake via the +wake-settle hook. + +## Pros and Cons of the Options + +### Compile the quirk in unconditionally, gate at runtime only + +* Rejected: every user carries dead Dell code and the private-API + reference; the runtime key alone does not remove the fragility + surface from their binaries. + +### A generic quirk framework (registry, per-display hooks) + +* Rejected as YAGNI: exactly one quirk exists or is planned. One + `#ifdef`-guarded hook is cheaper to read and delete. + +### Standalone resident daemon + +* Rejected in inject_edid ADR 0002: reintroduces the two-actor race + on the reconfiguration callback (nested CG transactions, err=1014). + +### Do nothing (keep BetterDisplay + watchdog) + +* Rejected: proprietary, reactive (the cast appears, then clears), + and a competing display actor. MonitorControlLite is retained for + brightness precisely because it avoids DDC and does not trigger + renegotiation. + +## More Information + +* Technical-debt **P26** — the tracking entry whose acceptance + criteria mirror this ADR. +* `inject_edid` ADRs 0001 (why runtime injection) and 0002 (why + blackoutd hosts the one-shot), `inject_edid/docs/sp2309w-display-notes.md` + (EDID decode, firmware investigation), and + `inject_edid/docs/investigations/connection-mode.md` (the + YCbCr→RGB flip measurement; CEA byte 131 `0xF1` → `0xC1`). +* `docs/SESSION-HANDOFF-2026-05-25.md` — the panel-defect summary and + the do-not-entangle instruction (track D). diff --git a/docs/decisions/0010-cursor-on-black-detection-and-recovery.md b/docs/decisions/0010-cursor-on-black-detection-and-recovery.md new file mode 100644 index 0000000..c6b328a --- /dev/null +++ b/docs/decisions/0010-cursor-on-black-detection-and-recovery.md @@ -0,0 +1,171 @@ +--- +# SPDX-FileCopyrightText: Copyright 2026 Todd Schulman +# +# SPDX-License-Identifier: GPL-3.0-or-later + +number: 10 +title: Detector-gated displaysleep recovery for cursor-on-black +status: accepted +date: 2026-07-20 +decision-makers: + - toobuntu +--- + +# Detector-gated displaysleep recovery for cursor-on-black + +## Context and Problem Statement + +The cursor-on-black failure (technical-debt P20/P29) leaves the external +display black with only the mouse cursor after a sleep/wake cycle, while +every CoreGraphics- and WindowServer-visible field reports a healthy, +fully configured display: the mode is set (`current == preferred`), the +framebuffer is found, `dcp.txt` / `connection-mode.txt` / `ioreg` state +is byte-identical to a clean wake. The failure is a DCP/scanout stall +below WindowServer. Automatic recovery therefore needs two things +blackoutd did not have: a **detection signal** (so recovery does not +flicker every healthy wake) and a **recovery action** stronger than the +ADR-0003 no-op recommit (empirically insufficient, early and late). + +The empirical matrix (`docs/debug/cursor-on-black-matrix.md`, runs +1–104) settled both: + +- **Detection.** One WindowServer log line separates black wakes from + clean ones: `[ Display:Hotplug ] Replacing existing hotplug event for + state "out" with "in", display id: 2` — SkyLight coalescing a + still-pending hotplug "out" with the wake's "in". Tally: present in + the wake window of **56/56** eyewitness-confirmed black wakes, absent + from **33/33** clean ones, across cable-triggered, software-triggered + (`repro --trigger extcycle`), natural, locked, AC, and lid-open + regimes. Control runs show the pre-sleep trigger itself never plants + the marker; every marker timestamp sits at the wake instant. +- **Recovery.** A display-sleep cycle (`pmset displaysleepnow`, then + `caffeinate -u` to relight) cleared **every** programmatic recovery + attempt in the matrix, locked (n=5) and unlocked alike, plus all + manual hot-corner clears. It needs no privileges. + +## Decision Drivers + +* Recovery must not flicker healthy wakes — the majority — so it needs + a detection gate, not a schedule. +* Both halves must rest on the recorded run matrix, not on mechanism + theory (every theory-first candidate in P29 was falsified). +* Must work at the lock screen (the lived scenario includes locked + wakes). +* No new resident actors or privileges; fail quiet on any uncertainty. + +## Considered Options + +* **Marker-gated displaysleep cycle at wake-settle** (chosen). +* Reconfiguration-flag keying (`0x133e` flap vs `0x111e`). +* Below-CG state polling (`DCPPowerState`, `NormalModeActive`, ioreg). +* Unconditional recovery on every wake-with-external. +* `extcycle` (CG disable/re-enable of the external) as the recovery. +* IOMobileFramebuffer HPD notifications as the detector. +* OSLogStore instead of spawning `log show`. + +## Decision Outcome + +Chosen: **marker-gated displaysleep cycle at wake-settle**, because it +is the only detector that survived n ≥ 3 validation (56/56 vs 0/33) +paired with the only recovery that cleared every recorded black, +locked and unlocked. + +At `wakeSettleTimerFired` (the ADR-0003 quiet point), when an external +display is present, the daemon: + +1. queries the unified log for the coalescing marker over a window + opening 30 s before the recorded wake, by spawning + `/usr/bin/log show --predicate …` (the same unprivileged path + `diagnose` uses) off the main queue; +2. if — and only if — the marker is present, runs the display-sleep + cycle; +3. logs the verdict either way (`[wake] recovery=… marker=…`). + +The behavior is gated by a `recoveryStrategy` NSUserDefaults key +(String; default `displaysleep`, `none` disables, unknown values fall +back to `none`), settable live via `blackoutd recovery +` (SIGHUP reload, same pattern as `verbosity`). + +Guard rails: a once-per-wake latch (the settle timer re-fires on +err=1014 retries), a `_systemSleeping` re-check after the query, and +the fact that a display-sleep cycle emits screen — not system — sleep +notifications, so it cannot re-arm the wake path and loop. + +### Consequences + +* Good, because recovery is targeted: healthy wakes (marker absent) see + no flicker at all, and black wakes recover in seconds without user + action, including at the lock screen. +* Good, because both halves are validated at n = 56 + 33 rather than + inferred from a mechanism theory. +* Bad, because the detector is the *wording of a private SkyLight log + line*, pinned to macOS 26 behavior. A macOS update can silently break + detection. Mitigation: absence is always treated as clean-or-unknown + (fail quiet, never fail loud), the daemon logs the verdict every wake + so a broken detector is visible in the log, and the repro matrix + remains the revalidation loop after OS updates. +* Bad, because the recovery blinks both panels (display sleep is + global). Accepted for now; a flicker-free, external-only action is + the follow-up (candidates: `IOMobileFramebufferRequestPowerChange`, + BetterDisplay's `_reinitializeOnWake` approach — see P29). +* Bad, because matrix data collection now requires `blackoutd recovery + none` first, or captures record the recovered panel (documented in + the matrix how-to). + +### Confirmation + +Live-validated on the target hardware (matrix runs 106–107): with +`recoveryStrategy displaysleep` active and no repro-side recovery, a +soft-trigger black wake cleared to E0/B0 before the post-wake capture +cue, and a clean wake passed through untouched — with no perceived +flicker, the cycle landing inside the wake transition. + +## Pros and Cons of the Options + +### Reconfiguration-flag keying (`0x133e` flap vs `0x111e`) + +* Falsified 2026-05-26: a confirmed black wake reconnected at `0x111e`. + Not a classifier. + +### Below-CG state polling (`DCPPowerState`, `NormalModeActive`, ioreg) + +* Dead. Role-attributed reads are identical on black and clean wakes; + the cohort-2 diff found the normalized `dcpext` subtrees + byte-identical between black and clean captures. The stall is not + surfaced to ioreg. + +### Unconditional recovery on every wake-with-external + +* Rejected: flickers every healthy wake — the majority. The detector + exists precisely to avoid this. + +### `extcycle` (CG disable/re-enable of the external) as the recovery + +* Rejected for automatic use. It clears blacks reliably **unlocked** + (10/10) but while **locked** it fails and *induces* blacks — twice + turning a clean wake black, once leaving the external absent from + `CGGetOnlineDisplayList` until the next sleep/wake. Retained as a + CLI-only experimental method. + +### IOMobileFramebuffer HPD notifications as the detector + +* Deferred, not rejected: private but linkable + (`IOMobileFramebufferEnableHotPlugDetectNotifications`), and would + observe hotplug upstream of SkyLight's coalescing with no log-wording + dependency. Needs a validation series of its own before it can + replace the marker query. + +### OSLogStore instead of spawning `log show` + +* Deferred. In-process and structured, but its system-scope access + rights for a user LaunchAgent are unverified, while `log show` is + proven unprivileged on this machine by every diagnose bundle. Revisit + if the spawn ever becomes a problem (one ~1 s spawn per wake today). + +## More Information + +* `docs/debug/cursor-on-black-matrix.md` — the run-sheet matrix this + decision rests on (runs 1–104, tallies and per-run eyewitness data). +* technical-debt P20 (recovery gap, hypotheses) and P29 (mechanism, + falsifications, the 2026-07-19/20 update trail). +* ADR 0003 — the wake-settle quiet timer this recovery hangs off. diff --git a/docs/technical-debt.md b/docs/technical-debt.md index f0dee62..d5b9d22 100644 --- a/docs/technical-debt.md +++ b/docs/technical-debt.md @@ -787,6 +787,21 @@ that list — capture the connection mode (BetterDisplay `get -connectionMode`, a native reader once it exists) *during* the broken state, to distinguish "stuck in a bad mode" from "no active mode". +**2026-07-19 update — capture gate met; first surviving detector +candidate.** Cohort 2 (matrix runs 011+) delivered the required ≥3 +black / ≥3 clean settled `verbosityLevel=2` captures during genuine +occurrences, satisfying the "Diagnostic data needed" list below via the +`repro` workflow. The field diff found the first black/clean splitter +to survive n ≥ 3: a WindowServer hotplug-coalescing log marker, 10/10 +black wakes vs 0/15 clean across both cohorts, supporting hypothesis A +(link drop during sleep, coalesced on wake). Full diff record, +mechanism reading, caveats, and next steps under P29 (2026-07-19 +update). **2026-07-20**: the marker stands at 56/56 vs 0/33 across +runs 1–104, and the detector-gated displaysleep auto-recovery is +implemented in the daemon (`recoveryStrategy` pref, `blackoutd +recovery` subcommand) — the acceptance-criteria recovery path for +hypothesis A/C is landed; see P29 (2026-07-20 update). + **Architecture analysis confirming the gap**: `recommitDisplayConfiguration` fires from exactly two paths today @@ -1689,6 +1704,325 @@ black/clean split. `DCPPowerState` carries no cursor-on-black signal on either controller. `NormalModeActive` and `DCPPowerAssertionCount` remain untested as detectors; capture them at the black instant to test. +**Update (2026-07-19) — cohort 2 diff: a WindowServer log marker splits +black from clean, 10/10 vs 0/15.** Cohort 2 (matrix runs 011+, sheets +under `docs/debug/repro-matrix/`, v0.4.0 build) satisfied the capture +gate: settled group A blacks runs 14, 22, 24 (battery, on-condition; +run 11 also black but on AC) vs cleans runs 12, 13, 15, 16, 18, 20, +21, 23, 36. Run 17 settled E2 (black, no cursor — a black panel the +E1-keyed Outcome line scores "no"); run 19 disregarded (maintainer: +possibly misreported). Field-by-field diff of the post-wake bundles +under matching conditions: + +- `dcp.txt` / `connection-mode.txt`: **byte-identical** across all + three on-condition blacks and all nine cleans. This extends the dead + `DCPPowerState` verdict to the previously untested fields: + `NormalModeActive`, `DCPPowerAssertionCount`, resolution / + `DPTimingModeId` / `Transport`, and the advertised ColorElements + catalog — none carries any black/clean signal (negative). +- `config.txt`: differs only in timestamps (negative). +- `ioreg.txt`: with volatile fields masked (object ids, busy times, + retain counts, `IOPowerManagement`, `DebugState`, statistics, + `TransferCount`), the external `dcpext@71C00000` subtree of black + run 14 is byte-identical to clean runs 13 and 36, and black run 22 + to clean run 18; the built-in `dcp@31C00000` subtree is identical + across all on-condition blacks and cleans. The stalled scanout is + not surfaced to ioreg at all — confirms the 2026-06-10 reading + (negative). +- `windowserver.txt`: **positive.** Clipped to each run's own wake + window (per the classify-per-wake rule above), the SkyLight line + `[ Display:Hotplug ] Replacing existing hotplug event for state + "out" with "in", display id: 2` appears exactly once at the wake of + every black-panel run — run 14 (11:02:20), run 22 (12:19:31), run 24 + (12:23:07), plus corroborating run 11 (00:09:39, AC), run 37 + (14:18:40, group B post-wake), and the E2-variant run 17 (12:06:07) + — and zero times in all nine cleans, each of whose logs covers the + full wake window. Cohort 1 corroborates: in-window marker on all + four blacks (runs 3, 5, 7, 8), on none of the five cleans nor + stricken run 9 (run 10's file carries the line 7 minutes *before* + its run — outside the window, the same per-wake trap as the 0x133e + grep). Combined: **10/10 black-panel wakes vs 0/15 clean**; the + n ≥ 3 bar is met on cohort 2 alone. + +**Mechanism reading.** The marker means WindowServer still holds an +unprocessed hotplug "out" for display 2 when the wake "in" arrives, +and SkyLight coalesces the pair. So on black wakes the external +dropped its link during sleep (hypothesis A, Alt Mode dropout) and +WindowServer — seeing continuity — skips the full teardown/re-attach, +leaving the DCP scanout un-rearmed while every CG/WS-visible field +looks healthy (the below-WS mechanism above). Clean wakes show no +coalescing. A secondary, effect-side split points the same way: the +FuseBoard `updated window N from "occluded" to "visible"` line appears +≥ 5 times in every clean wake window and never in a black one (windows +only become visible when the external actually renders). + +**Side observations (not detectors).** + +- Runs 11 and 37 — the only two blacks whose sheets record an + *immediate* E1/B0 wake (no role reversal) — are also the only two + captures whose built-in DCP reads `DCPPowerState=4` / assertion 1, + with the built-in advertising ColorElements; run 37's post-recover + bundle still reads 4, and the clean music-playing runs 38–40 read 0. + The on-condition blacks (14, 22, 24; immediate E2/B1) read 0 like + every clean. This sub-classes the wake shape, not black vs clean. +- Disregarded run 19: later runs' log windows show the marker fired at + 12:08:58, inside run 19's wake window — consistent with its original + "black" record, but it stays out of every tally per the maintainer. + +**Next steps.** Detection is now plausible with no new private API: at +`wakeSettleTimerFired`, query the unified log for the marker over the +last ~60 s (`OSLogStore`, or `log show --last 60s --predicate 'process +== "WindowServer" AND eventMessage CONTAINS "Replacing existing +hotplug event"'`; the line logs at Default level, so it is in the +persistent store without any debug-logging setup; in cohort 2 it fires +~3.5 s *before* the daemon's settle marker, so it is already on disk +when the settle timer fires). Marker present ⇒ arm the +display-power-cycle-class recovery; absent ⇒ do nothing. Caveats: the +wording is private SkyLight internals and can change across macOS +releases — treat a missing marker as "unknown", pin the predicate per +macOS version, and keep the eyewitness matrix as the validation loop. +Sequence: + +1. More group B runs until a second black lands (recovery is 1/1 in + cohort 2 — run 37 black ⇒ `--recover displaysleep` ⇒ cleared, + post-recover E0/B0 — corroborated by cohort 1's four manual clears, + but below the matrix's n ≥ 2 bar); then group C (locked). +2. Prototype the detector read-only (log the query verdict at settle + time, no recovery wiring) and validate it live against ≥ 3 further + eyewitness-confirmed blacks and cleans. +3. Only then wire detector ⇒ recovery, behind a `recoveryStrategy` + pref (separate PR, per the matrix's "What we do with the data"). + +**Update (2026-07-19, later) — extension runs 41–60: marker at 19/19 +vs 0/26; a manual repro lever (C1); recovery question answered.** + +- *Marker validated on 20 more runs.* Blacks 49, 51–54, 56–57 (group + B) and 58, 60 (group C series): the hotplug-coalescing marker fires + in-window on **9/9**; the 11 cleans (41–48, 50, 55, 59) show **0**. + Running total **19/19 black vs 0/26 clean** across all recorded + runs. The FuseBoard occluded→visible secondary is demoted: the + extension breaks its clean split (black run 49 shows 1, locked + black run 60 shows 5) — corroborator only, not a detector. +- *C1 — the cable trigger (first on-demand repro lever).* Unplugging + the external's **USB-C end** pre-run (leaving it out until the + built-in fully redraws, then replugging) provoked cursor-on-black + on the next wake in 8 of 9 tries (blacks 49, 51–54, 56–57, 60; + miss 55). Mechanistically consistent with the coalescing story: a + fresh physical re-attach immediately before sleep leaves link + state that drops again across the sleep boundary. Critically, the + C1-miss run 55 shows the pre-run replug alone does **not** plant + the marker — the marker still tracks the black, not the cable + handling. Run 58's Notes record no C1, but the maintainer later + recalled the cable trigger may have been applied there too — do + not cite run 58 as a trigger-free natural repro. C1 makes + recovery-candidate testing cheap: black on demand, ~3 min per + data point. +- *Recovery: displaysleep is 10/10.* Every black in the extension was + cleared end-to-end by `--recover displaysleep` (`pmset + displaysleepnow` + `caffeinate -u`, no root), including run 60 + **while the session stayed locked**. With run 37 that is 10/10 + programmatic clears plus cohort 1's four manual ones. Group B's + question is answered; group C (locked) is 1/1 and stays open until + n ≥ 2. The 2026-05-21 "displaysleepnow = flicker dead end" note is + fully superseded for the *recovery* path (flicker accepted). +- *Deterministic detection beyond the log query (survey, untested).* + The marker is not a proxy — it is the trace of the exact SkyLight + branch that matters (the pending-"out" coalescing decision), and + no exported SkyLight symbol surfaces that queue state; a private + SkyLight *notification* would sit downstream of the same coalescing + and inherit the blind spot (the falsified `0x133e` flag is the + public shadow of exactly that). The genuinely deterministic path is + to observe the raw hotplug **upstream** of WindowServer: + `IOMobileFramebuffer.framework` exports + `IOMobileFramebufferEnableHotPlugDetectNotifications` / + `IOMobileFramebufferGetHotPlugRunLoopSource` (per-framebuffer HPD + events; BetterDisplay links this framework from an ordinary + notarized app, so the user client is reachable without special + entitlements). blackoutd already opens no IOMFB connection; a + read-only spike would open the external framebuffer + (`IOMobileFramebufferOpenByName`, name `external-0` per the WS + logs) at daemon start, log timestamped HPD events, and let a C1 + series show whether an HPD-event pattern around the sleep boundary + splits black/clean. If it does, detection needs no log query at + all. Until then, `OSLogStore` (in-process, structured predicate, + no subprocess) is the implementation of record for the marker + query — prefer it over spawning `log show`. +- *Targeted recovery beyond displaysleep (survey, untested).* + Candidates, in prototype order: (1) + `IOMobileFramebufferRequestPowerChange` on the external framebuffer + only — a DCP-level power cycle scoped to one display, no global + display sleep, no lock-screen interaction; (2) blackoutd's own + primitive, `CGSConfigureDisplayEnabled(external, false→true)` — no + new API but CG-level, may not reach the DCP (P29 lever b, still + untested); (3) DDC power cycle of the sink (VCP 0xD6 off→on via + `IOAVService` I2C, the Lunar/m1ddc path, open source — no RE + needed) — the software analog of the physical replug that C1 shows + re-arms the link, but sink-side and dependent on the SP2309W's DDC + support. Test each as a new `--recover` method against C1 blacks; + adopt over displaysleep only if ≥ its reliability. BetterDisplay + RE (its `_reinitializeOnWake` implementation) is the fallback if + all three miss. +- *Group C invocation* (osascript lock before `repro`) is recorded in + the matrix, with run 58's ordering lesson (lock keystroke chained + after `repro` never took effect; its machine-read session field + caught it). +- *Tooling (implemented same day, runtime-untested).* Candidate (2) + above — the external disable/re-enable cycle — is now built as + `extcycle`, dispatchable both as a recovery (`recover --method + extcycle`, `repro --recover extcycle`) and as a pre-sleep trigger + (`repro --trigger extcycle`, the software analog of C1: disable, + 5 s for the built-in restore/redraw, re-enable, 5 s re-attach + settle). The sheet's conditions block gains a machine-filled + `trigger` line so soft-trigger runs are distinguished from + physical C1 runs, and `blackoutd --help` now documents the + recover/repro methods. Validation order: (a) soft-trigger A/B — + does `--trigger extcycle` reproduce the black like C1? Either + answer is informative: yes ⇒ fully software repro, no ⇒ the + trigger's essence is physical HPD, itself a mechanism datum; + (b) if C1 (or the soft trigger) keeps producing blacks, race + `--recover extcycle` against them — does a CG-level cycle clear + what the no-op recommit could not? + +**Update (2026-07-19, evening) — soft trigger works (18/20); marker +at 39/39 vs 0/31; extcycle recovery is lock-state-dependent; the bug +is host-side.** Runs 61–85 (matrix "soft-trigger series" and group C +extension tables). + +- *The repro is now fully software.* `--trigger extcycle` provoked + cursor-on-black in **18 of 20** runs (blacks 61–70, 73–80; misses + 71–72) with **no cable handling and no physical HPD event** — the + external only entered powersave during the cycle. Healthy-state + sanity: 9/9 manual `recover --method extcycle` cycles ran the same + sequence (external drops → built-in lights at native resolution → + external powersave → built-in re-mirrors the external's resolution + → built-in re-blacks → external returns). +- *All 20 trigger runs were `W1` (scheduled wake failed, manual + Space/trackpad wake) — a repro bug, not a power quirk*: the wake + was scheduled before the trigger, whose ~15 s of cueing and + settling left the wake time in the past by the time the machine + slept. Fixed same evening: `sudo --validate` is now built into + repro (no more manual prefix), the trigger runs before the + schedule step and the wake time is computed after it, a new + **"awake" cue** speaks within ~1 s of the detected wake (the + settle countdown now anchors at the wake, not at sleepnow), and a + new `--lock` flag replaces the manual osascript lock chain. + Silver lining recorded: the W1 runs show the black reproduces on + *manual* wakes too — it is not scheduled-wake-specific. +- *Marker: 20/20 new blacks, 0/5 new cleans — running total 39/39 + vs 0/31.* Critically, the two trigger-but-clean runs (71, 72) + show the trigger's own pre-sleep hotplug pair does **not** plant + the marker, and every marker timestamp sits at the wake instant + (e.g. run 61: start 20:24:01, marker 20:25:20 at the manual + wake), not at the trigger. The detector survives the soft-trigger + regime intact. +- *extcycle as recovery: usable only unlocked.* Unlocked (runs + 69–80): cleared **10/10** blacks — the first recovery besides the + display-sleep class, and evidence that a CG-level teardown/rebuild + *can* re-arm the scanout (where the no-op recommit could not). + Locked (runs 81–85): it failed on both blacks (81, 83: cleared + then **both panels went black**) and **induced** a black external + on clean wakes (84, 85); run 84's aftermath left the external + absent from CGGetOnlineDisplayList entirely (`extcycle: no + external display online`) until the next sleep/wake re-onlined + it, and `blackoutd off`/`on` cycles did not restore it after run + 81. **displaysleep remains the recovery of record** (works locked + and unlocked); any future automatic wiring must either gate + extcycle on an unlocked session or just use displaysleep. +- *Shakedown after run 85 (23:01, unnumbered — Ctrl-C'd, no sheet): + the tick-loop wake detector was defective and is replaced.* On the + reordered build the trigger again produced the black (E2/B1 → + E1/B1 → E1/B0, held indefinitely; hot corner recovered), but repro + went silent: no "awake" cue, no capture. Root cause: the loop + detected a wake only when one 1 s tick spanned > 5 s of wall + clock, and with the wake lead consumed by fall-asleep time the + actual sleep span can be under 5 s — the scheduled wake most + likely fired (the panels lit unprompted) and the detector missed + it, spinning forever (it had no ceiling). Reworked same night: + repro now registers `NSWorkspaceWillSleepNotification` / + `NSWorkspaceDidWakeNotification` observers before `sleepnow` and + waits on the did-wake notification in a run loop (deterministic + for arbitrarily short sleeps), keeps the tick-jump check only as + a fallback, aborts if no will-sleep arrives within 60 s of + `sleepnow` (a sleep that never happens must error, not hang; the + wake side deliberately has no ceiling — overnight manual wakes + are legitimate), and schedules the wake immediately before + `sleepnow` so lock/conditions/cue time no longer shaves the + sleep span. For the record: the 1 s ticks cannot cause or prevent + a wake — sleeping threads hold no power assertions and userspace + timers do not program RTC wakes. +- *Attribution (maintainer asked: OS, cable, display, or interfering + software?).* The evidence points at the **macOS display stack + (SkyLight/DCP wake path), host-side**: the soft trigger reproduces + the black with the cable untouched and no physical HPD (cable and + monitor-side link exonerated as necessary conditions); the + detector is WindowServer's own hotplug-coalescing bookkeeping; and + every recovery that works (hot corner, displaysleep, unlocked + extcycle, physical replug) is a host-side pipeline rebuild. The + SP2309W/adapter's slow re-attach remains a plausible + *frequency* contributor (it shapes when a hotplug pair straddles + the wake), and one third-party confound is untested: + MonitorControlLite (software brightness shade) was running during + all recorded runs. Cheap control series: quit MonitorControlLite + (slider at 100%), run ~6 soft-trigger runs — if blacks persist, + the confound is retired; Brisync was never running and is + irrelevant. + +**Update (2026-07-20) — confound retired; auto-recovery implemented.** +Runs 86–104 (matrix "shakedowns, MCL controls, groups C/D/E" table): + +- *Marker all-time tally: 56/56 black vs 0/33 clean* (17 more blacks, + 2 more cleans; every capture regime — cable, soft trigger, locked, + AC, lid). +- *MonitorControlLite retired as a confound*: 15 runs with MCL quit + produced 13 blacks, indistinguishable from before. Attribution + stands: a macOS SkyLight/DCP wake-path defect, host-side. +- *Recovery evidence complete*: locked displaysleep n=5 (runs 60, + 98–101); group D answered (six AC blacks — not power-dependent); + group E's lived scenario (run 104 lid-open wake) reproduced black + and recovered. Every programmatic displaysleep recovery across the + whole matrix has cleared its black. +- *Auto-recovery is now wired into the daemon.* At + `wakeSettleTimerFired`, when an external is present, the daemon + queries the unified log (spawning `log show` over a window opening + 30 s before the recorded wake — the same unprivileged path + `diagnose` uses) for the coalescing marker; if present it runs the + displaysleep cycle (`pmset displaysleepnow`, 2 s, `caffeinate -u`) + off the main queue. Gated by the new `recoveryStrategy` pref + (String, default `displaysleep`; `none` disables; unknown values + fall back to none), set via the new `blackoutd recovery + ` subcommand (SIGHUP reload, P23 pattern) and + shown in `status`/`config.txt`. Guards: once-per-wake latch, + `_systemSleeping` re-check, marker absence = no action; the + display-sleep cycle emits screen (not system) sleep notifications, + so it cannot re-arm the wake path and loop. **Matrix data + collection must now disable it first** (`blackoutd recovery none`) + or captures record the recovered panel — see the matrix how-to. +- *Flicker-free recovery and BetterDisplay RE remain open* (the + displaysleep cycle blinks both panels): candidates stay + `IOMobileFramebufferRequestPowerChange` (per-framebuffer, private) + and a BD `_reinitializeOnWake` RE pass — behind the now-standing + detector, as a follow-up PR. The extcycle recovery is ruled out for + automatic use (locked-session hazard, 2026-07-19 evening update). + A first probe is in the tree: `recover --method fbpower` (CLI-only, + dlsym-resolved, signatures unverified — a crash is a data point). + Smoke it against a soft-trigger black; even a refusal at + `IOMobileFramebufferOpenByName` is an answer, since the + HPD-notification detection idea rides on the same user-client + access. +- *Wake-cue audibility*: the "awake" `say` cue is intermittently + inaudible right after wake (audio-device bring-up race; both power + sources affected). Detection and captures are unaffected; the + Notification Center banner carries the timestamp. Known quirk. +- *Live validation (runs 106–107).* With `recoveryStrategy + displaysleep` active and no repro-side `--recover`, a soft-trigger + black wake (run 106) cleared to E0/B0 before the "capturing post + wake" cue, and a clean wake (run 107) passed through untouched — + and the maintainer perceived **no flicker**: firing the cycle at + wake-settle lands it inside the wake transition, before the panels + have visibly stabilized. That takes most of the urgency out of the + flicker-free follow-up (fbpower probe untried as of run 107); it + remains worth one smoke test on principle. + **Files**: `src/main.m` (the `diagnose` `dcp.txt` / `connection-mode.txt` readers); investigation otherwise. Relates to P2, P20, P28, ADR 0003, ADR 0009 (SP2309W YCbCr), and the inject_edid `--mode` reader (of which diff --git a/src/AppDelegate.m b/src/AppDelegate.m index f44bba9..d6727de 100644 --- a/src/AppDelegate.m +++ b/src/AppDelegate.m @@ -78,8 +78,9 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification { _displayController.delegate = self; [self reloadPreferences]; - NSLog(@"[startup] — verbosityLevel=%ld", - (long)_displayController.verbosityLevel); + NSLog(@"[startup] — verbosityLevel=%ld recoveryStrategy=%@", + (long)_displayController.verbosityLevel, + _displayController.recoveryStrategy); [self setupMenuBar]; [self setupSignalHandlers]; [self setupSleepWakeObservers]; @@ -152,6 +153,9 @@ - (void)reloadPreferences { ? [_defaults integerForKey:@"verbosityLevel"] : 1; _displayController.verbosityLevel = verbosity; + NSString *recovery = + [_defaults stringForKey:@"recoveryStrategy"] ?: @"displaysleep"; + _displayController.recoveryStrategy = recovery; } // MARK: - Quitting diff --git a/src/DisplayController.h b/src/DisplayController.h index 0e00d4c..460479c 100644 --- a/src/DisplayController.h +++ b/src/DisplayController.h @@ -35,6 +35,15 @@ NS_ASSUME_NONNULL_BEGIN /// killall -HUP blackoutd @property(nonatomic, assign) NSInteger verbosityLevel; +/// Post-wake automatic recovery for the cursor-on-black failure +/// (technical-debt.md P20/P29). At wake-settle time, when the WindowServer +/// hotplug-coalescing marker is present in this wake's unified-log window, +/// the daemon runs the configured recovery. Values: "none" (disabled) or +/// "displaysleep" (display-sleep cycle; the default). Unknown values fall +/// back to "none". Set via `blackoutd recovery ` or +/// reload via SIGHUP like verbosityLevel. +@property(nonatomic, copy) NSString *recoveryStrategy; + /// Set to YES during system sleep. Display change events are still logged /// but state changes are not acted on until wake. @property(nonatomic, assign) BOOL systemSleeping; diff --git a/src/DisplayController.m b/src/DisplayController.m index f4fa33a..77e97d6 100644 --- a/src/DisplayController.m +++ b/src/DisplayController.m @@ -201,6 +201,12 @@ @implementation DisplayController { // invalidateDisplayState). See incidents 14:42 and 17:28 on 2026-04-29 // in docs/debug/. NSInteger _failedActionRetries; + // Cursor-on-black wake recovery (P20/P29): timestamp of the last system + // wake (bounds the marker query window) and a once-per-wake latch so + // settle-timer re-fires (err=1014 retries re-arm it) cannot run the + // recovery twice for one wake. + NSDate *_lastWakeAt; + BOOL _wakeRecoveryAttempted; } - (instancetype)init { @@ -238,6 +244,23 @@ - (void)setVerbosityLevel:(NSInteger)verbosityLevel { NSLog(@"[prefs] verbosityLevel=%ld", (long)verbosityLevel); } +- (void)setRecoveryStrategy:(NSString *)recoveryStrategy { + // Unknown values disable the recovery rather than guessing: a typo in the + // pref must not silently pick an action. + NSString *normalized = recoveryStrategy; + if (![normalized isEqualToString:@"none"] && + ![normalized isEqualToString:@"displaysleep"]) { + NSLog(@"[prefs] recoveryStrategy=%@ unknown (known: none, displaysleep); " + @"using none", + normalized); + normalized = @"none"; + } + BOOL changed = ![normalized isEqualToString:_recoveryStrategy]; + _recoveryStrategy = [normalized copy]; + if (changed) + NSLog(@"[prefs] recoveryStrategy=%@", normalized); +} + - (BOOL)isBlackedOut { return _isBlackedOut; } @@ -329,6 +352,8 @@ - (BOOL)invalidateDisplayState { // MARK: - Wake Settle Timer (P0 / P2) - (void)handleSystemWake { + _lastWakeAt = NSDate.date; + _wakeRecoveryAttempted = NO; [self resetWakeSettleTimer]; } @@ -406,6 +431,136 @@ - (void)wakeSettleTimerFired { @"blackout action"); [self applyEnable:NO]; } + if (hasExternal) + [self attemptCursorOnBlackRecovery]; +} + +// MARK: - Cursor-on-black wake recovery (P20 / P29) + +// The one host-visible signal that separates cursor-on-black wakes from +// clean ones: SkyLight coalescing a still-pending hotplug "out" with the +// wake's "in" for the external display. Validated 56/56 black vs 0/33 +// clean across matrix runs 1-104 (docs/debug/cursor-on-black-matrix.md). +// Private log wording — pinned to macOS 26 behavior; absence is treated as +// "clean or unknown", never as an error. +static NSString *const kBDHotplugCoalescedNeedle = + @"for state \"out\" with \"in\""; + +// Queries the unified log for the coalescing marker between `since` and +// now, via /usr/bin/log show — the same unprivileged path `diagnose` uses +// for windowserver.txt. The predicate matches the stable message prefix; +// the exact out/in direction is confirmed in-process so a future +// in-with-out sibling line cannot false-positive. Runs off the main queue +// (the query takes ~1 s). Returns NO on any spawn/read failure. +static BOOL BDWakeMarkerPresentSince(NSDate *since) { + NSDateFormatter *f = [[NSDateFormatter alloc] init]; + f.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + // Explicit zone and offset: log(1) documents "YYYY-MM-DD HH:MM:SSZZZZZ" + // as an accepted --start form, so emit the offset (Z pattern -> "-0400") + // rather than relying on the formatter's local-zone default matching + // log's local-zone interpretation of an unqualified timestamp. + f.timeZone = NSTimeZone.localTimeZone; + f.dateFormat = @"yyyy-MM-dd HH:mm:ssZ"; + NSTask *task = [[NSTask alloc] init]; + task.executableURL = [NSURL fileURLWithPath:@"/usr/bin/log"]; + NSString *predicate = + @"process == \"WindowServer\" AND eventMessage CONTAINS \"Replacing " + @"existing hotplug event\""; + task.arguments = @[ + @"show", @"--style", @"compact", @"--start", [f stringFromDate:since], + @"--predicate", predicate + ]; + NSPipe *pipe = [NSPipe pipe]; + task.standardOutput = pipe; + task.standardError = [NSFileHandle fileHandleWithNullDevice]; + NSError *err = nil; + if (![task launchAndReturnError:&err]) { + NSLog(@"[wake] recovery marker query failed to launch: %@", + err.localizedDescription); + return NO; + } + NSData *data = [pipe.fileHandleForReading readDataToEndOfFile]; + [task waitUntilExit]; + if (task.terminationStatus != 0) { + NSLog(@"[wake] recovery marker query exited rc=%d", + (int)task.terminationStatus); + return NO; + } + NSString *out = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + return [out containsString:kBDHotplugCoalescedNeedle]; +} + +// Runs one display-sleep cycle: pmset displaysleepnow (unprivileged), a 2 s +// pause, then caffeinate -u to re-declare user activity and relight the +// panels. The one recovery the matrix confirms end-to-end, locked and +// unlocked (runs 37-104: every programmatic clear). Called off the main +// queue. +static void BDRunDisplaySleepCycle(void) { + NSTask *sleepTask = [[NSTask alloc] init]; + sleepTask.executableURL = [NSURL fileURLWithPath:@"/usr/bin/pmset"]; + sleepTask.arguments = @[ @"displaysleepnow" ]; + NSError *err = nil; + if (![sleepTask launchAndReturnError:&err]) { + NSLog(@"[wake] recovery displaysleep failed to launch: %@", + err.localizedDescription); + return; + } + [sleepTask waitUntilExit]; + if (sleepTask.terminationStatus != 0) { + NSLog(@"[wake] recovery displaysleep exited rc=%d — skipping wake nudge", + (int)sleepTask.terminationStatus); + return; + } + [NSThread sleepForTimeInterval:2.0]; + NSTask *wakeTask = [[NSTask alloc] init]; + wakeTask.executableURL = [NSURL fileURLWithPath:@"/usr/bin/caffeinate"]; + wakeTask.arguments = @[ @"-u", @"-t", @"2" ]; + if (![wakeTask launchAndReturnError:&err]) { + NSLog(@"[wake] recovery caffeinate failed to launch: %@", + err.localizedDescription); + return; + } + [wakeTask waitUntilExit]; + NSLog(@"[wake] recovery=displaysleep cycle complete (pmset rc=%d)", + (int)sleepTask.terminationStatus); +} + +// At settle time, decide whether this wake needs the recovery. Gated on the +// recoveryStrategy pref, once per wake, and on the marker being present in +// this wake's log window. The display-sleep cycle produces screen (not +// system) sleep notifications, so it cannot re-arm handleSystemWake and +// loop; the once-per-wake latch is belt and suspenders. +- (void)attemptCursorOnBlackRecovery { + if (![_recoveryStrategy isEqualToString:@"displaysleep"]) + return; + if (_wakeRecoveryAttempted || !_lastWakeAt) + return; + _wakeRecoveryAttempted = YES; + // Query from shortly before the wake: the marker lands at the wake + // instant. 30 s comfortably covers the skew between the marker's log + // timestamp and _lastWakeAt (the NSWorkspace notification can trail the + // hardware wake by a few seconds) plus log-flush latency, while staying + // far inside the >= 2-3 min spacing of consecutive repro wakes — so the + // previous wake's marker can never leak into this window. + NSDate *since = [_lastWakeAt dateByAddingTimeInterval:-30.0]; + __weak typeof(self) weakSelf = self; + dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ + BOOL present = BDWakeMarkerPresentSince(since); + if (!present) { + NSLog(@"[wake] recovery=displaysleep marker=absent — no action"); + return; + } + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf || strongSelf->_systemSleeping) { + NSLog(@"[wake] recovery=displaysleep marker=present but system " + @"sleeping — skipped"); + return; + } + NSLog(@"[wake] recovery=displaysleep marker=present — issuing " + @"display-sleep cycle"); + BDRunDisplaySleepCycle(); + }); } // MARK: - Private diff --git a/src/main.m b/src/main.m index a78cf0f..d0029c0 100644 --- a/src/main.m +++ b/src/main.m @@ -11,6 +11,7 @@ #import #import #import +#import #import #import #import @@ -31,6 +32,7 @@ static NSString *const kSuiteName = @"blackoutd"; static NSString *const kAutoBlackoutKey = @"autoBlackoutOnExternalConnect"; static NSString *const kVerbosityKey = @"verbosityLevel"; +static NSString *const kRecoveryKey = @"recoveryStrategy"; static NSString *const kAgentLabel = @BD_BUNDLE_ID; static NSString *agentPlistPath(void) { @@ -575,6 +577,8 @@ static void appendConnectionModes(NSMutableString *r) { [r appendFormat:@"daemon log tail : %@\n", state]; [r appendFormat:@"auto-blackout : %s\n", autoMode ? "enabled" : "disabled"]; [r appendFormat:@"verbosity : %ld\n", (long)verbosity]; + [r appendFormat:@"recovery : %@\n", + [defaults stringForKey:kRecoveryKey] ?: @"displaysleep"]; [r appendFormat:@"bundle-id : %s\n", kBundleID.UTF8String]; [r appendFormat:@"lid : %@\n", clamshellState()]; [r appendFormat:@"session : %@\n", sessionLockState()]; @@ -834,6 +838,8 @@ static int printStatus(void) { builtInIsOnline() ? "active" : "blacked out"); printf(" auto-blackout : %s\n", autoMode ? "enabled" : "disabled"); printf(" verbosity : %ld\n", (long)verbosity); + printf(" recovery : %s\n", + ([defaults stringForKey:kRecoveryKey] ?: @"displaysleep").UTF8String); return pid > 0 ? 0 : 1; } @@ -851,6 +857,45 @@ static int setAutoBlackout(const char *value) { return sendSignalToDaemon(SIGHUP); } +// Sets the daemon's post-wake cursor-on-black recovery strategy, persisted +// in NSUserDefaults and applied live via SIGHUP — same pattern as +// setVerbosity below. "displaysleep" (the default) runs a display-sleep +// cycle at wake-settle when the WindowServer hotplug-coalescing marker is +// present (P20/P29); "none" disables it — required before matrix baseline +// data collection, which must observe the un-recovered wake. +static int setRecoveryStrategy(const char *value) { + static const char *kRecoveryUsage = + "Usage: blackoutd recovery \n"; + if (value == NULL || + (strcmp(value, "none") != 0 && strcmp(value, "displaysleep") != 0)) { + fprintf(stderr, "%s", kRecoveryUsage); + return 1; + } + NSUserDefaults *defaults = + [[NSUserDefaults alloc] initWithSuiteName:kSuiteName]; + [defaults setObject:@(value) forKey:kRecoveryKey]; + [defaults synchronize]; + NSString *applied = [defaults stringForKey:kRecoveryKey]; + pid_t pid = daemonPid(); + if (pid > 0) { + if (kill(pid, SIGHUP) != 0) { + if (errno == ESRCH) { + printf("recovery: %s (daemon exited before signal delivery; takes " + "effect on next start)\n", + applied.UTF8String); + return 0; + } + perror("blackoutd: kill SIGHUP"); + return 1; + } + printf("recovery: %s (daemon notified)\n", applied.UTF8String); + } else { + printf("recovery: %s (daemon not running; takes effect on next start)\n", + applied.UTF8String); + } + return 0; +} + // Sets the daemon's log verbosity level, persisted in NSUserDefaults so it // survives daemon restart, and signals the running daemon (if any) to // reload preferences immediately. Single-step replacement for the @@ -958,6 +1003,11 @@ static void printUsage(void) { " auto on|off Enable or disable auto-blackout on external connect\n" " verbosity Set daemon log verbosity (0=quiet, 1=normal," " 2=verbose)\n" + " recovery Set post-wake cursor-on-black auto-recovery:\n" + " displaysleep (default) runs a display-sleep cycle\n" + " when the wake carries the WindowServer marker;\n" + " none disables (required before repro baseline\n" + " data collection)\n" " diagnose Collect a diagnostic bundle for bug reports\n" " (auto-bounds the window to the last wake; override\n" " with --minutes N or --start \"T\" --end \"T\";\n" @@ -968,12 +1018,23 @@ static void printUsage(void) { " daemon stop Stop the daemon and restore built-in display\n" "\n" "Experimental (cursor-on-black investigation; maintainer-run):\n" - " recover Run one display-sleep recovery cycle\n" - " (--method displaysleep, --dry-run)\n" + " recover Run one recovery cycle (--method METHOD, --dry-run)\n" + " Methods: displaysleep — display-sleep cycle (pmset\n" + " displaysleepnow + caffeinate -u; the validated\n" + " recovery); extcycle — disable/re-enable the external\n" + " display via CG (unlocked sessions ONLY — hazardous\n" + " while locked); fbpower — experimental private\n" + " IOMobileFramebuffer power-cycle probe (unverified\n" + " signatures; flicker-free candidate)\n" " repro Sleep/wake repro with labeled captures + recovery;\n" " emits a numbered, prefilled run sheet and copies\n" " bundles to docs/debug/ when run from the repo root\n" - " (--wake N, --settle S, --recover METHOD,\n" + " (--wake N [seconds; 0 = manual wake], --settle S,\n" + " --recover METHOD [as above],\n" + " --trigger extcycle [pre-sleep software analog of\n" + " the C1 cable trigger — see the matrix legend],\n" + " --lock [lock the session just before sleep;\n" + " keep the Apple Watch off/out of range],\n" " --group a..e [matrix coverage row, any case],\n" " --no-copy, --no-prompt, --silent, --dry-run)\n" "\n" @@ -1072,14 +1133,161 @@ static void sayCue(NSString *text, BOOL silent, BOOL dryRun) { runStep(@"/usr/bin/say", @[ text ], dryRun); } -// Performs one recovery attempt. The only method today is "displaysleep": a -// programmatic display-sleep cycle (pmset displaysleepnow, then caffeinate -u -// to re-declare user activity and wake the panel). Flicker is accepted; this -// supersedes the older "displaysleepnow = flicker dead end" note now that the -// maintainer confirms it reliably recovers regardless of lock state. +// Private API (see DisplayController.m for the daemon-side declaration and +// rationale): enables/disables a display inside a CGDisplayConfigRef +// transaction. Declared here for the CLI-side extcycle primitive below. +extern CGError CGSConfigureDisplayEnabled(CGDisplayConfigRef, CGDirectDisplayID, + bool); + +// Returns the first online hardware-backed external display, or +// kCGNullDirectDisplay if none. Virtual/placeholder displays carry FourCC +// pseudo-vendor IDs > 0xFFFF (see displayIsHardwareBacked() in +// DisplayController.m; the vendor heuristic alone suffices here and avoids +// the deprecated CGDisplayIOServicePort in this file). +static CGDirectDisplayID firstExternalDisplayID(void) { + CGDirectDisplayID displays[8]; + uint32_t count = 0; + CGGetOnlineDisplayList(8, displays, &count); + for (uint32_t i = 0; i < count; i++) { + if (!CGDisplayIsBuiltin(displays[i]) && + CGDisplayVendorNumber(displays[i]) <= 0xFFFF) + return displays[i]; + } + return kCGNullDirectDisplay; +} + +// CLI-side mirror of DisplayController's setDisplay:enabled: transaction +// (without the daemon's recommit preamble). The resident daemon reacts to +// the resulting reconfiguration callbacks exactly as it would to a physical +// unplug/replug: safety-invariant restore on disable, auto-blackout on +// re-enable. +static CGError cliSetDisplayEnabled(CGDirectDisplayID display, bool enabled) { + CGDisplayConfigRef config; + CGError err = CGBeginDisplayConfiguration(&config); + if (err != kCGErrorSuccess) + return err; + err = CGSConfigureDisplayEnabled(config, display, enabled); + if (err != kCGErrorSuccess) { + CGCancelDisplayConfiguration(config); + return err; + } + return CGCompleteDisplayConfiguration(config, kCGConfigurePermanently); +} + +// The "extcycle" primitive — software analog of the C1 cable trigger and +// P29's recovery lever (b): disable the external display, wait for the +// built-in to come back and fully redraw (the daemon's safety invariant +// restores it; C1's lesson is that replugging before the redraw completes +// weakens the trigger), then re-enable the external and give it the +// few-seconds re-attach settle (P20). Serves as both a repro --trigger +// (provoke cursor-on-black on the next wake) and a --recover method +// (tear down and rebuild the external's configuration). CG-level: whether +// it reaches the DCP where the no-op recommit did not is exactly the open +// question it exists to answer. +static int performExternalCycle(BOOL dryRun) { + if (dryRun) { + printf(" [dry-run] extcycle: disable external, wait 5 s, re-enable, " + "wait 5 s\n"); + return 0; + } + CGDirectDisplayID external = firstExternalDisplayID(); + if (external == kCGNullDirectDisplay) { + fprintf(stderr, "blackoutd: extcycle: no external display online\n"); + return 1; + } + CGError err = cliSetDisplayEnabled(external, false); + if (err != kCGErrorSuccess) { + fprintf(stderr, "blackoutd: extcycle: disable failed (CGError %d)\n", + (int)err); + return 1; + } + [NSThread sleepForTimeInterval:5.0]; + err = cliSetDisplayEnabled(external, true); + if (err != kCGErrorSuccess) { + fprintf(stderr, "blackoutd: extcycle: re-enable failed (CGError %d)\n", + (int)err); + return 1; + } + [NSThread sleepForTimeInterval:5.0]; + return 0; +} + +// Experimental flicker-free recovery candidate (P29): power-cycle only the +// external framebuffer via private IOMobileFramebuffer.framework calls, +// resolved with dlsym so the binary carries no link-time dependency. The +// signatures are reconstructed from public reverse-engineering headers and +// are UNVERIFIED on macOS 26 — a crash of this CLI invocation is a data +// point, not a regression. Even a refusal at open answers a standing +// question (whether a third-party process can reach the IOMFB user client +// at all — the same gate the HPD-notification detection idea depends on). +// CLI-only: the daemon's recoveryStrategy accepts only none|displaysleep. +static int performFramebufferPowerCycle(BOOL dryRun) { + if (dryRun) { + printf(" [dry-run] fbpower: open external-0 via IOMobileFramebuffer, " + "power 0, wait 2 s, power 1, wait 5 s\n"); + return 0; + } + void *fw = dlopen("/System/Library/PrivateFrameworks/" + "IOMobileFramebuffer.framework/IOMobileFramebuffer", + RTLD_LAZY); + if (!fw) { + fprintf(stderr, "blackoutd: fbpower: dlopen failed: %s\n", dlerror()); + return 1; + } + typedef int (*BDIOMFBOpenByNameFn)(CFStringRef, void **); + typedef int (*BDIOMFBRequestPowerChangeFn)(void *, uint32_t); + BDIOMFBOpenByNameFn openByName = + (BDIOMFBOpenByNameFn)dlsym(fw, "IOMobileFramebufferOpenByName"); + BDIOMFBRequestPowerChangeFn requestPower = (BDIOMFBRequestPowerChangeFn)dlsym( + fw, "IOMobileFramebufferRequestPowerChange"); + if (!openByName || !requestPower) { + fprintf(stderr, + "blackoutd: fbpower: symbol lookup failed (OpenByName=%d " + "RequestPowerChange=%d)\n", + openByName != NULL, requestPower != NULL); + return 1; + } + void *fb = NULL; + // "external-0" is the framebuffer name WindowServer's own open logs show + // for the external display on this machine. + int rc = openByName(CFSTR("external-0"), &fb); + printf("fbpower: OpenByName(external-0) rc=0x%x fb=%p\n", rc, fb); + if (rc != 0 || fb == NULL) + return 1; + // Power values mirror IOMFB's own update_power_state logging (0 = off, + // 1 = on); whether this API level uses the same scale is part of what + // the probe answers. + rc = requestPower(fb, 0); + printf("fbpower: RequestPowerChange(0) rc=0x%x\n", rc); + [NSThread sleepForTimeInterval:2.0]; + int rc2 = requestPower(fb, 1); + printf("fbpower: RequestPowerChange(1) rc=0x%x\n", rc2); + [NSThread sleepForTimeInterval:5.0]; + return (rc == 0 && rc2 == 0) ? 0 : 1; +} + +// Performs one recovery attempt. Methods: +// displaysleep — programmatic display-sleep cycle (pmset displaysleepnow, +// then caffeinate -u to re-declare user activity and wake +// the panel). Flicker is accepted; this supersedes the +// older "displaysleepnow = flicker dead end" note now that +// the matrix confirms it reliably recovers regardless of +// lock state (10/10 through run 60). +// extcycle — external display disable/re-enable cycle (see +// performExternalCycle above). Clears blacks unlocked +// (10/10, runs 69-80) but is hazardous while locked +// (runs 81-85) — never wire it to automatic use. +// fbpower — experimental IOMobileFramebuffer probe (see +// performFramebufferPowerCycle above). static int performRecovery(NSString *method, BOOL dryRun) { + if ([method isEqualToString:@"extcycle"]) + return performExternalCycle(dryRun); + if ([method isEqualToString:@"fbpower"]) + return performFramebufferPowerCycle(dryRun); if (![method isEqualToString:@"displaysleep"]) { - fprintf(stderr, "blackoutd: unknown recovery method '%s'\n", + fprintf(stderr, + "blackoutd: unknown recovery method '%s' (known: displaysleep, " + "extcycle, fbpower)\n", method.UTF8String); return 1; } @@ -1091,7 +1299,7 @@ static int performRecovery(NSString *method, BOOL dryRun) { return runStep(@"/usr/bin/caffeinate", @[ @"-u", @"-t", @"2" ], dryRun); } -// blackoutd recover [--method displaysleep] [--dry-run] +// blackoutd recover [--method displaysleep|extcycle] [--dry-run] static int recoverCommand(int argc, const char *argv[]) { NSString *method = @"displaysleep"; BOOL dryRun = NO; @@ -1266,13 +1474,12 @@ static NSInteger ansIndex(NSDictionary *ans, // `settleMarker` is sampled ONCE by the caller and passed to every render: // re-reading it on the post-prompt rewrite could replace the post-wake // marker with one from a later event (e.g. a manual hot-corner recovery). -static NSString * -renderRunSheet(NSDate *started, int runNumber, int wake, int settle, - NSString *recover, NSString *group, NSString *lidPre, - NSString *lidCap, NSString *lockPre, NSString *lockCap, - NSString *powerPre, NSString *powerCap, NSString *settleMarker, - NSString *postWakeBundle, NSString *postRecoverBundle, - NSDictionary *ans) { +static NSString *renderRunSheet( + NSDate *started, int runNumber, int wake, int settle, NSString *recover, + NSString *trigger, NSString *group, NSString *lidPre, NSString *lidCap, + NSString *lockPre, NSString *lockCap, NSString *powerPre, + NSString *powerCap, NSString *settleMarker, NSString *postWakeBundle, + NSString *postRecoverBundle, NSDictionary *ans) { NSDateFormatter *stamp = [[NSDateFormatter alloc] init]; stamp.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; stamp.dateFormat = @"yyyyMMdd-HHmmss"; @@ -1329,6 +1536,8 @@ static NSInteger ansIndex(NSDictionary *ans, ? [NSString stringWithFormat:@"scheduled (--wake %d)", wake] : @"manual (--wake 0)"]; + [s appendFormat:@" trigger ................ %@\n", + trigger.length ? trigger : @"none"]; [s appendFormat:@" recovery applied ....... %@\n", recover.length ? recover : @"none"]; [s appendFormat:@" settle ................. %d s\n", settle]; @@ -1593,14 +1802,17 @@ static NSInteger promptChoice(NSString *question, int maxIndex, } // blackoutd repro [--wake N] [--settle S] [--recover METHOD] [--group G] -// [--no-copy] [--no-prompt] [--silent] [--dry-run] -// Schedules an auto-wake (sudo — only the schedule needs it), sleeps, then on -// wake captures a labeled bundle, optionally runs a recovery, and captures +// [--trigger extcycle] [--lock] [--no-copy] [--no-prompt] +// [--silent] [--dry-run] +// Primes sudo, optionally fires a pre-sleep trigger (the software analog +// of the C1 cable trigger), schedules an auto-wake (sudo — only the +// schedule needs it), optionally locks the session, sleeps, then on wake +// captures a labeled bundle, optionally runs a recovery, and captures // again. Built to run blind: it narrates each step via `say`. static int reproCommand(int argc, const char *argv[]) { int wake = 15, settle = 20; - NSString *recover = nil, *group = nil; - BOOL silent = NO, dryRun = NO, noCopy = NO, noPrompt = NO; + NSString *recover = nil, *group = nil, *trigger = nil; + BOOL silent = NO, dryRun = NO, noCopy = NO, noPrompt = NO, lock = NO; for (int i = 2; i < argc; i++) { const char *opt = argv[i]; if (strcmp(opt, "--silent") == 0) { @@ -1611,6 +1823,22 @@ static int reproCommand(int argc, const char *argv[]) { noCopy = YES; } else if (strcmp(opt, "--no-prompt") == 0) { noPrompt = YES; + } else if (strcmp(opt, "--lock") == 0) { + lock = YES; + } else if (strcmp(opt, "--trigger") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "blackoutd: --trigger requires a value\n"); + return 1; + } + const char *val = argv[++i]; + // Validate at parse time: an unknown trigger discovered pre-sleep + // would otherwise abort the run after the sudo prompt already fired. + if (strcmp(val, "extcycle") != 0) { + fprintf(stderr, "blackoutd: unknown trigger '%s' (known: extcycle)\n", + val); + return 1; + } + trigger = @(val); } else if (strcmp(opt, "--wake") == 0 || strcmp(opt, "--settle") == 0 || strcmp(opt, "--recover") == 0 || strcmp(opt, "--group") == 0) { if (i + 1 >= argc) { @@ -1669,27 +1897,91 @@ static int reproCommand(int argc, const char *argv[]) { printf("repro: started %s — build %s (built %s)\n", [human stringFromDate:started].UTF8String, BD_BUILD_GIT, BD_BUILD_TIME); - // Schedule the auto-wake first (only this needs sudo). wake=0 means the - // maintainer will wake the machine manually (e.g. by opening the lid). + // Prime sudo credentials up front (one interactive prompt at most, + // screen lit, nothing captured yet), so the schedule step below never + // prompts. Previously the matrix doc asked for a manual `sudo --validate + // &&` prefix; building it in removes that failure mode. + if (wake > 0) { + printf("repro: priming sudo credentials (sudo --validate)\n"); + if (runStep(@"/usr/bin/sudo", @[ @"--validate" ], dryRun) != 0) { + fprintf(stderr, "repro: sudo --validate failed; aborting\n"); + return 1; + } + } + + // Pre-sleep trigger, BEFORE the wake is scheduled: the cycle takes + // ~12-20 s (two cues, two 5 s settles, CG transactions), which is most + // of a --wake 15 lead — scheduling first left the wake time in the past + // by the time the machine slept, the cause of every W1 "scheduled wake + // failed" run (61-80). A failed trigger aborts: nothing is captured + // yet, and a run whose trigger silently misfired would poison the + // matrix's C1-vs-soft data. + if (trigger.length) { + sayCue(@"triggering external cycle", silent, dryRun); + int trigRC = performExternalCycle(dryRun); + if (trigRC != 0) { + fprintf(stderr, + "repro: trigger '%s' failed (rc=%d); aborting pre-sleep\n", + trigger.UTF8String, trigRC); + return 1; + } + } + + // Lock the session before the conditions read records the lock state. + // Same mechanism as the manual group C invocation this replaces: a + // ctrl-cmd-q keystroke via System Events (needs Accessibility permission + // for the terminal app). Remember to keep the Apple Watch off/out of + // range for the run. + if (lock) { + sayCue(@"locking session", silent, dryRun); + NSString *lockScript = + @"Application(\"System Events\").keystroke(\"q\", {using:[\"control " + @"down\",\"command down\"]})"; + int lockRC = runStep(@"/usr/bin/osascript", + @[ @"-l", @"JavaScript", @"-e", lockScript ], dryRun); + if (lockRC != 0) { + fprintf(stderr, + "repro: session lock failed (rc=%d); aborting — a run " + "recorded as locked must actually be locked\n", + lockRC); + return 1; + } + if (!dryRun) + [NSThread sleepForTimeInterval:2.0]; + } + + // Conditions are read twice — just before sleep and again just before + // the capture — so the sheet shows both ends of the run; only "did the + // lid move DURING sleep" stays a maintainer field (unobservable from + // either end). The only interactive prompt (sudo --validate) already + // ran, so this reading cannot go stale behind a prompt. + NSString *lidPre = clamshellStateShort(); + NSString *lockPre = sessionLockState(); + NSString *powerPre = powerSource(); + + // Schedule the auto-wake LAST, immediately before sleepnow, so the lead + // is consumed only by falling asleep — cue speech, lock, and condition + // reads all happen before the clock starts. (The machine takes ~5-10 s + // to commit to sleep after sleepnow; a wake time that passes during + // that window is silently dropped, which is what made short effective + // leads unreliable.) wake=0 means the maintainer will wake the machine + // manually (e.g. by opening the lid). if (wake > 0) { NSDateFormatter *f = [[NSDateFormatter alloc] init]; f.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; f.dateFormat = @"MM/dd/yy HH:mm:ss"; NSString *when = [f stringFromDate:[NSDate dateWithTimeIntervalSinceNow:wake]]; - // Probe with --non-interactive first: when credentials are primed - // (sudo --validate, per the matrix doc) this never prompts. If they are - // not — including policies that disable credential caching entirely - // (timestamp_timeout=0), where priming cannot help — fall back to one - // interactive prompt. The fallback is safe by construction: this is - // repro's first step, before any sleep, with the screen lit; the blind - // phase contains no sudo calls. printf("repro: scheduling wake at %s (sudo)\n", when.UTF8String); + // Non-interactive only when locked (nobody can answer a prompt); the + // interactive fallback remains for unlocked runs under policies that + // disable credential caching (timestamp_timeout=0), where the + // --validate priming cannot help. int schedRC = runStep(@"/usr/bin/sudo", @[ @"--non-interactive", @"pmset", @"schedule", @"wake", when ], dryRun); - if (schedRC != 0 && !dryRun) { + if (schedRC != 0 && !dryRun && !lock) { fprintf(stderr, "repro: sudo credentials not cached; prompting now " "(pre-sleep)\n"); schedRC = runStep(@"/usr/bin/sudo", @@ -1702,18 +1994,69 @@ static int reproCommand(int argc, const char *argv[]) { } } - // Conditions are read twice — just before sleep and again just before - // the capture — so the sheet shows both ends of the run; only "did the - // lid move DURING sleep" stays a maintainer field (unobservable from - // either end). Read AFTER the schedule step: a sudo prompt can sit for - // minutes, and a pre-prompt reading would be stale. - NSString *lidPre = clamshellStateShort(); - NSString *lockPre = sessionLockState(); - NSString *powerPre = powerSource(); + // Register for the sleep/wake notifications BEFORE issuing sleepnow so + // neither can be missed. NSWorkspaceDidWakeNotification is the + // deterministic wake signal (fires however short the sleep was — a + // wall-clock tick-jump heuristic missed sub-5 s sleeps, the shakedown + // failure after run 85); the will-sleep observer feeds a 60 s + // did-not-sleep guard so a sleepnow that silently fails cannot hang the + // run. The wake side has no ceiling on purpose: a manual overnight wake + // is a legitimate run shape. + __block BOOL sleepSeen = NO, wakeSeen = NO; + NSNotificationCenter *wsCenter = + NSWorkspace.sharedWorkspace.notificationCenter; + id willSleepObs = + [wsCenter addObserverForName:NSWorkspaceWillSleepNotification + object:nil + queue:nil + usingBlock:^(NSNotification *__unused note) { + sleepSeen = YES; + }]; + id didWakeObs = + [wsCenter addObserverForName:NSWorkspaceDidWakeNotification + object:nil + queue:nil + usingBlock:^(NSNotification *__unused note) { + wakeSeen = YES; + }]; sayCue(@"sleeping now", silent, dryRun); - if (runStep(@"/usr/bin/pmset", @[ @"sleepnow" ], dryRun) != 0) + int sleepRC = runStep(@"/usr/bin/pmset", @[ @"sleepnow" ], dryRun); + if (sleepRC != 0) { + [wsCenter removeObserver:willSleepObs]; + [wsCenter removeObserver:didWakeObs]; return 1; - // System sleeps here; execution resumes on the scheduled wake. + } + // System sleeps here. Run the run loop in 1 s slices (delivering the + // observers' notifications) until the wake arrives. The tick-jump check + // stays as a fallback in case notification delivery ever fails in a + // CLI process: a slice that spans > 5 s of wall clock straddled a + // sleep. + if (!dryRun) { + NSDate *sleepCmdAt = NSDate.date; + for (;;) { + NSDate *tick = NSDate.date; + [[NSRunLoop currentRunLoop] + runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; + if (wakeSeen || -[tick timeIntervalSinceNow] > 5.0) + break; + if (!sleepSeen && -[sleepCmdAt timeIntervalSinceNow] > 60.0) { + fprintf(stderr, + "repro: no sleep observed within 60 s of sleepnow (or the " + "sleep notification was missed); aborting\n"); + [wsCenter removeObserver:willSleepObs]; + [wsCenter removeObserver:didWakeObs]; + return 1; + } + } + } + [wsCenter removeObserver:willSleepObs]; + [wsCenter removeObserver:didWakeObs]; + // First cue after wake — within ~1 s of the wake instant, so the + // eyewitness can anchor the "immediate" observation to it while the + // panels first light. The settle countdown starts here, so the capture + // is a consistent `settle` seconds post-wake regardless of how long the + // machine slept. + sayCue(@"awake", silent, dryRun); if (!dryRun) [NSThread sleepForTimeInterval:settle]; @@ -1799,10 +2142,10 @@ static int reproCommand(int argc, const char *argv[]) { // Write the blank sheet BEFORE the prompt pass: an unanswered or // interrupted pass must still leave the run fully recorded on disk. sheetPath = runSheetPath(started, group, &runNumber); - NSString *sheet = - renderRunSheet(started, runNumber, wake, settle, recover, group, lidPre, - lidCap, lockPre, lockCap, powerPre, powerCap, - settleMarker, postWakeShown, postRecoverShown, nil); + NSString *sheet = renderRunSheet(started, runNumber, wake, settle, recover, + trigger, group, lidPre, lidCap, lockPre, + lockCap, powerPre, powerCap, settleMarker, + postWakeShown, postRecoverShown, nil); if (writeBundleText(sheet, sheetPath)) printf("repro: run sheet written to %s\n", sheetPath.UTF8String); else { @@ -1831,9 +2174,9 @@ static int reproCommand(int argc, const char *argv[]) { sayCue(@"prompts timed out, sheet left blank", silent, NO); } else if (answers.count) { NSString *filled = renderRunSheet( - started, runNumber, wake, settle, recover, group, lidPre, lidCap, - lockPre, lockCap, powerPre, powerCap, settleMarker, postWakeShown, - postRecoverShown, answers); + started, runNumber, wake, settle, recover, trigger, group, lidPre, + lidCap, lockPre, lockCap, powerPre, powerCap, settleMarker, + postWakeShown, postRecoverShown, answers); if (writeBundleText(filled, sheetPath)) printf("repro: observations recorded in %s\n", sheetPath.UTF8String); else { @@ -1946,6 +2289,12 @@ int main(int argc, const char *argv[]) { return 1; } return setVerbosity(argv[2]); + } else if (strcmp(cmd, "recovery") == 0) { + if (argc < 3) { + fprintf(stderr, "Usage: blackoutd recovery \n"); + return 1; + } + return setRecoveryStrategy(argv[2]); } // "daemon" with no subcommand falls through to daemon run loop below. if (strcmp(cmd, "daemon") != 0) {