# Strength

# Strength

Package `@ferret/physics-strength`, capability name `strength`, phase `strength` (order 0, after `friction` and `impact`).

## What it is for

Things that break: a crate that splinters when it hits a wall at speed, a rope that parts under a heavy load, a bridge bracket that fails, a statue that falls apart in designed pieces. It turns *impulse* (the N·s a hit delivers) into *damage*, damage into a **break**, and a break into **pieces** that carry the parent's mass and momentum. It also makes **links** between bodies break at the force their material can hold (σ·A).

A body with no `toughness` is unbreakable. This is deliberate: nothing in a game breaks unless you said it can.

## The model

```
per contact:  damage = max(0, impulse − threshold) × scale      (only for bodies whose toughness is finite)
health        = toughness − damage taken so far
health ≤ 0    → the body breaks: `physics:break`, and `pieces` fragments replace it
```

| Value | Default | Effect |
|---|---|---|
| `toughness` | ∞ | impulse (N·s) a body absorbs before breaking. ∞ = unbreakable |
| `threshold` | 0 | impulse of one hit below which nothing is taken (brushes and resting contact) |
| `scale` | 1 | multiplies the damage |
| `pieces` | 0 | fragments that replace a broken body; 0 = the event only, the body stays |
| `burst` | 1 | m/s the fragments fly apart at, on top of the parent's velocity |
| `maxPieces` | 200 | pieces alive at once over the whole world; past it a body still breaks (the event) and leaves **no** new pieces |

All except `maxPieces` are scoped per world, level, zone and entity. `maxPieces` is the number the tier table sets (budgets (skill `ferret-physics-fx-tuning`)).

Events on the world's bus: `physics:damage` `{ entity, body, amount, health }`, `physics:break` `{ entity, body, point, impulse, pieces, seed }`, `physics:joint-break` `{ constraint, force, userData }`.

## First use: a crate that breaks at speed

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

const cap = strength();
const host = world.addService(new PhysicsHost({ capabilities: [cap] }));
host.kernel.gravity = [0, 0, 0];
host.materials.add('m', { restitution: 0, friction: 0.5 });
host.enable('strength');
host.config('strength').set('world', 'toughness', 10);               // absorbs 10 N·s
host.config('strength').set('world', 'pieces', 5);

const wall = world.spawn('wall');
wall.add('RigidBody', { shape: 'plane', normal: [-1, 0, 0], offset: 0, kind: 'static', material: 'm' });
const crate = (name: string, z: number, v: number) => {
  const e = world.spawn(name, { position: new BABYLON.Vector3(-3, 0, z) });
  e.add('RigidBody', { shape: 'sphere', radius: 0.5, mass: 1, material: 'm', velocity: [v, 0, 0] });
  return e;
};
const breaks: BreakEvent[] = [];
world.events.on('physics:break', (e: BreakEvent) => breaks.push(e));
const slow = crate('slow', 0, 3), fast = crate('fast', 5, 20);
world.run(3);
```

A 3 m/s hit takes some of the toughness away; a 20 m/s hit exceeds it. Damage accumulates: ten small taps break what one hard hit breaks.

## Recipes

### Make one kind of thing breakable

Set `toughness` at **entity** scope for the breakable ones and leave the world value at ∞: `host.config('strength').set('entity', 'toughness', 800, crate.id)`. Or give the whole level a value and set ∞ on the things that must not break. A level can do it with `fractured` (below).

### What the pieces are

- **Light kernel, a sphere**: spheres of the parent's mass (volume split), flying apart by `burst`.
- **Havok, any shape** (box, capsule, hull, compound): the shape is cut into boxes where the body stood; each piece has the velocity of *its own point* of the parent (`v + ω × r`) plus the outward push; **masses sum to the parent's** and the pushes are arranged to add no momentum, so momentum is conserved apart from the burst itself.
- **Pre-fractured** (the level's `fractured`): the artist's pieces take over, see below.

The same seed gives the same pieces and the same scatter on every machine: `breakNow(target, point, seed)` is how a *remote copy* of a body breaks identically when its owner's did (see net (skill `ferret-net-accounts`)). `BreakEvent.seed` is what to send.

### A wall that breaks in designed pieces

Author the whole wall and its pieces as nodes; list them in the level (level block (skill `ferret`)):

```json
"fractured": [{ "node": "Wall", "pieces": ["Wall_a", "Wall_b", "Wall_c"], "toughness": 800 }]
```

The pieces wait disabled until the wall breaks; then each becomes a box body of its own bounds with its share of the wall's mass.

### A link that breaks at what its material holds

`cap.joint({ type, a, b, tensile, area })` joins two bodies with a constraint that breaks at `tensile × area` newtons (σ·A). Give `maxForce` directly when you do not have the material numbers. A 40 MPa rod with a 1 cm² section (10⁻⁴ m²) holds 4 kN.

```ts
const rod = cap.joint({ type: 'rod', a: anchorEntity, b: loadEntity, tensile: 40e6, area: 1e-4 });   // holds 4 kN
world.events.on('physics:joint-break', e => console.log('snapped at', e.force));
```

Every constraint also reports its live `force`, so you can draw strain: green to red as `force / maxForce` goes from 0 to 1.

### Damage that is not an impact

`cap.damage(target, amount, point?)` takes health off (an explosion, a poison, a command) and breaks the body at zero; `cap.breakNow(target, point?, seed?)` breaks it regardless of toughness; `cap.health(target)` is what is left. Commands: `strength.damage {entity, amount}`, `strength.health {entity}`.

### Keeping debris under control

`maxPieces` bounds the *total* alive. When a break would exceed it, the body still breaks (its event fires, the game reacts) but leaves no pieces. Combine with a short lifetime on your own debris and the governor (skill `ferret-physics-fx-tuning`) (its first rule halves `maxPieces`).

## Parameters

| Value | Unit | Safe range | Note |
|---|---|---|---|
| `toughness` | N·s | 1 – 10⁶ or ∞ | a 1 kg crate hit at 10 m/s against a wall delivers about 10 N·s |
| `threshold` | N·s | 0 – toughness/10 | set above the resting-contact impulse so stacks do not wear away |
| `scale` | – | 0.1 – 10 | |
| `pieces` | count | 0 – 12 | each piece is a body; budget it |
| `burst` | m/s | 0 – 10 | |
| `maxPieces` | count | by tier | 200 desktop … 30 low |

## Interplay

- Reads `physics:contact` impulses from the kernel: `friction` and `impact` run before it in the same phase.
- Materials: the capability reads `toughness`, `tensile` etc. from scope values, not from the material table; the material gives the pieces their mass (density) and their surface.
- `net` replicates breaks by seed; `editor` builds `fractured` blocks; `explosions` calls `breakNow` on bodies a blast passes its rupture limit on.

## Budget

One number per body that has been hit; a force check per link per step. The cost that matters is the **pieces**: each is a body. Keep `pieces` at 4–8, and `maxPieces` at the tier's value.

## Debugging

| Symptom | Cause |
|---|---|
| Nothing ever breaks | `toughness` is ∞ at that scope (the default). Set it at entity or world scope |
| It breaks from a gentle touch | `threshold` is 0 and resting contact accumulates: raise it |
| `physics:break` fires but no pieces | `pieces` is 0, or `maxPieces` is used up, or (light kernel) the body is not a sphere |
| Pieces fly through each other | `burst` too high for the shape size |
| `strength is not enabled` from `joint` | enable the capability first; `joint` needs the host |
| The remote copy breaks differently | pass the owner's `BreakEvent.seed` to `breakNow` |

## Reference

`strength`, `StrengthCapability`, `JointDef`, `DamageEvent`, `BreakEvent`, `STRENGTH_DEFAULTS`, `LevelFracture`, `@ferret/physics-strength`.
