2D physics engine
StableTested and in active use. Breaking changes will be called out.
noeira.physics2d is a purpose-built impulse-based engine for
LunarLander, BipedalWalker and CarRacing. It is not
a general 2D physics library — it implements exactly what those three need,
which is why it can be fully batched on GPU.
The batched layout
Section titled “The batched layout”The design decision that shapes everything: state is stored strided across the
batch, as [BATCH, STATE_SIZE].
noeira/physics2d/├── layout.mojo PhysicsLayout — compile-time offsets for bodies, contacts, joints├── state.mojo the state slab├── config.mojo compile-time configuration├── constants.mojo└── env_helpers.mojoPhysicsLayout computes every field offset at compile time, so a kernel indexes
directly into the slab with no runtime arithmetic and no per-body object to
chase. One kernel launch advances every environment. CarRacing goes furthest:
CarPhysicsKernel fuses its whole physics step into a single step_gpu()
call.
Components
Section titled “Components”| Package | Contents |
|---|---|
integrators/ | semi-implicit Euler |
collision/ | flat terrain, edge terrain, circle against polygon |
solvers/ | the impulse contact solver |
joints/ | revolute joints with limits, motors and springs |
car/ | slip-based tire friction, tile collision, the fused car kernel |
lidar/ | distance sensing |
traits/ | the Integrator, CollisionSystem and ConstraintSolver traits |
Impulse solving
Section titled “Impulse solving”Contacts are resolved by applying impulses that correct velocity directly, rather than by integrating penalty forces. For the shallow contact stacks these environments produce, that is both cheaper and more stable than a full constraint formulation — and it parallelises cleanly across the batch, since each environment’s contacts are independent.
Articulated chains
Section titled “Articulated chains”BipedalWalker’s legs are a connected multi-body system, not free rigid bodies.
RevoluteJointSolver in joints/ solves the revolute constraints along the
chain — anchor coincidence, plus optional angle limits, motors and springs —
which is what keeps the limbs attached under load.
The solver works on any set of revolute joints in the layout, so a new jointed 2D robot does not need new solver code.
The tire model
Section titled “The tire model”CarRacing’s distinguishing feature. Rather than assuming a friction coefficient, the model computes lateral and longitudinal slip at each wheel and derives the friction force from it.
The consequence is that the car behaves like a car: it can lose traction under hard cornering, oversteer, and spin. Throttle and steering interact.
This makes CarRacing meaningfully harder than the Gymnasium original, and policies do not transfer between them.
BipedalWalker’s 10 rangefinder readings come from lidar/, casting rays against
the terrain each step. They are what let the agent see upcoming ground rather
than react to contact alone.
Using it directly
Section titled “Using it directly”The environments are the intended interface, but the engine is usable on its own:
from noeira.physics2d import ( PhysicsLayout, PhysicsState, SemiImplicitEuler, ImpulseSolver, RevoluteJointSolver,)Define a layout for your bodies, contacts and joints, then compose an integrator, a collision system and the solvers over the batched state.
Relationship to physics3d
Section titled “Relationship to physics3d”Two engines exist because the requirements genuinely differ.
physics2d | physics3d | |
|---|---|---|
| Coordinates | maximal (world-space bodies) | generalized (joint space) |
| Contacts | impulse | constraint solve (Newton/CG/PGS) |
| Batching | designed in from the start | added over the field layout |
| Model source | code | MJCF XML |
| Scope | three environments | any articulated rigid body |
physics2d trades generality for throughput. For LunarLander it is the right
call — the dynamics are simple and the win is running 256 of them at once.
- 3D physics — the generalized-coordinates engine
- 2D physics environments — what runs on it
- CPU and GPU paths — how batching works framework-wide