lingbot training gym · snn observation contract

Episode replay — continuous run, collision spikes, lag-stamped sensors

LEFTproximity.left
0.00 0 = clear
1 = contact
forward camera, 128×128
COLLISION SPIKE
RIGHTproximity.right
0.00 0 = clear
1 = contact

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.

step 0/ forward t=0 ms count=0 sensor age= ms

Proximity timeline

proximity.left (up) proximity.right (down) collision flat grey tick = measured clear (0.00), not missing data click to jump · ←/→ keys · space plays

Filmstrip

Plug in your controller

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.

POST /gym/sessions {"world": "epuck", "camera": 128} → {"session_id"} POST /gym/sessions/:id/step {command} → observation (this contract) POST /gym/sessions/:id/reset → fresh run  ·  DELETE /gym/sessions/:id → teardown

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.

Raw record — current step (camera truncated)