# Media

# Media

Package `@ferret/physics-media`, capability name `media`, phase `field` (order 10).

## What it is for

Anything that moves through something. A medium is a *substance with a density and a viscosity (and maybe a flow)* that fills a place; a body in it feels

- **drag** against the flow: Stokes' law at low Reynolds numbers, a quadratic law at high ones (Schiller–Naumann for a sphere), so a feather, a stone and a cannonball each fall as their size says;
- **buoyancy**: ρ_medium · V_submerged · g from the exact submerged part of the body, so wood floats at a draft that depends on its density, lead sinks, a hull rights itself;
- **rotational drag**.

Air is the default medium (density and temperature from an atmosphere by altitude), can have a wind with gusts, and can be switched off for vacuum. A body half in water and half in air feels the volume-weighted blend. It is not a fluid simulation: the medium is *a property of a place*. For flowing, splashing water use [liquids](liquids.md).

## The model

```
medium at p  =  the dense medium whose region contains p (surface, zone, bounds)   else   air
body feels   =  blend of the media by the submerged fraction, with their flows
force        =  buoyancy (along gravity) + drag against (body velocity − flow)
```

A **medium** (`MediumDef`) is `{ name, density (kg/m³), viscosity (Pa·s), flow?, surface?, zone?, bounds?, gust? }`:

| Field | Meaning |
|---|---|
| `surface` | the y of the top of the medium, or a **function** when it moves (a reservoir that fills and drains). Below it the medium is there |
| `zone` | limits the medium to a named zone of `@ferret/zones` |
| `bounds` | `{min, max}` box the medium fills (a tank); outside it the medium is not there |
| `flow` | velocity of the medium, m/s: a current, a river |
| none of `surface`, `zone`, `bounds` | fills everything |

`EXAMPLE_MEDIA` has `water`, `seawater`, `mud` and `honey` to start from.

## First use

```ts
import { PhysicsHost, RigidBody } from '@ferret/physics';
import { gravity } from '@ferret/physics-gravity';
import { media, EXAMPLE_MEDIA, submergedFraction } from '@ferret/physics-media';

const m = media();
const host = world.addService(new PhysicsHost({ capabilities: [gravity(), m] }));
host.enable('gravity'); host.enable('media');
m.addMedium({ ...EXAMPLE_MEDIA.water, surface: 10 });                  // a lake with its top at y = 10

const ball = (name: string, x: number, density: number) => {
  const r = 0.5;
  const e = world.spawn(name, { position: new BABYLON.Vector3(x, 11, 0) });
  e.add('RigidBody', { shape: 'sphere', radius: r, mass: density * (4 / 3) * Math.PI * r ** 3, linearDamping: 4 });
  return e.get(RigidBody)!.body;
};
const wood = ball('wood', 0, 600), lead = ball('lead', 3, 3000);
world.run(12);
const draft = submergedFraction(wood.position[1], 0.5, 10);
```

Wood of density 600 settles with 60 % of its volume under: ρ_body / ρ_water, as Archimedes says. Lead sinks and falls at the terminal speed its drag allows.

## Recipes

### Wind that gusts

`wind` is a vector at a reference height (`windRefHeight`, 10 m); `windExponent` gives a power-law profile with height (0 = uniform); `gust: { amplitude, period, seed? }` adds a deterministic gust along it: the same seed gives the same gusts in every run and on every client.

```ts
import { PhysicsHost, RigidBody } from '@ferret/physics';
import { media, uniformAtmosphere } from '@ferret/physics-media';

const host = world.addService(new PhysicsHost({ capabilities: [media()] }));
host.kernel.gravity = [0, 0, 0];
host.enable('media');
host.config('media').set('world', 'atmosphere', uniformAtmosphere(1.2));
host.config('media').set('world', 'wind', [10, 0, 0]);
const leaf = world.spawn('leaf', { position: new BABYLON.Vector3(0, 5, 0) });
leaf.add('RigidBody', { shape: 'sphere', radius: 0.1, mass: 50 * (4 / 3) * Math.PI * 0.001 });
world.run(60);
```

A body in a flow is carried toward the flow's speed and never past it: drag acts on the *relative* velocity.

### A river or a current

`m.addMedium({ ...EXAMPLE_MEDIA.water, surface: 100, flow: [0, 0, 2] })`: anything in the water drifts toward 2 m/s along z.

### Mud, honey and tar pits

Same call with a different medium: `EXAMPLE_MEDIA.mud` (1800 kg/m³, 0.5 Pa·s) or `honey` (10 Pa·s). The high viscosity gives Stokes drag: a small sphere sinks at `2 r² (ρs − ρ) g / (9 μ)`, slowly. Limit a bog to a place with `zone: 'bog'`. For *soil* that deforms, sinks under load and floods, use terrain (skill `ferret-physics-terrain`); mud as a medium is drag and buoyancy only.

### Walking, wading and swimming

The character controller samples the medium at the character: air changes nothing, water slows a runner to what its push can do. A level that puts `water` with a `surface` over a pool therefore makes wading work with no code.

### Vacuum and other planets

`host.config('media').set('world', 'air', false)` makes a vacuum: no drag at all, a rock keeps its speed. Another planet's air is an `Atmosphere`: `standardAtmosphere` (the International Standard Atmosphere up to 47 km), `exponentialAtmosphere(seaLevelDensity, scaleHeight, temperature)` for a game world, `uniformAtmosphere(density)` for a closed hall, `vacuum`. `seaLevel` is the y of altitude zero.

### A closed volume of gas

`GasVolume` is a bounded amount of gas obeying p·V = n·R·T: a balloon envelope, an air pocket in a ballast tank, a diving bell. It is *one pressure and one temperature for the whole volume*, not a flow.

```ts
import { GasVolume, standardAtmosphere } from '@ferret/physics-media';

const g = GasVolume.ofAir(standardAtmosphere, 0, 2);        // 2 m³ of sea-level air
const p0 = g.pressure, T0 = g.temperature;
g.heat(g.cv * 30);                                           // +30 K at constant volume
const hot = g.pressure;
const squeezed = GasVolume.ofAir(standardAtmosphere, 0, 2);
squeezed.setVolume(1, 'adiabatic');                          // halve the volume without letting the heat out
```

`heat(joules, 'volume' | 'pressure')`, `setVolume(v, 'isothermal' | 'adiabatic')`, `addMoles(dn)` (a leak lowers the pressure). Vessels use gas volumes for ballast and buoyancy; see vehicles (skill `ferret-physics-vehicles`).

## Parameters

| Value | Default | Effect |
|---|---|---|
| `air` | true | air is a medium; false = vacuum |
| `atmosphere` | `standardAtmosphere` | density, pressure, temperature by altitude |
| `seaLevel` | 0 | y of altitude zero |
| `wind` | `[0,0,0]` | m/s at `windRefHeight` |
| `windRefHeight`, `windExponent` | 10, 0 | power-law profile; 0 = the same wind at every height |
| `gust` | none | `{ amplitude, period, seed? }` along the wind |
| `media` | `[]` | list of `MediumDef`; `addMedium` / `removeMedium` edit it |
| `cd` | by Reynolds number | a constant drag coefficient instead of the law; an entity may set its own |
| `angularDrag` | true | rotational drag |
| `samples` | 4 | how many points a body is sampled at when it is part in, part out of a medium |

All scoped (world, level, zone, entity), so one room can be windy and one boat can be draggy.

## Commands

`media.add {name, density, viscosity, flow?, surface?, zone?}`, `media.remove {name}`, `media.at {position}` (the medium there), `atmosphere.at {altitude}` (density, pressure, temperature), `wind.set {vector, gust?, scope?}`.

## Interplay

- **`gravity`** gives the direction and strength buoyancy acts against; with it off, the kernel's constant is used.
- **`terrain`** reports `terrain:flooded` when a dig reaches the water table. It does not add a medium by itself: put a `water` medium over the flooded area (a `surface` at the water table's height) so things float in it.
- **Vessels** (`vehicles`) compute their own weight and buoyancy from hull volume and are marked `exempt` from `gravity`, `media` and `surface`; read vehicles (skill `ferret-physics-vehicles`) for how the water they sit in acts on them.
- **Characters** read `fields.medium` and `fields.column` (how much of a vertical segment stands in a dense medium) to wade and swim.

## Budget

A medium lookup plus a few samples per body per step. Cost grows with the number of *bodies in a dense medium*, not with the number of media. A hundred bodies in a lake is cheap; if the step runs long, lower `samples`.

## Debugging

| Symptom | Cause |
|---|---|
| A body sinks that should float | its density is above the medium's (mass / volume): check the mass against the shape and the units |
| It floats but bobs forever | no damping: water drag damps it only as long as it moves; give `linearDamping` for a crude fix, or let the quadratic drag work (do not set `cd` to 0) |
| Nothing happens in the water | `media` is not **enabled**, or the body is not inside the medium's region: `media.at` at the body's position tells which medium it is in |
| A pool medium is everywhere | with neither `surface`, `zone` nor `bounds` a medium fills everything: give it a place |
| Wind does nothing in a closed room | `bounds` or a zone limit the *dense* media; the wind is air's: set `wind` in the room's zone scope |
| A body in a vacuum keeps its speed | that is correct: `air: false` removes drag entirely |

## Reference

`media`, `MediaCapability`, `MediumDef`, `MEDIA_DEFAULTS`, `EXAMPLE_MEDIA`, `submergedFraction`, `sphereCd`, `gustAt`, `standardAtmosphere`, `exponentialAtmosphere`, `GasVolume`, `@ferret/physics-media`.
