Issue #23: boot lock-screen silent-capture self-heal (re-open ladder)

Corrected diagnosis (Ed: the machine's OWN speakers audibly play the Windows tune
and NVDA at the boot lock screen — the endpoint is NOT silent): a loopback capture
attached in the first seconds of boot can land on an audio-engine mix the
logon-session audio path was never wired into. Callbacks flow (fed by our own
silence keepalive) but carry none of the audio that is audibly playing, and Windows
fires no device event about it — the previously-shipped device-change reopen never
triggers. Signing in re-plumbs the session audio into the graph, which is why sound
starts instantly with zero change on our side; a capture opened after sign-out
(graph fully live) works at the lock screen, matching Jonathan's reports exactly.

Fix: while sending, the 15s capture pulse now drives a self-heal — if the capture
has heard only silence since it opened (pre-encode peak < 0.001 on every pulse), or
its callbacks freeze, tear down and re-open the capture so it re-attaches to the
live graph. Capped at 3 attempts per sending stint; the first real audio ends the
ladder so a quiet-but-healthy capture is never churned. Ladder refills on Resume()
and on power resume (wake re-plumbs the graph like boot). Every re-open is logged
with the attempt count, so Jonathan's next log shows either "re-open recovered
audio" (fixed) or three silent re-opens (deeper Windows routing issue, and we know
exactly where we stand).

Decision core (ShouldReopenSilentCapture) is pure and pinned by a new self-test.
Gate: 39/39.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-07-16 23:08:21 +01:00
co-authored by Claude Opus 4.8
parent 1003bbfdf2
commit c72169c7f5
2 changed files with 69 additions and 6 deletions
+20
View File
@@ -68,6 +68,7 @@ internal static class SelfTest
RunStep(results, "Service send host (headless stream + yield)", ServiceSendHostStream);
RunStep(results, "Service network presence (reachable + shell teardown)", ServiceNetworkPresenceReachable);
RunStep(results, "Service reachability-gated sending (drop dead peers, re-arm recovered)", ServiceReachabilityGating);
RunStep(results, "Service silent-capture self-heal (issue #23 boot re-open ladder)", ServiceSilentCaptureSelfHeal);
RunStep(results, "Send-app capture change-detection (catch an app the instant it opens)", SendAppCaptureChangeDetection);
RunStep(results, "Remembered applications list is global + clearable", RememberedApplicationsGlobal);
RunStep(results, "Send-app lists semantics (ticked → Active, out of Remembered)", SendAppListSemantics);
@@ -1547,6 +1548,25 @@ internal static class SelfTest
return "reachable armed; long-unreachable dropped; grace-window kept; recovery re-arms (issues #8/#15)";
}
/// <summary>Issue #23 boot self-heal decision core: a capture that has heard only silence since it
/// opened (or whose callbacks froze) gets re-opened, capped at 3 attempts per stint, and the ladder
/// ends for good once real audio has been heard. The scenario: at the boot lock screen the machine's
/// speakers audibly play (Windows tune, NVDA) but a capture attached in the first seconds of boot
/// taps an engine mix the logon-session audio was never wired into — re-attaching lands on the live
/// graph.</summary>
private static string? ServiceSilentCaptureSelfHeal()
{
Check(ServiceSendHost.ShouldReopenSilentCapture(stalled: false, everHeardAudio: false, attemptsSoFar: 0),
"a capture that has never heard audio must be re-opened");
Check(ServiceSendHost.ShouldReopenSilentCapture(stalled: true, everHeardAudio: true, attemptsSoFar: 0),
"a stalled capture must be re-opened even after audio has been heard");
Check(!ServiceSendHost.ShouldReopenSilentCapture(stalled: false, everHeardAudio: true, attemptsSoFar: 0),
"a healthy capture that has heard real audio must be left alone");
Check(!ServiceSendHost.ShouldReopenSilentCapture(stalled: false, everHeardAudio: false, attemptsSoFar: 3),
"the re-open ladder must stop at the attempt cap");
return "silent-since-open and stalled captures re-open; heard-audio healthy captures don't; capped at 3";
}
/// <summary>The fix for "a saved app that launches later never gets captured": the send engine
/// re-applies capture whenever the ticked apps' running process ids change. This tests the pure
/// change-detector that drives it — the signature is stable while nothing changes (so we don't churn),