@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,229 @@
1
+ /**
2
+ * `@driftengine/chemistry/library/biological` — decay, fermentation and the heat they make.
3
+ *
4
+ * **Every rate here is peaked rather than monotonic, and `§8.6` refuses Arrhenius structurally.** An
5
+ * enzyme-catalysed rate rises to an optimum and then collapses as the enzyme denatures; modelling
6
+ * decay with Arrhenius gives compost that gets hotter forever, which is the single most common
7
+ * mistake in this area and is worth making impossible rather than discouraged.
8
+ *
9
+ * **The two enthalpies are the published ones and neither is stored.** Aerobic respiration comes out
10
+ * at −2,802.7 kJ/mol against `§8.6`'s quoted 2,803, and fermentation at −68.9 against a textbook
11
+ * −68. That the ratio is forty is the whole reason fermentation is a survival strategy and
12
+ * respiration is a fire hazard.
13
+ *
14
+ * **Nothing decides the aerobic/anaerobic branch.** Aerobic decay has oxygen as a reactant, so a
15
+ * submerged parcel cannot run it and runs the methane path instead. A fallen log rots one way and a
16
+ * drowned one rots the other, out of mass action.
17
+ */
18
+ import { KIND_BIOLOGICAL } from '../reaction/reaction.js';
19
+ import { SHAPE_CYLINDER, SHAPE_SPHERE } from '../parcel/shells.js';
20
+ import { massFractions } from '../substance/composition.js';
21
+ import { wetComposition } from '../substance/substance.js';
22
+ import { BOIL_WATER, COMMON_REACTIONS } from './common.js';
23
+ export const DECAY_MESOPHILIC = 'decay-mesophilic';
24
+ export const DECAY_THERMOPHILIC = 'decay-thermophilic';
25
+ export const DECAY_ANAEROBIC = 'decay-anaerobic';
26
+ export const FERMENT = 'ferment';
27
+ /**
28
+ * The rate at the optimum, per second, and it is **back-derived from a measured self-heating rate**.
29
+ *
30
+ * A wet bale or a fresh heap warms at roughly twenty to thirty kelvin a day in its mesophilic phase,
31
+ * which is a measurement anyone with a compost thermometer can make. Working back through the
32
+ * enthalpy of respiration, the labile sugar fraction and the heat capacity of wet organic matter
33
+ * gives this. There is no published pre-exponential for "decay"; there is a published temperature.
34
+ */
35
+ const MESOPHILIC_PEAK = 6e-5;
36
+ const REACTIONS = [
37
+ ...COMMON_REACTIONS,
38
+ {
39
+ /*
40
+ * `C₆H₁₂O₆ + 6 O₂ → 6 CO₂ + 6 H₂O`, and the water is **liquid** because a compost heap is wet.
41
+ * That is what makes the derived enthalpy 2,803 kJ/mol rather than the 2,539 it would be if the
42
+ * water left as steam — and 2,803 is the figure `§8.6` quotes.
43
+ */
44
+ id: DECAY_MESOPHILIC,
45
+ reactants: [
46
+ { species: 'glucose', moles: 1 },
47
+ { species: 'O2', moles: 6 },
48
+ ],
49
+ products: [
50
+ { species: 'CO2', moles: 6 },
51
+ { species: 'H2O(l)', moles: 6 },
52
+ ],
53
+ kinetics: {
54
+ type: 'cardinal',
55
+ peak: MESOPHILIC_PEAK,
56
+ minimum: 273.15,
57
+ optimum: 308.15,
58
+ maximum: 318.15,
59
+ },
60
+ kind: KIND_BIOLOGICAL,
61
+ },
62
+ {
63
+ /*
64
+ * The same reaction, a different population. **It takes over exactly where the first one dies**,
65
+ * which is why a heap steams: the mesophiles heat it past their own maximum and hand it on.
66
+ * Nothing hands it on; two bands overlap by five kelvin and the rates do the rest.
67
+ */
68
+ id: DECAY_THERMOPHILIC,
69
+ reactants: [
70
+ { species: 'glucose', moles: 1 },
71
+ { species: 'O2', moles: 6 },
72
+ ],
73
+ products: [
74
+ { species: 'CO2', moles: 6 },
75
+ { species: 'H2O(l)', moles: 6 },
76
+ ],
77
+ kinetics: {
78
+ type: 'cardinal',
79
+ peak: MESOPHILIC_PEAK * 3,
80
+ minimum: 313.15,
81
+ optimum: 328.15,
82
+ maximum: 343.15,
83
+ },
84
+ kind: KIND_BIOLOGICAL,
85
+ },
86
+ {
87
+ /* The swamp-gas path: no oxygen consumed, so it is the only one a submerged parcel can run, and
88
+ it releases a fortieth of the heat — which is why a bog does not catch fire. */
89
+ id: DECAY_ANAEROBIC,
90
+ reactants: [{ species: 'glucose', moles: 1 }],
91
+ products: [
92
+ { species: 'CH4', moles: 3 },
93
+ { species: 'CO2', moles: 3 },
94
+ ],
95
+ kinetics: {
96
+ type: 'cardinal',
97
+ peak: 5e-6,
98
+ minimum: 283.15,
99
+ optimum: 310.15,
100
+ maximum: 318.15,
101
+ },
102
+ kind: KIND_BIOLOGICAL,
103
+ },
104
+ {
105
+ /* Yeast, and it dies above 40 °C. The ethanol it makes is the fuel library's fuel, so a cellar
106
+ that ferments and then catches is two libraries meeting with nothing between them. */
107
+ id: FERMENT,
108
+ reactants: [{ species: 'glucose', moles: 1 }],
109
+ products: [
110
+ { species: 'ethanol', moles: 2 },
111
+ { species: 'CO2', moles: 2 },
112
+ ],
113
+ kinetics: {
114
+ type: 'cardinal',
115
+ peak: 3e-4,
116
+ minimum: 278.15,
117
+ optimum: 303.15,
118
+ maximum: 313.15,
119
+ },
120
+ kind: KIND_BIOLOGICAL,
121
+ },
122
+ ];
123
+ const DECAY = [DECAY_MESOPHILIC, DECAY_THERMOPHILIC, DECAY_ANAEROBIC];
124
+ export const BIOLOGICAL = {
125
+ id: 'biological',
126
+ species: [],
127
+ reactions: REACTIONS,
128
+ substances: (species) => [
129
+ {
130
+ /* Half water by mass, which is what a heap has to be for anything to live in it. */
131
+ id: 'compost',
132
+ composition: wetComposition(species, massFractions(species, {
133
+ cellulose: 0.35,
134
+ hemicellulose: 0.2,
135
+ lignin: 0.2,
136
+ glucose: 0.1,
137
+ protein: 0.05,
138
+ ash: 0.1,
139
+ }), 1.0),
140
+ density: 400,
141
+ conductivity: { k0: 0.15, k1: 0.0003 },
142
+ shells: 3,
143
+ shape: SHAPE_SPHERE,
144
+ porosity: 0.6,
145
+ emissivity: 0.92,
146
+ reactions: [BOIL_WATER, ...DECAY],
147
+ appearance: {
148
+ albedo: [0.18, 0.14, 0.1],
149
+ charAlbedo: [0.03, 0.03, 0.03],
150
+ sootYield: 0.02,
151
+ },
152
+ },
153
+ {
154
+ /*
155
+ * **The one the design points at.** Twenty-five percent moisture is the dangerous band: wet
156
+ * enough to respire and dry enough that the water is not itself the heat sink that stops it.
157
+ * Low conductivity and low density mean a large bale keeps what it makes.
158
+ */
159
+ id: 'hay',
160
+ composition: wetComposition(species, massFractions(species, {
161
+ cellulose: 0.36,
162
+ hemicellulose: 0.26,
163
+ lignin: 0.16,
164
+ glucose: 0.06,
165
+ protein: 0.06,
166
+ ash: 0.1,
167
+ }), 0.25),
168
+ density: 130,
169
+ conductivity: { k0: 0.05, k1: 0.0001 },
170
+ shells: 3,
171
+ shape: SHAPE_CYLINDER,
172
+ porosity: 0.75,
173
+ emissivity: 0.9,
174
+ reactions: [BOIL_WATER, ...DECAY],
175
+ /* Cellulosic, so it catches on cellulosic terms once something gets it there. */
176
+ ignition: { pilotedSurfaceK: 623, autoSurfaceK: 773, criticalMassFlux: 0.0035 },
177
+ appearance: {
178
+ albedo: [0.58, 0.5, 0.28],
179
+ charAlbedo: [0.04, 0.04, 0.04],
180
+ sootYield: 0.015,
181
+ },
182
+ },
183
+ {
184
+ /* Living tissue, which is the same reactions running slowly enough to be called being alive. */
185
+ id: 'flesh',
186
+ composition: wetComposition(species, massFractions(species, {
187
+ protein: 0.7,
188
+ 'triglyceride(saturated)': 0.15,
189
+ collagen: 0.08,
190
+ glucose: 0.05,
191
+ ash: 0.02,
192
+ }), 2.33),
193
+ density: 1010,
194
+ conductivity: { k0: 0.49, k1: 0.0005 },
195
+ shells: 4,
196
+ shape: SHAPE_SPHERE,
197
+ porosity: 0.05,
198
+ emissivity: 0.98,
199
+ reactions: [BOIL_WATER, ...DECAY],
200
+ appearance: {
201
+ albedo: [0.44, 0.16, 0.14],
202
+ charAlbedo: [0.05, 0.03, 0.02],
203
+ sootYield: 0.04,
204
+ },
205
+ },
206
+ {
207
+ /* Grape must: mostly sugar and water, and it ferments rather than decays because that is what
208
+ the rates say at cellar temperature with no air getting in. */
209
+ id: 'must',
210
+ composition: wetComposition(species, massFractions(species, {
211
+ glucose: 0.85,
212
+ fructose: 0.1,
213
+ protein: 0.02,
214
+ ash: 0.03,
215
+ }), 4.0),
216
+ density: 1080,
217
+ conductivity: { k0: 0.55, k1: 0.0005 },
218
+ shells: 2,
219
+ shape: SHAPE_SPHERE,
220
+ emissivity: 0.96,
221
+ reactions: [BOIL_WATER, FERMENT, DECAY_MESOPHILIC],
222
+ appearance: {
223
+ albedo: [0.32, 0.1, 0.16],
224
+ charAlbedo: [0.32, 0.1, 0.16],
225
+ sootYield: 0.01,
226
+ },
227
+ },
228
+ ],
229
+ };
@@ -0,0 +1,26 @@
1
+ import { type Reaction } from '../reaction/reaction.ts';
2
+ export declare const MELT_ICE = "melt-ice";
3
+ export declare const BOIL_WATER = "boil-water";
4
+ export declare const BURN_METHANE = "burn-methane";
5
+ export declare const BURN_CARBON_MONOXIDE = "burn-carbon-monoxide";
6
+ export declare const BURN_HYDROGEN = "burn-hydrogen";
7
+ export declare const BURN_ETHYLENE = "burn-ethylene";
8
+ export declare const BURN_SOOT = "burn-soot";
9
+ export declare const SOOT_METHANE = "soot-methane";
10
+ export declare const OXIDISE_CHAR = "oxidise-char";
11
+ export declare const OXIDISE_CHAR_RICH = "oxidise-char-rich";
12
+ /** The gas-phase steps, which is what a `AtmosphereField` is given rather than a parcel. */
13
+ export declare const GAS_REACTIONS: readonly string[];
14
+ /**
15
+ * How much char goes to CO rather than CO₂ at a temperature, per Arthur's measured relation.
16
+ *
17
+ * **Nothing in the model calls this.** It is here so a test can assert that the two char reactions
18
+ * reproduce it, and so a reader can see what the two activation energies below mean. The relation
19
+ * itself is `§8.5`'s, and its consequence is the one the design cares about: a cool smoulder makes
20
+ * mostly CO₂ and a hot one makes mostly CO, so an oxygen-starved fire in a closed room produces
21
+ * carbon monoxide faster than an open one would.
22
+ */
23
+ export declare function arthurRatio(temperature: number): number;
24
+ export declare const COMMON_REACTIONS: readonly Reaction[];
25
+ /** Every char and water reaction a solid needs, which is what a burnable substance declares. */
26
+ export declare const SOLID_COMMON: readonly string[];
@@ -0,0 +1,208 @@
1
+ /**
2
+ * The reactions every family reaches through: water changing phase, gas burning, char oxidising.
3
+ *
4
+ * **Not an entry point.** A consumer imports a family and gets whatever of this it needs, so
5
+ * `library/mineral` never pulls in a methane flame.
6
+ *
7
+ * **The activation energies are literature; the pre-exponentials are order of magnitude.** A
8
+ * published single-step combustion rate law carries fractional reaction orders — Westbrook and
9
+ * Dryer give methane as `[CH₄]^−0.3·[O₂]^1.3` — and there is no exact conversion of that into the
10
+ * first-order mass action this solver runs. So `Ea` is the measured barrier and `A` is chosen to put
11
+ * the rate in the right decade at flame temperature. Said here rather than implied, because a
12
+ * reader is entitled to know which half of a rate constant is a measurement.
13
+ *
14
+ * That is a bounded looseness rather than a free one: a flame's *temperature* comes from the
15
+ * enthalpy, which is derived, and its *extent* from the oxygen the field delivers, which is
16
+ * transport. What `A` sets is how thin the reaction zone is.
17
+ */
18
+ import { GAS_CONSTANT } from '../reaction/rates.js';
19
+ import { KIND_CHAR_OXIDATION, KIND_COMBUSTION, phaseChange, } from '../reaction/reaction.js';
20
+ export const MELT_ICE = 'melt-ice';
21
+ export const BOIL_WATER = 'boil-water';
22
+ export const BURN_METHANE = 'burn-methane';
23
+ export const BURN_CARBON_MONOXIDE = 'burn-carbon-monoxide';
24
+ export const BURN_HYDROGEN = 'burn-hydrogen';
25
+ export const BURN_ETHYLENE = 'burn-ethylene';
26
+ export const BURN_SOOT = 'burn-soot';
27
+ export const SOOT_METHANE = 'soot-methane';
28
+ export const OXIDISE_CHAR = 'oxidise-char';
29
+ export const OXIDISE_CHAR_RICH = 'oxidise-char-rich';
30
+ /** The gas-phase steps, which is what a `AtmosphereField` is given rather than a parcel. */
31
+ export const GAS_REACTIONS = [
32
+ BURN_METHANE,
33
+ BURN_CARBON_MONOXIDE,
34
+ BURN_HYDROGEN,
35
+ BURN_ETHYLENE,
36
+ BURN_SOOT,
37
+ SOOT_METHANE,
38
+ ];
39
+ /**
40
+ * How much char goes to CO rather than CO₂ at a temperature, per Arthur's measured relation.
41
+ *
42
+ * **Nothing in the model calls this.** It is here so a test can assert that the two char reactions
43
+ * reproduce it, and so a reader can see what the two activation energies below mean. The relation
44
+ * itself is `§8.5`'s, and its consequence is the one the design cares about: a cool smoulder makes
45
+ * mostly CO₂ and a hot one makes mostly CO, so an oxygen-starved fire in a closed room produces
46
+ * carbon monoxide faster than an open one would.
47
+ */
48
+ export function arthurRatio(temperature) {
49
+ // determinism: build-time — a substance's tabulated property, built at registration
50
+ return 2500 * Math.exp(-6240 / temperature);
51
+ }
52
+ /**
53
+ * The gap between the two char paths' activation energies, and it is `6240·R` exactly.
54
+ *
55
+ * That is the whole trick. `k_rich/k_lean = (A_rich/A_lean)·exp(−ΔEa/R·T)`, so setting
56
+ * `ΔEa = 6240·R` and `A_rich/A_lean = 2500` makes the ratio Arthur's relation identically, at every
57
+ * temperature, with nothing tabulated and no branch anywhere.
58
+ */
59
+ const ARTHUR_GAP = 6240 * GAS_CONSTANT;
60
+ /** Kinetically controlled char oxidation, before transport takes over above about 800 K. */
61
+ const CHAR_A = 1e7;
62
+ const CHAR_EA = 140000;
63
+ /**
64
+ * The ceiling oxygen delivery puts on char oxidation in still air, per second.
65
+ *
66
+ * **This is what makes char glow for an hour rather than vanishing in a minute**, and `§7` is the
67
+ * argument. Above about 800 K the kinetic rate is orders of magnitude faster than oxygen can reach
68
+ * the surface, so what is being watched is transport; the solver combines the two as a series
69
+ * resistance and the transport half wins. It rises with the gas moving past, which is why blowing
70
+ * on an ember brightens it.
71
+ *
72
+ * At this value a kilogram of char lasts tens of thousands of seconds in still air, which is the
73
+ * hour a real bed of embers lasts.
74
+ */
75
+ const CHAR_TRANSPORT = 1e-5;
76
+ export const COMMON_REACTIONS = [
77
+ /*
78
+ * The two water transitions, as CH-6's gated reactions rather than as shape in a curve. Their
79
+ * latent heats are Hess's law over the formation enthalpies and are not written anywhere: 333.6
80
+ * kJ/kg for fusion and 2,442.6 for vaporisation, the latter being the value at 298.15 K rather
81
+ * than the 2,256.4 quoted at the boiling point — which is right, and is why the latent heat at any
82
+ * temperature is a consequence here rather than a stored number.
83
+ */
84
+ phaseChange(MELT_ICE, 'H2O(s)', 'H2O(l)', 273.15),
85
+ phaseChange(BOIL_WATER, 'H2O(l)', 'H2O(g)', 373.15),
86
+ /* Gas-phase combustion. Every flame over a solid is one of these; the solid only makes the gas. */
87
+ {
88
+ id: BURN_METHANE,
89
+ reactants: [
90
+ { species: 'CH4', moles: 1 },
91
+ { species: 'O2', moles: 2 },
92
+ ],
93
+ products: [
94
+ { species: 'CO2', moles: 1 },
95
+ { species: 'H2O(g)', moles: 2 },
96
+ ],
97
+ kinetics: { type: 'arrhenius', A: 2.1e11, activationEnergy: 202500 },
98
+ kind: KIND_COMBUSTION,
99
+ },
100
+ {
101
+ /* The second stage, and where a flame's blue comes from. It is also the slow one: CO burns out
102
+ well after the hydrocarbons, which is why a rich flame's tip is where the CO finally goes. */
103
+ id: BURN_CARBON_MONOXIDE,
104
+ reactants: [
105
+ { species: 'CO', moles: 1 },
106
+ { species: 'O2', moles: 0.5 },
107
+ ],
108
+ products: [{ species: 'CO2', moles: 1 }],
109
+ kinetics: { type: 'arrhenius', A: 1e10, activationEnergy: 167400 },
110
+ kind: KIND_COMBUSTION,
111
+ },
112
+ {
113
+ id: BURN_HYDROGEN,
114
+ reactants: [
115
+ { species: 'H2', moles: 1 },
116
+ { species: 'O2', moles: 0.5 },
117
+ ],
118
+ products: [{ species: 'H2O(g)', moles: 1 }],
119
+ kinetics: { type: 'arrhenius', A: 1e11, activationEnergy: 125500 },
120
+ kind: KIND_COMBUSTION,
121
+ },
122
+ {
123
+ id: BURN_ETHYLENE,
124
+ reactants: [
125
+ { species: 'C2H4', moles: 1 },
126
+ { species: 'O2', moles: 3 },
127
+ ],
128
+ products: [
129
+ { species: 'CO2', moles: 2 },
130
+ { species: 'H2O(g)', moles: 2 },
131
+ ],
132
+ kinetics: { type: 'arrhenius', A: 2e12, activationEnergy: 125500 },
133
+ kind: KIND_COMBUSTION,
134
+ },
135
+ {
136
+ /* Soot burning out in the flame tip, which is what stops a luminous flame smoking when there is
137
+ enough air — and what fails to happen when there is not. */
138
+ id: BURN_SOOT,
139
+ reactants: [
140
+ { species: 'C(soot)', moles: 1 },
141
+ { species: 'O2', moles: 1 },
142
+ ],
143
+ products: [{ species: 'CO2', moles: 1 }],
144
+ kinetics: { type: 'arrhenius', A: 1e6, activationEnergy: 140000 },
145
+ kind: KIND_COMBUSTION,
146
+ },
147
+ {
148
+ /*
149
+ * **Soot, and nothing decides when it forms.** `§8.4` says a flame is orange because it holds
150
+ * incandescent soot and blue because it does not, and that the difference is the equivalence
151
+ * ratio — but there is no gate on φ here and there does not need to be. This reaction needs no
152
+ * oxygen and `burn-methane` needs two, so in a cell with air the oxidation wins and in one that
153
+ * has run out this is the only path left. **A rich flame soots and a lean one does not, out of
154
+ * mass action.**
155
+ *
156
+ * `CH₄ → C(soot) + 2 H₂` is methane's real thermal decomposition, and its activation energy
157
+ * puts the onset near 1,000 K — which is where soot genuinely starts forming in a flame.
158
+ *
159
+ * Soot is `C(soot)`, a **species**, so the carbon stays in the element ledger. A soot channel
160
+ * that carried mass outside `elementTotals` would make the most valuable assertion in the track
161
+ * quietly wrong every time a fire made smoke.
162
+ */
163
+ id: SOOT_METHANE,
164
+ reactants: [{ species: 'CH4', moles: 1 }],
165
+ products: [
166
+ { species: 'C(soot)', moles: 1 },
167
+ { species: 'H2', moles: 2 },
168
+ ],
169
+ kinetics: { type: 'arrhenius', A: 1e13, activationEnergy: 250000 },
170
+ kind: KIND_COMBUSTION,
171
+ },
172
+ /*
173
+ * Char, the two ways. See `ARTHUR_GAP`: the pair carries the measured CO/CO₂ split in the gap
174
+ * between their activation energies, so the split emerges rather than being decided anywhere.
175
+ */
176
+ {
177
+ id: OXIDISE_CHAR,
178
+ reactants: [
179
+ { species: 'C(char)', moles: 1 },
180
+ { species: 'O2', moles: 1 },
181
+ ],
182
+ products: [{ species: 'CO2', moles: 1 }],
183
+ kinetics: {
184
+ type: 'diffusion',
185
+ A: CHAR_A,
186
+ activationEnergy: CHAR_EA,
187
+ transportLimit: CHAR_TRANSPORT,
188
+ },
189
+ kind: KIND_CHAR_OXIDATION,
190
+ },
191
+ {
192
+ id: OXIDISE_CHAR_RICH,
193
+ reactants: [
194
+ { species: 'C(char)', moles: 1 },
195
+ { species: 'O2', moles: 0.5 },
196
+ ],
197
+ products: [{ species: 'CO', moles: 1 }],
198
+ kinetics: {
199
+ type: 'diffusion',
200
+ A: CHAR_A * 2500,
201
+ activationEnergy: CHAR_EA + ARTHUR_GAP,
202
+ transportLimit: CHAR_TRANSPORT,
203
+ },
204
+ kind: KIND_CHAR_OXIDATION,
205
+ },
206
+ ];
207
+ /** Every char and water reaction a solid needs, which is what a burnable substance declares. */
208
+ export const SOLID_COMMON = [BOIL_WATER, OXIDISE_CHAR, OXIDISE_CHAR_RICH];
@@ -0,0 +1,9 @@
1
+ import type { SubstanceLibrary } from './library.ts';
2
+ export declare const MELT_FAT = "melt-fat";
3
+ export declare const DENATURE_MYOSIN = "denature-myosin";
4
+ export declare const DENATURE_ALBUMEN = "denature-albumen";
5
+ export declare const GELATINISE_COLLAGEN = "gelatinise-collagen";
6
+ export declare const HYDROLYSE_STARCH = "hydrolyse-starch";
7
+ export declare const BROWN_MAILLARD = "brown-maillard";
8
+ export declare const CARAMELISE = "caramelise";
9
+ export declare const FOOD: SubstanceLibrary;