> ## 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.

# Train an SNN, step by step

> The end-to-end path for training a spiking controller against a live world model.

This is the complete loop for training a spiking neural network controller
against a live world model through the simulation gym, from first session to
a certified result. The observation contract was co-designed for SNNs: sensor
channels are bounded scalars that map naturally onto input spike rates, and
collisions arrive as discrete spikes, not dense reward.

## 0. What you need

* A Train bearer key (provisioned per partner).
* The gym endpoint URL for your key.
* Optional, for offline pre-training: the recorded episode dataset (JSONL,
  same observation schema; controllers trained on recordings plug straight
  into the live loop).

## 1. Create a gym session

```bash theme={null}
curl -s -X POST $RUNNER_URL/v1/sim/sessions \
  -H "Authorization: Bearer $TRAIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"world": "epuck", "camera": 0, "terminal_collision": false}'
# → {"session_id": "…", "world": "epuck", "camera": 0}
```

* `world`: `robot` or `epuck` (e-puck front-sensor-pair convention matches the
  proximity contract).
* `camera: 0` if your SNN only consumes proximity; observations get smaller
  and faster.
* `terminal_collision: false` is training semantics: collisions are countable
  spikes and the run continues (the contact engine re-arms after a cooldown).
  Set `true` for game-style terminal episodes.

<Warning>
  Each session holds a real world-model GPU stream. Create only as many
  parallel environments as your session budget allows, and `DELETE` every
  session when you are done; an orphaned session keeps billing wall-time.
</Warning>

## 2. The control loop

One `step` per control tick; the response *is* the post-action observation.

```python theme={null}
import requests

S = requests.Session()
S.headers["Authorization"] = f"Bearer {KEY}"

sid = S.post(f"{URL}/v1/sim/sessions",
             json={"world": "epuck", "camera": 0}).json()["session_id"]

obs = S.get(f"{URL}/v1/sim/sessions/{sid}/obs").json()
while True:
    # 1) encode: bounded scalars -> input spike rates
    left_rate  = obs["proximity"]["left"]    # 0 = clear … 1 = contact range
    right_rate = obs["proximity"]["right"]
    pain_spike = obs["collision"]            # True exactly once per hit

    # 2) run your SNN for the tick window; decode motor populations
    wheels = my_snn.tick(left_rate, right_rate, pain_spike)   # e.g. {"left": 0.8, "right": 0.3}

    # 3) act, SNN-native differential drive (or discrete `action`)
    obs = S.post(f"{URL}/v1/sim/sessions/{sid}/step",
                 json={"wheels": wheels, "holdMs": 300}).json()

    if obs["done"]:
        obs = S.post(f"{URL}/v1/sim/sessions/{sid}/reset").json()
```

Contract details that matter for spiking controllers:

* `wheels` is the SNN-native command: differential velocities from your motor
  populations, quantized server-side onto the world's drive (the response
  echoes `applied_action`). Discrete `action` also works.
* `collision` is edge-triggered: true exactly once per hit, derived from a
  monotonic counter, so a spike between two reads is never missed. Use it as
  your punishment or terminal signal; `collision_count` is the running total.
* `holdMs` (60–2000, default 300) is your control tick: how long the command
  holds before the observation is read. Match it to your SNN's simulation
  window.
* `sensor_age_ms` reports how stale proximity/labels are relative to the
  camera frame. Cloud-detector worlds run ≈500–1000 ms behind, local-detector
  worlds tens of ms; discount or compensate in your dynamics.
* Reward is yours to shape client-side. The observation gives you survival
  time (`t_ms`), collision spikes, and proximity margins.

## 3. Pre-train offline (optional but recommended)

The dataset exporter emits recorded episodes as JSONL in exactly the same
observation schema, so you can warm up your encoder and controller on
recordings (imitation, STDP pre-exposure, calibration of input scaling)
before spending live GPU sessions. Ask us for the current vetted dataset
release with your key.

## 4. Scale up

* Sessions on one runner are serialized; parallel training uses several
  runner instances. Coordinate the parallel-session count with us; it maps
  1:1 onto GPU streams (your budget).
* Keep episodes honest: don't sense or score the first frames right after a
  `reset` (world-model settle). The physics exam has no settle window (it
  senses from tick 0), so never make your controller depend on blindness.

## 5. Evaluate and certify

Gym telemetry (`t_ms`, `collision_count`) is self-reported training signal,
not certification. When your controller is worth a claim:

<Steps>
  <Step title="Benchmark in the gym">
    Freeze the weights, run a fixed episode battery (same worlds, same
    settings), and record survival + collision stats.
  </Step>

  <Step title="Certify your trained SNN">
    Package it as a Python module (`reset`/`act` over the same obs dict) and
    submit it to the frozen [Webots exam](/train/own-policy): your own
    architecture, sandboxed, no 9-float restriction. The verdict is comparable
    to the whole program history because everything except the policy slot
    stays frozen.
  </Step>

  <Step title="Transfer showcase">
    A trained SNN can drive a live hosted world client-side (the SNN transfer
    demo): the same brain, unmodified, in a world it never trained in.
  </Step>
</Steps>

## Timings and budget summary

| Item                         | Value                                             |
| ---------------------------- | ------------------------------------------------- |
| Control tick (`holdMs`)      | 60–2000 ms, default 300 ms                        |
| Sensor lag (`sensor_age_ms`) | tens of ms (local detector) … ≈1 s (cloud)        |
| Session cost                 | one GPU stream per session, billed by wall-time   |
| Sessions per runner          | serialized (1 active); parallelism = more runners |
