--- title: "GameStream Host" description: "Stream to a stock Moonlight client on a client-sized virtual display." --- > **Status:** SHIPPED — works end-to-end with a stock Moonlight/Artemis client (initial > merge `ab6dda2`, June 2026). Code: [`crates/punktfunk-host/src/gamestream/`](../crates/punktfunk-host/src/gamestream/). > Byte-level wire reference: [`research/gamestream-protocol-research.json`](research/gamestream-protocol-research.json) > (distilled from Sunshine + moonlight-common-c). This doc is trimmed to design rationale + > open items; the shipped code is the source of truth for wire/packet detail. A stock Moonlight client discovers this host, pairs, launches, and gets video + input + audio on a client-sized virtual display. ## Architecture (respects the "one core" invariant) - **punktfunk-core** holds the **P1 GameStream wire codec** (`ProtocolPhase::P1GameStream`): the RTP+`NV_VIDEO_PACKET` framing, the GameStream FEC shard layout, and the video/audio AES-GCM/CBC paths. Hot path, native threads, **no async**. Kept beside punktfunk's native internal format (P2), selected by phase. - **punktfunk-host** holds the **control plane** (tokio/axum OK — I/O-bound, *not* the hot path): mDNS discovery, nvhttp serverinfo + the 4-phase pairing, the RTSP handshake, the ENet control stream + input injection, the virtual-display lifecycle, and Opus audio encode. ## Why we shipped in this order (the two highest interop risks) These two mitigations are why early bring-up deliberately skipped crypto and FEC — both turn out to be unnecessary on a clean LAN, and both have a wire-incompatibility that would have silently broken interop if done naively. 1. **RS-FEC matrix incompatibility — clean-LAN first.** Sunshine + Moonlight both use **nanors** (GF(2⁸), poly 0x11d, Vandermonde systematic). punktfunk-core uses `reed-solomon-erasure` (Cauchy) — parity bytes **don't match**, so Moonlight silently fails to recover any frame with a lost data shard. Mitigation: **on a clean LAN with no loss the client never runs RS decode**, so we deferred it — get a frame decoded first, then port nanors for loss recovery. 2. **Crypto layout incompatibility — plaintext video first.** punktfunk's `SessionCrypto` (salt + seq-as-AAD) is wire-incompatible with GameStream's GCM; P1 needs a separate GameStream GCM path (key = raw 16-byte RIKEY, IV = `counter_le[8]||0,0,0||'V'(0x56)`, **no AAD**, **FEC first, then encrypt per shard**). Mitigation: **video encryption is negotiated and usually off on LAN** — we implemented plaintext video first and added GCM later. ## Open items - **HDR / 10-bit.** Needs HDR capture + metadata plumbing. (`av1_nvenc -highbitdepth 1` already encodes Main10 from 8-bit input on this box.) - **Reconnect-at-new-mode robustness.** - **AV1 negotiation.** Implemented + unit/live-capture tested; needs a **live confirmation with a stock Moonlight client** (select AV1 in a stock client). - **Surround 5.1/7.1 audio.** Implemented + tested; needs a **real listen** including FEC under loss, plus a live Moonlight confirmation. ## Testing note The host is headless; end-to-end needs a **stock Moonlight client on the LAN** pointed at this box (manual "add host" by IP works without mDNS). `/serverinfo` + the pair flow are testable with `curl`; video needs a client that can display.