@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.
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +194 -0
- package/dist/conservation.d.ts +9 -0
- package/dist/conservation.js +38 -0
- package/dist/element/elements.d.ts +31 -0
- package/dist/element/elements.js +73 -0
- package/dist/element/matrix.d.ts +27 -0
- package/dist/element/matrix.js +80 -0
- package/dist/field/ambient.d.ts +49 -0
- package/dist/field/ambient.js +82 -0
- package/dist/field/atmosphere.d.ts +308 -0
- package/dist/field/atmosphere.js +1033 -0
- package/dist/ignition/criteria.d.ts +97 -0
- package/dist/ignition/criteria.js +135 -0
- package/dist/ignition/events.d.ts +56 -0
- package/dist/ignition/events.js +90 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +66 -0
- package/dist/library/biological.d.ts +6 -0
- package/dist/library/biological.js +229 -0
- package/dist/library/common.d.ts +26 -0
- package/dist/library/common.js +208 -0
- package/dist/library/food.d.ts +9 -0
- package/dist/library/food.js +272 -0
- package/dist/library/fuel.d.ts +19 -0
- package/dist/library/fuel.js +290 -0
- package/dist/library/library.d.ts +45 -0
- package/dist/library/library.js +29 -0
- package/dist/library/metal.d.ts +6 -0
- package/dist/library/metal.js +181 -0
- package/dist/library/mineral.d.ts +4 -0
- package/dist/library/mineral.js +177 -0
- package/dist/library/organic.d.ts +12 -0
- package/dist/library/organic.js +489 -0
- package/dist/library/polymer.d.ts +4 -0
- package/dist/library/polymer.js +237 -0
- package/dist/parcel/conduction.d.ts +20 -0
- package/dist/parcel/conduction.js +160 -0
- package/dist/parcel/shells.d.ts +49 -0
- package/dist/parcel/shells.js +73 -0
- package/dist/parcel/store.d.ts +395 -0
- package/dist/parcel/store.js +1152 -0
- package/dist/parcel/tier.d.ts +48 -0
- package/dist/parcel/tier.js +51 -0
- package/dist/present/audio.d.ts +41 -0
- package/dist/present/audio.js +50 -0
- package/dist/present/blackbody.d.ts +55 -0
- package/dist/present/blackbody.js +112 -0
- package/dist/present/flame.d.ts +34 -0
- package/dist/present/flame.js +103 -0
- package/dist/present/index.d.ts +25 -0
- package/dist/present/index.js +22 -0
- package/dist/present/install.d.ts +87 -0
- package/dist/present/install.js +157 -0
- package/dist/present/smoke.d.ts +63 -0
- package/dist/present/smoke.js +85 -0
- package/dist/present/surface.d.ts +40 -0
- package/dist/present/surface.js +76 -0
- package/dist/reaction/localise.d.ts +16 -0
- package/dist/reaction/localise.js +95 -0
- package/dist/reaction/rates.d.ts +59 -0
- package/dist/reaction/rates.js +101 -0
- package/dist/reaction/reaction.d.ts +178 -0
- package/dist/reaction/reaction.js +104 -0
- package/dist/reaction/registry.d.ts +47 -0
- package/dist/reaction/registry.js +215 -0
- package/dist/reaction/solver.d.ts +51 -0
- package/dist/reaction/solver.js +277 -0
- package/dist/species/registry.d.ts +26 -0
- package/dist/species/registry.js +97 -0
- package/dist/species/species.d.ts +48 -0
- package/dist/species/species.js +20 -0
- package/dist/species/standard.d.ts +30 -0
- package/dist/species/standard.js +436 -0
- package/dist/species/thermal.d.ts +18 -0
- package/dist/species/thermal.js +72 -0
- package/dist/substance/boiling.d.ts +29 -0
- package/dist/substance/boiling.js +54 -0
- package/dist/substance/composition.d.ts +34 -0
- package/dist/substance/composition.js +53 -0
- package/dist/substance/registry.d.ts +86 -0
- package/dist/substance/registry.js +187 -0
- package/dist/substance/substance.d.ts +94 -0
- package/dist/substance/substance.js +48 -0
- package/dist/testing/drift.d.ts +15 -0
- package/dist/testing/drift.js +29 -0
- package/dist/transport/contacts.d.ts +22 -0
- package/dist/transport/contacts.js +50 -0
- package/dist/transport/radiation.d.ts +101 -0
- package/dist/transport/radiation.js +199 -0
- package/dist/transport/world.d.ts +92 -0
- package/dist/transport/world.js +381 -0
- package/dist/world/fingerprint.d.ts +31 -0
- package/dist/world/fingerprint.js +70 -0
- package/package.json +100 -0
- package/src/conservation.ts +44 -0
- package/src/element/elements.ts +80 -0
- package/src/element/matrix.ts +89 -0
- package/src/field/ambient.ts +111 -0
- package/src/field/atmosphere.ts +1159 -0
- package/src/ignition/criteria.ts +177 -0
- package/src/ignition/events.ts +100 -0
- package/src/index.ts +213 -0
- package/src/library/biological.ts +252 -0
- package/src/library/common.ts +224 -0
- package/src/library/food.ts +302 -0
- package/src/library/fuel.ts +299 -0
- package/src/library/library.ts +62 -0
- package/src/library/metal.ts +186 -0
- package/src/library/mineral.ts +194 -0
- package/src/library/organic.ts +545 -0
- package/src/library/polymer.ts +243 -0
- package/src/parcel/conduction.ts +188 -0
- package/src/parcel/shells.ts +100 -0
- package/src/parcel/store.ts +1270 -0
- package/src/parcel/tier.ts +60 -0
- package/src/present/audio.ts +54 -0
- package/src/present/blackbody.ts +118 -0
- package/src/present/flame.ts +111 -0
- package/src/present/index.ts +25 -0
- package/src/present/install.ts +209 -0
- package/src/present/smoke.ts +139 -0
- package/src/present/surface.ts +107 -0
- package/src/reaction/localise.ts +126 -0
- package/src/reaction/rates.ts +153 -0
- package/src/reaction/reaction.ts +235 -0
- package/src/reaction/registry.ts +257 -0
- package/src/reaction/solver.ts +326 -0
- package/src/species/registry.ts +117 -0
- package/src/species/species.ts +53 -0
- package/src/species/standard.ts +449 -0
- package/src/species/thermal.ts +74 -0
- package/src/substance/boiling.ts +64 -0
- package/src/substance/composition.ts +81 -0
- package/src/substance/registry.ts +223 -0
- package/src/substance/substance.ts +148 -0
- package/src/testing/drift.ts +28 -0
- package/src/transport/contacts.ts +56 -0
- package/src/transport/radiation.ts +238 -0
- package/src/transport/world.ts +437 -0
- package/src/world/fingerprint.ts +106 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The species a world starts knowing about: sixty-eight of them, in thirteen groups.
|
|
3
|
+
*
|
|
4
|
+
* **Every formation enthalpy is J/mol at 298.15 K and every heat capacity is J/(kg·K).** Real
|
|
5
|
+
* molecules carry published values. Pseudo-species — a lumped repeat unit, an average residue, a
|
|
6
|
+
* fuel surrogate — carry a value **back-derived from a measured higher heating value**, so that
|
|
7
|
+
* Hess's law over this table reproduces the heat the world actually releases. `standard.test.ts`
|
|
8
|
+
* asserts eighteen of them against published figures to within two percent, which is inside the
|
|
9
|
+
* spread of the published figures themselves.
|
|
10
|
+
*
|
|
11
|
+
* That derivation is the honest way round. A pseudo-species has no measured formation enthalpy,
|
|
12
|
+
* because it is not a substance anyone has put in a calorimeter; what *has* been measured is what
|
|
13
|
+
* a kilogram of it gives off. Deriving from the latter makes the table agree with the world at the
|
|
14
|
+
* only point the world was ever asked.
|
|
15
|
+
*
|
|
16
|
+
* **Argon is not here, and the design's §6 listed it.** It is 0.93% of air and chemically inert, so
|
|
17
|
+
* the only thing it contributes is heat capacity and dilution — and carrying it would have meant a
|
|
18
|
+
* sixteenth element for nothing else. It is lumped into N2, which is inert in the same way and
|
|
19
|
+
* makes up 78%.
|
|
20
|
+
*
|
|
21
|
+
* **Char and soot are graphite thermochemically**, all three at zero, because they are the same
|
|
22
|
+
* element in the same standard state. What separates them is density, conductivity, porosity and
|
|
23
|
+
* which reactions they take part in — every one of which is a property of a *substance* or a
|
|
24
|
+
* reaction rather than of a species.
|
|
25
|
+
*/
|
|
26
|
+
import { PHASE_GAS, PHASE_LIQUID, PHASE_SOLID, type SpeciesDefinition } from './species.ts';
|
|
27
|
+
import type { SpeciesRegistry } from './registry.ts';
|
|
28
|
+
|
|
29
|
+
export const STANDARD_SPECIES: readonly SpeciesDefinition[] = [
|
|
30
|
+
/* Atmospheric gases. The far field is these four plus whatever a fire has put into it. */
|
|
31
|
+
{ id: 'O2', formula: { O: 2 }, phase: PHASE_GAS, formationEnthalpy: 0, cpA: 918 },
|
|
32
|
+
{ id: 'N2', formula: { N: 2 }, phase: PHASE_GAS, formationEnthalpy: 0, cpA: 1040 },
|
|
33
|
+
{
|
|
34
|
+
id: 'CO2',
|
|
35
|
+
formula: { C: 1, O: 2 },
|
|
36
|
+
phase: PHASE_GAS,
|
|
37
|
+
formationEnthalpy: -393510,
|
|
38
|
+
cpA: 844,
|
|
39
|
+
cpB: 0.6,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'H2O(g)',
|
|
43
|
+
formula: { H: 2, O: 1 },
|
|
44
|
+
phase: PHASE_GAS,
|
|
45
|
+
formationEnthalpy: -241826,
|
|
46
|
+
cpA: 1996,
|
|
47
|
+
cpB: 0.4,
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/* Combustion gases. CO and HCN are the two that kill; both are reported in ppm, never judged. */
|
|
51
|
+
{ id: 'CO', formula: { C: 1, O: 1 }, phase: PHASE_GAS, formationEnthalpy: -110530, cpA: 1040 },
|
|
52
|
+
{
|
|
53
|
+
id: 'CH4',
|
|
54
|
+
formula: { C: 1, H: 4 },
|
|
55
|
+
phase: PHASE_GAS,
|
|
56
|
+
formationEnthalpy: -74600,
|
|
57
|
+
cpA: 2220,
|
|
58
|
+
cpB: 5.2,
|
|
59
|
+
},
|
|
60
|
+
{ id: 'H2', formula: { H: 2 }, phase: PHASE_GAS, formationEnthalpy: 0, cpA: 14300 },
|
|
61
|
+
{ id: 'C2H4', formula: { C: 2, H: 4 }, phase: PHASE_GAS, formationEnthalpy: 52400, cpA: 1530 },
|
|
62
|
+
{ id: 'C2H6', formula: { C: 2, H: 6 }, phase: PHASE_GAS, formationEnthalpy: -84000, cpA: 1750 },
|
|
63
|
+
{ id: 'NH3', formula: { N: 1, H: 3 }, phase: PHASE_GAS, formationEnthalpy: -45900, cpA: 2190 },
|
|
64
|
+
{
|
|
65
|
+
id: 'HCN',
|
|
66
|
+
formula: { H: 1, C: 1, N: 1 },
|
|
67
|
+
phase: PHASE_GAS,
|
|
68
|
+
formationEnthalpy: 135100,
|
|
69
|
+
cpA: 1330,
|
|
70
|
+
},
|
|
71
|
+
{ id: 'HCl', formula: { H: 1, Cl: 1 }, phase: PHASE_GAS, formationEnthalpy: -92310, cpA: 799 },
|
|
72
|
+
{ id: 'SO2', formula: { S: 1, O: 2 }, phase: PHASE_GAS, formationEnthalpy: -296840, cpA: 640 },
|
|
73
|
+
{ id: 'NO', formula: { N: 1, O: 1 }, phase: PHASE_GAS, formationEnthalpy: 91290, cpA: 995 },
|
|
74
|
+
{ id: 'NO2', formula: { N: 1, O: 2 }, phase: PHASE_GAS, formationEnthalpy: 33200, cpA: 805 },
|
|
75
|
+
|
|
76
|
+
/* Condensed water. Ice sits one heat of fusion below liquid, which is where the plateau is. */
|
|
77
|
+
{
|
|
78
|
+
id: 'H2O(l)',
|
|
79
|
+
formula: { H: 2, O: 1 },
|
|
80
|
+
phase: PHASE_LIQUID,
|
|
81
|
+
formationEnthalpy: -285830,
|
|
82
|
+
cpA: 4182,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'H2O(s)',
|
|
86
|
+
formula: { H: 2, O: 1 },
|
|
87
|
+
phase: PHASE_SOLID,
|
|
88
|
+
formationEnthalpy: -291840,
|
|
89
|
+
cpA: 2108,
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
/* Carbon, in the three forms a fire makes. See the header on why all three are zero. */
|
|
93
|
+
{ id: 'C(graphite)', formula: { C: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 709 },
|
|
94
|
+
{ id: 'C(char)', formula: { C: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 1100 },
|
|
95
|
+
{ id: 'C(soot)', formula: { C: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 850 },
|
|
96
|
+
|
|
97
|
+
/* Wood, as its three polymers plus the resins. Each pyrolyses on its own kinetics in CH-3, which
|
|
98
|
+
is why the smoke changes character as a log heats rather than arriving all at once. */
|
|
99
|
+
{
|
|
100
|
+
id: 'cellulose',
|
|
101
|
+
formula: { C: 6, H: 10, O: 5 },
|
|
102
|
+
phase: PHASE_SOLID,
|
|
103
|
+
formationEnthalpy: -968957,
|
|
104
|
+
cpA: 1340,
|
|
105
|
+
cpB: 4.9,
|
|
106
|
+
pseudo: true,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'hemicellulose',
|
|
110
|
+
formula: { C: 5, H: 8, O: 4 },
|
|
111
|
+
phase: PHASE_SOLID,
|
|
112
|
+
formationEnthalpy: -997030,
|
|
113
|
+
cpA: 1380,
|
|
114
|
+
cpB: 4.9,
|
|
115
|
+
pseudo: true,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 'lignin',
|
|
119
|
+
formula: { C: 9, H: 10, O: 3 },
|
|
120
|
+
phase: PHASE_SOLID,
|
|
121
|
+
formationEnthalpy: -982516,
|
|
122
|
+
cpA: 1250,
|
|
123
|
+
cpB: 4.0,
|
|
124
|
+
pseudo: true,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'extractives',
|
|
128
|
+
formula: { C: 10, H: 16 },
|
|
129
|
+
phase: PHASE_SOLID,
|
|
130
|
+
formationEnthalpy: -91030,
|
|
131
|
+
cpA: 1700,
|
|
132
|
+
pseudo: true,
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
/* The other biopolymers, and the two protein states cooking turns on. A denatured protein sits
|
|
136
|
+
*above* its native form, because unfolding costs energy; gelatin sits below collagen plus the
|
|
137
|
+
water it took up, because hydrolysing a peptide bond releases a little. */
|
|
138
|
+
{
|
|
139
|
+
id: 'starch',
|
|
140
|
+
formula: { C: 6, H: 10, O: 5 },
|
|
141
|
+
phase: PHASE_SOLID,
|
|
142
|
+
formationEnthalpy: -952742,
|
|
143
|
+
cpA: 1250,
|
|
144
|
+
pseudo: true,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'chitin',
|
|
148
|
+
formula: { C: 8, H: 13, N: 1, O: 5 },
|
|
149
|
+
phase: PHASE_SOLID,
|
|
150
|
+
formationEnthalpy: -1450080,
|
|
151
|
+
cpA: 1300,
|
|
152
|
+
pseudo: true,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'keratin',
|
|
156
|
+
formula: { C: 4.6, H: 7.6, N: 1.3, O: 1.4, S: 0.13 },
|
|
157
|
+
phase: PHASE_SOLID,
|
|
158
|
+
formationEnthalpy: -479628,
|
|
159
|
+
cpA: 1550,
|
|
160
|
+
pseudo: true,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'collagen',
|
|
164
|
+
formula: { C: 4.3, H: 7.1, N: 1.3, O: 1.5 },
|
|
165
|
+
phase: PHASE_SOLID,
|
|
166
|
+
formationEnthalpy: -484532,
|
|
167
|
+
cpA: 1600,
|
|
168
|
+
pseudo: true,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: 'protein',
|
|
172
|
+
formula: { C: 4.4, H: 7.7, N: 1.2, O: 1.4, S: 0.04 },
|
|
173
|
+
phase: PHASE_SOLID,
|
|
174
|
+
formationEnthalpy: -427487,
|
|
175
|
+
cpA: 1600,
|
|
176
|
+
pseudo: true,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'protein(denatured)',
|
|
180
|
+
formula: { C: 4.4, H: 7.7, N: 1.2, O: 1.4, S: 0.04 },
|
|
181
|
+
phase: PHASE_SOLID,
|
|
182
|
+
formationEnthalpy: -417487,
|
|
183
|
+
cpA: 1600,
|
|
184
|
+
pseudo: true,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'gelatin',
|
|
188
|
+
formula: { C: 4.3, H: 9.1, N: 1.3, O: 2.5 },
|
|
189
|
+
phase: PHASE_SOLID,
|
|
190
|
+
formationEnthalpy: -780362,
|
|
191
|
+
cpA: 1700,
|
|
192
|
+
pseudo: true,
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
/* Small organics: the sugars that caramelise and the liquids that dissolve things. */
|
|
196
|
+
{
|
|
197
|
+
id: 'glucose',
|
|
198
|
+
formula: { C: 6, H: 12, O: 6 },
|
|
199
|
+
phase: PHASE_SOLID,
|
|
200
|
+
formationEnthalpy: -1273300,
|
|
201
|
+
cpA: 1220,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: 'fructose',
|
|
205
|
+
formula: { C: 6, H: 12, O: 6 },
|
|
206
|
+
phase: PHASE_SOLID,
|
|
207
|
+
formationEnthalpy: -1265600,
|
|
208
|
+
cpA: 1220,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: 'sucrose',
|
|
212
|
+
formula: { C: 12, H: 22, O: 11 },
|
|
213
|
+
phase: PHASE_SOLID,
|
|
214
|
+
formationEnthalpy: -2226100,
|
|
215
|
+
cpA: 1250,
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
id: 'ethanol',
|
|
219
|
+
formula: { C: 2, H: 6, O: 1 },
|
|
220
|
+
phase: PHASE_LIQUID,
|
|
221
|
+
formationEnthalpy: -277600,
|
|
222
|
+
cpA: 2440,
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: 'methanol',
|
|
226
|
+
formula: { C: 1, H: 4, O: 1 },
|
|
227
|
+
phase: PHASE_LIQUID,
|
|
228
|
+
formationEnthalpy: -239200,
|
|
229
|
+
cpA: 2530,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: 'acetic-acid',
|
|
233
|
+
formula: { C: 2, H: 4, O: 2 },
|
|
234
|
+
phase: PHASE_LIQUID,
|
|
235
|
+
formationEnthalpy: -484300,
|
|
236
|
+
cpA: 2050,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: 'glycerol',
|
|
240
|
+
formula: { C: 3, H: 8, O: 3 },
|
|
241
|
+
phase: PHASE_LIQUID,
|
|
242
|
+
formationEnthalpy: -669600,
|
|
243
|
+
cpA: 2430,
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
/* Lipids. Saturated is tristearin and unsaturated is triolein, which is the difference between a
|
|
247
|
+
fat that is solid at room temperature and an oil that is not — a melting point rather than a
|
|
248
|
+
reaction, so it lives on the substance in CH-1. */
|
|
249
|
+
{
|
|
250
|
+
id: 'triglyceride(saturated)',
|
|
251
|
+
formula: { C: 57, H: 110, O: 6 },
|
|
252
|
+
phase: PHASE_SOLID,
|
|
253
|
+
formationEnthalpy: -2193700,
|
|
254
|
+
cpA: 1900,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: 'triglyceride(unsaturated)',
|
|
258
|
+
formula: { C: 57, H: 104, O: 6 },
|
|
259
|
+
phase: PHASE_LIQUID,
|
|
260
|
+
formationEnthalpy: -2005000,
|
|
261
|
+
cpA: 2000,
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: 'fatty-acid',
|
|
265
|
+
formula: { C: 18, H: 36, O: 2 },
|
|
266
|
+
phase: PHASE_SOLID,
|
|
267
|
+
formationEnthalpy: -947700,
|
|
268
|
+
cpA: 1600,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: 'wax',
|
|
272
|
+
formula: { C: 25, H: 52 },
|
|
273
|
+
phase: PHASE_SOLID,
|
|
274
|
+
formationEnthalpy: -869199,
|
|
275
|
+
cpA: 2100,
|
|
276
|
+
pseudo: true,
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
/* Fuel surrogates. Petrol is octane and diesel is dodecane — real molecules standing in for a
|
|
280
|
+
distillation cut, which is what a surrogate is. Kerosene has no integer formula at all. */
|
|
281
|
+
{
|
|
282
|
+
id: 'octane',
|
|
283
|
+
formula: { C: 8, H: 18 },
|
|
284
|
+
phase: PHASE_LIQUID,
|
|
285
|
+
formationEnthalpy: -250100,
|
|
286
|
+
cpA: 2230,
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: 'diesel',
|
|
290
|
+
formula: { C: 12, H: 26 },
|
|
291
|
+
phase: PHASE_LIQUID,
|
|
292
|
+
formationEnthalpy: -350900,
|
|
293
|
+
cpA: 2210,
|
|
294
|
+
pseudo: true,
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
id: 'kerosene',
|
|
298
|
+
formula: { C: 12, H: 23 },
|
|
299
|
+
phase: PHASE_LIQUID,
|
|
300
|
+
formationEnthalpy: -279166,
|
|
301
|
+
cpA: 2010,
|
|
302
|
+
pseudo: true,
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
id: 'propane',
|
|
306
|
+
formula: { C: 3, H: 8 },
|
|
307
|
+
phase: PHASE_GAS,
|
|
308
|
+
formationEnthalpy: -103800,
|
|
309
|
+
cpA: 1670,
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: 'butane',
|
|
313
|
+
formula: { C: 4, H: 10 },
|
|
314
|
+
phase: PHASE_GAS,
|
|
315
|
+
formationEnthalpy: -125700,
|
|
316
|
+
cpA: 1700,
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
/* Polymers, as repeat units. PVC carries the chlorine that becomes HCl in a fire and polyurethane
|
|
320
|
+
carries the nitrogen that becomes HCN — which is why a burning sofa is the dangerous one, and
|
|
321
|
+
it falls out of the formulas rather than out of a rule. */
|
|
322
|
+
{
|
|
323
|
+
id: 'polyethylene',
|
|
324
|
+
formula: { C: 2, H: 4 },
|
|
325
|
+
phase: PHASE_SOLID,
|
|
326
|
+
formationEnthalpy: -54169,
|
|
327
|
+
cpA: 2300,
|
|
328
|
+
pseudo: true,
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
id: 'polypropylene',
|
|
332
|
+
formula: { C: 3, H: 6 },
|
|
333
|
+
phase: PHASE_SOLID,
|
|
334
|
+
formationEnthalpy: -102294,
|
|
335
|
+
cpA: 1920,
|
|
336
|
+
pseudo: true,
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: 'PVC',
|
|
340
|
+
formula: { C: 2, H: 3, Cl: 1 },
|
|
341
|
+
phase: PHASE_SOLID,
|
|
342
|
+
formationEnthalpy: -40232,
|
|
343
|
+
cpA: 900,
|
|
344
|
+
pseudo: true,
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: 'polystyrene',
|
|
348
|
+
formula: { C: 8, H: 8 },
|
|
349
|
+
phase: PHASE_SOLID,
|
|
350
|
+
formationEnthalpy: 72569,
|
|
351
|
+
cpA: 1300,
|
|
352
|
+
pseudo: true,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: 'PMMA',
|
|
356
|
+
formula: { C: 5, H: 8, O: 2 },
|
|
357
|
+
phase: PHASE_SOLID,
|
|
358
|
+
formationEnthalpy: -487805,
|
|
359
|
+
cpA: 1470,
|
|
360
|
+
pseudo: true,
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
id: 'polyurethane',
|
|
364
|
+
formula: { C: 25, H: 42, N: 2, O: 6 },
|
|
365
|
+
phase: PHASE_SOLID,
|
|
366
|
+
formationEnthalpy: -3708086,
|
|
367
|
+
cpA: 1800,
|
|
368
|
+
pseudo: true,
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
/* Minerals. Gypsum is here for one reason: the two waters it loses at 100-150 C are an enormous
|
|
372
|
+
latent-heat sink, which is *why* plasterboard is a fire barrier. Limestone is here for the
|
|
373
|
+
other: calcination is endothermic and releases gas, which is why stone spalls in a fire. */
|
|
374
|
+
{
|
|
375
|
+
id: 'SiO2',
|
|
376
|
+
formula: { Si: 1, O: 2 },
|
|
377
|
+
phase: PHASE_SOLID,
|
|
378
|
+
formationEnthalpy: -910700,
|
|
379
|
+
cpA: 740,
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
id: 'CaCO3',
|
|
383
|
+
formula: { Ca: 1, C: 1, O: 3 },
|
|
384
|
+
phase: PHASE_SOLID,
|
|
385
|
+
formationEnthalpy: -1207600,
|
|
386
|
+
cpA: 820,
|
|
387
|
+
},
|
|
388
|
+
{ id: 'CaO', formula: { Ca: 1, O: 1 }, phase: PHASE_SOLID, formationEnthalpy: -634900, cpA: 750 },
|
|
389
|
+
{
|
|
390
|
+
id: 'gypsum',
|
|
391
|
+
formula: { Ca: 1, S: 1, O: 6, H: 4 },
|
|
392
|
+
phase: PHASE_SOLID,
|
|
393
|
+
formationEnthalpy: -2022600,
|
|
394
|
+
cpA: 1090,
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
id: 'anhydrite',
|
|
398
|
+
formula: { Ca: 1, S: 1, O: 4 },
|
|
399
|
+
phase: PHASE_SOLID,
|
|
400
|
+
formationEnthalpy: -1434500,
|
|
401
|
+
cpA: 730,
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
id: 'Al2O3',
|
|
405
|
+
formula: { Al: 2, O: 3 },
|
|
406
|
+
phase: PHASE_SOLID,
|
|
407
|
+
formationEnthalpy: -1675700,
|
|
408
|
+
cpA: 880,
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
id: 'Fe2O3',
|
|
412
|
+
formula: { Fe: 2, O: 3 },
|
|
413
|
+
phase: PHASE_SOLID,
|
|
414
|
+
formationEnthalpy: -824200,
|
|
415
|
+
cpA: 650,
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
id: 'Fe3O4',
|
|
419
|
+
formula: { Fe: 3, O: 4 },
|
|
420
|
+
phase: PHASE_SOLID,
|
|
421
|
+
formationEnthalpy: -1118400,
|
|
422
|
+
cpA: 620,
|
|
423
|
+
},
|
|
424
|
+
{ id: 'MgO', formula: { Mg: 1, O: 1 }, phase: PHASE_SOLID, formationEnthalpy: -601600, cpA: 930 },
|
|
425
|
+
{ id: 'CuO', formula: { Cu: 1, O: 1 }, phase: PHASE_SOLID, formationEnthalpy: -157300, cpA: 530 },
|
|
426
|
+
/* Ash is the one species with an arbitrary enthalpy, and it is arbitrary because nothing ever
|
|
427
|
+
produces or consumes it: a substance holds its ash from the start and every reaction leaves it
|
|
428
|
+
alone. Zero is therefore not a claim about ash, it is the absence of one. */
|
|
429
|
+
{
|
|
430
|
+
id: 'ash',
|
|
431
|
+
formula: { Si: 0.4, Ca: 0.3, K: 0.15, Mg: 0.1, P: 0.05, O: 1.2 },
|
|
432
|
+
phase: PHASE_SOLID,
|
|
433
|
+
formationEnthalpy: 0,
|
|
434
|
+
cpA: 800,
|
|
435
|
+
pseudo: true,
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
/* Metals. Magnesium is here because it burns, which most metals do not at any temperature a fire
|
|
439
|
+
reaches, and iron because it rusts, which is the slowest reaction in the whole model. */
|
|
440
|
+
{ id: 'Fe', formula: { Fe: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 449 },
|
|
441
|
+
{ id: 'Al', formula: { Al: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 897 },
|
|
442
|
+
{ id: 'Cu', formula: { Cu: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 385 },
|
|
443
|
+
{ id: 'Mg', formula: { Mg: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 1023 },
|
|
444
|
+
];
|
|
445
|
+
|
|
446
|
+
/** Register the whole standard set into a registry, in order. */
|
|
447
|
+
export function registerStandardSpecies(registry: SpeciesRegistry): void {
|
|
448
|
+
for (const definition of STANDARD_SPECIES) registry.register(definition);
|
|
449
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temperature from enthalpy, in closed form, from whatever a thing is made of right now.
|
|
3
|
+
*
|
|
4
|
+
* **This replaces `§3`'s per-substance enthalpy curve, and the reversal is CH-6's opening move.**
|
|
5
|
+
* That curve was built from a substance's *initial* composition, so the temperature it reported
|
|
6
|
+
* drifted as reactions changed what was actually there — about 20% of the rise for cellulose
|
|
7
|
+
* becoming char. Ignition is a surface temperature crossing 623 K, so nothing could be allowed to
|
|
8
|
+
* read it.
|
|
9
|
+
*
|
|
10
|
+
* **The repair is smaller than the thing it repairs, because the latent heat was never sensible
|
|
11
|
+
* enthalpy's job.** It is already in the formation enthalpies CH-0 stores: `H₂O(s) → H₂O(l)` comes
|
|
12
|
+
* out of Hess's law at 333,611 J/kg against a literature 333,550. So a phase change is a *reaction*
|
|
13
|
+
* with a temperature gate, its plateau emerges from the substep-and-clamp solver that already
|
|
14
|
+
* exists, and what is left here is purely sensible heat:
|
|
15
|
+
*
|
|
16
|
+
* h = A·ΔT + B·ΔT²/2 A = Σ mᵢ·cpAᵢ, B = Σ mᵢ·cpBᵢ, ΔT = T − 298.15
|
|
17
|
+
*
|
|
18
|
+
* A quadratic, so inverting it is a formula rather than a search:
|
|
19
|
+
*
|
|
20
|
+
* ΔT = (−A + √(A² + 2·B·h)) / B
|
|
21
|
+
*
|
|
22
|
+
* **One square root, which is one of the two operations ECMAScript specifies exactly** — `§15`
|
|
23
|
+
* permits it where it forbids `exp` and `pow`, and Track B's solver already leans on the same fact.
|
|
24
|
+
* Cheaper than the 256-knot binary search it replaces, composition-aware by construction, and it
|
|
25
|
+
* deletes machinery rather than adding to it.
|
|
26
|
+
*/
|
|
27
|
+
import { STANDARD_TEMPERATURE } from './species.ts';
|
|
28
|
+
|
|
29
|
+
/** K. Below this nothing in this engine is modelled, and a reading clamps rather than extrapolates. */
|
|
30
|
+
export const MIN_MODEL_TEMPERATURE = 100;
|
|
31
|
+
|
|
32
|
+
/** K. Above this likewise — past any flame this model produces. */
|
|
33
|
+
export const MAX_MODEL_TEMPERATURE = 4000;
|
|
34
|
+
|
|
35
|
+
/** Specific enthalpy of a species at a temperature, J/kg, zero at the standard state. */
|
|
36
|
+
export function sensibleEnthalpy(cpA: number, cpB: number, temperature: number): number {
|
|
37
|
+
const dT = temperature - STANDARD_TEMPERATURE;
|
|
38
|
+
return cpA * dT + (cpB * dT * dT) / 2;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The temperature a body of heat capacity `A + B·ΔT` sits at holding `joules`.
|
|
43
|
+
*
|
|
44
|
+
* `A` and `B` are **absolute** — kilograms times specific heat, summed over whatever is present —
|
|
45
|
+
* so `joules` is the total the thing holds rather than a specific value, and there is no division
|
|
46
|
+
* before the inversion.
|
|
47
|
+
*
|
|
48
|
+
* Clamped at both ends rather than extrapolated, and answering the standard state for a body with
|
|
49
|
+
* no heat capacity at all: a temperature is not a meaningful question about an empty shell, and
|
|
50
|
+
* inventing one is worse than declining.
|
|
51
|
+
*/
|
|
52
|
+
export function temperatureFromCapacity(A: number, B: number, joules: number): number {
|
|
53
|
+
if (!(A > 0)) return STANDARD_TEMPERATURE;
|
|
54
|
+
|
|
55
|
+
let dT: number;
|
|
56
|
+
if (B === 0) {
|
|
57
|
+
dT = joules / A;
|
|
58
|
+
} else {
|
|
59
|
+
const discriminant = A * A + 2 * B * joules;
|
|
60
|
+
if (!(discriminant >= 0)) {
|
|
61
|
+
/* Past the turning point of the quadratic, which means past where a linear `cp` is a model of
|
|
62
|
+
anything. The sign of `B` says which end it ran off. */
|
|
63
|
+
return B > 0 ? MIN_MODEL_TEMPERATURE : MAX_MODEL_TEMPERATURE;
|
|
64
|
+
}
|
|
65
|
+
/* The branch through `ΔT = 0`, which is this root for either sign of `B`: at `h = 0` the
|
|
66
|
+
discriminant is `A²`, the square root is `A`, and the numerator is zero. */
|
|
67
|
+
dT = (-A + Math.sqrt(discriminant)) / B;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const temperature = STANDARD_TEMPERATURE + dT;
|
|
71
|
+
if (temperature < MIN_MODEL_TEMPERATURE) return MIN_MODEL_TEMPERATURE;
|
|
72
|
+
if (temperature > MAX_MODEL_TEMPERATURE) return MAX_MODEL_TEMPERATURE;
|
|
73
|
+
return temperature;
|
|
74
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a liquid boils at a pressure that is not one atmosphere.
|
|
3
|
+
*
|
|
4
|
+
* Clausius-Clapeyron, integrated with the latent heat held constant over the range:
|
|
5
|
+
*
|
|
6
|
+
* 1/T = 1/T0 - (R / L_molar) * ln(P / P0)
|
|
7
|
+
*
|
|
8
|
+
* **Why it matters is `§8.1` and `§22`.** A boiling point is the ceiling a wet surface's
|
|
9
|
+
* temperature is pinned at, so moving it moves what the surface can do. At 3,000 metres water boils
|
|
10
|
+
* near 90 C, a stew there cannot reach the 140 C Maillard browning needs, and food genuinely does
|
|
11
|
+
* not brown in a pot at altitude. Under two atmospheres it boils near 121 C, which is a pressure
|
|
12
|
+
* cooker and is also why an autoclave sterilises at that number.
|
|
13
|
+
*
|
|
14
|
+
* **Authoring-time only, and that is a rule rather than a note.** This calls `log`, and `§7` bans
|
|
15
|
+
* transcendentals from the tick — they are the easiest place in the engine to lose determinism
|
|
16
|
+
* between platforms. CH-4 supplies a per-cell pressure, and when it does this is tabulated the way
|
|
17
|
+
* every rate law already is. Until then a caller passes a pressure at setup.
|
|
18
|
+
*
|
|
19
|
+
* Constant latent heat is the approximation, and it is a good one over the range a game covers:
|
|
20
|
+
* water's `L` falls about 6% between 100 C and 120 C, which moves the answer by a few tenths of a
|
|
21
|
+
* kelvin. It breaks down approaching the critical point, which nothing here goes near.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** J/(mol·K). */
|
|
25
|
+
const GAS_CONSTANT = 8.314462618;
|
|
26
|
+
|
|
27
|
+
/** Pa. The standard atmosphere every reference temperature in this package is quoted at. */
|
|
28
|
+
const REFERENCE_PRESSURE = 101325;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param referenceTemperature K, where this liquid boils at `referencePressure`
|
|
32
|
+
* @param latentHeat J **per kilogram** of the species that changes phase
|
|
33
|
+
* @param molarMass kg/mol, matching `SpeciesRegistry.molarMass`
|
|
34
|
+
* @param pressure Pa
|
|
35
|
+
*/
|
|
36
|
+
export function boilingPoint(
|
|
37
|
+
referenceTemperature: number,
|
|
38
|
+
latentHeat: number,
|
|
39
|
+
molarMass: number,
|
|
40
|
+
pressure: number,
|
|
41
|
+
referencePressure: number = REFERENCE_PRESSURE,
|
|
42
|
+
): number {
|
|
43
|
+
if (!(pressure > 0)) {
|
|
44
|
+
throw new Error(`boiling point asked at a pressure of ${pressure} Pa`);
|
|
45
|
+
}
|
|
46
|
+
if (!(referencePressure > 0)) {
|
|
47
|
+
throw new Error(`boiling point asked against a reference pressure of ${referencePressure} Pa`);
|
|
48
|
+
}
|
|
49
|
+
if (!(latentHeat > 0)) {
|
|
50
|
+
throw new Error(`latent heat is ${latentHeat}, which is not positive`);
|
|
51
|
+
}
|
|
52
|
+
if (!(molarMass > 0)) {
|
|
53
|
+
throw new Error(`molar mass is ${molarMass}, which is not positive`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const molarLatentHeat = latentHeat * molarMass;
|
|
57
|
+
// determinism: build-time — a boiling point derived at registration, from constants
|
|
58
|
+
const logPressureRatio = Math.log(pressure / referencePressure);
|
|
59
|
+
const inverse = 1 / referenceTemperature - (GAS_CONSTANT / molarLatentHeat) * logPressureRatio;
|
|
60
|
+
if (!(inverse > 0)) {
|
|
61
|
+
throw new Error(`a pressure of ${pressure} Pa puts this liquid past its critical point`);
|
|
62
|
+
}
|
|
63
|
+
return 1 / inverse;
|
|
64
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a material is made of: mass fractions over species, summing to one.
|
|
3
|
+
*
|
|
4
|
+
* **Mass fractions rather than moles**, and it is the open question `§30` of the design marks
|
|
5
|
+
* first. Mass is what a consumer thinks in, what a density multiplies, and what a parcel stores;
|
|
6
|
+
* moles are what stoichiometry wants. The conversion is one multiply by the derived molar mass and
|
|
7
|
+
* it happens at the reaction boundary, which is the one place that needs it.
|
|
8
|
+
*
|
|
9
|
+
* Two parallel arrays rather than a map, because every consumer of a composition walks it in a
|
|
10
|
+
* loop and a map's iteration allocates.
|
|
11
|
+
*/
|
|
12
|
+
import type { SpeciesRegistry } from '../species/registry.ts';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* How far a hand-written composition may be from summing to one.
|
|
16
|
+
*
|
|
17
|
+
* Generous enough for six fractions typed to three decimals, tight enough that a transposed digit
|
|
18
|
+
* is a refusal rather than a material that quietly loses half a percent of its mass on every
|
|
19
|
+
* conservation check.
|
|
20
|
+
*/
|
|
21
|
+
export const SUM_TOLERANCE = 1e-6;
|
|
22
|
+
|
|
23
|
+
export interface Composition {
|
|
24
|
+
/** Species indices, ascending. */
|
|
25
|
+
readonly species: Int32Array;
|
|
26
|
+
/** Mass fraction of each, parallel to `species`. */
|
|
27
|
+
readonly fraction: Float64Array;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A composition from species ids and mass fractions.
|
|
32
|
+
*
|
|
33
|
+
* Fails fast on anything wrong, per `AGENTS.md`: an unknown id, a fraction that is negative or not
|
|
34
|
+
* a number, a sum that is not one. Each names the offending value, because "invalid composition" in
|
|
35
|
+
* a stack trace is a sentence somebody has to reproduce to act on.
|
|
36
|
+
*/
|
|
37
|
+
export function massFractions(
|
|
38
|
+
registry: SpeciesRegistry,
|
|
39
|
+
entries: Readonly<Record<string, number>>,
|
|
40
|
+
): Composition {
|
|
41
|
+
const ids = Object.keys(entries);
|
|
42
|
+
if (ids.length === 0) {
|
|
43
|
+
throw new Error('a composition may not be empty');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const pairs: { species: number; fraction: number }[] = [];
|
|
47
|
+
let sum = 0;
|
|
48
|
+
for (const id of ids) {
|
|
49
|
+
const species = registry.indexOf(id);
|
|
50
|
+
if (species < 0) {
|
|
51
|
+
throw new Error(`composition names "${id}", which is not a registered species`);
|
|
52
|
+
}
|
|
53
|
+
const fraction = entries[id] as number;
|
|
54
|
+
if (!Number.isFinite(fraction) || fraction < 0) {
|
|
55
|
+
throw new Error(`composition gives "${id}" a mass fraction of ${fraction}`);
|
|
56
|
+
}
|
|
57
|
+
sum += fraction;
|
|
58
|
+
pairs.push({ species, fraction });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (Math.abs(sum - 1) > SUM_TOLERANCE) {
|
|
62
|
+
throw new Error(`composition sums to ${sum} rather than 1`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
* Sorted by species index, not by the order the author typed.
|
|
67
|
+
*
|
|
68
|
+
* Adding doubles is not associative, so two spellings of the same material would reduce to
|
|
69
|
+
* different last bits — and a conservation check is exactly where a last bit becomes a failing
|
|
70
|
+
* assertion nobody can explain. Sorting here makes the storage canonical, once, at init.
|
|
71
|
+
*/
|
|
72
|
+
pairs.sort((a, b) => a.species - b.species);
|
|
73
|
+
|
|
74
|
+
const species = new Int32Array(pairs.length);
|
|
75
|
+
const fraction = new Float64Array(pairs.length);
|
|
76
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
77
|
+
species[i] = (pairs[i] as { species: number }).species;
|
|
78
|
+
fraction[i] = (pairs[i] as { fraction: number }).fraction;
|
|
79
|
+
}
|
|
80
|
+
return { species, fraction };
|
|
81
|
+
}
|