Quick answer: VR crash reports need headset-specific context that standard crash reporters don’t capture by default: headset model, firmware version, OpenXR runtime, rendering resolution, tracking mode, and controller state at crash time. Integrate Bugnet early in your XR initialization sequence and set these as session properties so every report is immediately filterable by hardware configuration.
VR crash reporting has a problem that flat game crash reporting doesn’t: the crash context that matters most is hardware-specific in ways that go beyond GPU model and driver version. A crash on a Meta Quest 3 with Link over USB-C, running at 120Hz with a custom resolution override, is a different bug environment than the same game running on a Valve Index via SteamVR at 90Hz. Without capturing that context at crash time, you’re staring at a stack trace with no idea which of your seventeen supported headset configurations it applies to. Here’s how to fix that.
What VR Crash Reports Need That Standard Reports Don’t
Standard crash reports capture OS version, CPU, GPU, driver version, and memory. All of that remains relevant for VR. On top of it, VR crash reports need:
- Headset model and firmware version: Different headset models and different firmware versions of the same model have meaningfully different OpenXR runtime behavior, tracking performance, and compositor characteristics. A bug that manifests on Quest 3 firmware 68.0 may be fixed in 69.0. You need to know which firmware was running.
- OpenXR runtime identifier: Your game may be running through the Meta OpenXR runtime (via Link or Air Link), the SteamVR OpenXR runtime, the Windows Mixed Reality runtime, or the Varjo runtime. These runtimes have different behavior, different extension support, and different performance characteristics. The same crash can be runtime-specific.
- Rendering resolution and refresh rate: Players frequently override the default resolution through the platform tools. Running at 1.5× supersampling with a 90Hz refresh rate is a significantly heavier GPU workload than the default. GPU timeout crashes are often resolution-specific.
- Tracking mode: Whether the player is using 6DOF (full positional tracking) or 3DOF (rotation only), and whether they are using inside-out tracking or external base station tracking, can affect physics, locomotion behavior, and camera rig logic.
- IPD setting: Interpupillary distance affects the lens correction and projection matrices. While rarely a crash cause directly, it can surface rendering math bugs that manifest as GPU errors.
- Controller state at crash time: The button held, the thumbstick position, and the trigger value at the moment of crash. This is the VR equivalent of “what was the player doing when it crashed,” and in VR, physical hand position matters in ways that keyboard state does not in flat games.
Capturing Headset Context in Bugnet
All of this context should be set as session properties in Bugnet immediately after your XR session initializes. The OpenXR API exposes most of what you need through system properties and the XrSystemProperties structure. Platform SDKs (Meta XR SDK, SteamVR API) provide additional detail.
For a Unity XR Toolkit integration:
using UnityEngine.XR;
using UnityEngine.XR.Management;
IEnumerator SetVRSessionContext() {
// Wait for XR to initialize
yield return new WaitUntil(() => XRGeneralSettings.Instance.Manager.isInitializationComplete);
var xrDisplaySubsystem = XRGeneralSettings.Instance.Manager
.activeLoader?.GetLoadedSubsystem<XRDisplaySubsystem>();
// Headset info
var headDevice = InputDevices.GetDeviceAtXRNode(XRNode.Head);
headDevice.TryGetFeatureValue(CommonUsages.deviceName, out string deviceName);
headDevice.TryGetFeatureValue(CommonUsages.deviceManufacturer, out string manufacturer);
Bugnet.SetSessionProperty("vr_headset_model", deviceName ?? "unknown");
Bugnet.SetSessionProperty("vr_headset_manufacturer", manufacturer ?? "unknown");
Bugnet.SetSessionProperty("vr_openxr_runtime", XRSettings.loadedDeviceName);
Bugnet.SetSessionProperty("vr_refresh_rate", XRDevice.refreshRate.ToString("F0"));
Bugnet.SetSessionProperty("vr_render_scale", XRSettings.eyeTextureResolutionScale.ToString("F2"));
Bugnet.SetSessionProperty("vr_tracking_origin",
XRInputSubsystem.GetTrackingOriginMode().ToString());
}
For a Godot OpenXR integration, set these properties in the _on_openxr_session_begun signal handler, where the XR session is confirmed active and system properties are available via the OpenXRInterface singleton.
VR-Specific Crash Causes
VR games crash for all the same reasons flat games do, plus a set of VR-specific causes that require different debugging approaches.
GPU timeout from missed frame deadline (TDR): VR compositors operate at a fixed refresh rate (72, 90, 120 Hz depending on headset and settings). If your GPU does not complete a frame within the deadline, the compositor may recover by dropping the frame or may escalate to a full GPU device removed error (DXGI_ERROR_DEVICE_REMOVED or Vulkan equivalent). This is a crash in the traditional sense — your render loop encounters an unrecoverable device error. These crashes are almost always GPU/resolution/content complexity related. Filter them by GPU model, refresh rate, and render scale to find the configuration combinations that are over budget.
OpenXR runtime errors: The OpenXR API returns result codes from every call. The correct pattern is to check every XrResult and handle failures explicitly. In practice, many developers check results only during initialization and skip them during the hot frame loop. An XrResult failure mid-frame — a tracking data query failure, a swapchain acquire timeout, a session state transition — that goes unhandled becomes either a silent malfunction or an access violation when the bad return value is subsequently used as a pointer or index.
Tracking loss crashes: When the headset loses tracking (the player goes outside the guardian boundary, covers the cameras, or encounters a poorly-lit environment), the XR runtime transitions through a set of session states. If your game treats the tracking-lost state as an error condition that terminates the session rather than a recoverable state, the player experiences a crash. Test tracking loss explicitly: cover the cameras mid-session and verify the game handles the state transition gracefully.
HMD disconnect: If the player’s headset cable disconnects or Air Link drops, your XR session may terminate abruptly. This often does not produce a traditional crash dump because the process may keep running while the XR session is gone. Handle the XR_SESSION_STATE_LOSS_PENDING and XR_SESSION_STATE_EXITING states, and send a Bugnet non-fatal event with the disconnect reason if the session is unrecoverable.
Capturing Headset Pose and Controller State at Crash Time
In a flat game, capturing what the player was doing at crash time means logging the current level, the last checkpoint, and recent input. In VR, physical state matters too. Crash context should include:
- Headset position and rotation (head pose) at the time of the crash
- Left and right controller positions, rotations, and grip states
- Whether the player was in a room-scale, stationary, or seated tracking configuration
- The current locomotion state (teleporting, smooth locomotion, stationary)
This data is useful for catching pose-dependent bugs — for example, a crash that only occurs when the player looks directly up (gimbal lock in a rotation implementation), or one that occurs only when the player reaches physically out of their guardian boundary. Log the pose data as a breadcrumb in Bugnet, updated every few seconds, so the crash report includes the last known physical state before the crash.
// Update VR breadcrumb every 2 seconds
IEnumerator UpdateVRBreadcrumb() {
while (true) {
var headPose = InputTracking.GetLocalPosition(XRNode.Head);
var leftHand = InputTracking.GetLocalPosition(XRNode.LeftHand);
var rightHand = InputTracking.GetLocalPosition(XRNode.RightHand);
Bugnet.LeaveBreadcrumb("vr_pose", new Dictionary<string, string> {
["head_x"] = headPose.x.ToString("F2"),
["head_y"] = headPose.y.ToString("F2"),
["head_z"] = headPose.z.ToString("F2"),
["left_hand_y"] = leftHand.y.ToString("F2"),
["right_hand_y"] = rightHand.y.ToString("F2")
});
yield return new WaitForSeconds(2f);
}
}
Testing Crash Reports in Developer Mode on Meta Quest
Meta Quest’s developer mode enables adb access and the developer options menu, which lets you test crash reporting without publishing to the store. To test Bugnet integration on Quest standalone:
- Enable developer mode on your Quest via the Meta smartphone app
- Build your APK with Bugnet SDK integrated and debug symbols included
- Sideload the APK via
adb install - Use
adb logcatto monitor log output while testing, confirming Bugnet initialization messages appear - Trigger a test crash using
Bugnet.ForceCrash()or a null dereference in test code - Verify the crash report appears in your Bugnet dashboard within 60 seconds of the crash
- Check that the headset model, firmware, and session properties are populated correctly
For PC VR (Unity or Godot targeting Windows with SteamVR or Oculus Link), you can trigger and verify test crashes in the editor with your headset connected. Use Bugnet’s test mode to send a synthetic crash report without actually crashing, which is useful for verifying that session properties are populated before committing to a real crash test.
Privacy Considerations for Headset Usage Data
VR data collection raises privacy questions that flat game telemetry does not. Headset pose data — your player’s physical position and head orientation in their real-world space — can theoretically be used to infer physical characteristics like height. IPD settings reveal biometric information. Room-scale boundaries describe the physical geometry of the player’s space.
The appropriate approach:
- Device capability metadata is low-risk: Headset model, firmware version, refresh rate, and rendering resolution are no more sensitive than GPU model and driver version. Collect these freely for crash diagnostics.
- Pose data should be crash-time only: Capturing head and controller pose every 2 seconds as a breadcrumb (as shown above) is reasonable for debugging. Collecting continuous pose telemetry for analytics purposes is a different question with stricter consent requirements.
- Disclose in your privacy policy: List the specific VR-related data you collect. Something like: “When a crash occurs, we collect your headset model, firmware version, and approximate head and controller position at the time of the crash for the purpose of debugging.”
- Respect platform policies: Meta’s developer policies have specific rules about collecting and storing positional tracking data. Review these before publishing to the Meta Horizon Store.
VR players are immersed when they crash — the experience is jarring in a way flat game crashes aren’t. Good crash reporting helps you fix these fast, and your players will feel the difference.“After integrating Bugnet with proper headset context tagging, we discovered that 80% of our GPU timeout crashes were coming from players who had manually set their render resolution above 1.4× in the SteamVR video settings. One line in our settings screen recommending the default resolution eliminated the vast majority of our crash reports.”