Chemistry · class
AtmosphereField
Explained in Chemistry.
class AtmosphereFieldimport { AtmosphereField } from '@driftengine/chemistry';Constructor
new
constructor(registry: SpeciesRegistry, options?: AtmosphereOptions)| Parameter | Type | Description |
|---|---|---|
registry | SpeciesRegistry | |
options? | AtmosphereOptions |
Properties
| Name | Type | Description |
|---|---|---|
cellSizereadonly | number | |
cellVolumereadonly | number | |
ambientreadonly | AmbientState | |
speciesreadonly | Int32Array | Global species indices this field tracks, in storage order. |
maxChunksreadonly | number | One cell of untouched air, filled once and copied into every new cell. |
Accessors
| Name | Type | Description |
|---|---|---|
chunkCountget | number | |
speciesCountget | number | How many species this field tracks. |
Methods
slotOf
slotOf(species: number): numberThe local slot a global species occupies, or -1.
| Parameter | Type | Description |
|---|---|---|
species | number |
addSpecies
addSpecies(x: number, y: number, z: number, species: number, kilograms: number): voidAdd (or with a negative value, remove) mass of one species at a point.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number | |
species | number | |
kilograms | number |
addHeat
addHeat(x: number, y: number, z: number, joules: number): void| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number | |
joules | number |
addSoot
addSoot(x: number, y: number, z: number, kilograms: number): void| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number | |
kilograms | number |
addAerosol
addAerosol(x: number, y: number, z: number, kilograms: number): void| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number | |
kilograms | number |
setBlocked
setBlocked(x: number, y: number, z: number, blocked: boolean): voidMark a cell as occupied by geometry. A blocked face transports nothing.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number | |
blocked | boolean |
isBlocked
isBlocked(x: number, y: number, z: number): boolean| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
speciesMassAt
speciesMassAt(x: number, y: number, z: number, species: number): number| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number | |
species | number |
sootAt
sootAt(x: number, y: number, z: number): numberKilograms of soot in the cell: the C(soot) its own chemistry made, plus anything added.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
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): numberKilograms of suspended droplets in the cell — the pale half of smoke.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
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): numberThe upward speed this cell's own lightness gives it, m/s.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
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): numberTotal moles of gas in the cell containing a point.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
enthalpyAt
enthalpyAt(x: number, y: number, z: number): numberJoules held by the cell containing a point.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
massAt
massAt(x: number, y: number, z: number): numberkg of gas in the cell containing a point.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
temperatureAt
temperatureAt(x: number, y: number, z: number): numberK of the cell containing a point.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
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): numberVolume fraction of one species, which for a gas is its mole fraction.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number | |
species | number |
fuelFractionAt
fuelFractionAt(x: number, y: number, z: number): numberVolume fraction of the cell that is gas which can burn.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
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| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
concentrationAt
concentrationAt(x: number, y: number, z: number, species: number): numberParts per million by volume, which is the reading a game acts on.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number | |
species | number |
densityAt
densityAt(x: number, y: number, z: number): numberkg/m³.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
pressureAt
pressureAt(x: number, y: number, z: number): numberPa, by the ideal gas law over what the cell holds at the temperature it is.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
smokeDensityAt
smokeDensityAt(x: number, y: number, z: number): numberHow thick the smoke is, as an extinction coefficient in inverse metres.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
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): numberHow far you can see, metres.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
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): numberRelative humidity, 0 to 1: the water vapour present against what this air could hold.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
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): numberKilograms of one species above what the ambient would have held. Diagnostics and tests.
| Parameter | Type | Description |
|---|---|---|
species | number |
step
step(dt: number, windX?: number, windZ?: number): voidAdvance the field one step: expansion, buoyancy, wind, diffusion and the far field.
| Parameter | Type | Description |
|---|---|---|
dt | number | |
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): voidMoles of each element the field holds, over every live chunk. Writes into out.
| Parameter | Type | Description |
|---|---|---|
out | Float64Array |
totalMass
totalMass(): numberTotal gas mass across every live chunk, kg.
ventedMass
ventedMass(species: number): numberKilograms of one species that have left through an open boundary.
| Parameter | Type | Description |
|---|---|---|
species | number |
ventedEnthalpy
ventedEnthalpy(): numberJoules that have left through an open boundary.
ventedTotalMass
ventedTotalMass(): numberKilograms that have crossed the far-field boundary in total, in either direction.
ventedElementTotals
ventedElementTotals(out: Float64Array): voidMoles of each element that have crossed the far-field boundary. Writes into out.
| Parameter | Type | Description |
|---|---|---|
out | Float64Array |
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(): numberTotal enthalpy across every live chunk, J.