# physics/engines.md

`@ferret/physics-engines` (PLAN-DYNAMICS.md stage A10). Both engines are a **torque source** for a drivetrain shaft (`d.drive(shaft, engine.torque)`); the torque comes from the cycle, not from an averaged power, so the pulsation, the dead centre and the stall come out of the physics.

## Steam

`SteamEngine({ bore, crank, rod, boiler, inlet, exhaustPort, back?, clearance?, phases?, gear?, reciprocatingMass?, mounts? })` is one or more double-acting `SteamCylinder`s on one crank (`phases`: the crank pin of each; a locomotive has `[0, π/2]`).

- Each end of the cylinder is a mass of steam as a perfect gas (γ = 1.3, the model of `steamWork`), fed from a `Boiler` through a port, exhausting to `back` (the atmosphere by default, or a condenser pressure).
- The **valve gear is a function of the crank angle**: `gear = { cutoff, lead, release, compression, reverse }`. Cut-off, release and compression are shares of the stroke; `lead` is a crank angle. The controls change the gear while the engine runs. `reverse` takes the events in the other turning direction, so the engine runs backwards.
- Energy closes: the boiler gives up the steam's real enthalpy; the chamber gets the perfect-gas enthalpy `cp·T` (the latent heat the model does not use leaves with the exhaust, as it does in a real engine) and does `p dV` on the piston. The work per turn is the area of the indicator diagram, within 20 % of the ideal one for the cut-off (tested).
- At standstill an open port still fills the chamber, so a two-cylinder engine at 90° starts from either dead centre; a single cylinder can stick on the centre.

## Combustion

`MultiCylinderEngine({ cylinder: EngineSpec, count, order?, cycle?, reciprocatingMass?, mounts? })` puts `count` `PistonEngine`s on one crank. `order` is the firing order from 1 (`[1, 3, 4, 2]`); each cylinder is shifted in the crank angle so that it fires at `i·4π/n` (`firing` lists the angles). The crank pins follow from the order.

Controls (also `EngineControl`, what the forms use): `throttle(v)`, `ignition(advance)` (rad before top dead centre, moved while running), `fuel(on)` (off is the ignition cut), `cutCylinder(i, cut)`.

## Housings and mounts

Give the shaft a `housing` (A0) and whatever the engine does to the shaft acts the other way on the block. `engine.applyInertia(omega, theta, mount)` puts the unbalanced forces of the reciprocating masses on the mounts: along each cylinder's `axis` at its `point`, `-m·a` of the piston at the shaft's speed. A single cylinder shakes at the first order; the primary forces of an inline four cancel and the secondary ones add (`4·m·r·(r/l)·ω²`).

## In the language

`engineForms(bridge, { engines, boilers })` registers, with argument checks at load:

| word | |
|---|---|
| `(throttle e v)` `(cutoff e v)` `(reverse e on)` `(ignition e rad)` `(fuel e on)` | controls; an engine without that control refuses the call |
| `(engine-rpm e)` `(pressure b)` `(level b)` | sensors |

A centrifugal governor is a program: `(loop (cutoff "engine" (clamp (+ 0.3 (* 0.03 (- 25 (speed-now)))) 0.1 0.9)) (wait 0.02))`. A starter is a program that cranks until the engine catches: `(seq (starter #t) (wait-until (> (engine-rpm "car") 700)) (starter #f))`.
