• v0.16.0 ba3180cd3b

    v0.16.0 — the cap drops, the hold breathes, the banner diagnoses
    All checks were successful
    Release / build-and-test (push) Successful in 1m5s
    CI / build-and-test (push) Successful in 2m26s
    Release / release-image (push) Successful in 31s
    Stable

    Ghost released this 2026-09-27 21:23:38 -04:00 | 78 commits to main since this release

    The stream stops inventing frames to fill the gap — on the deployed seat the single biggest cause of lag was our own duplicate fill — and a player who gets no video is now told what is actually wrong.

    • The framerate cap drops excess frames, never manufactures them. A bare videorate normalizes in BOTH directions: a game rendering 67 fps against the 200 fps cap was FILLED to 200 fps — measured on the deployed seat as vrate {in 334, dup 668} per 5 s window, three quarters of the stream manufactured. A duplicate carries its source frame's pts, so the fill inflated stage.ageMs p99 and hid the game's real cadence. drop-only=true on every encoder variant; the test covers all six.
    • CQP sessions can no longer leave the transport unbounded. max_frame_kb_for_qp(0) returned 0 — no quantizer floor commanded meant nothing to derive from. It now returns MAX_FRAME_KB_CEIL, the ceiling's own "a stall, not a picture". Measured 2026-09-27 at 1440p qp-i 28: real IDRs peak 94–254 KB, five times the headroom — the bound never shapes a real keyframe.
    • A playout spike's hold is shed in a few windows, and bounded at 90 ms. The flat 2 ms/window fall left a one-window arrival spike holding glass-to-glass up for ~30 s after the path recovered (g2g breathing to 250 ms on a 40 ms network). The fall is proportional now — halve the remaining gap per rotation, floor DECAY_STEP_MS — and MAX_TARGET_MS=90 binds even the constructor's initial target: a 200 ms game render stall must not trade the whole tail for buffer.
    • The no-video banner names its cause and its fix. On 2026-09-27 Brave showed "this seat sends no RTP video" while input worked fine; the real cause was the direct UDP path to the seat being blocked (the join rides the relay, so everything works except video). Each transport state now names its own problem and its own next step, wt-transport records which advert URLs failed, and tests/browser/banner-case.mjs drives the real page and fault tick with both field states and asserts the advice differs.

    Validated: 590 client unit tests, 616 workspace tests, cargo fmt and clippy -D warnings clean, tsc zero errors on both configs, clean-checkout load check green. Runtime confirmation (vrate dup = 0, IDRs bounded at the ceiling) lands with the next seat session's telemetry.

    No change to STUN/TURN. Client build ui80.

    Downloads