# `tecs.physics`
Rigid-body simulation as part of the world.
Install physics, spawn an entity with a
[`Transform2D`](tecs.ecs.Transform2D), and attach a body:
```nupp
tecs.physics.install(world, {gravity = {0.0, 980.0}})
local ground = world:spawn(tecs.ecs.Transform2D(320, 460))
tecs.physics.attach(world, ground, {kind = "static", halfWidth = 320, halfHeight = 20})
local crate = world:spawn(tecs.ecs.Transform2D(320, 100))
tecs.physics.attach(world, crate, {halfWidth = 16, halfHeight = 16, friction = 0.4})
tecs.physics.applyImpulse(world, crate, 400.0, 0.0)
```
The solver writes each body's `Transform2D` back, so nothing else has to move
the entity. `detach` takes a body out of the solve without despawning its
entity. On successful body creation, physics seeds
[`PreviousTransform2D`](tecs.ecs.PreviousTransform2D) from the creation pose
before publishing the solver's first result. The builtin snapshot system
refreshes that history before subsequent fixed steps. Rendering interpolates
position and the shortest rotation arc without changing simulation state.
Public positions, extents, linear velocities, impulses, forces and gravity use
pixels. Angles and angular velocities use radians. The solver works in meters
through the `pixelsPerMeter` conversion.
`Body` and `Collider` declare what the game requested. `Motion` preserves
simulation state across pauses. Engine-owned `RigidBody` keeps the attachment
visible to tools, and ordinary game code should ignore it. A secondary
collider lives on its own entity and relates back through `ColliderOf`; a
joint lives on its own entity and relates to its two bodies through
`JointBodyA` and `JointBodyB`.
The fixed phases own the simulation, and one fixed step is one crossing into
the solver. Every mutation a game requests is queued and applied at the start
of the step that follows it, and every read reports the state after the last
completed step. That is what keeps the crossing batched: a world of ten
thousand bodies costs one call per step rather than one call per body.
A body declared during a frame becomes live during that frame's next fixed
step, and its `RigidBody` row appears at the end of that step's `FixedUpdate`,
which is where the solver hands back the slot it assigned.
## Submodules
| Module | Description |
| --- | --- |
| `tecs.physics.contract` | The batched crossing between Tecs entities and a native rigid-body solver. |
| `tecs.physics.rapier` | The Rapier implementation of the batched physics crossing. |
## Types
### `Body` _type_
```nupp
type Body = BodyValue
```
The value a `Body` column holds.
### `BodyOptions` _type_
```nupp
type BodyOptions = {
kind: string?,
halfWidth: number?,
halfHeight: number?,
radius: number?,
capsuleLength: number?,
segment: {number}?,
offsetX: number?,
offsetY: number?,
density: number?,
friction: number?,
restitution: number?,
categoryBits: integer?,
maskBits: integer?,
isSensor: boolean?,
fixedRotation: boolean?,
isBullet: boolean?,
sleepEnabled: boolean?,
gravityScale: number?,
linearDamping: number?,
angularDamping: number?
}
```
The initial body and collider settings `attach` reads.
`segment = {x1, y1, x2, y2}` selects a line collider in local pixels,
useful for static map boundaries. It takes precedence over box or radius
settings. Segment coordinates may be negative; endpoints must differ.
### `Collider` _type_
```nupp
type Collider = ColliderValue
```
The value a `Collider` column holds.
### `ContactBegin` _record_
```nupp
record ContactBegin
entityA: integer
entityB: integer
end
```
`@derive(events.Event)` `@event(name="ContactBegin")`
#### Fields
##### `entityA`
```nupp
entityA: integer
```
Read-only. Identifies one body of the pair.
##### `entityB`
```nupp
entityB: integer
```
Read-only. Identifies the other body of the pair.
### `ContactEnd` _record_
```nupp
record ContactEnd
entityA: integer
entityB: integer
end
```
`@derive(events.Event)` `@event(name="ContactEnd")`
#### Fields
##### `entityA`
```nupp
entityA: integer
```
Read-only. Identifies one body of the pair.
##### `entityB`
```nupp
entityB: integer
```
Read-only. Identifies the other body of the pair.
### `Joint` _type_
```nupp
type Joint = JointValue
```
The value a `Joint` column holds.
### `JointOptions` _type_
```nupp
type JointOptions = {
kind: string?,
anchorAX: number?,
anchorAY: number?,
anchorBX: number?,
anchorBY: number?,
axisX: number?,
axisY: number?,
limitMin: number?,
limitMax: number?,
motorTargetVelocity: number?,
motorMaxForce: number?,
contactsEnabled: boolean?
}
```
The joint settings `attachJoint` reads.
### `Motion` _type_
```nupp
type Motion = MotionValue
```
The value a `Motion` column holds.
### `Options` _type_
```nupp
type Options = {
gravity: {number}?,
subStepCount: integer?,
workerCount: integer?,
simulation: contract.Simulation?
}
```
The settings `install` reads before it creates a simulation.
### `QueryOptions` _type_
```nupp
type QueryOptions = {
categoryBits: integer?,
maskBits: integer?
}
```
The collision filters a raycast reads.
### `RaycastHit` _record_
```nupp
record RaycastHit
entity: integer
x: number
y: number
normalX: number
normalY: number
fraction: number
end
```
Reports where a ray first met a collider.
#### Fields
##### `entity`
```nupp
entity: integer
```
Read-only. Names the shape owner. A secondary collider reports its own
entity rather than its body's.
##### `x`
```nupp
x: number
```
Read-only. Holds the contact point along x, in world pixels.
##### `y`
```nupp
y: number
```
Read-only. Holds the contact point along y, in world pixels, positive
downward.
##### `normalX`
```nupp
normalX: number
```
Read-only. Holds the outward unit normal along x, unscaled by pixels.
##### `normalY`
```nupp
normalY: number
```
Read-only. Holds the outward unit normal along y.
##### `fraction`
```nupp
fraction: number
```
Read-only. Holds the position along the segment, zero at its start and
one at its end.
### `SensorBegin` _record_
```nupp
record SensorBegin
sensor: integer
visitor: integer
end
```
`@derive(events.Event)` `@event(name="SensorBegin")`
#### Fields
##### `sensor`
```nupp
sensor: integer
```
Read-only. Identifies the sensor.
##### `visitor`
```nupp
visitor: integer
```
Read-only. Identifies the body that entered it.
### `SensorEnd` _record_
```nupp
record SensorEnd
sensor: integer
visitor: integer
end
```
`@derive(events.Event)` `@event(name="SensorEnd")`
#### Fields
##### `sensor`
```nupp
sensor: integer
```
Read-only. Identifies the sensor.
##### `visitor`
```nupp
visitor: integer
```
Read-only. Identifies the body that left it.
### `SolverHandle` _struct_
```nupp
struct SolverHandle
index1: integer
world0: integer
generation: uint32
end
```
`@derive(nupp.derive.Debug, nupp.derive.Serde)`
Identifies one live slot in a simulation.
Engine-owned and transient. `RigidBody`, `ColliderShape` and `JointLink`
all use this shape so debug tools can read an attachment without knowing
which arena it names. Ordinary game code should use `Body`, `Motion` and
the functions on this module.
#### Fields
##### `index1`
```nupp
index1: integer
```
Engine-owned. Identifies the slot, one-based, and zero when the entity
has no live counterpart. Ordinary game code should ignore it.
##### `world0`
```nupp
world0: integer
```
Engine-owned. Identifies the simulation, so a row left over from
another world is rejected rather than resolved against whatever took
its place. Ordinary game code should ignore it.
##### `generation`
```nupp
generation: uint32
```
Engine-owned. Rejects a stale slot. Ordinary game code should ignore
it.
## Functions
### `angularVelocity` _function_
```nupp
function angularVelocity(borrows world: ecs.World, entity: integer): number
```
Reads angular velocity in radians per second.
Radians need no conversion, so this is the number the solver holds, unlike
the linear velocity beside it.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | the target entity |
#### Returns
| Type | Description |
| --- | --- |
| `number` | angular velocity in radians per second, zero without a live body |
### `applyForce` _function_
```nupp
function applyForce(borrows world: ecs.World, entity: integer, x: number, y: number): nil
```
Applies a continuous force at the body's center.
The solver clears forces at the end of every step, so holding a body up
against gravity means calling this every fixed step rather than once.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | the target entity; a missing live body makes this a no-op |
| `x` | `number` | the pixel-scaled force along x |
| `y` | `number` | the pixel-scaled force along y, positive downward |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `applyForceAt` _function_
```nupp
function applyForceAt(borrows world: ecs.World, entity: integer, x: number, y: number, pointX: number, pointY: number): nil
```
Applies a continuous force at a world-space point.
Cleared at the end of every step, like `applyForce`.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | the target entity; a missing live body makes this a no-op |
| `x` | `number` | the pixel-scaled force along x |
| `y` | `number` | the pixel-scaled force along y, positive downward |
| `pointX` | `number` | the world-pixel application point along x |
| `pointY` | `number` | the world-pixel application point along y |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `applyImpulse` _function_
```nupp
function applyImpulse(borrows world: ecs.World, entity: integer, x: number, y: number): nil
```
Pushes a body once, at its center of mass.
An impulse rather than a force, so the effect does not depend on how long
the step happened to be. The solver applies it at the start of the next
fixed step and wakes the body, because a resting island would otherwise
ignore it.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | the target entity; a missing live body makes this a no-op |
| `x` | `number` | the pixel-scaled impulse along x |
| `y` | `number` | the pixel-scaled impulse along y, positive downward |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `applyImpulseAt` _function_
```nupp
function applyImpulseAt(borrows world: ecs.World, entity: integer, x: number, y: number, pointX: number, pointY: number): nil
```
Pushes a body once at a world-space point, which spins it as well.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | the target entity; a missing live body makes this a no-op |
| `x` | `number` | the pixel-scaled impulse along x |
| `y` | `number` | the pixel-scaled impulse along y, positive downward |
| `pointX` | `number` | the world-pixel impact position along x |
| `pointY` | `number` | the world-pixel impact position along y |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `applyTorque` _function_
```nupp
function applyTorque(borrows world: ecs.World, entity: integer, torque: number): nil
```
Applies torque, cleared at the end of every step like the two forces.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | the target entity; a missing live body makes this a no-op |
| `torque` | `number` | the solver's native torque units, where a positive value raises angular velocity |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `attach` _function_
```nupp
function attach(exclusive world: ecs.World, entity: integer, options: BodyOptions?): nil
```
Declares a body on an entity.
Physics creates the body during the next fixed update, at which point the
entity gains its `RigidBody` row.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive world` | `ecs.World` | a world with physics installed |
| `entity` | `integer` | the entity that gains `Body` and `Collider`, and gains a default `Transform2D` and `Motion` at the next fixed step when it does not already carry them |
| `options` | `BodyOptions?` | the initial body and collider settings, or nil for a dynamic eight-pixel box |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
#### Raises
- when the world has no simulation, the body kind is unknown, or a capsule has no positive radius
### `attachCollider` _function_
```nupp
function attachCollider(exclusive world: ecs.World, entity: integer, body: integer, options: BodyOptions?): nil
```
Adds another collider to a declared body.
The collider is its own entity, which makes each shape independently
inspectable and mutable. Physics creates the shape at the first fixed step
after its body is live, and an owner that never gains a
[`Body`](tecs.physics.Body) leaves the shape declared and
unrealized.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive world` | `ecs.World` | the world that owns both entities |
| `entity` | `integer` | the secondary collider entity, which gains `Collider` and `ColliderOf` |
| `body` | `integer` | an entity carrying `Body` |
| `options` | `BodyOptions?` | the shape, material, filter and offset settings; body-level fields have no effect |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
#### Raises
- when a capsule is requested without a positive radius
### `attachJoint` _function_
```nupp
function attachJoint(exclusive world: ecs.World, entity: integer, bodyA: integer, bodyB: integer, options: JointOptions?): nil
```
Declares a joint between two bodies.
Physics creates the joint at the first fixed step after both bodies are
live, so declaring the joint in the same frame as the bodies it constrains
costs no extra frame. A target that never gains a
[`Body`](tecs.physics.Body) leaves the joint declared and
unrealized, which is deliberate: a spawn staged this frame is not
published yet, so reading the target here would reject correct code.
Destroying either body destroys the joint entity with it.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive world` | `ecs.World` | the world that owns all three entities |
| `entity` | `integer` | the joint entity, which gains `Joint`, `JointBodyA` and `JointBodyB` |
| `bodyA` | `integer` | the first constrained entity |
| `bodyB` | `integer` | the second constrained entity |
| `options` | `JointOptions?` | the joint kind, anchors, limits and motor settings |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
#### Raises
- when the joint kind is unknown
### `detach` _function_
```nupp
function detach(exclusive world: ecs.World, entity: integer): nil
```
Removes a body's declaration.
Physics destroys the body during the next fixed update. The entity's other
physics components remain.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | the entity that loses `Body` |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `hasBody` _function_
```nupp
function hasBody(borrows world: ecs.World, entity: integer): boolean
```
Reports whether the solver is still simulating a body for an entity.
False for an entity whose `RigidBody` row came out of a snapshot into a
simulation that never issued it, which is how a game tells "was simulating
and is not any more" from "never had a body".
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | any entity |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | true only while the simulation holds a valid live body |
### `install` _function_
```nupp
function install(exclusive world: ecs.World, options: Options?): contract.Simulation
```
Installs a simulation and its fixed-step systems in a world.
Every system this registers runs in the fixed phases, so a simulation
advances by whole steps of the world's own timestep and replays the same
way on every machine.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive world` | `ecs.World` | a world with no simulation yet |
| `options` | `Options?` | the gravity, substep count, solver width, and an explicit simulation to drive instead of the default native one |
#### Returns
| Type | Description |
| --- | --- |
| `contract.Simulation` | the installed simulation, also available through `of` |
#### Raises
- when the world already has a simulation
### `isAwake` _function_
```nupp
function isAwake(borrows world: ecs.World, entity: integer): boolean
```
Reports whether the solver currently considers a body awake.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | any entity |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | true while the solver actively integrates the body, false when it sleeps or does not exist |
### `of` _function_
```nupp
function of(borrows world: ecs.World): contract.Simulation?
```
Returns the simulation installed in a world.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world to inspect |
#### Returns
| Type | Description |
| --- | --- |
| `contract.Simulation?` | the world's live simulation, or nil before `install` and after shutdown |
### `raycast` _function_
```nupp
function raycast(borrows world: ecs.World, x1: number, y1: number, x2: number, y2: number, options: QueryOptions?): RaycastHit?
```
Casts a segment and returns its nearest collider, in pixels.
The cast tests one segment and ignores everything beyond its end, so a miss
may mean the segment was too short.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | a world; one without physics returns nil |
| `x1` | `number` | the segment start in world pixels along x |
| `y1` | `number` | the segment start in world pixels along y |
| `x2` | `number` | the segment end in world pixels along x |
| `y2` | `number` | the segment end in world pixels along y |
| `options` | `QueryOptions?` | the collision filters, or nil to test every shape including sensors |
#### Returns
| Type | Description |
| --- | --- |
| `RaycastHit?` | a fresh caller-owned nearest hit, or nil on a miss |
### `setAngularVelocity` _function_
```nupp
function setAngularVelocity(borrows world: ecs.World, entity: integer, omega: number): nil
```
Sets angular velocity in radians per second and wakes the body.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | the target entity; a missing live body makes this a no-op |
| `omega` | `number` | angular velocity in radians per second |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `setAwake` _function_
```nupp
function setAwake(borrows world: ecs.World, entity: integer, awake: boolean): nil
```
Wakes or sleeps a body at the next fixed step.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | the target entity; a missing live body makes this a no-op |
| `awake` | `boolean` | true to wake the body, false to sleep it |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `setVelocity` _function_
```nupp
function setVelocity(borrows world: ecs.World, entity: integer, vx: number, vy: number): nil
```
Sets a body's velocity, in pixels per second, and wakes it.
Does nothing for an entity with no live body, which matches the zero
`velocity` returns for one.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | the target entity |
| `vx` | `number` | horizontal velocity in pixels per second |
| `vy` | `number` | vertical velocity in pixels per second, positive downward |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `teleport` _function_
```nupp
function teleport(exclusive world: ecs.World, entity: integer, x: number, y: number, angle: number?): nil
```
Teleports a body and immediately updates its `Transform2D`.
A move rather than a push: velocity is left exactly as it was, so a falling
body carries on falling from wherever it lands. The solver receives the
move at the next fixed step, while the transform changes now so the frame
that requested the teleport already draws it there.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `exclusive world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | the target entity; a missing live body makes this a no-op, including its `Transform2D` |
| `x` | `number` | the body's origin in world pixels along x |
| `y` | `number` | the body's origin in world pixels along y, positive downward |
| `angle` | `number?` | radians, or nil to keep the current `Transform2D.rotation` |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `velocity` _function_
```nupp
function velocity(borrows world: ecs.World, entity: integer): number, number
```
Reads a body's velocity after the last completed fixed step, in pixels per
second.
Pixels match every other linear number this module accepts and returns. A
caller that wants meters divides by `pixelsPerMeter`.
This reports what the solver last computed. `Motion` stores velocity only
at a pause or a save, and does not mirror live motion.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `borrows world` | `ecs.World` | the world holding the entity |
| `entity` | `integer` | the target entity |
#### Returns
| Type | Description |
| --- | --- |
| `number` | horizontal velocity in pixels per second, zero without a live body |
| `number` | vertical velocity in pixels per second, positive downward |
## Values
### `Body` _variable_
```nupp
const Body: ecs.ComponentDefinition
```
### `Collider` _variable_
```nupp
const Collider: ecs.ComponentDefinition
```
### `ColliderOf` _variable_
```nupp
const ColliderOf: ecs.Relationship
```
### `ColliderShape` _variable_
```nupp
const ColliderShape: ecs.ComponentDefinition
```
### `Joint` _variable_
```nupp
const Joint: ecs.ComponentDefinition
```
### `JointBodyA` _variable_
```nupp
const JointBodyA: ecs.Relationship
```
### `JointBodyB` _variable_
```nupp
const JointBodyB: ecs.Relationship
```
### `JointLink` _variable_
```nupp
const JointLink: ecs.ComponentDefinition
```
### `Motion` _variable_
```nupp
const Motion: ecs.ComponentDefinition
```
### `PhysicsDisabled` _variable_
```nupp
const PhysicsDisabled: ecs.Component
```
### `PhysicsHeld` _variable_
```nupp
const PhysicsHeld: ecs.Component
```
### `pixelsPerMeter` _variable_
```nupp
const pixelsPerMeter: number
```
Reports the fixed conversion between public pixels and solver meters.
The conversion keeps game coordinates in pixels and solver coordinates in
meter-sized values.
### `RigidBody` _variable_
```nupp
const RigidBody: ecs.ComponentDefinition
```