# Materials

# Materials

A **material** is a table of numbers about a substance. Capabilities read the numbers they need; the kernel reads friction and restitution. Materials are how realism is *authored*: the same code makes ice slippery and rubber bounce because the data says so.

## What it is for

Every body can name a material (`material: 'ice'`). The host's `MaterialLibrary` holds the table. Use materials, not per-body hacks, for anything that is a property of stuff: density (so mass follows from shape), friction, bounce, stiffness and strength, heat capacity.

## The key rule: a missing value means "does not apply"

A material without a `tensileStrength` is not "weak"; it is **unbreakable**, because the `strength` capability has nothing to compare against. A material without a heat capacity does not heat. The few numbers a contact cannot do without are the exception and have library defaults:

| Property | Unit | Meaning | When missing |
|---|---|---|---|
| `density` | kg/m³ | mass per volume | mass must be given on the body |
| `friction` | – | static Coulomb coefficient | 0.5 |
| `kineticFriction` | – | sliding coefficient | 0.8 × friction |
| `rollingFriction` | – | rolling resistance: torque = coefficient × normal force × radius | 0 |
| `pivotFriction` | – | resistance to spinning about the contact normal | 0 |
| `restitution` | – | 0 sticks, 1 perfectly elastic | 0 |
| `youngModulus` | Pa | stiffness | stiffness-based behaviour does not apply |
| `yieldStrength` | Pa | where it starts to deform for good | does not yield |
| `tensileStrength` | Pa | where it tears | does not tear |
| `toughness` | – | energy it takes to crack | does not crack |

Other capabilities add their own keys (heat capacity, conductivity, melting point, resistivity, flammability...). Their guides list them. Any numeric key is allowed; `validateMaterials` checks the known ones against sane ranges.

```ts
import { MaterialLibrary, EXAMPLE_MATERIALS, validateMaterials } from '@ferret/physics';

const lib = new MaterialLibrary().load(EXAMPLE_MATERIALS);
lib.add('glass', { density: 2500, friction: 0.4, restitution: 0.6, tensileStrength: 3e7 });
const bad = validateMaterials({ plastic: { friction: 99, density: -5 } });
```

`load` throws with every problem listed when the table is invalid, so a bad level never half-loads. `EXAMPLE_MATERIALS` (iron, wood, stone, ice, rubber) are for examples and tests; a game defines its own.

## Two materials meet

A contact has two sides. What it uses for each property comes from, in this order:

1. a **pair table** entry for exactly these two materials (`setPair`), else
2. the **combine rule** of that property, applied to both sides' values, else
3. the one value that exists, if only one side has it.

| Rule | Result | Default for |
|---|---|---|
| `geometric` | √(a·b) | friction |
| `max` | the larger | restitution |
| `mean` | (a+b)/2 | rolling friction and anything else |
| `min` | the smaller | |
| `multiply` | a·b | |

```ts
import { MaterialLibrary, EXAMPLE_MATERIALS } from '@ferret/physics';

const lib = new MaterialLibrary().load(EXAMPLE_MATERIALS);
const iceOnRubber = lib.contact('ice', 'rubber');
lib.setPair('ice', 'rubber', { friction: 0.9 });                // studded tyres on ice: not the geometric mean
const studded = lib.contact('ice', 'rubber');
```

Pair tables work on the **light kernel**. Havok does not let a game change a contact before it is solved, so it takes friction and restitution from the combine rules only; to get a different result there, change the materials themselves.

## Recipes

- **A level's own materials**: put them in the `physics.materials` block of the level manifest ([level block](level-block.md)); they load with the level.
- **Ice, mud, rubber floors**: give the floor entity a material; tune `friction`. Or dial it per scope with the `friction` capability's `scale` without touching the material.
- **A bounce that fades with speed**: restitution is applied above `restitutionThreshold`; below it the contact is a rest. Raise the threshold for less jitter.
- **Mass from density**: leave `mass` off a body with a material that has `density`; the kernel uses density × the shape's volume.

## Debugging

| Symptom | Cause |
|---|---|
| `invalid materials: … is outside …` | a number out of range: units (friction is a coefficient, not a percentage) |
| A thing never breaks | its material has no `tensileStrength` (or the capability's own key): unbreakable by definition |
| Friction looks like the geometric mean of the two when you set a pair | you are on Havok, which does not read pair tables |
| `physics.materials` command | prints the table the host sees right now |

## Reference

`MaterialLibrary`, `PhysicsMaterial`, `CombineRule`, `validateMaterials`, `EXAMPLE_MATERIALS`, `DEFAULT_CONTACT`.
