fix(core/speed-test): packet-level throughput + paced burst (kill the 0/100% cliff)

The punktfunk/1 speed test was unusable across every client/host: at the start of
a burst a little data got through, then everything read as dropped (~10 MB total).
Two compounding bugs:

1. Receive side measured throughput from fully-reassembled FLAG_PROBE *access
   units* only. The instant loss crossed the 20% FEC budget no AU completed, so the
   figure cliffed to 0 / 100% loss even though most bytes still arrived — a binary
   cliff, not a graded measurement.
2. Send side blasted each filler AU (up to 256 KB ≈ 200 packets) into the socket
   buffer in one unpaced batch, unlike the real video path which paces. On a small
   buffer (e.g. the Steam Deck's 416 KB) a single AU overflowed it, so the test
   measured self-inflicted buffer overflow instead of the link.

Fixes:
- Host `run_probe_burst` keeps each AU a small (~16 KB) burst and paces by the byte
  budget, mirroring `paced_submit`; reports the WIRE packets the kernel accepted and
  the ones the send buffer dropped (stat deltas), separating host-side drops from
  link loss.
- `ProbeResult` gains `wire_packets_sent` + `send_dropped` (back-compat decode: a
  21-byte pre-wire-stats result still decodes, new fields 0).
- Clients (probe + connector) count delivered traffic at the packet level via
  `session.stats()` deltas over the burst window, so throughput/loss degrade
  gracefully. Connector freezes the delivered figure when the host report lands so
  resumed video can't inflate it. New `ProbeOutcome`/`PunktfunkProbeResult` fields:
  `host_drop_pct`, `wire_packets_sent`, `send_dropped`.

Validated on loopback (graded 142→1391 Mbps, host_drop/link_loss split correctly,
no cliff) and live against the Deck: clean to ~200 Mbps goodput / 273 Mbps wire at
0% link loss, host send buffer the wall above that (the lever-#1 target).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-20 17:46:17 +00:00
parent 2dc54bc651
commit f37a304fba
6 changed files with 264 additions and 129 deletions
+13 -6
View File
@@ -468,22 +468,29 @@ typedef struct {
// A speed-test measurement, filled by [`punktfunk_connection_probe_result`]. `done` is 0 until
// the host's end-of-burst report lands, then 1 (the numbers are final). `throughput_kbps` is the
// measured goodput to drive a bitrate choice from; `loss_pct` is the delivery loss at that rate.
// delivered wire throughput to drive a bitrate choice from; `loss_pct` is the link loss and
// `host_drop_pct` the host-side send-buffer drop (raise `net.core.wmem_max`) — they're measured
// separately so a host that can't keep up reads differently from a lossy link.
typedef struct {
// 1 once the host's end-of-burst report arrived (measurement final); else 0 (partial).
uint8_t done;
// Probe payload bytes / packets the client received.
// Delivered wire bytes (header + shard) / packets the client received during the burst.
uint64_t recv_bytes;
uint32_t recv_packets;
// Probe payload bytes / packets the host reported sending.
// Application goodput bytes / access units the host offered.
uint64_t host_bytes;
uint32_t host_packets;
// Client-measured receive window (first→last probe AU), milliseconds.
// The host's measured burst duration, milliseconds (the throughput denominator).
uint32_t elapsed_ms;
// Measured goodput = `recv_bytes * 8 / elapsed_ms` (kilobits/second).
// Delivered wire throughput = `recv_bytes * 8 / elapsed_ms` (kilobits/second).
uint32_t throughput_kbps;
// Delivery loss `(host_bytes - recv_bytes) / host_bytes` as a percentage (0 if unknown).
// Link loss `(wire_packets_sent recv_packets) / wire_packets_sent` as a percentage.
float loss_pct;
// Host-side send-buffer drop `send_dropped / (wire_packets_sent + send_dropped)`, percent.
float host_drop_pct;
// Wire packets the host put on the link, and the ones its send buffer dropped (raw counts).
uint32_t wire_packets_sent;
uint32_t send_dropped;
} PunktfunkProbeResult;
#ifdef __cplusplus