diff --git a/zebra-report/zebra-spaces.html b/zebra-report/zebra-spaces.html
index 63518e7..f057f04 100644
--- a/zebra-report/zebra-spaces.html
+++ b/zebra-report/zebra-spaces.html
@@ -5175,16 +5175,30 @@ function primeAudioOnGesture(){
if (p && p.then) p.then(() => {
setTimeout(() => { try { a.remove(); } catch(_){} }, 500);
}).catch(()=>{ try { a.remove(); } catch(_){} });
- /* Create audioCtx if absent + resume so the listener path
- * (attachListenerStreamViaAudioContext) has a granted-and-running
- * AudioContext by the time the first remote MediaStream arrives.
- * Listeners never grab a mic so without this, audioCtx would be
- * null forever and the Web Audio listener route couldn't bootstrap.
- * Speakers also benefit — meter creation later won't have to
- * resume-from-suspended (which can fail outside a gesture). */
+ /* Create audioCtx + actually start the render thread inside the
+ * gesture. Just calling resume() isn't enough on Firefox Android —
+ * telemetry fox 2026-06-04 caught attachListenerStreamViaAudioContext
+ * logging ctxState=suspended even though resume() was called at
+ * entry-click time. resume() outside a subsequent gesture silently
+ * fails on Android.
+ *
+ * Play a brief silent oscillator through audioCtx.destination
+ * inside this gesture — that forces the audio rendering thread to
+ * ACTUALLY START rather than queueing-pending. After this the
+ * context stays running for the session and every
+ * MediaStreamAudioSource attached to destination plays through. */
try {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume().catch(()=>{});
+ const osc = audioCtx.createOscillator();
+ const g = audioCtx.createGain();
+ g.gain.value = 0; /* silent — only here to wake the render thread */
+ osc.connect(g);
+ g.connect(audioCtx.destination);
+ osc.start();
+ osc.stop(audioCtx.currentTime + 0.05);
+ osc.onended = () => { try { osc.disconnect(); g.disconnect(); } catch(_){} };
+ logLine('', 'audioCtx primed state='+audioCtx.state);
} catch(e){ logLine('err', 'audioCtx prime: '+e.message); }
/* pre-bless a pool of audio elements for unmuted autoplay. Each one
* gets play() called inside this click handler — the gesture grants
@@ -5342,8 +5356,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');