# @ferret/xr: API

VR through WebXR: a session service (detect, enter, leave, pause while the headset system menu is up), loaded only when a game asks for it.

52 exports. Generated from the source (`packages/xr/src`), so it matches the code.

## Classes

### BabylonXrDriver

*class* · `packages/xr/src/driver.ts:64`

```ts
class BabylonXrDriver implements XrDriver
```

The default driver: Babylon's WebXR experience helper on the game's scene.

- `constructor(private scene: BABYLON.Scene, private nav: { xr?: { isSessionSupported(m: string): Promise<boolean> } } = navigator as never)`
- `async isSupported(): Promise<boolean>` — `immersive-vr` is available (navigator.xr.isSessionSupported)
- `async enter(opts: XrEnterOptions, onVisibility: (v: XrVisibility) => void, onEnd: () => void): Promise<XrSessionInfo>` — enter the session; resolves when the first frame can be rendered
- `async exit(): Promise<void>`
- `headPose(): HeadPose | null` — the head in the room (local-floor space) for this moment; null while untracked
- `controllers(): Partial<Record<'left' | 'right', XrControllerState>>` — The xr-standard gamepad of Quest Touch / Touch Plus: 0 trigger, 1 squeeze, 3 thumbstick, 4 A or X, 5 B or Y; axes 2, 3 the stick (down-positive).
- `setFoveation(level: number): boolean` — fixed foveation 0..1 (false when the browser has none)
- `supportedFrameRates(): number[]` — the display rates the headset offers, Hz
- `async setFrameRate(hz: number): Promise<boolean>`
- `setFramebufferScale(scale: number): boolean` — change the framebuffer scale of a running session (false when this browser cannot)
- `pulse(hand: 'left' | 'right', strength: number, ms: number): void` — vibrate a controller (strength 0..1)
- `dispose()`

### FramebufferScaler

*class* · `packages/xr/src/tier.ts:15`

```ts
class FramebufferScaler
```

Moves the framebuffer scale between `min` and `max` from the measured frame times: down when frames are late, up when there is headroom.
It only says what it wants (`wanted`); the caller applies it when nobody would see the resolution change: between levels or behind a fade.

- `current: number` — the scale the session runs at now
- `constructor(readonly min = 0.8, readonly max = 1.2, start = 1, readonly step = 0.05, readonly window = 90)`
- `sample(frameMs: number, periodMs: number, dt: number)` — one rendered frame's time; `periodMs` is the display's frame period (13.9 at 72 Hz, 11.1 at 90 Hz)
- `get wanted(): number` — the scale it would like, one step at a time: down after 0.5 s of late frames, up after 3 s of headroom
- `applied(scale: number)` — the caller changed the framebuffer: start measuring again

### LazyFollow

*class* · `packages/xr/src/panel.ts:59`

```ts
class LazyFollow
```

Where the HUD hangs: in front of the head, catching up lazily when the head turns past `angle` radians.

- `yaw: number`
- `constructor(public angle = 30 * Math.PI / 180, public rate = 6)`
- `update(headYaw: number, dt: number, first = false): number` — returns the yaw the panel faces for a head yaw; call once per frame

### XrBody

*class* · `packages/xr/src/body.ts:20`

```ts
class XrBody
```

Arms by IK: the character's hands follow the controllers through the animation layer `IkRig.limb`, the legs keep
the locomotion animation. A hand that is not tracked leaves its arm as animated. Only the hand's position is driven; its turning is the animation's.

- `constructor(readonly o: XrBodyOptions, hands: () => Partial<Record<Hand, HandPose>> | undefined)`
- `dispose()`

### XrEffects

*class* · `packages/xr/src/effects.ts:11`

```ts
class XrEffects
```

Effects that move the view, made safe for a headset. The headset owns the head, so in a session:
- a **shake** becomes a short controller vibration (`camera:shake` { strength, seconds }, or `shake()`);
- a **scripted camera move** is a fade out, the move, a fade in (`camera:move` { apply }, or `scriptedMove()`);
- a **cutscene camera** plays on a virtual screen in front of the player (`camera:cutscene` { camera, seconds? }, or `cutscene()`).
Camera blends of `CameraRig` do nothing while the headset has the view (the rig is not placed). A game can handle the events itself first and mark them `handled`.

- `constructor(private xr: XrService, private world: World)`
- `shake(strength: number, seconds: number)` — Both controllers buzz for `seconds`, as strong as `strength` (0..1).
- `scriptedMove(apply: () => void)` — Fade out, `apply` the move while the view is black, fade in.
- `cutscene(camera: BABYLON.Camera, seconds?: number, width = 2.4)` — Show what `camera` sees on a screen `width` metres wide, 2.2 m in front of the player (fixed in the world), for `seconds` (until `stopCutscene` when omitted).
- `stopCutscene()`
- `get cutsceneActive(): boolean`
- `update(dt: number)` — Per frame: the cutscene's timer.
- `dispose()`

### XrHands

*class* · `packages/xr/src/hands.ts:31`

```ts
class XrHands
```

The player's hands as focus sources and grabbers. Each hand points a ray (the laser) and can touch: both feed the player's `Interactor` as
focus sources (the trigger uses what the ray points at; touching uses it too), and the squeeze takes the `Grabbable` the hand touches or points at.
`Interactable` does not change: `interact(actor)` is called by whatever source focused it.

- `readonly poses: Partial<Record<Hand, HandPose>>`
- `readonly aimed: Partial<Record<Hand, { target: Entity; point: BABYLON.Vector3 }>>` — what each hand's ray points at now, for drawing the laser
- `reach: number` — how far a hand reaches with its ray, metres, and how close counts as touching
- `touchPad: number`
- `grabPad: number`
- `showModels: boolean` — draw a simple procedural controller and a laser while a ray points at something (the Quest Touch glTF models are not shipped: no asset to ship offline yet)
- `constructor(readonly world: World, readonly rig: XrRig, readonly actor: Entity, public dominant: () => Hand = () => 'right')`
- `update(cs: Partial<Record<Hand, XrControllerState>> | undefined)` — One rendered frame: hand poses in the world, the trigger and squeeze edges.
- `dispose()`

### XrLocomotion

*class* · `packages/xr/src/locomotion.ts:28`

```ts
class XrLocomotion
```

Turn, teleport and the comfort effects of a VR player. Moving is the game's `move` action, read by the player controller relative to
the rig's forward; this class adds the stick turn (snap or smooth), the vignette and the teleport with its blink.

- `vignette: number` — 0..1 strength of the vignette this frame
- `blink: number` — 0..1 black-out of a jump of the view (teleport, respawn)
- `aiming: Arc | null`
- `constructor(readonly rig: XrRig, public settings: () => XrSettings, readonly blinkTime = 0.12)`
- `turn(x: number, dt: number): number` — The stick turn for one frame: x in -1..1. Returns the yaw turned.
- `update(dt: number, turnX: number, moveSpeed01: number)` — Per frame: stick turn, vignette and the blink of a teleport. `moveSpeed01` is the player's speed as a fraction of the walking speed.
- `aim(physics: PhysicsService, hand: { origin: BABYLON.Vector3; dir: BABYLON.Vector3 } | null, held: boolean, canStand: (p: BABYLON.Vector3) => boolean = () => true)` — Aim with the hand: call each frame; `held` is the teleport action. Releasing on a valid target teleports.
- `teleportTo(p: BABYLON.Vector3)` — Blink out, move the capsule, blink back in: the view never slides.
- `jump(change: () => void)` — Any jump of the view (teleport, respawn, a scripted camera move): black out, run `change` while nothing is seen, black back in.
- `get blackout(): number` — What the black-out sphere shows: the larger of the rig's wall fade and the blink.

### XrPanel

*class* · `packages/xr/src/panel.ts:74`

```ts
class XrPanel
```

One panel in the world: a plane with a texture the painter draws. Headless (no document) it has no texture but lays out and hit-tests the same.

- `readonly mesh: BABYLON.Mesh`
- `width: number`
- `height: number`
- `yaw: number`
- `constructor(scene: BABYLON.Scene, readonly name: string)`
- `get shown()`
- `get center()`
- `place(pos: BABYLON.Vector3, yaw: number)`
- `show(pxW: number, pxH: number, signature: string, paint: (ctx: CanvasRenderingContext2D, w: number, h: number) => void)` — Show with a layout in texture pixels; `signature` changes when the content does, and only then is the texture painted again.
- `hide()`
- `dispose()`

### XrRemoteAvatar

*class* · `packages/xr/src/presence.ts:45`

```ts
class XrRemoteAvatar extends Component
```

The look of a remote VR player: a head and two hands, moved smoothly between the updates (about 10 per second). Hidden when the owner leaves VR.

- `head: BABYLON.Mesh`
- `hands: Record<'left' | 'right', BABYLON.Mesh>`
- `follow: number` — how fast the pieces follow the latest pose, 1/s
- `onAttach()`
- `receive(p: XrPoses | undefined)` — Newest poses from the owner; `undefined` when the owner is no longer in VR.
- `get inVr(): boolean`
- `onUpdate(dt: number)`
- `onDetach()`

### XrRig

*class* · `packages/xr/src/rig.ts:25`

```ts
class XrRig
```

The head and body of a VR player. The character's capsule is the player: the rig's `root` sits at the capsule's feet and turns with the
character's yaw, and the headset adds its own offset and rotation under it. When the player walks in the room the capsule is moved toward the head's
floor position through `CharacterMovement` (with collisions), so the character cannot be walked through a wall; if the head gets into geometry
the view fades to black until it comes out. The rig never pushes the view back: the head owns the head.

- `readonly root: BABYLON.TransformNode` — the node the XR camera is parented to: at the feet, turned by the rig's yaw
- `yaw: number`
- `seated: boolean`
- `forward: 'head' | 'left'`
- `fade: number` — 0..1, how much of the view is faded to black because the head is in geometry
- `head: HeadPose`
- `leftYaw: number` — the left controller's yaw in the room, when the game follows it
- `constructor(scene: BABYLON.Scene, readonly movement: CharacterMovement, opts: XrRigOptions = {})`
- `take(player: PlayerController)` — Take the view from the player controller (and give it back with `release`).
- `release()`
- `forwardYaw(): number` — The yaw "move forward" means in the world.
- `recenter()` — Put the rig under the capsule so the head is over it, now.
- `turnBy(dyaw: number)` — A turn of the view around the head (snap or smooth): the head stays where it is in the world.
- `step(head: HeadPose)` — One fixed step with the head pose sampled for it: locomotion carries the root, then the capsule follows the head.
- `dispose()`

### XrService

*class* · `packages/xr/src/service.ts:54`

```ts
class XrService implements Service
```

VR through WebXR. The game turns it on with `createGame({ xr: { enabled: true } })`; the service detects
`immersive-vr`, enters and leaves a session at run time, and pauses the world while the headset's system menu is up.
Events: `xr:enter`, `xr:exit`, `xr:state`.

- `readonly name: "xr"`
- `supported: boolean`
- `state: XrState`
- `session?: XrSessionInfo`
- `rig?: XrRig` — the head and body of the player while in a session (needs a character with a PlayerController)
- `settings: XrSettings`
- `loco?: XrLocomotion`
- `ui?: XrUi` — the UI model drawn in the world, when the game has a `ui` service
- `hands?: XrHands` — the hands: ray and touch focus for the Interactor, grabbing
- `bodyArms?: XrBody` — the character's arms, when `body: 'arms'`
- `effects?: XrEffects` — shake, scripted camera moves and cutscene cameras, as 4.3 says
- `scaler?: FramebufferScaler` — the framebuffer scale that follows the frame time (tier `xr`)
- `tier: { foveation: boolean; frameRate: number; framebufferScale: number; }` — what the tier asked of the browser and what it gave: for `xr.status`
- `constructor(readonly opts: XrOptions = {}, scene?: BABYLON.Scene)`
- `get active(): boolean`
- `init(world: World)`
- `setSettings(p: Partial<XrSettings>)`
- `settingsItem()` — The "In VR" page for the game's pause menu: `items.push(xr.settingsItem())`.
- `fixedUpdate()` — Called before the components' fixed update, at the fixed rate.
- `update(dt: number)` — Per rendered frame: controllers into the input service, then the stick turn, the vignette, the teleport and the black-out.
- `checkSupport(): Promise<boolean>` — Ask the browser once whether `immersive-vr` can start.
- `status(): XrStatus`
- `async enter(opts: XrEnterOptions = {}): Promise<boolean>`
- `async exit(): Promise<void>`
- `pulse(hand: 'left' | 'right', strength: number, ms: number)` — Vibrate a controller. No-op outside a session.
- `dispose()`

### XrUi

*class* · `packages/xr/src/ui.ts:19`

```ts
class XrUi
```

The UI model drawn in the world: the menu at 1.5 m when opened, then fixed in the world; the HUD and the prompt on a small panel that follows
the head lazily; the loading bar as a panel. The menu is chosen by the laser and the trigger, or by the stick and A / B as on the screen.

- `readonly menu: XrPanel`
- `readonly hud: XrPanel`
- `readonly loading: XrPanel`
- `hover: number` — the row the laser points at, -1 for none
- `laser: { from: BABYLON.Vector3; to: BABYLON.Vector3 } | null`
- `constructor(scene: BABYLON.Scene, readonly ui: UIService, readonly distance = 1.5)`
- `update(dt: number, head: Head, input: UiInput)`
- `dispose()`

## Functions

### addXr

*function* · `packages/xr/src/service.ts:340`

```ts
function addXr(world: World, opts: XrOptions | undefined, nav?: unknown): XrService | undefined
```

Add the service where it can work: `xr` set and WebXR in the browser. Anywhere else (OS Beroun) nothing is added.

### arcTarget

*function* · `packages/xr/src/locomotion.ts:8`

```ts
function arcTarget(physics: PhysicsService, origin: BABYLON.Vector3, dir: BABYLON.Vector3, opts: { speed?: number; gravity?: number; steps?: number; minUp?: number } = {}): Arc
```

Where the hand's arc lands: a ballistic arc from the hand along its pointing direction, stopped by the first surface.

### chooseFrameRate

*function* · `packages/xr/src/tier.ts:4`

```ts
function chooseFrameRate(supported: number[], frameMs: number, preferred = 90): number
```

The highest display rate the frame time allows (frames must fit 85% of the period), never below the lowest the headset offers.

### clampSettings

*function* · `packages/xr/src/settings.ts:35`

```ts
function clampSettings(s: XrSettings, limits: XrLimits = {}): XrSettings
```

### findPlayer

*function* · `packages/xr/src/rig.ts:104`

```ts
function findPlayer(world: { entitiesWith<C extends PlayerController>(c: new (...a: any[]) => C): Entity[] }): { entity: Entity; player: PlayerController } | undefined
```

The entity of the first player controller in a world, if any.

### hasWebXr

*function* · `packages/xr/src/driver.ts:59`

```ts
function hasWebXr(nav: unknown = typeof navigator === 'undefined' ? undefined : navigator): boolean
```

True where the browser has WebXR at all: the only check made on a page that does not use VR (OS Beroun has none).

### hitMenu

*function* · `packages/xr/src/panel.ts:21`

```ts
function hitMenu(l: MenuLayout, px: number, py: number): number
```

The row under a point of the panel (texture pixels), or -1.

### hudLayout

*function* · `packages/xr/src/panel.ts:30`

```ts
function hudLayout(ui: Pick<UIService, 'hud' | 'promptText' | 'toasts'>): HudLayout | null
```

### loadingLayout

*function* · `packages/xr/src/panel.ts:39`

```ts
function loadingLayout(ui: Pick<UIService, 'loading'>): LoadingLayout | null
```

### localStore

*function* · `packages/xr/src/settings.ts:24`

```ts
function localStore(key: string): SettingsStore
```

Browser storage under the game's key.

### menuLayout

*function* · `packages/xr/src/panel.ts:14`

```ts
function menuLayout(menu: MenuDef, index: number): MenuLayout
```

The menu as rows on a panel, in texture pixels: the same model the DOM overlay draws.

### rayOnPanel

*function* · `packages/xr/src/panel.ts:44`

```ts
function rayOnPanel(origin: BABYLON.Vector3, dir: BABYLON.Vector3, center: BABYLON.Vector3, yaw: number): { u: number; v: number; distance: number } | null
```

A point on a panel (the plane through `center` facing `yaw`) hit by a ray: metres across and up from the panel's centre, or null.

### settingsItem

*function* · `packages/xr/src/settings.ts:48`

```ts
function settingsItem(get: () => XrSettings, set: (p: Partial<XrSettings>) => void, limits: XrLimits = {}): MenuItem
```

The "In VR" page a game adds to its pause menu: `items.push(xr.settingsItem())`

### toPixels

*function* · `packages/xr/src/panel.ts:56`

```ts
function toPixels: (u: number, v: number, w: number, h: number) => { px: number; py: number; }
```

The panel in texture pixels from metres across and up.

### xrPoseField

*function* · `packages/xr/src/presence.ts:19`

```ts
function xrPoseField(xr: () => XrService | undefined): ReplicatedField
```

The replicated field of a VR player: add it to the avatar's `Replicated` fields (`{ xr: xrPoseField(() => game.xr) }`). A player on a screen
(no session) sends nothing new, so the field is absent for them; the others see a VR player's head and hands move.

## Interfaces

### HandPose

*interface* · `packages/xr/src/hands.ts:9`

```ts
interface HandPose
```

A hand in the world: where it is, which way it points, and its pointing ray.

- `position: BABYLON.Vector3`
- `rotation: BABYLON.Quaternion`
- `dir: BABYLON.Vector3`

### Head

*interface* · `packages/xr/src/ui.ts:13`

```ts
interface Head
```

- `position: BABYLON.Vector3`
- `yaw: number`

### HeadPose

*interface* · `packages/xr/src/rig.ts:5`

```ts
interface HeadPose
```

The head in the room (the `local-floor` reference space): metres from the room's origin on the floor, and the head's yaw.

- `x: number`
- `y: number`
- `z: number`
- `yaw: number`

### HudLayout

*interface* · `packages/xr/src/panel.ts:26`

```ts
interface HudLayout
```

- `lines: string[]`
- `prompt: string`
- `toasts: string[]`
- `width: number`
- `height: number`

### LoadingLayout

*interface* · `packages/xr/src/panel.ts:38`

```ts
interface LoadingLayout
```

- `title: string`
- `progress: number`
- `width: number`
- `height: number`

### MenuLayout

*interface* · `packages/xr/src/panel.ts:6`

```ts
interface MenuLayout
```

- `width: number`
- `height: number`
- `title: string`
- `message?: string`
- `items: Array<{ id: string; y: number; h: number; label: string; value: string; disabled: boolean; selected: boolean }>`

### SettingsStore

*interface* · `packages/xr/src/settings.ts:21`

```ts
interface SettingsStore
```

- `load(): Partial<XrSettings> | null`
- `save(s: XrSettings): void`

### UiInput

*interface* · `packages/xr/src/ui.ts:4`

```ts
interface UiInput
```

- `ray: { origin: BABYLON.Vector3; dir: BABYLON.Vector3 } | null` — world ray of the pointing hand, or null
- `accept: boolean` — edges this frame: trigger/A = accept, B = back, stick up/down = move
- `back: boolean`
- `move: number`
- `select?: boolean` — the trigger: chooses the row the laser points at (nothing when it points elsewhere)

### XrBodyOptions

*interface* · `packages/xr/src/body.ts:4`

```ts
interface XrBodyOptions
```

- `ik: IkRig` — the character's IK rig (built before any clip played)
- `arms: { left: [string, string, string]; right: [string, string, string] }` — [shoulder, elbow, hand] bone names per arm
- `show?: BABYLON.AbstractMesh[]` — meshes of the body to show in the headset (arms, torso) and to hide (the head the camera sits in)
- `hide?: BABYLON.AbstractMesh[]`
- `elbow?: [number, number, number]` — the elbow bends toward this point relative to the hand, in the rig's frame: behind and below by default

### XrControllerState

*interface* · `packages/xr/src/driver.ts:48`

```ts
interface XrControllerState
```

- `buttons: Partial<Record<'trigger' | 'squeeze' | 'thumbstick' | 'a' | 'b' | 'x' | 'y' | 'menu', boolean>>`
- `stick: { x: number; y: number }`
- `pinch?: boolean` — hand tracking: thumb to index finger
- `pose?: XrPose`

### XrDriver

*interface* · `packages/xr/src/driver.ts:26`

```ts
interface XrDriver
```

- `isSupported(): Promise<boolean>` — `immersive-vr` is available (navigator.xr.isSessionSupported)
- `enter(opts: XrEnterOptions, onVisibility: (v: XrVisibility) => void, onEnd: () => void): Promise<XrSessionInfo>` — enter the session; resolves when the first frame can be rendered
- `exit(): Promise<void>`
- `pulse?(hand: 'left' | 'right', strength: number, ms: number): void` — vibrate a controller (strength 0..1)
- `headPose?(): HeadPose | null` — the head in the room (local-floor space) for this moment; null while untracked
- `controllers?(): Partial<Record<'left' | 'right', XrControllerState>>` — both controllers for this frame: buttons, the stick (up-positive) and the grip pose in the room
- `setFoveation?(level: number): boolean` — fixed foveation 0..1 (false when the browser has none)
- `supportedFrameRates?(): number[]` — the display rates the headset offers, Hz
- `setFrameRate?(hz: number): Promise<boolean>`
- `setFramebufferScale?(scale: number): boolean` — change the framebuffer scale of a running session (false when this browser cannot)
- `dispose?(): void`

### XrEnterOptions

*interface* · `packages/xr/src/driver.ts:8`

```ts
interface XrEnterOptions
```

- `features?: string[]` — WebXR features asked for as optional (default: hand-tracking, layers, local-floor, bounded-floor)
- `framebufferScale?: number` — the framebuffer scale (1 = the headset's recommended size)

### XrLimits

*interface* · `packages/xr/src/settings.ts:33`

```ts
interface XrLimits
```

Limits a game puts on the settings: `teleport: false` = a game with no teleport.

- `teleport?: boolean`
- `seated?: boolean`
- `smoothTurn?: boolean`

### XrOptions

*interface* · `packages/xr/src/service.ts:20`

```ts
interface XrOptions extends XrEnterOptions
```

- `enabled?: boolean`
- `rig?: XrRigOptions` — the head-and-body rig: seated play, which hand "forward" follows
- `frameRate?: number` — the display rate the game prefers, Hz (default 90: the highest the headset offers up to it)
- `body?: 'hands' | 'arms'` — `hands` (default): controllers or tracked hands only, the character's body hidden; `arms`: the character's arms follow the controllers by two-bone IK (needs `bodyRig`)
- `bodyRig?: XrBodyOptions`
- `storageKey?: string` — the game's key for saving the player's VR settings (default "game")
- `store?: SettingsStore`
- `limits?: XrLimits`
- `actions?: { turn?: string; teleport?: string }` — the game's actions the VR locomotion reads (default turn = "turn", teleport = "teleport")
- `driver?: XrDriver` — a driver other than Babylon's (tests)

### XrPoses

*interface* · `packages/xr/src/presence.ts:6`

```ts
interface XrPoses
```

Head and hands of a VR player relative to the avatar's feet, in world axes, millimetres rounded: what travels.

- `h: [number, number, number, number]`
- `l?: [number, number, number, number, number, number, number]`
- `r?: [number, number, number, number, number, number, number]`

### XrRigOptions

*interface* · `packages/xr/src/rig.ts:7`

```ts
interface XrRigOptions
```

- `seated?: boolean` — seated play: the rig is raised so the eyes are at the character's eye height
- `forward?: 'head' | 'left'` — "move forward" follows the head's yaw (default) or the left controller's
- `fadeStart?: number` — horizontal distance between the head and the capsule (blocked by a wall) where the view starts to fade, and where it is black
- `fadeFull?: number`

### XrSessionInfo

*interface* · `packages/xr/src/driver.ts:15`

```ts
interface XrSessionInfo
```

- `features: string[]` — the features the session actually got
- `frameRate: number` — the headset's display rate in Hz (0 = unknown)
- `multiview?: boolean` — one draw for both eyes is on (reported by the browser's capabilities; only the headset run shows whether it helps)
- `camera?: BABYLON.Camera` — the camera of the player's view in the session

### XrSettings

*interface* · `packages/xr/src/settings.ts:4`

```ts
interface XrSettings
```

Comfort and control settings of the player; a game may limit them, never remove the snap turn and the vignette.

- `turn: 'snap' | 'smooth'`
- `snapAngle: 30 | 45 | 90` — degrees per snap
- `turnSpeed: number` — rad/s at full stick
- `vignette: number` — 0..1, the vignette while moving or turning smoothly
- `seated: boolean`
- `forward: 'head' | 'left'`
- `dominantHand: 'left' | 'right'`
- `teleport: boolean` — teleport (hold the `teleport` action, release to go) in addition to smooth move

### XrStatus

*interface* · `packages/xr/src/service.ts:39`

```ts
interface XrStatus
```

- `supported: boolean`
- `state: XrState`
- `active: boolean`
- `features: string[]`
- `frameRate: number`
- `tier?: { foveation: boolean; frameRate: number; framebufferScale: number; multiview: boolean }` — what the headset tier got from the browser, while a session runs

## Types

### Hand

*type* · `packages/xr/src/hands.ts:6`

```ts
type Hand = 'left' | 'right'
```

### XrState

*type* · `packages/xr/src/service.ts:18`

```ts
type XrState = 'off' | 'starting' | 'visible' | 'blurred'
```

`blurred`: the headset's system menu is up or the headset is off the head.

### XrVisibility

*type* · `packages/xr/src/driver.ts:6`

```ts
type XrVisibility = 'visible' | 'visible-blurred' | 'hidden'
```

What the session service needs from a WebXR implementation. Babylon's is the default; tests pass a fake.

## Constants

### DEFAULT_FEATURES

*const* · `packages/xr/src/driver.ts:56`

```ts
const DEFAULT_FEATURES: string[]
```

### DEFAULT_SETTINGS

*const* · `packages/xr/src/settings.ts:19`

```ts
const DEFAULT_SETTINGS: XrSettings
```

### PPM

*const* · `packages/xr/src/panel.ts:4`

```ts
const PPM: 512
```

Pixels per metre on a panel's texture

