> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reskate.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Physics bodies

> reskate.world.physics: rigid bodies of your plugin's own in the local physics world, pushed one physics step at a time, read back, and probed with ray batches.

`reskate.world.physics` lets a plugin make rigid bodies of its own in the local physics world: a ball, a cart, a crate, a vehicle. Each body is a compound of capsules. The plugin reads where the body is, asks for a force, torque or impulse on the next physics step, and reads what happened. What a body is for, and how it is steered, is up to your plugin.

Every function needs the `world.physics` [permission](https://docs.reskate.dev/plugins/permissions.md). `probe_batch` needs `world.raycast` as well, and three of its options need `world.physics.observe`. `create`'s `reuse` option needs `world.physics.reuse`. Calling a function without its permission raises an error.

> [!WARNING]
>
> Bodies are local. They exist on your game only, and nothing about them is sent to other players. Every function works in the client copy only: in a server copy it raises `native bodies are client phase only`. Making, reading and pushing bodies is refused during a multiplayer session and while joining a lobby.

> [!WARNING]
>
> The game has room for eight bodies, shared by every plugin, every reload and every level, until it restarts. A released body is stopped and parked, not destroyed, and keeps its room. Once the eight are used, `create` answers `nil, "the eight retained body slots are exhausted; native destruction is unproven"`. A plugin you reload while you work on it runs out after eight bodies: restart the game. A plugin with `world.physics.reuse` can bring one of its own parked bodies back instead of using another place: see [bringing a parked body back](#bringing-a-parked-body-back).

## How a body moves

The physics world steps 60 times a second. Each `read` gives a sample of the body at one step, with an `epoch`: an opaque token for that step of that body. You compute a command from the sample and `submit` it with the epoch. The command is applied on the very next step and on no other: if that step is missed, or 100 ms pass, it expires.

Every command gets a receipt. The receipt says whether the command was applied, and later carries the first sample taken after it (its `successor`), so your plugin reads what actually happened. Acknowledge each receipt once you are done with it. A body keeps at most four receipts; while all four wait, `submit` is refused.

```lua theme={null}
local P = reskate.world.physics
local body

reskate.on("tick", function()
    if not body then
        local ready = P.prepare()
        if not ready then return end
        local why
        body, why = P.create({
            mass = 12,
            inertia = vector.create(2, 3, 4),
            position = vector.create(0, 2, 0),
            rotation = {0, 0, 0, 1},
            capsules = {{a = vector.create(0, 0, 0), b = vector.create(0, 0.5, 0), radius = 0.1}},
        })
        if not body then reskate.log("no body: " .. why) end
        return
    end
    local sample = P.read(body)
    if not sample then return end
    -- Hold it up against gravity.
    P.submit(body, sample.epoch, {force = vector.create(0, 12 * 9.81, 0)})
    local receipt = P.receipt(body)
    if receipt.outcome ~= "none" and receipt.outcome ~= "queued" then
        P.acknowledge(body, receipt.sequence)
    end
end)
```

When your plugin stops, reloads or fails, ReSkate releases all of its bodies. A level change, a multiplayer session starting, or the local skater's world changing lets go of every body too: it stops answering `read`, and still takes its place among the eight.

## Preparing

### `reskate.world.physics.prepare()`

Asks for the game's one-time setup for bodies, so that `create` is not the call that finds it pending. Call it, and after `true`, make your first `create`.

Takes no arguments; passing any raises an error.

**ready** `boolean`

True when the setup is done and the local skater's physics step can be read now. It reserves nothing: the room for bodies can still run out before `create`.

**why** `string`

Why not:

| Reason | Meaning |
| - | - |
| `native body producer setup pending; call prepare again explicitly` | This call asked for the setup. Call again on a later tick. |
| `waiting for a fresh qualified 60 Hz physics step` | Set up, but the local skater's physics step cannot be read now. |
| `native body ABI or producer hook unavailable; another producer may own it` | The setup ran once and failed. It is not tried again. |
| `owned bodies require the current local client phase outside multiplayer` | Not in the client's phase, or a session is on. |
| `asked to prepare native bodies more than once in one tick` | Each plugin may call `prepare` once a tick. |
| `native body preparation failed closed` | An error was caught before anything was asked. |

`create` answers a pending setup and a skater, world or step it cannot read now alike, with `body_preallocation_unavailable`. `prepare` tells them apart.

> [!NOTE]
>
> Needs the `world.physics` permission. Once a tick.

## Making and reading

### `reskate.world.physics.create(definition, options)`

Makes a body, or with `reuse` brings back one of your own parked bodies.

**definition** `table` *required*

Any field not listed raises an error.

**fields**

**capsules** `table` *required*

1 to 8 capsules, each `{a = vector, b = vector, radius = number}`: the two end points in the body's own frame, each axis within 10 m of its centre, and the radius 0.01 to 2 m. The body's centre of mass is its own origin.

**mass** `number` *required*

1 to 1000 kg.

**inertia** `vector` *required*

The principal moments of inertia, each 0.01 to 10000 kg m².

**position** `vector` *required*

Where it starts in the world, within 100 km.

**rotation** `table` *required*

A unit quaternion as a list `{x, y, z, w}`.

**options** `table`

Any field not listed raises an error, and so does a value that is not a table or `nil`.

**fields**

**reuse** `boolean` (default: false)

True looks first among your own parked bodies for one to bring back, and makes a new body only when none can come. Needs `world.physics.reuse`. See [bringing a parked body back](#bringing-a-parked-body-back).

**body** `userdata or nil`

The body: an opaque handle that only this plugin, until it reloads, can use.

**why** `string`

`nil` with a body. Otherwise why not. These are fixed codes, to compare against (see [refusal codes](#refusal-codes)):

| Code | Meaning |
| - | - |
| `body_preallocation_unavailable` | The setup is still pending, or the local skater, the world or the 60 Hz step cannot be read now. Nothing was reserved or made: a later `create` may work. |
| `body_reuse_pending` | Only with `reuse`: see [bringing a parked body back](#bringing-a-parked-body-back). |
| `body_reactivation_failed` | Only with `reuse`: see [bringing a parked body back](#bringing-a-parked-body-back). |

The other refusals are sentences meant for your log, such as `"owned bodies require the current local client phase outside multiplayer"`, `"the eight retained body slots are exhausted; native destruction is unproven"` or `"a previous partial allocation in this world prohibits another native allocation"`.

**origin** `string`

Only when you pass `options`, with a body: `"reused"` (a parked body came back) or `"allocated"` (a new body).

**miss** `string`

Only for a `reuse` that made a new body: why none of your parked bodies came back. See [bringing a parked body back](#bringing-a-parked-body-back).

Without `options`, or with `nil` in their place, `create` returns the body alone, or `nil` and why. With options it returns `body, nil, origin`, and `miss` after them for a `reuse` that made a new body.

The capsules collide with the world only. A plugin cannot choose what a body collides with. A failed `create` is not tried again for you.

> [!NOTE]
>
> Needs the `world.physics` permission, and `world.physics.reuse` for `reuse = true`. A definition out of these ranges raises `invalid native body: mass 1..1000, inertia .01..10000, unit XYZW rotation, capsule endpoints +/-10m and radius .01..2m`.

### `reskate.world.physics.read(body)`

The body at its latest physics step.

**body** `userdata` *required*

A body from `create`. Another plugin's body raises an error.

**sample** `table or nil`

`nil` and why when the body cannot be read now, such as `"body owner or generation unavailable"` for a released body, or `"body snapshot or native epoch changed; no sample published"`.

**fields**

**epoch** `userdata`

This step of this body, for `submit` and `probe_batch`. It cannot be made from numbers, used for another body, or kept past a reload.

**serial** `string`

The step's number, as a decimal string.

**time** `number`

Seconds of physics steps so far.

**seconds** `number`

The step's length: 1/60 s.

**position** `vector`

Where the body is.

**rotation** `table`

Its rotation as a list `{x, y, z, w}`.

**velocity** `vector`

Metres per second.

**angular\_velocity** `vector`

Radians per second about each axis.

**mass** `number`

Kilograms.

**inertia** `vector`

Its principal moments of inertia.

> [!NOTE]
>
> Needs the `world.physics` permission.

## Pushing

### `reskate.world.physics.submit(body, epoch, command)`

Asks for a force, torque or impulse on the body for the step after `epoch`.

**body** `userdata` *required*

A body from `create`.

**epoch** `userdata` *required*

The `epoch` of a sample of the same body, from `read` or a receipt's `successor`.

**command** `table` *required*

Any field not listed raises an error. A vector left out is zero.

**fields**

**force** `vector`

Newtons, in the world, at most 40000 long.

**torque** `vector`

Newton metres, at most 4000 long.

**impulse** `vector`

A one-shot push in newton seconds, at most 600 long. The force and the impulse spread over the step, together, are at most 40000 N.

**impulse\_id** `number`

Needed with an impulse: a whole number from 1 to 4294967295, higher than the last impulse's, so an impulse is never applied twice.

**ok** `boolean`

True when the command waits for its step.

**why** `string or nil`

`nil` when it waits. Otherwise why not. These two are fixed codes, to compare against (see [refusal codes](#refusal-codes)):

| Code | Meaning |
| - | - |
| `body_clock_unavailable` | The body's physics step cannot be trusted now: no step in the last 250 ms, the latest step was not published or was another skater's or world's, the step is not 1/60 s, or it was being written at that moment. Try again on a later tick. |
| `body_epoch_expired` | The step has moved past the sample your epoch came from, or a later `read` or `receipt` gave a newer sample. Read again and compute from the new sample. |

The other refusals are sentences meant for your log: `"body owner or generation changed"`, `"body commands require the current local client phase"` or `"body command invalid, repeated, stopped, pending or receipt ring full"`.

**sequence** `string`

The command's number, as a decimal string, for `receipt` and `acknowledge`.

Neither code assigns a sequence, takes the body's waiting place or changes a receipt. One command waits at a time for each body, and each must come from a newer step than the last. A plugin that reads and submits every step drives its body at 60 Hz. A command the game takes only in part fails the body for good: it is quarantined, and only `receipt`, `acknowledge` and `release` still answer for it.

> [!NOTE]
>
> Needs the `world.physics` permission. A command out of these bounds, or an epoch of another body, raises an error.

### `reskate.world.physics.receipt(body, sequence)`

What became of a command.

**body** `userdata` *required*

A body from `create`.

**sequence** `string`

A `sequence` exactly as `submit` gave it. Left out, the oldest receipt not yet acknowledged.

**receipt** `table or nil`

`nil` and why for a sequence this body does not hold.

**fields**

**outcome** `string`

`"queued"` (waiting for its step), `"applied"`, `"partial"`, `"rejected"`, `"expired"`, or `"none"` when there is no receipt.

**sequence** `string`

The command's number.

**requested\_serial** `string`

The step it was computed from.

**produced\_serial** `string`

The step that took it.

**force\_attempted** `boolean`

Whether the force was handed to the game.

**torque\_attempted** `boolean`

Whether the torque was handed to the game.

**retained** `boolean`

Whether the body has been let go: released, or by a level change or a session.

**quarantined** `boolean`

Whether the body failed, or was let go without being parked, and is no longer touched.

**successor** `table or nil`

The first sample read after the step that took the command, in `read`'s form. Only for an applied command, once it has been read.

`"applied"` says the game took the force and torque. It does not say how the body moved: read the `successor`.

> [!NOTE]
>
> Needs the `world.physics` permission. A `sequence` that is not a decimal string raises an error.

### `reskate.world.physics.acknowledge(body, sequence)`

Frees the oldest receipt, so another command can be submitted.

**body** `userdata` *required*

A body from `create`.

**sequence** `string` *required*

The oldest receipt's `sequence`.

**ok** `boolean`

True when it was freed.

**why** `string`

`"receipt unavailable, already acknowledged or awaiting a measured successor"`: receipts are freed oldest first, a queued one cannot be, and an applied one waits for its `successor`.

> [!NOTE]
>
> Needs the `world.physics` permission.

### `reskate.world.physics.release(body)`

Stops the body: its waiting command is cancelled, its force and velocity are cleared, it stops colliding, and it is parked. It still takes one of the eight places. With `world.physics.reuse`, your plugin can [bring it back](#bringing-a-parked-body-back) later.

**body** `userdata` *required*

A body from `create`.

**ok** `boolean`

True when the body was released.

**why** `string`

Why not, such as `"body owner or generation unavailable"`.

The receipts of a released body can still be read and acknowledged.

> [!NOTE]
>
> Needs the `world.physics` permission.

## Bringing a parked body back

A released body keeps one of the eight places until the game restarts, so a plugin that makes and releases the same body again and again, such as a bike made on each mount and released on each return, would run out. `create(definition, {reuse = true})` brings back one of its own parked bodies in place of a new one, at the definition's position and rotation.

```lua theme={null}
local P = reskate.world.physics
local bike -- the definition, the same capsules, mass and inertia each time

local function mount(at)
    bike.position = at
    local body, why, origin, miss = P.create(bike, {reuse = true})
    if not body then
        if why == "body_reuse_pending" then return nil end -- released just now: try next tick
        reskate.log("no body: " .. why, "warn")
        return nil
    end
    if origin == "allocated" then reskate.log("a new body (" .. miss .. ")") end
    return body
end
```

`reuse` looks only among the bodies this copy of your plugin parked itself: never another plugin's, and not those from before your plugin reloaded. A body can come back when:

* your plugin released it with `release`, in the same world it was made or brought back in;
* it never failed and was never quarantined;
* its capsules, mass and inertia are exactly those of the new definition, to the last bit. Its position and rotation may differ: it comes back at the new ones.

Any tick on which the world is not ready, such as a level load or the game leaving play, starts a new world. Every body parked before it stays parked, still takes its place among the eight, and is never brought back.

Before it writes anything, ReSkate checks that the body is still as `release` left it. Then it places the body, lets it collide with the world again and sets it moving, as at the end of a creation. It comes back with a new handle: the old handle, its epochs and its receipts are gone.

When no body can come back, `create` makes a new one, as without `reuse`, and its fourth result, `miss`, says why, from your own bodies only:

| `miss` | Meaning |
| - | - |
| `none_parked` | Your plugin has no parked body. |
| `definition_differs` | Its parked bodies have other capsules, mass or inertia. |
| `world_changed` | They were parked in an earlier world. |
| `quarantined` | They failed, or were barred by an earlier failure. |
| `parked_body_changed` | The check above failed: that body is never brought back. |

Two refusals come only from `reuse`:

| Code | Meaning |
| - | - |
| `body_reuse_pending` | A body that could come back was released fewer than two physics steps ago. Nothing was reserved, written or made: ask again on a later tick. |
| `body_reactivation_failed` | A write while bringing it back did not hold. The body is kept in quarantine, no handle is returned, and no more bodies can be made in that world, as after a partial creation. |

`create` without `reuse` never brings a body back, so it is how a plugin asks for a new one.

> [!NOTE]
>
> Needs `world.physics.reuse` as well as `world.physics`; a manifest with the first and not the second fails to load. A plugin may ask to reuse once a tick. A second request that tick, after a refused one too, returns `nil, "asked to reuse a native body more than once in one tick"` without reaching the game.

## Refusal codes

Five refusals are fixed codes rather than sentences, so a plugin can compare `why` with them and decide whether to try again. Every other refusal on this page is a sentence meant for your log, whose words can change. `probe_batch` has its own reasons, in [`batch.reason`](#ray-batches).

| Code | From | Try again? |
| - | - | - |
| `body_preallocation_unavailable` | `create` | Yes, on a later tick. Nothing was reserved or made. |
| `body_reuse_pending` | `create` with `reuse` | Yes, on a later tick. Nothing was reserved or made. |
| `body_reactivation_failed` | `create` with `reuse` | No: that world allows no more bodies. |
| `body_clock_unavailable` | `submit` | Yes, on a later tick. |
| `body_epoch_expired` | `submit` | Yes, with the epoch of a new `read`. |

## Ray batches

### `reskate.world.physics.probe_batch(body, epoch, rays, options)`

Casts up to 32 rays at the moment of one sample of a body: what is under a wheel, what a bumper is about to meet. A batch is all or nothing. When anything about the body or the world changed between the first ray and the last, every ray is discarded and the batch is `"unavailable"`.

**body** `userdata` *required*

A body from `create`. Its own capsules are never a hit.

**epoch** `userdata` *required*

The `epoch` of the latest sample of that body.

**rays** `table` *required*

1 to 32 rays, each exactly `{from = vector, to = vector}`, in a list with no gaps. Each end within 100 km; each ray at least 10 micrometres and at most 1000 m long.

**options** `table`

Any field not listed raises an error. Each flag takes only `true` or `false`.

**fields**

**exclude\_bodies** `table`

Up to 8 more of your own bodies whose capsules are not a hit either.

**require\_contact** `boolean` (default: false)

Every hit must carry its `normal` and `surface_velocity`, or the whole batch is `"unavailable"` with `contact_measurement_unavailable`.

**query\_preset** `string` (default: camera)

What the rays test: `"camera"`, the shapes `world.raycast` meets; `"terrain"`, the game's terrain shapes only; or `"all"`, every shape, skaters included, which needs `world.physics.observe`.

**exclude\_local\_actor** `boolean` (default: false)

`true` is refused today with `local_actor_exclusion_unavailable`, before any ray is cast.

**require\_named\_actor\_categories\_absent** `boolean`

`true` refuses the batch when a hit is a shape of a character, ragdoll, skateboard or skater. It cannot be used with `"all"`. The result then has `named_actor_categories_absent`.

**include\_shape\_categories** `boolean` (default: false)

Adds each hit shape's collision categories, `in_categories`. Needs `world.physics.observe`.

**include\_hit\_owner** `boolean` (default: false)

Adds `owner_assigned` and `owner` to each hit. Needs `world.physics.observe`.

**batch** `table`

**fields**

**status** `string`

`"available"` or `"unavailable"`.

**reason** `string`

Only when unavailable: why, such as `"body_epoch_or_observation_changed"`, `"native_cast_budget_exhausted"`, `"owned_body_hit"` (the nearest hit was one of your excluded bodies), `"hit_identity_unqualified"`, `"contact_measurement_unavailable"`, `"client_phase_unavailable"`, `"body_exclusions_unavailable"`, `"query_preconditions_unavailable"` or `"query_unavailable"`.

**native\_casts** `number`

How many casts the game made.

**budget\_exhausted** `boolean`

True when the tick's casts ran out.

**epoch** `userdata`

The epoch you passed.

**owned\_bodies\_excluded** `boolean`

True on an available batch: its nearest hits were checked against your bodies.

**local\_actor\_excluded** `boolean`

Always false today.

**requested\_query\_preset** `string`

Only when you named `query_preset`: the preset you asked for.

**named\_actor\_categories\_absent** `boolean`

Only when you named `require_named_actor_categories_absent`.

**rays** `table`

One row for each ray, in order.

**each ray**

**status** `string`

`"hit"`, `"clear"` or `"unavailable"`.

**position** `vector`

A hit's point.

**fraction** `number`

How far along the ray, 0 to 1.

**normal\_available** `boolean`

Whether `normal` is there.

**normal** `vector`

The surface's unit normal at the hit, facing the ray.

**surface\_velocity\_available** `boolean`

Whether `surface_velocity` is there.

**surface\_velocity** `vector`

How fast the surface moved at the hit, in metres per second.

**motion** `string`

`"measured"` with a `surface_velocity`, else `"unknown"`.

**in\_categories** `number`

With `include_shape_categories`: the hit shape's collision categories as a whole number. Missing means they could not be read; 0 is a known empty set.

**owner\_assigned** `boolean`

With `include_hit_owner`: whether the hit shape has an owner.

**owner** `userdata`

With `include_hit_owner`, when `owner_assigned`: a token that compares equal (`==`) to another hit's token when both shapes have the same owner, within the same sample. It says nothing else.

Without `require_contact`, a hit's normal and surface velocity are left out. A hit never names the body, object or face it met.

```lua theme={null}
local sample = reskate.world.physics.read(body)
if sample then
    local batch = reskate.world.physics.probe_batch(body, sample.epoch, {
        {from = sample.position, to = sample.position - vector.create(0, 1, 0)},
    }, {require_contact = true})
    if batch.status == "available" and batch.rays[1].status == "hit" then
        local ground = batch.rays[1]
        reskate.log(string.format("ground %.2f m below", (sample.position - ground.position).y))
    end
end
```

> [!NOTE]
>
> Needs the `world.physics` and `world.raycast` permissions, and `world.physics.observe` for `query_preset = "all"`, `include_shape_categories` and `include_hit_owner`. Each ray is one of the plugin's 64 raycasts a tick, shared with `reskate.world.raycast`, `pick`, `probe` and `reskate.session.raycast`; past them the call raises an error. Batch rays and [`reskate.world.probe`](https://docs.reskate.dev/plugins/api/world#raycasts) also share 64 casts in the game a tick, each ray using at most two; when they run out the batch is `"unavailable"` with `budget_exhausted`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.