• v0.16.4 fabe7214ad

    v0.16.4
    All checks were successful
    CI / build-and-test (push) Successful in 2m19s
    Release / build-and-test (push) Successful in 1m5s
    Release / release-image (push) Successful in 30s
    Stable

    Ghost released this 2026-09-30 17:48:34 -04:00 | 64 commits to main since this release

    Seat pair for v0.16.4. Install seat-streamer and gadgetd TOGETHER — see INSTALL.md in the archive.

    Downloads
  • v0.16.3 5f08a3ae91

    v0.16.3
    All checks were successful
    CI / build-and-test (push) Successful in 2m26s
    Release / build-and-test (push) Successful in 1m6s
    Release / release-image (push) Successful in 31s
    Stable

    Ghost released this 2026-09-30 17:11:50 -04:00 | 67 commits to main since this release

    Seat pair for v0.16.3. Install seat-streamer and gadgetd TOGETHER — see INSTALL.md in the archive.

    Downloads
  • v0.16.2 5b44028ae5

    v0.16.2
    All checks were successful
    CI / build-and-test (push) Successful in 2m21s
    Release / build-and-test (push) Successful in 1m6s
    Release / release-image (push) Successful in 31s
    Stable

    Ghost released this 2026-09-30 16:52:22 -04:00 | 69 commits to main since this release

    Seat pair for v0.16.2. Install seat-streamer and gadgetd TOGETHER — see INSTALL.md in the archive.

    Downloads
  • v0.16.1 20c57dda7e

    v0.16.1
    Some checks failed
    CI / build-and-test (push) Failing after 1m34s
    Release / build-and-test (push) Successful in 1m5s
    Release / release-image (push) Successful in 41s
    Stable

    Ghost released this 2026-09-30 15:18:15 -04:00 | 72 commits to main since this release

    Web: low-latency playout preset; ?wtpath=wan|lan|race transport pin, touch devices default to wan. Seat: declared_hz in ctl messages. Install seat-streamer and gadgetd together (INSTALL.md in the archive).

    Downloads
  • 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
  • v0.15.0 cb62770328

    v0.15.0
    All checks were successful
    CI / build-and-test (push) Successful in 2m24s
    Release / build-and-test (push) Successful in 1m27s
    Release / release-image (push) Successful in 30s
    Stable

    Ghost released this 2026-09-24 23:19:44 -04:00 | 88 commits to main since this release

    g2gMs is measured for the first time, and the seat now reports how old a frame is at each stage, so the ~130 ms glass-to-glass figure can be split per stage before any of it is tuned.

    • g2gMs fixed. The encoder shifts every output pts forward 1000 h (min_pts); the wire pts and enc-latency read that raw pts as running time, so the client was 164,026,163 ms off and enc-latency read −3.6×10¹² µs. The seat now reads pts through the pad's segment. The client's clock offset also had the wrong sign; fixed. Readings outside 0–5000 ms are dropped and counted as wc.g2gBad, which is what a pre-0.15 seat produces.
    • Per-stage age on the seat. stage.ageMs = {src, vr, cap, encin, encout, sent}, each [p50, p99] ms: capture source → after the rate stage → after upload → encoder in → encoder out → last chunk handed to quinn. ageNeg counts negative ages (should be absent). stage.vrate has videorate's in/out/dup/drop per window.
    • Client rungs. The wc block adds g2g50/99, dLat50/99 (decode submit → output), train50/99 (first → last chunk of a frame) and disp90Mbps (delivery rate on frames of 16+ chunks).
    • Parity carry bounded. For a source slightly faster than the panel the carry never ended, and the draw sat a frame (16.7 ms) behind on 71% of paints. It now releases after two consecutive carries; wc.carRel counts them.
    • Input counters at every session end. gaps/stale/dups are logged and written as input-session on viewer leave, input close, and seat shutdown. They used to be written only when the viewer left.

    No change to STUN/TURN. Client build ui79.

    Downloads
  • v0.14.2 0122a4f0d5

    v0.14.2
    All checks were successful
    CI / build-and-test (push) Successful in 2m20s
    Release / build-and-test (push) Successful in 1m5s
    Release / release-image (push) Successful in 29s
    Stable

    Ghost released this 2026-09-23 12:58:17 -04:00 | 92 commits to main since this release

    The seat learns its public address by STUN at startup and every 60 s, and that address leads the WebTransport advert. Before this, the first viewer after each launch got only the static --wt-advertise list, which went stale with the next DHCP lease and sent remote viewers to a dead address.

    The probe binds inside --ice-ports, since only that range bypasses the LAN's VPN. Seat-side only; the orchestrator image is rebuilt with no functional change.

    Downloads
  • v0.14.1 5855626a69

    v0.14.1
    All checks were successful
    CI / build-and-test (push) Successful in 2m20s
    Release / build-and-test (push) Successful in 1m5s
    Release / release-image (push) Successful in 32s
    Stable

    Ghost released this 2026-09-23 10:58:43 -04:00 | 94 commits to main since this release

    Input moves off the datachannel. On wifi its retransmits cost +48 to +98 ms at p99; it now rides WebTransport datagrams as full held state, so a lost frame costs at most one frame and a key cannot stick.

    • Key and button changes are sent immediately instead of on the next animation frame, then once more 4 ms later.
    • Gamepads are polled every 4 ms, and a change is sent at once.
    • New stat inUp (p50/p95/p99/max, ms): input's own uplink delay over its floor, from seat arrival acks. inputRttMs is a control-stream ping and does not measure input.
    • splitux-relay removed. Wire tags 7 (RelaySeg) and 8 (SeatNet) are retired.
    • app.js no longer crashes on load (TDZ since 413feea). CI now evaluates it, and tsc, clippy -D warnings and cargo fmt --check all block.
    • install-gadgetd.sh no longer exits silently when no seat is running.

    Client build ui78. Install the seat pair together per INSTALL.md; gadgetd restarts and drops any live session.

    Downloads
  • v0.13.0 7c6821b4ad

    v0.13.0 — the seat survives its own streamer
    All checks were successful
    CI / build-and-test (push) Successful in 2m27s
    Release / build-and-test (push) Successful in 1m8s
    Release / release-image (push) Successful in 4s
    Stable

    gabrielgad released this 2026-08-08 15:50:03 -04:00 | 172 commits to main since this release

    placeholder, replaced below

    Downloads
  • v0.9.2 10e3597b04

    gabrielgad released this 2026-07-03 06:26:30 -04:00 | 381 commits to main since this release

    seat-streamer now refuses to pair WebRTC media across the WireGuard mesh: mesh-subnet candidates (default 10.0.0.0/24,fd00::/64, override with GSE_ICE_EXCLUDE) are neither signalled to viewers nor accepted from them. Mesh-peer viewers previously formed host pairs through the tunnel — hairpinning all media via the VPS instead of going LAN-direct or through the TURN relay — regardless of relay-side denies. Viewers with a mesh VPN profile active now stream over the same clean paths as everyone else.

    Downloads