@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,436 @@
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 } from './species.js';
27
+ export const STANDARD_SPECIES = [
28
+ /* Atmospheric gases. The far field is these four plus whatever a fire has put into it. */
29
+ { id: 'O2', formula: { O: 2 }, phase: PHASE_GAS, formationEnthalpy: 0, cpA: 918 },
30
+ { id: 'N2', formula: { N: 2 }, phase: PHASE_GAS, formationEnthalpy: 0, cpA: 1040 },
31
+ {
32
+ id: 'CO2',
33
+ formula: { C: 1, O: 2 },
34
+ phase: PHASE_GAS,
35
+ formationEnthalpy: -393510,
36
+ cpA: 844,
37
+ cpB: 0.6,
38
+ },
39
+ {
40
+ id: 'H2O(g)',
41
+ formula: { H: 2, O: 1 },
42
+ phase: PHASE_GAS,
43
+ formationEnthalpy: -241826,
44
+ cpA: 1996,
45
+ cpB: 0.4,
46
+ },
47
+ /* Combustion gases. CO and HCN are the two that kill; both are reported in ppm, never judged. */
48
+ { id: 'CO', formula: { C: 1, O: 1 }, phase: PHASE_GAS, formationEnthalpy: -110530, cpA: 1040 },
49
+ {
50
+ id: 'CH4',
51
+ formula: { C: 1, H: 4 },
52
+ phase: PHASE_GAS,
53
+ formationEnthalpy: -74600,
54
+ cpA: 2220,
55
+ cpB: 5.2,
56
+ },
57
+ { id: 'H2', formula: { H: 2 }, phase: PHASE_GAS, formationEnthalpy: 0, cpA: 14300 },
58
+ { id: 'C2H4', formula: { C: 2, H: 4 }, phase: PHASE_GAS, formationEnthalpy: 52400, cpA: 1530 },
59
+ { id: 'C2H6', formula: { C: 2, H: 6 }, phase: PHASE_GAS, formationEnthalpy: -84000, cpA: 1750 },
60
+ { id: 'NH3', formula: { N: 1, H: 3 }, phase: PHASE_GAS, formationEnthalpy: -45900, cpA: 2190 },
61
+ {
62
+ id: 'HCN',
63
+ formula: { H: 1, C: 1, N: 1 },
64
+ phase: PHASE_GAS,
65
+ formationEnthalpy: 135100,
66
+ cpA: 1330,
67
+ },
68
+ { id: 'HCl', formula: { H: 1, Cl: 1 }, phase: PHASE_GAS, formationEnthalpy: -92310, cpA: 799 },
69
+ { id: 'SO2', formula: { S: 1, O: 2 }, phase: PHASE_GAS, formationEnthalpy: -296840, cpA: 640 },
70
+ { id: 'NO', formula: { N: 1, O: 1 }, phase: PHASE_GAS, formationEnthalpy: 91290, cpA: 995 },
71
+ { id: 'NO2', formula: { N: 1, O: 2 }, phase: PHASE_GAS, formationEnthalpy: 33200, cpA: 805 },
72
+ /* Condensed water. Ice sits one heat of fusion below liquid, which is where the plateau is. */
73
+ {
74
+ id: 'H2O(l)',
75
+ formula: { H: 2, O: 1 },
76
+ phase: PHASE_LIQUID,
77
+ formationEnthalpy: -285830,
78
+ cpA: 4182,
79
+ },
80
+ {
81
+ id: 'H2O(s)',
82
+ formula: { H: 2, O: 1 },
83
+ phase: PHASE_SOLID,
84
+ formationEnthalpy: -291840,
85
+ cpA: 2108,
86
+ },
87
+ /* Carbon, in the three forms a fire makes. See the header on why all three are zero. */
88
+ { id: 'C(graphite)', formula: { C: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 709 },
89
+ { id: 'C(char)', formula: { C: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 1100 },
90
+ { id: 'C(soot)', formula: { C: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 850 },
91
+ /* Wood, as its three polymers plus the resins. Each pyrolyses on its own kinetics in CH-3, which
92
+ is why the smoke changes character as a log heats rather than arriving all at once. */
93
+ {
94
+ id: 'cellulose',
95
+ formula: { C: 6, H: 10, O: 5 },
96
+ phase: PHASE_SOLID,
97
+ formationEnthalpy: -968957,
98
+ cpA: 1340,
99
+ cpB: 4.9,
100
+ pseudo: true,
101
+ },
102
+ {
103
+ id: 'hemicellulose',
104
+ formula: { C: 5, H: 8, O: 4 },
105
+ phase: PHASE_SOLID,
106
+ formationEnthalpy: -997030,
107
+ cpA: 1380,
108
+ cpB: 4.9,
109
+ pseudo: true,
110
+ },
111
+ {
112
+ id: 'lignin',
113
+ formula: { C: 9, H: 10, O: 3 },
114
+ phase: PHASE_SOLID,
115
+ formationEnthalpy: -982516,
116
+ cpA: 1250,
117
+ cpB: 4.0,
118
+ pseudo: true,
119
+ },
120
+ {
121
+ id: 'extractives',
122
+ formula: { C: 10, H: 16 },
123
+ phase: PHASE_SOLID,
124
+ formationEnthalpy: -91030,
125
+ cpA: 1700,
126
+ pseudo: true,
127
+ },
128
+ /* The other biopolymers, and the two protein states cooking turns on. A denatured protein sits
129
+ *above* its native form, because unfolding costs energy; gelatin sits below collagen plus the
130
+ water it took up, because hydrolysing a peptide bond releases a little. */
131
+ {
132
+ id: 'starch',
133
+ formula: { C: 6, H: 10, O: 5 },
134
+ phase: PHASE_SOLID,
135
+ formationEnthalpy: -952742,
136
+ cpA: 1250,
137
+ pseudo: true,
138
+ },
139
+ {
140
+ id: 'chitin',
141
+ formula: { C: 8, H: 13, N: 1, O: 5 },
142
+ phase: PHASE_SOLID,
143
+ formationEnthalpy: -1450080,
144
+ cpA: 1300,
145
+ pseudo: true,
146
+ },
147
+ {
148
+ id: 'keratin',
149
+ formula: { C: 4.6, H: 7.6, N: 1.3, O: 1.4, S: 0.13 },
150
+ phase: PHASE_SOLID,
151
+ formationEnthalpy: -479628,
152
+ cpA: 1550,
153
+ pseudo: true,
154
+ },
155
+ {
156
+ id: 'collagen',
157
+ formula: { C: 4.3, H: 7.1, N: 1.3, O: 1.5 },
158
+ phase: PHASE_SOLID,
159
+ formationEnthalpy: -484532,
160
+ cpA: 1600,
161
+ pseudo: true,
162
+ },
163
+ {
164
+ id: 'protein',
165
+ formula: { C: 4.4, H: 7.7, N: 1.2, O: 1.4, S: 0.04 },
166
+ phase: PHASE_SOLID,
167
+ formationEnthalpy: -427487,
168
+ cpA: 1600,
169
+ pseudo: true,
170
+ },
171
+ {
172
+ id: 'protein(denatured)',
173
+ formula: { C: 4.4, H: 7.7, N: 1.2, O: 1.4, S: 0.04 },
174
+ phase: PHASE_SOLID,
175
+ formationEnthalpy: -417487,
176
+ cpA: 1600,
177
+ pseudo: true,
178
+ },
179
+ {
180
+ id: 'gelatin',
181
+ formula: { C: 4.3, H: 9.1, N: 1.3, O: 2.5 },
182
+ phase: PHASE_SOLID,
183
+ formationEnthalpy: -780362,
184
+ cpA: 1700,
185
+ pseudo: true,
186
+ },
187
+ /* Small organics: the sugars that caramelise and the liquids that dissolve things. */
188
+ {
189
+ id: 'glucose',
190
+ formula: { C: 6, H: 12, O: 6 },
191
+ phase: PHASE_SOLID,
192
+ formationEnthalpy: -1273300,
193
+ cpA: 1220,
194
+ },
195
+ {
196
+ id: 'fructose',
197
+ formula: { C: 6, H: 12, O: 6 },
198
+ phase: PHASE_SOLID,
199
+ formationEnthalpy: -1265600,
200
+ cpA: 1220,
201
+ },
202
+ {
203
+ id: 'sucrose',
204
+ formula: { C: 12, H: 22, O: 11 },
205
+ phase: PHASE_SOLID,
206
+ formationEnthalpy: -2226100,
207
+ cpA: 1250,
208
+ },
209
+ {
210
+ id: 'ethanol',
211
+ formula: { C: 2, H: 6, O: 1 },
212
+ phase: PHASE_LIQUID,
213
+ formationEnthalpy: -277600,
214
+ cpA: 2440,
215
+ },
216
+ {
217
+ id: 'methanol',
218
+ formula: { C: 1, H: 4, O: 1 },
219
+ phase: PHASE_LIQUID,
220
+ formationEnthalpy: -239200,
221
+ cpA: 2530,
222
+ },
223
+ {
224
+ id: 'acetic-acid',
225
+ formula: { C: 2, H: 4, O: 2 },
226
+ phase: PHASE_LIQUID,
227
+ formationEnthalpy: -484300,
228
+ cpA: 2050,
229
+ },
230
+ {
231
+ id: 'glycerol',
232
+ formula: { C: 3, H: 8, O: 3 },
233
+ phase: PHASE_LIQUID,
234
+ formationEnthalpy: -669600,
235
+ cpA: 2430,
236
+ },
237
+ /* Lipids. Saturated is tristearin and unsaturated is triolein, which is the difference between a
238
+ fat that is solid at room temperature and an oil that is not — a melting point rather than a
239
+ reaction, so it lives on the substance in CH-1. */
240
+ {
241
+ id: 'triglyceride(saturated)',
242
+ formula: { C: 57, H: 110, O: 6 },
243
+ phase: PHASE_SOLID,
244
+ formationEnthalpy: -2193700,
245
+ cpA: 1900,
246
+ },
247
+ {
248
+ id: 'triglyceride(unsaturated)',
249
+ formula: { C: 57, H: 104, O: 6 },
250
+ phase: PHASE_LIQUID,
251
+ formationEnthalpy: -2005000,
252
+ cpA: 2000,
253
+ },
254
+ {
255
+ id: 'fatty-acid',
256
+ formula: { C: 18, H: 36, O: 2 },
257
+ phase: PHASE_SOLID,
258
+ formationEnthalpy: -947700,
259
+ cpA: 1600,
260
+ },
261
+ {
262
+ id: 'wax',
263
+ formula: { C: 25, H: 52 },
264
+ phase: PHASE_SOLID,
265
+ formationEnthalpy: -869199,
266
+ cpA: 2100,
267
+ pseudo: true,
268
+ },
269
+ /* Fuel surrogates. Petrol is octane and diesel is dodecane — real molecules standing in for a
270
+ distillation cut, which is what a surrogate is. Kerosene has no integer formula at all. */
271
+ {
272
+ id: 'octane',
273
+ formula: { C: 8, H: 18 },
274
+ phase: PHASE_LIQUID,
275
+ formationEnthalpy: -250100,
276
+ cpA: 2230,
277
+ },
278
+ {
279
+ id: 'diesel',
280
+ formula: { C: 12, H: 26 },
281
+ phase: PHASE_LIQUID,
282
+ formationEnthalpy: -350900,
283
+ cpA: 2210,
284
+ pseudo: true,
285
+ },
286
+ {
287
+ id: 'kerosene',
288
+ formula: { C: 12, H: 23 },
289
+ phase: PHASE_LIQUID,
290
+ formationEnthalpy: -279166,
291
+ cpA: 2010,
292
+ pseudo: true,
293
+ },
294
+ {
295
+ id: 'propane',
296
+ formula: { C: 3, H: 8 },
297
+ phase: PHASE_GAS,
298
+ formationEnthalpy: -103800,
299
+ cpA: 1670,
300
+ },
301
+ {
302
+ id: 'butane',
303
+ formula: { C: 4, H: 10 },
304
+ phase: PHASE_GAS,
305
+ formationEnthalpy: -125700,
306
+ cpA: 1700,
307
+ },
308
+ /* Polymers, as repeat units. PVC carries the chlorine that becomes HCl in a fire and polyurethane
309
+ carries the nitrogen that becomes HCN — which is why a burning sofa is the dangerous one, and
310
+ it falls out of the formulas rather than out of a rule. */
311
+ {
312
+ id: 'polyethylene',
313
+ formula: { C: 2, H: 4 },
314
+ phase: PHASE_SOLID,
315
+ formationEnthalpy: -54169,
316
+ cpA: 2300,
317
+ pseudo: true,
318
+ },
319
+ {
320
+ id: 'polypropylene',
321
+ formula: { C: 3, H: 6 },
322
+ phase: PHASE_SOLID,
323
+ formationEnthalpy: -102294,
324
+ cpA: 1920,
325
+ pseudo: true,
326
+ },
327
+ {
328
+ id: 'PVC',
329
+ formula: { C: 2, H: 3, Cl: 1 },
330
+ phase: PHASE_SOLID,
331
+ formationEnthalpy: -40232,
332
+ cpA: 900,
333
+ pseudo: true,
334
+ },
335
+ {
336
+ id: 'polystyrene',
337
+ formula: { C: 8, H: 8 },
338
+ phase: PHASE_SOLID,
339
+ formationEnthalpy: 72569,
340
+ cpA: 1300,
341
+ pseudo: true,
342
+ },
343
+ {
344
+ id: 'PMMA',
345
+ formula: { C: 5, H: 8, O: 2 },
346
+ phase: PHASE_SOLID,
347
+ formationEnthalpy: -487805,
348
+ cpA: 1470,
349
+ pseudo: true,
350
+ },
351
+ {
352
+ id: 'polyurethane',
353
+ formula: { C: 25, H: 42, N: 2, O: 6 },
354
+ phase: PHASE_SOLID,
355
+ formationEnthalpy: -3708086,
356
+ cpA: 1800,
357
+ pseudo: true,
358
+ },
359
+ /* Minerals. Gypsum is here for one reason: the two waters it loses at 100-150 C are an enormous
360
+ latent-heat sink, which is *why* plasterboard is a fire barrier. Limestone is here for the
361
+ other: calcination is endothermic and releases gas, which is why stone spalls in a fire. */
362
+ {
363
+ id: 'SiO2',
364
+ formula: { Si: 1, O: 2 },
365
+ phase: PHASE_SOLID,
366
+ formationEnthalpy: -910700,
367
+ cpA: 740,
368
+ },
369
+ {
370
+ id: 'CaCO3',
371
+ formula: { Ca: 1, C: 1, O: 3 },
372
+ phase: PHASE_SOLID,
373
+ formationEnthalpy: -1207600,
374
+ cpA: 820,
375
+ },
376
+ { id: 'CaO', formula: { Ca: 1, O: 1 }, phase: PHASE_SOLID, formationEnthalpy: -634900, cpA: 750 },
377
+ {
378
+ id: 'gypsum',
379
+ formula: { Ca: 1, S: 1, O: 6, H: 4 },
380
+ phase: PHASE_SOLID,
381
+ formationEnthalpy: -2022600,
382
+ cpA: 1090,
383
+ },
384
+ {
385
+ id: 'anhydrite',
386
+ formula: { Ca: 1, S: 1, O: 4 },
387
+ phase: PHASE_SOLID,
388
+ formationEnthalpy: -1434500,
389
+ cpA: 730,
390
+ },
391
+ {
392
+ id: 'Al2O3',
393
+ formula: { Al: 2, O: 3 },
394
+ phase: PHASE_SOLID,
395
+ formationEnthalpy: -1675700,
396
+ cpA: 880,
397
+ },
398
+ {
399
+ id: 'Fe2O3',
400
+ formula: { Fe: 2, O: 3 },
401
+ phase: PHASE_SOLID,
402
+ formationEnthalpy: -824200,
403
+ cpA: 650,
404
+ },
405
+ {
406
+ id: 'Fe3O4',
407
+ formula: { Fe: 3, O: 4 },
408
+ phase: PHASE_SOLID,
409
+ formationEnthalpy: -1118400,
410
+ cpA: 620,
411
+ },
412
+ { id: 'MgO', formula: { Mg: 1, O: 1 }, phase: PHASE_SOLID, formationEnthalpy: -601600, cpA: 930 },
413
+ { id: 'CuO', formula: { Cu: 1, O: 1 }, phase: PHASE_SOLID, formationEnthalpy: -157300, cpA: 530 },
414
+ /* Ash is the one species with an arbitrary enthalpy, and it is arbitrary because nothing ever
415
+ produces or consumes it: a substance holds its ash from the start and every reaction leaves it
416
+ alone. Zero is therefore not a claim about ash, it is the absence of one. */
417
+ {
418
+ id: 'ash',
419
+ formula: { Si: 0.4, Ca: 0.3, K: 0.15, Mg: 0.1, P: 0.05, O: 1.2 },
420
+ phase: PHASE_SOLID,
421
+ formationEnthalpy: 0,
422
+ cpA: 800,
423
+ pseudo: true,
424
+ },
425
+ /* Metals. Magnesium is here because it burns, which most metals do not at any temperature a fire
426
+ reaches, and iron because it rusts, which is the slowest reaction in the whole model. */
427
+ { id: 'Fe', formula: { Fe: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 449 },
428
+ { id: 'Al', formula: { Al: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 897 },
429
+ { id: 'Cu', formula: { Cu: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 385 },
430
+ { id: 'Mg', formula: { Mg: 1 }, phase: PHASE_SOLID, formationEnthalpy: 0, cpA: 1023 },
431
+ ];
432
+ /** Register the whole standard set into a registry, in order. */
433
+ export function registerStandardSpecies(registry) {
434
+ for (const definition of STANDARD_SPECIES)
435
+ registry.register(definition);
436
+ }
@@ -0,0 +1,18 @@
1
+ /** K. Below this nothing in this engine is modelled, and a reading clamps rather than extrapolates. */
2
+ export declare const MIN_MODEL_TEMPERATURE = 100;
3
+ /** K. Above this likewise — past any flame this model produces. */
4
+ export declare const MAX_MODEL_TEMPERATURE = 4000;
5
+ /** Specific enthalpy of a species at a temperature, J/kg, zero at the standard state. */
6
+ export declare function sensibleEnthalpy(cpA: number, cpB: number, temperature: number): number;
7
+ /**
8
+ * The temperature a body of heat capacity `A + B·ΔT` sits at holding `joules`.
9
+ *
10
+ * `A` and `B` are **absolute** — kilograms times specific heat, summed over whatever is present —
11
+ * so `joules` is the total the thing holds rather than a specific value, and there is no division
12
+ * before the inversion.
13
+ *
14
+ * Clamped at both ends rather than extrapolated, and answering the standard state for a body with
15
+ * no heat capacity at all: a temperature is not a meaningful question about an empty shell, and
16
+ * inventing one is worse than declining.
17
+ */
18
+ export declare function temperatureFromCapacity(A: number, B: number, joules: number): number;
@@ -0,0 +1,72 @@
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.js';
28
+ /** K. Below this nothing in this engine is modelled, and a reading clamps rather than extrapolates. */
29
+ export const MIN_MODEL_TEMPERATURE = 100;
30
+ /** K. Above this likewise — past any flame this model produces. */
31
+ export const MAX_MODEL_TEMPERATURE = 4000;
32
+ /** Specific enthalpy of a species at a temperature, J/kg, zero at the standard state. */
33
+ export function sensibleEnthalpy(cpA, cpB, temperature) {
34
+ const dT = temperature - STANDARD_TEMPERATURE;
35
+ return cpA * dT + (cpB * dT * dT) / 2;
36
+ }
37
+ /**
38
+ * The temperature a body of heat capacity `A + B·ΔT` sits at holding `joules`.
39
+ *
40
+ * `A` and `B` are **absolute** — kilograms times specific heat, summed over whatever is present —
41
+ * so `joules` is the total the thing holds rather than a specific value, and there is no division
42
+ * before the inversion.
43
+ *
44
+ * Clamped at both ends rather than extrapolated, and answering the standard state for a body with
45
+ * no heat capacity at all: a temperature is not a meaningful question about an empty shell, and
46
+ * inventing one is worse than declining.
47
+ */
48
+ export function temperatureFromCapacity(A, B, joules) {
49
+ if (!(A > 0))
50
+ return STANDARD_TEMPERATURE;
51
+ let dT;
52
+ if (B === 0) {
53
+ dT = joules / A;
54
+ }
55
+ else {
56
+ const discriminant = A * A + 2 * B * joules;
57
+ if (!(discriminant >= 0)) {
58
+ /* Past the turning point of the quadratic, which means past where a linear `cp` is a model of
59
+ anything. The sign of `B` says which end it ran off. */
60
+ return B > 0 ? MIN_MODEL_TEMPERATURE : MAX_MODEL_TEMPERATURE;
61
+ }
62
+ /* The branch through `ΔT = 0`, which is this root for either sign of `B`: at `h = 0` the
63
+ discriminant is `A²`, the square root is `A`, and the numerator is zero. */
64
+ dT = (-A + Math.sqrt(discriminant)) / B;
65
+ }
66
+ const temperature = STANDARD_TEMPERATURE + dT;
67
+ if (temperature < MIN_MODEL_TEMPERATURE)
68
+ return MIN_MODEL_TEMPERATURE;
69
+ if (temperature > MAX_MODEL_TEMPERATURE)
70
+ return MAX_MODEL_TEMPERATURE;
71
+ return temperature;
72
+ }
@@ -0,0 +1,29 @@
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
+ * @param referenceTemperature K, where this liquid boils at `referencePressure`
25
+ * @param latentHeat J **per kilogram** of the species that changes phase
26
+ * @param molarMass kg/mol, matching `SpeciesRegistry.molarMass`
27
+ * @param pressure Pa
28
+ */
29
+ export declare function boilingPoint(referenceTemperature: number, latentHeat: number, molarMass: number, pressure: number, referencePressure?: number): number;
@@ -0,0 +1,54 @@
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
+ /** J/(mol·K). */
24
+ const GAS_CONSTANT = 8.314462618;
25
+ /** Pa. The standard atmosphere every reference temperature in this package is quoted at. */
26
+ const REFERENCE_PRESSURE = 101325;
27
+ /**
28
+ * @param referenceTemperature K, where this liquid boils at `referencePressure`
29
+ * @param latentHeat J **per kilogram** of the species that changes phase
30
+ * @param molarMass kg/mol, matching `SpeciesRegistry.molarMass`
31
+ * @param pressure Pa
32
+ */
33
+ export function boilingPoint(referenceTemperature, latentHeat, molarMass, pressure, referencePressure = REFERENCE_PRESSURE) {
34
+ if (!(pressure > 0)) {
35
+ throw new Error(`boiling point asked at a pressure of ${pressure} Pa`);
36
+ }
37
+ if (!(referencePressure > 0)) {
38
+ throw new Error(`boiling point asked against a reference pressure of ${referencePressure} Pa`);
39
+ }
40
+ if (!(latentHeat > 0)) {
41
+ throw new Error(`latent heat is ${latentHeat}, which is not positive`);
42
+ }
43
+ if (!(molarMass > 0)) {
44
+ throw new Error(`molar mass is ${molarMass}, which is not positive`);
45
+ }
46
+ const molarLatentHeat = latentHeat * molarMass;
47
+ // determinism: build-time — a boiling point derived at registration, from constants
48
+ const logPressureRatio = Math.log(pressure / referencePressure);
49
+ const inverse = 1 / referenceTemperature - (GAS_CONSTANT / molarLatentHeat) * logPressureRatio;
50
+ if (!(inverse > 0)) {
51
+ throw new Error(`a pressure of ${pressure} Pa puts this liquid past its critical point`);
52
+ }
53
+ return 1 / inverse;
54
+ }
@@ -0,0 +1,34 @@
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
+ * How far a hand-written composition may be from summing to one.
15
+ *
16
+ * Generous enough for six fractions typed to three decimals, tight enough that a transposed digit
17
+ * is a refusal rather than a material that quietly loses half a percent of its mass on every
18
+ * conservation check.
19
+ */
20
+ export declare const SUM_TOLERANCE = 0.000001;
21
+ export interface Composition {
22
+ /** Species indices, ascending. */
23
+ readonly species: Int32Array;
24
+ /** Mass fraction of each, parallel to `species`. */
25
+ readonly fraction: Float64Array;
26
+ }
27
+ /**
28
+ * A composition from species ids and mass fractions.
29
+ *
30
+ * Fails fast on anything wrong, per `AGENTS.md`: an unknown id, a fraction that is negative or not
31
+ * a number, a sum that is not one. Each names the offending value, because "invalid composition" in
32
+ * a stack trace is a sentence somebody has to reproduce to act on.
33
+ */
34
+ export declare function massFractions(registry: SpeciesRegistry, entries: Readonly<Record<string, number>>): Composition;