google/xb-simulator
>- Develop and test XR Blocks apps on the desktop without a headset using the built-in simulator — a simulated user, hands, depth, and planes rendered in a normal browser, with control modes for moving the user, posing hands, or driving controllers. Use when running/iterating locally, reproducing XR interactions on desktop, posing hands for gesture work, or adding the optional 2D simulator settings UI. Covers the `?formFactor=desktop` autostart, `options.simulator.*`, `xb.SimulatorMode`, the `SimulatorAddons` 2D UI import, and the `onSimulatorStarted()` hook.
npx skills add https://github.com/google/xrblocks --skill xb-simulator
The simulator runs the same app in a normal browser so you can iterate without a device. It is
on by default (options.enableSimulator).
npm run dev # serves http://127.0.0.1:8080
?formFactor=desktop to the URL to autostart the simulator, orconst options = new xb.Options();
options.formFactor = 'desktop'; // autostart simulator
// or expose a button: options.xrButton.showEnterSimulatorButton = true;
Import the simulator addon to get on-screen settings/instruction panels (hand-pose picker,
gamepad settings, mic button, etc.) on desktop:
import 'xrblocks/addons/simulator/SimulatorAddons.js';
options.simulator.defaultMode = xb.SimulatorMode.POSE; // pose hands (great for gestures/hands)
SimulatorMode.POSE lets you pose virtual hands; other modes move the user or drive
controllers — see src/simulator/SimulatorOptions.ts
and src/simulator/controlModes/.
You can limit how far each virtual hand controller can travel from the user's shoulder origin (in meters) and restrict their movement to an angular cone (in radians) facing forward from the camera:
options.simulator.reachDistance.enabled = true;
options.simulator.reachDistance.radius = 0.75; // meters from shoulder origin
options.simulator.leftHandOrigin = {x: -0.2, y: -0.2, z: 0};
options.simulator.rightHandOrigin = {x: 0.2, y: -0.2, z: 0};
options.simulator.reachAngle.enabled = true;
options.simulator.reachAngle.angle = Math.PI; // radians (default Math.PI is a front hemisphere)
Physical simulated hands are opt-in and require Rapier:
options.simulator.handPhysics.enabled = true;
Disable all simulator-owned physics while leaving regular app physics available with:
options.simulator.physics.enabled = false;
The top-level hand origins are shared by reach limits and hand physics. When hand physics
is enabled, they act as invisible shoulder-to-palm tethers so fixed geometry cannot leave
a hand trapped on the far side of a wall. Dynamic objects remain pushable by the hands.
Simulator environments use one strict JSON manifest:
options.simulator.environments = [
{name: 'Evaluation Room', manifestPath: './evaluation-room.json'},
];
The manifest can contain scenePath or videoPath, scenePlanesPath,
navMeshPath, a root position/quaternion/scale, and an objects array.
Object entries support assetPath, optional transform arrays,
physics: false | 'fixed' | 'dynamic', and detectObject plus label. All paths
may be manifest-relative, root-relative, or absolute CDN URLs.
At runtime, use the plural API:
await xb.core.simulator.objects.addObjects([
{
assetPath: './chair.glb',
physics: 'fixed',
detectObject: true,
label: 'chair',
},
]);
xb.core.simulator.objects.get();
xb.core.simulator.objects.removeObjects(['simulator-object-1']);
Set options.world.objects.simulatorOverride = true after enabling object
detection to make the normal world.objects.runDetection() API use simulator
frustum and visibility checks on desktop. Real XR sessions continue to use the
configured detector backend.
onSimulatorStarted() fires when the simulator boots — a common pattern is to mirror your XR
startup:
onSimulatorStarted() { this.onXRSessionStarted(); }
xb-depth andxb-world features work on desktop.
demos/sim_hand_poses is a focused example of posing hands in the simulator.options.enableSimulator = false (or formFactor: 'xr') disables it for device-only builds.Take google/xb-simulator from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.