# Impact

# Impact

Package `@ferret/physics-impact`, capability name `impact`, phase `strength` (order −10: after `friction`, before breakage).

## What it is for

The kernel bounces bodies by the restitution of their two materials. `impact` adds what a game wants to control per place and per hit:

- a bounce that fades at high speed (a ball that splats instead of ricocheting);
- a **rest threshold**: the speed below which a bounce is a rest, so things settle instead of buzzing;
- **sticking**: two bodies that hit leave as one (clay, a grappling hook, a magnet);
- an **event** per hit with the impulse, the speed and the energy lost, so you can play a thud, spawn dust, deal damage. The `strength` capability reads the same hits.

## The model

For each contact the capability changes how it is solved (restitution, rest, stick), and after the step it reports what happened. The loss of energy follows physics: with the reduced mass `μ = m₁m₂/(m₁+m₂)` and closing speed `v`,

```
energyLost = ½ · μ · (1 − e²) · v²
```

which equals the kinetic energy the pair really lost.

| Value | Default | Effect |
|---|---|---|
| `threshold` | the kernel's 0.2 m/s | closing speed below which a bounce is a rest |
| `scale` | 1 | multiplies the restitution. 0 = every hit is inelastic |
| `falloff` | none | `{ from, to, factor }`: restitution falls linearly from 1× at `from` m/s to `factor`× at `to` m/s |
| `stick` | false | two dynamic bodies that hit leave as one body |
| `minEventSpeed` | 0.1 | smallest closing speed that raises `physics:impact` |

All scoped (world, level, zone, entity).

## First use

```ts
import { PhysicsHost, RigidBody } from '@ferret/physics';
import { impact, type ImpactEvent } from '@ferret/physics-impact';

const host = world.addService(new PhysicsHost({ capabilities: [impact()] }));
host.kernel.gravity = [0, 0, 0];
host.materials.add('m', { restitution: 1, friction: 0 });
host.enable('impact');
host.config('impact').set('world', 'scale', 0.5);                      // every hit has e = 0.5

const ball = (name: string, x: number, mass: number, vx: number) => {
  const e = world.spawn(name, { position: new BABYLON.Vector3(x, 0, 0) });
  e.add('RigidBody', { shape: 'sphere', radius: 0.5, mass, material: 'm', velocity: [vx, 0, 0] });
  return e;
};
const hits: ImpactEvent[] = [];
world.events.on('physics:impact', (e: ImpactEvent) => hits.push(e));
const a = ball('a', -3, 2, 4), b = ball('b', 3, 3, -1);
world.run(3);
```

## Recipes

### Make things settle

A ball dropped on a floor with restitution 0.7 bounces many times, each smaller. Raise `threshold` (say 1 m/s) so the small final bounces become rests: the ball stops instead of vibrating. Do it in a zone to settle only the places that matter.

### A bounce that dies at high speed

`falloff: { from: 5, to: 20, factor: 0.2 }`: below 5 m/s the material's bounce stands; at 20 m/s and above it is a fifth of that. A dropped rubber ball bounces; the same ball shot from a cannon splats.

### Clay, glue and magnets

`stick: true` (world, zone or entity): two dynamic bodies that hit leave moving at `(m₁v₁ + m₂v₂)/(m₁+m₂)` and stay in contact. Conserves momentum exactly, loses the energy `½ μ v²`. `ImpactEvent.stuck` is true for those hits. Scope it to an entity to make *one* thing sticky (a flypaper, a grappling hook).

### Sound and sparks from hits

Listen to `physics:impact` (an `ImpactEvent`: `a`, `b`, `entityA`, `entityB`, `point`, `normal`, `impulse`, `speed`, `restitution`, `energyLost`, `stuck`) and map `speed` or `impulse` to volume. Pick the sound by the two materials' names (`a.material`, `b.material`). Do not play a sound for every event: use `minEventSpeed` (e.g. 1 m/s) to ignore resting contact.

### Damage from impacts

Do not invent damage here: switch on `strength` (skill `ferret-physics-strength`), which reads impact energy against the material's strengths. Use the `energyLost` of the event only for game logic that is *not* about breaking (a score for a big hit).

## Parameters

| Value | Scopes | Unit | Safe range |
|---|---|---|---|
| `threshold` | all | m/s | 0.05 – 2 |
| `scale` | all | – | 0 – 1.5 |
| `falloff.from`, `falloff.to` | all | m/s | `from < to` |
| `falloff.factor` | all | – | 0 – 1 |
| `stick` | all | boolean | |
| `minEventSpeed` | all | m/s | 0 – 5 |

## Kernel differences

`threshold`, `scale` and `falloff` need contact hooks on the light kernel. On Havok the restitution is each body's material value times `scale` (the surface update mechanism of [friction](friction.md)); `falloff` and `stick` are applied through contact hooks, which Havok does not offer: test them on the light kernel before relying on them.

## Interplay

- `friction` runs first in the same phase; both change the contact before the solve.
- `strength` (skill `ferret-physics-strength`) reads the same contacts to damage and break.
- The events leave on the world's bus once per step, after the whole step.

## Budget

One hook per contact and one event per contact with speed above `minEventSpeed`. In a pile of 200 resting pieces the event filter is what keeps it cheap: raise `minEventSpeed`.

## Debugging

| Symptom | Cause |
|---|---|
| Everything bounces forever | restitution is high and `threshold` low: raise `threshold`, or `scale` below 1 |
| No `physics:impact` events | the capability is off, or `minEventSpeed` is above the closing speeds |
| Bodies do not stick | `stick` must be set where the contact is (the dynamic body's scope) and both bodies must be dynamic; on Havok it is not available |
| Energy seems to appear | `scale` above 1 gives restitution above 1: keep it at 1 or below |
| A bounce height is not `e² h` | friction, spin, a slope or the threshold took part; test with spin-free spheres |

## Reference

`impact`, `ImpactEvent`, `ImpactFalloff`, `IMPACT_DEFAULTS`, `ContactEvent`, `@ferret/physics-impact`.
