# The level's physics block

# The level's physics block

A level declares its physics in the manifest, in one `physics` block (`PhysicsConfig`). It is how a level designer, the editor and an agent author realism without writing code.

## What it is for

The block says *what is on* in this level and *with which numbers*. When the level loads, the host applies it (`beginLevel`); when the level goes, everything it did is undone (`endLevel`). A game keeps nothing of one level's physics in the next.

## The fields

```json
"physics": {
  "capabilities": ["gravity", "media", "friction", "impact", "strength"],
  "gravity": { "vector": [0, -9.81, 0] },
  "materials": { "ice": { "friction": 0.03, "restitution": 0.1 } },
  "pairs": [{ "a": "ice", "b": "rubber", "values": { "friction": 0.9 } }],
  "combine": { "friction": "min" },
  "friction": { "scale": 1.2 },
  "constraints": [{ "type": "rope", "a": "Beam", "b": "Weight", "length": 3 }],
  "fractured": [{ "node": "Wall", "pieces": ["Wall_a", "Wall_b", "Wall_c"], "toughness": 800 }]
}
```

| Field | Meaning |
|---|---|
| `capabilities` | names to switch on, in this order. Each must be registered by the game or the load fails with the list of known names |
| `materials` | `{name: material}`, validated; see materials (skill `ferret-physics-bodies`) |
| `pairs` | `{a, b, values}`: override the combine rule for two materials (light kernel) |
| `combine` | `{property: rule}`: `mean`, `geometric`, `min`, `max`, `multiply` |
| `gravity.vector` | the kernel's gravity for this level (the `gravity` capability has its own `vector`) |
| `<capability name>` | an object of that capability's values, applied at **level** scope before it is switched on: `"friction": {"scale": 1.2}` |
| `constraints` | links between bodies **by the names of their nodes** |
| `fractured` | bodies authored in pieces, see below |

### Constraints

`type` is `rod`, `rope`, `spring`, `ball`, `hinge` or `fixed` (the last three need Havok). Fields: `a`, `b` (node names), `length`, `stiffness`, `damping`, `maxForce` (breaks above it), `anchor` (world point), `axis`, `limits` (radians, `[min, max]`), `id`. The result is the same as `addConstraint` in code; it is removed when the level unloads. A constraint whose node has no body is **not an error**: `beginLevel` returns it as a warning, because a level may be loaded before its streamed parts.

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

const host = world.addService(new PhysicsHost({ kernel: new LightKernel({ gravity: [0, -9.81, 0] }) }));
const beam = world.spawn('Beam', { position: new BABYLON.Vector3(0, 5, 0) });
beam.add('RigidBody', { shape: 'sphere', radius: 0.2, kind: 'static' });
const weight = world.spawn('Weight', { position: new BABYLON.Vector3(0, 3, 0) });
weight.add('RigidBody', { shape: 'sphere', radius: 0.3, mass: 5 });

const warnings = host.beginLevel({ constraints: [
  { type: 'rope', a: 'Beam', b: 'Weight', length: 2 },
  { type: 'rope', a: 'Beam', b: 'Missing', length: 2 },
] });
const linked = host.kernel.constraints.length;
host.endLevel();
```

### Pre-fractured bodies

For a wall, a pillar or a statue that breaks in *designed* pieces (rather than split by a rule), author the pieces as nodes next to the whole. `fractured: [{ node, pieces, material?, toughness? }]` says: when the body on `node` breaks, show these pieces instead. The pieces wait hidden until then. `toughness` sets the whole body's toughness for the `strength` capability at entity scope. At least two pieces are needed. When the whole breaks, each piece becomes a box body of its own size (the bounds of its node), the whole's mass is shared by volume, each piece keeps the motion of its own point of the whole (velocity plus spin) and a push outward from the centre, sized by the capability's `burst`. The pieces use the fracture's `material` when given, otherwise the whole's.

## Scope: what the level changes and gives back

`beginLevel` applies at **level** scope and `endLevel` clears it, so nothing leaks:

- the capabilities it switched on go off again (unless another capability still needs them: those stay on);
- level and zone values of every capability are cleared;
- its constraints are removed and its fractures forgotten.

What the block does **not** undo: materials it added to the library stay (they are only data), and anything a game set at **world** scope.

## Validating before you ship

```ts
import { validatePhysicsConfig } from '@ferret/physics';

const issues = validatePhysicsConfig({
  capabilities: ['gravity', 'warp'],
  gravity: { vector: [0, -9.81] },
  constraints: [{ type: 'chain', a: 'x', b: 'y' }],
}, ['gravity', 'friction']);
```

The editor session runs this check on the draft level, and `beginLevel` runs it before applying; call it yourself in tools. Messages carry the path (`physics.constraints[0]`) so a designer finds the line.

## Debugging

| Symptom | Cause |
|---|---|
| `invalid physics block: …` | the list says which paths; fix them, nothing was applied |
| A constraint is missing | `beginLevel` returned a warning: the node name has no `RigidBody`; check the spelling and that the body exists before the level's physics begins |
| Physics of the previous level lingers | the game applied it at **world** scope: use `beginLevel`, not `configure(…, 'world')` |
| A capability is not on after loading | it was not registered, or a `requires` capability is missing from the list: put the required one first |

## Reference

`PhysicsConfig`, `LevelConstraint`, `LevelFracture`, `validatePhysicsConfig`, `PhysicsHost` (`beginLevel`, `endLevel`, `configure`), [level manifests](levels.md).
