feat: M3 — lumen/1 native streaming: real video at client mode + input over QUIC datagrams

The native protocol now does the real thing, end to end:

- Hello carries the client's requested mode; the host creates a NATIVE virtual output at
  exactly that size/refresh (same vdisplay backends as the GameStream path) and streams
  NVENC HEVC through the M1 Session (GF(2^16) Leopard FEC + AES-GCM, QUIC-negotiated).
- Input rides QUIC DATAGRAMS — encrypted, congestion-managed, no ENet retransmission
  spikes — decoded into lumen_core InputEvents and fed to the session's input injector.
- Frames are stamped with the capture wall clock; the reference client computes per-frame
  capture→reassembled latency percentiles and writes a playable .h265.
- m3-host gains --source synthetic|virtual + --seconds; the client gains --mode WxHxFPS,
  --out, --input-test (scripted mouse/keyboard datagrams).

VALIDATED live (gamescope session, xev nested): client requested 1280x720@120 → host
created gamescope at that mode → 1680/1680 frames over 14s, zero loss, valid HEVC;
pipeline latency p50 0.83ms / p95 1.2ms / p99 1.3ms (capture→encode→FEC→crypto→UDP→
reassembled, same-host clock); 176 input datagrams sent → injector (GamescopeEi) → 164
X events observed inside the nested session.

Known follow-on: slice-level sub-frame pipelining needs the NVENC SDK directly (libavcodec
emits whole AUs only) — the next big latency lever.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 06:56:47 +00:00
parent de3123038f
commit 5b0d84acd0
4 changed files with 429 additions and 84 deletions
+17 -14
View File
@@ -60,21 +60,24 @@ fn real_main() -> Result<()> {
Some("zerocopy-probe") => zerocopy::probe(),
// M0 pipeline spike.
Some("m0") => m0::run(parse_m0(&args[1..])?),
// M3 seed: native lumen/1 host (QUIC control plane + UDP data plane).
// M3: native lumen/1 host (QUIC control plane + UDP data plane).
Some("m3-host") => {
let port = args
.iter()
.skip_while(|a| *a != "--port")
.nth(1)
.and_then(|s| s.parse().ok())
.unwrap_or(9777);
let frames = args
.iter()
.skip_while(|a| *a != "--frames")
.nth(1)
.and_then(|s| s.parse().ok())
.unwrap_or(300);
m3::run(port, frames)
let get = |flag: &str| {
args.iter()
.skip_while(|a| *a != flag)
.nth(1)
.map(String::as_str)
};
let source = match get("--source") {
Some("virtual") => m3::M3Source::Virtual,
_ => m3::M3Source::Synthetic,
};
m3::run(m3::M3Options {
port: get("--port").and_then(|s| s.parse().ok()).unwrap_or(9777),
source,
seconds: get("--seconds").and_then(|s| s.parse().ok()).unwrap_or(30),
frames: get("--frames").and_then(|s| s.parse().ok()).unwrap_or(300),
})
}
Some("-h") | Some("--help") | Some("help") | None => {
print_usage();