@driftengine/chemistry 3.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/README.md +194 -0
  4. package/dist/conservation.d.ts +9 -0
  5. package/dist/conservation.js +38 -0
  6. package/dist/element/elements.d.ts +31 -0
  7. package/dist/element/elements.js +73 -0
  8. package/dist/element/matrix.d.ts +27 -0
  9. package/dist/element/matrix.js +80 -0
  10. package/dist/field/ambient.d.ts +49 -0
  11. package/dist/field/ambient.js +82 -0
  12. package/dist/field/atmosphere.d.ts +308 -0
  13. package/dist/field/atmosphere.js +1033 -0
  14. package/dist/ignition/criteria.d.ts +97 -0
  15. package/dist/ignition/criteria.js +135 -0
  16. package/dist/ignition/events.d.ts +56 -0
  17. package/dist/ignition/events.js +90 -0
  18. package/dist/index.d.ts +84 -0
  19. package/dist/index.js +66 -0
  20. package/dist/library/biological.d.ts +6 -0
  21. package/dist/library/biological.js +229 -0
  22. package/dist/library/common.d.ts +26 -0
  23. package/dist/library/common.js +208 -0
  24. package/dist/library/food.d.ts +9 -0
  25. package/dist/library/food.js +272 -0
  26. package/dist/library/fuel.d.ts +19 -0
  27. package/dist/library/fuel.js +290 -0
  28. package/dist/library/library.d.ts +45 -0
  29. package/dist/library/library.js +29 -0
  30. package/dist/library/metal.d.ts +6 -0
  31. package/dist/library/metal.js +181 -0
  32. package/dist/library/mineral.d.ts +4 -0
  33. package/dist/library/mineral.js +177 -0
  34. package/dist/library/organic.d.ts +12 -0
  35. package/dist/library/organic.js +489 -0
  36. package/dist/library/polymer.d.ts +4 -0
  37. package/dist/library/polymer.js +237 -0
  38. package/dist/parcel/conduction.d.ts +20 -0
  39. package/dist/parcel/conduction.js +160 -0
  40. package/dist/parcel/shells.d.ts +49 -0
  41. package/dist/parcel/shells.js +73 -0
  42. package/dist/parcel/store.d.ts +395 -0
  43. package/dist/parcel/store.js +1152 -0
  44. package/dist/parcel/tier.d.ts +48 -0
  45. package/dist/parcel/tier.js +51 -0
  46. package/dist/present/audio.d.ts +41 -0
  47. package/dist/present/audio.js +50 -0
  48. package/dist/present/blackbody.d.ts +55 -0
  49. package/dist/present/blackbody.js +112 -0
  50. package/dist/present/flame.d.ts +34 -0
  51. package/dist/present/flame.js +103 -0
  52. package/dist/present/index.d.ts +25 -0
  53. package/dist/present/index.js +22 -0
  54. package/dist/present/install.d.ts +87 -0
  55. package/dist/present/install.js +157 -0
  56. package/dist/present/smoke.d.ts +63 -0
  57. package/dist/present/smoke.js +85 -0
  58. package/dist/present/surface.d.ts +40 -0
  59. package/dist/present/surface.js +76 -0
  60. package/dist/reaction/localise.d.ts +16 -0
  61. package/dist/reaction/localise.js +95 -0
  62. package/dist/reaction/rates.d.ts +59 -0
  63. package/dist/reaction/rates.js +101 -0
  64. package/dist/reaction/reaction.d.ts +178 -0
  65. package/dist/reaction/reaction.js +104 -0
  66. package/dist/reaction/registry.d.ts +47 -0
  67. package/dist/reaction/registry.js +215 -0
  68. package/dist/reaction/solver.d.ts +51 -0
  69. package/dist/reaction/solver.js +277 -0
  70. package/dist/species/registry.d.ts +26 -0
  71. package/dist/species/registry.js +97 -0
  72. package/dist/species/species.d.ts +48 -0
  73. package/dist/species/species.js +20 -0
  74. package/dist/species/standard.d.ts +30 -0
  75. package/dist/species/standard.js +436 -0
  76. package/dist/species/thermal.d.ts +18 -0
  77. package/dist/species/thermal.js +72 -0
  78. package/dist/substance/boiling.d.ts +29 -0
  79. package/dist/substance/boiling.js +54 -0
  80. package/dist/substance/composition.d.ts +34 -0
  81. package/dist/substance/composition.js +53 -0
  82. package/dist/substance/registry.d.ts +86 -0
  83. package/dist/substance/registry.js +187 -0
  84. package/dist/substance/substance.d.ts +94 -0
  85. package/dist/substance/substance.js +48 -0
  86. package/dist/testing/drift.d.ts +15 -0
  87. package/dist/testing/drift.js +29 -0
  88. package/dist/transport/contacts.d.ts +22 -0
  89. package/dist/transport/contacts.js +50 -0
  90. package/dist/transport/radiation.d.ts +101 -0
  91. package/dist/transport/radiation.js +199 -0
  92. package/dist/transport/world.d.ts +92 -0
  93. package/dist/transport/world.js +381 -0
  94. package/dist/world/fingerprint.d.ts +31 -0
  95. package/dist/world/fingerprint.js +70 -0
  96. package/package.json +100 -0
  97. package/src/conservation.ts +44 -0
  98. package/src/element/elements.ts +80 -0
  99. package/src/element/matrix.ts +89 -0
  100. package/src/field/ambient.ts +111 -0
  101. package/src/field/atmosphere.ts +1159 -0
  102. package/src/ignition/criteria.ts +177 -0
  103. package/src/ignition/events.ts +100 -0
  104. package/src/index.ts +213 -0
  105. package/src/library/biological.ts +252 -0
  106. package/src/library/common.ts +224 -0
  107. package/src/library/food.ts +302 -0
  108. package/src/library/fuel.ts +299 -0
  109. package/src/library/library.ts +62 -0
  110. package/src/library/metal.ts +186 -0
  111. package/src/library/mineral.ts +194 -0
  112. package/src/library/organic.ts +545 -0
  113. package/src/library/polymer.ts +243 -0
  114. package/src/parcel/conduction.ts +188 -0
  115. package/src/parcel/shells.ts +100 -0
  116. package/src/parcel/store.ts +1270 -0
  117. package/src/parcel/tier.ts +60 -0
  118. package/src/present/audio.ts +54 -0
  119. package/src/present/blackbody.ts +118 -0
  120. package/src/present/flame.ts +111 -0
  121. package/src/present/index.ts +25 -0
  122. package/src/present/install.ts +209 -0
  123. package/src/present/smoke.ts +139 -0
  124. package/src/present/surface.ts +107 -0
  125. package/src/reaction/localise.ts +126 -0
  126. package/src/reaction/rates.ts +153 -0
  127. package/src/reaction/reaction.ts +235 -0
  128. package/src/reaction/registry.ts +257 -0
  129. package/src/reaction/solver.ts +326 -0
  130. package/src/species/registry.ts +117 -0
  131. package/src/species/species.ts +53 -0
  132. package/src/species/standard.ts +449 -0
  133. package/src/species/thermal.ts +74 -0
  134. package/src/substance/boiling.ts +64 -0
  135. package/src/substance/composition.ts +81 -0
  136. package/src/substance/registry.ts +223 -0
  137. package/src/substance/substance.ts +148 -0
  138. package/src/testing/drift.ts +28 -0
  139. package/src/transport/contacts.ts +56 -0
  140. package/src/transport/radiation.ts +238 -0
  141. package/src/transport/world.ts +437 -0
  142. package/src/world/fingerprint.ts +106 -0
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Heat crossing empty space, which is how a fire reaches anything it is not touching.
3
+ *
4
+ * **Radiation dominates every fire beyond a few centimetres, and `§10` is the argument.** A flame
5
+ * at 1200 K radiates about 118 kW/m², and the fourth power means halving its temperature cuts that
6
+ * sixteenfold. A model without it cannot make a log ignite from across a hearth — which is the
7
+ * scenario the whole track exists for.
8
+ *
9
+ * **The view factor is a point source: `cos θ · A / (π r²)`.** It is wrong close in, where a flame
10
+ * is not a point, and it is right where it matters. What it buys is the single relationship this
11
+ * model is built on: **flux falls as `1/r²`**, so moving a log twice as far from a fire makes it
12
+ * take four times as long to catch. One line of arithmetic, and without it "leading a piece of wood
13
+ * near the fire" has no right answer at all.
14
+ */
15
+ /** W/(m²·K⁴). */
16
+ export declare const STEFAN_BOLTZMANN = 5.670374419e-8;
17
+ /**
18
+ * Radiant flux onto a surface, W/m².
19
+ *
20
+ * @param sourceTemperature K
21
+ * @param sourceArea m² of radiating surface
22
+ * @param surfaceTemperature K of the receiver
23
+ * @param emissivity of the receiver, 0..1
24
+ * @param distance m
25
+ * @param cosine of the angle between the receiver's normal and the line to the source
26
+ */
27
+ export declare function radiantFlux(sourceTemperature: number, sourceArea: number, surfaceTemperature: number, emissivity: number, distance: number, cosine: number): number;
28
+ /**
29
+ * How far a source of a given power is worth considering, m.
30
+ *
31
+ * From `P / (4π r²) = q_min`, so a **big fire reaches further than a small one** rather than every
32
+ * source sharing a constant radius. That is what stops a candle being tested against every parcel
33
+ * in a scene and a bonfire being cut off at the same distance.
34
+ */
35
+ export declare function sourceRange(power: number, minimumFlux: number): number;
36
+ /**
37
+ * Convective heat transfer coefficient from the local gas speed, W/(m²·K).
38
+ *
39
+ * Two to twenty-five in still air, and up to about two hundred and fifty in a gale. **Above a fire
40
+ * convection dominates radiation and beside one radiation dominates convection**, and the crossover
41
+ * is at the plume edge — which is why standing beside a bonfire is pleasant and standing in the
42
+ * smoke is not.
43
+ */
44
+ export declare function convectiveCoefficient(gasSpeed: number): number;
45
+ /** Whether anything solid stands between two points. */
46
+ export type OcclusionTest = (ax: number, ay: number, az: number, bx: number, by: number, bz: number) => boolean;
47
+ /**
48
+ * The radiating things in a scene this tick: flames and hot surfaces both.
49
+ *
50
+ * Gathered into columns once per tick and read many times, which is why it is a structure rather
51
+ * than a callback. Cleared and refilled rather than reallocated.
52
+ */
53
+ export declare class RadiativeSources {
54
+ private x;
55
+ private y;
56
+ private z;
57
+ private temperature;
58
+ private area;
59
+ private range;
60
+ private used;
61
+ get count(): number;
62
+ clear(): void;
63
+ /** @param power W, which decides how far this source is worth testing against. */
64
+ add(x: number, y: number, z: number, temperature: number, area: number, power: number): void;
65
+ temperatureOf(source: number): number;
66
+ areaOf(source: number): number;
67
+ /** Whether a point is inside this source's own reach. */
68
+ reaches(source: number, x: number, y: number, z: number): boolean;
69
+ /**
70
+ * Total flux onto a point from every source that can see it, W/m².
71
+ *
72
+ * The receiver is treated as facing each source — a parcel is a lump rather than a plane, so
73
+ * there is no one normal to take a cosine against. What that costs is that a flat plate edge-on
74
+ * to a fire receives as much as one facing it; what it buys is not asking a consumer for a normal
75
+ * per parcel, which is a number most of them do not have.
76
+ */
77
+ fluxAt(x: number, y: number, z: number, surfaceTemperature: number, emissivity: number, occluded: OcclusionTest | null): number;
78
+ /**
79
+ * The temperature of the hottest source that reaches a point, or zero where none does.
80
+ *
81
+ * **What a radiative exchange is bounded by**, and the reason it has to be bounded at all: the
82
+ * radiative term is explicit, so a parcel whose surface shell has a small heat capacity — a coin,
83
+ * a nail, a leaf — can absorb more in one step than would take it past the source that is heating
84
+ * it, and then radiate the excess back as `T⁴` and ring itself to pieces. It was found by putting
85
+ * a copper coin on a hearth in `demo/dev/chemistry.ts`, where it reached 4,000 K on the first step
86
+ * and 100 K a minute later.
87
+ *
88
+ * Nothing can be heated past the thing heating it, so this is the equilibrium the step may reach
89
+ * and not exceed — which is what an implicit solve would give and costs one comparison per source.
90
+ */
91
+ hottestAt(x: number, y: number, z: number, occluded: OcclusionTest | null): number;
92
+ /**
93
+ * Whether **any** source reaches a point at all, which is the whole of the wake test.
94
+ *
95
+ * `§14`'s scan has to be coarse or sleeping saves nothing: this is a range comparison per source
96
+ * and no view factor, no temperature and no occlusion ray. A source's range already comes from its
97
+ * own power, so a bonfire wakes a parcel a candle would not.
98
+ */
99
+ reachesPoint(x: number, y: number, z: number): boolean;
100
+ private grow;
101
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Heat crossing empty space, which is how a fire reaches anything it is not touching.
3
+ *
4
+ * **Radiation dominates every fire beyond a few centimetres, and `§10` is the argument.** A flame
5
+ * at 1200 K radiates about 118 kW/m², and the fourth power means halving its temperature cuts that
6
+ * sixteenfold. A model without it cannot make a log ignite from across a hearth — which is the
7
+ * scenario the whole track exists for.
8
+ *
9
+ * **The view factor is a point source: `cos θ · A / (π r²)`.** It is wrong close in, where a flame
10
+ * is not a point, and it is right where it matters. What it buys is the single relationship this
11
+ * model is built on: **flux falls as `1/r²`**, so moving a log twice as far from a fire makes it
12
+ * take four times as long to catch. One line of arithmetic, and without it "leading a piece of wood
13
+ * near the fire" has no right answer at all.
14
+ */
15
+ /** W/(m²·K⁴). */
16
+ export const STEFAN_BOLTZMANN = 5.670374419e-8;
17
+ /**
18
+ * How close a point source may be approached before the approximation is capped.
19
+ *
20
+ * A point source's flux is unbounded at zero distance, and the honest failure there is a large
21
+ * number rather than an infinite one. The clamp is the scale at which a real source stops looking
22
+ * like a point, which is its own size.
23
+ */
24
+ const MIN_DISTANCE = 0.05;
25
+ /**
26
+ * Radiant flux onto a surface, W/m².
27
+ *
28
+ * @param sourceTemperature K
29
+ * @param sourceArea m² of radiating surface
30
+ * @param surfaceTemperature K of the receiver
31
+ * @param emissivity of the receiver, 0..1
32
+ * @param distance m
33
+ * @param cosine of the angle between the receiver's normal and the line to the source
34
+ */
35
+ export function radiantFlux(sourceTemperature, sourceArea, surfaceTemperature, emissivity, distance, cosine) {
36
+ if (!(cosine > 0))
37
+ return 0;
38
+ const range = Math.max(distance, MIN_DISTANCE);
39
+ const viewFactor = (cosine * sourceArea) / (Math.PI * range * range);
40
+ const source = sourceTemperature * sourceTemperature * sourceTemperature * sourceTemperature;
41
+ const surface = surfaceTemperature * surfaceTemperature * surfaceTemperature * surfaceTemperature;
42
+ return emissivity * STEFAN_BOLTZMANN * viewFactor * (source - surface);
43
+ }
44
+ /**
45
+ * How far a source of a given power is worth considering, m.
46
+ *
47
+ * From `P / (4π r²) = q_min`, so a **big fire reaches further than a small one** rather than every
48
+ * source sharing a constant radius. That is what stops a candle being tested against every parcel
49
+ * in a scene and a bonfire being cut off at the same distance.
50
+ */
51
+ export function sourceRange(power, minimumFlux) {
52
+ if (!(power > 0) || !(minimumFlux > 0))
53
+ return 0;
54
+ return Math.sqrt(power / (4 * Math.PI * minimumFlux));
55
+ }
56
+ /**
57
+ * Convective heat transfer coefficient from the local gas speed, W/(m²·K).
58
+ *
59
+ * Two to twenty-five in still air, and up to about two hundred and fifty in a gale. **Above a fire
60
+ * convection dominates radiation and beside one radiation dominates convection**, and the crossover
61
+ * is at the plume edge — which is why standing beside a bonfire is pleasant and standing in the
62
+ * smoke is not.
63
+ */
64
+ export function convectiveCoefficient(gasSpeed) {
65
+ const speed = gasSpeed > 0 ? gasSpeed : 0;
66
+ const h = 5 + 12 * Math.sqrt(speed);
67
+ return h > 250 ? 250 : h;
68
+ }
69
+ /** Below this a source is not worth testing against anything, W/m². */
70
+ const MINIMUM_USEFUL_FLUX = 200;
71
+ /**
72
+ * The radiating things in a scene this tick: flames and hot surfaces both.
73
+ *
74
+ * Gathered into columns once per tick and read many times, which is why it is a structure rather
75
+ * than a callback. Cleared and refilled rather than reallocated.
76
+ */
77
+ export class RadiativeSources {
78
+ x = new Float64Array(32);
79
+ y = new Float64Array(32);
80
+ z = new Float64Array(32);
81
+ temperature = new Float64Array(32);
82
+ area = new Float64Array(32);
83
+ range = new Float64Array(32);
84
+ used = 0;
85
+ get count() {
86
+ return this.used;
87
+ }
88
+ clear() {
89
+ this.used = 0;
90
+ }
91
+ /** @param power W, which decides how far this source is worth testing against. */
92
+ add(x, y, z, temperature, area, power) {
93
+ if (this.used === this.x.length)
94
+ this.grow();
95
+ const at = this.used++;
96
+ this.x[at] = x;
97
+ this.y[at] = y;
98
+ this.z[at] = z;
99
+ this.temperature[at] = temperature;
100
+ this.area[at] = area;
101
+ this.range[at] = sourceRange(power, MINIMUM_USEFUL_FLUX);
102
+ }
103
+ temperatureOf(source) {
104
+ return this.temperature[source];
105
+ }
106
+ areaOf(source) {
107
+ return this.area[source];
108
+ }
109
+ /** Whether a point is inside this source's own reach. */
110
+ reaches(source, x, y, z) {
111
+ const dx = x - this.x[source];
112
+ const dy = y - this.y[source];
113
+ const dz = z - this.z[source];
114
+ const reach = this.range[source];
115
+ return dx * dx + dy * dy + dz * dz <= reach * reach;
116
+ }
117
+ /**
118
+ * Total flux onto a point from every source that can see it, W/m².
119
+ *
120
+ * The receiver is treated as facing each source — a parcel is a lump rather than a plane, so
121
+ * there is no one normal to take a cosine against. What that costs is that a flat plate edge-on
122
+ * to a fire receives as much as one facing it; what it buys is not asking a consumer for a normal
123
+ * per parcel, which is a number most of them do not have.
124
+ */
125
+ fluxAt(x, y, z, surfaceTemperature, emissivity, occluded) {
126
+ let total = 0;
127
+ for (let source = 0; source < this.used; source++) {
128
+ if (!this.reaches(source, x, y, z))
129
+ continue;
130
+ const sx = this.x[source];
131
+ const sy = this.y[source];
132
+ const sz = this.z[source];
133
+ if (occluded !== null && occluded(sx, sy, sz, x, y, z))
134
+ continue;
135
+ const dx = x - sx;
136
+ const dy = y - sy;
137
+ const dz = z - sz;
138
+ total += radiantFlux(this.temperature[source], this.area[source], surfaceTemperature, emissivity, Math.sqrt(dx * dx + dy * dy + dz * dz), 1);
139
+ }
140
+ return total;
141
+ }
142
+ /**
143
+ * The temperature of the hottest source that reaches a point, or zero where none does.
144
+ *
145
+ * **What a radiative exchange is bounded by**, and the reason it has to be bounded at all: the
146
+ * radiative term is explicit, so a parcel whose surface shell has a small heat capacity — a coin,
147
+ * a nail, a leaf — can absorb more in one step than would take it past the source that is heating
148
+ * it, and then radiate the excess back as `T⁴` and ring itself to pieces. It was found by putting
149
+ * a copper coin on a hearth in `demo/dev/chemistry.ts`, where it reached 4,000 K on the first step
150
+ * and 100 K a minute later.
151
+ *
152
+ * Nothing can be heated past the thing heating it, so this is the equilibrium the step may reach
153
+ * and not exceed — which is what an implicit solve would give and costs one comparison per source.
154
+ */
155
+ hottestAt(x, y, z, occluded) {
156
+ let hottest = 0;
157
+ for (let source = 0; source < this.used; source++) {
158
+ if (!this.reaches(source, x, y, z))
159
+ continue;
160
+ const sx = this.x[source];
161
+ const sy = this.y[source];
162
+ const sz = this.z[source];
163
+ if (occluded !== null && occluded(sx, sy, sz, x, y, z))
164
+ continue;
165
+ const temperature = this.temperature[source];
166
+ if (temperature > hottest)
167
+ hottest = temperature;
168
+ }
169
+ return hottest;
170
+ }
171
+ /**
172
+ * Whether **any** source reaches a point at all, which is the whole of the wake test.
173
+ *
174
+ * `§14`'s scan has to be coarse or sleeping saves nothing: this is a range comparison per source
175
+ * and no view factor, no temperature and no occlusion ray. A source's range already comes from its
176
+ * own power, so a bonfire wakes a parcel a candle would not.
177
+ */
178
+ reachesPoint(x, y, z) {
179
+ for (let source = 0; source < this.used; source++) {
180
+ if (this.reaches(source, x, y, z))
181
+ return true;
182
+ }
183
+ return false;
184
+ }
185
+ grow() {
186
+ const size = this.x.length * 2;
187
+ const grow = (from) => {
188
+ const to = new Float64Array(size);
189
+ to.set(from);
190
+ return to;
191
+ };
192
+ this.x = grow(this.x);
193
+ this.y = grow(this.y);
194
+ this.z = grow(this.z);
195
+ this.temperature = grow(this.temperature);
196
+ this.area = grow(this.area);
197
+ this.range = grow(this.range);
198
+ }
199
+ }
@@ -0,0 +1,92 @@
1
+ import type { AtmosphereField } from '../field/atmosphere.ts';
2
+ import type { ParcelStore } from '../parcel/store.ts';
3
+ import { ContactSet } from './contacts.ts';
4
+ import { ChemistryEvents } from '../ignition/events.ts';
5
+ import { type OcclusionTest, RadiativeSources } from './radiation.ts';
6
+ export interface ChemistryWorldOptions {
7
+ /**
8
+ * How fast a porous parcel equalises its pore gas with the cell around it, per second.
9
+ *
10
+ * Scaled by porosity, so a substance nobody described as porous exchanges nothing. What this
11
+ * governs in practice: how quickly volatiles get out to burn, and how quickly oxygen gets in to
12
+ * burn them.
13
+ */
14
+ readonly exchangeRate?: number;
15
+ /** Ambient gas speed where there is no field to ask, m/s. */
16
+ readonly stillAir?: number;
17
+ }
18
+ export declare class ChemistryWorld {
19
+ /** The parcels this world steps. Readable, because `fingerprintChemistry` hashes them. */
20
+ readonly parcels: ParcelStore;
21
+ /** The air, or `null` for a world with none. Readable for the same reason. */
22
+ readonly field: AtmosphereField | null;
23
+ /** Refilled by the caller each tick: what is radiating. */
24
+ readonly sources: RadiativeSources;
25
+ /** Refilled by the caller each tick: what is touching what. */
26
+ readonly contacts: ContactSet;
27
+ /** Drained by the caller each tick: what happened. Cleared at the top of every `simulate`. */
28
+ readonly events: ChemistryEvents;
29
+ /** Which slice of the sleeping set this tick's wake scan looks at. See `simulate`. */
30
+ private scanPhase;
31
+ /**
32
+ * Whether anything solid stands between two points.
33
+ *
34
+ * Absent, every source is visible — the cheap default, and wrong indoors. A consumer supplies a
35
+ * raycast; `§10` budgets and caches it, which is CH-6's work rather than this phase's.
36
+ */
37
+ occluded: OcclusionTest | null;
38
+ private readonly exchangeRate;
39
+ private readonly stillAir;
40
+ private readonly scratch;
41
+ constructor(
42
+ /** The parcels this world steps. Readable, because `fingerprintChemistry` hashes them. */
43
+ parcels: ParcelStore,
44
+ /** The air, or `null` for a world with none. Readable for the same reason. */
45
+ field?: AtmosphereField | null, options?: ChemistryWorldOptions);
46
+ /**
47
+ * One fixed step, in the order `§13` gives.
48
+ *
49
+ * **Sleeping and the tier cadence are both here rather than inside the terms they skip**, because
50
+ * both are decisions about *whether* a parcel is stepped at all and a term that decided for itself
51
+ * would be four places deciding the same thing four ways.
52
+ */
53
+ simulate(dt: number, windX?: number, windZ?: number): void;
54
+ /** What the fires can see of this parcel, into shell 0. */
55
+ private radiate;
56
+ /**
57
+ * Joules, clipped to what would bring the surface to `driving` and no further.
58
+ *
59
+ * Zero `driving` means nothing was found to be driving it, and then the joules pass through: a
60
+ * parcel radiating to a sky with no source in it is losing heat to the far field, and clipping
61
+ * that against zero kelvin would be clipping it against nothing.
62
+ */
63
+ private bounded;
64
+ /** What the air around it is doing to it. */
65
+ private convect;
66
+ /** Heat across the faces the caller says are touching. */
67
+ private conductContacts;
68
+ /**
69
+ * The pore gas in a parcel relaxes toward the air around it.
70
+ *
71
+ * **One mechanism, both directions.** Volatiles a parcel's reactions made are above the cell's
72
+ * concentration and leave; oxygen its reactions consumed is below and comes in. Writing those as
73
+ * two rules would be two things to keep in step, and the second would be found wrong on the day a
74
+ * fire went out with oxygen still in the room.
75
+ *
76
+ * The target is what the pore volume would hold at the cell's own density, so a dense material
77
+ * with little void exchanges little — which is why a log smoulders and sawdust does not.
78
+ */
79
+ private exchange;
80
+ /**
81
+ * Whether this parcel catches, keeps burning, or stops — and why.
82
+ *
83
+ * **Nothing here starts a fire.** Five conditions decide it, a pilot is one of them, and the
84
+ * pilot a caller supplied is spent at the end of the tick whether or not it was enough. That is
85
+ * `§11`, and it is why holding a match to wet wood does exactly nothing.
86
+ */
87
+ private criteria;
88
+ /** How close a parcel is to catching, 0 to 1. A readout; nothing branches on it. */
89
+ ignitionProgressOf(parcel: number): number;
90
+ /** Moles of each element in the parcels and the air together. Writes into `out`. */
91
+ elementTotals(out: Float64Array): void;
92
+ }