Outerfar Studios Field note / 01
PHOTONKATANA // Field note

Eight Milliseconds
Acht Millisekunden

For seventeen days our WebXR game died on every cold start of the Meta Quest — silently, with nothing logged. Four theories were wrong, the answer was eight milliseconds wide, and Meta shipped the fix within days of seeing the evidence.

Field note 0125 August 2026WebXR · Trusted Web Activity
Published 25 August 2026 Subject WebXR · Trusted Web Activity · Meta Quest Status Fixed by Meta, 24 August 2026

The symptom

PHOTONKATANA runs on the Meta Quest as a Trusted Web Activity — a thin native wrapper around the same WebXR build that runs in the browser. From the first sideload it had one reproducible defect: after every restart of the headset, the app died a few seconds in. Start it a second time and it ran fine.

Nothing was logged. No error, no warning, no rejected promise. The immersive session simply stopped existing. It took seventeen days to find out why — and in the end we did not change a single line of the game to fix it.

An uncaught NullPointerException inside the browser's media session handling — eight milliseconds after a looping audio element opened its output stream.

What it wasn't

The crash always followed the start of a music track, so audio looked guilty. Four theories followed, and each was killed by a test built to kill it. If you are chasing something similar, this is the list you can skip:

Disproven
The file size Same track re-encoded from 3.5 MB to 821 KB. Identical crash, same point in the sequence.
Disproven
Frame starvation Main thread blocked for three full seconds, no frames submitted, full 3D scene. The session survived.
Disproven
decodeAudioData Twenty-two decodes fired in a burst during an active session. All completed, nothing died.
Disproven
Too much load at once Full scene, decode burst and main-thread block together. Still alive — it was never a matter of degree.
Held up
The media element itself One observation survived everything: after the app was gone, the music kept playing. A media session was outliving its page — and a long-lived HTMLAudioElement is exactly what creates one.

Two things made this harder than it needed to be, and both are worth knowing if you debug WebXR on a headset. Timers are throttled while you are inside VR — the 2D document is hidden, so a setInterval heartbeat marks nothing; only the XR frame loop is a trustworthy clock. And localStorage never flushes on a hard kill, so the marker you most need is guaranteed to be the missing one. We moved telemetry to batched beacons driven by the frame loop, and added a build stamp — because for several days the headset had also been running code we had already replaced.

What it was

Once four theories were dead, the fastest move was subtraction. We built a separate WebXR page, packaged it with Bubblewrap into its own TWA, and staged it: bare scene, animation, short clips, looping media element. Everything ran. The media element stage died every time. Six hundred kilobytes reproduced what forty thousand lines had hidden — and, crucially, Meta could now run it themselves.

The unfiltered capture then gave up the answer:

E AndroidRuntime: org.chromium.base.JniAndroid$UncaughtExceptionException
  Caused by: java.lang.NullPointerException: Attempt to invoke virtual
      method 'java.lang.String java.lang.Class.getName()' on a null
      object reference
    at android.content.ComponentName.<init>(ComponentName.java:133)
    at android.content.Intent.<init>(Intent.java:7662)
    at org.chromium.content.browser.MediaSessionImpl
        .mediaSessionStateChanged(chromium-OculusBrowser.apk:26)
AAudioStreamBuilder_openStream() — this one is fine
AAudioStreamBuilder_openStream() — the looping media element
Uncaught Java exception — eight milliseconds later
Process killed, destroyTimeout

The most useful line in the whole capture is the one that doesn't crash. Seven seconds earlier another audio stream opens and nothing happens. Opening an audio stream is not the trigger — the second one belongs to a looping HTMLAudioElement, and that creates a media session. The trigger is the media session state change. That single contrast explains everything at once: why one-shot clips were safe, why decoded buffers through an AudioContext were safe, why file size never mattered, and why the music went on playing after the app was gone.

Working with Meta Developer Support

We expected this to be the slow part of the story. It turned out to be the fastest.

Our first report was wrong. We led with OpenGLRenderer setStopped(1) — the last line before the app vanished — and presented it as the cause. It was the end of the chain, not the beginning.

The reply didn't argue with our conclusion. It asked for the one thing that would settle it: a completely unfiltered capture from all buffers, all priorities, across every process. That was exactly the right question, and it was one we had not thought to ask ourselves.

adb logcat -b all -v threadtime "*:V"

From there it moved quickly. We delivered two clean captures and a signed reproduction APK. Meta read a difficult log, located the defect in their own browser, confirmed it, and on 24 August 2026 told us a fix is shipping in a coming version of the Oculus Browser — with no change required in our app.

For a one-person studio filing a bug against a platform's own browser, that is not the outcome you plan for. The whole exchange took days, not months. We want to say that plainly, because the opposite story gets told far more often: the guidance was precise, the engineering was taken seriously, and the fix happened because someone on the other side actually read the evidence. Our part was simply to stop guessing and hand over something that could be executed.

What we took from it

  1. Read a log from the start of the chainOur first report described the last line before the silence. The cause sat five seconds earlier, in a buffer we were not capturing.
  2. Subtract instead of addingFour theories and a fortnight of instrumentation moved us less than one afternoon of building a minimal reproduction.
  3. Give the vendor something they can executeA description invites a discussion. A signed APK that fails in one click, with a capture and a timestamped timeline, invites a fix.

PHOTONKATANA is a WebXR title for the Meta Quest, running at photonkatana.outerfar.com. The minimal reproduction and both unfiltered captures are available on request — c.prokop@outerfar.com. If you are shipping WebXR in a Trusted Web Activity and hitting the same wall, get in touch; we would rather you skipped the seventeen days.