# Links between capabilities

# Links between capabilities

Package `@ferret/physics-links`, capability name `links`, phase `stretch` (order 1000, last).

## What it is for

Each physics capability knows only itself. A **link** is where one capability's output becomes another's input, so a world closes its loops: a chemical vessel sits in a water bath, a hot wall warms the gas of a tank, a slipping brake heats its drum, steam from a boiler turns a shaft, a reactor's coolant drives a turbine, a screw on a shaft pushes a ship and loads the engine, an arc fault blasts, an overloaded wire lights a fire.

Links read and write the plain fields and methods of the objects they join (a heat part, a chemical vessel, a gas volume, a shaft, a reactor, a thruster, a kernel body), so the package imports none of their packages and you can write your own in a dozen lines.

## The model

A `Link` is `{ kind, moved, step(dt, host, contacts) }`. `step` reads one side, writes the other, once per world step after the capabilities have stepped; `moved` counts the energy that crossed (J). The `links` capability holds a list of them and keeps the **scene energy account**: you register terms (stores such as kinetic, heat, chemical, electric; and sinks such as losses and the condenser), and `energy()` sums them. In a closed scene the total stays where it started.

| Link | Joins | Does |
|---|---|---|
| `vesselInPart(vessel, part, net)` | Q → H | the part is the vessel's surroundings; what the vessel loses goes into the part |
| `gasInPart(gas, part, net, G, at?)` | gas ↔ H | heat flows through `G` W/K; a hot wall raises the pressure of a tank |
| `slipHeats(coupling, part, net)` | M → H | what a slipping clutch, belt or brake loses heats the part |
| `plasticHeats(link, part, net, share?)` | E → H | 90 % of the plastic work of a stretched link becomes heat |
| `stagnationHeats(part, net, {speed, ambient, G})` | air → H | a skin warms toward `T + r v² / 2cp` |
| `reservoirInPart(res, part, net, {G, density, cp})` | L → H | a tank's liquid warms to its wall; the wall pays what the liquid took |
| `reactorHeatsPart(reactor, part, net, G)` | N → H | decay heat that the coolant no longer carries warms the structure |
| `turbineOnReactor(reactor, shaft, {efficiency, maxTorque})` | N → M | a share of the coolant's heat becomes shaft torque |
| `engineOnBoiler(boiler, shaft, {area, exhaust, work, maxTorque, throttle?})` | K → M | steam through a throttle turns the shaft; the boiler loses it with its enthalpy |
| `boilerFed(boiler, tank, {setpoint, rate})` | L → K | a feed pump moves liquid into a boiler; an empty tank starves it |
| `screwOnShaft(thruster, shaft, {diameter, kt, kq})` | M → V | thrust from shaft speed, the water's torque back on the shaft |
| `armatureOf(coil, body, pole, kernel)`, `coilPulls(...)` | W → bodies | an electromagnet pulls an armature |
| `arcFlash(world, {circuit, at, voltage, detonate})` | W → X | from a short until the circuit clears, the arc's energy goes into a blast |
| `wireFire(world, {circuit, element, target})` | W → fire | an overheated wire ignites its target once |
| `mudOver(terrain, media, {box})` | T → D | wet ground makes a mud medium; it goes when the ground dries |
| `navFollowsTerrain(world, terrain, {rebuild})` | T → nav | a dug trench or heap rebuilds the navigation mesh once digging stops |

## First use: a brake heats its drum, and the energy adds up

```ts
import { slipHeats, links } from '@ferret/physics-links';

const cap = links();
cap.add(slipHeats(brake, drum, net));                          // the brake's losses become heat in the drum
cap.account({ name: 'wheel', joules: () => train.kinetic() }); // the stores that make up the scene's energy
cap.account({ name: 'drum', joules: () => drum.H });
cap.energy().total;                                            // stays where it started, in a closed scene
```

When the brake slows the wheel, its rotation goes into the drum: the wheel's term falls, the drum's rises by the same amount, and the total stays put. If the sum of your terms drifts, a link or a capability is losing energy. That is the way to find a bug in a scene.

## Recipes

### A hot wall and a tank

```ts
cap.add(gasInPart(gas, wall, net, 2));       // 2 W/K between the wall and the gas
```

A hot wall warms the gas, and the pressure in a closed tank rises with it. The same pattern with a cooling wall and a vessel (`vesselInPart`) gives a reactor in a water bath; a vessel that raises `chem:runaway` in a bath that cannot carry the heat is a thermal-runaway scenario.

### A turbine on a reactor

```ts
import { turbineOnReactor } from '@ferret/physics-links';

cap.add(turbineOnReactor(reactor, shaft, { efficiency: 0.33, maxTorque: 1e5 }));
```

A third of the coolant's heat becomes torque on the shaft. Chain it on: a generator on that shaft (electric (skill `ferret-physics-electric`) `bindMachine`) lights a lamp whose power comes from the reactor. The steam path in between has its own link, `engineOnBoiler`.

### In a world

```ts
const cap = links();
host.register(cap);
host.enable('links');
cap.add(slipHeats(brake, drum, heat.network));
cap.account({ name: 'wheel', joules: () => train.kinetic() });
```

The capability steps every link after the other capabilities, once per world step. `links.energy` (command) returns `{ total, terms }`. Events from the capabilities (`circuit:short`, `circuit:trip`, `circuit:overheat`) are what `arcFlash` and `wireFire` listen to; each fires once per fault and has `dispose()`.

### Write your own link

A link is any object with `kind`, `moved` and `step`. Read the source, write the target, add the energy that crossed to `moved`:

```ts
const solarHeatsRoof: Link = {
  kind: 'sun→roof', moved: 0,
  step(dt) { const q = irradiance() * area * dt; net.add(roofPart, q); this.moved += q; },
};
cap.add(solarHeatsRoof);
```

## Budget

A few reads and writes per link per step; hundreds of links are free.

## Debugging

| Symptom | Cause |
|---|---|
| The energy total drifts | a link writes without taking from the other side, or a store is missing from `terms` |
| A link does nothing | it was not `add`ed to the capability, or `links` is not enabled |
| Heat appears twice | two links join the same pair (a part in a vessel and the vessel in the part) |
| Order effects | a link runs after the capabilities have stepped: what it writes is used next step |
| `arcFlash` fires twice | it does not: one blast per fault; a second needs the circuit to clear first |

## Reference

`links`, `LinksCapability`, `Link`, `EnergyTerm`, `vesselInPart`, `gasInPart`, `slipHeats`, `turbineOnReactor`, `engineOnBoiler`, `screwOnShaft`, `arcFlash`, `wireFire`, `mudOver`, `boilerFed`, `@ferret/physics-links`.
