@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,97 @@
1
+ /**
2
+ * Ignition is a criterion, not a flag — and extinction is four of them.
3
+ *
4
+ * **There is no `ignite()` that sets a boolean and starts a fire**, and `§11` is the argument.
5
+ * Ignition is what happens when the conditions for it are met, and there are five because there are
6
+ * five real ones. `ignite` supplies a *pilot* for one tick; holding a match to wet wood does exactly
7
+ * nothing, which is correct and is the API telling the truth about what a match is.
8
+ *
9
+ * **The mass flux is the real criterion and the temperature is the cheap proxy.** Ignition happens
10
+ * when enough fuel is coming off the surface to make a flammable mixture above it; the surface
11
+ * temperature is a correlate of that rather than a cause. Both are kept, because the temperature is
12
+ * what a consumer reads in an interface — *this is getting hot* — and the flux is what decides.
13
+ */
14
+ export interface IgnitionModel {
15
+ /** K at which a surface will catch **given a pilot**. Wood: 623. */
16
+ readonly pilotedSurfaceK: number;
17
+ /** K at which it catches with no pilot at all. Wood: 773, and the gap is why a spark matters. */
18
+ readonly autoSurfaceK: number;
19
+ /** kg/(m²·s) of volatiles leaving the surface. Wood: about 0.0035, or 3.5 g/m²·s. */
20
+ readonly criticalMassFlux: number;
21
+ /** Volume fraction below which a flame cannot be sustained. About 0.14 for most solids. */
22
+ readonly limitingOxygen?: number;
23
+ /** And below which even a smoulder cannot. Far lower — about 0.05 — which is why smothering leaves embers. */
24
+ readonly smoulderingOxygen?: number;
25
+ /** Volume fractions between which this material's volatiles carry a flame. */
26
+ readonly lowerFlammable?: number;
27
+ readonly upperFlammable?: number;
28
+ }
29
+ export declare const DEFAULT_LIMITING_OXYGEN = 0.14;
30
+ export declare const DEFAULT_SMOULDERING_OXYGEN = 0.05;
31
+ /** m/s of local gas past a surface, beyond which a flame cannot anchor and is blown off. */
32
+ export declare const BLOW_OFF_SPEED = 8;
33
+ /**
34
+ * How close a parcel is to catching, 0 to 1.
35
+ *
36
+ * The **minimum** of the five criteria's individual satisfactions, so it reads as "the nearest
37
+ * thing to ready that everything is". A readout rather than a state: nothing branches on it, and a
38
+ * consumer wanting a *this is about to catch* indicator reads one number instead of five.
39
+ */
40
+ export declare function ignitionProgress(model: IgnitionModel, surfaceTemperature: number, fuelFlux: number, oxygenFraction: number, piloted: boolean): number;
41
+ /**
42
+ * The fuel fraction **at the surface**, which is not the fuel fraction in the room.
43
+ *
44
+ * A flame sits in a boundary layer a few millimetres thick, and a cell in this model is a cubic
45
+ * metre. Reading a cell average and calling it the mixture over a surface is a resolution error,
46
+ * and it is one this phase made and measured: a log at 1,063 K pouring off nearly a kilogram per
47
+ * square metre per second registered 1.7% combustible gas in the cell around it and never caught,
48
+ * because the flame was being asked to burn the room rather than the boundary layer.
49
+ *
50
+ * What is right is the fuel's share of everything leaving the surface plus everything sweeping past:
51
+ *
52
+ * C ≈ ṁ_fuel / (ṁ_total + v·ρ)
53
+ *
54
+ * so a strong flux is rich, a weak one is lean, and a wind thins it — the same mechanism that blows
55
+ * a flame off, arriving here too and for the same reason.
56
+ *
57
+ * **The two fluxes are separate, and that is what a green log turns on.** `§21`: steam leaving a wet
58
+ * surface dilutes the gas above it, so even once volatiles appear the mixture is below its lower
59
+ * limit. With one flux standing in for both, water vapour counted as fuel and a log at 60% moisture
60
+ * caught in front of a campfire — which it does not, and which this test said before this line
61
+ * existed.
62
+ */
63
+ export declare function surfaceFuelFraction(fuelFlux: number, totalFlux: number, gasSpeed: number): number;
64
+ /**
65
+ * Whether all five conditions for flaming ignition hold.
66
+ *
67
+ * Surface hot enough, enough fuel coming off it, oxygen above the limiting index, the mixture **at
68
+ * the surface** within its flammability limits, and — for the lower of the two temperatures — a
69
+ * pilot.
70
+ */
71
+ export declare function ignites(model: IgnitionModel, surfaceTemperature: number, fuelFlux: number, totalFlux: number, oxygenFraction: number, gasSpeed: number, piloted: boolean): boolean;
72
+ /** Volume fractions between which a lumped volatile mixture will carry a flame. */
73
+ export declare const DEFAULT_LOWER_FLAMMABLE = 0.03;
74
+ export declare const DEFAULT_UPPER_FLAMMABLE = 0.95;
75
+ /** Why a flame went out, or `-1` where it did not. The order is the order they are checked in. */
76
+ export declare const EXTINCT_NONE = -1;
77
+ export declare const EXTINCT_FUEL = 0;
78
+ export declare const EXTINCT_OXYGEN = 1;
79
+ export declare const EXTINCT_COOLED = 2;
80
+ export declare const EXTINCT_BLOWN_OFF = 3;
81
+ /**
82
+ * Whether a flame stops, and which of the four reasons it was.
83
+ *
84
+ * **Blow-off is the one that reads oddly and is right.** Blowing on a candle puts it out because
85
+ * the gas moves faster than the flame can propagate back into it; blowing on embers makes them
86
+ * brighter, because there is no flame to blow off and more oxygen arrives. Both come out of this
87
+ * function returning a reason for one and not for the other.
88
+ */
89
+ export declare function extinguishes(model: IgnitionModel, fuelFlux: number, oxygenFraction: number, surfaceTemperature: number, gasSpeed: number): number;
90
+ /**
91
+ * Whether a smoulder can persist: char present, and oxygen above the far lower index it needs.
92
+ *
93
+ * A smoulder needs no flammable mixture and no pilot, because there is no gas-phase flame at all —
94
+ * oxygen meets solid carbon at the surface. That is why it survives conditions a flame cannot, and
95
+ * why it is the half of a fire that reignites a room after the flames are out.
96
+ */
97
+ export declare function smoulders(model: IgnitionModel, charFraction: number, oxygenFraction: number, surfaceTemperature: number): boolean;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Ignition is a criterion, not a flag — and extinction is four of them.
3
+ *
4
+ * **There is no `ignite()` that sets a boolean and starts a fire**, and `§11` is the argument.
5
+ * Ignition is what happens when the conditions for it are met, and there are five because there are
6
+ * five real ones. `ignite` supplies a *pilot* for one tick; holding a match to wet wood does exactly
7
+ * nothing, which is correct and is the API telling the truth about what a match is.
8
+ *
9
+ * **The mass flux is the real criterion and the temperature is the cheap proxy.** Ignition happens
10
+ * when enough fuel is coming off the surface to make a flammable mixture above it; the surface
11
+ * temperature is a correlate of that rather than a cause. Both are kept, because the temperature is
12
+ * what a consumer reads in an interface — *this is getting hot* — and the flux is what decides.
13
+ */
14
+ export const DEFAULT_LIMITING_OXYGEN = 0.14;
15
+ export const DEFAULT_SMOULDERING_OXYGEN = 0.05;
16
+ /** m/s of local gas past a surface, beyond which a flame cannot anchor and is blown off. */
17
+ export const BLOW_OFF_SPEED = 8;
18
+ /**
19
+ * How close a parcel is to catching, 0 to 1.
20
+ *
21
+ * The **minimum** of the five criteria's individual satisfactions, so it reads as "the nearest
22
+ * thing to ready that everything is". A readout rather than a state: nothing branches on it, and a
23
+ * consumer wanting a *this is about to catch* indicator reads one number instead of five.
24
+ */
25
+ export function ignitionProgress(model, surfaceTemperature, fuelFlux, oxygenFraction, piloted) {
26
+ const threshold = piloted ? model.pilotedSurfaceK : model.autoSurfaceK;
27
+ const limiting = model.limitingOxygen ?? DEFAULT_LIMITING_OXYGEN;
28
+ const heat = ratio(surfaceTemperature - 293.15, threshold - 293.15);
29
+ const fuel = ratio(fuelFlux, model.criticalMassFlux);
30
+ const air = ratio(oxygenFraction, limiting);
31
+ const smallest = Math.min(heat, fuel, air);
32
+ return smallest < 0 ? 0 : smallest > 1 ? 1 : smallest;
33
+ }
34
+ function ratio(value, against) {
35
+ if (!(against > 0))
36
+ return value > 0 ? 1 : 0;
37
+ return value / against;
38
+ }
39
+ /**
40
+ * The fuel fraction **at the surface**, which is not the fuel fraction in the room.
41
+ *
42
+ * A flame sits in a boundary layer a few millimetres thick, and a cell in this model is a cubic
43
+ * metre. Reading a cell average and calling it the mixture over a surface is a resolution error,
44
+ * and it is one this phase made and measured: a log at 1,063 K pouring off nearly a kilogram per
45
+ * square metre per second registered 1.7% combustible gas in the cell around it and never caught,
46
+ * because the flame was being asked to burn the room rather than the boundary layer.
47
+ *
48
+ * What is right is the fuel's share of everything leaving the surface plus everything sweeping past:
49
+ *
50
+ * C ≈ ṁ_fuel / (ṁ_total + v·ρ)
51
+ *
52
+ * so a strong flux is rich, a weak one is lean, and a wind thins it — the same mechanism that blows
53
+ * a flame off, arriving here too and for the same reason.
54
+ *
55
+ * **The two fluxes are separate, and that is what a green log turns on.** `§21`: steam leaving a wet
56
+ * surface dilutes the gas above it, so even once volatiles appear the mixture is below its lower
57
+ * limit. With one flux standing in for both, water vapour counted as fuel and a log at 60% moisture
58
+ * caught in front of a campfire — which it does not, and which this test said before this line
59
+ * existed.
60
+ */
61
+ export function surfaceFuelFraction(fuelFlux, totalFlux, gasSpeed) {
62
+ const sweep = (TRANSFER_VELOCITY + gasSpeed) * AIR_DENSITY;
63
+ const total = totalFlux + sweep;
64
+ return total > 0 ? fuelFlux / total : 0;
65
+ }
66
+ /** m/s of natural mass transfer past a surface in still air. */
67
+ const TRANSFER_VELOCITY = 0.02;
68
+ /** kg/m³. */
69
+ const AIR_DENSITY = 1.2;
70
+ /**
71
+ * Whether all five conditions for flaming ignition hold.
72
+ *
73
+ * Surface hot enough, enough fuel coming off it, oxygen above the limiting index, the mixture **at
74
+ * the surface** within its flammability limits, and — for the lower of the two temperatures — a
75
+ * pilot.
76
+ */
77
+ export function ignites(model, surfaceTemperature, fuelFlux, totalFlux, oxygenFraction, gasSpeed, piloted) {
78
+ const threshold = piloted ? model.pilotedSurfaceK : model.autoSurfaceK;
79
+ if (surfaceTemperature < threshold)
80
+ return false;
81
+ /* The critical mass flux is a flux of **fuel**, which is what the measured figure means. */
82
+ if (fuelFlux < model.criticalMassFlux)
83
+ return false;
84
+ if (oxygenFraction < (model.limitingOxygen ?? DEFAULT_LIMITING_OXYGEN))
85
+ return false;
86
+ const mixture = surfaceFuelFraction(fuelFlux, totalFlux, gasSpeed);
87
+ if (mixture < (model.lowerFlammable ?? DEFAULT_LOWER_FLAMMABLE))
88
+ return false;
89
+ if (mixture > (model.upperFlammable ?? DEFAULT_UPPER_FLAMMABLE))
90
+ return false;
91
+ return true;
92
+ }
93
+ /** Volume fractions between which a lumped volatile mixture will carry a flame. */
94
+ export const DEFAULT_LOWER_FLAMMABLE = 0.03;
95
+ export const DEFAULT_UPPER_FLAMMABLE = 0.95;
96
+ /** Why a flame went out, or `-1` where it did not. The order is the order they are checked in. */
97
+ export const EXTINCT_NONE = -1;
98
+ export const EXTINCT_FUEL = 0;
99
+ export const EXTINCT_OXYGEN = 1;
100
+ export const EXTINCT_COOLED = 2;
101
+ export const EXTINCT_BLOWN_OFF = 3;
102
+ /**
103
+ * Whether a flame stops, and which of the four reasons it was.
104
+ *
105
+ * **Blow-off is the one that reads oddly and is right.** Blowing on a candle puts it out because
106
+ * the gas moves faster than the flame can propagate back into it; blowing on embers makes them
107
+ * brighter, because there is no flame to blow off and more oxygen arrives. Both come out of this
108
+ * function returning a reason for one and not for the other.
109
+ */
110
+ export function extinguishes(model, fuelFlux, oxygenFraction, surfaceTemperature, gasSpeed) {
111
+ if (fuelFlux < model.criticalMassFlux)
112
+ return EXTINCT_FUEL;
113
+ if (oxygenFraction < (model.limitingOxygen ?? DEFAULT_LIMITING_OXYGEN))
114
+ return EXTINCT_OXYGEN;
115
+ /* A surface below its own piloted ignition point is no longer feeding a flame it could relight. */
116
+ if (surfaceTemperature < model.pilotedSurfaceK)
117
+ return EXTINCT_COOLED;
118
+ if (gasSpeed > BLOW_OFF_SPEED)
119
+ return EXTINCT_BLOWN_OFF;
120
+ return EXTINCT_NONE;
121
+ }
122
+ /**
123
+ * Whether a smoulder can persist: char present, and oxygen above the far lower index it needs.
124
+ *
125
+ * A smoulder needs no flammable mixture and no pilot, because there is no gas-phase flame at all —
126
+ * oxygen meets solid carbon at the surface. That is why it survives conditions a flame cannot, and
127
+ * why it is the half of a fire that reignites a room after the flames are out.
128
+ */
129
+ export function smoulders(model, charFraction, oxygenFraction, surfaceTemperature) {
130
+ if (!(charFraction > 0))
131
+ return false;
132
+ if (oxygenFraction < (model.smoulderingOxygen ?? DEFAULT_SMOULDERING_OXYGEN))
133
+ return false;
134
+ return surfaceTemperature > 600;
135
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * What happened this tick, as a buffer a caller drains.
3
+ *
4
+ * The shape `drift/physics`'s contact buffer established, and for the reason that one exists: a
5
+ * stream a consumer polls is allocation-free, and a callback per event is not. Cleared at the top
6
+ * of every tick, so an event not read this frame is gone — which is the same contract contacts have
7
+ * and the same one a consumer already knows.
8
+ *
9
+ * **The kinds are closed.** A consumer that wants a distinction this list does not draw reads the
10
+ * numbers instead; adding a kind is a decision about what the model claims to notice.
11
+ */
12
+ export declare const EVENT_IGNITED = 0;
13
+ export declare const EVENT_EXTINGUISHED = 1;
14
+ export declare const EVENT_SMOULDER_START = 2;
15
+ export declare const EVENT_SMOULDER_END = 3;
16
+ export declare const EVENT_CONSUMED = 4;
17
+ export declare const EVENT_CHARRED = 5;
18
+ export declare const EVENT_STRUCTURAL_FAIL = 6;
19
+ export declare const EVENT_FROZEN = 7;
20
+ export declare const EVENT_MELTED = 8;
21
+ export declare const EVENT_BOILED = 9;
22
+ export declare const EVENT_CONDENSED = 10;
23
+ export declare const EVENT_SUBLIMED = 11;
24
+ export declare const EVENT_CORRODED = 12;
25
+ export declare const EVENT_CALCINED = 13;
26
+ export declare const EVENT_DISSOLVED = 14;
27
+ export declare const EVENT_DENATURED = 15;
28
+ export declare const EVENT_BROWNED = 16;
29
+ export declare const EVENT_CARAMELISED = 17;
30
+ export declare const EVENT_FERMENTED = 18;
31
+ export declare const EVENT_DECAYED = 19;
32
+ /**
33
+ * A confined deflagration crossed a pressure threshold.
34
+ *
35
+ * `§26` refuses detonation in writing: a supersonic shock front is a physics problem in a package
36
+ * that owns no solver. This is the refusal **reporting** rather than simulating — the event says a
37
+ * pressure was exceeded and where, and the consumer decides what a wall does about it.
38
+ */
39
+ export declare const EVENT_EXPLODED = 20;
40
+ export declare const EVENT_KIND_COUNT = 21;
41
+ export declare class ChemistryEvents {
42
+ private kind;
43
+ private parcel;
44
+ private species;
45
+ private value;
46
+ private used;
47
+ get count(): number;
48
+ clear(): void;
49
+ /** @param species `-1` where the kind does not name one. @param value the kind's own quantity. */
50
+ push(kind: number, parcel: number, species: number, value: number): void;
51
+ kindOf(event: number): number;
52
+ parcelOf(event: number): number;
53
+ speciesOf(event: number): number;
54
+ valueOf(event: number): number;
55
+ private grow;
56
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * What happened this tick, as a buffer a caller drains.
3
+ *
4
+ * The shape `drift/physics`'s contact buffer established, and for the reason that one exists: a
5
+ * stream a consumer polls is allocation-free, and a callback per event is not. Cleared at the top
6
+ * of every tick, so an event not read this frame is gone — which is the same contract contacts have
7
+ * and the same one a consumer already knows.
8
+ *
9
+ * **The kinds are closed.** A consumer that wants a distinction this list does not draw reads the
10
+ * numbers instead; adding a kind is a decision about what the model claims to notice.
11
+ */
12
+ export const EVENT_IGNITED = 0;
13
+ export const EVENT_EXTINGUISHED = 1;
14
+ export const EVENT_SMOULDER_START = 2;
15
+ export const EVENT_SMOULDER_END = 3;
16
+ export const EVENT_CONSUMED = 4;
17
+ export const EVENT_CHARRED = 5;
18
+ export const EVENT_STRUCTURAL_FAIL = 6;
19
+ export const EVENT_FROZEN = 7;
20
+ export const EVENT_MELTED = 8;
21
+ export const EVENT_BOILED = 9;
22
+ export const EVENT_CONDENSED = 10;
23
+ export const EVENT_SUBLIMED = 11;
24
+ export const EVENT_CORRODED = 12;
25
+ export const EVENT_CALCINED = 13;
26
+ export const EVENT_DISSOLVED = 14;
27
+ export const EVENT_DENATURED = 15;
28
+ export const EVENT_BROWNED = 16;
29
+ export const EVENT_CARAMELISED = 17;
30
+ export const EVENT_FERMENTED = 18;
31
+ export const EVENT_DECAYED = 19;
32
+ /**
33
+ * A confined deflagration crossed a pressure threshold.
34
+ *
35
+ * `§26` refuses detonation in writing: a supersonic shock front is a physics problem in a package
36
+ * that owns no solver. This is the refusal **reporting** rather than simulating — the event says a
37
+ * pressure was exceeded and where, and the consumer decides what a wall does about it.
38
+ */
39
+ export const EVENT_EXPLODED = 20;
40
+ export const EVENT_KIND_COUNT = 21;
41
+ export class ChemistryEvents {
42
+ kind = new Int32Array(64);
43
+ parcel = new Int32Array(64);
44
+ species = new Int32Array(64);
45
+ value = new Float64Array(64);
46
+ used = 0;
47
+ get count() {
48
+ return this.used;
49
+ }
50
+ clear() {
51
+ this.used = 0;
52
+ }
53
+ /** @param species `-1` where the kind does not name one. @param value the kind's own quantity. */
54
+ push(kind, parcel, species, value) {
55
+ if (this.used === this.kind.length)
56
+ this.grow();
57
+ const at = this.used++;
58
+ this.kind[at] = kind;
59
+ this.parcel[at] = parcel;
60
+ this.species[at] = species;
61
+ this.value[at] = value;
62
+ }
63
+ kindOf(event) {
64
+ return this.kind[event];
65
+ }
66
+ parcelOf(event) {
67
+ return this.parcel[event];
68
+ }
69
+ speciesOf(event) {
70
+ return this.species[event];
71
+ }
72
+ valueOf(event) {
73
+ return this.value[event];
74
+ }
75
+ grow() {
76
+ const size = this.kind.length * 2;
77
+ const kind = new Int32Array(size);
78
+ kind.set(this.kind);
79
+ this.kind = kind;
80
+ const parcel = new Int32Array(size);
81
+ parcel.set(this.parcel);
82
+ this.parcel = parcel;
83
+ const species = new Int32Array(size);
84
+ species.set(this.species);
85
+ this.species = species;
86
+ const value = new Float64Array(size);
87
+ value.set(this.value);
88
+ this.value = value;
89
+ }
90
+ }
@@ -0,0 +1,84 @@
1
+ /*! DriftEngine | Copyright 2026 Drift Technologies | Apache-2.0 | https://github.com/drftrun/driftengine */
2
+ /**
3
+ * `@driftengine/chemistry` — thermochemistry of bulk matter.
4
+ *
5
+ * Game code imports from `@driftengine/chemistry` only; nothing reaches inside. This package
6
+ * **imports no other engine package**, which is the property `@driftengine/physics` and
7
+ * `@driftengine/entities` have and the reason all three were affordable: everything it needs from
8
+ * the rest of the engine arrives as plain data or through a structural target.
9
+ *
10
+ * **CH-0 through CH-7 of Track P.** CH-0 is the ledger: the closed element set, the species registry
11
+ * whose molar masses are derived from formulas, the species-by-element matrix, mass-fraction
12
+ * compositions, and the reduction that turns kilograms of species into moles of elements. CH-1 is
13
+ * the state: **enthalpy rather than temperature**, a curve per substance whose flat regions are the
14
+ * phase changes, boiling against pressure, and a parcel store. CH-2 is the depth: nested shells of
15
+ * equal mass and inward conduction, which is what makes a twig catch and a log not. CH-3 is the
16
+ * network: reactions balanced against the element matrix at registration, enthalpies derived by
17
+ * Hess's law, rates tabulated so `exp` never runs on a tick, and a substep-and-clamp solver that
18
+ * cannot produce a negative mass. CH-4 is the air: one sparse atmosphere carrying oxygen to a fire
19
+ * and smoke away from it, with conservative flux-form transport, buoyancy, the one wind, and a
20
+ * connectivity that makes a sealed room genuinely sealed. CH-5 joins them: radiation with the
21
+ * `1/r²` that makes "near the fire" mean something, convection, contact conduction, and the
22
+ * exchange that lets a burning parcel eat the oxygen around it and push its smoke into the air.
23
+ *
24
+ * CH-6 retired the enthalpy curve: the latent heat was already in the formation enthalpies, so a
25
+ * phase change is a gated reaction and its plateau emerges from the solver. Ignition became five
26
+ * criteria rather than a flag. **CH-7 is the substances**, and they are not here: thirty-nine of
27
+ * them across seven entry points under `library/*`, because a substance is forty numbers and a
28
+ * composition, and a consumer who wants a campfire should not pay for polyurethane. What this
29
+ * barrel exports of CH-7 is `installLibrary` and nothing else.
30
+ *
31
+ * That shape is deliberate. `elementTotals` is the assertion every later phase is checked with, and
32
+ * it exists before there is anything to conserve through, so the day a reaction first moves matter
33
+ * there is already a test that says whether it moved the right amount.
34
+ *
35
+ * The design is recorded in this package's README.
36
+ */
37
+ export type { ElementSymbol } from './element/elements.ts';
38
+ export { ATOMIC_MASS, ELEMENTS, ELEMENT_COUNT, elementIndex } from './element/elements.ts';
39
+ export { SpeciesElementMatrix } from './element/matrix.ts';
40
+ export type { Phase, SpeciesDefinition } from './species/species.ts';
41
+ export { PHASE_GAS, PHASE_LIQUID, PHASE_SOLID, STANDARD_PRESSURE, STANDARD_TEMPERATURE, } from './species/species.ts';
42
+ export { SpeciesRegistry } from './species/registry.ts';
43
+ export { STANDARD_SPECIES, registerStandardSpecies } from './species/standard.ts';
44
+ export type { Composition } from './substance/composition.ts';
45
+ export { SUM_TOLERANCE, massFractions } from './substance/composition.ts';
46
+ export type { AppearanceModel, SubstanceThermal } from './substance/substance.ts';
47
+ export { compositionElements, wetComposition } from './substance/substance.ts';
48
+ export { MAX_MODEL_TEMPERATURE, MIN_MODEL_TEMPERATURE, sensibleEnthalpy, temperatureFromCapacity, } from './species/thermal.ts';
49
+ export { boilingPoint } from './substance/boiling.ts';
50
+ export { SubstanceRegistry } from './substance/registry.ts';
51
+ export type { Shape, ShellGeometry } from './parcel/shells.ts';
52
+ export { MAX_SHELLS, SHAPE_CYLINDER, SHAPE_SLAB, SHAPE_SPHERE, shellGeometry, } from './parcel/shells.ts';
53
+ export type { ConductionModel, Conductivity } from './parcel/conduction.ts';
54
+ export { conductShells } from './parcel/conduction.ts';
55
+ export type { ParcelSpec } from './parcel/store.ts';
56
+ export { PHASE_MIXED, ParcelStore } from './parcel/store.ts';
57
+ export type { Tier } from './parcel/tier.ts';
58
+ export { TIER_CADENCE, TIER_COUNT, TIER_DISTANT, TIER_FAR, TIER_HERO, TIER_NEAR, TIER_SHELLS, TIER_SUBSTEPS, } from './parcel/tier.ts';
59
+ export { fingerprintChemistry } from './world/fingerprint.ts';
60
+ export type { ArrheniusKinetics, CardinalKinetics, DiffusionKinetics, InstantKinetics, Kinetics, Reaction, ReactionKind, SpeciesAmount, } from './reaction/reaction.ts';
61
+ export type { ReactionGates } from './reaction/reaction.ts';
62
+ export { KIND_BIOLOGICAL, KIND_BROWNING, KIND_CALCINATION, KIND_CHAR_OXIDATION, KIND_COMBUSTION, KIND_CORROSION, KIND_DENATURATION, KIND_DISSOLUTION, KIND_DRYING, KIND_PHASE_CHANGE, KIND_PYROLYSIS, phaseChange, } from './reaction/reaction.ts';
63
+ export { ReactionRegistry } from './reaction/registry.ts';
64
+ export type { RateTable } from './reaction/rates.ts';
65
+ export { GAS_CONSTANT, cardinalRate, rateAt, tabulateArrhenius, tabulateCardinal, } from './reaction/rates.ts';
66
+ export type { SubstanceReactions } from './reaction/solver.ts';
67
+ export { MAX_SUBSTANCE_REACTIONS, MAX_SUBSTANCE_SPECIES, reactShell } from './reaction/solver.ts';
68
+ export type { AmbientState } from './field/ambient.ts';
69
+ export { STANDARD_AIR, STANDARD_FIELD_SPECIES, STANDARD_PRESSURE_PA, ambientCellMass, saturationVapourPressure, } from './field/ambient.ts';
70
+ export type { AtmosphereOptions } from './field/atmosphere.ts';
71
+ export { AtmosphereField, CHUNK_CELLS, CHUNK_SIZE } from './field/atmosphere.ts';
72
+ export { localiseReactions } from './reaction/localise.ts';
73
+ export type { OcclusionTest } from './transport/radiation.ts';
74
+ export { RadiativeSources, STEFAN_BOLTZMANN, convectiveCoefficient, radiantFlux, sourceRange, } from './transport/radiation.ts';
75
+ export { ContactSet } from './transport/contacts.ts';
76
+ export type { IgnitionModel } from './ignition/criteria.ts';
77
+ export { BLOW_OFF_SPEED, DEFAULT_LIMITING_OXYGEN, DEFAULT_LOWER_FLAMMABLE, DEFAULT_SMOULDERING_OXYGEN, DEFAULT_UPPER_FLAMMABLE, surfaceFuelFraction, EXTINCT_BLOWN_OFF, EXTINCT_COOLED, EXTINCT_FUEL, EXTINCT_NONE, EXTINCT_OXYGEN, extinguishes, ignitionProgress, ignites, smoulders, } from './ignition/criteria.ts';
78
+ export { ChemistryEvents, EVENT_BOILED, EVENT_BROWNED, EVENT_CALCINED, EVENT_CARAMELISED, EVENT_CHARRED, EVENT_CONDENSED, EVENT_CONSUMED, EVENT_CORRODED, EVENT_DECAYED, EVENT_DENATURED, EVENT_DISSOLVED, EVENT_EXPLODED, EVENT_EXTINGUISHED, EVENT_FERMENTED, EVENT_FROZEN, EVENT_IGNITED, EVENT_KIND_COUNT, EVENT_MELTED, EVENT_SMOULDER_END, EVENT_SMOULDER_START, EVENT_STRUCTURAL_FAIL, EVENT_SUBLIMED, } from './ignition/events.ts';
79
+ export type { ChemistryWorldOptions } from './transport/world.ts';
80
+ export { ChemistryWorld } from './transport/world.ts';
81
+ export { elementTotals } from './conservation.ts';
82
+ export type { SubstanceLibrary } from './library/library.ts';
83
+ export { installLibrary } from './library/library.ts';
84
+ export { maxElementDrift } from './testing/drift.ts';
package/dist/index.js ADDED
@@ -0,0 +1,66 @@
1
+ /*! DriftEngine | Copyright 2026 Drift Technologies | Apache-2.0 | https://github.com/drftrun/driftengine */
2
+ /**
3
+ * `@driftengine/chemistry` — thermochemistry of bulk matter.
4
+ *
5
+ * Game code imports from `@driftengine/chemistry` only; nothing reaches inside. This package
6
+ * **imports no other engine package**, which is the property `@driftengine/physics` and
7
+ * `@driftengine/entities` have and the reason all three were affordable: everything it needs from
8
+ * the rest of the engine arrives as plain data or through a structural target.
9
+ *
10
+ * **CH-0 through CH-7 of Track P.** CH-0 is the ledger: the closed element set, the species registry
11
+ * whose molar masses are derived from formulas, the species-by-element matrix, mass-fraction
12
+ * compositions, and the reduction that turns kilograms of species into moles of elements. CH-1 is
13
+ * the state: **enthalpy rather than temperature**, a curve per substance whose flat regions are the
14
+ * phase changes, boiling against pressure, and a parcel store. CH-2 is the depth: nested shells of
15
+ * equal mass and inward conduction, which is what makes a twig catch and a log not. CH-3 is the
16
+ * network: reactions balanced against the element matrix at registration, enthalpies derived by
17
+ * Hess's law, rates tabulated so `exp` never runs on a tick, and a substep-and-clamp solver that
18
+ * cannot produce a negative mass. CH-4 is the air: one sparse atmosphere carrying oxygen to a fire
19
+ * and smoke away from it, with conservative flux-form transport, buoyancy, the one wind, and a
20
+ * connectivity that makes a sealed room genuinely sealed. CH-5 joins them: radiation with the
21
+ * `1/r²` that makes "near the fire" mean something, convection, contact conduction, and the
22
+ * exchange that lets a burning parcel eat the oxygen around it and push its smoke into the air.
23
+ *
24
+ * CH-6 retired the enthalpy curve: the latent heat was already in the formation enthalpies, so a
25
+ * phase change is a gated reaction and its plateau emerges from the solver. Ignition became five
26
+ * criteria rather than a flag. **CH-7 is the substances**, and they are not here: thirty-nine of
27
+ * them across seven entry points under `library/*`, because a substance is forty numbers and a
28
+ * composition, and a consumer who wants a campfire should not pay for polyurethane. What this
29
+ * barrel exports of CH-7 is `installLibrary` and nothing else.
30
+ *
31
+ * That shape is deliberate. `elementTotals` is the assertion every later phase is checked with, and
32
+ * it exists before there is anything to conserve through, so the day a reaction first moves matter
33
+ * there is already a test that says whether it moved the right amount.
34
+ *
35
+ * The design is recorded in this package's README.
36
+ */
37
+ export { ATOMIC_MASS, ELEMENTS, ELEMENT_COUNT, elementIndex } from './element/elements.js';
38
+ export { SpeciesElementMatrix } from './element/matrix.js';
39
+ export { PHASE_GAS, PHASE_LIQUID, PHASE_SOLID, STANDARD_PRESSURE, STANDARD_TEMPERATURE, } from './species/species.js';
40
+ export { SpeciesRegistry } from './species/registry.js';
41
+ export { STANDARD_SPECIES, registerStandardSpecies } from './species/standard.js';
42
+ export { SUM_TOLERANCE, massFractions } from './substance/composition.js';
43
+ export { compositionElements, wetComposition } from './substance/substance.js';
44
+ export { MAX_MODEL_TEMPERATURE, MIN_MODEL_TEMPERATURE, sensibleEnthalpy, temperatureFromCapacity, } from './species/thermal.js';
45
+ export { boilingPoint } from './substance/boiling.js';
46
+ export { SubstanceRegistry } from './substance/registry.js';
47
+ export { MAX_SHELLS, SHAPE_CYLINDER, SHAPE_SLAB, SHAPE_SPHERE, shellGeometry, } from './parcel/shells.js';
48
+ export { conductShells } from './parcel/conduction.js';
49
+ export { PHASE_MIXED, ParcelStore } from './parcel/store.js';
50
+ export { TIER_CADENCE, TIER_COUNT, TIER_DISTANT, TIER_FAR, TIER_HERO, TIER_NEAR, TIER_SHELLS, TIER_SUBSTEPS, } from './parcel/tier.js';
51
+ export { fingerprintChemistry } from './world/fingerprint.js';
52
+ export { KIND_BIOLOGICAL, KIND_BROWNING, KIND_CALCINATION, KIND_CHAR_OXIDATION, KIND_COMBUSTION, KIND_CORROSION, KIND_DENATURATION, KIND_DISSOLUTION, KIND_DRYING, KIND_PHASE_CHANGE, KIND_PYROLYSIS, phaseChange, } from './reaction/reaction.js';
53
+ export { ReactionRegistry } from './reaction/registry.js';
54
+ export { GAS_CONSTANT, cardinalRate, rateAt, tabulateArrhenius, tabulateCardinal, } from './reaction/rates.js';
55
+ export { MAX_SUBSTANCE_REACTIONS, MAX_SUBSTANCE_SPECIES, reactShell } from './reaction/solver.js';
56
+ export { STANDARD_AIR, STANDARD_FIELD_SPECIES, STANDARD_PRESSURE_PA, ambientCellMass, saturationVapourPressure, } from './field/ambient.js';
57
+ export { AtmosphereField, CHUNK_CELLS, CHUNK_SIZE } from './field/atmosphere.js';
58
+ export { localiseReactions } from './reaction/localise.js';
59
+ export { RadiativeSources, STEFAN_BOLTZMANN, convectiveCoefficient, radiantFlux, sourceRange, } from './transport/radiation.js';
60
+ export { ContactSet } from './transport/contacts.js';
61
+ export { BLOW_OFF_SPEED, DEFAULT_LIMITING_OXYGEN, DEFAULT_LOWER_FLAMMABLE, DEFAULT_SMOULDERING_OXYGEN, DEFAULT_UPPER_FLAMMABLE, surfaceFuelFraction, EXTINCT_BLOWN_OFF, EXTINCT_COOLED, EXTINCT_FUEL, EXTINCT_NONE, EXTINCT_OXYGEN, extinguishes, ignitionProgress, ignites, smoulders, } from './ignition/criteria.js';
62
+ export { ChemistryEvents, EVENT_BOILED, EVENT_BROWNED, EVENT_CALCINED, EVENT_CARAMELISED, EVENT_CHARRED, EVENT_CONDENSED, EVENT_CONSUMED, EVENT_CORRODED, EVENT_DECAYED, EVENT_DENATURED, EVENT_DISSOLVED, EVENT_EXPLODED, EVENT_EXTINGUISHED, EVENT_FERMENTED, EVENT_FROZEN, EVENT_IGNITED, EVENT_KIND_COUNT, EVENT_MELTED, EVENT_SMOULDER_END, EVENT_SMOULDER_START, EVENT_STRUCTURAL_FAIL, EVENT_SUBLIMED, } from './ignition/events.js';
63
+ export { ChemistryWorld } from './transport/world.js';
64
+ export { elementTotals } from './conservation.js';
65
+ export { installLibrary } from './library/library.js';
66
+ export { maxElementDrift } from './testing/drift.js';
@@ -0,0 +1,6 @@
1
+ import type { SubstanceLibrary } from './library.ts';
2
+ export declare const DECAY_MESOPHILIC = "decay-mesophilic";
3
+ export declare const DECAY_THERMOPHILIC = "decay-thermophilic";
4
+ export declare const DECAY_ANAEROBIC = "decay-anaerobic";
5
+ export declare const FERMENT = "ferment";
6
+ export declare const BIOLOGICAL: SubstanceLibrary;