Skip to content

@gameable/xr ​

Interfaces ​

ComfortOptions ​

What a game may change about comfort; every default is the safe one.

Properties ​

catchUp? ​
ts
readonly optional catchUp?: "glide" | "snap";

How a following view catches up with its hero: a short glide (the default), or one step.

deadZone? ​
ts
readonly optional deadZone?: number;

How far the hero may move before the view follows, real metres. Default DEAD_ZONE.

narrow? ​
ts
readonly optional narrow?: boolean;

Narrow the view (a soft tunnel) while it moves. Default true.

turn? ​
ts
readonly optional turn?: "steps" | "smooth";

Turning with the right stick: smoothly while held (the default: 90 degrees a second at full tilt; Isaac on 3 Oct 2026 found steps "impossible to use"), or in 30-degree steps per flick. The Turning lever changes it.

yawDeadZone? ​
ts
readonly optional yawDeadZone?: number;

How far the hero may turn before the view follows, radians. Default YAW_DEAD_ZONE.


ContentFrame ​

What the content is: where it stands and which way it faces, in the scene.

Properties ​

anchor ​
ts
readonly anchor: Vec3;

The floor point under the content (under the characters' feet, between them), scene units.

facing ​
ts
readonly facing: number;

The way the content faces, as a yaw about the vertical (0 faces +z, pi/2 faces +x), radians.


FollowState ​

Where a following view holds, and whether it is on the move.

Properties ​

at ​
ts
at: [number, number, number];

The floor point the view holds, scene units.

facing ​
ts
facing: number;

The facing the view holds, radians.

moving ​
ts
moving: boolean;

True while the view is catching up (gliding, or just after a snap).

narrowLeft ​
ts
narrowLeft: number;

Seconds left of a snap's narrowing.

started ​
ts
started: boolean;

True once the first sample has been taken.


PanelAngles ​

A button's place as seen from the head: degrees below the horizon and to the side of straight ahead.

Properties ​

below ​
ts
below: number;

Degrees below the eye line (negative above).

corners ​
ts
corners: [number, number, number][];

The button's four corners, real metres in the rig (for the proof that it covers nothing).

id ​
ts
id: string;
label ​
ts
label: string;
left ​
ts
left: number;

The button's left and right edges, degrees to the side.

position ​
ts
position: [number, number, number];

Real metres in the rig.

ts
right: number;
side ​
ts
side: number;

Degrees to the side of straight ahead (positive right).


PanelButton ​

One button on the panel.

Properties ​

disabled? ​
ts
readonly optional disabled?: () => boolean;

Greyed and inert while true.

Returns ​

boolean

id ​
ts
readonly id: string;

A stable id.

label ​
ts
readonly label: () => string;

Its words now (read on every redraw, so a page's own button's words follow).

Returns ​

string

onPress ​
ts
readonly onPress: () => void;

What a press does.

Returns ​

void

pin? ​
ts
readonly optional pin?: boolean;

On every page of the panel (Leave): the last slots before the page flip.

primary? ​
ts
readonly optional primary?: boolean;

Mint (the one action the page wants most), or black.


RealTarget ​

Where the content should appear in the real room.

Properties ​

point ​
ts
readonly point: Vec3;

The real point the anchor lands on, metres from the real origin on the floor.

size ​
ts
readonly size: number;

Real metres per scene unit: 1 life-size, 0.1 at a tenth of the size.

yaw ​
ts
readonly yaw: number;

The real yaw the content should face (0 faces +z, towards a viewer standing at the origin).


RigPose ​

The camera parent's transform, in scene units.

Properties ​

position ​
ts
readonly position: [number, number, number];

Where the real-space origin lands in the scene.

scale ​
ts
readonly scale: number;

Scene units per real metre (10 on a tabletop at a tenth of the size).

yaw ​
ts
readonly yaw: number;

The turn about the vertical, radians.


RoomControlsOptions ​

Physics and authored-floor hooks for a VR room, also usable from a desktop ray.

Example ​

ts
const controls = createRoomControls({ ...hooks, context: engine.ctx, xr: engine.get('xr'), floor });

Properties ​

context ​
ts
context: EngineContext;
floor ​
ts
floor: TeleportFloor;
xr ​
ts
xr: XrService;

Methods ​

clear() ​
ts
clear(at): boolean;
Parameters ​
ParameterType
atVec3
Returns ​

boolean

move() ​
ts
move(
   body, 
   at, 
   rotation
): void;

Move a held body; callbacks borrow arrays only for the duration of the call.

Parameters ​
ParameterType
bodynumber
atVec3
rotationreadonly [number, number, number, number]
Returns ​

void

pick() ​
ts
pick(origin, direction): number;

Nearest grabbable within reach of a ray, or zero.

Parameters ​
ParameterType
originVec3
directionVec3
Returns ​

number

pull()? ​
ts
optional pull(
   hand, 
   start, 
   at
): void;

A pull gesture while gripping empty space (start and current hand positions).

Parameters ​
ParameterType
handnumber
startVec3
atVec3
Returns ​

void

release() ​
ts
release(body, velocity): void;
Parameters ​
ParameterType
bodynumber
velocityVec3
Returns ​

void


ShoulderNumbers ​

How far behind and beside a hero the over-the-shoulder view stands, real metres.

Properties ​

back? ​
ts
readonly optional back?: number;

Behind the hero. Default 1.6.

right? ​
ts
readonly optional right?: number;

To the hero's right (negative: left). Default 0.45.


SnapTurn ​

A snap turn's state: armed until the stick goes past the flick point, re-armed when it comes back.

Properties ​

armed ​
ts
armed: boolean;

SpeedResult ​

One measured step.

Properties ​

cpuMs ​
ts
cpuMs: number;
cut ​
ts
cut: boolean;

True when the step was cut short under 15 frames a second ("too slow").

fps ​
ts
fps: number;
gpuMs ​
ts
gpuMs: number | null;
inForce? ​
ts
optional inForce?: unknown;

What the runtimes ran during the step, from their own stats (the page's inForce).

label ​
ts
label: string;
levers ​
ts
levers: Record<string, string> | null;

The levers' settings in force (the ids' words: the log's names; the drawing size and the eyes' passes as entered).

points ​
ts
points: number;
samples? ​
ts
optional samples?: number;

Half-second samples the median came from, and their spread.

skipped? ​
ts
optional skipped?: boolean;

True when the step was not run because an earlier step of its group was too slow.

spread? ​
ts
optional spread?: [number, number];

SpeedStep ​

One step of the speed test.

Properties ​

apply ​
ts
readonly apply: () => void | Promise<void>;

Put the setting in place.

Returns ​

void | Promise<void>

group? ​
ts
readonly optional group?: string;

The level the step tests ('512'): a step cut as too slow skips the rest of its group.

label ​
ts
readonly label: string;

Plain words for the table.

needsEntry? ​
ts
readonly optional needsEntry?: boolean;

The setting is read when a view starts (the eyes' passes, the drawing size): run after a fresh entry, last.

seconds? ​
ts
readonly optional seconds?: number;

Seconds to measure after the first second is discarded. Default 3 (at least 3).


StickKeys ​

Which keys a stick's position holds down.

Properties ​

KeyA ​
ts
readonly KeyA: boolean;
KeyD ​
ts
readonly KeyD: boolean;
KeyS ​
ts
readonly KeyS: boolean;
KeyW ​
ts
readonly KeyW: boolean;

TapFacts ​

The facts a tap is decided on.

Properties ​

hitFresh ​
ts
hitFresh: boolean;

The ring shows a floor spot this frame.

listening ​
ts
listening: boolean;

A game listens for floor (the visit page's walk rule does).

onCharacter ​
ts
onCharacter: boolean;

The tap's own ray passes through a character (press was taken).

placed ​
ts
placed: boolean;

The content stands somewhere already (a phone: a tap placed it; a headset: always).


TeleportFloor ​

Walkable floor rectangle in scene metres; clearance keeps feet off edges.

Example ​

ts
const floor: TeleportFloor = { minX: -3, maxX: 3, minZ: -3, maxZ: 3, y: 0, clearance: 0.3 };

Properties ​

clearance ​
ts
clearance: number;
maxX ​
ts
maxX: number;
maxZ ​
ts
maxZ: number;
minX ​
ts
minX: number;
minZ ​
ts
minZ: number;
y ​
ts
y: number;

ViewNumbers ​

What the fixed views need beyond the content.

Properties ​

standBack? ​
ts
readonly optional standBack?: number;

How far in front of the viewer the content stands in a headset, real metres. Default 2.2.

tableHeight? ​
ts
readonly optional tableHeight?: number;

The table's height when no real table was detected, real metres. Default 0.75.

tabletopScale? ​
ts
readonly optional tabletopScale?: number;

The tabletop view's size, real metres per scene unit. Default 0.1.


XrEvents ​

The module's events.

Properties ​

end ​
ts
end: object;

The view ended; the page is as before.

mode ​
ts
mode: XrMode;
floor ​
ts
floor: object;

The floor was tapped once the content stands in the room (a phone: the spot the ring shows; a headset: where the controller's ray meets the real floor), and no press listener took the tap (floor.ts has the rule). point is the spot and head the person's head, both in scene units (through the rig), so a game sends its character walking there and has it arrive facing the person. A listener that sends the character sets handled = true: the module then marks the spot with a ring until mark(null) and does not place the content again. With nobody listening, or handled left false, a phone places the content there as before and a headset's trigger stays the game's click.

hand ​
ts
hand: XRHandedness;
handled ​
ts
handled: boolean;
ts
head: Vec3;
point ​
ts
point: Vec3;
placed ​
ts
placed: object;

The content was placed (a phone) or the view changed.

target ​
ts
target: RealTarget;
view ​
ts
view: XrView;
press ​
ts
press: object;

A controller's trigger was pulled off the panel, or a phone's screen was tapped: the pointing ray in scene units (through the rig), so a game can pick what it points at (a hero to choose, a foe to aim at). A listener that uses the press sets handled = true: the module then neither clicks the canvas for it nor, on a phone, places the content again.

direction ​
ts
direction: Vec3;
hand ​
ts
hand: XRHandedness;
handled ​
ts
handled: boolean;
origin ​
ts
origin: Vec3;
ready ​
ts
ready: object;

The browser answered, and the buttons are decided.

ar ​
ts
ar: boolean;
vr ​
ts
vr: boolean;
start ​
ts
start: object;

A view started.

mode ​
ts
mode: XrMode;
view ​
ts
view: XrView;

XrFollow ​

What a game tells a following view (over the shoulder, through the eyes) about its hero.

Properties ​

at ​
ts
readonly at: (view) => 
  | {
  at: Vec3;
  facing: number;
  offset?: Vec3;
}
  | null;

Where the hero stands this frame: the FLOOR point under it (scene units) and the yaw it faces (the game's own look, so the stick's forward is the hero's forward), or null to hold the last. Read every frame while such a view is on. The module adds the shoulder's offset itself for overShoulder (numbers.shoulder: 1.6 m back, 0.45 m right) unless the sample names its own offset: where the viewer stands from the point in the view's frame, metres right, up and back ([0.35, 0, 1.7] behind a shoulder; [0, 0, -0.25] a hand ahead of the eyes); it turns with the facing. Its up raises the floor the viewer stands on (the real floor stays the floor: a person sees from their own height above it).

Parameters ​
ParameterType
viewFollowView
Returns ​

| { at: Vec3; facing: number; offset?: Vec3; } | null

turn? ​
ts
readonly optional turn?: (radians) => void;

Turn the hero's look by this yaw (radians, positive left): the right stick's turn goes to the game, so the hero and the view turn together and walking stays in the direction looked. A game that looks with the locked mouse passes (r) => input.look(-r / sensitivity, 0) (the input module's look turn, in the mouse's pixels). Without it the rig turns, and the next catch-up undoes it.

Parameters ​
ParameterType
radiansnumber
Returns ​

void


XrLeverHooks ​

What the page gives the levers that need it.

Properties ​

detail? ​
ts
readonly optional detail?: object;

The character detail levels: their names, the one in force, and how to swap. Absent: no detail lever.

card? ​
ts
readonly optional card?: () => string;

Words for the card's settings line (with automatic: which level each character is at). Default: current().

Returns ​

string

current ​
ts
readonly current: () => string;
Returns ​

string

levels ​
ts
readonly levels: readonly string[];

The levels, lightest first; 'automatic' last when the page runs the governor.

set ​
ts
readonly set: (level) => void | Promise<void>;
Parameters ​
ParameterType
levelstring
Returns ​

void | Promise<void>

draw? ​
ts
readonly optional draw?: boolean;

Offer the drawing-size lever. Default true.

drawShape? ​
ts
readonly optional drawShape?: (mode) => void;

How far or small characters are drawn: 'full' (today's vertex stage for every point) or 'auto' (the cheap disc stage once a character stands under about 220 px tall on screen). Shown as "Far characters: full / simple". Absent: no such lever.

Parameters ​
ParameterType
mode"auto" | "full"
Returns ​

void

extra? ​
ts
readonly optional extra?: object;

A lever of the page's own (the hall's Heroes 1 / 2 / 4): its name, its settings' words, the one in force, the move.

current ​
ts
readonly current: () => string;
Returns ​

string

name ​
ts
readonly name: string;
set ​
ts
readonly set: (setting) => void | Promise<void>;
Parameters ​
ParameterType
settingstring
Returns ​

void | Promise<void>

settings ​
ts
readonly settings: readonly string[];
fill? ​
ts
readonly optional fill?: object;

The fill's presets (FILL_PRESET_NAMES of gameable/character: the square tighter, faint points dropped, a smaller buffer per eye) and how to apply one. Shown as "Lighter drawing". Absent: no fill lever.

check? ​
ts
readonly optional check?: (preset) => boolean;
Parameters ​
ParameterType
presetstring
Returns ​

boolean

presets ​
ts
readonly presets: readonly string[];
set ​
ts
readonly set: (preset) => void;
Parameters ​
ParameterType
presetstring
Returns ​

void

foveation? ​
ts
readonly optional foveation?: boolean;

Offer the foveation lever. Default true.

inForceWords? ​
ts
readonly optional inForceWords?: () => string;

What the characters' runtimes report as IN FORCE (read from their own stats) where it differs from what the levers asked: plain words for the card ("running speed-up off, asked 1"), or '' when all is as asked.

Returns ​

string

onPanel? ​
ts
readonly optional onPanel?: readonly LeverId[];

Which levers go on the in-headset panel (every lever stays on the flat page). Default: all of them.

pointShare? ​
ts
readonly optional pointShare?: (share) => void;

Draw this share of the characters' points (1, 0.25, 0.0625). Absent: no points lever.

Parameters ​
ParameterType
sharenumber
Returns ​

void

poseBudget? ​
ts
readonly optional poseBudget?: (points) => void;

The scene's shared budget of points re-posed a frame (setPoseBudgetPoints of the character host; 0 = off). Shown as "Shared budget". Absent: no such lever.

Parameters ​
ParameterType
pointsnumber
Returns ​

void

poseRate? ​
ts
readonly optional poseRate?: (hz) => void;

How often every character's movement is re-posed: poses a second (0 = every frame), through runtime.setSkinOptions({ poseHz }) on each character. Shown as "Movement updates". Absent: no such lever.

Parameters ​
ParameterType
hznumber
Returns ​

void

posesPerSecond? ​
ts
readonly optional posesPerSecond?: () => number | null;

The real posing rate (the slowest character's stats.posesPerSecond), for the card; null when unknown.

Returns ​

number | null

skin? ​
ts
readonly optional skin?: object;

How the fallback skin spends the chip's time: the presets' names (SKIN_PRESET_NAMES of gameable/character, the first today's way) and how to apply one (runtime.setSkinOptions(SKIN_PRESETS[name])). Absent: no skin lever.

check? ​
ts
readonly optional check?: (preset) => boolean;

After a switch settled: is the character drawn with it (the pass ran, the options in force)? Default yes.

Parameters ​
ParameterType
presetstring
Returns ​

boolean

presets ​
ts
readonly presets: readonly string[];
set ​
ts
readonly set: (preset) => void;
Parameters ​
ParameterType
presetstring
Returns ​

void

smallerBufferWaiting? ​
ts
readonly optional smallerBufferWaiting?: () => boolean;

Is the smaller buffer (a fill preset's drawScale under 1) waiting rather than drawn, read from the runtimes' own stats.lowRes? It steps aside while both eyes go in one pass. Default: guessed from the eyes lever.

Returns ​

boolean

sortMode? ​
ts
readonly optional sortMode?: (mode) => void;

When the characters' draw order is remade. Absent: no sorting lever.

Parameters ​
ParameterType
mode"turn" | "frozen" | "pose" | "slow"
Returns ​

void


XrLevers ​

The levers, made once per module.

Methods ​

busy() ​
ts
busy(): boolean;

True while a lever's move is under way (a level loading, a view restarting): the safety net waits for it.

Returns ​

boolean

eachFrame() ​
ts
eachFrame(pose): void;

Each presenting frame, with the viewer pose (kept for the module's loop; nothing today).

Parameters ​
ParameterType
poseXRViewerPose
Returns ​

void

framebufferScale() ​
ts
framebufferScale(): number;

The framebuffer scale a session should start with.

Returns ​

number

label() ​
ts
label(id): string;

A lever's words on a button now (Detail: 256), whichever way it was moved.

Parameters ​
ParameterType
idLeverId
Returns ​

string

next() ​
ts
next(id): Promise<void>;

Cycle a lever to its next setting.

Parameters ​
ParameterType
idLeverId
Returns ​

Promise<void>

nextEntry() ​
ts
nextEntry(id): boolean;

True while a headset view is on and this lever's choice waits for the next entry (drawing, eyes).

Parameters ​
ParameterType
idLeverId
Returns ​

boolean

pageButtons() ​
ts
pageButtons(changed): HTMLButtonElement[];

The page's pill buttons, one per offered lever; changed runs after each press.

Parameters ​
ParameterType
changed() => void
Returns ​

HTMLButtonElement[]

panelButtons() ​
ts
panelButtons(changed): PanelButton[];

The in-headset panel's buttons, one per offered lever.

Parameters ​
ParameterType
changed() => void
Returns ​

PanelButton[]

sessionEnded() ​
ts
sessionEnded(): void;

A session ended.

Returns ​

void

sessionStarted() ​
ts
sessionStarted(): void;

A session started: apply the foveation and the framebuffer scale in force.

Returns ​

void

set() ​
ts
set(id, setting): Promise<boolean>;

Move a lever by its words; false when the lever or the setting is unknown.

Parameters ​
ParameterType
idLeverId
settingstring
Returns ​

Promise<boolean>

settings() ​
ts
settings(): Record<LeverId, string>;

The settings asked, by lever (an empty string for a lever the page does not offer).

Returns ​

Record<LeverId, string>

settingsInForce() ​
ts
settingsInForce(): Record<LeverId, string>;

The settings IN FORCE: as settings(), except that inside a view the drawing size and the eyes' passes are the ones the view was entered with (a change asked inside waits for the next entry). A speed row carries these.

Returns ​

Record<LeverId, string>

settingsLine() ​
ts
settingsLine(): string;

One line for the card: 512 · drawing 80 % · foveation off · points all.

Returns ​

string


XrModule ​

The module, with its service once the engine has run init.

Extends ​

Properties ​

id ​
ts
readonly id: string;

Unique id. Also the service id when init returns a service.

Inherited from ​

EngineModule.id

order? ​
ts
readonly optional order?: number;

Sort key. Lower runs first; equal keys keep registration order.

Rough convention: input -100, gameplay -50, physics 0, rendering helpers 200. Gameplay runs before physics so that the commands a guest tick emits are simulated by the step that follows rather than the next one.

Inherited from ​

EngineModule.order

service ​
ts
readonly service: XrService | null;

Methods ​

beginFrame()? ​
ts
optional beginFrame(): void;

Run before the frame's fixed steps. Input capture lives here.

Returns ​

void

Inherited from ​

EngineModule.beginFrame

dispose() ​
ts
dispose(): void;

Release everything this module took.

Returns ​

void

Inherited from ​

EngineModule.dispose

endFrame()? ​
ts
optional endFrame(): void;

Run after rendering. Input's end-of-frame bookkeeping lives here.

Returns ​

void

Inherited from ​

EngineModule.endFrame

fixedUpdate()? ​
ts
optional fixedUpdate(dt): void;

Run once per fixed step.

Parameters ​
ParameterTypeDescription
dtnumberAlways ctx.config.fixedDt, whatever time.timeScale is; time scale changes how many steps a frame runs, not their length.
Returns ​

void

Inherited from ​

EngineModule.fixedUpdate

init() ​
ts
init(ctx): void | object | Promise<void | object>;

Acquire resources.

Parameters ​
ParameterTypeDescription
ctxHostContextThe host surface.
Returns ​

void | object | Promise<void | object>

Nothing, or the service to publish under id.

Inherited from ​

EngineModule.init

update()? ​
ts
optional update(dt, alpha): void;

Run once per frame, after the fixed steps and before rendering.

Parameters ​
ParameterTypeDescription
dtnumberClamped wall-clock seconds since the previous frame, scaled by time.timeScale.
alphanumberInterpolation factor in [0, 1) for smoothing transforms.
Returns ​

void

Inherited from ​

EngineModule.update


XrNarrow ​

The tunnel.

Properties ​

object ​
ts
readonly object: Object3D;

The object; a child of the camera.

open ​
ts
readonly open: number;

How open it is now, 0 to 1 (1: out of sight).

Methods ​

dispose() ​
ts
dispose(): void;
Returns ​

void

set() ​
ts
set(open): void;

Set how open it is: at 1 it hides; below, the hole's half-angle shrinks towards the narrowed view.

Parameters ​
ParameterType
opennumber
Returns ​

void


XrNavigatorLike ​

The parts of navigator and the global object the detection reads.

Properties ​

maxTouchPoints? ​
ts
readonly optional maxTouchPoints?: number;
userAgent? ​
ts
readonly optional userAgent?: string;
xr? ​
ts
readonly optional xr?: object;
isSessionSupported() ​
ts
isSessionSupported(mode): Promise<boolean>;
Parameters ​
ParameterType
modestring
Returns ​

Promise<boolean>


XrOptions ​

What a game sets up: which views it offers, where its content stands, what to show in a room.

Properties ​

arShow? ​
ts
readonly optional arShow?: "all" | "characters" | (() => "all" | "characters");

What stays visible in a phone's camera view: 'characters' (the default) keeps the studio characters (the objects the character bridge flags, or any object with userData.xrKeep = true) and the lights, and hides the rest (the place, floors, walls, the sky); 'all' keeps everything. A function is asked when the view starts (a game whose selector wants the heroes alone and whose rooms want everything).

back? ​
ts
readonly optional back?: object;

A way back on the flat page (the start page of the tests): a pill link, shown when no view is on.

href ​
ts
readonly href: string;
words? ​
ts
readonly optional words?: string;
beforeEnter? ​
ts
readonly optional beforeEnter?: (mode) => Promise<void>;

Run before a view is asked for, on the same press (a talk page asks for the microphone here).

Parameters ​
ParameterType
modeXrMode
Returns ​

Promise<void>

busy? ​
ts
readonly optional busy?: () => boolean;

The page says it is loading (a level or a place on its way, the first character not yet standing): the safety net waits while this answers true. Loading is not a stall (a phone's room view was left at 7:56 pm on 6 Oct 2026 while its room was loading: "it was drawing 0 frames a second").

Returns ​

boolean

buttons? ​
ts
readonly optional buttons?: 
  | false
  | {
  container?: HTMLElement;
  words?: XrWords;
};

The buttons: false for none (the page makes its own and calls enter), or where they go (a container: the buttons flow inside it with the page's own; none: a row floating along the bottom) and their words.

caption? ​
ts
readonly optional caption?: () => readonly string[] | null;

Lines shown on a card by the content in a headset (the talk state, the words heard), or null for none.

Returns ​

readonly string[] | null

comfort? ​
ts
readonly optional comfort?: ComfortOptions;

Comfort in a headset: turns smoothly (default) or in steps; a following view's catch-up as a glide (default) or a snap; the view narrowed while it moves (default on). Every default is the safe one.

content? ​
ts
readonly optional content?: () => ContentFrame;

Where the content stands and faces in the scene, read when a view starts and when it is placed. Defaults to the origin facing +z.

Returns ​

ContentFrame

controls? ​
ts
readonly optional controls?: boolean;

Controllers onto the game's input. Default true.

desk? ​
ts
readonly optional desk?: string;

An address on the desk that answers at once (/desk-alive): asked every ten seconds while the flat page shows; when it does not answer the flat page says "The desk is not answering; try again in a moment." and the log (which queues) notes how long it was silent.

follow? ​
ts
readonly optional follow?: XrFollow;

Where the hero stands each frame, for the following views; and how the stick turns it.

framebufferScale? ​
ts
readonly optional framebufferScale?: number;

The headset drawing size factor; by device when omitted (framebufferScaleFor).

levers? ​
ts
readonly optional levers?: false | XrLeverHooks;

The test levers, each a button on the page and on the in-headset panel, the setting in force on the card. The drawing-size and foveation levers need nothing from the page; the detail and points levers need the page's hooks. Nothing moves by itself: the defaults stay as they are until a person presses.

leversRemember? ​
ts
readonly optional leversRemember?: string;

Remember the levers for the tab under this key.

leversStart? ​
ts
readonly optional leversStart?: Partial<Record<LeverId, string>>;

Where the levers start (a headset test order: the lightest first); the defaults when omitted.

multiview? ​
ts
readonly optional multiview?: boolean;

Ask for both eyes in one pass (multiview) when a headset view starts: the renderer's own setting (createEngine({ renderer: { multiview } })) decides unless this says otherwise; the eyes lever and setMultiview change it for the next entry. renderer.xr.useMultiview() says what the browser gave.

numbers? ​
ts
readonly optional numbers?: XrNumbers | (() => XrNumbers);

The distances behind the fixed views, and how far behind the shoulder the over-the-shoulder view stands. A function is asked whenever a fixed view is applied (a selector's hall at a tenth of its size, a game's rooms at a twentieth).

obstacles? ​
ts
readonly optional obstacles?: () => readonly Vec3[];

World points the person cannot walk into (the characters' feet). Defaults to the content's anchor.

Returns ​

readonly Vec3[]

overlay? ​
ts
readonly optional overlay?: HTMLElement;

The page element shown over a phone's camera view (dom-overlay): the module's buttons and readout are put in it. Defaults to a full-screen layer of the module's own. A page that wants its own panels seen in the room passes document.body and makes its backgrounds see-through under html.xr-ar.

panel? ​
ts
readonly optional panel?: 
  | readonly PanelButton[]
  | (() => readonly PanelButton[]);

The page's own controls for the in-headset panel (a headset shows no page): Start talking, the microphone. Each mirrors a page button's words and presses it. The module adds the size switch and Leave itself. A function is asked when the view starts and on every refreshPanel() (a game whose controls change with its phase: choosing a hero, then playing).

panelSize? ​
ts
readonly optional panelSize?: boolean;

The view buttons (one per other view the game lists) on the in-headset panel. Default true; a talk page may keep them on the flat page.

readout? ​
ts
readonly optional readout?: boolean;

The test readout (frames a second, points, eyes). Default false.

speed? ​
ts
readonly optional speed?: number;

Walking speed, metres a second. Default 1.5.

speedTest? ​
ts
readonly optional speedTest?: object;

The one-button speed test: steps run inside the headset, a few seconds each, logged, a table after. The steps may be a function, asked when the test starts (the steps of the phase the game is in).

describe? ​
ts
readonly optional describe?: () => Record<string, unknown>;

What the page knows about its characters' textures and buffers, for the log.

Returns ​

Record<string, unknown>

inForce? ​
ts
readonly optional inForce?: () => unknown;

What the characters' runtimes run right now (skin kind, passes, sort mode, share), read from their stats.

Returns ​

unknown

reset? ​
ts
readonly optional reset?: () => void | Promise<void>;

Put the page back as it was before the test.

Returns ​

void | Promise<void>

steps ​
ts
readonly steps: 
  | readonly SpeedStep[]
  | (() => readonly SpeedStep[]);
summary? ​
ts
readonly optional summary?: (results) => string;

One plain line under the table once the test is done (a page names its three biggest costs from the results, in words a person reads: "Biggest costs: the shadow pass, the rain, the lights"). Nothing when omitted.

Parameters ​
ParameterType
resultsreadonly SpeedResult[]
Returns ​

string

support? ​
ts
readonly optional support?: XrSupport;

What the browser answered already (the page asked to pick the backend); asked again when omitted.

telemetry? ​
ts
readonly optional telemetry?: XrTelemetryOptions;

The headset-to-desk log: every few seconds the page sends the session's state, the frame budget, the drawing size, the inputs and every event to this address (POST, JSON lines). Off when omitted.

views? ​
ts
readonly optional views?: object;

The views offered in each mode, the first the default; the in-headset panel and the page's view button offer these and no others. Defaults to life-size then tabletop in both. A following view (overShoulder, throughEyes) needs follow.

ar? ​
ts
readonly optional ar?: readonly XrView[];
vr? ​
ts
readonly optional vr?: readonly XrView[];
walk? ​
ts
readonly optional walk?: "keys" | "rig" | "none" | (() => "keys" | "rig" | "none");

Walking in a headset's fixed views: 'rig' (the default) moves the person with the left stick along the way they face, at speed metres a second, never through the content (obstacles, 0.5 m short); on a table view the stick moves the table instead. 'keys' presses W A S D for a game that walks itself; 'none' neither. In the following views the left stick is always the game's W A S D (the hero walks, the view follows). A function is asked each frame (a game whose hall walks the person and whose rooms walk the hero).

warning? ​
ts
readonly optional warning?: () => string;

Plain words for a fault the page can see in its characters' own stats ("drawing order broken: Elara"), or '' when all is well; read every frame, shown as a red line on the card and the page's readout.

Returns ​

string

watchdog? ​
ts
readonly optional watchdog?: 
  | false
  | {
  forSeconds?: number;
  minFps?: number;
  stallSeconds?: number;
};

The safety net: leave the headset view by itself when it draws fewer than minFps frames a second for forSeconds (default 15 for 6), or when no frame comes for stallSeconds (default 5). Measured on timers, not on drawn frames, so it works when the graphics card is starved. false turns it off.


XrPanel ​

The panel.

Properties ​

group ​
ts
readonly group: Group;

The panel's object; a child of the rig.

open ​
ts
readonly open: boolean;

True while the full two columns show; false while it rests as the handle.

Methods ​

angles() ​
ts
angles(headPosition, headQuaternion): PanelAngles[];

Each button's angles from a head pose (real metres in the rig), for the proof.

Parameters ​
ParameterType
headPositionVector3
headQuaternionQuaternion
Returns ​

PanelAngles[]

dispose() ​
ts
dispose(): void;
Returns ​

void

follow() ​
ts
follow(
   headPosition, 
   headQuaternion, 
   dt
): void;

Each frame: ease towards the place beside the person when they walked or turned away from it.

Parameters ​
ParameterType
headPositionVector3
headQuaternionQuaternion
dtnumber
Returns ​

void

hitIndex() ​
ts
hitIndex(ray): number;

The button a ray hits right now, or -1.

Parameters ​
ParameterType
rayMatrix4
Returns ​

number

layout() ​
ts
layout(): object[];

Each button's words and real position (in the rig), for tests.

Returns ​

object[]

pages() ​
ts
pages(): object;

The pages (eight buttons each; more than eight and the last slot flips the page) and the one shown.

Returns ​

object

count ​
ts
count: number;
shown ​
ts
shown: number;
placeInFront() ​
ts
placeInFront(headPosition, headQuaternion): void;

Put the panel beside the right hand of a head pose (real metres, in the rig), at once.

Parameters ​
ParameterType
headPositionVector3
headQuaternionQuaternion
Returns ​

void

press() ​
ts
press(ray): string | null;

Press the button a ray hovers; the label pressed, or null.

Parameters ​
ParameterType
raynumber
Returns ​

string | null

pressAt() ​
ts
pressAt(ray): string | null;

Press the button a ray (a world matrix, its -z the pointing direction) hits right now; the label, or null.

Parameters ​
ParameterType
rayMatrix4
Returns ​

string | null

refresh() ​
ts
refresh(): void;

Redraw the words.

Returns ​

void

setButtons() ​
ts
setButtons(buttons): void;

Replace the buttons.

Parameters ​
ParameterType
buttonsreadonly PanelButton[]
Returns ​

void

setOpen() ​
ts
setOpen(
   open, 
   holdMs?, 
   why?
): void;

Open the full panel (held open for holdMs whatever the ray does) or rest it as the handle; why goes to onOpenChange.

Parameters ​
ParameterType
openboolean
holdMs?number
why?string
Returns ​

void

showPage() ​
ts
showPage(index): void;

Show a page (0-based).

Parameters ​
ParameterType
indexnumber
Returns ​

void

tick() ​
ts
tick(nowMs, holdOpen): void;

Each frame: close after the ray has left for PANEL_HANDLE.closeAfterMs unless holdOpen.

Parameters ​
ParameterType
nowMsnumber
holdOpenboolean
Returns ​

void

toggle() ​
ts
toggle(): void;

A / X: open when resting, rest when open.

Returns ​

void

update() ​
ts
update(rays): void;

Hover from the controllers' rays (world matrices of their target rays).

Parameters ​
ParameterType
raysreadonly Matrix4[]
Returns ​

void


XrReadout ​

The readout: its words, its page panel and its in-headset card.

Properties ​

card ​
ts
readonly card: Object3D;

The card for a headset: a small plane to hang on the camera.

element ​
ts
readonly element: HTMLElement | null;

The page panel, or null without a document.

stats ​
ts
readonly stats: XrStats;

The stats it shows, updated by sample.

Methods ​

dispose() ​
ts
dispose(): void;

Remove the panel, free the card's texture.

Returns ​

void

sample() ​
ts
sample(
   nowMs, 
   scene, 
   eyes
): void;

Count one frame at nowMs; redraw the words every quarter second.

Parameters ​
ParameterType
nowMsnumber
sceneScene
eyesnumber
Returns ​

void

setCompact() ​
ts
setCompact(value): void;

A phone's small screen: the page panel is one short line in the top corner ("48 frames a second"); a tap opens the whole of it, a second tap folds it again.

Parameters ​
ParameterType
valueboolean
Returns ​

void

setSettings() ​
ts
setSettings(text): void;

A third line: the settings in force (the levers), plain words.

Parameters ​
ParameterType
textstring
Returns ​

void

setVisible() ​
ts
setVisible(value): void;

Show or hide both the panel and the card.

Parameters ​
ParameterType
valueboolean
Returns ​

void

setWarning() ​
ts
setWarning(text): void;

A red line for a fault the page sees in its characters ("drawing order broken: Elara"); '' clears it.

Parameters ​
ParameterType
textstring
Returns ​

void


XrService ​

What the module publishes as the xr service.

Properties ​

available ​
ts
readonly available: object;

Which views can actually be entered on this engine's backend.

ar ​
ts
readonly ar: boolean;
vr ​
ts
readonly vr: boolean;
leftByItself ​
ts
readonly leftByItself: string | null;

Why the safety net left the view by itself (plain words), or null.

mode ​
ts
readonly mode: XrMode | null;

The mode showing, or null.

multiview ​
ts
readonly multiview: object;

Whether both eyes are drawn in one pass now (false outside a headset view), and whether that was asked for.

inForce ​
ts
readonly inForce: boolean;
wanted ​
ts
readonly wanted: boolean;
narrowing ​
ts
readonly narrowing: "strong" | "light" | "off";

The comfort tunnel's strength: 'off' never narrows; 'light' (the default) narrows to a wide hole; 'strong' to the usual 30-degree tunnel. Through the eyes it narrows only while the left stick walks the hero or the right stick turns him (the person's own head is the camera: a hit or a wall push must not narrow it); in the other following views while the view glides.

noteWords ​
ts
readonly noteWords: string;

The note's words now ('' for none).

panelOpen ​
ts
readonly panelOpen: boolean;

True while the full panel shows; false while it rests as the "Menu" handle.

placed ​
ts
readonly placed: boolean;

True once the content has been placed in a phone's camera view.

presenting ​
ts
readonly presenting: boolean;

True while a headset or camera view is showing.

rig ​
ts
readonly rig: Group;

The camera's parent while presenting: real metres into the scene.

speedResults ​
ts
readonly speedResults: readonly SpeedResult[];

The last speed test's rows.

stats ​
ts
readonly stats: XrStats;

The readout's numbers, live whether the readout is shown or not.

support ​
ts
readonly support: XrSupport | null;

What the browser answered; null until init has asked.

tunnelOpen ​
ts
readonly tunnelOpen: number;

How open the comfort tunnel is: 1 fully open (the view still), 0 at its narrowest (a glide or a smooth turn).

turnMode ​
ts
readonly turnMode: "steps" | "smooth";

How the right stick turns now: smoothly while held (the default), or in 30-degree steps (the Turning lever).

view ​
ts
readonly view: XrView;

The view showing or last chosen.

Methods ​

enter() ​
ts
enter(mode, view?): Promise<void>;

Enter a view. Needs a user gesture (a button press). Refused with a plain-words error when the device cannot.

Parameters ​
ParameterType
modeXrMode
view?XrView
Returns ​

Promise<void>

exit() ​
ts
exit(): Promise<void>;

Leave the view; resolves when the page is back as it was.

Returns ​

Promise<void>

levers() ​
ts
levers(): Record<LeverId, string>;

The test levers' settings in force, by lever.

Returns ​

Record<LeverId, string>

log() ​
ts
log(kind, data?): void;

Put a line in the headset-to-desk log (nothing without telemetry).

Parameters ​
ParameterType
kindstring
data?Record<string, unknown>
Returns ​

void

mark() ​
ts
mark(point): void;

Mark a floor spot with a ring (scene units: where a character is walking to), or clear the mark with null. Set by the module itself when a floor listener takes a tap; a page clears it when the character has arrived.

Parameters ​
ParameterType
pointVec3 | null
Returns ​

void

nextView() ​
ts
nextView(): XrView | null;

The view after the one showing, round the offered list; the view switch's target.

Returns ​

XrView | null

note() ​
ts
note(words): void;

Plain words on the card's settings line and the flat page ("Loading 256…"); null or '' clears them.

Parameters ​
ParameterType
wordsstring | null
Returns ​

void

offered() ​
ts
offered(): readonly XrView[];

The views the game offers in the mode showing (or in a headset when none is).

Returns ​

readonly XrView[]

on() ​
ts
on<K>(type, listener): () => void;

Listen to an event; returns the function that stops listening.

Type Parameters ​
Type Parameter
K extends keyof XrEvents
Parameters ​
ParameterType
typeK
listener(payload) => void
Returns ​

() => void

panelAngles() ​
ts
panelAngles(): PanelAngles[];

The in-headset panel's buttons with their angles from the head (degrees below, degrees to the side).

Returns ​

PanelAngles[]

panelLayout() ​
ts
panelLayout(): object[];

The in-headset panel's buttons and their real positions (metres in the rig), for tests; empty off a headset.

Returns ​

object[]

place() ​
ts
place(): boolean;

On a phone: put the content at the pointed spot now. True when there was a spot.

Returns ​

boolean

refreshPanel() ​
ts
refreshPanel(): void;

Rebuild the in-headset panel's buttons (the page's panel is asked again); nothing off a headset.

Returns ​

void

runSpeedTest() ​
ts
runSpeedTest(): Promise<SpeedResult[]>;

Run the speed test (enters the view first when needed). Resolves with the rows when done.

Returns ​

Promise<SpeedResult[]>

setDistance() ​
ts
setDistance(metres): void;

Stand this far from the content in a life-size headset view, metres.

Parameters ​
ParameterType
metresnumber
Returns ​

void

setLever() ​
ts
setLever(id, setting): Promise<boolean>;

Move a lever to a setting by its words ('256', '80 %', 'medium', 'a quarter'); false when unknown.

Parameters ​
ParameterType
idLeverId
settingstring
Returns ​

Promise<boolean>

setMultiview() ​
ts
setMultiview(onePass): void;

Ask for both eyes in one pass (multiview) at the next entry; what the browser gives is renderer.xr.useMultiview().

Parameters ​
ParameterType
onePassboolean
Returns ​

void

setNarrowing() ​
ts
setNarrowing(level): void;
Parameters ​
ParameterType
level"strong" | "light" | "off"
Returns ​

void

setView() ​
ts
setView(view): void;

Change the view while presenting (one the game offers in this mode), or the default for the next entry.

Parameters ​
ParameterType
viewXrView
Returns ​

void

tap() ​
ts
tap(): boolean;

On a phone: a tap on the spot the ring shows, as a finger's tap on the screen is taken (floor.ts): before placing it places; after placing, a game listening for floor sends its character there; else it places again. The emulator rig's door (no finger in emulation). True when something was done.

Returns ​

boolean

teleportTo() ​
ts
teleportTo(point): boolean;

Move the viewer's feet to a validated scene-floor point in a life-size headset view.

Parameters ​
ParameterType
pointVec3
Returns ​

boolean

Example ​
ts
service.teleportTo([0, 0, -2]);
togglePanel() ​
ts
togglePanel(): void;

Open the in-headset panel from its handle, or rest it as the handle (the A / X button does the same).

Returns ​

void


XrStats ​

What the readout shows.

Properties ​

cpuMs ​
ts
cpuMs: number;

The frame's processor time, milliseconds, smoothed.

eyes ​
ts
eyes: number;

How many eyes the frame is drawn for: 2 in a headset, 1 otherwise.

fps ​
ts
fps: number;

Frames a second over the last second.

gpuMs ​
ts
gpuMs: number | null;

The graphics card's time for the frame's drawing, milliseconds, when its timer exists; else null.

points ​
ts
points: number;

Points of every splat in view (loaded points of the visible splats).

rate ​
ts
rate: number | null;

The headset's refresh rate target, frames a second, or null off a headset.


XrSupport ​

What the browser answered about headsets and camera views.

Properties ​

ar ​
ts
readonly ar: boolean;

A phone's (or headset's) camera view (immersive-ar) can be asked for.

device ​
ts
readonly device: DeviceKind;

What the browser says it runs on.

gpuInXr ​
ts
readonly gpuInXr: boolean;

The browser can show a WebXR session from a WebGPU device (XRGPUBinding); rare today.

vr ​
ts
readonly vr: boolean;

A headset view (immersive-vr) can be asked for.


XrTelemetry ​

The log.

Methods ​

event() ​
ts
event(kind, data): void;

Note an event now; it goes with the next snapshot (or the flush on leaving).

Parameters ​
ParameterType
kindstring
dataRecord<string, unknown>
Returns ​

void

flush() ​
ts
flush(): void;

Send what is pending now.

Returns ​

void

frame() ​
ts
frame(headPos, headQuat): void;

Each presenting frame: the head and the frame times, folded into the next snapshot.

Parameters ​
ParameterType
headPosVector3
headQuatQuaternion
Returns ​

void

start() ​
ts
start(): void;

Begin: the error hooks, the timer.

Returns ​

void

stop() ​
ts
stop(): void;

End: the hooks off, the last lines sent.

Returns ​

void

watchSession() ​
ts
watchSession(session, mode): void;

Watch a session's own events (input sources arriving, visibility, end).

Parameters ​
ParameterType
sessionXRSession
modestring
Returns ​

void


XrTelemetryOptions ​

Where and how often the log goes.

Properties ​

everySeconds? ​
ts
readonly optional everySeconds?: number;

Seconds between snapshots. Default 3.

extra? ​
ts
readonly optional extra?: () => Record<string, unknown>;

Anything else the page wants in each snapshot (the characters' sort stats).

Returns ​

Record<string, unknown>

page? ​
ts
readonly optional page?: string;

A word for the page (hall, visit), in every line.

url ​
ts
readonly url: string;

The address, on the page's own origin (/xr-log).


XrTelemetrySources ​

What the module gives the log to read.

Properties ​

engine ​
ts
engine: () => object;
Returns ​

object

frameTiming ​
ts
frameTiming: 
  | {
  renderMs: number;
  totalMs: number;
  updateMs: number;
}
  | null;
renderer ​
ts
renderer: WebGPURenderer;
session ​
ts
session: () => XRSession | null;
Returns ​

XRSession | null

state ​
ts
state: () => Record<string, unknown>;
Returns ​

Record<string, unknown>


XrWords ​

Words on the buttons; a page may change them (another language), never to a vendor's name.

Properties ​

ar? ​
ts
readonly optional ar?: string;

The phone button. Default "View in your room".

hint? ​
ts
readonly optional hint?: string;

The phone's line while it looks for the floor, before the content is placed. Default "Point at the floor" (short: the old sentence ran off a 440 px wide phone and across the character, 17 Oct 2026).

leave? ​
ts
readonly optional leave?: string;

The button that leaves the view. Default "Leave".

lifeSize? ​
ts
readonly optional lifeSize?: string;
overShoulder? ​
ts
readonly optional overShoulder?: string;
place? ​
ts
readonly optional place?: string;

The button that puts the content at the pointed spot. Default "Place here".

tabletop? ​
ts
readonly optional tabletop?: string;

The views' names, on the view button and the panel. Defaults: "Table size", "Life size", "Over the shoulder", "Through the eyes".

throughEyes? ​
ts
readonly optional throughEyes?: string;
vr? ​
ts
readonly optional vr?: string;

The headset button. Default "Enter VR".

Type Aliases ​

FollowView ​

ts
type FollowView = "overShoulder" | "throughEyes";

The two views that follow the game's hero.


LeverId ​

ts
type LeverId = 
  | "detail"
  | "draw"
  | "foveation"
  | "points"
  | "sort"
  | "extra"
  | "skin"
  | "fill"
  | "eyes"
  | "turn"
  | "pose"
  | "budget"
  | "shape";

The levers.


TapAction ​

ts
type TapAction = "nothing" | "place" | "send";

What a tap does.


Vec3 ​

ts
type Vec3 = readonly [number, number, number];

A point as three numbers.


XrDeviceKind ​

ts
type XrDeviceKind = DeviceKind;

The kind of device, read from the browser's own words about itself.


XrMode ​

ts
type XrMode = "vr" | "ar";

A headset view or a phone's camera view.


XrView ​

ts
type XrView = "lifeSize" | "tabletop" | "overShoulder" | "throughEyes";

The named views a game can offer.

Variables ​

ARRIVED ​

ts
const ARRIVED: 0.02 = 0.02;

The gap at which a glide counts as arrived and the view holds, metres and radians.


CATCH_UP_RATE ​

ts
const CATCH_UP_RATE: 10 = 10;

The glide's approach rate, a second: the gap closes by about two thirds every 0.1 s.


COMFORT_DEFAULTS ​

ts
const COMFORT_DEFAULTS: Required<ComfortOptions>;

The documented defaults of ComfortOptions.


DEAD_ZONE ​

ts
const DEAD_ZONE: 0.35 = 0.35;

How far the followed thing may stray before the view moves, metres.


FLOOR_REACH ​

ts
const FLOOR_REACH: 10 = 10;

How far along a controller's ray the floor may be and still count (metres, the real room).


LEVER_WORDS ​

ts
const LEVER_WORDS: object;

The levers' words.

Type Declaration ​

budget ​
ts
budget: readonly ["off", "120,000 points", "60,000 points"];

The scene's shared budget of points re-posed a frame.

draw ​
ts
draw: readonly ["full", "80 %", "60 %"];
eyes ​
ts
eyes: readonly ["two passes", "one pass"];

Both eyes drawn in two passes (today), or in one where the browser allows it (multiview; takes the next entry). Shown as "Both eyes at once: off / on".

foveation ​
ts
foveation: readonly ["off", "medium", "high"];
points ​
ts
points: readonly ["all", "a quarter", "a sixteenth"];
pose ​
ts
pose: readonly ["every frame", "45 a second", "30 a second"];

How often every character's movement is re-posed (every frame = the drawing rate).

shape ​
ts
shape: readonly ["full", "simple"];

Far or small characters drawn in full, or the simple way (the disc stage under about 220 px on screen).

sort ​
ts
sort: readonly ["live", "when turning", "every 2 s", "frozen"];
turn ​
ts
turn: readonly ["smooth", "steps"];

The right stick turns smoothly (a calm rate, the default) or in 30-degree steps.


PACKAGE ​

ts
const PACKAGE: "@gameable/xr";

Package identity marker for gameable/xr.

Example ​

ts
import { PACKAGE } from 'gameable/xr';

console.log(PACKAGE); // 'gameable/xr'

PANEL_BUTTON ​

ts
const PANEL_BUTTON: object;

A button's size and the grid, real metres.

Type Declaration ​

columns ​
ts
columns: number = 2;
gapX ​
ts
gapX: number = 0.01;
gapY ​
ts
gapY: number = 0.014;
height ​
ts
height: number = 0.066;
rows ​
ts
rows: number = 4;
width ​
ts
width: number = 0.12;

PANEL_FOLLOW ​

ts
const PANEL_FOLLOW: object;

When the person walked this far (metres) or turned this far (degrees) from where the panel was placed, it follows.

Type Declaration ​

degrees ​
ts
degrees: number = 50;
ease ​
ts
ease: number = 6;
metres ​
ts
metres: number = 0.6;

PANEL_HANDLE ​

ts
const PANEL_HANDLE: object;

The handle the panel rests as: its slot (the first column's lowest: about 23 degrees below, 20 to 28 to the side), how long after the ray left the open panel closes, and how long it stays open on entering.

Type Declaration ​

closeAfterMs ​
ts
closeAfterMs: number = 4000;
firstOpenMs ​
ts
firstOpenMs: number = 6000;
slot ​
ts
slot: number;
words ​
ts
words: string = 'Menu';

PANEL_PLACE ​

ts
const PANEL_PLACE: object;

The panel's place from the head: ahead, the first column's left edge to the right, the top row below the eyes. Beside a character standing straight ahead, never over it: the first column starts about 20 degrees to the side (a character at 1.8 m is about 11 degrees each way), the second ends under 38; every row within 25 degrees below.

Type Declaration ​

ahead ​
ts
ahead: number = 0.75;
right ​
ts
right: number = 0.27;
topBelow ​
ts
topBelow: number = 0.095;

SHOULDER_DEFAULTS ​

ts
const SHOULDER_DEFAULTS: Required<ShoulderNumbers>;

The documented defaults of ShoulderNumbers.


SHOWN_WORDS ​

ts
const SHOWN_WORDS: Record<string, string>;

What the person reads for the skin's presets (the panel, the flat page, the card, the speed table): plain words. The presets' own names stay the ids in the hooks, the log and settings().


SMOOTH_TURN_RATE ​

ts
const SMOOTH_TURN_RATE: number;

A smooth turn's speed while the stick is held, radians a second (60 degrees a second).


SNAP_FLICK ​

ts
const SNAP_FLICK: 0.7 = 0.7;

How far the right stick must go for a step turn, and how far back to re-arm.


SNAP_NARROW_SECONDS ​

ts
const SNAP_NARROW_SECONDS: 0.25 = 0.25;

How long the view stays narrowed after a snap, seconds.


SNAP_REARM ​

ts
const SNAP_REARM: 0.3 = 0.3;

SNAP_STEP ​

ts
const SNAP_STEP: number;

One step of the turn, radians (30 degrees).


STICK_THRESHOLD ​

ts
const STICK_THRESHOLD: 0.5 = 0.5;

The dead zone past which a thumb stick counts as pushed.


TURN_RATE ​

ts
const TURN_RATE: number;

Smooth turning's rate at full tilt, radians a second (90 degrees).


VIEW_DEFAULTS ​

ts
const VIEW_DEFAULTS: Required<ViewNumbers>;

The documented defaults of ViewNumbers.


VIEW_WORDS ​

ts
const VIEW_WORDS: Readonly<Record<XrView, string>>;

The plain words for each view, as the buttons say them.


XR_VIEWS ​

ts
const XR_VIEWS: readonly XrView[];

Every view, in the order the module offers them by default.


XR_WORDS ​

ts
const XR_WORDS: Required<XrWords>;

The documented defaults of XrWords.


YAW_DEAD_ZONE ​

ts
const YAW_DEAD_ZONE: 0.2 = 0.2;

How far the followed thing may turn before the view turns, radians (about 11 degrees).

Functions ​

applyGesture() ​

ts
function applyGesture(
   current, 
   gesture, 
   bounds?
): object;

A pinch and a twist applied to a placed content's size and yaw: the size scales by the fingers' distance ratio within bounds, the yaw turns by the fingers' angle change.

Parameters ​

ParameterTypeDescription
current{ size: number; yaw: number; }The placed size and yaw.
current.sizenumber-
current.yawnumber-
gesture{ ratio: number; turn: number; }The fingers' distance ratio (1 unchanged) and angle change (radians).
gesture.rationumber-
gesture.turnnumber-
bounds{ max: number; min: number; }The smallest and largest size allowed. Defaults to 0.02 to 2.
bounds.maxnumber-
bounds.minnumber-

Returns ​

object

The new size and yaw.

size ​
ts
size: number;
yaw ​
ts
yaw: number;

Example ​

ts
import { applyGesture } from 'gameable/xr';

console.log(applyGesture({ size: 0.1, yaw: 0 }, { ratio: 2, turn: 0.5 })); // { size: 0.2, yaw: 0.5 }

buttonRow() ​

ts
function buttonRow(container?, inline?): HTMLDivElement;

A row of buttons: along the bottom of the page, centred, wrapping whole pills onto more rows when the window is narrow (never a word per line), when it goes on the page itself (or the module's own overlay layer); inside a page's own row of controls (display: contents, its buttons flow with the page's) when a container is given. Each button takes taps, the row itself none.

Parameters ​

ParameterTypeDefault valueDescription
containerHTMLElementdocument.bodyWhere the row goes. Defaults to document.body.
inlinebooleanfalseTrue to flow inside the container instead of floating over the page.

Returns ​

HTMLDivElement

The row element.

Example ​

ts
import { buttonRow, pillButton } from 'gameable/xr';

const row = buttonRow();
row.append(pillButton('Enter VR', true));

canEnter() ​

ts
function canEnter(support, webgpu): object;

Whether a headset view can actually be entered on the backend the engine got.

Parameters ​

ParameterTypeDescription
supportPick<XrSupport, "vr" | "ar" | "gpuInXr">What the browser answered.
webgpubooleanTrue when the engine runs on WebGPU (engine.ctx.caps.webgpu).

Returns ​

object

True when enter('vr') has a way to show the scene.

ar ​
ts
ar: boolean;
vr ​
ts
vr: boolean;

Example ​

ts
import { canEnter } from 'gameable/xr';

console.log(canEnter({ vr: true, ar: false, gpuInXr: false }, false)); // true: WebGL backend, a headset
console.log(canEnter({ vr: true, ar: false, gpuInXr: false }, true)); // false: WebGPU with no binding for it

catchUp() ​

ts
function catchUp(
   state, 
   target, 
   dt, 
   options?
): FollowState;

One frame of catching up: hold while the hero is within the dead zones; glide or snap after it when it is not.

Parameters ​

ParameterTypeDescription
stateFollowStateThe view's state, changed in place.
target{ at: Vec3; facing: number; }Where the hero stands and faces this frame.
target.atVec3-
target.facingnumber-
dtnumberSeconds since the last frame.
optionsComfortOptionsThe comfort options; the defaults where omitted.

Returns ​

FollowState

The same state, for chaining.

Example ​

ts
import { catchUp, createFollowState } from 'gameable/xr';

const state = createFollowState();
catchUp(state, { at: [0, 0, 0], facing: 0 }, 1 / 72); // the first sample: taken as is
catchUp(state, { at: [0.1, 0, 0], facing: 0 }, 1 / 72); // within the dead zone: holds
console.log(state.at[0], state.moving); // 0 false

clickCanvas() ​

ts
function clickCanvas(canvas, down): void;

Press and release the left mouse button on the canvas, at its centre: the controller's trigger as the click the game already reads (the input module's LMB, its Tap).

Parameters ​

ParameterTypeDescription
canvasHTMLElementThe engine's canvas.
downbooleanPress or release.

Returns ​

void

Example ​

ts
import { clickCanvas } from 'gameable/xr';

clickCanvas(canvas, true);
clickCanvas(canvas, false);

countPoints() ​

ts
function countPoints(scene): number;

Count the points of every visible splat in a scene (the engine's fork keeps one entry per gaussian of its capacity; three's own splat has them in its geometry).

Parameters ​

ParameterTypeDescription
sceneSceneThe scene.

Returns ​

number

The sum.

Example ​

ts
import { countPoints } from 'gameable/xr';

console.log(countPoints(engine.scene));

createFollowState() ​

ts
function createFollowState(): FollowState;

A fresh follow state, holding nowhere yet: the first sample is taken as it comes.

Returns ​

FollowState

The state.

Example ​

ts
import { createFollowState } from 'gameable/xr';

const state = createFollowState();
console.log(state.started); // false

createGrabTracker() ​

ts
function createGrabTracker(): object;

Exclusive two-controller ownership and tracked release velocity, allocated once.

Returns ​

Persistent ownership and velocity tracker.

held() ​
ts
held(hand): number;

Body held by this hand, or zero.

Parameters ​
ParameterTypeDescription
handnumberLeft (0) or right (1).
Returns ​

number

Held body id, or zero.

move() ​
ts
move(
   hand, 
   at, 
   dt
): void;

Sample a held pose. Invalid time resets velocity rather than throwing on stale tracking.

Parameters ​
ParameterTypeDescription
handnumberLeft (0) or right (1).
atVec3Current hand position.
dtnumberSeconds since the preceding sample.
Returns ​

void

release() ​
ts
release(hand, out): number;

Release into caller-owned velocity; clamp tracking spikes to a safe speed.

Parameters ​
ParameterTypeDescription
handnumberLeft (0) or right (1).
out[number, number, number]Reusable release velocity destination.
Returns ​

number

Released body id, or zero.

take() ​
ts
take(
   hand, 
   body, 
   at
): boolean;

Acquire an unheld body; either hand can hold a different body.

Parameters ​
ParameterTypeDescription
handnumberLeft (0) or right (1).
bodynumberNonzero physics body id.
atVec3Initial hand position.
Returns ​

boolean

Whether ownership was acquired.

Example ​

ts
const hands = createGrabTracker(); hands.take(0, 12, [0, 1, 0]);

createKeyPresser() ​

ts
function createKeyPresser(target): object;

Press and release keys on the page as the stick moves, so the input module sees them as a keyboard.

Parameters ​

ParameterTypeDescription
targetEventTargetWhere key events go (the window: the input module listens there).

Returns ​

object

An object that applies a stick reading and releases everything.

apply() ​
ts
apply(keys): void;
Parameters ​
ParameterType
keysStickKeys
Returns ​

void

releaseAll() ​
ts
releaseAll(): void;
Returns ​

void

Example ​

ts
import { createKeyPresser } from 'gameable/xr';

const keys = createKeyPresser(window);
keys.apply({ KeyW: true, KeyS: false, KeyA: false, KeyD: false });
keys.releaseAll();

createLevers() ​

ts
function createLevers(options): XrLevers;

Make the levers.

Parameters ​

ParameterTypeDescription
options{ hooks: XrLeverHooks; initial?: Partial<Record<LeverId, string>>; log: (kind, data) => void; multiview?: (onePass) => void; pointsDrawn?: () => number; rememberKey?: string; renderer: WebGPURenderer; turnMode?: (mode) => void; }The page's hooks, the renderer, and where to log a change.
options.hooksXrLeverHooks-
options.initial?Partial<Record<LeverId, string>>Where the levers start (a test order on a headset: the lightest first); the defaults when omitted.
options.log(kind, data) => void-
options.multiview?(onePass) => voidThe eyes lever: ask for both eyes in one pass at the next entry (the module's own hook). Absent: no eyes lever.
options.pointsDrawn?() => numberPoints drawn now (the module counts them), for the skin guard: a switch that leaves none falls back.
options.rememberKey?stringRemember the person's choices for the tab under this key (sessionStorage); they win over initial.
options.rendererWebGPURenderer-
options.turnMode?(mode) => voidThe turning lever: how the right stick turns (the module's own hook). Absent: no turning lever.

Returns ​

XrLevers

The levers.

Example ​

ts
import { createLevers } from 'gameable/xr';

const levers = createLevers({ hooks: {}, renderer, log: () => undefined });
console.log(levers.settings().draw); // 'full'

createNarrow() ​

ts
function createNarrow(): XrNarrow;

Make the tunnel.

Returns ​

XrNarrow

The tunnel, open (hidden); add its object to the camera.

Example ​

ts
import { createNarrow } from 'gameable/xr';

const narrow = createNarrow();
camera.add(narrow.object);
narrow.set(0); // the moving view, at its narrowest

createPanel() ​

ts
function createPanel(options?): XrPanel;

Make the panel.

Parameters ​

ParameterType
options{ onOpenChange?: (open, why) => void; }
options.onOpenChange?(open, why) => void

Returns ​

XrPanel

The panel, empty; add it to the rig and give it buttons.

Example ​

ts
import { createPanel } from 'gameable/xr';

const panel = createPanel();
panel.setButtons([{ id: 'leave', label: () => 'Leave', onPress: () => void xr.exit() }]);

createReadout() ​

ts
function createReadout(container?): XrReadout;

Make the readout.

Parameters ​

ParameterTypeDescription
container?HTMLElement | nullWhere the page panel goes. Defaults to document.body.

Returns ​

XrReadout

The readout.

Example ​

ts
import { createReadout } from 'gameable/xr';

const readout = createReadout();
engine.camera.add(readout.card);

createRoomControls() ​

ts
function createRoomControls(options): object;

Arc preview, release-to-teleport, exclusive hand grips and throw velocity. Call update once per rendered frame, and cancel on restart/session loss.

Parameters ​

ParameterTypeDescription
optionsRoomControlsOptionsHost context, XR service and authored physics hooks.

Returns ​

Room input lifecycle and desktop input adapter.

cancel ​
ts
cancel: () => void;

Drop active props and hide the teleport preview.

Returns ​

void

desktop() ​
ts
desktop(
   down, 
   at, 
   aim, 
   dt
): void;

A desktop or touch hold uses the same ownership and throw path.

Parameters ​
ParameterTypeDescription
downbooleanWhether grab is held.
atVec3Hand anchor on the view ray.
aimVec3View ray direction.
dtnumberSeconds since the preceding sample.
Returns ​

void

dispose() ​
ts
dispose(): void;
Returns ​

void

update() ​
ts
update(dt): void;
Parameters ​
ParameterType
dtnumber
Returns ​

void

Example ​

ts
controls.update(1 / 72); controls.dispose();

createTelemetry() ​

ts
function createTelemetry(options, sources): XrTelemetry;

Make the log.

Parameters ​

ParameterTypeDescription
optionsXrTelemetryOptionsWhere and how often.
sourcesXrTelemetrySourcesWhat to read.

Returns ​

XrTelemetry

The log, not started.

Example ​

ts
import { createTelemetry } from 'gameable/xr';

const log = createTelemetry({ url: '/xr-log', page: 'hall' }, sources);
log.start();
log.event('note', { words: 'hello' });

detectXrSupport() ​

ts
function detectXrSupport(nav?, globalObject?): Promise<XrSupport>;

Ask the browser once what it can do: a headset view, a camera view, WebGPU inside them.

Never throws: a browser without WebXR, or one that refuses the question (an insecure page), answers no.

Parameters ​

ParameterTypeDefault valueDescription
navXrNavigatorLikenavigatorThe navigator, for tests; the page's by default.
globalObjectobjectglobalThisThe global object, for tests; globalThis by default.

Returns ​

Promise<XrSupport>

The support flags.

Example ​

ts
import { detectXrSupport } from 'gameable/xr';

const support = await detectXrSupport();
console.log(support.vr, support.ar); // true on a headset's browser, false on a laptop without one

deviceKind() ​

ts
function deviceKind(userAgent, maxTouchPoints?): DeviceKind;

Which kind of device a browser string names: a headset (the Quest browser, Pico, Apple's headset), a phone or tablet (Android, iPhone, iPad), or a computer.

Parameters ​

ParameterTypeDefault valueDescription
userAgentstringundefinedThe browser's navigator.userAgent.
maxTouchPointsnumber0navigator.maxTouchPoints, which tells an iPad apart from a Mac.

Returns ​

DeviceKind

The device kind.

Example ​

ts
import { deviceKind } from 'gameable/xr';

console.log(deviceKind('Mozilla/5.0 (X11; Linux x86_64; Quest 3) ... OculusBrowser/35.0')); // 'headset'

fingers() ​

ts
function fingers(a, b): object;

Two fingers' distance and angle, for applyGesture: compare one frame's reading with the last.

Parameters ​

ParameterTypeDescription
areadonly [number, number]The first finger, page pixels.
breadonly [number, number]The second finger, page pixels.

Returns ​

object

The distance between them and the angle of the line between them.

angle ​
ts
angle: number;
distance ​
ts
distance: number;

Example ​

ts
import { fingers } from 'gameable/xr';

console.log(fingers([0, 0], [3, 4])); // { distance: 5, angle: Math.atan2(4, 3) }

followRig() ​

ts
function followRig(
   floor, 
   facing, 
   head, 
   offset?
): RigPose;

The rig for a view that follows a thing in the scene (a hero seen over the shoulder, or through their eyes): the followed FLOOR point, moved by the offset, lands under the viewer's own head, and the followed facing becomes the room's forward. The real floor stays the floor: a viewer 1.6 m tall sees from 1.6 m above the hero's floor, whatever the hero's height, so nothing moves the view up or down that the head did not.

Parameters ​

ParameterTypeDescription
floorVec3The followed floor point in the scene (under the hero).
facingnumberThe view's yaw in the scene, radians, so that way is the room's forward.
headVec3The head's real position this frame, metres from the real origin (only its x and z are used).
offsetVec3Where the viewer stands from the followed point, in the view's own frame: metres to the right, up and back ([0.45, 0, 1.6] stands behind a shoulder, the same point shoulderPoint gives; [0, 0, -0.25] a hand's width ahead). It turns with facing, so a turn of the view carries the viewer round the followed thing; its up raises the floor the viewer stands on.

Returns ​

RigPose

The rig's position, yaw and scale (always life-size).

Example ​

ts
import { followRig } from 'gameable/xr';

const rig = followRig([3, 0, -4], Math.PI, [0.2, 1.6, 0.1]);
console.log(rig.scale); // 1

framebufferScaleFor() ​

ts
function framebufferScaleFor(userAgent): number;

How much of the headset's full drawing size to draw at: a Quest 2 (the weakest device we test on) gets a smaller picture upscaled by the device, everything else the full size.

Parameters ​

ParameterTypeDescription
userAgentstringThe browser's navigator.userAgent.

Returns ​

number

The scale factor for the headset's drawing buffer, 0 to 1.

Example ​

ts
import { framebufferScaleFor } from 'gameable/xr';

console.log(framebufferScaleFor('... Quest 2 ... OculusBrowser/35.0')); // 0.85
console.log(framebufferScaleFor('... Quest 3 ... OculusBrowser/35.0')); // 1

headsetTarget() ​

ts
function headsetTarget(view, numbers?): RealTarget;

Where a fixed view puts the content in a headset: ahead of a viewer standing at the real origin, facing them.

Parameters ​

ParameterTypeDescription
view"tabletop" | "lifeSize"lifeSize or tabletop.
numbersViewNumbersThe distances; the defaults where omitted.

Returns ​

RealTarget

The real target for rigFor.

Example ​

ts
import { headsetTarget } from 'gameable/xr';

console.log(headsetTarget('tabletop')); // { point: [0, 0.75, -2.2], yaw: 0, size: 0.1 }

lightFactorsFor() ​

ts
function lightFactorsFor(rigScale): object;

The factors for a rig scale: the intensity's and the reach's.

Parameters ​

ParameterTypeDescription
rigScalenumberThe rig's scale (the camera's parent): 1 life-size, 20 at a twentieth.

Returns ​

object

intensity to multiply a point light's intensity by, distance its reach; 1 and 1 at life size.

distance ​
ts
distance: number;
intensity ​
ts
intensity: number;

Example ​

ts
import { lightFactorsFor } from 'gameable/xr';

console.log(lightFactorsFor(20)); // { intensity: 0.0025, distance: 0.05 }

narrowStep() ​

ts
function narrowStep(
   open, 
   moving, 
   dt, 
   narrowed?
): number;

How open the view should be this frame: 1 fully open, less while moving.

Parameters ​

ParameterTypeDefault valueDescription
opennumberundefinedHow open it is now, 0 to 1.
movingbooleanundefinedTrue while the view is catching up or turning smoothly.
dtnumberundefinedSeconds since the last frame.
narrowednumber0How open the narrowed view is (0: the tunnel at its narrowest, about 30 degrees each way). Default 0.

Returns ​

number

How open it should be now.

Example ​

ts
import { narrowStep } from 'gameable/xr';

console.log(narrowStep(1, true, 0.1) < 0.4); // true: well on the way to the narrowed view
console.log(narrowStep(0, false, 0.1) > 0.4); // true: opening again

pickXrBackend() ​

ts
function pickXrBackend(support, preferred?): RendererBackend;

Which renderer backend to boot for a page that offers headsets or camera views.

The WebGL path whenever a headset or camera view is possible and the browser cannot show one from WebGPU (every Quest and Android phone today); otherwise what the page asked for (auto by default). A page that must keep WebGPU (its characters' compute skin, its decoders) passes 'webgpu' and gets no headset button on such a device; the module tells it so through XrService.support.

Parameters ​

ParameterTypeDefault valueDescription
supportPick<XrSupport, "vr" | "ar" | "gpuInXr">undefinedWhat the browser answered (detectXrSupport).
preferredRendererBackend'auto'The backend the page would use without a headset. Defaults to 'auto'.

Returns ​

RendererBackend

The backend for createEngine.

Example ​

ts
import { detectXrSupport, pickXrBackend } from 'gameable/xr';

const support = await detectXrSupport();
const backend = pickXrBackend(support); // 'webgl' on a Quest, 'auto' on a laptop

pillButton() ​

ts
function pillButton(text, primary?): HTMLButtonElement;

Make one pill button.

Parameters ​

ParameterTypeDefault valueDescription
textstringundefinedIts words.
primarybooleanfalseMint (the one action the page wants), or black.

Returns ​

HTMLButtonElement

The button.

Example ​

ts
import { pillButton } from 'gameable/xr';

const button = pillButton('Enter VR', true);
document.body.append(button);

placedTarget() ​

ts
function placedTarget(
   view, 
   hit, 
   viewer, 
   numbers?
): RealTarget;

Where a view puts the content on a phone: at the real point the person tapped, facing the phone.

Parameters ​

ParameterTypeDescription
view"tabletop" | "lifeSize"lifeSize or tabletop.
hitVec3The tapped real point (a floor for life-size, a floor or a table for the tabletop).
viewerVec3The phone's real position, so the content faces it.
numbersViewNumbersThe sizes; the defaults where omitted.

Returns ​

RealTarget

The real target for rigFor.

Example ​

ts
import { placedTarget } from 'gameable/xr';

const t = placedTarget('lifeSize', [0, 0, -1.5], [0, 1.4, 0]);
console.log(t.yaw); // 0: the content faces +z, back towards the phone

rayFloorHit() ​

ts
function rayFloorHit(
   origin, 
   direction, 
   reach?
): Vec3 | null;

Where a ray meets the floor (the plane y = 0 of the frame the ray is given in), or null when it points up, runs level, or meets it beyond reach.

Parameters ​

ParameterTypeDefault valueDescription
originVec3undefinedThe ray's start.
directionVec3undefinedThe ray's direction (need not be unit length).
reachnumberFLOOR_REACHThe furthest hit that counts, metres along the ray.

Returns ​

Vec3 | null

The floor point, or null.

Example ​

ts
rayFloorHit([0, 1.2, 0], [0, -1, -1]); // [0, 0, -1.2]

rayNearStanding() ​

ts
function rayNearStanding(
   origin, 
   direction, 
   feet, 
   height, 
   radius
): boolean;

Whether a ray passes through a standing character: within radius of the upright segment from its feet up by height. The nearest point of the ray to that segment decides (the ray's own closest approach, never behind its start).

Parameters ​

ParameterTypeDescription
originVec3The ray's start.
directionVec3The ray's direction (need not be unit length).
feetVec3Where the character stands (its feet, on the floor).
heightnumberHow tall it is, in the same units.
radiusnumberHow far from its axis still counts as "on it".

Returns ​

boolean

True when the ray is on the character.

Example ​

ts
rayNearStanding([0, 1.4, 2], [0, 0, -1], [0, 0, 0], 1.7, 0.35); // true

readoutWords() ​

ts
function readoutWords(stats): string;

The words the readout shows for a set of numbers.

Parameters ​

ParameterTypeDescription
statsXrStatsThe numbers.

Returns ​

string

Two lines of plain words.

Example ​

ts
import { readoutWords } from 'gameable/xr';

console.log(readoutWords({ fps: 72, points: 1_204_000, eyes: 2 }));
// '72 frames a second\n1,204,000 points, two eyes'

rigFor() ​

ts
function rigFor(content, target): RigPose;

The camera parent's transform that puts the content where the viewer should see it.

Real point r appears at scene point anchor + R(yaw) * (r - point) / size, so the content's anchor lands on the real point and its facing lands on the real yaw.

Parameters ​

ParameterTypeDescription
contentContentFrameWhere the content stands and faces in the scene.
targetRealTargetWhere it should appear in the room.

Returns ​

RigPose

The rig's position, yaw and scale.

Example ​

ts
import { rigFor } from 'gameable/xr';

// A hero at the scene origin facing +z, shown life-size 2 m in front of a viewer at the real origin:
const rig = rigFor({ anchor: [0, 0, 0], facing: 0 }, { point: [0, 0, -2], yaw: 0, size: 1 });
console.log(rig.position); // [0, 0, 2]: the viewer stands 2 m behind the hero's spot... facing them

shoulderPoint() ​

ts
function shoulderPoint(
   floor, 
   facing, 
   numbers?
): Vec3;

The floor point behind a hero's shoulder: where the viewer stands in the over-the-shoulder view.

Parameters ​

ParameterTypeDescription
floorVec3The floor point under the hero.
facingnumberThe hero's yaw (0 faces +z, pi/2 faces +x).
numbersShoulderNumbersHow far behind and beside; the defaults where omitted.

Returns ​

Vec3

The floor point behind the shoulder.

Example ​

ts
import { shoulderPoint } from 'gameable/xr';

// A hero at the origin facing -z (yaw pi): the viewer stands 1.6 m behind (+z) and 0.45 m to the right (-x).
console.log(shoulderPoint([0, 0, 0], Math.PI)); // [-0.45, 0, 1.6] (within rounding)

shownWord() ​

ts
function shownWord(id, setting): string;

The words shown for a lever's setting ("Speed-up: 2", "Both eyes at once: on"); other levers show their own.

Parameters ​

ParameterType
idLeverId
settingstring

Returns ​

string


smoothTurn() ​

ts
function smoothTurn(
   x, 
   dt, 
   rate?, 
   threshold?
): number;

A smooth turn from the right stick: the turn to apply this frame while the stick is held past the dead zone, proportional to how far it is pushed.

Parameters ​

ParameterTypeDefault valueDescription
xnumberundefinedThe right stick's x.
dtnumberundefinedSeconds since the last frame.
ratenumber...The full speed, radians a second. Defaults to 60 degrees a second.
thresholdnumberSTICK_THRESHOLDThe dead zone. Defaults to STICK_THRESHOLD.

Returns ​

number

The turn this frame, radians (positive turns left), 0 at rest.

Example ​

ts
import { smoothTurn } from 'gameable/xr';

console.log(smoothTurn(1, 0.5)); // -Math.PI / 6: half a second of full speed to the right
console.log(smoothTurn(0.2, 0.5)); // 0: within the dead zone

snapTurn() ​

ts
function snapTurn(state, x): number;

A step turn from the right stick: one step per flick, none while held.

Parameters ​

ParameterTypeDescription
stateSnapTurnThe snap turn's state, kept between frames.
xnumberThe right stick's x.

Returns ​

number

The turn to apply this frame, radians (positive turns left), usually 0.

Example ​

ts
import { snapTurn } from 'gameable/xr';

const state = { armed: true };
console.log(snapTurn(state, 0.9)); // -Math.PI / 6: one step to the right
console.log(snapTurn(state, 0.9)); // 0: held, no second step

stickKeys() ​

ts
function stickKeys(
   x, 
   y, 
   threshold?
): StickKeys;

Read a thumb stick as the four walking keys: forward is up on the stick (negative y).

Parameters ​

ParameterTypeDefault valueDescription
xnumberundefinedThe stick's x, -1 to 1 (right positive).
ynumberundefinedThe stick's y, -1 to 1 (down positive, as WebXR reports it).
thresholdnumberSTICK_THRESHOLDHow far it must be pushed. Defaults to STICK_THRESHOLD.

Returns ​

StickKeys

Which keys are held.

Example ​

ts
import { stickKeys } from 'gameable/xr';

console.log(stickKeys(0, -1)); // { KeyW: true, KeyS: false, KeyA: false, KeyD: false }

tapAction() ​

ts
function tapAction(facts): TapAction;

Decide a tap.

Parameters ​

ParameterTypeDescription
factsTapFactsThe facts.

Returns ​

TapAction

What happens.

Example ​

ts
tapAction({ placed: true, hitFresh: true, listening: true, onCharacter: false }); // 'send'

teleportArc() ​

ts
function teleportArc(
   out, 
   origin, 
   direction, 
   floorY
): number;

Sample a ballistic arc into caller-owned xyz points; returns landing index, or -1. The first downward crossing stops the arc even when the landing is invalid.

Parameters ​

ParameterTypeDescription
outFloat32ArrayReusable packed xyz preview points.
originVec3Controller ray origin in scene metres.
directionVec3Controller ray direction, normalized internally.
floorYnumberAuthored floor height.

Returns ​

number

First floor crossing index, or -1 when none exists.

Example ​

ts
const landing = teleportArc(new Float32Array(99), [0, 1.5, 0], [0, 0, -1], 0);

teleportValid() ​

ts
function teleportValid(
   at, 
   floor, 
   clear
): boolean;

Validate a landing, including authored obstacles and head clearance via the callback.

Parameters ​

ParameterTypeDescription
atVec3Proposed scene-floor point.
floorTeleportFloorAuthored floor and edge clearance.
clear(at) => booleanObstacle and head-clearance validator.

Returns ​

boolean

Whether the landing is safe.

Example ​

ts
teleportValid([0, 0, 0], floor, () => true);

viewsByDevice() ​

ts
function viewsByDevice(views, device): object;

One button by device (the published page's rule, 6 Oct 2026): a phone is for the room view (its two-lens headset view is not offered); everything else, a headset or a computer with headset software, is for the headset view (a Quest's passthrough and a laptop's emulated room both answer yes to the room view, and neither gets a button for it). The gate's button on the ordinary page and the module's row on the lighter path both follow it.

Parameters ​

ParameterTypeDescription
views{ ar: boolean; vr: boolean; }What the browser can enter (canEnter, or the support flags).
views.arboolean-
views.vrboolean-
deviceDeviceKindThe device kind.

Returns ​

object

The views to offer.

ar ​
ts
ar: boolean;
vr ​
ts
vr: boolean;

Example ​

ts
import { viewsByDevice } from 'gameable/xr';

viewsByDevice({ vr: true, ar: true }, 'phone'); // { vr: false, ar: true }
viewsByDevice({ vr: true, ar: true }, 'computer'); // { vr: true, ar: false }

xr() ​

ts
function xr(options?): XrModule;

Make the xr module.

Parameters ​

ParameterTypeDescription
optionsXrOptionsWhat the game offers and where its content stands.

Returns ​

XrModule

The module; register it with createEngine({ xr: true, modules: [xr(...)] }).

Example ​

ts
import { createEngine } from 'gameable/core';
import { detectXrSupport, pickXrBackend, xr } from 'gameable/xr';

const support = await detectXrSupport();
const engine = await createEngine({
  canvas,
  xr: true,
  renderer: { backend: pickXrBackend(support) },
  modules: [xr({ support, content: () => ({ anchor: [0, 0, 0], facing: 0 }), readout: true })],
});

xrLights() ​

ts
function xrLights(xrModule, options?): EngineModule;

Make the lights module.

Parameters ​

ParameterTypeDescription
xrModuleXrModuleThe xr module, whose rig scale it follows.
options{ order?: number; }The module's options.
options.order?numberWhere it runs among the modules: after the page's own (default 9000).

Returns ​

EngineModule

The module; register it after the game's modules.

Example ​

ts
import { xr, xrLights } from 'gameable/xr';

const xrModule = xr({ support });
const engine = await createEngine({ canvas, xr: true, modules: [input(), game, xrModule, xrLights(xrModule)] });

yawDelta() ​

ts
function yawDelta(from, to): number;

The shortest signed turn from one yaw to another, radians in (-pi, pi].

Parameters ​

ParameterTypeDescription
fromnumberThe yaw now.
tonumberThe yaw wanted.

Returns ​

number

The turn.

Example ​

ts
import { yawDelta } from 'gameable/xr';

console.log(yawDelta(3, -3)); // about 0.283 (the short way round, not -6)

yawOf() ​

ts
function yawOf(x, z): number;

The yaw of a horizontal direction: 0 along +z, pi/2 along +x.

Parameters ​

ParameterTypeDescription
xnumberThe direction's x.
znumberThe direction's z.

Returns ​

number

The yaw, radians.

Example ​

ts
import { yawOf } from 'gameable/xr';

console.log(yawOf(0, 1)); // 0
console.log(yawOf(1, 0)); // Math.PI / 2