diff --git a/.github/workflows/publish-wasm.yml b/.github/workflows/publish-wasm.yml
index 5ca7e78d..0e481ead 100644
--- a/.github/workflows/publish-wasm.yml
+++ b/.github/workflows/publish-wasm.yml
@@ -42,13 +42,9 @@ jobs:
- name: Prepare README for dist
run: |
- if [ -f wasm/README.npm.md ]; then
- mkdir -p wasm/dist
- cp wasm/README.npm.md wasm/dist/README.md
- echo "Copied wasm/README.npm.md -> wasm/dist/README.md"
- else
- echo "No wasm/README.npm.md found; skipping README copy"
- fi
+ mkdir -p wasm/dist
+ cp wasm/README.md wasm/dist/README.md
+
- name: Sync version with tag (dist)
run: |
diff --git a/wasm/README.md b/wasm/README.md
index cdda7b23..651deaf9 100644
--- a/wasm/README.md
+++ b/wasm/README.md
@@ -119,6 +119,192 @@ joint using `data.jnt('myjoint')`.
For more details and examples of how to use named access, please refer to the [named access tests](tests/bindings_test.ts#L1876-L2378) and [documentation](https://mujoco.readthedocs.io/en/stable/python.html#named-access).
+## Usage Guide
+When interacting with MuJoCo objects through the WASM bindings, it's important to understand how data is accessed. Properties on objects like `MjModel` and `MjData` can expose data in two ways: by copy or by reference.
+
+### Install
+```sh
+npm install mujoco
+```
+
+```ts
+import loadMujoco from 'mujoco';
+
+const mujoco = await loadMujoco();
+
+const model = mujoco.MjModel.fromXMLString(`
+
+
+
+
+
+`);
+
+const data = new mujoco.MjData(model);
+mujoco.mj_step(model, data);
+```
+
+### Copy vs. Reference
+
+#### 1. By Copy (Value-based access)
+
+Some properties return a copy of the data at the time of access. This is common for complex data structures that need to be marshalled from C++ to JavaScript.
+
+A key example is `MjData.contact`. When you access `data.contact`, you get a new array containing the contacts at that specific moment in the simulation. If you step the simulation forward, this array will not be updated. You must access `data.contact` again to get the new contact information.
+
+Example:
+```typescript
+// Gets contacts at the current time.
+const contacts = data.contact;
+
+// Step the simulation
+mujoco.mj_step(model, data);
+
+// `contacts` is now stale. To get the new contacts, you must access the property again:
+const newContacts = data.contact;
+```
+
+#### 2. By Reference (View-based access)
+Many properties, especially large numerical arrays, return a live view directly into the WebAssembly memory. This is highly efficient as it avoids copying large amounts of data.
+
+A key example is `MjData.qpos` (joint positions). When you get a reference to this array, it points directly to the simulation's state data. Any changes in the simulation (e.g., after a call to `mj_step`) will be immediately reflected in this array.
+
+```typescript
+// `qpos` is a live view into the simulation state.
+const qpos = data.qpos;
+
+console.log(qpos[0]); // Print initial position
+
+// Step the simulation
+mujoco.mj_step(model, data);
+
+// `qpos` is automatically updated.
+console.log(qpos[0]); // Print new position
+```
+
+### Data Layout: Row-Major Matrices
+When a function from the MuJoCo C API returns a matrix (or needs a matrix as input), these are represented in the JavaScript bindings as flat, one-dimensional `TypedArray`'s. The elements are stored in row-major order.
+
+For example, a 3x10 matrix will be returned as a flat array with 30 elements. The first 10 elements represent the first row, the next 10 represent the second row, and so on.
+
+Example: Accessing an element at `(row, col)`
+```typescript
+// A 3x10 matrix stored as a flat array.
+const matrix: Float64Array = ...;
+const nRows = 3;
+const nCols = 10;
+
+// To access the element at row `i` and column `j`:
+const element = matrix[i * nCols + j];
+```
+
+### Working with Out Parameters
+Many functions in the MuJoCo C API use "out parameters" to return data. This means instead of returning a value, they write the result into one of the arguments passed to them by reference (using pointers). In our JavaScript bindings, you'll need to handle these cases specifically.
+
+There are two main scenarios you'll encounter:
+
+#### 1. Array-like Out Parameters
+When a function expects a pointer to a primitive type (like `mjtNum*` or `int*`) to write an array of values, you need to pre-allocate memory for the result on the JavaScript side. We provide helper classes for this: `mujoco.Uint8Buffer`, `mujoco.DoubleBuffer`, `mujoco.FloatBuffer`, and `mujoco.IntBuffer`.
+
+Here's how to use them:
+
+1. Create a buffer: Instantiate the appropriate buffer class with an initial array of the correct size (e.g., an array of zeros).
+2. Call the function: Pass the buffer instance to the function as the out parameter.
+3. Access the result: Use the `.getView()` method on the buffer to get a `TypedArray` view of the data written by the C++ function.
+4. Free the memory: When you are done with the buffer, you must call the `.delete()` method to free the underlying memory and prevent memory leaks.
+
+Example: Rotating a vector
+
+The function `mju_rotVecQuat` rotates a vector `vec` by a quaternion `quat` and stores the result in the `res` out parameter.
+
+```typescript
+// Create a buffer to hold the 3D vector result.
+const res = new mujoco.DoubleBuffer([0, 0, 0]);
+
+const vec = [1, 0, 0];
+const quat = [0.707, 0, 0, 0.707]; // 90-degree rotation around z-axis
+
+try {
+ // Call the function with the buffer as the out parameter.
+ mujoco.mju_rotVecQuat(res, vec, quat);
+
+ // Get the result as a Float64Array.
+ const resultView = res.getView();
+ console.log(resultView); // Expected: approximately [0, 1, 0]
+
+} finally {
+ // IMPORTANT: Free the memory allocated for the buffer.
+ res.delete();
+}
+```
+
+#### 2. Struct Out Parameters (e.g., mjvCamera*, mjvScene*)
+When a function modifies a struct passed by pointer, you should pass an instance of the corresponding JavaScript wrapper class. The underlying C++ struct will be modified in place.
+
+Example: Updating a scene
+
+The function `mjv_updateScene` populates an `mjvScene` object with information from `mjModel` and `mjData`.
+```typescript
+// Create instances of the necessary structs.
+const model = mujoco.MjModel.loadFromXML(xmlContent);
+const data = new mujoco.MjData(model);
+const scene = new mujoco.MjvScene(model, 1000);
+const option = new mujoco.MjvOption();
+const perturb = new mujoco.MjvPerturb();
+const camera = new mujoco.MjvCamera();
+
+// ... (step simulation, etc.)
+
+// Update the scene. The 'scene' object is modified by the function.
+mujoco.mjv_updateScene(
+ model,
+ data,
+ option,
+ perturb,
+ camera,
+ mujoco.mjtCatBit.mjCAT_ALL.value,
+ scene
+);
+
+console.log('Number of geoms in scene:', scene.ngeom);
+
+// Remember to delete all created objects when they are no longer needed.
+scene.delete();
+camera.delete();
+perturb.delete();
+option.delete();
+data.delete();
+model.delete();
+```
+
+As with buffers, you are responsible for managing the memory of these struct instances and must call `.delete()` on them when you are finished.
+
+### Enums
+Access via `.value`:
+```javascript
+mujoco.mjtDisableBit.mjDSBL_CLAMPCTRL.value
+```
+
+### Constants
+Scalar constants will be accessed the same way they are on python, simply:
+
+```javascript
+mujoco.mjNEQDATA
+```
+
+Due to Embind limitations, more complex constants that are not scalar, but are represented in more dimensions are exposed as functions. E.g to use `mujoco.mjFRAMESTRING` you will need to call a function:
+
+```javascript
+mujoco.get_mjFRAMESTRING()
+```
+
+This will return a javascript array representation of the values in MuJoCo `mjFRAMESTRING`.
+
+### About assets
+The package is ESM (`type: module`) and ships TypeScript types.
+
+Ensure your bundler or dev server serves the `.wasm` asset at runtime.
+
## Development
In order to change the bindings you will need to change the [`bindings.cc`](codegen/generated/bindings.cc)
@@ -183,6 +369,14 @@ method to do this only works internally at Google, but it should be possible to
replicate the experience with open-source tooling — community suggestions are
welcome!
+## Versioning
+Package versions follow the official MuJoCo release versions.
+For example:
+
+| npm version | MuJoCo version |
+|-------------|----------------|
+| 3.5.0 | 3.5.0 |
+
## Future Work
1. **Bind all useful APIs.**
diff --git a/wasm/README.npm.md b/wasm/README.npm.md
deleted file mode 100644
index ce9bb70c..00000000
--- a/wasm/README.npm.md
+++ /dev/null
@@ -1,208 +0,0 @@
-# MuJoCo WASM bindings
-
-Official WebAssembly (WASM) bindings for the MuJoCo physics engine, compiled from the MuJoCo C/C++ sources into WASM with JS glue (Emscripten + Embind). The package ships prebuilt ESM-ready JS/WASM artifacts and TypeScript types for immediate use.
-
-**Important**:_These bindings are still a WIP_.
-
-## Install
-```sh
-npm install mujoco
-```
-
-## Quick start (ESM / TypeScript)
-
-```ts
-import loadMujoco from 'mujoco';
-
-const mujoco = await loadMujoco();
-
-const model = mujoco.MjModel.fromXMLString(`
-
-
-
-
-
-`);
-
-const data = new mujoco.MjData(model);
-mujoco.mj_step(model, data);
-```
-
-## Usage Guide
-When interacting with MuJoCo objects through the WASM bindings, it's important to understand how data is accessed. Properties on objects like `MjModel` and `MjData` can expose data in two ways: by copy or by reference.
-
-### Copy vs. Reference
-
-#### 1. By Copy (Value-based access)
-
-Some properties return a copy of the data at the time of access. This is common for complex data structures that need to be marshalled from C++ to JavaScript.
-
-A key example is `MjData.contact`. When you access `data.contact`, you get a new array containing the contacts at that specific moment in the simulation. If you step the simulation forward, this array will not be updated. You must access `data.contact` again to get the new contact information.
-
-Example:
-```typescript
-// Gets contacts at the current time.
-const contacts = data.contact;
-
-// Step the simulation
-mujoco.mj_step(model, data);
-
-// `contacts` is now stale. To get the new contacts, you must access the property again:
-const newContacts = data.contact;
-```
-
-#### 2. By Reference (View-based access)
-Many properties, especially large numerical arrays, return a live view directly into the WebAssembly memory. This is highly efficient as it avoids copying large amounts of data.
-
-A key example is `MjData.qpos` (joint positions). When you get a reference to this array, it points directly to the simulation's state data. Any changes in the simulation (e.g., after a call to `mj_step`) will be immediately reflected in this array.
-
-```typescript
-// `qpos` is a live view into the simulation state.
-const qpos = data.qpos;
-
-console.log(qpos[0]); // Print initial position
-
-// Step the simulation
-mujoco.mj_step(model, data);
-
-// `qpos` is automatically updated.
-console.log(qpos[0]); // Print new position
-```
-
-### Data Layout: Row-Major Matrices
-When a function from the MuJoCo C API returns a matrix (or needs a matrix as input), these are represented in the JavaScript bindings as flat, one-dimensional `TypedArray`'s. The elements are stored in row-major order.
-
-For example, a 3x10 matrix will be returned as a flat array with 30 elements. The first 10 elements represent the first row, the next 10 represent the second row, and so on.
-
-Example: Accessing an element at `(row, col)`
-```typescript
-// A 3x10 matrix stored as a flat array.
-const matrix: Float64Array = ...;
-const nRows = 3;
-const nCols = 10;
-
-// To access the element at row `i` and column `j`:
-const element = matrix[i * nCols + j];
-```
-
-### Working with Out Parameters
-Many functions in the MuJoCo C API use "out parameters" to return data. This means instead of returning a value, they write the result into one of the arguments passed to them by reference (using pointers). In our JavaScript bindings, you'll need to handle these cases specifically.
-
-There are two main scenarios you'll encounter:
-
-#### 1. Array-like Out Parameters
-When a function expects a pointer to a primitive type (like `mjtNum*` or `int*`) to write an array of values, you need to pre-allocate memory for the result on the JavaScript side. We provide helper classes for this: `mujoco.Uint8Buffer`, `mujoco.DoubleBuffer`, `mujoco.FloatBuffer`, and `mujoco.IntBuffer`.
-
-Here's how to use them:
-
-1. Create a buffer: Instantiate the appropriate buffer class with an initial array of the correct size (e.g., an array of zeros).
-2. Call the function: Pass the buffer instance to the function as the out parameter.
-3. Access the result: Use the `.getView()` method on the buffer to get a `TypedArray` view of the data written by the C++ function.
-4. Free the memory: When you are done with the buffer, you must call the `.delete()` method to free the underlying memory and prevent memory leaks.
-
-Example: Rotating a vector
-
-The function `mju_rotVecQuat` rotates a vector `vec` by a quaternion `quat` and stores the result in the `res` out parameter.
-
-```typescript
-// Create a buffer to hold the 3D vector result.
-const res = new mujoco.DoubleBuffer([0, 0, 0]);
-
-const vec = [1, 0, 0];
-const quat = [0.707, 0, 0, 0.707]; // 90-degree rotation around z-axis
-
-try {
- // Call the function with the buffer as the out parameter.
- mujoco.mju_rotVecQuat(res, vec, quat);
-
- // Get the result as a Float64Array.
- const resultView = res.getView();
- console.log(resultView); // Expected: approximately [0, 1, 0]
-
-} finally {
- // IMPORTANT: Free the memory allocated for the buffer.
- res.delete();
-}
-```
-
-#### 2. Struct Out Parameters (e.g., mjvCamera*, mjvScene*)
-When a function modifies a struct passed by pointer, you should pass an instance of the corresponding JavaScript wrapper class. The underlying C++ struct will be modified in place.
-
-Example: Updating a scene
-
-The function `mjv_updateScene` populates an `mjvScene` object with information from `mjModel` and `mjData`.
-```typescript
-// Create instances of the necessary structs.
-const model = mujoco.MjModel.loadFromXML(xmlContent);
-const data = new mujoco.MjData(model);
-const scene = new mujoco.MjvScene(model, 1000);
-const option = new mujoco.MjvOption();
-const perturb = new mujoco.MjvPerturb();
-const camera = new mujoco.MjvCamera();
-
-// ... (step simulation, etc.)
-
-// Update the scene. The 'scene' object is modified by the function.
-mujoco.mjv_updateScene(
- model,
- data,
- option,
- perturb,
- camera,
- mujoco.mjtCatBit.mjCAT_ALL.value,
- scene
-);
-
-console.log('Number of geoms in scene:', scene.ngeom);
-
-// Remember to delete all created objects when they are no longer needed.
-scene.delete();
-camera.delete();
-perturb.delete();
-option.delete();
-data.delete();
-model.delete();
-```
-
-As with buffers, you are responsible for managing the memory of these struct instances and must call `.delete()` on them when you are finished.
-
-### Enums
-Access via `.value`:
-```javascript
-mujoco.mjtDisableBit.mjDSBL_CLAMPCTRL.value
-```
-
-### Constants
-Scalar constants will be accessed the same way they are on python, simply:
-
-```javascript
-mujoco.mjNEQDATA
-```
-
-Due to Embind limitations, more complex constants that are not scalar, but are represented in more dimensions are exposed as functions. E.g to use `mujoco.mjFRAMESTRING` you will need to call a function:
-
-```javascript
-mujoco.get_mjFRAMESTRING()
-```
-
-This will return a javascript array representation of the values in MuJoCo `mjFRAMESTRING`.
-
-## Notes
-The package is ESM (`type: module`) and ships TypeScript types.
-
-Ensure your bundler or dev server serves the `.wasm` asset at runtime.
-
-### Development
-For detailed build instructions see the repository [README](https://github.com/google-deepmind/mujoco/tree/main/wasm#mujoco-javascript-bindings) (Emscripten toolchain, Embind bindings, producing the .wasm artifact, and targets for Node and browser).
-
-## Versioning
-Package versions follow the official MuJoCo release versions.
-For example:
-
-| npm version | MuJoCo version |
-|-------------|----------------|
-| 3.5.0 | 3.5.0 |
-
----
-
-For full engine documentation, see: https://mujoco.readthedocs.io