@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.
right
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
| Parameter | Type |
|---|---|
at | Vec3 |
Returns
boolean
move()
ts
move(
body,
at,
rotation
): void;Move a held body; callbacks borrow arrays only for the duration of the call.
Parameters
| Parameter | Type |
|---|---|
body | number |
at | Vec3 |
rotation | readonly [number, number, number, number] |
Returns
void
pick()
ts
pick(origin, direction): number;Nearest grabbable within reach of a ray, or zero.
Parameters
| Parameter | Type |
|---|---|
origin | Vec3 |
direction | Vec3 |
Returns
number
pull()?
ts
optional pull(
hand,
start,
at
): void;A pull gesture while gripping empty space (start and current hand positions).
Parameters
| Parameter | Type |
|---|---|
hand | number |
start | Vec3 |
at | Vec3 |
Returns
void
release()
ts
release(body, velocity): void;Parameters
| Parameter | Type |
|---|---|
body | number |
velocity | Vec3 |
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;head
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
| Parameter | Type |
|---|---|
view | FollowView |
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
| Parameter | Type |
|---|---|
radians | number |
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
| Parameter | Type |
|---|---|
level | string |
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
| Parameter | Type |
|---|---|
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
| Parameter | Type |
|---|---|
setting | string |
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
| Parameter | Type |
|---|---|
preset | string |
Returns
boolean
presets
ts
readonly presets: readonly string[];set
ts
readonly set: (preset) => void;Parameters
| Parameter | Type |
|---|---|
preset | string |
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
| Parameter | Type |
|---|---|
share | number |
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
| Parameter | Type |
|---|---|
points | number |
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
| Parameter | Type |
|---|---|
hz | number |
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
| Parameter | Type |
|---|---|
preset | string |
Returns
boolean
presets
ts
readonly presets: readonly string[];set
ts
readonly set: (preset) => void;Parameters
| Parameter | Type |
|---|---|
preset | string |
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
| Parameter | Type |
|---|---|
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
| Parameter | Type |
|---|---|
pose | XRViewerPose |
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
| Parameter | Type |
|---|---|
id | LeverId |
Returns
string
next()
ts
next(id): Promise<void>;Cycle a lever to its next setting.
Parameters
| Parameter | Type |
|---|---|
id | LeverId |
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
| Parameter | Type |
|---|---|
id | LeverId |
Returns
boolean
pageButtons()
ts
pageButtons(changed): HTMLButtonElement[];The page's pill buttons, one per offered lever; changed runs after each press.
Parameters
| Parameter | Type |
|---|---|
changed | () => void |
Returns
HTMLButtonElement[]
panelButtons()
ts
panelButtons(changed): PanelButton[];The in-headset panel's buttons, one per offered lever.
Parameters
| Parameter | Type |
|---|---|
changed | () => void |
Returns
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
| Parameter | Type |
|---|---|
id | LeverId |
setting | string |
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
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
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
dispose()
ts
dispose(): void;Release everything this module took.
Returns
void
Inherited from
endFrame()?
ts
optional endFrame(): void;Run after rendering. Input's end-of-frame bookkeeping lives here.
Returns
void
Inherited from
fixedUpdate()?
ts
optional fixedUpdate(dt): void;Run once per fixed step.
Parameters
| Parameter | Type | Description |
|---|---|---|
dt | number | Always ctx.config.fixedDt, whatever time.timeScale is; time scale changes how many steps a frame runs, not their length. |
Returns
void
Inherited from
init()
ts
init(ctx): void | object | Promise<void | object>;Acquire resources.
Parameters
| Parameter | Type | Description |
|---|---|---|
ctx | HostContext | The host surface. |
Returns
void | object | Promise<void | object>
Nothing, or the service to publish under id.
Inherited from
update()?
ts
optional update(dt, alpha): void;Run once per frame, after the fixed steps and before rendering.
Parameters
| Parameter | Type | Description |
|---|---|---|
dt | number | Clamped wall-clock seconds since the previous frame, scaled by time.timeScale. |
alpha | number | Interpolation factor in [0, 1) for smoothing transforms. |
Returns
void
Inherited from
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
| Parameter | Type |
|---|---|
open | number |
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
| Parameter | Type |
|---|---|
mode | string |
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
| Parameter | Type |
|---|---|
mode | XrMode |
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
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
| Parameter | Type |
|---|---|
results | readonly 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
| Parameter | Type |
|---|---|
headPosition | Vector3 |
headQuaternion | Quaternion |
Returns
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
| Parameter | Type |
|---|---|
headPosition | Vector3 |
headQuaternion | Quaternion |
dt | number |
Returns
void
hitIndex()
ts
hitIndex(ray): number;The button a ray hits right now, or -1.
Parameters
| Parameter | Type |
|---|---|
ray | Matrix4 |
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
| Parameter | Type |
|---|---|
headPosition | Vector3 |
headQuaternion | Quaternion |
Returns
void
press()
ts
press(ray): string | null;Press the button a ray hovers; the label pressed, or null.
Parameters
| Parameter | Type |
|---|---|
ray | number |
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
| Parameter | Type |
|---|---|
ray | Matrix4 |
Returns
string | null
refresh()
ts
refresh(): void;Redraw the words.
Returns
void
setButtons()
ts
setButtons(buttons): void;Replace the buttons.
Parameters
| Parameter | Type |
|---|---|
buttons | readonly 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
| Parameter | Type |
|---|---|
open | boolean |
holdMs? | number |
why? | string |
Returns
void
showPage()
ts
showPage(index): void;Show a page (0-based).
Parameters
| Parameter | Type |
|---|---|
index | number |
Returns
void
tick()
ts
tick(nowMs, holdOpen): void;Each frame: close after the ray has left for PANEL_HANDLE.closeAfterMs unless holdOpen.
Parameters
| Parameter | Type |
|---|---|
nowMs | number |
holdOpen | boolean |
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
| Parameter | Type |
|---|---|
rays | readonly 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
| Parameter | Type |
|---|---|
nowMs | number |
scene | Scene |
eyes | number |
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
| Parameter | Type |
|---|---|
value | boolean |
Returns
void
setSettings()
ts
setSettings(text): void;A third line: the settings in force (the levers), plain words.
Parameters
| Parameter | Type |
|---|---|
text | string |
Returns
void
setVisible()
ts
setVisible(value): void;Show or hide both the panel and the card.
Parameters
| Parameter | Type |
|---|---|
value | boolean |
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
| Parameter | Type |
|---|---|
text | string |
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
| Parameter | Type |
|---|---|
mode | XrMode |
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
| Parameter | Type |
|---|---|
kind | string |
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
| Parameter | Type |
|---|---|
point | Vec3 | 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
| Parameter | Type |
|---|---|
words | string | 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
| Parameter | Type |
|---|---|
type | K |
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
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
| Parameter | Type |
|---|---|
metres | number |
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
| Parameter | Type |
|---|---|
id | LeverId |
setting | string |
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
| Parameter | Type |
|---|---|
onePass | boolean |
Returns
void
setNarrowing()
ts
setNarrowing(level): void;Parameters
| Parameter | Type |
|---|---|
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
| Parameter | Type |
|---|---|
view | XrView |
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
| Parameter | Type |
|---|---|
point | Vec3 |
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
| Parameter | Type |
|---|---|
kind | string |
data | Record<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
| Parameter | Type |
|---|---|
headPos | Vector3 |
headQuat | Quaternion |
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
| Parameter | Type |
|---|---|
session | XRSession |
mode | string |
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
| Parameter | Type | Description |
|---|---|---|
current | { size: number; yaw: number; } | The placed size and yaw. |
current.size | number | - |
current.yaw | number | - |
gesture | { ratio: number; turn: number; } | The fingers' distance ratio (1 unchanged) and angle change (radians). |
gesture.ratio | number | - |
gesture.turn | number | - |
bounds | { max: number; min: number; } | The smallest and largest size allowed. Defaults to 0.02 to 2. |
bounds.max | number | - |
bounds.min | number | - |
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
| Parameter | Type | Default value | Description |
|---|---|---|---|
container | HTMLElement | document.body | Where the row goes. Defaults to document.body. |
inline | boolean | false | True 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
| Parameter | Type | Description |
|---|---|---|
support | Pick<XrSupport, "vr" | "ar" | "gpuInXr"> | What the browser answered. |
webgpu | boolean | True 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 itcatchUp()
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
| Parameter | Type | Description |
|---|---|---|
state | FollowState | The view's state, changed in place. |
target | { at: Vec3; facing: number; } | Where the hero stands and faces this frame. |
target.at | Vec3 | - |
target.facing | number | - |
dt | number | Seconds since the last frame. |
options | ComfortOptions | The comfort options; the defaults where omitted. |
Returns
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 falseclickCanvas()
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
| Parameter | Type | Description |
|---|---|---|
canvas | HTMLElement | The engine's canvas. |
down | boolean | Press 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
| Parameter | Type | Description |
|---|---|---|
scene | Scene | The 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
The state.
Example
ts
import { createFollowState } from 'gameable/xr';
const state = createFollowState();
console.log(state.started); // falsecreateGrabTracker()
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
| Parameter | Type | Description |
|---|---|---|
hand | number | Left (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
| Parameter | Type | Description |
|---|---|---|
hand | number | Left (0) or right (1). |
at | Vec3 | Current hand position. |
dt | number | Seconds 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
| Parameter | Type | Description |
|---|---|---|
hand | number | Left (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
| Parameter | Type | Description |
|---|---|---|
hand | number | Left (0) or right (1). |
body | number | Nonzero physics body id. |
at | Vec3 | Initial 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
| Parameter | Type | Description |
|---|---|---|
target | EventTarget | Where 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
| Parameter | Type |
|---|---|
keys | StickKeys |
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
| Parameter | Type | Description |
|---|---|---|
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.hooks | XrLeverHooks | - |
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) => void | The eyes lever: ask for both eyes in one pass at the next entry (the module's own hook). Absent: no eyes lever. |
options.pointsDrawn? | () => number | Points drawn now (the module counts them), for the skin guard: a switch that leaves none falls back. |
options.rememberKey? | string | Remember the person's choices for the tab under this key (sessionStorage); they win over initial. |
options.renderer | WebGPURenderer | - |
options.turnMode? | (mode) => void | The turning lever: how the right stick turns (the module's own hook). Absent: no turning lever. |
Returns
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
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 narrowestcreatePanel()
ts
function createPanel(options?): XrPanel;Make the panel.
Parameters
| Parameter | Type |
|---|---|
options | { onOpenChange?: (open, why) => void; } |
options.onOpenChange? | (open, why) => void |
Returns
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
| Parameter | Type | Description |
|---|---|---|
container? | HTMLElement | null | Where the page panel goes. Defaults to document.body. |
Returns
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
| Parameter | Type | Description |
|---|---|---|
options | RoomControlsOptions | Host 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
| Parameter | Type | Description |
|---|---|---|
down | boolean | Whether grab is held. |
at | Vec3 | Hand anchor on the view ray. |
aim | Vec3 | View ray direction. |
dt | number | Seconds since the preceding sample. |
Returns
void
dispose()
ts
dispose(): void;Returns
void
update()
ts
update(dt): void;Parameters
| Parameter | Type |
|---|---|
dt | number |
Returns
void
Example
ts
controls.update(1 / 72); controls.dispose();createTelemetry()
ts
function createTelemetry(options, sources): XrTelemetry;Make the log.
Parameters
| Parameter | Type | Description |
|---|---|---|
options | XrTelemetryOptions | Where and how often. |
sources | XrTelemetrySources | What to read. |
Returns
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
| Parameter | Type | Default value | Description |
|---|---|---|---|
nav | XrNavigatorLike | navigator | The navigator, for tests; the page's by default. |
globalObject | object | globalThis | The 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 onedeviceKind()
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
| Parameter | Type | Default value | Description |
|---|---|---|---|
userAgent | string | undefined | The browser's navigator.userAgent. |
maxTouchPoints | number | 0 | navigator.maxTouchPoints, which tells an iPad apart from a Mac. |
Returns
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
| Parameter | Type | Description |
|---|---|---|
a | readonly [number, number] | The first finger, page pixels. |
b | readonly [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
| Parameter | Type | Description |
|---|---|---|
floor | Vec3 | The followed floor point in the scene (under the hero). |
facing | number | The view's yaw in the scene, radians, so that way is the room's forward. |
head | Vec3 | The head's real position this frame, metres from the real origin (only its x and z are used). |
offset | Vec3 | Where 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
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); // 1framebufferScaleFor()
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
| Parameter | Type | Description |
|---|---|---|
userAgent | string | The 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')); // 1headsetTarget()
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
| Parameter | Type | Description |
|---|---|---|
view | "tabletop" | "lifeSize" | lifeSize or tabletop. |
numbers | ViewNumbers | The distances; the defaults where omitted. |
Returns
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
| Parameter | Type | Description |
|---|---|---|
rigScale | number | The 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
| Parameter | Type | Default value | Description |
|---|---|---|---|
open | number | undefined | How open it is now, 0 to 1. |
moving | boolean | undefined | True while the view is catching up or turning smoothly. |
dt | number | undefined | Seconds since the last frame. |
narrowed | number | 0 | How 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 againpickXrBackend()
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
| Parameter | Type | Default value | Description |
|---|---|---|---|
support | Pick<XrSupport, "vr" | "ar" | "gpuInXr"> | undefined | What the browser answered (detectXrSupport). |
preferred | RendererBackend | 'auto' | The backend the page would use without a headset. Defaults to 'auto'. |
Returns
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 laptoppillButton()
ts
function pillButton(text, primary?): HTMLButtonElement;Make one pill button.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
text | string | undefined | Its words. |
primary | boolean | false | Mint (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
| Parameter | Type | Description |
|---|---|---|
view | "tabletop" | "lifeSize" | lifeSize or tabletop. |
hit | Vec3 | The tapped real point (a floor for life-size, a floor or a table for the tabletop). |
viewer | Vec3 | The phone's real position, so the content faces it. |
numbers | ViewNumbers | The sizes; the defaults where omitted. |
Returns
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 phonerayFloorHit()
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
| Parameter | Type | Default value | Description |
|---|---|---|---|
origin | Vec3 | undefined | The ray's start. |
direction | Vec3 | undefined | The ray's direction (need not be unit length). |
reach | number | FLOOR_REACH | The 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
| Parameter | Type | Description |
|---|---|---|
origin | Vec3 | The ray's start. |
direction | Vec3 | The ray's direction (need not be unit length). |
feet | Vec3 | Where the character stands (its feet, on the floor). |
height | number | How tall it is, in the same units. |
radius | number | How 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); // truereadoutWords()
ts
function readoutWords(stats): string;The words the readout shows for a set of numbers.
Parameters
| Parameter | Type | Description |
|---|---|---|
stats | XrStats | The 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
| Parameter | Type | Description |
|---|---|---|
content | ContentFrame | Where the content stands and faces in the scene. |
target | RealTarget | Where it should appear in the room. |
Returns
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 themshoulderPoint()
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
| Parameter | Type | Description |
|---|---|---|
floor | Vec3 | The floor point under the hero. |
facing | number | The hero's yaw (0 faces +z, pi/2 faces +x). |
numbers | ShoulderNumbers | How far behind and beside; the defaults where omitted. |
Returns
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
| Parameter | Type |
|---|---|
id | LeverId |
setting | string |
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
| Parameter | Type | Default value | Description |
|---|---|---|---|
x | number | undefined | The right stick's x. |
dt | number | undefined | Seconds since the last frame. |
rate | number | ... | The full speed, radians a second. Defaults to 60 degrees a second. |
threshold | number | STICK_THRESHOLD | The 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 zonesnapTurn()
ts
function snapTurn(state, x): number;A step turn from the right stick: one step per flick, none while held.
Parameters
| Parameter | Type | Description |
|---|---|---|
state | SnapTurn | The snap turn's state, kept between frames. |
x | number | The 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 stepstickKeys()
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
| Parameter | Type | Default value | Description |
|---|---|---|---|
x | number | undefined | The stick's x, -1 to 1 (right positive). |
y | number | undefined | The stick's y, -1 to 1 (down positive, as WebXR reports it). |
threshold | number | STICK_THRESHOLD | How far it must be pushed. Defaults to STICK_THRESHOLD. |
Returns
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
| Parameter | Type | Description |
|---|---|---|
facts | TapFacts | The facts. |
Returns
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
| Parameter | Type | Description |
|---|---|---|
out | Float32Array | Reusable packed xyz preview points. |
origin | Vec3 | Controller ray origin in scene metres. |
direction | Vec3 | Controller ray direction, normalized internally. |
floorY | number | Authored 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
| Parameter | Type | Description |
|---|---|---|
at | Vec3 | Proposed scene-floor point. |
floor | TeleportFloor | Authored floor and edge clearance. |
clear | (at) => boolean | Obstacle 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
| Parameter | Type | Description |
|---|---|---|
views | { ar: boolean; vr: boolean; } | What the browser can enter (canEnter, or the support flags). |
views.ar | boolean | - |
views.vr | boolean | - |
device | DeviceKind | The 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
| Parameter | Type | Description |
|---|---|---|
options | XrOptions | What the game offers and where its content stands. |
Returns
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
| Parameter | Type | Description |
|---|---|---|
xrModule | XrModule | The xr module, whose rig scale it follows. |
options | { order?: number; } | The module's options. |
options.order? | number | Where it runs among the modules: after the page's own (default 9000). |
Returns
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
| Parameter | Type | Description |
|---|---|---|
from | number | The yaw now. |
to | number | The 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
| Parameter | Type | Description |
|---|---|---|
x | number | The direction's x. |
z | number | The 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