# Vehicles

# Vehicles

Package `@ferret/physics-vehicles`, capability name `vehicles`, phase `field` (order 980: after the other force fields).

## What it is for

Things that float, dive or fly by displacement: a boat that rights itself or capsizes, a submarine that holds depth with its ballast tank and breaks at its crush depth, a ship that floods through a breach and settles, a sailboat that sails upwind to the angle the polar allows, a hot-air balloon that rises to where its lift equals its weight. It is not a car or aircraft model: there are no tyres or wings-and-engine flight here (a [control surface](#rudders-foils-sails) is a foil in water or air).

## The model

A `Vessel` is **one rigid body with parts**. The parts read the water and the air and write forces:

| Part | What it is |
|---|---|
| **hull** | a box divided into *cells* that are tested against the water surface, so buoyancy and the centre of buoyancy come from the real submerged shape (draft, heel and trim follow) |
| **tanks** | ballast tanks with a volume, a position and a fill; `fill` and `blow` move the weight and the centre of gravity |
| **compartments, breaches, doors** | spaces that flood through holes at the Torricelli rate, and between each other through doors; free surface and sloshing are modelled |
| **envelopes** | `hot` (a burner heats the air inside) or `gas` (a lighter gas that expands with height and vents when full) |
| **thrusters** | axis, max thrust, medium (water or air), optional speed limit |
| **surfaces** | foils and rudders in water or air; sails |

Body axes: **x to starboard, y up, z to the bow**. The vessel integrates its own six degrees of freedom. On a rigid-body kernel (Havok) it **also has a body** there, so it meets the shore, a quay and other vessels; on the light kernel (no dynamic boxes) it runs on its own integrator and meets nothing. Everything is SI.

## First use: a boat on the water

```ts
import { Vessel, G, type VesselSpec } from '@ferret/physics-vehicles';

const size: [number, number, number] = [4, 1, 10], m = 12000;           // a 4 x 1 x 10 m box hull, 12 t
const spec: VesselSpec = {
  mass: m,
  inertia: [m * (size[1] ** 2 + size[2] ** 2) / 12, m * (size[0] ** 2 + size[2] ** 2) / 12, m * (size[0] ** 2 + size[1] ** 2) / 12],
  hull: { size, cells: [8, 8, 12] },
  position: [0, 0.5 - 0.2, 0],                                           // dropped with 0.2 m of draft
  hullDamping: [0, 1e5, 0],
};
const boat = new Vessel(spec);
boat.step(1 / 120, 1);                                                     // second argument: sub-steps
```

The box floats at the draft `m/(ρA)`, which is 0.3 m here (Archimedes), and without damping it heaves with the period `2π√(m/(ρgA))`.

Readouts a game needs: `draft`, `depth` (of the gauge point), `altitude`, `heel` and `trim` (radians), `pressure` (Pa at the gauge), `wetShare`, `total` (mass now), and `stability()` which returns `draft`, `volume`, `kb`, `kg`, `bm`, **`gm`** (metacentric height, with free-surface correction) and `freeSurface`. The hull rights itself when `gm > 0` and rolls away when it is negative.

## Put it in the world

```ts
import { PhysicsHost } from '@ferret/physics';
import { vehicles } from '@ferret/physics-vehicles';

const cap = vehicles();
const host = world.addService(new PhysicsHost({ capabilities: [cap] }));
host.enable('vehicles');
const e = world.spawn('sub', { position: new BABYLON.Vector3(0, 0, 0) });
const size: [number, number, number] = [4, 4, 20], dry = 250000;
const sub = cap.addVessel('sub', {
  mass: dry, cog: [0, -0.5, 0],
  inertia: [dry * (16 + 400) / 12, dry * (16 + 16) / 12, dry * (16 + 400) / 12],
  hull: { size, cells: [8, 8, 12] },
  tanks: [{ name: 'main', volume: 80, at: [0, -1, 0], rate: 5 }],
  hullDamping: [2e5, 4e5, 2e5], angularDamping: [1e7, 1e7, 1e7],
}, e);
world.events.on('vessel:submerged', () => console.log('under'));
await world.commands.execute('vessel.fill', { vessel: 'sub', tank: 'main', fraction: 1 });
```

`addVessel(name, spec, entity?, { body?, material? })` registers the vessel, steps it with the world and moves the entity's node to follow it. `body: false` keeps it on its own integrator even over Havok.

## Recipes

### Ballast: dive, hold, surface

A submarine's tank fills toward `fraction` at its `rate` (m³/s): `sub.fill('main', 0.875)` for neutral buoyancy, `1` to dive, `sub.blow('main')` to empty it with compressed air. For the hull above (320 m³, 250 t dry, 80 m³ tank) neutral is a fill of `(320·1000 − 250000)/(80·1000)` = 0.875. Events: `vessel:submerged`, `vessel:surfaced`. Set `crushDepth` and it **breaks** (`vessel:crushed`, `sub.crushed`) when the depth passes it; `pressure` follows `101325 + ρ g depth`.

### A hole in the hull

`breach({ name, compartment, at, area, Cd? })` opens a hole into a compartment; water enters at the Torricelli rate `Cd·a·√(2 g h)`, the vessel settles by the added weight, and `vessel:breach` fires, later `vessel:flooded`. `doors: [{ name, a, b, at, area, open }]` between compartments: shut nothing passes, open the levels equalise and the volume is kept. A part-filled compartment takes metacentric height away by its **free surface**: a half-full tank makes a ship less stable than a full or an empty one.

### Ship dynamics that matter

- `addedMass: [x, y, z]`: the water dragged along when the hull accelerates, as a share of the displaced water: 0.05–0.2 along a slender hull, near 1 across a flat bottom. It lengthens the heave period by `√(1 + k)`.
- `addedInertia`: the same for rotation; lengthens the roll period by `√((I + Iₐ)/I)`.
- `hullDrag` (quadratic, per axis, ½ρ·Cd·A) and `hullDamping` (linear), `angularDamping`: scaled by the submerged share.
- Sloshing (`slosh` on a compartment): the water across a part-filled tank swings at `ω² = (π g/B) tanh(π h/B)`.

### Hot-air and gas balloons

`envelopes: [{ name, kind: 'hot', volume, temperature, burner (W), loss (W/K), vent }]`. Lift is `(ρ_air − ρ_gas) V g`; it rises to where lift equals weight in the standard atmosphere and sinks when the burner stops: `vessel.burner { on }`. A `gas` envelope holds `moles` of a lighter gas, expands with height until full, then vents. Altitude is `vessel.altitude`. Drag against wind uses `dragArea` and `dragCd`.

### Thrusters, and driving by command

A thruster `{ name, at, axis, maxThrust, medium, maxSpeed? }` is commanded 0..1 (negative for reverse): `vessel.thrust { vessel, part, command }`. `maxSpeed` is where thrust is gone (∞ = constant); `source: (u) => N` lets a machine supply it (see links (skill `ferret-physics-links`): a screw on a shaft).

### Rudders, foils, sails

A **surface** `{ name, at, area, hinge, chord, medium, clAlpha, stall, cd0, k }` is a foil: lift per radian `clAlpha` (2π·AR/(AR+2) for a finite foil), stall angle, induced drag `Cd = cd0 + k·Cl²`. A rudder is a surface in water turned by `vessel.surface { vessel, part, angle }`; a **sail** is a surface in air, and `sailPolar` gives the polar a sailboat sails by: it makes way upwind to the angle the polar allows and holds a course with a keel (without one it slides sideways and is slower).

### Forces from outside

`vessel.applyForce(f, worldPoint?)` holds a force (N, world frame) for the step at a world point (the centre of gravity when none): an oar at its oarlock turns the boat by itself. `applyTorque(t)` holds a torque, `applyImpulse(j, worldPoint?)` changes the velocity and the spin at once. The lists clear after the step, so a source applies them again every step.

### The environment

`waterLevel` (m), `waterDensity` (kg/m³) and `wind` are scoped values of the capability; `cap.surface` is a function `(x, z, t) → y` when the sea is not flat (a wave field). `Environment` also carries the atmosphere, so a balloon's density follows the altitude.

## Parameters

`VesselSpec` fields (SI): `mass`, `cog`, `inertia` (principal, kg·m²), `hull`, `tanks`, `compartments`, `doors`, `addedMass`, `addedInertia`, `envelopes`, `thrusters`, `surfaces`, `position`, `orientation`, `velocity`, `crushDepth`, `hullDrag`, `hullDamping`, `angularDamping`, `gravity` (m/s², 9.80665; 0 holds a test vessel up), `gauge`.

| Capability value | Default | Effect |
|---|---|---|
| `waterLevel` | 0 | height of the water surface |
| `waterDensity` | 1000 | kg/m³ (1025 sea water) |
| `wind` | `[0,0,0]` | m/s at all heights |

`hull.cells`: how finely the hull is tested against the surface: `[8, 8, 12]` is a good default; more cells = smoother buoyancy and more cost.

## Commands and events

Commands: `vessel.info {vessel}` (draft, depth, altitude, heel, trim, metacentric height, buoyancy against weight, tanks), `vessel.fill`, `vessel.blow`, `vessel.thrust`, `vessel.surface`, `vessel.burner`, `vessel.breach`. Events: `vessel:breach`, `vessel:flooded`, `vessel:crushed`, `vessel:surfaced`, `vessel:submerged`, `vessel:capsized`.

## Interplay

- Vessels compute their own weight and buoyancy, so the host marks their body `exempt` from `gravity`, `media` and `surface`; they read the water level and density from their own scoped values, not from a `media` medium.
- With Havok the vessel meets static geometry and other bodies; two boats that meet push each other apart, and a boat driven at a quay stops at it.
- `links` (skill `ferret-physics-links`): `screwOnShaft` drives a vessel's thruster from a shaft and loads the shaft with the torque the water takes back.

## Budget

A vessel costs its hull cells (default 8×8×12 ≈ 770 tests) per step plus the parts. A handful of vessels is cheap; hundreds is not: use a simple buoyancy (media (skill `ferret`)) for background boats and a vessel for the ones the player is in or near.

## Debugging

| Symptom | Cause |
|---|---|
| The boat floats too high or low | mass and hull size do not match: draft = mass / (ρ × waterplane area) |
| It rolls over | `gm` is negative: lower the centre of gravity (`cog`), widen the hull, empty part-filled compartments |
| It never settles | no `hullDamping` or `angularDamping`: set them (a real ship is damped by its wake) |
| Heave looks too fast | no `addedMass`: add 0.1–1 depending on the hull |
| A submarine drifts at "neutral" | fill is not exactly the neutral fraction: use `stability()`/`vessel.info` buoyancy against weight |
| The vessel passes through the shore | it is on the light kernel or `body: false`: use Havok |
| `vessel.breach` refused | the compartment name does not exist |

## Reference

`vehicles`, `VehiclesCapability`, `Vessel`, `VesselSpec`, `HullSpec`, `TankSpec`, `CompartmentSpec`, `BreachSpec`, `DoorSpec`, `EnvelopeSpec`, `ThrusterSpec`, `SurfaceSpec`, `Environment`, `sailPolar`, `VEHICLES_DEFAULTS`, `@ferret/physics-vehicles`.
