Skip to content

2D physics

StableTested and in active use. Breaking changes will be called out. CarRacing's reward shaping is still being tuned.

Three environments on noeira.physics2d, a custom impulse-based engine written for batched GPU simulation. State is laid out strided as [BATCH, STATE_SIZE], so one kernel launch steps hundreds of environments at once.

LunarLanderPPO · 8 obs
BipedalWalkerSAC · 24 obs
CarRacingRainbow · pixels
EnvironmentObsActionsPhysics features
LunarLander84 discrete or 2 continuousterrain contact, flame particles
BipedalWalker244 continuousrevolute joints, articulated chain, LIDAR
CarRacing13 clean or pixels3 continuous or 5 discreteslip-based tire friction

physics2d is not a general-purpose 2D physics library — it implements exactly what these three environments need, which is why it can be fast and fully batched:

  • Impulse-based contact solving with a unified constraint solver
  • Revolute joints and articulated multi-body chains
  • Flat and edge terrain collision detection
  • LIDAR distance sensing
  • Slip-based tire friction for CarRacing

Everything runs as GPU kernels over the batch dimension. There is no per-body object graph to traverse.

Land a craft between two flags. Eight observations — position, velocity, angle, angular velocity, and two ground-contact flags — with reward for approaching the pad, landing softly and coming to rest, and a penalty for crashing or firing engines.

Both action spaces are available from the same physics:

from noeira.envs import LunarLander
  • Discrete (4): do nothing, fire left, fire main, fire right
  • Continuous (2): [main throttle, side throttle]

That makes it one of the few environments where you can compare a discrete algorithm and a continuous one on identical dynamics — DQN against SAC on the same task.

The terrain is generated per episode, so a policy has to generalise rather than memorise one landscape.

Walk a two-legged robot across terrain. Twenty-four observations: hull angle and velocity, joint angles and speeds, ground contact, and 10 LIDAR rangefinder readings that let the agent see the ground ahead.

Four continuous actions — torques for both hip and knee joints.

The native environment implements the normal terrain, a reasonable SAC/TD3 benchmark. Gymnasium’s hardcore variant (stumps, pits and stairs) is reachable only through the Gymnasium wrapper.

The interesting engine feature here is the articulated chain — the legs are a connected multi-body system solved with revolute joint constraints, not independent rigid bodies.

Drive a car around a procedurally generated track. The distinguishing feature is the tire model: friction is computed from wheel slip rather than assumed, so the car can lose traction, oversteer and spin. Throttle and steering interact the way they do in a real vehicle.

Two observation modes:

  • Clean (13 dimensions) — car state plus track-relative geometry
  • Pixels — a rendered top-down view, for vision-based agents

and two action spaces: 3 continuous (steer, gas, brake) or 5 discrete.

Terminal window
pixi run mojo run -I . examples/car_racing/rainbow_car_racing_pixel_hybrid_training.mojo
pixi run mojo run -I . examples/car_racing/play_car_racing.mojo # drive it yourself

All three implement GPUDiscreteEnv or GPUContinuousEnv and wrap into a batched driver:

from noeira.deep_agents.training.batched_env import BatchedGpuEnv
comptime N_ENVS = 64
comptime EnvT = BatchedGpuEnv[LunarLander[DT], N_ENVS, OBS_DIM, ACT_DIM]

Because the physics is written for the batch dimension, going from 1 to 256 environments costs almost nothing per environment — it is the same kernel with a wider grid. See CPU and GPU paths.