Chemistry · class

AtmosphereField

Explained in Chemistry.

class AtmosphereField
import { AtmosphereField } from '@driftengine/chemistry';

Constructor

new

constructor(registry: SpeciesRegistry, options?: AtmosphereOptions)
ParameterTypeDescription
registrySpeciesRegistry
options?AtmosphereOptions

Properties

NameTypeDescription
cellSizereadonlynumber
cellVolumereadonlynumber
ambientreadonlyAmbientState
speciesreadonlyInt32ArrayGlobal species indices this field tracks, in storage order.
maxChunksreadonlynumberOne cell of untouched air, filled once and copied into every new cell.

Accessors

NameTypeDescription
chunkCountgetnumber
speciesCountgetnumberHow many species this field tracks.

Methods

slotOf

slotOf(species: number): number

The local slot a global species occupies, or -1.

ParameterTypeDescription
speciesnumber

addSpecies

addSpecies(x: number, y: number, z: number, species: number, kilograms: number): void

Add (or with a negative value, remove) mass of one species at a point.

ParameterTypeDescription
xnumber
ynumber
znumber
speciesnumber
kilogramsnumber

addHeat

addHeat(x: number, y: number, z: number, joules: number): void
ParameterTypeDescription
xnumber
ynumber
znumber
joulesnumber

addSoot

addSoot(x: number, y: number, z: number, kilograms: number): void
ParameterTypeDescription
xnumber
ynumber
znumber
kilogramsnumber

addAerosol

addAerosol(x: number, y: number, z: number, kilograms: number): void
ParameterTypeDescription
xnumber
ynumber
znumber
kilogramsnumber

setBlocked

setBlocked(x: number, y: number, z: number, blocked: boolean): void

Mark a cell as occupied by geometry. A blocked face transports nothing.

ParameterTypeDescription
xnumber
ynumber
znumber
blockedboolean

isBlocked

isBlocked(x: number, y: number, z: number): boolean
ParameterTypeDescription
xnumber
ynumber
znumber

speciesMassAt

speciesMassAt(x: number, y: number, z: number, species: number): number
ParameterTypeDescription
xnumber
ynumber
znumber
speciesnumber

sootAt

sootAt(x: number, y: number, z: number): number

Kilograms of soot in the cell: the C(soot) its own chemistry made, plus anything added.

ParameterTypeDescription
xnumber
ynumber
znumber
More

Two sources on purpose. Soot a fire produced is the C(soot) species, which keeps its carbon inside elementTotals — a channel carrying mass outside the ledger would make the most valuable assertion in the track quietly wrong every time something smoked. The channel is for a consumer who wants smoke without chemistry behind it: a smoke grenade, a chimney, a dust cloud.

aerosolAt

aerosolAt(x: number, y: number, z: number): number

Kilograms of suspended droplets in the cell — the pale half of smoke.

ParameterTypeDescription
xnumber
ynumber
znumber
More

Soot is near-black and a droplet is near-white, so the ratio between them is what colours a plume: §17 reads it, and a wet fire steams white where a rich one smokes black with neither being chosen. Symmetric with sootAt, and for the same two reasons.

Two sources, exactly as soot has. Anything the chemistry condensed is a species — wood tar coming out of a cooling plume is the case this was built for — so its mass stays inside elementTotals and the most valuable assertion in the track keeps covering it. The channel addAerosol writes is for a consumer who wants steam without a boiler behind it: a kettle dressed for a scene, a waterfall's spray, a breath on a cold morning.

riseAt

riseAt(x: number, y: number, z: number): number

The upward speed this cell's own lightness gives it, m/s.

ParameterTypeDescription
xnumber
ynumber
znumber
More

The field's own number rather than a second one. §17 gives a smoke particle its velocity from "the cell's advected plus buoyant velocity", and this is the buoyant half — the same function transport uses, so a particle rises at the speed the gas carrying it actually rises at rather than at a rate chosen to look right.

molesAt

molesAt(x: number, y: number, z: number): number

Total moles of gas in the cell containing a point.

ParameterTypeDescription
xnumber
ynumber
znumber

enthalpyAt

enthalpyAt(x: number, y: number, z: number): number

Joules held by the cell containing a point.

ParameterTypeDescription
xnumber
ynumber
znumber

massAt

massAt(x: number, y: number, z: number): number

kg of gas in the cell containing a point.

ParameterTypeDescription
xnumber
ynumber
znumber

temperatureAt

temperatureAt(x: number, y: number, z: number): number

K of the cell containing a point.

ParameterTypeDescription
xnumber
ynumber
znumber
More

ambientTemperatureExact rather than this.ambient.temperature, and the difference is a determinism bug rather than a rounding nicety. A cell nobody has touched is not stored; one a neighbour realised is. Returning the authored 293.15 for the first and the derived 293.15000000000003 for the second made a cell's reported temperature depend on whether some other parcel had happened to create the chunk first — which put an order dependence into convection, and from there into whether a log ignites on tick N or tick N+1.

Found by world/fingerprint.test.ts: two logs spawned the other way round diverged in the eleventh digit on their very first tick, and nothing about them was different.

volumeFractionAt

volumeFractionAt(x: number, y: number, z: number, species: number): number

Volume fraction of one species, which for a gas is its mole fraction.

ParameterTypeDescription
xnumber
ynumber
znumber
speciesnumber

fuelFractionAt

fuelFractionAt(x: number, y: number, z: number): number

Volume fraction of the cell that is gas which can burn.

ParameterTypeDescription
xnumber
ynumber
znumber
More

What a flammability limit is checked against. Below the lower limit there is not enough fuel to carry a flame; above the upper there is not enough air, which is why a fuel-soaked rag smokes rather than flames until it thins out.

oxygenFractionAt

oxygenFractionAt(x: number, y: number, z: number): number
ParameterTypeDescription
xnumber
ynumber
znumber

concentrationAt

concentrationAt(x: number, y: number, z: number, species: number): number

Parts per million by volume, which is the reading a game acts on.

ParameterTypeDescription
xnumber
ynumber
znumber
speciesnumber

densityAt

densityAt(x: number, y: number, z: number): number

kg/m³.

ParameterTypeDescription
xnumber
ynumber
znumber

pressureAt

pressureAt(x: number, y: number, z: number): number

Pa, by the ideal gas law over what the cell holds at the temperature it is.

ParameterTypeDescription
xnumber
ynumber
znumber

smokeDensityAt

smokeDensityAt(x: number, y: number, z: number): number

How thick the smoke is, as an extinction coefficient in inverse metres.

ParameterTypeDescription
xnumber
ynumber
znumber
More

The quantity visibilityAt is derived from, and the one a consumer attenuating a light along a ray actually needs: metres of visibility is one answer to it and exp(−σ·d) is another. Exporting only the first would have every consumer inverting Koschmieder to get back here.

Soot and droplets both. It counted soot alone, so a smoulder's smoke, which is droplets, hid nothing and was drawn at an alpha of about 1e-29 by emitSmoke: the pale half of smoke was in the field and nowhere on screen.

visibilityAt

visibilityAt(x: number, y: number, z: number): number

How far you can see, metres.

ParameterTypeDescription
xnumber
ynumber
znumber
More

Koschmieder over the smoke's extinction, soot and droplets. Clamped at ten kilometres, past which the answer stops being about the smoke.

humidityAt

humidityAt(x: number, y: number, z: number): number

Relative humidity, 0 to 1: the water vapour present against what this air could hold.

ParameterTypeDescription
xnumber
ynumber
znumber
More

The reading that decides whether anything dries. §8.1's evaporation is driven by the vapour pressure deficit, so washing dries in wind and nothing dries in fog — and the same number is why a fire dries a room out while removing no water at all: warm air could hold more, so the same vapour is a smaller fraction of saturation.

Saturation is tabulated, per §7's rule, because it is exp of a rational function of temperature and this is a query a script may make every frame. 256 knots over 200-500 K, where linear interpolation is under a tenth of a percent.

totalAdded

totalAdded(species: number): number

Kilograms of one species above what the ambient would have held. Diagnostics and tests.

ParameterTypeDescription
speciesnumber

step

step(dt: number, windX?: number, windZ?: number): void

Advance the field one step: expansion, buoyancy, wind, diffusion and the far field.

ParameterTypeDescription
dtnumber
windX?number
windZ?number
More

Every transfer is flux-form and conservative — computed once for a face and applied to both sides — which is a departure from §9 worth recording. The design chose semi-Lagrangian advection for unconditional stability; semi-Lagrangian is not conservative, and conservation is the invariant this whole track is asserted on. Upwind flux has a CFL limit instead, and a CFL limit is handled here the same way stiffness is handled in the reaction solver: by sub-stepping. That is the trade taken deliberately.

Deltas are accumulated and applied afterwards, so no cell sees a neighbour that an earlier cell in the sweep already changed. A Gauss-Seidel sweep would give an answer that depended on chunk allocation order, which is a determinism hole with no symptom until two runs allocate differently.

windX and windZ come from the one WindField, sampled once per frame by the caller.

elementTotals

elementTotals(out: Float64Array): void

Moles of each element the field holds, over every live chunk. Writes into out.

ParameterTypeDescription
outFloat64Array

totalMass

totalMass(): number

Total gas mass across every live chunk, kg.

ventedMass

ventedMass(species: number): number

Kilograms of one species that have left through an open boundary.

ParameterTypeDescription
speciesnumber

ventedEnthalpy

ventedEnthalpy(): number

Joules that have left through an open boundary.

ventedTotalMass

ventedTotalMass(): number

Kilograms that have crossed the far-field boundary in total, in either direction.

ventedElementTotals

ventedElementTotals(out: Float64Array): void

Moles of each element that have crossed the far-field boundary. Writes into out.

ParameterTypeDescription
outFloat64Array
More

Live chunks alone do not balance, and the reason is not transport: creating a chunk draws a chunk of air in from outside, which is matter appearing in the live set. The ledger records it, so elementTotals + ventedElementTotals is invariant under chunk creation as well as under everything else — and a conservation assertion over a growing plume is possible at all.

totalEnthalpy

totalEnthalpy(): number

Total enthalpy across every live chunk, J.