> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alakazam.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# Simulation & data generation

> Drive worlds headlessly as training environments and generate labeled robotics datasets — proximity sensors, collision spikes, camera frames.

<Note>
  **Beta — hosted gym.** The `/v1/sim/*` endpoints are live at
  `https://gym.alakazam.gg`, authenticated with a bearer token we issue you
  (`Authorization: Bearer <token>`). Every session holds one live world-model
  GPU stream, so the runner caps concurrent sessions and returns `429` when
  full. You can also run the gym yourself from the repo (`gym-server.mjs`) if
  you prefer local.
</Note>

Any playable world doubles as a **training environment**: your controller reads
an observation, sends a command, and gets the next observation back — one HTTP
call per control tick. The observation contract is built for embodied learning
(originally for spiking neural networks driving an e-puck-style robot):

```json theme={null}
{
  "proximity": { "left": 0.32, "right": 0.76 },
  "collision": false,
  "collision_count": 1,
  "done": false,
  "t_ms": 41230,
  "sensor_age_ms": 770,
  "applied_action": "left",
  "labels": [{ "id": "obj_12", "type": "wall", "confidence": 1 }],
  "camera": { "image": "data:image/jpeg;base64,..." }
}
```

* **`proximity`** — two virtual range sensors (like the e-puck's IR pair).
  `0` = clear, `1.0` = at contact range. Objects left of the robot's centre
  excite the left sensor, right excites right; something dead ahead (a wall)
  excites both. Compact obstacles grade by screen-space closeness, walls by
  time-to-contact.
* **`collision`** — a **spike**: `true` exactly once per confirmed collision,
  and guaranteed to read `1.0` proximity on the colliding side. The monotonic
  `collision_count` means a spike can never be missed between reads.
* **`sensor_age_ms`** — how far the sensors lag the camera frame in the same
  observation (cloud-detector worlds ≈ 0.5–1 s, local-detector worlds tens of
  ms). Compensate or discount accordingly.
* **`camera`** — optional square RGB frame (64/128/256), requested per session.

## The control loop

```python theme={null}
import requests

GYM = "https://gym.alakazam.gg"   # bearer token issued to you

H = {"Authorization": "Bearer <your-token>"}

sid = requests.post(f"{GYM}/v1/sim/sessions", headers=H, json={
    "world": "epuck",
    "camera": 128,
    "terminal_collision": False,   # collisions = spikes, run continues
}).json()["session_id"]

obs = requests.get(f"{GYM}/v1/sim/sessions/{sid}/obs", headers=H).json()
while not obs["done"]:
    v_l, v_r = my_controller.step(obs["proximity"], obs.get("camera"))
    obs = requests.post(f"{GYM}/v1/sim/sessions/{sid}/step", headers=H, json={
        "wheels": {"left": v_l, "right": v_r},   # or {"action": "left"}
        "holdMs": 300,
    }).json()

requests.post(f"{GYM}/v1/sim/sessions/{sid}/reset", headers=H)   # next episode (new seed)
```

Commands take either form: a discrete `action`
(`left | right | forward | none`) or your controller's native **differential
wheels** — quantized onto the world's drive (right wheel faster → veers left,
near-equal → forward, near-zero → idle) and echoed back as `applied_action`.

With `terminal_collision: false` (the default) a bump emits its spike, the
collision engine re-arms, and the run keeps going — punishment signals without
episode churn. Pass `true` for game semantics where the first hit latches
`done` until you `reset`.

## Generating a dataset

Your loop **is** the dataset generator: append each `step` response as a JSON
line and you have on-policy training data with your own actions embedded. Each
`reset` is a fresh world boot — a new stochastic seed — so episodes are
independent.

Don't have a controller yet? The bundled batch generator drives a reactive
avoidance policy (the same shape a trained SNN produces) across worlds and
episodes, then converts to train-ready arrays:

```bash theme={null}
node scripts/robot-gauntlet/generate_snn_dataset.mjs \
  --worlds epuck,robot --episodes 10 --seconds 30 --camera 64

python scripts/robot-gauntlet/snn_jsonl_to_npz.py \
  datasets/robot_gauntlet/snn/dataset_<stamp>.jsonl --camera
```

The JSONL is the canonical interchange (one step per line:
`world / episode / seed / action / obs`). The NPZ companion is the load-once
training format:

| array                           | dtype / shape        | notes                             |
| ------------------------------- | -------------------- | --------------------------------- |
| `proximity`                     | `float32 [T, 2]`     | left, right                       |
| `action`                        | `int8 [T]`           | 0 none · 1 fwd · 2 left · 3 right |
| `collision`                     | `uint8 [T]`          | the spike channel                 |
| `t_ms`, `sensor_age_ms`         | `int32 [T]`          | `-1` = unknown age                |
| `episode_starts`                | `int64 [E]`          | row index per episode             |
| `episode_world`, `episode_seed` | `str [E]`            |                                   |
| `camera`                        | `uint8 [T, N, N, 3]` | optional                          |

## Credits & rate limits

Simulation spends the same — and only — billed unit as every other live
surface: [`session_seconds`](/pricing). A gym session holds a live world-model
stream, so what you consume is **stream time, not API calls**:

* **Cost of an episode** ≈ `steps × holdMs` of live seconds (plus \~30–60 s of
  world boot per session). Example: 100 steps at 600 ms = 60 s — the free
  developer tier's 7,200 `session_seconds`/day covers about 120 such episodes.
* **Steps, observations, resets and dataset files are free.** `reset` ends the
  old stream before booting the new one, so seconds never double-count.
* **No per-minute rate limit applies to the control loop.** Like the other
  runtime routes, `step`/`obs` sit outside the per-key 60-second limiter (see
  [rate limits](/rate-limits)); they're bounded by your daily quota, the
  live-session **concurrency cap** (admission queues FIFO when capacity is
  full), and the `holdMs` floor of 60 ms (≈16 steps/s per session).
* Exhausting the daily quota returns `402` at session creation, like any other
  session mint.

<Note>
  In the current self-hosted beta none of this is enforced — the runner binds
  `127.0.0.1` with no key, and you consume your own account's stream time
  directly. The hosted rollout meters sim sessions through the same admission
  path as runtime sessions.
</Note>

## Budgeting

* One session = one live GPU stream. Generate sequentially, or cap parallel
  sessions to your quota.
* `reset` reuses the gym session — it is the cheap way to get new episode
  seeds.
* Delete sessions when done; abandoned ones fall to the world's idle governor.

Full endpoint schemas live in the [API reference](/api-reference) under
**Simulation & Data**.
