@driftengine/chemistry 3.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +194 -0
- package/dist/conservation.d.ts +9 -0
- package/dist/conservation.js +38 -0
- package/dist/element/elements.d.ts +31 -0
- package/dist/element/elements.js +73 -0
- package/dist/element/matrix.d.ts +27 -0
- package/dist/element/matrix.js +80 -0
- package/dist/field/ambient.d.ts +49 -0
- package/dist/field/ambient.js +82 -0
- package/dist/field/atmosphere.d.ts +308 -0
- package/dist/field/atmosphere.js +1033 -0
- package/dist/ignition/criteria.d.ts +97 -0
- package/dist/ignition/criteria.js +135 -0
- package/dist/ignition/events.d.ts +56 -0
- package/dist/ignition/events.js +90 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +66 -0
- package/dist/library/biological.d.ts +6 -0
- package/dist/library/biological.js +229 -0
- package/dist/library/common.d.ts +26 -0
- package/dist/library/common.js +208 -0
- package/dist/library/food.d.ts +9 -0
- package/dist/library/food.js +272 -0
- package/dist/library/fuel.d.ts +19 -0
- package/dist/library/fuel.js +290 -0
- package/dist/library/library.d.ts +45 -0
- package/dist/library/library.js +29 -0
- package/dist/library/metal.d.ts +6 -0
- package/dist/library/metal.js +181 -0
- package/dist/library/mineral.d.ts +4 -0
- package/dist/library/mineral.js +177 -0
- package/dist/library/organic.d.ts +12 -0
- package/dist/library/organic.js +489 -0
- package/dist/library/polymer.d.ts +4 -0
- package/dist/library/polymer.js +237 -0
- package/dist/parcel/conduction.d.ts +20 -0
- package/dist/parcel/conduction.js +160 -0
- package/dist/parcel/shells.d.ts +49 -0
- package/dist/parcel/shells.js +73 -0
- package/dist/parcel/store.d.ts +395 -0
- package/dist/parcel/store.js +1152 -0
- package/dist/parcel/tier.d.ts +48 -0
- package/dist/parcel/tier.js +51 -0
- package/dist/present/audio.d.ts +41 -0
- package/dist/present/audio.js +50 -0
- package/dist/present/blackbody.d.ts +55 -0
- package/dist/present/blackbody.js +112 -0
- package/dist/present/flame.d.ts +34 -0
- package/dist/present/flame.js +103 -0
- package/dist/present/index.d.ts +25 -0
- package/dist/present/index.js +22 -0
- package/dist/present/install.d.ts +87 -0
- package/dist/present/install.js +157 -0
- package/dist/present/smoke.d.ts +63 -0
- package/dist/present/smoke.js +85 -0
- package/dist/present/surface.d.ts +40 -0
- package/dist/present/surface.js +76 -0
- package/dist/reaction/localise.d.ts +16 -0
- package/dist/reaction/localise.js +95 -0
- package/dist/reaction/rates.d.ts +59 -0
- package/dist/reaction/rates.js +101 -0
- package/dist/reaction/reaction.d.ts +178 -0
- package/dist/reaction/reaction.js +104 -0
- package/dist/reaction/registry.d.ts +47 -0
- package/dist/reaction/registry.js +215 -0
- package/dist/reaction/solver.d.ts +51 -0
- package/dist/reaction/solver.js +277 -0
- package/dist/species/registry.d.ts +26 -0
- package/dist/species/registry.js +97 -0
- package/dist/species/species.d.ts +48 -0
- package/dist/species/species.js +20 -0
- package/dist/species/standard.d.ts +30 -0
- package/dist/species/standard.js +436 -0
- package/dist/species/thermal.d.ts +18 -0
- package/dist/species/thermal.js +72 -0
- package/dist/substance/boiling.d.ts +29 -0
- package/dist/substance/boiling.js +54 -0
- package/dist/substance/composition.d.ts +34 -0
- package/dist/substance/composition.js +53 -0
- package/dist/substance/registry.d.ts +86 -0
- package/dist/substance/registry.js +187 -0
- package/dist/substance/substance.d.ts +94 -0
- package/dist/substance/substance.js +48 -0
- package/dist/testing/drift.d.ts +15 -0
- package/dist/testing/drift.js +29 -0
- package/dist/transport/contacts.d.ts +22 -0
- package/dist/transport/contacts.js +50 -0
- package/dist/transport/radiation.d.ts +101 -0
- package/dist/transport/radiation.js +199 -0
- package/dist/transport/world.d.ts +92 -0
- package/dist/transport/world.js +381 -0
- package/dist/world/fingerprint.d.ts +31 -0
- package/dist/world/fingerprint.js +70 -0
- package/package.json +100 -0
- package/src/conservation.ts +44 -0
- package/src/element/elements.ts +80 -0
- package/src/element/matrix.ts +89 -0
- package/src/field/ambient.ts +111 -0
- package/src/field/atmosphere.ts +1159 -0
- package/src/ignition/criteria.ts +177 -0
- package/src/ignition/events.ts +100 -0
- package/src/index.ts +213 -0
- package/src/library/biological.ts +252 -0
- package/src/library/common.ts +224 -0
- package/src/library/food.ts +302 -0
- package/src/library/fuel.ts +299 -0
- package/src/library/library.ts +62 -0
- package/src/library/metal.ts +186 -0
- package/src/library/mineral.ts +194 -0
- package/src/library/organic.ts +545 -0
- package/src/library/polymer.ts +243 -0
- package/src/parcel/conduction.ts +188 -0
- package/src/parcel/shells.ts +100 -0
- package/src/parcel/store.ts +1270 -0
- package/src/parcel/tier.ts +60 -0
- package/src/present/audio.ts +54 -0
- package/src/present/blackbody.ts +118 -0
- package/src/present/flame.ts +111 -0
- package/src/present/index.ts +25 -0
- package/src/present/install.ts +209 -0
- package/src/present/smoke.ts +139 -0
- package/src/present/surface.ts +107 -0
- package/src/reaction/localise.ts +126 -0
- package/src/reaction/rates.ts +153 -0
- package/src/reaction/reaction.ts +235 -0
- package/src/reaction/registry.ts +257 -0
- package/src/reaction/solver.ts +326 -0
- package/src/species/registry.ts +117 -0
- package/src/species/species.ts +53 -0
- package/src/species/standard.ts +449 -0
- package/src/species/thermal.ts +74 -0
- package/src/substance/boiling.ts +64 -0
- package/src/substance/composition.ts +81 -0
- package/src/substance/registry.ts +223 -0
- package/src/substance/substance.ts +148 -0
- package/src/testing/drift.ts +28 -0
- package/src/transport/contacts.ts +56 -0
- package/src/transport/radiation.ts +238 -0
- package/src/transport/world.ts +437 -0
- package/src/world/fingerprint.ts +106 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Four levels of detail, and every one of them conserves.
|
|
3
|
+
*
|
|
4
|
+
* **`§14`'s table.** A parcel far away or barely doing anything does not need sixteen shells and a
|
|
5
|
+
* tick of its own; a burning log in the player's hand does. The tiers are the two axes that matter:
|
|
6
|
+
* how finely the depth is resolved, and how often the chemistry runs.
|
|
7
|
+
*
|
|
8
|
+
* | Tier | Shells | Chemistry | Substeps |
|
|
9
|
+
* |---|---|---|---|
|
|
10
|
+
* | `Hero` | as declared | every tick | 32 |
|
|
11
|
+
* | `Near` | 4 | every tick | 8 |
|
|
12
|
+
* | `Far` | 2 | every 4th tick, 4× step | 4 |
|
|
13
|
+
* | `Distant` | 1 | every 16th tick, 16× step | 2 |
|
|
14
|
+
*
|
|
15
|
+
* **A tier only ever reduces resolution.** `Hero` is what the substance's author asked for, and a
|
|
16
|
+
* tier that invented shells would be inventing a depth profile nobody measured.
|
|
17
|
+
*
|
|
18
|
+
* **`Far` and `Distant` integrate the same `dt`, just less often**, with the accumulated step handed
|
|
19
|
+
* over when their turn comes. That is what makes a cadence conserving for free: the same seconds are
|
|
20
|
+
* integrated either way, at a coarser resolution in time.
|
|
21
|
+
*/
|
|
22
|
+
export declare const TIER_HERO = 0;
|
|
23
|
+
export declare const TIER_NEAR = 1;
|
|
24
|
+
export declare const TIER_FAR = 2;
|
|
25
|
+
export declare const TIER_DISTANT = 3;
|
|
26
|
+
export type Tier = typeof TIER_HERO | typeof TIER_NEAR | typeof TIER_FAR | typeof TIER_DISTANT;
|
|
27
|
+
export declare const TIER_COUNT = 4;
|
|
28
|
+
/** Shells each tier resolves to, capped by what the substance declared. */
|
|
29
|
+
export declare const TIER_SHELLS: readonly number[];
|
|
30
|
+
/** Ticks between updates. A parcel skipped for three ticks is stepped with four ticks of `dt`. */
|
|
31
|
+
export declare const TIER_CADENCE: readonly number[];
|
|
32
|
+
/** The most sub-steps a reaction solve will take at this tier. */
|
|
33
|
+
export declare const TIER_SUBSTEPS: readonly number[];
|
|
34
|
+
/**
|
|
35
|
+
* Which shells of an `n`-shell parcel make up target shell `j` of `m`, as a half-open range in
|
|
36
|
+
* **fractional** shell coordinates.
|
|
37
|
+
*
|
|
38
|
+
* The fold is a proportional redistribution: target `j` takes `[j·n/m, (j+1)·n/m)`, splitting a
|
|
39
|
+
* source shell where the ratio is not an integer. Every target ends with exactly `n/m` shells' worth
|
|
40
|
+
* of mass, which is what keeps the result equal-mass — and equal mass is the invariant conduction
|
|
41
|
+
* rests on, so this is not a detail.
|
|
42
|
+
*/
|
|
43
|
+
export declare function foldSpan(from: number, to: number, target: number): {
|
|
44
|
+
start: number;
|
|
45
|
+
end: number;
|
|
46
|
+
};
|
|
47
|
+
/** How much of source shell `i` belongs to a target spanning `[start, end)`. 0 to 1. */
|
|
48
|
+
export declare function foldShare(i: number, start: number, end: number): number;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Four levels of detail, and every one of them conserves.
|
|
3
|
+
*
|
|
4
|
+
* **`§14`'s table.** A parcel far away or barely doing anything does not need sixteen shells and a
|
|
5
|
+
* tick of its own; a burning log in the player's hand does. The tiers are the two axes that matter:
|
|
6
|
+
* how finely the depth is resolved, and how often the chemistry runs.
|
|
7
|
+
*
|
|
8
|
+
* | Tier | Shells | Chemistry | Substeps |
|
|
9
|
+
* |---|---|---|---|
|
|
10
|
+
* | `Hero` | as declared | every tick | 32 |
|
|
11
|
+
* | `Near` | 4 | every tick | 8 |
|
|
12
|
+
* | `Far` | 2 | every 4th tick, 4× step | 4 |
|
|
13
|
+
* | `Distant` | 1 | every 16th tick, 16× step | 2 |
|
|
14
|
+
*
|
|
15
|
+
* **A tier only ever reduces resolution.** `Hero` is what the substance's author asked for, and a
|
|
16
|
+
* tier that invented shells would be inventing a depth profile nobody measured.
|
|
17
|
+
*
|
|
18
|
+
* **`Far` and `Distant` integrate the same `dt`, just less often**, with the accumulated step handed
|
|
19
|
+
* over when their turn comes. That is what makes a cadence conserving for free: the same seconds are
|
|
20
|
+
* integrated either way, at a coarser resolution in time.
|
|
21
|
+
*/
|
|
22
|
+
export const TIER_HERO = 0;
|
|
23
|
+
export const TIER_NEAR = 1;
|
|
24
|
+
export const TIER_FAR = 2;
|
|
25
|
+
export const TIER_DISTANT = 3;
|
|
26
|
+
export const TIER_COUNT = 4;
|
|
27
|
+
/** Shells each tier resolves to, capped by what the substance declared. */
|
|
28
|
+
export const TIER_SHELLS = [16, 4, 2, 1];
|
|
29
|
+
/** Ticks between updates. A parcel skipped for three ticks is stepped with four ticks of `dt`. */
|
|
30
|
+
export const TIER_CADENCE = [1, 1, 4, 16];
|
|
31
|
+
/** The most sub-steps a reaction solve will take at this tier. */
|
|
32
|
+
export const TIER_SUBSTEPS = [32, 8, 4, 2];
|
|
33
|
+
/**
|
|
34
|
+
* Which shells of an `n`-shell parcel make up target shell `j` of `m`, as a half-open range in
|
|
35
|
+
* **fractional** shell coordinates.
|
|
36
|
+
*
|
|
37
|
+
* The fold is a proportional redistribution: target `j` takes `[j·n/m, (j+1)·n/m)`, splitting a
|
|
38
|
+
* source shell where the ratio is not an integer. Every target ends with exactly `n/m` shells' worth
|
|
39
|
+
* of mass, which is what keeps the result equal-mass — and equal mass is the invariant conduction
|
|
40
|
+
* rests on, so this is not a detail.
|
|
41
|
+
*/
|
|
42
|
+
export function foldSpan(from, to, target) {
|
|
43
|
+
const width = from / to;
|
|
44
|
+
return { start: target * width, end: (target + 1) * width };
|
|
45
|
+
}
|
|
46
|
+
/** How much of source shell `i` belongs to a target spanning `[start, end)`. 0 to 1. */
|
|
47
|
+
export function foldShare(i, start, end) {
|
|
48
|
+
const low = i > start ? i : start;
|
|
49
|
+
const high = i + 1 < end ? i + 1 : end;
|
|
50
|
+
return high > low ? high - low : 0;
|
|
51
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fire as three numbers, and **this package never touches `@driftengine/audio`**.
|
|
3
|
+
*
|
|
4
|
+
* `§17` is deliberate about that: what a fire sounds like is a consumer's decision made with
|
|
5
|
+
* quantities the model already has, and a chemistry package that reached for a mix bus would have
|
|
6
|
+
* imported an engine package for the first time to play a sound.
|
|
7
|
+
*
|
|
8
|
+
* All three are *rates and levels* rather than volumes. A consumer maps them to whatever their mix
|
|
9
|
+
* wants; nothing here knows what a decibel is worth in their game.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* How fast it crackles, from the volatile release rate and the area releasing them.
|
|
13
|
+
*
|
|
14
|
+
* **Crackling is pockets of gas bursting out of the wood**, so it tracks pyrolysis rather than a
|
|
15
|
+
* flag — which means it starts before the flame does, peaks while the log is gasifying hardest, and
|
|
16
|
+
* fades on its own as the volatiles run out. A loop played while `burning` is true does none of that.
|
|
17
|
+
*
|
|
18
|
+
* @param fuelFlux kg/(m²·s) of combustible gas leaving the surface.
|
|
19
|
+
* @param area m² of that surface.
|
|
20
|
+
*/
|
|
21
|
+
export declare function crackleRate(fuelFlux: number, area: number): number;
|
|
22
|
+
/**
|
|
23
|
+
* How hard it hisses, from the steam leaving rather than the fuel.
|
|
24
|
+
*
|
|
25
|
+
* The difference between the two fluxes is the whole reason both are stored: a green log hisses and
|
|
26
|
+
* does not crackle, a seasoned one crackles and does not hiss, and a log part-way through does both
|
|
27
|
+
* in the proportion its own drying front is at.
|
|
28
|
+
*
|
|
29
|
+
* @param steamFlux kg/(m²·s) of water vapour leaving the surface.
|
|
30
|
+
*/
|
|
31
|
+
export declare function hissRate(steamFlux: number, area: number): number;
|
|
32
|
+
/**
|
|
33
|
+
* How loud it roars, from the air it entrains.
|
|
34
|
+
*
|
|
35
|
+
* A fire's roar is the sound of air being pulled into the plume, so it goes with the plume's own
|
|
36
|
+
* velocity — and as the **square** of it, because acoustic power does. That is why a large fire is
|
|
37
|
+
* disproportionately louder rather than proportionally louder.
|
|
38
|
+
*
|
|
39
|
+
* @param entrainedSpeed m/s of gas rising in the plume, which the field reports as `riseAt`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function roarLevel(entrainedSpeed: number): number;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fire as three numbers, and **this package never touches `@driftengine/audio`**.
|
|
3
|
+
*
|
|
4
|
+
* `§17` is deliberate about that: what a fire sounds like is a consumer's decision made with
|
|
5
|
+
* quantities the model already has, and a chemistry package that reached for a mix bus would have
|
|
6
|
+
* imported an engine package for the first time to play a sound.
|
|
7
|
+
*
|
|
8
|
+
* All three are *rates and levels* rather than volumes. A consumer maps them to whatever their mix
|
|
9
|
+
* wants; nothing here knows what a decibel is worth in their game.
|
|
10
|
+
*/
|
|
11
|
+
/** Bursts per second per unit rate, at one square metre. A gain, and the only one in this file. */
|
|
12
|
+
const CRACKLE_GAIN = 900;
|
|
13
|
+
const HISS_GAIN = 40;
|
|
14
|
+
/**
|
|
15
|
+
* How fast it crackles, from the volatile release rate and the area releasing them.
|
|
16
|
+
*
|
|
17
|
+
* **Crackling is pockets of gas bursting out of the wood**, so it tracks pyrolysis rather than a
|
|
18
|
+
* flag — which means it starts before the flame does, peaks while the log is gasifying hardest, and
|
|
19
|
+
* fades on its own as the volatiles run out. A loop played while `burning` is true does none of that.
|
|
20
|
+
*
|
|
21
|
+
* @param fuelFlux kg/(m²·s) of combustible gas leaving the surface.
|
|
22
|
+
* @param area m² of that surface.
|
|
23
|
+
*/
|
|
24
|
+
export function crackleRate(fuelFlux, area) {
|
|
25
|
+
return fuelFlux > 0 ? fuelFlux * area * CRACKLE_GAIN : 0;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* How hard it hisses, from the steam leaving rather than the fuel.
|
|
29
|
+
*
|
|
30
|
+
* The difference between the two fluxes is the whole reason both are stored: a green log hisses and
|
|
31
|
+
* does not crackle, a seasoned one crackles and does not hiss, and a log part-way through does both
|
|
32
|
+
* in the proportion its own drying front is at.
|
|
33
|
+
*
|
|
34
|
+
* @param steamFlux kg/(m²·s) of water vapour leaving the surface.
|
|
35
|
+
*/
|
|
36
|
+
export function hissRate(steamFlux, area) {
|
|
37
|
+
return steamFlux > 0 ? steamFlux * area * HISS_GAIN : 0;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* How loud it roars, from the air it entrains.
|
|
41
|
+
*
|
|
42
|
+
* A fire's roar is the sound of air being pulled into the plume, so it goes with the plume's own
|
|
43
|
+
* velocity — and as the **square** of it, because acoustic power does. That is why a large fire is
|
|
44
|
+
* disproportionately louder rather than proportionally louder.
|
|
45
|
+
*
|
|
46
|
+
* @param entrainedSpeed m/s of gas rising in the plume, which the field reports as `riseAt`.
|
|
47
|
+
*/
|
|
48
|
+
export function roarLevel(entrainedSpeed) {
|
|
49
|
+
return entrainedSpeed > 0 ? entrainedSpeed * entrainedSpeed : 0;
|
|
50
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A hot surface's colour, from Planck's law rather than from a gradient somebody liked.
|
|
3
|
+
*
|
|
4
|
+
* **`§8.5` and `§17` both lean on this.** A char surface at 1,000 K is a dull red and at 1,300 K is
|
|
5
|
+
* orange, and the reason is not a decision: at 1,000 K a blackbody puts nineteen times more power
|
|
6
|
+
* at 600 nm than at 450, and by 3,000 K the two are within a factor of three. Sampling the same law
|
|
7
|
+
* at three wavelengths reproduces that progression exactly, and an ember therefore has the right
|
|
8
|
+
* colour without anybody having chosen one.
|
|
9
|
+
*
|
|
10
|
+
* **Tabulated, because `exp` may not run on a frame any more than on a tick.** 256 knots over
|
|
11
|
+
* 600-4,000 K, which is every temperature a fire reaches; the ratios between channels vary smoothly
|
|
12
|
+
* enough over that range that linear interpolation is invisible.
|
|
13
|
+
*
|
|
14
|
+
* ---
|
|
15
|
+
*
|
|
16
|
+
* ## What this is not, stated rather than left to be discovered
|
|
17
|
+
*
|
|
18
|
+
* **It samples Planck at three wavelengths; it does not integrate against the CIE colour-matching
|
|
19
|
+
* functions.** A proper conversion integrates the spectrum against three overlapping curves and
|
|
20
|
+
* lands in XYZ, and the chromaticity that comes out is not quite the chromaticity of three point
|
|
21
|
+
* samples. What is right here is the **hue progression** — red, orange, yellow, white, in the order
|
|
22
|
+
* and roughly at the temperatures a real one does it. What is approximate is the exact chromaticity.
|
|
23
|
+
*
|
|
24
|
+
* **Reversal:** integrate against the CIE curves, tabulated the same way, if a consumer ever needs a
|
|
25
|
+
* blackbody to match a reference photograph. It is a bigger table and the same interface.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* K below which nothing is emitted at all.
|
|
29
|
+
*
|
|
30
|
+
* **Not a fade from room temperature**, which is the tempting shape and the wrong one: a surface at
|
|
31
|
+
* 400 K radiates plenty and none of it is visible, so an emissive term that faded smoothly would
|
|
32
|
+
* make every warm object in a scene glow faintly in the dark. 600 K is roughly where a real surface
|
|
33
|
+
* becomes visible as a dull red in an unlit room, which is the threshold this models.
|
|
34
|
+
*/
|
|
35
|
+
export declare const GLOW_MIN_TEMPERATURE = 600;
|
|
36
|
+
/**
|
|
37
|
+
* Linear RGB for a surface at `temperature`, normalised so the brightest channel is one.
|
|
38
|
+
*
|
|
39
|
+
* **A hue, not a brightness.** Separating them is what lets a consumer tone-map: at Stefan-Boltzmann
|
|
40
|
+
* scaling a 3,000 K flame is over eighty times a 1,000 K ember, and a colour channel carrying that
|
|
41
|
+
* range has nothing left for the colour. `glowIntensity` is the other half.
|
|
42
|
+
*
|
|
43
|
+
* Writes into `out` and allocates nothing, because this runs per parcel per frame.
|
|
44
|
+
*/
|
|
45
|
+
export declare function blackbodyRGB(temperature: number, out: Float32Array): void;
|
|
46
|
+
/**
|
|
47
|
+
* How brightly it glows, relative to 1,000 K.
|
|
48
|
+
*
|
|
49
|
+
* `(T/1000)⁴` — Stefan-Boltzmann, and one multiply chain rather than a `pow`. Doubling the
|
|
50
|
+
* temperature is sixteen times the power, which is why an ember beside a flame is not nearly as much
|
|
51
|
+
* cooler as it looks.
|
|
52
|
+
*
|
|
53
|
+
* Zero below `GLOW_MIN_TEMPERATURE`, for the reason that constant gives.
|
|
54
|
+
*/
|
|
55
|
+
export declare function glowIntensity(temperature: number): number;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A hot surface's colour, from Planck's law rather than from a gradient somebody liked.
|
|
3
|
+
*
|
|
4
|
+
* **`§8.5` and `§17` both lean on this.** A char surface at 1,000 K is a dull red and at 1,300 K is
|
|
5
|
+
* orange, and the reason is not a decision: at 1,000 K a blackbody puts nineteen times more power
|
|
6
|
+
* at 600 nm than at 450, and by 3,000 K the two are within a factor of three. Sampling the same law
|
|
7
|
+
* at three wavelengths reproduces that progression exactly, and an ember therefore has the right
|
|
8
|
+
* colour without anybody having chosen one.
|
|
9
|
+
*
|
|
10
|
+
* **Tabulated, because `exp` may not run on a frame any more than on a tick.** 256 knots over
|
|
11
|
+
* 600-4,000 K, which is every temperature a fire reaches; the ratios between channels vary smoothly
|
|
12
|
+
* enough over that range that linear interpolation is invisible.
|
|
13
|
+
*
|
|
14
|
+
* ---
|
|
15
|
+
*
|
|
16
|
+
* ## What this is not, stated rather than left to be discovered
|
|
17
|
+
*
|
|
18
|
+
* **It samples Planck at three wavelengths; it does not integrate against the CIE colour-matching
|
|
19
|
+
* functions.** A proper conversion integrates the spectrum against three overlapping curves and
|
|
20
|
+
* lands in XYZ, and the chromaticity that comes out is not quite the chromaticity of three point
|
|
21
|
+
* samples. What is right here is the **hue progression** — red, orange, yellow, white, in the order
|
|
22
|
+
* and roughly at the temperatures a real one does it. What is approximate is the exact chromaticity.
|
|
23
|
+
*
|
|
24
|
+
* **Reversal:** integrate against the CIE curves, tabulated the same way, if a consumer ever needs a
|
|
25
|
+
* blackbody to match a reference photograph. It is a bigger table and the same interface.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* K below which nothing is emitted at all.
|
|
29
|
+
*
|
|
30
|
+
* **Not a fade from room temperature**, which is the tempting shape and the wrong one: a surface at
|
|
31
|
+
* 400 K radiates plenty and none of it is visible, so an emissive term that faded smoothly would
|
|
32
|
+
* make every warm object in a scene glow faintly in the dark. 600 K is roughly where a real surface
|
|
33
|
+
* becomes visible as a dull red in an unlit room, which is the threshold this models.
|
|
34
|
+
*/
|
|
35
|
+
export const GLOW_MIN_TEMPERATURE = 600;
|
|
36
|
+
const TABLE_MIN = GLOW_MIN_TEMPERATURE;
|
|
37
|
+
const TABLE_MAX = 4000;
|
|
38
|
+
const KNOTS = 256;
|
|
39
|
+
/**
|
|
40
|
+
* The three wavelengths sampled, in metres: 600, 550 and 450 nm.
|
|
41
|
+
*
|
|
42
|
+
* Roughly where the human red, green and blue responses peak. Point samples rather than curves —
|
|
43
|
+
* see the header on what that costs.
|
|
44
|
+
*/
|
|
45
|
+
const WAVELENGTHS = [600e-9, 550e-9, 450e-9];
|
|
46
|
+
/** Planck's second radiation constant, `h·c/k`, in metre-kelvin. */
|
|
47
|
+
const C2 = 1.438776877e-2;
|
|
48
|
+
/**
|
|
49
|
+
* Spectral radiance at a wavelength and temperature, up to constants that cancel on normalisation.
|
|
50
|
+
*
|
|
51
|
+
* `λ⁻⁵ / (exp(hc/λkT) − 1)`, and the leading `2hc²` is dropped because every channel carries it and
|
|
52
|
+
* the result is divided by its own maximum.
|
|
53
|
+
*/
|
|
54
|
+
function planck(wavelength, temperature) {
|
|
55
|
+
const x = C2 / (wavelength * temperature);
|
|
56
|
+
/* `expm1` rather than `exp(x) - 1`: at low temperature `x` is large and the subtraction is fine,
|
|
57
|
+
but at high temperature it approaches zero from above and the naive form loses every digit. */
|
|
58
|
+
// determinism: build-time — presentation: Planck into a glow colour, never simulation state
|
|
59
|
+
return 1 / (wavelength ** 5 * Math.expm1(x));
|
|
60
|
+
}
|
|
61
|
+
/** Three channels per knot, normalised so the brightest is one. */
|
|
62
|
+
const TABLE = (() => {
|
|
63
|
+
const table = new Float32Array(KNOTS * 3);
|
|
64
|
+
for (let i = 0; i < KNOTS; i++) {
|
|
65
|
+
const temperature = TABLE_MIN + ((TABLE_MAX - TABLE_MIN) * i) / (KNOTS - 1);
|
|
66
|
+
let peak = 0;
|
|
67
|
+
for (let c = 0; c < 3; c++) {
|
|
68
|
+
const value = planck(WAVELENGTHS[c], temperature);
|
|
69
|
+
table[i * 3 + c] = value;
|
|
70
|
+
if (value > peak)
|
|
71
|
+
peak = value;
|
|
72
|
+
}
|
|
73
|
+
for (let c = 0; c < 3; c++)
|
|
74
|
+
table[i * 3 + c] = table[i * 3 + c] / peak;
|
|
75
|
+
}
|
|
76
|
+
return table;
|
|
77
|
+
})();
|
|
78
|
+
/**
|
|
79
|
+
* Linear RGB for a surface at `temperature`, normalised so the brightest channel is one.
|
|
80
|
+
*
|
|
81
|
+
* **A hue, not a brightness.** Separating them is what lets a consumer tone-map: at Stefan-Boltzmann
|
|
82
|
+
* scaling a 3,000 K flame is over eighty times a 1,000 K ember, and a colour channel carrying that
|
|
83
|
+
* range has nothing left for the colour. `glowIntensity` is the other half.
|
|
84
|
+
*
|
|
85
|
+
* Writes into `out` and allocates nothing, because this runs per parcel per frame.
|
|
86
|
+
*/
|
|
87
|
+
export function blackbodyRGB(temperature, out) {
|
|
88
|
+
const clamped = temperature <= TABLE_MIN ? TABLE_MIN : temperature >= TABLE_MAX ? TABLE_MAX : temperature;
|
|
89
|
+
const position = ((clamped - TABLE_MIN) / (TABLE_MAX - TABLE_MIN)) * (KNOTS - 1);
|
|
90
|
+
const low = Math.floor(position);
|
|
91
|
+
const high = low + 1 >= KNOTS ? low : low + 1;
|
|
92
|
+
const at = position - low;
|
|
93
|
+
for (let c = 0; c < 3; c++) {
|
|
94
|
+
out[c] = TABLE[low * 3 + c] * (1 - at) + TABLE[high * 3 + c] * at;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* How brightly it glows, relative to 1,000 K.
|
|
99
|
+
*
|
|
100
|
+
* `(T/1000)⁴` — Stefan-Boltzmann, and one multiply chain rather than a `pow`. Doubling the
|
|
101
|
+
* temperature is sixteen times the power, which is why an ember beside a flame is not nearly as much
|
|
102
|
+
* cooler as it looks.
|
|
103
|
+
*
|
|
104
|
+
* Zero below `GLOW_MIN_TEMPERATURE`, for the reason that constant gives.
|
|
105
|
+
*/
|
|
106
|
+
export function glowIntensity(temperature) {
|
|
107
|
+
if (temperature < GLOW_MIN_TEMPERATURE)
|
|
108
|
+
return 0;
|
|
109
|
+
const t = temperature / 1000;
|
|
110
|
+
const square = t * t;
|
|
111
|
+
return square * square;
|
|
112
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metres of visible flame over a fire of `kilowatts` on a base `diameter` metres across.
|
|
3
|
+
*
|
|
4
|
+
* **Heskestad's correlation, `L = 0.235·Q^(2/5) − 1.02·D`**, which is the standard flame-height
|
|
5
|
+
* relation in fire engineering and is here verbatim. A 100 kW campfire over a 0.3 m base stands
|
|
6
|
+
* 1.18 m; a megawatt over a metre stands 2.71.
|
|
7
|
+
*
|
|
8
|
+
* **The two-fifths power is the interesting part**: ten times the fire is two and a half times the
|
|
9
|
+
* flame, which is why a large fire looks less impressive than its output suggests and why the
|
|
10
|
+
* flames over a room fire are shorter than people draw them.
|
|
11
|
+
*
|
|
12
|
+
* Clamped at zero, and a negative result is physics rather than a domain error: a few kilowatts
|
|
13
|
+
* spread over a metre is a smouldering surface with no standing flame over it at all.
|
|
14
|
+
*/
|
|
15
|
+
export declare function flameHeight(kilowatts: number, diameter: number): number;
|
|
16
|
+
/**
|
|
17
|
+
* How luminous the soot makes a flame, 0 to 1, from the equivalence ratio.
|
|
18
|
+
*
|
|
19
|
+
* Zero for a lean flame — there is no soot in one, which is why a gas ring is nearly invisible in
|
|
20
|
+
* daylight — climbing through the band where soot forms. Above `SOOT_FULL` the flame is too rich to
|
|
21
|
+
* burn at all and what leaves is smoke, which `§8.4` says and the field's own chemistry produces;
|
|
22
|
+
* this stays at one rather than pretending to model that.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sootLuminosity(equivalenceRatio: number): number;
|
|
25
|
+
/**
|
|
26
|
+
* A flame's linear RGB: incandescent soot mixed with chemiluminescence by the equivalence ratio.
|
|
27
|
+
*
|
|
28
|
+
* `§8.4` again — the model computes φ and the soot mass, and this maps them. The result is that
|
|
29
|
+
* **nobody chose a flame colour**: a methanol flame is nearly invisible, a candle is orange, a
|
|
30
|
+
* bunsen burner with its collar open is blue, and all three come out of one ratio.
|
|
31
|
+
*
|
|
32
|
+
* Writes into `out`, allocates nothing.
|
|
33
|
+
*/
|
|
34
|
+
export declare function flameColour(equivalenceRatio: number, temperature: number, out: Float32Array): void;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a flame looks like, from what it is doing.
|
|
3
|
+
*
|
|
4
|
+
* **Nothing here is an art direction.** Its height is the published plume correlation over its own
|
|
5
|
+
* heat release; its colour is `§8.4`'s argument that a luminous flame is orange because it contains
|
|
6
|
+
* incandescent soot and a clean one is blue because it does not. A gas ring is short and blue and a
|
|
7
|
+
* bonfire is tall and orange for the reasons they actually are.
|
|
8
|
+
*/
|
|
9
|
+
import { blackbodyRGB } from './blackbody.js';
|
|
10
|
+
/**
|
|
11
|
+
* `Q^(2/5)`, tabulated, because `pow` is a transcendental and this runs per fire per frame.
|
|
12
|
+
*
|
|
13
|
+
* Knots uniform in `Q^(1/2)` rather than in `Q`, which puts them where the curve bends: the
|
|
14
|
+
* two-fifths power changes fastest near zero, and uniform-in-Q knots would spend three hundred of
|
|
15
|
+
* them on the flat end and none on the part where a fire grows from nothing.
|
|
16
|
+
*/
|
|
17
|
+
const POWER_MIN = 0;
|
|
18
|
+
const POWER_MAX = 20000;
|
|
19
|
+
const POWER_KNOTS = 512;
|
|
20
|
+
const POWER_TABLE = (() => {
|
|
21
|
+
const table = new Float64Array(POWER_KNOTS);
|
|
22
|
+
const rootMax = Math.sqrt(POWER_MAX);
|
|
23
|
+
for (let i = 0; i < POWER_KNOTS; i++) {
|
|
24
|
+
const root = (rootMax * i) / (POWER_KNOTS - 1);
|
|
25
|
+
// determinism: build-time — presentation: a flame-height lookup table, built once
|
|
26
|
+
table[i] = (root * root) ** 0.4;
|
|
27
|
+
}
|
|
28
|
+
return table;
|
|
29
|
+
})();
|
|
30
|
+
function twoFifths(kilowatts) {
|
|
31
|
+
if (!(kilowatts > POWER_MIN))
|
|
32
|
+
return 0;
|
|
33
|
+
if (kilowatts >= POWER_MAX)
|
|
34
|
+
return POWER_TABLE[POWER_KNOTS - 1];
|
|
35
|
+
const position = (Math.sqrt(kilowatts) / Math.sqrt(POWER_MAX)) * (POWER_KNOTS - 1);
|
|
36
|
+
const low = Math.floor(position);
|
|
37
|
+
const at = position - low;
|
|
38
|
+
return POWER_TABLE[low] * (1 - at) + POWER_TABLE[low + 1] * at;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Metres of visible flame over a fire of `kilowatts` on a base `diameter` metres across.
|
|
42
|
+
*
|
|
43
|
+
* **Heskestad's correlation, `L = 0.235·Q^(2/5) − 1.02·D`**, which is the standard flame-height
|
|
44
|
+
* relation in fire engineering and is here verbatim. A 100 kW campfire over a 0.3 m base stands
|
|
45
|
+
* 1.18 m; a megawatt over a metre stands 2.71.
|
|
46
|
+
*
|
|
47
|
+
* **The two-fifths power is the interesting part**: ten times the fire is two and a half times the
|
|
48
|
+
* flame, which is why a large fire looks less impressive than its output suggests and why the
|
|
49
|
+
* flames over a room fire are shorter than people draw them.
|
|
50
|
+
*
|
|
51
|
+
* Clamped at zero, and a negative result is physics rather than a domain error: a few kilowatts
|
|
52
|
+
* spread over a metre is a smouldering surface with no standing flame over it at all.
|
|
53
|
+
*/
|
|
54
|
+
export function flameHeight(kilowatts, diameter) {
|
|
55
|
+
const height = 0.235 * twoFifths(kilowatts) - 1.02 * diameter;
|
|
56
|
+
return height > 0 ? height : 0;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Where soot starts forming, as an equivalence ratio.
|
|
60
|
+
*
|
|
61
|
+
* `§8.4`'s table: below stoichiometric there is enough oxygen to burn everything and the flame is
|
|
62
|
+
* clean; between 1.5 and 3 it is the classic luminous orange. One below the other is the onset.
|
|
63
|
+
*/
|
|
64
|
+
const SOOT_ONSET = 1.0;
|
|
65
|
+
const SOOT_FULL = 3.0;
|
|
66
|
+
/**
|
|
67
|
+
* How luminous the soot makes a flame, 0 to 1, from the equivalence ratio.
|
|
68
|
+
*
|
|
69
|
+
* Zero for a lean flame — there is no soot in one, which is why a gas ring is nearly invisible in
|
|
70
|
+
* daylight — climbing through the band where soot forms. Above `SOOT_FULL` the flame is too rich to
|
|
71
|
+
* burn at all and what leaves is smoke, which `§8.4` says and the field's own chemistry produces;
|
|
72
|
+
* this stays at one rather than pretending to model that.
|
|
73
|
+
*/
|
|
74
|
+
export function sootLuminosity(equivalenceRatio) {
|
|
75
|
+
if (!(equivalenceRatio > SOOT_ONSET))
|
|
76
|
+
return 0;
|
|
77
|
+
const share = (equivalenceRatio - SOOT_ONSET) / (SOOT_FULL - SOOT_ONSET);
|
|
78
|
+
return share > 1 ? 1 : share;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Linear RGB of the CH* and C₂* chemiluminescence a clean flame is seen by.
|
|
82
|
+
*
|
|
83
|
+
* 431 nm and 516 nm, which is genuinely blue-green, and it is what you are looking at when you look
|
|
84
|
+
* at a gas ring. A fixed colour rather than a curve because it is two emission *lines* rather than a
|
|
85
|
+
* continuum — that is the whole difference between it and the blackbody term.
|
|
86
|
+
*/
|
|
87
|
+
const CHEMILUMINESCENCE = [0.12, 0.45, 1.0];
|
|
88
|
+
/**
|
|
89
|
+
* A flame's linear RGB: incandescent soot mixed with chemiluminescence by the equivalence ratio.
|
|
90
|
+
*
|
|
91
|
+
* `§8.4` again — the model computes φ and the soot mass, and this maps them. The result is that
|
|
92
|
+
* **nobody chose a flame colour**: a methanol flame is nearly invisible, a candle is orange, a
|
|
93
|
+
* bunsen burner with its collar open is blue, and all three come out of one ratio.
|
|
94
|
+
*
|
|
95
|
+
* Writes into `out`, allocates nothing.
|
|
96
|
+
*/
|
|
97
|
+
export function flameColour(equivalenceRatio, temperature, out) {
|
|
98
|
+
const soot = sootLuminosity(equivalenceRatio);
|
|
99
|
+
blackbodyRGB(temperature, out);
|
|
100
|
+
for (let c = 0; c < 3; c++) {
|
|
101
|
+
out[c] = out[c] * soot + CHEMILUMINESCENCE[c] * (1 - soot);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@driftengine/chemistry/present` — the numbers a renderer draws a fire from, and the installer.
|
|
3
|
+
*
|
|
4
|
+
* **Nothing here imports an engine package or names a core type.** Every target is described
|
|
5
|
+
* structurally by what it must have, which is the `ragdollFromBones` arrangement: core's
|
|
6
|
+
* `ParticleInstances` satisfies `SmokeTarget` exactly and neither package knows the other exists.
|
|
7
|
+
* That matters more here than it did there, because this package imports **no** engine package at
|
|
8
|
+
* all and `present/` is the module most tempted to break it.
|
|
9
|
+
*
|
|
10
|
+
* **Both backends, checked rather than assumed.** `ARCHITECTURE.md` §5 asks every visual feature to
|
|
11
|
+
* ship on WebGL2 and WebGPU or to refuse one in writing. Nothing in this directory is a shader, a
|
|
12
|
+
* pipeline or a bind group — it is arithmetic that writes into typed arrays a consumer already owns
|
|
13
|
+
* — so there is no backend-specific path to be missing. The one thing a consumer must know is not
|
|
14
|
+
* about backends at all and is written at `writeSurface`: **emissive in this engine is gated on
|
|
15
|
+
* `nightFactor`**, so embers written here look right at dusk and dead at noon.
|
|
16
|
+
*/
|
|
17
|
+
export { GLOW_MIN_TEMPERATURE, blackbodyRGB, glowIntensity } from './blackbody.ts';
|
|
18
|
+
export { flameColour, flameHeight, sootLuminosity } from './flame.ts';
|
|
19
|
+
export type { SmokeOptions, SmokeTarget } from './smoke.ts';
|
|
20
|
+
export { emitSmoke, smokeColour } from './smoke.ts';
|
|
21
|
+
export type { SurfaceTarget } from './surface.ts';
|
|
22
|
+
export { writeSurface } from './surface.ts';
|
|
23
|
+
export { crackleRate, hissRate, roarLevel } from './audio.ts';
|
|
24
|
+
export type { ChemistryOptions, InstalledChemistry, MatchReport, ParcelBox } from './install.ts';
|
|
25
|
+
export { installChemistry, parcelFromBounds } from './install.ts';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@driftengine/chemistry/present` — the numbers a renderer draws a fire from, and the installer.
|
|
3
|
+
*
|
|
4
|
+
* **Nothing here imports an engine package or names a core type.** Every target is described
|
|
5
|
+
* structurally by what it must have, which is the `ragdollFromBones` arrangement: core's
|
|
6
|
+
* `ParticleInstances` satisfies `SmokeTarget` exactly and neither package knows the other exists.
|
|
7
|
+
* That matters more here than it did there, because this package imports **no** engine package at
|
|
8
|
+
* all and `present/` is the module most tempted to break it.
|
|
9
|
+
*
|
|
10
|
+
* **Both backends, checked rather than assumed.** `ARCHITECTURE.md` §5 asks every visual feature to
|
|
11
|
+
* ship on WebGL2 and WebGPU or to refuse one in writing. Nothing in this directory is a shader, a
|
|
12
|
+
* pipeline or a bind group — it is arithmetic that writes into typed arrays a consumer already owns
|
|
13
|
+
* — so there is no backend-specific path to be missing. The one thing a consumer must know is not
|
|
14
|
+
* about backends at all and is written at `writeSurface`: **emissive in this engine is gated on
|
|
15
|
+
* `nightFactor`**, so embers written here look right at dusk and dead at noon.
|
|
16
|
+
*/
|
|
17
|
+
export { GLOW_MIN_TEMPERATURE, blackbodyRGB, glowIntensity } from './blackbody.js';
|
|
18
|
+
export { flameColour, flameHeight, sootLuminosity } from './flame.js';
|
|
19
|
+
export { emitSmoke, smokeColour } from './smoke.js';
|
|
20
|
+
export { writeSurface } from './surface.js';
|
|
21
|
+
export { crackleRate, hissRate, roarLevel } from './audio.js';
|
|
22
|
+
export { installChemistry, parcelFromBounds } from './install.js';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `installChemistry` — three lines, and a consumer's oak is oak.
|
|
3
|
+
*
|
|
4
|
+
* **`§16` is honest about what "free" means and this file is where it lands.** Free of *authoring*:
|
|
5
|
+
* a consumer never writes a reaction, never tunes an ignition temperature, never writes a state
|
|
6
|
+
* machine for fire. Not free of *importing*: core does not depend on chemistry and a game with no
|
|
7
|
+
* fire in it pays zero bytes, which is `ARCHITECTURE.md` §5's payload rule and this track does not
|
|
8
|
+
* get an exception to it.
|
|
9
|
+
*
|
|
10
|
+
* So the work goes into making the wiring free, and this is it.
|
|
11
|
+
*/
|
|
12
|
+
import { ReactionRegistry } from '../reaction/registry.ts';
|
|
13
|
+
import { SpeciesRegistry } from '../species/registry.ts';
|
|
14
|
+
import { SubstanceRegistry } from '../substance/registry.ts';
|
|
15
|
+
import { ParcelStore } from '../parcel/store.ts';
|
|
16
|
+
import { AtmosphereField } from '../field/atmosphere.ts';
|
|
17
|
+
import { type AmbientState } from '../field/ambient.ts';
|
|
18
|
+
import { ChemistryWorld } from '../transport/world.ts';
|
|
19
|
+
import { type SubstanceLibrary } from '../library/library.ts';
|
|
20
|
+
export interface ChemistryOptions {
|
|
21
|
+
/** The families this game uses. Everything else is optional. */
|
|
22
|
+
readonly libraries: readonly SubstanceLibrary[];
|
|
23
|
+
/** Metres per gas cell. One by default, which is a room-scale fire. */
|
|
24
|
+
readonly cellSize?: number;
|
|
25
|
+
readonly ambient?: AmbientState;
|
|
26
|
+
/** How many 8³ chunks of air may be live at once. */
|
|
27
|
+
readonly maxChunks?: number;
|
|
28
|
+
}
|
|
29
|
+
/** What a material name resolved to, and what did not resolve at all. */
|
|
30
|
+
export interface MatchReport {
|
|
31
|
+
/** One index per name asked about, in order. `-1` where nothing matched. */
|
|
32
|
+
readonly substances: readonly number[];
|
|
33
|
+
readonly matched: number;
|
|
34
|
+
/** Every name that matched nothing, in the order given. */
|
|
35
|
+
readonly unmatched: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
export interface InstalledChemistry {
|
|
38
|
+
readonly species: SpeciesRegistry;
|
|
39
|
+
readonly reactions: ReactionRegistry;
|
|
40
|
+
readonly substances: SubstanceRegistry;
|
|
41
|
+
readonly parcels: ParcelStore;
|
|
42
|
+
readonly air: AtmosphereField;
|
|
43
|
+
readonly world: ChemistryWorld;
|
|
44
|
+
/** The gas-phase steps the installed families brought, which the air is already running. */
|
|
45
|
+
readonly gasReactions: readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* Resolve material names to substances, **exactly**.
|
|
48
|
+
*
|
|
49
|
+
* **There is no synonym table and there will not be one.** `AGENTS.md` records the decision for
|
|
50
|
+
* retargeting — matching is exact and there is no name database, because a table of synonyms rots
|
|
51
|
+
* silently and its failure is a limb that does not move. The failure here is worse: a
|
|
52
|
+
* `pine_bark_02` quietly matching `pine` is a material with the wrong ignition temperature, and
|
|
53
|
+
* nobody will ever notice.
|
|
54
|
+
*
|
|
55
|
+
* So unmatched names come back in a list with a count, and the consumer decides.
|
|
56
|
+
*/
|
|
57
|
+
match(names: readonly string[]): MatchReport;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A world with the given families in it, and everything wired to everything else.
|
|
61
|
+
*
|
|
62
|
+
* **Every option after `libraries` removes a capability when omitted rather than breaking.** No
|
|
63
|
+
* ambient: standard air. No cell size: one metre. That is `§16`'s arrangement, and the reason none
|
|
64
|
+
* of them is required is that a consumer who has not decided yet should still get a fire.
|
|
65
|
+
*
|
|
66
|
+
* **The gas-phase reactions come from the libraries rather than from the caller.** A family knows
|
|
67
|
+
* what its own volatiles are, and a consumer who forgot to pass `burn-tar` would have a fire that
|
|
68
|
+
* made smoke and no flame — which is a silent wrong answer rather than an error.
|
|
69
|
+
*/
|
|
70
|
+
export declare function installChemistry(options: ChemistryOptions): InstalledChemistry;
|
|
71
|
+
/** Mass, volume, area and characteristic thickness, from a box. */
|
|
72
|
+
export interface ParcelBox {
|
|
73
|
+
readonly mass: number;
|
|
74
|
+
readonly volume: number;
|
|
75
|
+
readonly area: number;
|
|
76
|
+
/** Volume over area, which is the depth conduction divides into shells. */
|
|
77
|
+
readonly thickness: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Sensible parcel numbers from a bounding box and a density.
|
|
81
|
+
*
|
|
82
|
+
* **`§16`'s third mechanism**: a consumer with meshes gets parcels without measuring anything, and a
|
|
83
|
+
* consumer who cares supplies real numbers instead. The area is all six faces, which is right for
|
|
84
|
+
* something in the open and generous for something lying on a hearth — and being generous is the
|
|
85
|
+
* safe direction, because it makes things heat slightly faster rather than mysteriously refusing to.
|
|
86
|
+
*/
|
|
87
|
+
export declare function parcelFromBounds(width: number, height: number, depth: number, density: number): ParcelBox;
|