mujoco-react 7.0.1 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,18 +24,18 @@ import {
24
24
  MujocoCanvas,
25
25
  useIkController,
26
26
  IkGizmo,
27
- } from 'mujoco-react';
28
- import type { SceneConfig } from 'mujoco-react';
29
- import { OrbitControls } from '@react-three/drei';
27
+ } from "mujoco-react";
28
+ import type { SceneConfig } from "mujoco-react";
29
+ import { OrbitControls } from "@react-three/drei";
30
30
 
31
31
  const config: SceneConfig = {
32
- src: 'https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/franka_emika_panda/',
33
- sceneFile: 'scene.xml',
32
+ src: "https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/franka_emika_panda/",
33
+ sceneFile: "scene.xml",
34
34
  homeJoints: [1.707, -1.754, 0.003, -2.702, 0.003, 0.951, 2.490],
35
35
  };
36
36
 
37
37
  function Scene() {
38
- const ik = useIkController({ siteName: 'tcp', numJoints: 7 });
38
+ const ik = useIkController({ siteName: "tcp", numJoints: 7 });
39
39
  return (
40
40
  <>
41
41
  <OrbitControls enableDamping makeDefault />
@@ -53,7 +53,7 @@ function App() {
53
53
  config={config}
54
54
  camera={{ position: [2, -1.5, 2.5], up: [0, 0, 1], fov: 45 }}
55
55
  shadows
56
- style={{ width: '100%', height: '100vh' }}
56
+ style={{ width: "100%", height: "100vh" }}
57
57
  >
58
58
  <Scene />
59
59
  </MujocoCanvas>
@@ -67,7 +67,7 @@ function App() {
67
67
  Inside `<MujocoCanvas>` or `<MujocoPhysics>`, `useMujoco()` gives you the simulation API, refs to the live model/data, and status:
68
68
 
69
69
  ```tsx
70
- import { useMujoco } from 'mujoco-react';
70
+ import { useMujoco } from "mujoco-react";
71
71
 
72
72
  function MyComponent() {
73
73
  const { isPending, isError, error, api, mjModelRef } = useMujoco();
@@ -85,15 +85,19 @@ function MyComponent() {
85
85
 
86
86
  ## Writing a Controller
87
87
 
88
- A controller is a React component that calls `useBeforePhysicsStep` to write `data.ctrl` each frame:
88
+ A controller is a React component that uses handle-based hooks for type-safe actuator and sensor access:
89
89
 
90
90
  ```tsx
91
- import { useBeforePhysicsStep } from 'mujoco-react';
91
+ import { useCtrl, useSensor, useBeforePhysicsStep } from "mujoco-react";
92
92
 
93
93
  function MyController() {
94
- useBeforePhysicsStep((_model, data) => {
95
- data.ctrl[0] = Math.sin(data.time); // sine wave on actuator 0
96
- data.ctrl[1] = data.sensordata[0] * -0.5; // feedback from a sensor
94
+ const shoulder = useCtrl("shoulder");
95
+ const elbow = useCtrl("elbow");
96
+ const force = useSensor("force_sensor");
97
+
98
+ useBeforePhysicsStep(() => {
99
+ shoulder.write(Math.sin(Date.now() / 1000));
100
+ elbow.write(force.read()[0] * -0.5);
97
101
  });
98
102
  return null;
99
103
  }
@@ -110,10 +114,10 @@ Drop it into the tree:
110
114
  The `createController<TConfig>()` factory adds typed config and default merging for reusable plugins:
111
115
 
112
116
  ```tsx
113
- import { createController, useBeforePhysicsStep } from 'mujoco-react';
117
+ import { createController, useBeforePhysicsStep } from "mujoco-react";
114
118
 
115
119
  export const MyController = createController<{ gain: number }>(
116
- { name: 'MyController', defaultConfig: { gain: 1.0 } },
120
+ { name: "MyController", defaultConfig: { gain: 1.0 } },
117
121
  ({ config }) => {
118
122
  useBeforePhysicsStep((_model, data) => {
119
123
  data.ctrl[0] = config.gain * Math.sin(data.time);
@@ -145,13 +149,13 @@ export const MyController = createController<{ gain: number }>(
145
149
  The built-in `useIkController()` uses Damped Least-Squares. Pass `ikSolveFn` to swap in your own solver (analytical, learned, etc.):
146
150
 
147
151
  ```tsx
148
- import type { IKSolveFn } from 'mujoco-react';
152
+ import type { IKSolveFn } from "mujoco-react";
149
153
 
150
154
  const myIK: IKSolveFn = (pos, quat, currentQ) => {
151
155
  return myAnalyticalSolver(pos, currentQ); // return joint angles or null
152
156
  };
153
157
 
154
- const ik = useIkController({ siteName: 'tcp', numJoints: 7, ikSolveFn: myIK });
158
+ const ik = useIkController({ siteName: "tcp", numJoints: 7, ikSolveFn: myIK });
155
159
  ```
156
160
 
157
161
  ### `useIkController(config | null)`
@@ -159,7 +163,7 @@ const ik = useIkController({ siteName: 'tcp', numJoints: 7, ikSolveFn: myIK });
159
163
  Hook for interactive end-effector control. Pass `null` to disable IK (safe to call unconditionally):
160
164
 
161
165
  ```tsx
162
- const ik = useIkController({ siteName: 'tcp', numJoints: 7 });
166
+ const ik = useIkController({ siteName: "tcp", numJoints: 7 });
163
167
  return ik ? <IkGizmo controller={ik} /> : null;
164
168
  ```
165
169
 
@@ -175,6 +179,23 @@ Returns `IkContextValue | null` with methods like `setIkEnabled`, `moveTarget`,
175
179
 
176
180
  Pass the returned value to `<IkGizmo controller={ik} />` or to your own controller as a prop.
177
181
 
182
+ ## Type-Safe Resource Names
183
+
184
+ Use TypeScript module augmentation to get autocomplete and type checking for actuator, sensor, body, joint, site, geom, and keyframe names:
185
+
186
+ ```ts
187
+ // e.g. in src/mujoco-register.d.ts
188
+ declare module "mujoco-react" {
189
+ interface Register {
190
+ actuators: "joint1" | "joint2" | "joint3" | "gripper";
191
+ sensors: "force_sensor" | "torque_sensor";
192
+ bodies: "link0" | "link1" | "hand";
193
+ }
194
+ }
195
+ ```
196
+
197
+ Once declared, hooks like `useCtrl`, `useSensor`, `useBodyState`, and API methods like `setCtrl`, `applyForce`, `getSensorData` will only accept the declared names. When no `Register` augmentation is provided, all names fall back to `string`.
198
+
178
199
  ## Loading Models
179
200
 
180
201
  The loader fetches `src + sceneFile`, parses the XML for dependencies (meshes, textures, includes), recursively fetches those too, and writes everything to MuJoCo's in-memory WASM filesystem.
@@ -182,14 +203,14 @@ The loader fetches `src + sceneFile`, parses the XML for dependencies (meshes, t
182
203
  ```tsx
183
204
  // MuJoCo Menagerie
184
205
  const franka: SceneConfig = {
185
- src: 'https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/franka_emika_panda/',
186
- sceneFile: 'scene.xml',
206
+ src: "https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/franka_emika_panda/",
207
+ sceneFile: "scene.xml",
187
208
  };
188
209
 
189
210
  // Any URL
190
211
  const custom: SceneConfig = {
191
- src: 'http://localhost:3000/models/my_model/',
192
- sceneFile: 'model.xml',
212
+ src: "http://localhost:3000/models/my_model/",
213
+ sceneFile: "model.xml",
193
214
  };
194
215
  ```
195
216
 
@@ -198,7 +219,7 @@ const custom: SceneConfig = {
198
219
  ```ts
199
220
  interface SceneConfig {
200
221
  src: string; // Base URL for model files
201
- sceneFile: string; // Entry XML file, e.g. 'scene.xml'
222
+ sceneFile: string; // Entry XML file, e.g. "scene.xml"
202
223
  sceneObjects?: SceneObject[]; // Objects injected into scene XML at load time
203
224
  homeJoints?: number[]; // Initial joint positions
204
225
  xmlPatches?: XmlPatch[]; // Patches applied to XML files during loading
@@ -210,12 +231,12 @@ interface SceneConfig {
210
231
 
211
232
  ```tsx
212
233
  const config: SceneConfig = {
213
- src: 'https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/franka_emika_panda/',
214
- sceneFile: 'scene.xml',
234
+ src: "https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/franka_emika_panda/",
235
+ sceneFile: "scene.xml",
215
236
  sceneObjects: [
216
- { name: 'ball', type: 'sphere', size: [0.03, 0.03, 0.03],
237
+ { name: "ball", type: "sphere", size: [0.03, 0.03, 0.03],
217
238
  position: [0.5, 0, 0.1], rgba: [1, 0, 0, 1], mass: 0.1, freejoint: true },
218
- { name: 'platform', type: 'box', size: [0.2, 0.2, 0.01],
239
+ { name: "platform", type: "box", size: [0.2, 0.2, 0.01],
219
240
  position: [0.4, 0.3, 0], rgba: [0.5, 0.5, 0.5, 1] },
220
241
  ],
221
242
  };
@@ -225,10 +246,10 @@ const config: SceneConfig = {
225
246
 
226
247
  ```tsx
227
248
  xmlPatches: [{
228
- target: 'panda.xml',
229
- replace: ['name="actuator8"', 'name="gripper"'],
230
- inject: '<site name="tcp" pos="0 0 0.1" size="0.01"/>',
231
- injectAfter: '<body name="hand"',
249
+ target: "panda.xml",
250
+ replace: ["name=\"actuator8\"", "name=\"gripper\""],
251
+ inject: "<site name=\"tcp\" pos=\"0 0 0.1\" size=\"0.01\"/>",
252
+ injectAfter: "<body name=\"hand\"",
232
253
  }]
233
254
  ```
234
255
 
@@ -352,7 +373,7 @@ InstancedMesh showing MuJoCo contact points for debugging.
352
373
  |------|------|---------|-------------|
353
374
  | `maxContacts` | `number?` | `100` | Max contacts to display |
354
375
  | `radius` | `number?` | `0.005` | Marker sphere radius |
355
- | `color` | `string?` | `'#4f46e5'` | Marker color |
376
+ | `color` | `string?` | `"#4f46e5"` | Marker color |
356
377
  | `visible` | `boolean?` | `true` | Toggle visibility |
357
378
 
358
379
  ### `<SceneLights />`
@@ -372,10 +393,10 @@ Visualization overlays:
372
393
  | `showCOM` | `boolean?` | `false` | Center of mass markers |
373
394
  | `showInertia` | `boolean?` | `false` | Inertia ellipsoids |
374
395
  | `showTendons` | `boolean?` | `false` | Tendon paths |
375
- | `geomColor` | `string?` | `'#00ff00'` | Color for wireframe geoms |
376
- | `siteColor` | `string?` | `'#ff00ff'` | Color for site markers |
377
- | `contactColor` | `string?` | `'#ff4444'` | Color for contact force arrows |
378
- | `comColor` | `string?` | `'#ff0000'` | Color for COM markers |
396
+ | `geomColor` | `string?` | `"#00ff00"` | Color for wireframe geoms |
397
+ | `siteColor` | `string?` | `"#ff00ff"` | Color for site markers |
398
+ | `contactColor` | `string?` | `"#ff4444"` | Color for contact force arrows |
399
+ | `comColor` | `string?` | `"#ff0000"` | Color for COM markers |
379
400
 
380
401
  ### `<TendonRenderer />`
381
402
 
@@ -392,8 +413,8 @@ Component wrapper for contact events:
392
413
  ```tsx
393
414
  <ContactListener
394
415
  body="block_1"
395
- onContactEnter={(info) => console.log('contact!', info)}
396
- onContactExit={(info) => console.log('released', info)}
416
+ onContactEnter={(info) => console.log("contact!", info)}
417
+ onContactExit={(info) => console.log("released", info)}
397
418
  />
398
419
  ```
399
420
 
@@ -419,12 +440,12 @@ if (sim.isReady) {
419
440
  Access the raw WASM module lifecycle from any child of `<MujocoProvider>`. Most users won't need this — `useMujoco()` and hooks like `useBeforePhysicsStep` handle the model/data lifecycle for you.
420
441
 
421
442
  ```tsx
422
- import { useMujocoWasm } from 'mujoco-react';
443
+ import { useMujocoWasm } from "mujoco-react";
423
444
 
424
445
  const { mujoco, status } = useMujocoWasm();
425
446
 
426
447
  if (mujoco) {
427
- const model = mujoco.MjModel.loadFromXML('/path/to/scene.xml');
448
+ const model = mujoco.MjModel.loadFromXML("/path/to/scene.xml");
428
449
  const data = new mujoco.MjData(model);
429
450
  mujoco.mj_step(model, data);
430
451
  console.log(data.qpos); // joint positions after one step
@@ -466,10 +487,11 @@ await moveCameraTo(
466
487
 
467
488
  ### `useSensor(name)` / `useSensors()`
468
489
 
469
- Read sensor values by name (ref-based, no re-renders):
490
+ Read sensor values by name. Returns a `SensorHandle` with `read()`, `dim`, and `name`:
470
491
 
471
492
  ```tsx
472
- const { value, size, type } = useSensor('force_sensor_1');
493
+ const force = useSensor("force_sensor_1");
494
+ // force.read() -> Float64Array, force.dim -> number
473
495
  ```
474
496
 
475
497
  ### `useBodyState(name)`
@@ -477,7 +499,7 @@ const { value, size, type } = useSensor('force_sensor_1');
477
499
  Position, quaternion, linear/angular velocity of a body (ref-based):
478
500
 
479
501
  ```tsx
480
- const { position, quaternion, linearVelocity, angularVelocity } = useBodyState('block_1');
502
+ const { position, quaternion, linearVelocity, angularVelocity } = useBodyState("block_1");
481
503
  ```
482
504
 
483
505
  ### `useJointState(name)`
@@ -485,15 +507,16 @@ const { position, quaternion, linearVelocity, angularVelocity } = useBodyState('
485
507
  Joint position and velocity:
486
508
 
487
509
  ```tsx
488
- const { position, velocity } = useJointState('joint1');
510
+ const { position, velocity } = useJointState("joint1");
489
511
  ```
490
512
 
491
513
  ### `useCtrl(name)`
492
514
 
493
- Read/write actuator control by name:
515
+ Read/write actuator control by name. Returns a `CtrlHandle` with `read()`, `write()`, `name`, and `range`:
494
516
 
495
517
  ```tsx
496
- const [value, setValue] = useCtrl('gripper');
518
+ const gripper = useCtrl("gripper");
519
+ // gripper.read() -> number, gripper.write(0.04), gripper.range -> [min, max]
497
520
  ```
498
521
 
499
522
  ### `useContacts(bodyName?)` / `useContactEvents(bodyName, handlers)`
@@ -501,9 +524,9 @@ const [value, setValue] = useCtrl('gripper');
501
524
  Query contacts or subscribe to enter/exit events:
502
525
 
503
526
  ```tsx
504
- useContactEvents('block_1', {
505
- onEnter: (info) => console.log('contact!', info),
506
- onExit: (info) => console.log('released', info),
527
+ useContactEvents("block_1", {
528
+ onEnter: (info) => console.log("contact!", info),
529
+ onExit: (info) => console.log("released", info),
507
530
  });
508
531
  ```
509
532
 
@@ -514,9 +537,9 @@ Map keyboard keys to actuators:
514
537
  ```tsx
515
538
  useKeyboardTeleop({
516
539
  bindings: {
517
- 'w': { actuator: 'forward', delta: 0.1 },
518
- 's': { actuator: 'forward', delta: -0.1 },
519
- 'v': { actuator: 'gripper', toggle: [0, 0.04] },
540
+ "w": { actuator: "forward", delta: 0.1 },
541
+ "s": { actuator: "forward", delta: -0.1 },
542
+ "v": { actuator: "gripper", toggle: [0, 0.04] },
520
543
  },
521
544
  });
522
545
  ```
@@ -527,8 +550,8 @@ Map gamepad axes/buttons to actuators:
527
550
 
528
551
  ```tsx
529
552
  useGamepad({
530
- axes: { 0: 'joint1', 1: 'joint2' },
531
- buttons: { 0: 'gripper' },
553
+ axes: { 0: "joint1", 1: "joint2" },
554
+ buttons: { 0: "gripper" },
532
555
  deadzone: 0.1,
533
556
  });
534
557
  ```
@@ -550,11 +573,23 @@ const { step, isRunning } = usePolicy({
550
573
  Record and play back simulation trajectories:
551
574
 
552
575
  ```tsx
553
- const recorder = useTrajectoryRecorder({ fields: ['qpos', 'qvel', 'ctrl'] });
554
- // recorder.start(), recorder.stop(), recorder.downloadJSON(), recorder.downloadCSV()
576
+ // Record
577
+ const recorder = useTrajectoryRecorder({ fields: ["qpos", "ctrl"] });
578
+ recorder.start();
579
+ // ... interact with simulation ...
580
+ recorder.stop();
555
581
 
556
- const player = useTrajectoryPlayer(trajectory, { fps: 30, loop: true });
557
- // player.play(), player.pause(), player.seek(frameIdx)
582
+ // Play back recorded frames directly (no conversion needed)
583
+ const player = useTrajectoryPlayer(recorder.frames, {
584
+ fps: 30,
585
+ speed: 1.0, // 0.5x, 1x, 2x, etc.
586
+ loop: true,
587
+ mode: "kinematic", // or "physics" to replay ctrl through the sim
588
+ onComplete: () => console.log("done"),
589
+ });
590
+ // player.play(), player.pause(), player.seek(42), player.setSpeed(2)
591
+ // player.state → "idle" | "playing" | "paused" | "completed"
592
+ // player.progress → 0-1
558
593
  ```
559
594
 
560
595
  ### `useVideoRecorder(config)`
@@ -562,7 +597,7 @@ const player = useTrajectoryPlayer(trajectory, { fps: 30, loop: true });
562
597
  Record the canvas as video:
563
598
 
564
599
  ```tsx
565
- const video = useVideoRecorder({ fps: 30, mimeType: 'video/webm' });
600
+ const video = useVideoRecorder({ fps: 30, mimeType: "video/webm" });
566
601
  // video.start(), video.stop() -> returns Blob
567
602
  ```
568
603
 
@@ -601,7 +636,7 @@ const meshes = useBodyMeshes(selectedBodyId);
601
636
  Convenience wrapper around `useBodyMeshes` that applies an emissive highlight:
602
637
 
603
638
  ```tsx
604
- useSelectionHighlight(selectedBodyId, { color: '#00ff00', emissiveIntensity: 0.5 });
639
+ useSelectionHighlight(selectedBodyId, { color: "#00ff00", emissiveIntensity: 0.5 });
605
640
  ```
606
641
 
607
642
  ### `useSceneLights(intensity?)`
@@ -732,6 +767,7 @@ Features planned but not yet implemented:
732
767
  | **Physics interpolation** | P1 | Smooth rendering between physics ticks for very high refresh displays |
733
768
  | **Instanced geom rendering** | P2 | `<InstancedGeomRenderer />` for particle/granular sims |
734
769
  | **Web Worker physics** | P2 | Run `mj_step` off main thread via SharedArrayBuffer |
770
+ | **Register codegen** | P2 | CLI to auto-generate `Register` type augmentation from MJCF XML |
735
771
 
736
772
  ### WASM Limitations (mujoco-js 0.0.7)
737
773
 
package/dist/index.d.ts CHANGED
@@ -9,6 +9,45 @@ import * as THREE from 'three';
9
9
  * SPDX-License-Identifier: Apache-2.0
10
10
  */
11
11
 
12
+ /**
13
+ * Module augmentation interface for type-safe resource names.
14
+ *
15
+ * Declare your model's resource names via module augmentation:
16
+ * ```ts
17
+ * declare module 'mujoco-react' {
18
+ * interface Register {
19
+ * actuators: 'joint1' | 'joint2' | 'gripper';
20
+ * sensors: 'force_sensor' | 'torque_sensor';
21
+ * bodies: 'link0' | 'link1' | 'hand';
22
+ * }
23
+ * }
24
+ * ```
25
+ *
26
+ * When no augmentation is declared, all names fall back to `string`.
27
+ */
28
+ interface Register {
29
+ }
30
+ type Actuators = Register extends {
31
+ actuators: infer T extends string;
32
+ } ? T : string;
33
+ type Sensors = Register extends {
34
+ sensors: infer T extends string;
35
+ } ? T : string;
36
+ type Bodies = Register extends {
37
+ bodies: infer T extends string;
38
+ } ? T : string;
39
+ type Joints = Register extends {
40
+ joints: infer T extends string;
41
+ } ? T : string;
42
+ type Sites = Register extends {
43
+ sites: infer T extends string;
44
+ } ? T : string;
45
+ type Geoms = Register extends {
46
+ geoms: infer T extends string;
47
+ } ? T : string;
48
+ type Keyframes = Register extends {
49
+ keyframes: infer T extends string;
50
+ } ? T : string;
12
51
  /**
13
52
  * A single MuJoCo contact from the WASM module.
14
53
  * Accessed via `data.contact.get(i)`.
@@ -245,7 +284,7 @@ interface SceneConfig {
245
284
  }
246
285
  interface IkConfig {
247
286
  /** MuJoCo site name for IK target. */
248
- siteName: string;
287
+ siteName: Sites;
249
288
  /** Number of joints to solve for. */
250
289
  numJoints: number;
251
290
  /** Custom IK solver. When omitted, uses built-in Damped Least-Squares solver. */
@@ -364,8 +403,9 @@ interface TrajectoryData {
364
403
  frames: TrajectoryFrame[];
365
404
  fps: number;
366
405
  }
406
+ type PlaybackState = 'idle' | 'playing' | 'paused' | 'completed';
367
407
  interface KeyBinding {
368
- actuator: string;
408
+ actuator: Actuators;
369
409
  delta?: number;
370
410
  toggle?: [number, number];
371
411
  set?: number;
@@ -402,20 +442,25 @@ interface SceneLightsProps {
402
442
  /** Override intensity for all MJCF lights. Default: 1.0. */
403
443
  intensity?: number;
404
444
  }
445
+ type TrajectoryInput = TrajectoryFrame[] | number[][];
405
446
  interface TrajectoryPlayerProps {
406
- trajectory: number[][];
447
+ trajectory: TrajectoryInput;
407
448
  fps?: number;
449
+ speed?: number;
408
450
  loop?: boolean;
409
451
  playing?: boolean;
452
+ mode?: 'kinematic' | 'physics';
410
453
  onFrame?: (frameIdx: number) => void;
454
+ onComplete?: () => void;
455
+ onStateChange?: (state: PlaybackState) => void;
411
456
  }
412
457
  interface ContactListenerProps {
413
- body: string;
458
+ body: Bodies;
414
459
  onContactEnter?: (info: ContactInfo) => void;
415
460
  onContactExit?: (info: ContactInfo) => void;
416
461
  }
417
462
  interface BodyProps {
418
- name: string;
463
+ name: Bodies;
419
464
  type: 'box' | 'sphere' | 'cylinder';
420
465
  size: [number, number, number];
421
466
  position?: [number, number, number];
@@ -438,20 +483,20 @@ interface MujocoSimAPI {
438
483
  step(n?: number): void;
439
484
  getTime(): number;
440
485
  getTimestep(): number;
441
- applyKeyframe(nameOrIndex: string | number): void;
486
+ applyKeyframe(nameOrIndex: Keyframes | number): void;
442
487
  saveState(): StateSnapshot;
443
488
  restoreState(snapshot: StateSnapshot): void;
444
489
  setQpos(values: Float64Array | number[]): void;
445
490
  setQvel(values: Float64Array | number[]): void;
446
491
  getQpos(): Float64Array;
447
492
  getQvel(): Float64Array;
448
- setCtrl(nameOrValues: string | Record<string, number>, value?: number): void;
493
+ setCtrl(nameOrValues: Actuators | Record<Actuators, number>, value?: number): void;
449
494
  getCtrl(): Float64Array;
450
- applyForce(bodyName: string, force: THREE.Vector3, point?: THREE.Vector3): void;
451
- applyTorque(bodyName: string, torque: THREE.Vector3): void;
452
- setExternalForce(bodyName: string, force: THREE.Vector3, torque: THREE.Vector3): void;
495
+ applyForce(bodyName: Bodies, force: THREE.Vector3, point?: THREE.Vector3): void;
496
+ applyTorque(bodyName: Bodies, torque: THREE.Vector3): void;
497
+ setExternalForce(bodyName: Bodies, force: THREE.Vector3, torque: THREE.Vector3): void;
453
498
  applyGeneralizedForce(values: Float64Array | number[]): void;
454
- getSensorData(name: string): Float64Array | null;
499
+ getSensorData(name: Sensors): Float64Array | null;
455
500
  getContacts(): ContactInfo[];
456
501
  getBodies(): BodyInfo[];
457
502
  getJoints(): JointInfo[];
@@ -472,9 +517,9 @@ interface MujocoSimAPI {
472
517
  bodyId: number;
473
518
  geomId: number;
474
519
  } | null;
475
- setBodyMass(name: string, mass: number): void;
476
- setGeomFriction(name: string, friction: [number, number, number]): void;
477
- setGeomSize(name: string, size: [number, number, number]): void;
520
+ setBodyMass(name: Bodies, mass: number): void;
521
+ setGeomFriction(name: Geoms, friction: [number, number, number]): void;
522
+ setGeomSize(name: Geoms, size: [number, number, number]): void;
478
523
  readonly mjModelRef: React__default.RefObject<MujocoModel | null>;
479
524
  readonly mjDataRef: React__default.RefObject<MujocoData | null>;
480
525
  }
@@ -499,10 +544,29 @@ interface MujocoContextValue {
499
544
  status: 'loading' | 'ready' | 'error';
500
545
  error: string | null;
501
546
  }
547
+ /** @deprecated Use `SensorHandle` instead. */
502
548
  interface SensorResult {
503
549
  value: React__default.RefObject<Float64Array>;
504
550
  size: number;
505
551
  }
552
+ interface CtrlHandle {
553
+ /** Read the current ctrl value. */
554
+ read(): number;
555
+ /** Write a ctrl value (goes directly to data.ctrl). */
556
+ write(value: number): void;
557
+ /** Actuator name. */
558
+ name: Actuators;
559
+ /** Actuator control range [min, max]. */
560
+ range: [number, number];
561
+ }
562
+ interface SensorHandle {
563
+ /** Read the current sensor data. */
564
+ read(): Float64Array;
565
+ /** Sensor dimensionality. */
566
+ dim: number;
567
+ /** Sensor name. */
568
+ name: Sensors;
569
+ }
506
570
  interface BodyStateResult {
507
571
  position: React__default.RefObject<THREE.Vector3>;
508
572
  quaternion: React__default.RefObject<THREE.Quaternion>;
@@ -840,7 +904,7 @@ declare function ContactListener({ body, onContactEnter, onContactExit, }: Conta
840
904
  * Component wrapper for useTrajectoryPlayer.
841
905
  * Provides declarative trajectory playback controlled via props.
842
906
  */
843
- declare function TrajectoryPlayer({ trajectory, fps, loop, playing, onFrame, }: TrajectoryPlayerProps): null;
907
+ declare function TrajectoryPlayer({ trajectory, fps, speed, loop, playing, mode, onFrame, onComplete, onStateChange, }: TrajectoryPlayerProps): null;
844
908
 
845
909
  /**
846
910
  * @license
@@ -862,7 +926,7 @@ declare function useActuators(): ActuatorInfo[];
862
926
  * Returns reactive refs for a MuJoCo site's world position and orientation.
863
927
  * Refs are updated every frame without triggering React re-renders.
864
928
  */
865
- declare function useSitePosition(siteName: string): SitePositionResult;
929
+ declare function useSitePosition(siteName: Sites): SitePositionResult;
866
930
 
867
931
  /**
868
932
  * @license
@@ -885,10 +949,11 @@ declare function useGravityCompensation(enabled?: boolean): void;
885
949
  */
886
950
 
887
951
  /**
888
- * Access a single MuJoCo sensor by name. Returns a ref-based value
889
- * updated every physics frame without causing React re-renders.
952
+ * Access a single MuJoCo sensor by name. Returns a `SensorHandle` with
953
+ * `read()`, `dim`, and `name`. The backing array is updated every physics
954
+ * frame without causing React re-renders.
890
955
  */
891
- declare function useSensor(name: string): SensorResult;
956
+ declare function useSensor(name: Sensors): SensorHandle;
892
957
  /**
893
958
  * Enumerate all sensors in the loaded MuJoCo model.
894
959
  * Returns a stable array recomputed only when the model changes.
@@ -910,7 +975,7 @@ declare function useSensors(): SensorInfo[];
910
975
  * For ball joints, position is quat (4), velocity is angular vel (3).
911
976
  * For free joints, position is pos+quat (7), velocity is lin+ang vel (6).
912
977
  */
913
- declare function useJointState(name: string): JointStateResult;
978
+ declare function useJointState(name: Joints): JointStateResult;
914
979
 
915
980
  /**
916
981
  * @license
@@ -923,22 +988,22 @@ declare function useJointState(name: string): JointStateResult;
923
988
  * Track a MuJoCo body's world position, quaternion, and velocities.
924
989
  * All values are ref-based — updated every physics frame without re-renders.
925
990
  */
926
- declare function useBodyState(name: string): BodyStateResult;
991
+ declare function useBodyState(name: Bodies): BodyStateResult;
927
992
 
928
993
  /**
929
994
  * @license
930
995
  * SPDX-License-Identifier: Apache-2.0
931
996
  *
932
- * useCtrl — clean read/write access to a named actuator's ctrl value (spec 3.1)
997
+ * useCtrl — handle-based read/write access to a named actuator's ctrl value (spec 3.1)
933
998
  */
999
+
934
1000
  /**
935
1001
  * Access a single actuator's control value by name.
936
1002
  *
937
- * Returns [currentValue, setValue]:
938
- * - `currentValue` is a ref updated every frame (no re-renders).
939
- * - `setValue` writes directly to `data.ctrl[actuatorId]`.
1003
+ * Returns a `CtrlHandle` with `read()` and `write()` methods that
1004
+ * operate directly on `data.ctrl` without causing React re-renders.
940
1005
  */
941
- declare function useCtrl(name: string): [React.RefObject<number>, (value: number) => void];
1006
+ declare function useCtrl(name: Actuators): CtrlHandle;
942
1007
 
943
1008
  /**
944
1009
  * @license
@@ -953,13 +1018,13 @@ declare function useCtrl(name: string): [React.RefObject<number>, (value: number
953
1018
  * Calls the callback every physics frame with current contact list.
954
1019
  * Reads `data.ncon` first to avoid allocating for zero contacts.
955
1020
  */
956
- declare function useContacts(bodyName?: string, callback?: (contacts: ContactInfo[]) => void): React.RefObject<ContactInfo[]>;
1021
+ declare function useContacts(bodyName?: Bodies, callback?: (contacts: ContactInfo[]) => void): React.RefObject<ContactInfo[]>;
957
1022
  /**
958
1023
  * Contact enter/exit events for a specific body (spec 2.5).
959
1024
  * Tracks which geom pairs are in contact frame-to-frame and fires
960
1025
  * onEnter/onExit callbacks on transitions.
961
1026
  */
962
- declare function useContactEvents(bodyName: string, handlers: {
1027
+ declare function useContactEvents(bodyName: Bodies, handlers: {
963
1028
  onEnter?: (info: ContactInfo) => void;
964
1029
  onExit?: (info: ContactInfo) => void;
965
1030
  }): void;
@@ -1011,24 +1076,38 @@ declare function usePolicy(config: PolicyConfig): {
1011
1076
  *
1012
1077
  * useTrajectoryPlayer — trajectory playback/scrubbing (spec 13.2)
1013
1078
  */
1079
+
1014
1080
  interface TrajectoryPlayerOptions {
1015
1081
  fps?: number;
1082
+ speed?: number;
1016
1083
  loop?: boolean;
1084
+ mode?: 'kinematic' | 'physics';
1085
+ onComplete?: () => void;
1086
+ onStateChange?: (state: PlaybackState) => void;
1017
1087
  }
1018
1088
  /**
1019
- * Play back a sequence of qpos frames, overriding simulation state.
1089
+ * Play back a trajectory, overriding simulation state.
1090
+ *
1091
+ * Accepts either `TrajectoryFrame[]` (from useTrajectoryRecorder) or
1092
+ * `number[][]` (raw qpos arrays).
1093
+ *
1094
+ * In `kinematic` mode (default), the simulation is paused and qpos is
1095
+ * set directly each frame with mj_forward for rendering.
1020
1096
  *
1021
- * When playing, the simulation is effectively paused and qpos is set
1022
- * from the trajectory each render frame at the specified FPS.
1097
+ * In `physics` mode, the simulation keeps running and ctrl values from
1098
+ * the trajectory are applied each physics step via useBeforePhysicsStep.
1023
1099
  */
1024
- declare function useTrajectoryPlayer(trajectory: number[][], options?: TrajectoryPlayerOptions): {
1100
+ declare function useTrajectoryPlayer(trajectory: TrajectoryInput, options?: TrajectoryPlayerOptions): {
1025
1101
  play: () => void;
1026
1102
  pause: () => void;
1027
1103
  seek: (frameIdx: number) => void;
1028
1104
  reset: () => void;
1105
+ setSpeed: (s: number) => void;
1106
+ readonly state: PlaybackState;
1029
1107
  readonly frame: number;
1030
1108
  readonly playing: boolean;
1031
1109
  readonly totalFrames: number;
1110
+ readonly progress: number;
1032
1111
  };
1033
1112
 
1034
1113
  /**
@@ -1195,4 +1274,4 @@ interface CameraAnimationAPI {
1195
1274
  */
1196
1275
  declare function useCameraAnimation(): CameraAnimationAPI;
1197
1276
 
1198
- export { type ActuatorInfo, Body, type BodyInfo, type BodyProps, type BodyStateResult, type CameraAnimationAPI, type ContactInfo, ContactListener, type ContactListenerProps, ContactMarkers, type ControllerComponent, type ControllerOptions, Debug, type DebugProps, DragInteraction, type DragInteractionProps, FlexRenderer, type GeomInfo, type IKSolveFn, type IkConfig, type IkContextValue, IkGizmo, type IkGizmoProps, type JointInfo, type JointStateResult, type KeyBinding, type KeyboardTeleopConfig, type ModelOptions, MujocoCanvas, type MujocoCanvasProps, type MujocoContact, type MujocoContactArray, type MujocoContextValue, type MujocoData, type MujocoModel, type MujocoModule, MujocoPhysics, type MujocoPhysicsProps, MujocoProvider, type MujocoSimAPI, MujocoSimProvider, type PhysicsConfig, type PhysicsStepCallback, type PolicyConfig, type RayHit, type SceneConfig, SceneLights, type SceneLightsProps, type SceneMarker, type SceneObject, type SensorInfo, type SensorResult, type SiteInfo, type SitePositionResult, type StateSnapshot, TendonRenderer, type TrajectoryData, type TrajectoryFrame, TrajectoryPlayer, type TrajectoryPlayerProps, type XmlPatch, createController, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getContact, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
1277
+ export { type ActuatorInfo, type Actuators, type Bodies, Body, type BodyInfo, type BodyProps, type BodyStateResult, type CameraAnimationAPI, type ContactInfo, ContactListener, type ContactListenerProps, ContactMarkers, type ControllerComponent, type ControllerOptions, type CtrlHandle, Debug, type DebugProps, DragInteraction, type DragInteractionProps, FlexRenderer, type GeomInfo, type Geoms, type IKSolveFn, type IkConfig, type IkContextValue, IkGizmo, type IkGizmoProps, type JointInfo, type JointStateResult, type Joints, type KeyBinding, type KeyboardTeleopConfig, type Keyframes, type ModelOptions, MujocoCanvas, type MujocoCanvasProps, type MujocoContact, type MujocoContactArray, type MujocoContextValue, type MujocoData, type MujocoModel, type MujocoModule, MujocoPhysics, type MujocoPhysicsProps, MujocoProvider, type MujocoSimAPI, MujocoSimProvider, type PhysicsConfig, type PhysicsStepCallback, type PlaybackState, type PolicyConfig, type RayHit, type Register, type SceneConfig, SceneLights, type SceneLightsProps, type SceneMarker, type SceneObject, type SensorHandle, type SensorInfo, type SensorResult, type Sensors, type SiteInfo, type SitePositionResult, type Sites, type StateSnapshot, TendonRenderer, type TrajectoryData, type TrajectoryFrame, type TrajectoryInput, TrajectoryPlayer, type TrajectoryPlayerProps, type XmlPatch, createController, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getContact, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };