diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html
index 6b4fe68..6a64d39 100644
--- a/web/zebra-spaces.html
+++ b/web/zebra-spaces.html
@@ -2523,8 +2523,12 @@ function handleRemoteSfuTrack(ev){
logLine('', 'sfu: unknown kind '+kind+' from '+pubHex);
return;
}
- /* mic audio — 400ms jitter-buffer target absorbs Wi-Fi peak jitter */
- try { if (ev.receiver) ev.receiver.playoutDelayHint = 0.4; } catch(_){}
+ /* mic audio — 700ms jitter-buffer target absorbs cross-Wi-Fi peak
+ * jitter and burst packet loss. 400ms was too tight for users on
+ * different APs / cellular hand-offs — under-runs were producing
+ * the 'robot voice' artifact. 700ms costs an inaudible bit of
+ * end-to-end latency in exchange for clean playback under loss. */
+ try { if (ev.receiver) ev.receiver.playoutDelayHint = 0.7; } catch(_){}
/* cache by full pubkey (already resolved above) so it survives the
* member's session uuid changing across leave/rejoin */
sfuStreamsByPubHex.set(pubHex, ev.streams[0]);
@@ -3494,9 +3498,10 @@ async function connectToPeer(uuid, weOffer){
let a = remoteAudio.get(uuid);
if (!a){ a = document.createElement('audio'); a.autoplay = true; document.body.appendChild(a); remoteAudio.set(uuid, a); }
a.srcObject = ev.streams[0] || new MediaStream([ev.track]);
- /* 400ms jitter buffer — Wi-Fi peak inter-arrival is multiples of
- * the smoothed jitter, so a generous buffer absorbs the bursts */
- try { ev.receiver.playoutDelayHint = 0.4; } catch(_){}
+ /* 700ms jitter buffer — matches the SFU path. Cross-Wi-Fi and
+ * cellular hand-off jitter ate into the old 400ms cushion and
+ * produced robot-voice artifacts on tighter networks. */
+ try { ev.receiver.playoutDelayHint = 0.7; } catch(_){}
stopMeter(uuid); startMeter(uuid, a.srcObject);
};
pc.onicecandidate = (ev) => { /* using waitForIceGathering pattern, candidates ignored */ };
@@ -4086,8 +4091,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');