RioSerialLink: stop-and-wait command delivery (supersedes NAK-race resend)
Bench falsified the v1 retransmit (testlogs/riomash-patched-62500-retx): resends tracked NAKs 1:1 (321/321) yet lamps still stuck/missed — under mash bursts the NAK arrives after a newer command is already "latest", so the wrong packet was resent; and total shreds never NAK at all. Now commands are stop-and-wait: ONE in flight (_commandGate), resolved by ACK, NAK, or AckTimeout (50ms); NAK/timeout retransmits THE SAME packet up to CommandRetransmitLimit (2), then drops (idempotent - the next state update supersedes). Attribution is exact by construction and timeouts catch silent shreds. Control-byte replies bypass the gate so board traffic is never delayed; a request's own reply (analog/version/ check) also resolves the wait, so request/reply exchanges never burn the timeout even if the board sends no explicit ACK. 7 tests (same-packet resend, timeout retry+drop, ACK completion, serialization, reply-resolves-request, stray-NAK no-op, disable); 282 green. Mash summary now reports NAK/timeout resends. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -319,7 +319,7 @@ internal static class MashTest
|
||||
Raw($"firmware : {(version is null ? "(no reply)" : version.ToString())}");
|
||||
Raw($"presses/releases : {presses} / {releases} ({presses / mins:F0} presses/min)");
|
||||
Raw($"analog replies : {analogReplies} (~{expectedPolls} poll slots; {100.0 * analogReplies / Math.Max(1, expectedPolls):F1}%), sentinels {sentinels}");
|
||||
Raw($"framing / NAK : {framing} / {naks} (NAK-triggered resends: {link.NakRetransmits})");
|
||||
Raw($"framing / NAK : {framing} / {naks} (command resends NAK/timeout: {link.Retransmits})");
|
||||
Raw("gap histogram : " + string.Join(" ", bucketNames.Select((n, i) => $"{n}:{buckets[i]}")));
|
||||
Raw("longest gaps : " + (longestGaps.Count == 0
|
||||
? "(none)"
|
||||
|
||||
Reference in New Issue
Block a user