Mental model

Know what moves—and who owns it.

LavenderSim separates a Python-authored task from a compact native physics engine. Once that boundary is clear, environments, policies, sensors, and the browser UI fit together naturally.

Diagram of Python task, native engine, policy, and browser data flow
Python remains authoritative: it defines task semantics, receives actions, computes reward, and decides what the browser displays.

Scene, runtime, environment

01

Scene

An immutable description of bodies, joints, materials, sensors, actuators, sites, terrain, cameras, and render hints.

02

Runtime

CodeSceneEnv compiles the scene into native state and advances contacts, constraints, actuators, and sensors.

03

Task environment

lavendersim.env owns randomization, policy observations, reward, success, failure, and episode timing.

Important: geometry is not the task. The same articulated scene can support balance, tracking, reaching, identification, or recovery tasks by changing Python reset and reward logic.

Three clocks to keep straight

ClockTypical valueWhat happens
Native integrationInternal substepsContacts and joint constraints are resolved.
Control step1 / 60 sThe policy supplies one action and receives one observation.
Episode horizon3 sThe task returns truncated=True when time runs out.

A 60 Hz, three-second rollout therefore contains 180 policy decisions. Sensor sample intervals and delays are measured against simulation time, not wall-clock browser refreshes.

Observation → policy → action

python
observation, info = env.reset(seed=7)

action = policy(observation)        # shape == env.action_space.shape
observation, reward, terminated, truncated, info = env.step(action)

Policy observations are flat float32 arrays for simple batching. Rich native telemetry remains available under env.sim for debugging and visualization. Reward code may use privileged state, but policy inputs should contain only the signals the deployed controller is meant to observe.

SignalPolicy input?Reason
IMU, joint angle, joint velocityUsuallyPhysically measurable proprioception.
Command direction and speedYesThe behavior is conditional on the requested motion.
Exact world poseOften noUseful for reward/debugging, but may not exist on a real robot.
Contact force or tactile gridTask dependentCritical for grasping and insertion, unnecessary for CartPole.

The sensor pipeline is part of the environment

ideal native signalsample intervallow-pass filterseeded noisedelay buffer
python
config = SensorConfig(
    sample_interval=1 / 120,
    cutoff_hz=20,
    noise=0.005,
    delay=0.016,
)
scene.imu("torso.imu", torso, sensor=config)

Noise and latency are deterministic for a reset seed, which keeps debugging and snapshot replay useful while still exposing the policy to realistic imperfections.

Contacts, friction, and settling

Contact behavior comes from geometry and material properties. Static friction resists the start of sliding; dynamic friction acts while sliding; rolling and torsional friction slow spheres and spinning contacts; restitution controls bounce.

Goal tasks need a stopping condition. Reaching the target for one frame is usually not enough. Use a target radius, a speed threshold, and a dwell timer so “arrive and stay” is rewarded differently from “fly through.”
python
inside = distance_to_goal < 0.08
stationary = ball_speed < 0.04
success = dwell.update(inside and stationary)

Snapshots make counterfactuals cheap

python
branch = env.snapshot()
next_a = env.step(action_a)

env.restore(branch)
next_b = env.step(action_b)

A snapshot includes bodies, joints, controls, actuator activation, tendon state, sensor filters and delays, simulation time, RNG state, and a scene fingerprint. Restoring it answers “what would have happened under another action?” without rebuilding the world.

Coordinates and units

QuantityConvention
Position / lengthmetres
Timeseconds
Masskilograms
Anglesradians
Force / torquenewtons / newton-metres
World axes+Y up; planar tasks commonly use X/Z
Quaternion(x, y, z, w)