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).
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#
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). 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):
"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.
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 (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:contactimpulses from the kernel:frictionandimpactrun before it in the same phase. - Materials: the capability reads
toughness,tensileetc. from scope values, not from the material table; the material gives the pieces their mass (density) and their surface. netreplicates breaks by seed;editorbuildsfracturedblocks;explosionscallsbreakNowon 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.