# physics/pneumatics.md

`@ferret/physics-pneumatics` (PLAN-DYNAMICS.md stage A12). `PneumaticCircuit` joins closed volumes of gas with pipes, valves, regulators, compressors and cylinders.

## The gas

A node (`tank(id, { volume, pressure?, temperature?, mix?, wall? })`) holds **moles per species** and an internal energy `U`. Air is `{ N2: .79, O2: .21 }`; Any gas of the species library of chemistry (skill `ferret-physics-chemistry-nuclear`) can be named by formula (`CO2`, `Ar`, `He`, `H2`, `CH4`, `NH3`, `SO2`, `steam` for `H2O`, ...): the molar mass comes from the formula and the heat capacity from `cp(T)` of the species, so `gamma` follows the temperature of the node (a gas heated by a fast compression has a different `gamma`) and the energy `U`, the enthalpy that flows carry and the compressor work are all on `cp(T)`. `SPECIES` is the old table as a view of the library; `gasOf(formula)` gives `cp`, `cv`, `u`, `h`, `gamma`, `s`; `useGasLibrary(lib)` swaps the library. An unknown formula is refused with the list. The laws are tested by name:

| law | what holds |
|---|---|
| Boyle–Mariotte | `p·V` stays within 1.5 % when compressed slowly against a wall (`wall.hA` large) |
| Gay-Lussac / Charles | heating at constant pressure keeps `V/T`, at constant volume `p/T` |
| Poisson | compressed fast and insulated, `p·V^γ` stays within 0.5 % |
| Avogadro | equal moles, volume and temperature give equal pressure, whatever the gas |
| Dalton | `p` is the sum of `partial(species)`; a node's `fraction(sp)` is the mole share |

The wall (`hA` J/(s·K), `T`) exchanges heat exactly, so a slowly filled tank stays near the room temperature and a fast one warms (tested: +30 K fast, within 5 K slow).

## Flow

`gasMassFlow(p0, T0, p1, area, { gamma, R, Cd })` in `@ferret/physics-media` is the one orifice law (subsonic isentropic, **choked** below the critical ratio); combustion chambers use the same function. A donor loses moles and cools isentropically; the receiver gets the donor's enthalpy, so the energy closes. Sub-steps are limited to a share of the donor and half way to equal pressure, so a stiff small volume does not overshoot.

| link | call |
|---|---|
| orifice, valve (`open` 0..1), pipe (`pipe: { length, diameter, f? }` turns friction into area) | `orifice`, `link(id, { from, to, cdA | pipe })`, `setOpen` |
| check valve | `check(id, from, to, cdA)` |
| regulator (holds the downstream `set`, 5 % band) | `regulator(id, from, to, cdA, set)` |
| relief valve | `relief(id, from, to, cdA, crack)` |
| compressor (`rate` m³/s at inlet, `maxPressure`, `efficiency`; heats adiabatically, `link.work` counts its work) | `compressor(id, from, to, { … })`, `setOn` |

`air` is the atmosphere and never changes.

## Cylinders

`cylinder(id, { bore, rod, stroke, mass, dead?, x?, pressure?, vented?, coulomb?, viscous?, load? })` has chambers `id.cap` and `id.rod` (or `vented: true`: the rod side is open to the air, an air spring or a gun barrel). The piston is a mass; the gas does `p dV` on it adiabatically (or isothermally if the wall conducts): an air spring is softer isothermal than adiabatic and stiffer at small volume (tested); an air gun's best kinetic energy matches the adiabatic work less the work against the atmosphere within 5 %. With `attach: { a, b, axis }` in a level it pushes two bodies apart like the hydraulic one.

## Henry

`liquid(nodeId, { liquidVolume, henry: { CO2: 3.3e-4 }, rate, equilibrium? })` puts a liquid under the gas head: it dissolves `k·partial pressure·V_liquid` at `rate` both ways. Without `henry`, `liquid(nodeId, { liquidVolume, gases: ['CO2'] })` reads the constants of the aqueous set (`henryConstant(gas, T)`, with their temperature dependence, for CO2, N2, O2, Ar, He, Ne, H2, CH4, CO, NH3, SO2, H2S, Cl2, NO); `EXAMPLE_LIQUIDS.water.henry` is the same table at 298 K. With `vessel: <a Vessel of physics-chemistry>` the dissolved gas is that vessel's `CO2(aq)`: one amount for both solvers, the vessel reacts (carbonic acid, bicarbonate) and its `pH` falls as the gas goes in; `inventory` counts the carbon of the carbonates too. A bottle opened for a moment loses CO2, and closed again its head rises as the gas comes out (tested); `circuit.inventory(sp)` counts gas plus dissolved and does not lose moles.

## In a level and in the language

`physics.pneumatics`: a list of circuits `{ name, tanks, links, cylinders }`; errors name the node or gas. Commands: `pneumatics.state`, `.open`, `.compressor`, `.regulator`, `edit.physics.pneumatics.set/remove`. Forms: `(gas-pressure "c" "node")`, `(gas-temperature …)`, `(gas-valve "c" "link" x)`, `(compressor "c" "link" #t)`, `(regulator "c" "link" pa)`. (`pressure` stays the boiler word of the engines.)
