Skip to content

router: model the channel as a continuous carrier (never drop mid-transmission) - #15

Merged
M0LTE merged 1 commit into
mainfrom
continuous-carrier-no-drop
Jun 6, 2026
Merged

router: model the channel as a continuous carrier (never drop mid-transmission)#15
M0LTE merged 1 commit into
mainfrom
continuous-carrier-no-drop

Conversation

@M0LTE

@M0LTE M0LTE commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Problem

The per-link audio queue (internal/router/router.go linkQueue) was a fixed ~3 s buffered channel that dropped the oldest block on overflow. A TNC bursts a whole keyup's worth of audio with no real-time pacing, so any transmission longer than ~3 s overran the buffer and net-sim dropped blocks mid-transmission.

Dropping mid-transmission gaps the receiver's audio, which collapses its DCD / carrier sense — so the far end thinks the channel is idle and keys up on top of the in-progress transmission. That overlap is a collision (the mixer's MixCollision → silence), the frame is lost, and the loss kicks off a go-back-N retransmit storm that overruns the buffer further. A real continuous-carrier channel has no such limit.

Evidence

Measured on a real LinBPQ ↔ packet-radio-node link over the sim, driving a chat help-text dump (back-to-back long I-frames):

  • the receiver decoded only ~26 % of the sender's I-frames;
  • 36–63 % of the receiver's own transmissions collided with the sender's (composite-WAV L/R energy-overlap analysis);
  • the collisions began a median ~3.3 s into each transmission — exactly when the 3 s buffer saturated and started dropping;
  • reproduced identically under both the samoyed and direwolf backends, so it's the shared layer (net-sim), not the modem.

Fix

Model the channel as a continuous carrier: linkQueue becomes a non-dropping FIFO that grows as needed. The real-time rxFeeder still meters it out at the channel sample rate, so channel timing is unchanged — but the receiver now hears a gap-free carrier for the full transmission and its carrier sense holds. Memory is bounded in practice (a keyup is finite; the backing array is released once drained); only a pathological runaway past a 60 s safety cap ever drops, logged as the anomaly it is.

Result

Same link, after the fix:

before (3 s drop) after
audio queue overflow 2,876 0
receiver-TX-over-sender collisions 36–63 % 0 %
receiver I-frame delivery 26 % ~90 %
help text delivered 38 bytes full (1,050)

The go-back-N storm disappears (the sender stops retransmitting). Existing TestCompositeRealTimePacing / TestCompositeConcurrentFeed still pass (metering unchanged); adds TestLinkQueueDeliversLongBurstGapFree — a ~10 s burst (well past the old 3 s cap) must be delivered FIFO and gap-free.

🤖 Generated with Claude Code

…nsmission)

The per-link audio queue was a fixed ~3 s buffered channel that dropped the oldest block on overflow. A TNC bursts a whole keyup's audio with no real-time pacing, so any transmission longer than ~3 s overran the buffer and net-sim dropped blocks *mid-transmission* — gapping the receiver's audio, collapsing its DCD / carrier sense, and making the far end key up on top of the in-progress transmission. That overlap is a collision (the mixer's MixCollision → silence), the frame is lost, and the loss triggers a go-back-N retransmit storm that overruns the buffer further. A real continuous-carrier channel has no such limit.

Measured on a real LinBPQ ↔ packet-node link over the sim (a chat help-text dump = back-to-back long I-frames): the receiver decoded only ~26% of the sender's I-frames; ~36-63% of the receiver's own transmissions collided with the sender's (composite-WAV L/R overlap analysis); and the collisions began a median ~3.3 s into each transmission — i.e. exactly when the 3 s buffer saturated and started dropping.

Fix: linkQueue becomes a non-dropping FIFO that grows as needed. The real-time rxFeeder still meters it out at the channel sample rate, so channel timing is unchanged, but the receiver now hears a gap-free carrier for the full transmission and its carrier sense holds. Memory is bounded in practice (a keyup is finite; the backing array is released once drained); only a pathological runaway past a 60 s safety cap ever drops, logged as the anomaly it is.

After the fix, on the same link: overflow 0, 0 collisions, receiver I-frame delivery 26% → ~90%, the full help text delivered, and the go-back-N storm gone.

Adds TestLinkQueueDeliversLongBurstGapFree: a ~10 s burst (far past the old 3 s cap) must be delivered FIFO and gap-free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@M0LTE
M0LTE merged commit 1cdc91a into main Jun 6, 2026
1 check passed
@M0LTE
M0LTE deleted the continuous-carrier-no-drop branch June 6, 2026 12:14
M0LTE added a commit to packet-net/packet.net that referenced this pull request Jun 6, 2026
…ssion audio drops) (#324)

net-sim's per-link audio queue dropped blocks mid-transmission on any burst longer than its ~3 s buffer, which gapped the receiver's audio, collapsed its carrier sense, and made stations transmit over each other — silently degrading interop scenarios with back-to-back long frames (NET/ROM NODES bursts, chat help dumps, etc.). Fixed in packet-net/net-sim#15 with a non-dropping continuous-carrier FIFO; this bumps the interop stack's pinned digest to pick it up.

Validated on the lab against a real LinBPQ link: overflow 2876 -> 0, collisions 36-63% -> 0%, long-frame delivery 26% -> ~90%.

[skip-plan]

Co-authored-by: Tom M0LTE <tom@fann.ing>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
M0LTE added a commit to packet-net/packet.net that referenced this pull request Jun 12, 2026
Both test stacks pinned net-sim 0fb89608 (the 2026-06-06 continuous-carrier build, packet-net/net-sim#15) — which predates ACKMODE entirely. Bump to the v0.2.0 release: ACKMODE end-to-end with the echo delivered at PTT release (not render time) + the tq lost-wakeup fix reconciled, plus time_scale / collision-noise / squelch_open_ms / rt-priority. Forward-compatible — 0.2.0 carries the continuous-carrier fix and the new modes default to the prior behaviour (collision silence, squelch 0, time_scale 1), so plain-KISS interop is unchanged; the interop suite validates in CI.

[skip-plan]

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant