Peer extraction stage 1: arming logic shared in Core (PeerArming)

The 'which selected peers do we actually stream to' rule lived as near-identical
private copies in MainForm.RefreshAudioReceivers and ServiceSendHost (prune peers
unreachable past the grace window - issue #8 - re-arm on recovery, change-detect via
an ordered signature). Now ONE Core home: PeerArming.ComputeArmedEndpoints with the
app's actively-receiving carve-out injected as a predicate (asymmetric-path streams
are never cut), plus the shared order-independent Signature. Both sides delegate;
behaviour identical by construction. Reachability test extended: carve-out honoured,
false predicate doesn't rescue a dead peer, signature order-free. Gate 61/61.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-07-26 22:48:41 +01:00
co-authored by Claude Fable 5
parent e3bf28a414
commit 1b7fb47cd3
4 changed files with 66 additions and 35 deletions
+7 -20
View File
@@ -5888,25 +5888,12 @@ public sealed class MainForm : Form
// Carve-out: a peer we are actively RECEIVING audio from stays armed even if its heartbeat
// reads Unreachable — that covers an asymmetric path where audio flows but the heartbeat
// round-trip doesn't, so a working stream is never cut.
HashSet<string>? dead = null;
if (all.Length > 0 && heartbeatService is { } hb)
{
foreach (var ph in hb.GetAllPeerHealth())
{
if (ph.State == PeerHealthState.Unreachable
&& ph.AgeOfLastPong is { } age
&& age > AudioPruneUnreachableAfter
&& !receiver.IsAudioFlowingFrom(ph.AudioEndpoint.Address, TimeSpan.FromSeconds(3)))
{
(dead ??= new HashSet<string>(StringComparer.OrdinalIgnoreCase))
.Add($"{ph.AudioEndpoint.Address}:{ph.AudioEndpoint.Port}");
}
}
}
var armed = dead is null
? all
: all.Where(ep => !dead.Contains($"{ep.Address}:{ep.Port}")).ToArray();
// The prune itself is the shared Core rule (PeerArming) — same code the service arms with. The
// app's receiving-carve-out rides in as the keepAnyway predicate.
var armed = all.Length > 0 && heartbeatService is { } hb
? PeerArming.ComputeArmedEndpoints(all, hb.GetAllPeerHealth(), AudioPruneUnreachableAfter,
keepAnyway: addr => receiver.IsAudioFlowingFrom(addr, TimeSpan.FromSeconds(3)))
: all;
// There used to be a "never silence EVERY peer" safety net here that re-armed the whole
// set when pruning would leave nobody. Removed 2026-06-12: when NO peer is reachable we
@@ -5916,7 +5903,7 @@ public sealed class MainForm : Form
// after the only receiver was switched off hours earlier). The heartbeat still probes all
// peers, so the moment one answers again it is re-armed and audio resumes on its own.
var signature = string.Join("|", armed.Select(ep => $"{ep.Address}:{ep.Port}").OrderBy(s => s, StringComparer.OrdinalIgnoreCase));
var signature = PeerArming.Signature(armed);
if (signature == activeAudioReceiverSignature) return;
activeAudioReceiverSignature = signature;
sender.SetReceivers(armed);