Graceful FEC behavior on a lossy link: at a realistic 2% packet loss the stream is now steady 0% (was spiking 40-60%). Verified live. - IDR/RFI handling: the control thread recognizes the client's recovery requests (0x0301 invalidate-reference-frames, 0x0302 request-IDR, 0x0305) and sets a shared force_idr flag; the video thread forces an NVENC keyframe on the next frame (Encoder::request_keyframe → input frame pict_type = I). Without this, a frame that exceeds the FEC budget broke the reference chain until the next GOP IDR (~2s), cascading to most of the stream being undecodable. - Min-parity floor: honor the client's x-nv-vqos[0].fec.minRequiredFecPackets (it asks for 2). Small P-frames previously got m=ceil(k*20/100)=1 parity — a single loss broke them; flooring m>=2 (capped so k+m<=255, wire pct recomputed) protects them. This is what turned the 2% spikes into steady 0%. - Send pacing: spread each frame's packets evenly across the frame interval instead of blasting them at line rate (a real link drops microbursts), matching Sunshine's rate-controlled sends; sub-500us sleeps skipped (unreliable). Note: sustained ~8% uniform loss still degrades — that exceeds 20% FEC for reference-frame video and real Sunshine degrades there too; real networks are <1% or bursty, which this now handles cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lumen
A ground-up low-latency desktop streaming stack, built Linux-first, with a shared Rust protocol core and native clients per platform.
lumen is a placeholder codename. The bet: ship a Linux virtual-display streaming
host that speaks the existing Moonlight protocol (every Moonlight/Artemis client works
day one), then break the ~1 Gbps FEC wall with a GF(2¹⁶) Leopard-RS transport as a
negotiated extension. See docs/implementation-plan.md.
Status
| Milestone | State |
|---|---|
M1 — lumen-core + C ABI |
✅ done & tested (FEC, packetization, crypto, session, lumen_core.h) |
M0 — pipeline spike (wlroots→PipeWire→NVENC→file→lumen-core) |
✅ done & verified on NVIDIA (RTX 5070 Ti / driver 595) |
| M2 — P1 host → stock Moonlight | 🟡 capture+encode landed in M0; pairing/RTSP/vdisplay pending |
| M3 — measurement harness | 🟡 tools/loss-harness runs; latency-probe scaffolded |
| M4 — P2 transport + Rust client | 🟡 GF(2¹⁶) core done; lumen-client-rs scaffolded |
| M5 — Apple client | ⬜ scaffolded (clients/apple) |
lumen-core is complete and verified: it builds and its full test suite (FEC recovery,
loopback round-trip under loss, property tests, and a C ABI harness) passes on
macOS/aarch64. M0 is done: lumen-host captures a headless wlroots output via the
ScreenCast portal + PipeWire, encodes it with NVENC, writes a playable H.265 file, and
round-trips every access unit through a lumen_core host→client session (see
docs/linux-setup.md). The remaining Linux host backends (KWin/Mutter virtual displays,
libei input, web/pairing) are #[cfg(target_os = "linux")] seams — defined and compiling,
implementations pending (M2).
Layout
crates/
lumen-core/ protocol · FEC · pacing · crypto — the C ABI (lib + cdylib + staticlib)
lumen-host/ Linux host: vdisplay · capture · encode · inject · web (cfg-gated)
lumen-client-rs/ reference client (M4): VAAPI decode + wgpu present
clients/{apple,android}/ native client scaffolds (import lumen_core.h)
include/lumen_core.h cbindgen-generated C header (checked in)
tools/{latency-probe,loss-harness}/ measurement (plan §10)
docs/implementation-plan.md
Build & test
cargo build --workspace # green on Linux and macOS
cargo test --workspace # unit + loopback + proptest + C ABI harness
cargo clippy --workspace --all-targets
cargo run -p loss-harness # FEC loss-resilience sweep (no network needed)
bash crates/lumen-core/tests/c/run.sh # standalone C-ABI link+round-trip proof
The C header regenerates from crates/lumen-core/src/abi.rs on every build (cbindgen via
build.rs) into include/lumen_core.h.
Design invariants
- One core, linked everywhere. Protocol/FEC/crypto/pacing live in
lumen-coreexactly once, exposed over a stable, versioned C ABI (lumen_abi_version(),LumenConfigcarries its ownstruct_size). - No async on the hot path. The per-frame pipeline uses native threads only;
tokio/quinnare gated behind the off-by-defaultquicfeature (control plane only). - FEC is the wall-breaker. GF(2⁸) (≤255 shards/block) for Moonlight compat; GF(2¹⁶) (≤65535 shards/block, SIMD, O(n log n)) to push past ~1 Gbps.
License
MIT OR Apache-2.0.