# Bodies and kernels

# Bodies and kernels

A **kernel** is the rigid-body engine under the host: it owns the bodies, integrates them, resolves contacts and joints. Every capability is written against the one interface, `PhysicsKernel`, so a game picks the engine and the capabilities do not change.

## What it is for

Use a kernel when something has to *move under forces and collide*: a crate, a cannonball, a hull, a door on a hinge. Static scenery does not need one (characters walk against Babylon meshes with `checkCollisions`; see [layers, triggers and rays](#layers-triggers-and-rays)).

## Choosing a kernel

| | `LightKernel` | `HavokKernel` |
|---|---|---|
| Needs | nothing | the Havok WASM, loaded by `physics-havok` |
| Dynamic shapes | spheres | sphere, box, capsule, cylinder, convex hull, compound |
| Static shapes | box, plane, sphere | those plus triangle mesh |
| Stacking, resting boxes | no | yes |
| Joints | rod, rope, spring | those plus ball, hinge, fixed |
| Contact hooks (change friction before the solve) | yes | no: friction and restitution come from materials |
| Rolling resistance | native | added by the `friction` capability as a torque |
| Fast bodies | sub-steps scaled by speed | native swept collision |
| Deterministic | yes: the same inputs give the same run | yes on the same build |
| Use it for | projectiles, balls, debris, sliding and rolling tests, small games | everything with boxes, vessels, vehicles, mechanisms |

A game that needs stacking, joints beyond ropes or any non-sphere dynamic shape needs Havok. A capability that needs those (`needsBodies`) refuses to switch on over the light kernel with a message that says so.

```ts
import { PhysicsHost, LightKernel } from '@ferret/physics';

const host = world.addService(new PhysicsHost({ kernel: new LightKernel({ gravity: [0, -9.81, 0] }) }));
const ground = host.kernel.addBody({ shape: { type: 'plane', normal: [0, 1, 0], offset: 0 } });
const ball = host.kernel.addBody({ shape: { type: 'sphere', radius: 0.5 }, mass: 2, position: [0, 3, 0] });
for (let i = 0; i < 60 * 3; i++) host.fixedUpdate(1 / 60);
```

The same with Havok. The WASM is loaded once and handed to the kernel; the game decides when to pay for it:

```ts
import { PhysicsHost, HavokKernel } from '@ferret/physics';

const host = world.addService(new PhysicsHost({ kernel: new HavokKernel(await loadHavok(), { gravity: [0, -9.81, 0] }) }));
host.kernel.addBody({ shape: { type: 'plane', normal: [0, 1, 0], offset: 0 } });
const crate = host.kernel.addBody({ shape: { type: 'box', half: [0.5, 0.5, 0.5] }, mass: 20, position: [0, 2, 0] });
for (let i = 0; i < 60 * 4; i++) host.fixedUpdate(1 / 60);
```

## Bodies

A body is described by a `BodyDef`:

| Field | Meaning | Notes |
|---|---|---|
| `shape` | what it is | see the table above |
| `kind` | `dynamic` (moves under forces), `static` (never moves), `kinematic` (moved by you, pushes dynamic ones) | planes are static; the host turns a kinematic body's node movement into velocity so contacts see a moving wall |
| `mass` | kg | derived from the material's density and the shape's volume when missing |
| `material` | name in the material library | see [materials](materials.md) |
| `position`, `orientation`, `velocity`, `angularVelocity` | start state | SI units; orientation is a quaternion `[x, y, z, w]` |
| `linearDamping`, `angularDamping` | 1/s | for a world without a medium capability; with `physics-media` on, use the medium instead |
| `gravityScale` | multiplies gravity | 0 = floats |
| `lockRotation` | no spin | a crate that slides instead of a sphere that rolls |
| `noCollide` | touches nothing, joints still hold it | pivots and anchors; Havok only |

On an entity, a `RigidBody` component creates the body from parameters (`shape: 'sphere' | 'box' | 'capsule' | 'cylinder' | 'plane'`, `radius`, `height`, `size`, `mass`, `material`, `kind`, ...) and moves the entity's node after every step. `rotate: false` keeps a model upright while it translates. Anything the component cannot say (a hull, a compound) goes in `geometry`, which wins over `shape`.

Units are SI everywhere: metres, kilograms, seconds, newtons. A level made in centimetres must be scaled before it meets the kernel.

## Kinematic bodies that turn

A `kinematic` body moves by its `velocity` and turns by its `angularVelocity` on both kernels, and contacts use the speed of the point they touch (v + ω × r): a turning drum carries what rests on it by friction, a paddle wheel pushes what it meets. Forces on a kinematic body are ignored.

## Shapes you will actually use

- **Sphere, box, capsule, cylinder**: the cheap ones. Prefer them.
- **Convex hull** (`convex`, a list of points in body space): crates with bevels, rocks. The cost grows with the point count; 12–24 points is plenty.
- **Compound** (`children` with shapes and offsets): a vehicle from boxes, a table from a top and four legs. Cheaper and more stable than a mesh.
- **Mesh** (`positions`, `indices`): static or kinematic only: level geometry that bodies hit. Never dynamic.

## Joints

`addConstraint` links two bodies:

| Type | What it does | Kernel |
|---|---|---|
| `rod` | rigid distance (pushes and pulls) | both |
| `rope` | longest distance (pulls only, slack when closer) | both |
| `spring` | Hooke's law with damping, implicit so stable at any stiffness | both |
| `ball` | shared point, turns freely (a shoulder, a hook) | Havok |
| `hinge` | shared point, one axis, optional `limits` in radians (a door, a wheel) | Havok |
| `fixed` | welded in the present pose (a bracket) | Havok |

Every constraint reports `force` (N, positive in tension) and `stretch` (m) for the last step, and `maxForce` makes it **break**: it is removed and `onConstraintBreak` fires. The `strength` capability and bridges read the same data.

```ts
import { PhysicsHost, HavokKernel } from '@ferret/physics';

const host = world.addService(new PhysicsHost({ kernel: new HavokKernel(await loadHavok(), { gravity: [0, -9.81, 0] }) }));
const ceiling = host.kernel.addBody({ shape: { type: 'box', half: [0.2, 0.2, 0.2] }, kind: 'static', position: [0, 5, 0] });
const weight = host.kernel.addBody({ shape: { type: 'sphere', radius: 0.3 }, mass: 10, position: [0, 3, 0] });
const rope = host.kernel.addConstraint({ type: 'rope', a: ceiling, b: weight, length: 2, maxForce: 1000 });
for (let i = 0; i < 60 * 3; i++) host.fixedUpdate(1 / 60);
```

## Contacts and events

After each step the kernel reports, per contact, the total normal impulse (N·s), the largest closing speed (m/s), the slide speed and whether something asked the bodies to stick. The host puts them on the world's bus as `physics:contact`, in one batch after the step:

```ts
import { PhysicsHost, LightKernel } from '@ferret/physics';

const host = world.addService(new PhysicsHost({ kernel: new LightKernel({ gravity: [0, -9.81, 0] }) }));
host.kernel.addBody({ shape: { type: 'plane', normal: [0, 1, 0], offset: 0 } });
host.kernel.addBody({ shape: { type: 'sphere', radius: 0.5 }, mass: 1, position: [0, 5, 0] });
const hits: number[] = [];
world.events.on('physics:contact', (e: { speed: number }) => hits.push(e.speed));
for (let i = 0; i < 60 * 2; i++) host.fixedUpdate(1 / 60);
```

Capabilities read the same stream: `impact` for sounds and damage, `strength` for breakage. Your game can listen for effects (a thud sized by `speed`, a spark above a given `impulse`).

## Layers, triggers and rays

The non-realistic part of the physics package, always available: `PhysicsService`. Up to 31 named **collision layers** (`World`, `Pawn`, `NPC`, `Trigger`, `Interactable`, `Projectile`, `Camera` by default), `Collider` components (box, sphere, capsule) that act as *triggers* firing `trigger:enter` and `trigger:exit` for what overlaps them, `raycast` against static meshes, and `overlapSphere`. Level meshes become blocking for characters with `markStatic(root, layer)`. These are cheap and are the right tool for pickups, doors that open when someone stands there, damage volumes: no rigid body needed.

## Parameters

| Option | Where | Default | Effect |
|---|---|---|---|
| `gravity` | kernel | `[0, -9.81, 0]` | used when the `gravity` capability is off; with it on, the kernel's own is zeroed and the field supplies every force |
| `substeps` | both kernels | 1 (light: scaled by speed up to `maxSubsteps`) | more = steadier fast motion, costlier |
| `iterations` | light | solver passes | more = firmer contacts |
| `restitutionThreshold` | light | closing speed below which a bounce is a rest | raise it to stop jitter on resting bodies |

## Budget

Cost is per body and per contact. The light kernel is linear in contacts with sub-steps; Havok handles hundreds of bodies per step on the desktop tier (see budgets and tiers (skill `ferret-physics-fx-tuning`)). Bodies that stop moving go to **sleep** (`sleeping`) and cost almost nothing until touched; `wake(b)` and `sleep(b)` are there for you.

## Debugging

| Symptom | Cause |
|---|---|
| A box falls through a plane | on the light kernel dynamic boxes are not supported: use a sphere, or Havok |
| A hinge or ball joint throws | the light kernel has no joints beyond rod, rope and spring |
| Bodies jitter at rest | restitution threshold too low, or two overlapping start positions: separate them |
| A thin fast body tunnels | raise `substeps`; Havok sweeps natively |
| `body.state` says `sleeping` and it will not move | apply an impulse (it wakes the body) or `wake(b)` |
| Mass is huge or tiny | density of the material times shape volume: check the material and the units (metres) |
| Friction seems ignored on Havok | per-pair tables are not used there: set the materials themselves, see [materials](materials.md) |

## Reference

`PhysicsKernel`, `BodyDef`, `Body`, `Shape`, `ConstraintDef`, `Constraint`, `Contact`, `ContactEvent`, `KernelCaps`, `PhysicsService`, `Collider`, `CollisionLayers`.
