@driftengine/chemistry 4.7.0 → 4.7.2

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/README.md CHANGED
@@ -31,7 +31,7 @@ wants matter and no renderer pays for matter and no renderer.
31
31
  | **The parcels meet the air** | A burning parcel draws the oxygen its reactions want out of the cell it sits in and pushes the gas they make into it. One relaxation, both directions |
32
32
  | **Ignition is a criterion** | Five conditions, and `ignite` supplies one of them. **Holding a match to wet wood does nothing**, which is the API telling the truth about what a match is |
33
33
  | **Extinction is four** | Fuel gone, oxygen below the limiting index, surface cooled, or blown off — and a smoulder survives air a flame cannot, which is why smothering leaves embers |
34
- | **An event buffer** | Twenty-one kinds, drained per tick, in the shape `drift/physics`'s contact buffer established |
34
+ | **An event buffer** | Twenty-one kinds declared and drained per tick, in the shape `drift/physics`'s contact buffer established. Four are emitted today: ignition, extinction, and a smoulder starting and ending. The other seventeen, melting and boiling among them, are named and not yet raised, so a consumer watching for one reads the parcel's phase or temperature instead |
35
35
  | **Char glows for an hour** | Above about 800 K the chemistry outruns oxygen delivery, so the rate is transport, not kinetics. The two combine as a series resistance — and **blowing on an ember brightens it** |
36
36
  | **A smoulder's smoke is pale** | Tar leaving a surface cracks to soot and burns where it is hot, and **condenses to droplets where it is not** — a 523 K dew point, which is Clausius-Clapeyron at the loading a plume carries rather than levoglucosan's 658 K boiling point. Two rates that climb with temperature against one gate that closes with it, so a flame blackens its own smoke and a smoulder whitens its own with nothing choosing. `aerosolAt` sums every carried species in the **liquid phase**, so the field never names a species this library invented |
37
37
  | **A log dries, smokes and catches** | The whole of `§21`, asserted: the surface pins at the boiling point while the core stays cold, gas comes off, and it lights. **A green log at the same fire does not** |
@@ -214,12 +214,16 @@ export declare class AtmosphereField {
214
214
  * **The quantity `visibilityAt` is derived from**, and the one a consumer attenuating a light
215
215
  * along a ray actually needs: metres of visibility is one answer to it and `exp(−σ·d)` is another.
216
216
  * Exporting only the first would have every consumer inverting Koschmieder to get back here.
217
+ *
218
+ * **Soot and droplets both.** It counted soot alone, so a smoulder's smoke, which is droplets,
219
+ * hid nothing and was drawn at an alpha of about 1e-29 by `emitSmoke`: the pale half of smoke
220
+ * was in the field and nowhere on screen.
217
221
  */
218
222
  smokeDensityAt(x: number, y: number, z: number): number;
219
223
  /**
220
224
  * How far you can see, metres.
221
225
  *
222
- * Koschmieder over the soot's extinction. Clamped at ten kilometres, past which the answer stops
226
+ * Koschmieder over the smoke's extinction, soot and droplets. Clamped at ten kilometres, past which the answer stops
223
227
  * being about the smoke.
224
228
  */
225
229
  visibilityAt(x: number, y: number, z: number): number;
@@ -36,6 +36,15 @@ export const CHUNK_SIZE = 8;
36
36
  * rather than absorbs and runs lower.
37
37
  */
38
38
  const SOOT_EXTINCTION = 8700;
39
+ /**
40
+ * Extinction per kilogram of droplets per cubic metre, m²/kg.
41
+ *
42
+ * The mass-specific extinction measured for smouldering-fire smoke, which is mostly liquid aerosol
43
+ * and scatters where soot absorbs: about half of flaming smoke's, per kilogram. One number for every
44
+ * droplet, which is right for tar condensing out of a smoulder, the aerosol this field was built
45
+ * for, and generous for coarse water droplets, which scatter less per kilogram.
46
+ */
47
+ const AEROSOL_EXTINCTION = 4400;
39
48
  /** Koschmieder's constant: the contrast threshold that defines "can no longer see it". */
40
49
  const VISIBILITY_CONSTANT = 3.912;
41
50
  /**
@@ -583,14 +592,19 @@ export class AtmosphereField {
583
592
  * **The quantity `visibilityAt` is derived from**, and the one a consumer attenuating a light
584
593
  * along a ray actually needs: metres of visibility is one answer to it and `exp(−σ·d)` is another.
585
594
  * Exporting only the first would have every consumer inverting Koschmieder to get back here.
595
+ *
596
+ * **Soot and droplets both.** It counted soot alone, so a smoulder's smoke, which is droplets,
597
+ * hid nothing and was drawn at an alpha of about 1e-29 by `emitSmoke`: the pale half of smoke
598
+ * was in the field and nowhere on screen.
586
599
  */
587
600
  smokeDensityAt(x, y, z) {
588
- return (this.sootAt(x, y, z) / this.cellVolume) * SOOT_EXTINCTION;
601
+ return ((this.sootAt(x, y, z) * SOOT_EXTINCTION + this.aerosolAt(x, y, z) * AEROSOL_EXTINCTION) /
602
+ this.cellVolume);
589
603
  }
590
604
  /**
591
605
  * How far you can see, metres.
592
606
  *
593
- * Koschmieder over the soot's extinction. Clamped at ten kilometres, past which the answer stops
607
+ * Koschmieder over the smoke's extinction, soot and droplets. Clamped at ten kilometres, past which the answer stops
594
608
  * being about the smoke.
595
609
  */
596
610
  visibilityAt(x, y, z) {
@@ -294,6 +294,14 @@ export declare class ParcelStore {
294
294
  * simultaneously consuming, which is what makes drying visible.
295
295
  */
296
296
  wetnessOf(parcel: number): number;
297
+ /**
298
+ * Whether this parcel's substance can hold liquid water, which is what `wet` asks of it.
299
+ *
300
+ * A caller pouring rain over everything asks first: `wet` refuses a material with no water in its
301
+ * model, and refusing is right, but a loop over every parcel in the rain had no way to tell an
302
+ * iron nail from a log before it was told.
303
+ */
304
+ wettable(parcel: number): boolean;
297
305
  /**
298
306
  * Pour water on it: liquid water into the surface shell, where the boiling reaction will find it.
299
307
  *
@@ -812,6 +812,18 @@ export class ParcelStore {
812
812
  const free = held - authored;
813
813
  return free > 0 ? free / area : 0;
814
814
  }
815
+ /**
816
+ * Whether this parcel's substance can hold liquid water, which is what `wet` asks of it.
817
+ *
818
+ * A caller pouring rain over everything asks first: `wet` refuses a material with no water in its
819
+ * model, and refusing is right, but a loop over every parcel in the rain had no way to tell an
820
+ * iron nail from a log before it was told.
821
+ */
822
+ wettable(parcel) {
823
+ const water = this.species.indexOf('H2O(l)');
824
+ const substance = this.substance[this.check(parcel)];
825
+ return water >= 0 && this.substances.localSlotOf(substance, water) >= 0;
826
+ }
815
827
  /**
816
828
  * Pour water on it: liquid water into the surface shell, where the boiling reaction will find it.
817
829
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftengine/chemistry",
3
- "version": "4.7.0",
3
+ "version": "4.7.2",
4
4
  "description": "Thermochemistry of bulk matter: elements, species, substances and what they turn into",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -49,6 +49,16 @@ export const CHUNK_SIZE = 8;
49
49
  */
50
50
  const SOOT_EXTINCTION = 8700;
51
51
 
52
+ /**
53
+ * Extinction per kilogram of droplets per cubic metre, m²/kg.
54
+ *
55
+ * The mass-specific extinction measured for smouldering-fire smoke, which is mostly liquid aerosol
56
+ * and scatters where soot absorbs: about half of flaming smoke's, per kilogram. One number for every
57
+ * droplet, which is right for tar condensing out of a smoulder, the aerosol this field was built
58
+ * for, and generous for coarse water droplets, which scatter less per kilogram.
59
+ */
60
+ const AEROSOL_EXTINCTION = 4400;
61
+
52
62
  /** Koschmieder's constant: the contrast threshold that defines "can no longer see it". */
53
63
  const VISIBILITY_CONSTANT = 3.912;
54
64
 
@@ -657,15 +667,22 @@ export class AtmosphereField {
657
667
  * **The quantity `visibilityAt` is derived from**, and the one a consumer attenuating a light
658
668
  * along a ray actually needs: metres of visibility is one answer to it and `exp(−σ·d)` is another.
659
669
  * Exporting only the first would have every consumer inverting Koschmieder to get back here.
670
+ *
671
+ * **Soot and droplets both.** It counted soot alone, so a smoulder's smoke, which is droplets,
672
+ * hid nothing and was drawn at an alpha of about 1e-29 by `emitSmoke`: the pale half of smoke
673
+ * was in the field and nowhere on screen.
660
674
  */
661
675
  smokeDensityAt(x: number, y: number, z: number): number {
662
- return (this.sootAt(x, y, z) / this.cellVolume) * SOOT_EXTINCTION;
676
+ return (
677
+ (this.sootAt(x, y, z) * SOOT_EXTINCTION + this.aerosolAt(x, y, z) * AEROSOL_EXTINCTION) /
678
+ this.cellVolume
679
+ );
663
680
  }
664
681
 
665
682
  /**
666
683
  * How far you can see, metres.
667
684
  *
668
- * Koschmieder over the soot's extinction. Clamped at ten kilometres, past which the answer stops
685
+ * Koschmieder over the smoke's extinction, soot and droplets. Clamped at ten kilometres, past which the answer stops
669
686
  * being about the smoke.
670
687
  */
671
688
  visibilityAt(x: number, y: number, z: number): number {
@@ -915,6 +915,19 @@ export class ParcelStore {
915
915
  return free > 0 ? free / area : 0;
916
916
  }
917
917
 
918
+ /**
919
+ * Whether this parcel's substance can hold liquid water, which is what `wet` asks of it.
920
+ *
921
+ * A caller pouring rain over everything asks first: `wet` refuses a material with no water in its
922
+ * model, and refusing is right, but a loop over every parcel in the rain had no way to tell an
923
+ * iron nail from a log before it was told.
924
+ */
925
+ wettable(parcel: number): boolean {
926
+ const water = this.species.indexOf('H2O(l)');
927
+ const substance = this.substance[this.check(parcel)] as number;
928
+ return water >= 0 && this.substances.localSlotOf(substance, water) >= 0;
929
+ }
930
+
918
931
  /**
919
932
  * Pour water on it: liquid water into the surface shell, where the boiling reaction will find it.
920
933
  *