4.7 KiB
Group Call Audio V2 — Post-Cutover Upgrade Roadmap
This document tracks architectural improvements that become available AFTER the v2 architecture (control plane, receive engine, policy FSM, decode service, replay harness) is fully in production and all legacy paths have been deleted.
These are NOT blockers for the cutover. They are evaluated after the v2 baseline is stable and the regression fixture suite passes.
1. Native Reticulum-level improvements
Motivation: Phil had reticulumAudioPacketPathTimeouts = 11.9% (vs Kenny's 3.9%).
Path resolution failures at the Reticulum layer cannot be fixed by the v2 policy FSM —
they require changes at the network/routing level.
Candidates:
- Reticulum path pre-caching: resolve and cache paths for known group call peers before they are needed, so the first post-join packet doesn't trigger resolution.
- Reduce path resolution timeout from the current default to 2s for audio peers.
- Evaluate Reticulum-native multicast for group audio fanout to eliminate the application-level fanout hop.
Gate: Requires Reticulum Python daemon changes. Out of scope for the JavaScript
v2 architecture; file separate issues against reticulum-daemon.ts.
2. Native Opus decode via WASM / Emscripten
Motivation: The current WebCodecs AudioDecoder path does not expose PLC (packet
loss concealment) beyond silence fill. The WASM Opus decoder has a proper decode_loss
API that produces plausible audio for gaps.
Plan:
- Integrate the WASM libopus decoder as a
IDecodeServiceimplementation alongsideWebCodecsDecodeService. - The
DecodeServicefactory selects WASM whenAudioDecoderis not available, or when the caller requests the WASM path explicitly (e.g. for PLC quality). - Evaluate whether
gcall-opus-fec.worker.tscan be retired in favor of the unifiedIDecodeServiceinterface.
3. Opaque relay / fanout optimization
Motivation: Root forwarders today decrypt-and-reencrypt. For large rooms this is wasteful: the forwarder could relay the already-encrypted bytes opaquely.
Plan:
- The
ReticulumSessionControllershould emit acanRelayOpaqueflag per stream once the topology is stable. When true, the forwarder skips decrypt/reencrypt and forwards the wire bytes directly. - The
ReceiveEngineshould accept opaque relay packets and dispatch them without involving theDecodeService. - This requires the wire format to carry enough routing metadata outside the secretbox so the forwarder can route without decrypting.
4. SharedArrayBuffer playout bridge
Motivation: The current PCM ring lives on the main thread. The playout worklet
reads from it via postMessage, which adds one message-queue hop of latency.
Plan:
- Export the
PerSourcePcmRing's internalFloat32Arrayas aSharedArrayBuffer. - The playout worklet reads directly from the SAB with
Atomics-based fill tracking. - Eliminates the main-thread ↔ worklet round trip for PCM delivery.
Gate: Requires COOP/COEP headers (Cross-Origin-Opener-Policy: same-origin,
Cross-Origin-Embedder-Policy: require-corp). Verify Electron's context isolation
configuration supports this.
5. Multi-party jitter and FEC tuning
Motivation: The DEFAULT_POLICY_CONFIG targets 1:1 calls (the call-63 scenario).
For 3+ participants, targetBufferMs should be higher and backlogDrainTriggerRatio
may need adjustment.
Plan:
- The
GcallV2SessionorReceivePolicyEngineshould accept aparticipantCountsignal and scaletargetBufferMsaccordingly. - For N ≥ 3, consider enabling WASM FEC in the
DecodeServicefactory.
6. Replay harness CI integration
Motivation: The regression fixture tests currently run in Jest with simulated time. They should also run in a headless Electron instance to catch Electron-specific scheduling artifacts (the tick budget breaches from the Phil scenario are Electron/WebAudio scheduling artifacts).
Plan:
- Add a
npm run replay:citarget that runsreplayHarness.test.tsin a headless Electron context. - Use
electron-mochaorplaywrightwith@playwright/test's Electron driver. - Make this a required CI step before any group-call PR merges.
7. Paired export upload / comparison tool
Motivation: Today, comparing paired exports requires manual JSON archaeology.
The PairedExportAnalyzer automates classification, but the exports still need to
be collected manually.
Plan:
- Add an in-app "Share call diagnostics" button that exports a bundle and optionally uploads it to a diagnostics endpoint.
- The endpoint stores paired exports keyed by
roomId + exportedAtMsso the analyzer can fetch both peers' exports automatically. - Surface the
PairedAnalysisResult.callSummaryin the UI for QA triage.