The gauges are the contract's two virtual range sensors — like the e-puck's IR pair. Each fills as the nearest obstacle closes in on that side: LEFT for objects left of the robot's centre, RIGHT for the right; something dead ahead (a wall) fills both. 1.0 means “at contact range” — the same geometry the collision verdict uses.
The gym is a plain HTTP loop: read an observation, send a command, get the next observation back —
the response of every step is the post-action observation, so one call per control tick.
By default sessions run with terminal_collision: false: a bump emits a one-step
collision spike and bumps collision_count, the engine re-arms, and the run
continues; pass true for game semantics where the first hit latches done.
sensor_age_ms reports how far the sensors lag the camera frame in the same observation.
Commands take either form — discrete, or your SNN's native differential wheels (quantized onto the
world's drive: right wheel faster → veers left, near-equal → forward, near-zero → idle; the response echoes
applied_action):
import requests
GYM = "http://127.0.0.1:8790"
sid = requests.post(f"{GYM}/gym/sessions",
json={"world": "epuck", "camera": 128}).json()["session_id"]
obs = requests.get(f"{GYM}/gym/sessions/{sid}/obs").json()
while not obs["done"]:
v_l, v_r = my_snn.step(obs["proximity"], obs.get("camera")) # your network
obs = requests.post(f"{GYM}/gym/sessions/{sid}/step", json={
"wheels": {"left": v_l, "right": v_r}, # or {"action": "left"}
"holdMs": 300,
}).json()
# obs = {"proximity": {...}, "collision": ..., "camera": {...},
# "labels": [...], "t_ms": ..., "done": ..., "applied_action": ...}
requests.post(f"{GYM}/gym/sessions/{sid}/reset") # next episode
Run one gym server per parallel environment (each session holds a real world-model GPU stream). The identical schema comes out of the offline exporter, so a controller trained on recorded JSONL plugs straight into the live loop.