# Stretch

# Stretch

Package `@ferret/physics-stretch`, capability `stretch`. It runs last in each step, after the rigid bodies have moved and after breakage.

## What it is for

Anything that gives: a spring, a bungee, a rope that sags and may snap, a flag, a curtain, a tarp over a crate, a cube of jelly. Three tools, from cheapest to most expensive:

1. **Links**: springs, ropes and bands between two bodies. Elastic at first, then permanently bent past a yield point, then broken past a limit.
2. **Cloth**: a sheet of particles, pinned where you say, draped over colliders, torn if pulled too hard.
3. **Soft bodies**: a closed skin that keeps its volume, so a ball of jelly squashes and bulges rather than collapsing.

## How it works

Links are springs inside the rigid-body kernel. The capability keeps their rest length and stiffness up to date as they strain.

Cloth and soft bodies are not kernel bodies. They are particles advanced by their own solver with a fixed step and a fixed number of passes, so a replay is identical. They meet the world through colliders you give them (spheres, planes, boxes and rigid bodies) and read gravity and wind from the host.

## A spring

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

const cap = stretch();
const host = world.addService(new PhysicsHost({ kernel: new LightKernel(), capabilities: [cap] }));
host.enable('stretch');

const anchor = host.kernel.addBody({ shape: { type: 'sphere', radius: 0.05 }, kind: 'static', position: [0, 0, 0] });
const bob = host.kernel.addBody({ shape: { type: 'sphere', radius: 0.05 }, mass: 2, position: [1.1, 0, 0] });
cap.link({ a: anchor, b: bob, length: 1, k: 200 });          // stiffness in N/m
```

The bob oscillates with the period `2π√(m/k)`, as a real spring does.

## Ropes

`cap.rope` builds a chain of beads joined by links. Give it Young's modulus `E` (in pascals) and the cross-section `area` (m²), and the whole rope has the stiffness `E·A/L`, so a load stretches it by `F·L/(E·A)`. A soft rope stretches visibly under a weight; a stiff one hardly at all.

```ts
const rope = cap.rope({ from: [0, 0, 0], to: [0, -2, 0], segments: 8, mass: 0.5,
                        E: 1e7, area: 1e-4, damping: 30, anchorA: anchor, radius: 0.02 });
```

Hang the rope from a static or kinematic anchor. Leave `anchorB` out for a free end. By default a rope link does not push: when the ends come closer than the rest length it carries no force, so the rope sags instead of acting like a rod (`rope: false` makes it a rod).

### Bending and breaking

A link's strain is `(L − L₀) / L₀`.

| Strain | What happens |
|---|---|
| below `yieldStrain` | elastic: it springs back |
| above it | plastic: the rest length grows, a permanent set; `hardening` makes it harder to bend further (0 means perfectly plastic) |
| above `breakStrain` | it breaks and the link disappears |

Without `yieldStrain` a link is purely elastic; without `breakStrain` it never breaks. The events are `stretch:yield` (the first time a link goes plastic) and `stretch:break`, each carrying the link's `name`, so name the links you care about. For orientation: a bungee stays elastic up to 100 % strain, a copper wire yields at 0.2 %, a rubber band breaks at 400 %.

## Cloth

A cloth is a grid of `nu × nv` particles, `spacing` apart, with a total `mass`. `pinned: 'top'` holds the first row (a curtain, a flag on a pole), `'corners'` the two top corners, or you can list particle indices.

```ts
import { Cloth } from '@ferret/physics-stretch';

const cloth = new Cloth({ nu: 20, nv: 20, spacing: 0.05, mass: 0.4, pinned: 'top',
                          origin: [0, 0, 0], u: [1, 0, 0], v: [0, -1, 0],
                          strainLimit: 1.05, iterations: 6, substeps: 2 });
cloth.step(1 / 60);
```

To drape it over things, push **colliders** onto `cloth.colliders`: a `sphere`, a `plane`, a `box`, or `{ type: 'body', body }` for any rigid body (spheres, capsules, cylinders, compounds, convex hulls, closed meshes). Particles keep `thickness` away from the surface. `friction` says how much the cloth grips what it lies on.

The dials, in the order you will need them:

| Dial | What it does |
|---|---|
| `stiffness` (0–1) | how firmly the weave is held: lower is stretchier |
| `bending` | 0 a flexible sheet, 1 a stiff plate |
| `strainLimit` | no link gets longer than rest × this; 1.05 reads as cloth, 1.5 as rubber |
| `damping`, `drag` | energy loss, and resistance to wind |
| `iterations`, `substeps` | quality against cost; 6 and 2 hold a 400-particle curtain within 5 % |
| `tearStrain` | 0.15 tears a link stretched by 15 %; leave it out and the cloth never tears |

Move a pinned particle with `cloth.move(i, p)` (a hand carrying a flag), pin or release with `pin(i)` and `release(i)`. `cloth.onTear` and `cloth.torn` report tears.

### Cloth in a world: budget and wind

`cap.addCloth(name, def)` registers a cloth with the capability. It is stepped every fixed step with the host's gravity and the `wind` value.

The **particle budget** (`particleBudget`, 4000 across all cloths by default) is spent whole cloths at a time: a cloth that does not fit is not stepped in that frame, never half of it. `cap.particles()` returns how many were asked for and how many stepped. The tier table's `clothParticles` is the number to hand the budget (see budgets (skill `ferret-physics-fx-tuning`)).

## Soft bodies

A `SoftBody` is a closed skin of particles held by its edges and by one constraint on the **volume** it encloses. It squashes and bulges instead of losing volume. `pressure` is the volume it keeps as a share of its start volume: 1 is a closed ball of water, more is a blown-up balloon. It collides with planes in `floors`.

```ts
import { SoftBody } from '@ferret/physics-stretch';

const jelly = new SoftBody({ centre: [0, 0.6, 0], radius: 0.3, mass: 2, stiffness: 0.3, pressure: 1 });
jelly.floors.push({ normal: [0, 1, 0], offset: 0 });
jelly.step(1 / 60);
```

## Parameters

| Value (scoped) | Default | Effect |
|---|---|---|
| `particleBudget` | 4000 | most cloth particles stepped per frame, all cloths together |
| `wind` | `[0, 0, 0]` | wind velocity on cloth, m/s |

| Link setting | Meaning |
|---|---|
| `k`, or `E` and `area` | stiffness in N/m, or Young's modulus and section |
| `length` | rest length; the present distance if omitted |
| `damping` | N·s/m |
| `rope` | no force when slack |
| `yieldStrain`, `hardening`, `breakStrain` | see above |
| `name` | appears in events |

## Commands

`stretch.info` (links and cloths: strain, plastic set, force, the particle budget) and `cloth.pin { cloth, index, pinned }`.

## Works with

- Links need a kernel; they are kernel springs. On the light kernel they are the main way to join bodies; on Havok, joints (`hinge`, `ball`, `fixed`) are in bodies and kernels (skill `ferret-physics-bodies`).
- Cloth reads the `gravity` capability's field when that is on. It does not read the wind of `media`: stretch has its own `wind`.
- strength (skill `ferret-physics-strength`) breaks rigid joints by force; links here break by strain. Use links for things that stretch and rigid constraints for things that hold.

## Cost

Links cost one pass per step. Cloth costs particles × iterations × substeps: a 20×20 curtain at 6 iterations and 2 substeps is about half a millisecond on desktop. When time runs short the governor's last resort is to stop cloth; lower `iterations` before that. Use real cloth for what the player sees up close and fake distant flags with a shader.

## When it does not behave

| Symptom | Cause |
|---|---|
| A cloth does not move | it is over the particle budget (`cap.particles()`), `stretch` is off, or you step it by hand and did not call `step` |
| Cloth stretches like rubber | raise `stiffness`, lower `strainLimit` (1.05) |
| Cloth passes through a body | the collider is not in `cloth.colliders`, or `thickness` is smaller than the distance travelled in a step: raise `substeps` |
| A rope does not sag | `rope: false` (it is a rod), or it is too stiff for the load: lower `E` |
| A link never breaks | no `breakStrain` |
| Jelly collapses | `pressure` too low or `stiffness` 0 |
| Results differ between runs | a variable `dt`; always use the fixed step |

Worked numbers (spring period, rope stretch under load, curtain length, cloth over a sphere, budget, jelly volume) are in `tests/unit/examples/physics-stretch.test.ts`.

## Reference

`stretch`, `StretchCapability`, `Link`, `makeRope`, `Cloth`, `ClothCollider`, `SoftBody`, `STRETCH_DEFAULTS`.
