# BBR congestion control (vendored)

`bbr/` and `bbr/common/` are copied verbatim (imports repointed) from
Hysteria v2.12.1, `core/internal/congestion/{bbr,common}`:

    https://github.com/apernet/hysteria  (MIT, Copyright 2023 Toby)

Vendored rather than imported because the upstream package lives under
`internal/`, which Go forbids importing from another module. See LICENSE
for the upstream MIT terms.

## Why this exists

quic-go ships only the RFC 9002 (Reno/Cubic-style) controller and has no
pluggable congestion control (quic-go#776). That controller treats every
lost packet as congestion, so on a path with a little ambient loss its
throughput collapses to roughly `MSS / (RTT * sqrt(loss))`.

Measured against a real viewer 2026-08-22: 0.6% ambient loss at 50 ms RTT
held the QUIC connection to 3-5 Mbps while plain paced UDP over the same
path carried 22 Mbps. The command stream needs ~9 Mbps for a busy replay,
so the governor had to cut to ~5 fps. BBR estimates bandwidth and RTT
instead of reading loss as congestion, which is the textbook fix for a
lossy-but-fast path.

`replace github.com/quic-go/quic-go => github.com/apernet/quic-go` in
go.mod supplies the `SetCongestionControl` hook this needs; that fork is
MIT and tracks the same upstream release we already used.

## Local modifications

The copy is no longer verbatim. Every change is marked `LOCAL` in the
source so a future re-vendor can carry it over:

- `bbr_sender.go`: `ProfileRealtime` ("realtime") and the
  `recoveryWindowGain` profile field, applied by `recoveryWindowFloor()`
  at both exits of `calculateRecoveryWindow()`. The standard,
  conservative and aggressive profiles keep upstream behaviour (gain 0).
- `bbr_sender.go`: the `ackHeightGain` profile field, applied by
  `ackAggregationAllowance()` wherever upstream added the raw max ack
  height to a window. Realtime caps the allowance at one BDP; the other
  profiles keep upstream behaviour (gain 0).
- `bbr_sender.go`: `ProfileRealtime` derives from the CONSERVATIVE
  profile (Chromium's bandwidth-sampler overestimate avoidance, cwnd
  gain 1.75, overshoot detection), not the standard one. Measured
  2026-08-22 on the standard base over a path whose acks arrive
  aggregated: the sampler read the pacing rate back as delivery rate and
  the estimate ratcheted 16 -> 51 Mbps while 7-14 Mbps was delivered,
  with a 500 ms standing queue.
- `snapshot.go`: `Snapshot()`, an atomically published read-only view of
  the model for the bridge's per-session log line (with the sampler's
  lifetime byte totals, whose deltas are the delivered rate the relay
  governor cuts toward); and the ack-stall tracker
  (`armAckGap`/`noteAck`/`TakeMaxAckStall`): the longest ack silence
  beyond one smoothed RTT with data in flight, the signature of a link
  hold, which the bridge keeps out of its pressure reports.
- `recovery_window_test.go`, `ack_gap_test.go`: the tests for the above,
  including the upstream collapse as the regression reference.

Why: BBR v1's loss recovery seeds its window from the bytes in flight
when a loss is detected and holds in-flight there for a round (packet
conservation), then slow-starts. For a bulk flow that window equals the
ordinary one. The w3cs relay is app-limited between frames, so the seed
is a few packets and every ambient loss trickles the next ~55 KiB frame
out over several RTTs. Measured 2026-08-22 at 0.6% loss / 47 ms RTT:
stream writes blocked 200-500 ms (pacing alone needs ~45 ms), the bridge
dropped 20% of frames unstarted and the relay governor could not leave
15 fps, while a viewer on a clean path ran 32 fps on the same build. The
floor keeps recovery at one bandwidth-delay product (plus the measured
ack aggregation, as the ordinary window already carries), i.e. at the
delivery rate the path has proven, instead of below it.
