# Simulation Data

Simulation data lets you test the localization pipeline without being physically present at the mapped site. Each simulation data record is a zip file containing pre-recorded camera frames and metadata that can be replayed against the localization APIs.

A typical workflow is:

1. **Record** simulation data at the site.
2. **Upload** the resulting zip via `POST /v1/simulation-data`, the response gives you a unique `simulationCode`.
3. **Reference** the `simulationCode` when replaying the captured frames against your maps.
4. **Update, download, or delete** the simulation as your test set evolves.

{% hint style="info" %}
Max upload size is **10 MB** and the file must be a `.zip`. The uploaded part's `Content-Type` must be `application/zip` or `application/x-zip-compressed`, any other value (including the `application/octet-stream` default that some HTTP clients send) is rejected with `Only zip files are allowed`. The returned `simulationCode` is an 8-character alphanumeric identifier, use it in all subsequent calls.
{% endhint %}

{% hint style="warning" %}
**Setting the part's Content-Type from curl:** append `;type=application/zip` to the `-F` value, for example `-F 'file=@capture.zip;type=application/zip'`. Browsers and SDKs normally set this header from the file's extension or detected type, so no extra work is needed there.
{% endhint %}

### Zip File Format

The uploaded `.zip` represents a single capture session, a small set of AR camera frames plus the device pose and camera intrinsics at the moment each frame was captured. Replaying this archive against a map reproduces a real-world localization session offline.

#### What a capture contains

Each capture session contains **exactly one** dataset:

1. **A fixed number of camera frames** (4–6, default 5) stored as JPEGs.
2. **The 6-DoF camera pose** at the instant each frame was acquired, `(x, y, z)` position and `(qx, qy, qz, qw)` quaternion rotation, in a **left-handed (LHS) world coordinate system** (Unity convention). See [Coordinate system](#coordinate-system) below if your capture pipeline is right-handed.
3. **The camera intrinsics** for the session, focal length `(fx, fy)`, principal point `(px, py)`, and the image dimensions `(width, height)`. Captured once and assumed constant for the rest of the session.

#### Coordinate system

All poses in the manifest must be expressed in a **left-handed coordinate system (LHS)**, the same convention the MultiSet Unity SDK and the localization replay pipeline use. Uploading right-handed (RHS) poses without converting them first will produce poses that look mirrored along the X axis when replayed, and localization will not match the original capture.

If your capture pipeline produces RHS poses (for example, native iOS / Android, ARKit / ARCore raw transforms, or any OpenGL-style stack), convert each pose to LHS before writing it into the manifest. The full conversion is: **negate `position.x`, negate `rotation.qy` and `rotation.qz`, keep the rest unchanged**.

```javascript
/**
 * Convert a 6-DoF pose from a right-handed coordinate system (RHS) to the
 * left-handed coordinate system (LHS) expected by the simulation data
 * manifest. Apply this to every per-frame pose before writing the JSON.
 */
function flipPoseHandedness(pose) {
    return {
        position: {
            x: -pose.position.x,
            y:  pose.position.y,
            z:  pose.position.z,
        },
        rotation: {
            qx:  pose.rotation.qx,
            qy: -pose.rotation.qy,
            qz: -pose.rotation.qz,
            qw:  pose.rotation.qw,
        },
    };
}
```

{% hint style="info" %}
Captures produced by the MultiSet Unity SDK are already LHS, no conversion is needed.
{% endhint %}

#### Zip layout

Entries inside the zip are **flat**, there is no top-level folder:

```
SimulationData.zip
├── Image_0_<yyyyMMdd_HHmmss>.jpg
├── Image_1_<yyyyMMdd_HHmmss>.jpg
├── Image_2_<yyyyMMdd_HHmmss>.jpg
├── Image_3_<yyyyMMdd_HHmmss>.jpg
├── Image_4_<yyyyMMdd_HHmmss>.jpg
└── SimulationData_<yyyyMMdd_HHmmss>.json
```

* Image filenames follow `Image_<index>_<timestamp>.jpg`, where `<index>` is the zero-based capture order. Images must appear in the **same order** as the entries in the JSON manifest's `imageDataList` so they can be paired positionally.
* The timestamp suffix `<yyyyMMdd_HHmmss>` is identical for every file in the session.
* JPEGs should be encoded at quality \~80.

#### `SimulationData_<timestamp>.json` manifest

The single JSON manifest at the root of the zip describes the intrinsics and the per-frame pose, in capture order:

| Field                                     | Type  | Notes                                                               |
| ----------------------------------------- | ----- | ------------------------------------------------------------------- |
| `width`, `height`                         | int   | Working image resolution in pixels (must match the JPEGs).          |
| `fx`, `fy`                                | float | Camera focal length in pixels, scaled to match `width`/`height`.    |
| `px`, `py`                                | float | Camera principal point in pixels, scaled to match `width`/`height`. |
| `imageDataList`                           | array | One entry per image. Same order as `Image_0`, `Image_1`, …          |
| `imageDataList[].x`, `.y`, `.z`           | float | Camera position at frame acquisition.                               |
| `imageDataList[].qx`, `.qy`, `.qz`, `.qw` | float | Camera rotation as a quaternion.                                    |

Example:

```json
{
    "width": 720,
    "height": 960,
    "px": 361.3074645996094,
    "py": 481.46038818359377,
    "fx": 665.6408081054688,
    "fy": 665.6408081054688,
    "imageDataList": [
        {
            "x": 0.31831833720207217,
            "y": 1.156510353088379,
            "z": 1.3285411596298218,
            "qx": -0.009507684037089348,
            "qy": 0.30342480540275576,
            "qz": -0.00325992563739419,
            "qw": -0.9528024196624756
        },
        {
            "x": 0.3255421817302704,
            "y": 1.154714822769165,
            "z": 1.3292453289031983,
            "qx": -0.0043470230884850029,
            "qy": 0.27293628454208376,
            "qz": -0.006081813480705023,
            "qw": -0.9620030522346497
        },
        {
            "x": 0.3384963274002075,
            "y": 1.156157374382019,
            "z": 1.3352329730987549,
            "qx": -0.0042652105912566189,
            "qy": 0.18202008306980134,
            "qz": -0.007582413498312235,
            "qw": -0.9832563400268555
        },
        {
            "x": 0.37630680203437807,
            "y": 1.1523985862731934,
            "z": 1.3415110111236573,
            "qx": -0.0032018644269555809,
            "qy": 0.0914652869105339,
            "qz": -0.0009664428071118891,
            "qw": -0.995802640914917
        },
        {
            "x": 0.42654383182525637,
            "y": 1.1490235328674317,
            "z": 1.3500515222549439,
            "qx": -0.0047492762096226219,
            "qy": 0.0032952935434877874,
            "qz": -0.0013450992992147804,
            "qw": -0.9999824166297913
        }
    ]
}
```

{% hint style="warning" %}
`imageDataList.length` must equal the number of `Image_<n>_*.jpg` files in the zip, and each index `n` must have a corresponding entry at position `n` in the list. Mismatched counts or out-of-order indices cause the simulation to be rejected at replay time.
{% endhint %}

### Upload Simulation Data

Upload a new simulation data zip. Returns a `simulationCode` you use to reference it later.

{% openapi src="/files/Z73rNpLR5ivcc3uKTeTT" path="/simulation-data" method="post" %}
[simulation-data.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-ea098d99dde3d64ccce074808a7f3684565631b2%2Fsimulation-data.yaml?alt=media)
{% endopenapi %}

### List Simulation Data

Paginated list of simulation data for the authenticated account. Supports search by name and filtering by status.

{% openapi src="/files/Z73rNpLR5ivcc3uKTeTT" path="/simulation-data" method="get" %}
[simulation-data.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-ea098d99dde3d64ccce074808a7f3684565631b2%2Fsimulation-data.yaml?alt=media)
{% endopenapi %}

### Get Simulation Data by Code

Retrieve details (name, description, status, file size, timestamps) for a single simulation data record.

{% openapi src="/files/Z73rNpLR5ivcc3uKTeTT" path="/simulation-data/{simulationCode}" method="get" %}
[simulation-data.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-ea098d99dde3d64ccce074808a7f3684565631b2%2Fsimulation-data.yaml?alt=media)
{% endopenapi %}

### Update Simulation Data

Update the `name` and/or `description` of an existing simulation data record. At least one of the two fields must be supplied.

{% hint style="warning" %}
This endpoint updates **metadata only**. To replace the underlying zip file, delete the simulation and upload a new one.
{% endhint %}

{% openapi src="/files/Z73rNpLR5ivcc3uKTeTT" path="/simulation-data/{simulationCode}" method="put" %}
[simulation-data.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-ea098d99dde3d64ccce074808a7f3684565631b2%2Fsimulation-data.yaml?alt=media)
{% endopenapi %}

### Delete Simulation Data

Removes the simulation data record and deletes the underlying zip file from storage. This action is irreversible.

{% openapi src="/files/Z73rNpLR5ivcc3uKTeTT" path="/simulation-data/{simulationCode}" method="delete" %}
[simulation-data.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-ea098d99dde3d64ccce074808a7f3684565631b2%2Fsimulation-data.yaml?alt=media)
{% endopenapi %}

### Download Simulation Data

Generates a short-lived pre-signed URL for downloading the zip. The response includes the URL, the original filename, and the file size.

{% openapi src="/files/Z73rNpLR5ivcc3uKTeTT" path="/simulation-data/{simulationCode}/download" method="get" %}
[simulation-data.yaml](https://3163433004-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FokTDI7QVY04Zvb1pQ8Ry%2Fuploads%2Fgit-blob-ea098d99dde3d64ccce074808a7f3684565631b2%2Fsimulation-data.yaml?alt=media)
{% endopenapi %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.multiset.ai/basics/rest-api-docs/simulation-data.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
