---
name: ferret-xr
description: Use to make a Ferret game playable in a VR headset (WebXR, Meta Quest): turning it on, the Enter VR button, controller and hand bindings, comfort settings, menus in the world, grabbing, VR budgets and testing without a headset.
---

# VR in a Ferret game

## When to use
A game should run in a headset, a VR control scheme is needed, something must be grabbed or pointed at, or the frame time in the headset is off.

## The model
VR is opt-in and the game does not change: `createGame({ xr: { enabled: true } })` loads `@ferret/xr` by `import()` only where `navigator.xr` exists (never on OS Beroun). The headset is the camera, the capsule stays the player, `UIService` is drawn on panels in the world, `Interactor` gets ray and touch sources, and `Grabbable` gives grabbing on the screen and in the headset.

## Steps
1. Pass `xr: { enabled: true, storageKey: '<game>' }` to `createGame`; add the samples' `addEnterVrButton(game)` (a session must start from a click) and honour `?xr`.
2. Add `xr` bindings for the game's existing actions (`xrStick`, `xrButton`, `xrPinch`); keep action names, never branch game code on VR.
3. Keep `turn` and `teleport` as the action names or set `xr.actions`; leave snap turn and vignette on by default.
4. Put every piece of text through `UIService`; own DOM elements are not seen in the headset.
5. Add `Grabbable` (with a `RigidBody`) to objects to take; set `maxMass`, `snapPoints`, `twoHanded`.
6. Do loading (glTF, shapes, NavMesh) behind a fade; check budgets with `xr.status` and the section on budgets in the guide.

## Check afterwards
`xr.status` on a screen reports unsupported and the game plays as before; unit tests with a fake `XrDriver`; Playwright with an emulated headset; on a Quest: comfort first, then frame time (13.9 ms Quest 2, 11.1 ms Quest 3).

## What usually goes wrong
- Assuming a feature (hand tracking, layers, multiview): they are optional; read `xr.status`.
- Starting a session without a user click: it is refused.
- Pushing the view back from a wall or moving the camera in code: the headset owns the head; use blink and fades.
- Removing snap turn or the vignette, or dropping the "In VR" settings page.
- Loading a heavy level in free play: a dropped frame in both eyes.
- Mapping buttons "like the console": the headset has its own kind (`xr`); do not reuse cursor/A/B keys.

## Files

- `reference/xr.md` — Turn a game into a VR game with one option: head and hands, controller bindings, comfort settings, menus in the world, grabbing, budgets for Quest.
- `reference/api-xr.md` — every export of `@ferret/xr` with signature and doc comment
- `reference/api-interaction.md` — every export of `@ferret/interaction` with signature and doc comment
- `reference/commands.md` — 4 commands of these packages with their arguments
