FERRETGame engine over Babylon.js
Ferret

Pneumatics: a network of gas volumes

@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 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:

lawwhat holds
Boyle–Mariottep·V stays within 1.5 % when compressed slowly against a wall (wall.hA large)
Gay-Lussac / Charlesheating at constant pressure keeps V/T, at constant volume p/T
Poissoncompressed fast and insulated, p·V^γ stays within 0.5 %
Avogadroequal moles, volume and temperature give equal pressure, whatever the gas
Daltonp 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.

linkcall
orifice, valve (open 0..1), pipe (pipe: { length, diameter, f? } turns friction into area)orifice, `link(id, { from, to, cdApipe }), setOpen`
check valvecheck(id, from, to, cdA)
regulator (holds the downstream set, 5 % band)regulator(id, from, to, cdA, set)
relief valverelief(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.)