@codexo/exojs-particles 0.15.2 → 0.16.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 (211) hide show
  1. package/README.md +21 -43
  2. package/dist/esm/ParticleStorage.d.ts +188 -0
  3. package/dist/esm/ParticleStorage.d.ts.map +1 -0
  4. package/dist/esm/ParticleStorage.js +208 -0
  5. package/dist/esm/ParticleStorage.js.map +1 -0
  6. package/dist/esm/ParticleSystem.d.ts +189 -60
  7. package/dist/esm/ParticleSystem.d.ts.map +1 -0
  8. package/dist/esm/ParticleSystem.js +660 -560
  9. package/dist/esm/ParticleSystem.js.map +1 -1
  10. package/dist/esm/distributions/BoxArea.d.ts +1 -0
  11. package/dist/esm/distributions/BoxArea.d.ts.map +1 -0
  12. package/dist/esm/distributions/BoxArea.js +38 -44
  13. package/dist/esm/distributions/BoxArea.js.map +1 -1
  14. package/dist/esm/distributions/CircleArea.d.ts +1 -0
  15. package/dist/esm/distributions/CircleArea.d.ts.map +1 -0
  16. package/dist/esm/distributions/CircleArea.js +30 -28
  17. package/dist/esm/distributions/CircleArea.js.map +1 -1
  18. package/dist/esm/distributions/ColorGradient.d.ts +3 -2
  19. package/dist/esm/distributions/ColorGradient.d.ts.map +1 -0
  20. package/dist/esm/distributions/ColorGradient.js +70 -75
  21. package/dist/esm/distributions/ColorGradient.js.map +1 -1
  22. package/dist/esm/distributions/ConeDirection.d.ts +2 -1
  23. package/dist/esm/distributions/ConeDirection.d.ts.map +1 -0
  24. package/dist/esm/distributions/ConeDirection.js +41 -39
  25. package/dist/esm/distributions/ConeDirection.js.map +1 -1
  26. package/dist/esm/distributions/Constant.d.ts +1 -0
  27. package/dist/esm/distributions/Constant.d.ts.map +1 -0
  28. package/dist/esm/distributions/Constant.js +32 -32
  29. package/dist/esm/distributions/Constant.js.map +1 -1
  30. package/dist/esm/distributions/Curve.d.ts +2 -1
  31. package/dist/esm/distributions/Curve.d.ts.map +1 -0
  32. package/dist/esm/distributions/Curve.js +46 -55
  33. package/dist/esm/distributions/Curve.js.map +1 -1
  34. package/dist/esm/distributions/Distribution.d.ts +4 -3
  35. package/dist/esm/distributions/Distribution.d.ts.map +1 -0
  36. package/dist/esm/distributions/LineSegment.d.ts +1 -0
  37. package/dist/esm/distributions/LineSegment.d.ts.map +1 -0
  38. package/dist/esm/distributions/LineSegment.js +25 -23
  39. package/dist/esm/distributions/LineSegment.js.map +1 -1
  40. package/dist/esm/distributions/Range.d.ts +1 -0
  41. package/dist/esm/distributions/Range.d.ts.map +1 -0
  42. package/dist/esm/distributions/Range.js +18 -16
  43. package/dist/esm/distributions/Range.js.map +1 -1
  44. package/dist/esm/distributions/VectorRange.d.ts +1 -0
  45. package/dist/esm/distributions/VectorRange.d.ts.map +1 -0
  46. package/dist/esm/distributions/VectorRange.js +29 -27
  47. package/dist/esm/distributions/VectorRange.js.map +1 -1
  48. package/dist/esm/distributions/index.d.ts +1 -0
  49. package/dist/esm/distributions/index.d.ts.map +1 -0
  50. package/dist/esm/gpu/ParticleGpuState.d.ts +143 -18
  51. package/dist/esm/gpu/ParticleGpuState.d.ts.map +1 -0
  52. package/dist/esm/gpu/ParticleGpuState.js +655 -497
  53. package/dist/esm/gpu/ParticleGpuState.js.map +1 -1
  54. package/dist/esm/gpu/shaders/particle-simulate.wgsl.js +6 -0
  55. package/dist/esm/gpu/shaders/particle-simulate.wgsl.js.map +1 -0
  56. package/dist/esm/index.d.ts +1 -0
  57. package/dist/esm/index.d.ts.map +1 -0
  58. package/dist/esm/index.js +39 -32
  59. package/dist/esm/modules/AlphaFadeOverLifetime.d.ts +4 -3
  60. package/dist/esm/modules/AlphaFadeOverLifetime.d.ts.map +1 -0
  61. package/dist/esm/modules/AlphaFadeOverLifetime.js +68 -55
  62. package/dist/esm/modules/AlphaFadeOverLifetime.js.map +1 -1
  63. package/dist/esm/modules/ApplyForce.d.ts +4 -3
  64. package/dist/esm/modules/ApplyForce.d.ts.map +1 -0
  65. package/dist/esm/modules/ApplyForce.js +49 -43
  66. package/dist/esm/modules/ApplyForce.js.map +1 -1
  67. package/dist/esm/modules/AttractToPoint.d.ts +4 -3
  68. package/dist/esm/modules/AttractToPoint.d.ts.map +1 -0
  69. package/dist/esm/modules/AttractToPoint.js +73 -62
  70. package/dist/esm/modules/AttractToPoint.js.map +1 -1
  71. package/dist/esm/modules/BurstSpawn.d.ts +6 -18
  72. package/dist/esm/modules/BurstSpawn.d.ts.map +1 -0
  73. package/dist/esm/modules/BurstSpawn.js +56 -91
  74. package/dist/esm/modules/BurstSpawn.js.map +1 -1
  75. package/dist/esm/modules/ColorOverLifetime.d.ts +4 -3
  76. package/dist/esm/modules/ColorOverLifetime.d.ts.map +1 -0
  77. package/dist/esm/modules/ColorOverLifetime.js +65 -53
  78. package/dist/esm/modules/ColorOverLifetime.js.map +1 -1
  79. package/dist/esm/modules/ColorOverSpeed.d.ts +5 -4
  80. package/dist/esm/modules/ColorOverSpeed.d.ts.map +1 -0
  81. package/dist/esm/modules/ColorOverSpeed.js +90 -75
  82. package/dist/esm/modules/ColorOverSpeed.js.map +1 -1
  83. package/dist/esm/modules/DeathModule.d.ts +26 -10
  84. package/dist/esm/modules/DeathModule.d.ts.map +1 -0
  85. package/dist/esm/modules/DeathModule.js +38 -22
  86. package/dist/esm/modules/DeathModule.js.map +1 -1
  87. package/dist/esm/modules/Drag.d.ts +3 -2
  88. package/dist/esm/modules/Drag.d.ts.map +1 -0
  89. package/dist/esm/modules/Drag.js +43 -37
  90. package/dist/esm/modules/Drag.js.map +1 -1
  91. package/dist/esm/modules/OrbitalForce.d.ts +4 -3
  92. package/dist/esm/modules/OrbitalForce.d.ts.map +1 -0
  93. package/dist/esm/modules/OrbitalForce.js +70 -58
  94. package/dist/esm/modules/OrbitalForce.js.map +1 -1
  95. package/dist/esm/modules/RateSpawn.d.ts +7 -22
  96. package/dist/esm/modules/RateSpawn.d.ts.map +1 -0
  97. package/dist/esm/modules/RateSpawn.js +39 -71
  98. package/dist/esm/modules/RateSpawn.js.map +1 -1
  99. package/dist/esm/modules/RepelFromPoint.d.ts +4 -3
  100. package/dist/esm/modules/RepelFromPoint.d.ts.map +1 -0
  101. package/dist/esm/modules/RepelFromPoint.js +72 -62
  102. package/dist/esm/modules/RepelFromPoint.js.map +1 -1
  103. package/dist/esm/modules/RotateOverLifetime.d.ts +3 -2
  104. package/dist/esm/modules/RotateOverLifetime.d.ts.map +1 -0
  105. package/dist/esm/modules/RotateOverLifetime.js +40 -36
  106. package/dist/esm/modules/RotateOverLifetime.js.map +1 -1
  107. package/dist/esm/modules/ScaleOverLifetime.d.ts +5 -4
  108. package/dist/esm/modules/ScaleOverLifetime.d.ts.map +1 -0
  109. package/dist/esm/modules/ScaleOverLifetime.js +60 -50
  110. package/dist/esm/modules/ScaleOverLifetime.js.map +1 -1
  111. package/dist/esm/modules/SpawnModule.d.ts +15 -12
  112. package/dist/esm/modules/SpawnModule.d.ts.map +1 -0
  113. package/dist/esm/modules/SpawnModule.js +32 -28
  114. package/dist/esm/modules/SpawnModule.js.map +1 -1
  115. package/dist/esm/modules/SpawnOnDeath.d.ts +13 -9
  116. package/dist/esm/modules/SpawnOnDeath.d.ts.map +1 -0
  117. package/dist/esm/modules/SpawnOnDeath.js +41 -43
  118. package/dist/esm/modules/SpawnOnDeath.js.map +1 -1
  119. package/dist/esm/modules/Turbulence.d.ts +4 -3
  120. package/dist/esm/modules/Turbulence.d.ts.map +1 -0
  121. package/dist/esm/modules/Turbulence.js +101 -89
  122. package/dist/esm/modules/Turbulence.js.map +1 -1
  123. package/dist/esm/modules/UpdateModule.d.ts +21 -13
  124. package/dist/esm/modules/UpdateModule.d.ts.map +1 -0
  125. package/dist/esm/modules/UpdateModule.js +65 -63
  126. package/dist/esm/modules/UpdateModule.js.map +1 -1
  127. package/dist/esm/modules/VelocityOverLifetime.d.ts +7 -6
  128. package/dist/esm/modules/VelocityOverLifetime.d.ts.map +1 -0
  129. package/dist/esm/modules/VelocityOverLifetime.js +75 -73
  130. package/dist/esm/modules/VelocityOverLifetime.js.map +1 -1
  131. package/dist/esm/modules/WgslContribution.d.ts +16 -15
  132. package/dist/esm/modules/WgslContribution.d.ts.map +1 -0
  133. package/dist/esm/modules/WgslContribution.js +37 -29
  134. package/dist/esm/modules/WgslContribution.js.map +1 -1
  135. package/dist/esm/modules/index.d.ts +3 -1
  136. package/dist/esm/modules/index.d.ts.map +1 -0
  137. package/dist/esm/modules/spawnFields.d.ts +32 -0
  138. package/dist/esm/modules/spawnFields.d.ts.map +1 -0
  139. package/dist/esm/modules/spawnFields.js +31 -0
  140. package/dist/esm/modules/spawnFields.js.map +1 -0
  141. package/dist/esm/particlesBuildInfo.d.ts +1 -0
  142. package/dist/esm/particlesBuildInfo.d.ts.map +1 -0
  143. package/dist/esm/particlesBuildInfo.js +6 -4
  144. package/dist/esm/particlesBuildInfo.js.map +1 -1
  145. package/dist/esm/particlesExtension.d.ts +13 -5
  146. package/dist/esm/particlesExtension.d.ts.map +1 -0
  147. package/dist/esm/particlesExtension.js +38 -42
  148. package/dist/esm/particlesExtension.js.map +1 -1
  149. package/dist/esm/public.d.ts +11 -0
  150. package/dist/esm/public.d.ts.map +1 -0
  151. package/dist/esm/renderModes/MeshParticles.d.ts +128 -0
  152. package/dist/esm/renderModes/MeshParticles.d.ts.map +1 -0
  153. package/dist/esm/renderModes/MeshParticles.js +263 -0
  154. package/dist/esm/renderModes/MeshParticles.js.map +1 -0
  155. package/dist/esm/renderModes/ParticleBufferLayout.d.ts +59 -0
  156. package/dist/esm/renderModes/ParticleBufferLayout.d.ts.map +1 -0
  157. package/dist/esm/renderModes/ParticleBufferLayout.js +99 -0
  158. package/dist/esm/renderModes/ParticleBufferLayout.js.map +1 -0
  159. package/dist/esm/renderModes/ParticleInstanceWriter.d.ts +58 -0
  160. package/dist/esm/renderModes/ParticleInstanceWriter.d.ts.map +1 -0
  161. package/dist/esm/renderModes/ParticleInstanceWriter.js +173 -0
  162. package/dist/esm/renderModes/ParticleInstanceWriter.js.map +1 -0
  163. package/dist/esm/renderModes/ParticleMaterial.d.ts +18 -0
  164. package/dist/esm/renderModes/ParticleMaterial.d.ts.map +1 -0
  165. package/dist/esm/renderModes/ParticleMaterial.js +24 -0
  166. package/dist/esm/renderModes/ParticleMaterial.js.map +1 -0
  167. package/dist/esm/renderModes/ParticleRenderMode.d.ts +114 -0
  168. package/dist/esm/renderModes/ParticleRenderMode.d.ts.map +1 -0
  169. package/dist/esm/renderModes/ParticleRenderMode.js +99 -0
  170. package/dist/esm/renderModes/ParticleRenderMode.js.map +1 -0
  171. package/dist/esm/renderModes/QuadParticles.d.ts +55 -0
  172. package/dist/esm/renderModes/QuadParticles.d.ts.map +1 -0
  173. package/dist/esm/renderModes/QuadParticles.js +95 -0
  174. package/dist/esm/renderModes/QuadParticles.js.map +1 -0
  175. package/dist/esm/renderModes/RibbonParticles.d.ts +96 -0
  176. package/dist/esm/renderModes/RibbonParticles.d.ts.map +1 -0
  177. package/dist/esm/renderModes/RibbonParticles.js +202 -0
  178. package/dist/esm/renderModes/RibbonParticles.js.map +1 -0
  179. package/dist/esm/renderModes/shaders/mesh-particles.wgsl.js +6 -0
  180. package/dist/esm/renderModes/shaders/mesh-particles.wgsl.js.map +1 -0
  181. package/dist/esm/renderModes/shaders/mesh.vert.js +6 -0
  182. package/dist/esm/renderModes/shaders/mesh.vert.js.map +1 -0
  183. package/dist/esm/renderModes/shaders/quad-particles.wgsl.js +6 -0
  184. package/dist/esm/renderModes/shaders/quad-particles.wgsl.js.map +1 -0
  185. package/dist/esm/renderModes/shaders/ribbon-particles.wgsl.js +6 -0
  186. package/dist/esm/renderModes/shaders/ribbon-particles.wgsl.js.map +1 -0
  187. package/dist/esm/renderModes/shaders/ribbon.frag.js +6 -0
  188. package/dist/esm/renderModes/shaders/ribbon.frag.js.map +1 -0
  189. package/dist/esm/renderModes/shaders/ribbon.vert.js +6 -0
  190. package/dist/esm/renderModes/shaders/ribbon.vert.js.map +1 -0
  191. package/dist/esm/renderers/WebGl2ParticleRenderer.d.ts +48 -20
  192. package/dist/esm/renderers/WebGl2ParticleRenderer.d.ts.map +1 -0
  193. package/dist/esm/renderers/WebGl2ParticleRenderer.js +317 -321
  194. package/dist/esm/renderers/WebGl2ParticleRenderer.js.map +1 -1
  195. package/dist/esm/renderers/WebGpuParticleRenderer.d.ts +99 -18
  196. package/dist/esm/renderers/WebGpuParticleRenderer.d.ts.map +1 -0
  197. package/dist/esm/renderers/WebGpuParticleRenderer.js +542 -551
  198. package/dist/esm/renderers/WebGpuParticleRenderer.js.map +1 -1
  199. package/dist/esm/renderers/shaders/particle.frag.js +6 -0
  200. package/dist/esm/renderers/shaders/particle.frag.js.map +1 -0
  201. package/dist/esm/renderers/shaders/particle.vert.js +6 -0
  202. package/dist/esm/renderers/shaders/particle.vert.js.map +1 -0
  203. package/package.json +7 -14
  204. package/dist/esm/index.js.map +0 -1
  205. package/dist/esm/register.d.ts +0 -1
  206. package/dist/esm/register.js +0 -43
  207. package/dist/esm/register.js.map +0 -1
  208. package/dist/esm/renderers/glsl/particle.frag.js +0 -4
  209. package/dist/esm/renderers/glsl/particle.frag.js.map +0 -1
  210. package/dist/esm/renderers/glsl/particle.vert.js +0 -4
  211. package/dist/esm/renderers/glsl/particle.vert.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import type { Time } from '@codexo/exojs';
1
+ import type { Seconds } from '@codexo/exojs';
2
2
  import { Rectangle } from '@codexo/exojs';
3
3
  import { Drawable } from '@codexo/exojs';
4
4
  import { Spritesheet } from '@codexo/exojs';
@@ -8,8 +8,11 @@ import { ParticleGpuState } from '#gpu/ParticleGpuState';
8
8
  import type { DeathModule } from '#modules/DeathModule';
9
9
  import type { SpawnModule } from '#modules/SpawnModule';
10
10
  import type { UpdateModule } from '#modules/UpdateModule';
11
+ import type { ParticleEmitter, ParticleWriter } from '#ParticleStorage';
12
+ import { ParticleStorage } from '#ParticleStorage';
13
+ import type { ParticleRenderMode } from '#renderModes/ParticleRenderMode';
11
14
  /**
12
- * Options for {@link ParticleSystem}'s constructor orthogonal config
15
+ * Options for {@link ParticleSystem}'s constructor - orthogonal config
13
16
  * that's independent of the texture source. Texture / frames / spritesheet
14
17
  * live in positional arguments to enforce mutual exclusivity at the type
15
18
  * level (you can't pass both a texture and a spritesheet by accident).
@@ -21,30 +24,51 @@ export interface ParticleSystemOptions {
21
24
  * Direct GPU device. Lets advanced consumers wire a `GPUDevice` owned
22
25
  * outside an `Application` (or a mock device in tests). When omitted,
23
26
  * the backend reference is captured automatically on the first
24
- * {@link ParticleSystem.render} call `WebGpuBackend` ⇒ GPU mode,
27
+ * {@link ParticleSystem.render} call - `WebGpuBackend` ⇒ GPU mode,
25
28
  * anything else (incl. WebGL2) ⇒ CPU mode.
26
29
  */
27
30
  device?: GPUDevice;
31
+ /**
32
+ * How this system's particles become vertices. Fixed at construction. A mode
33
+ * with `gpuEligible === false` forces the system onto the CPU path -
34
+ * silently, exactly like an update module without a `wgsl()` implementation,
35
+ * and observable through {@link ParticleSystem.gpuMode}.
36
+ *
37
+ * **Ownership.** A mode passed here belongs to the system: the system
38
+ * destroys it in {@link ParticleSystem.destroy}, which destroys the mode's
39
+ * material and geometry and releases the GPU resources cached against them.
40
+ * Pass one mode instance per system. Handing the same instance to two
41
+ * systems is not supported - destroying either one pulls the material out
42
+ * from under the other, which then silently rebuilds a fresh material and
43
+ * pays for a fresh shader compile mid-life. Two systems that should share
44
+ * one program simply omit this option; the default mode is shared for
45
+ * exactly that reason and is not owned by any system.
46
+ *
47
+ * @default a process-wide shared {@link QuadParticles}
48
+ */
49
+ readonly render?: ParticleRenderMode;
28
50
  }
29
51
  /**
30
52
  * The central coordinator of the particle pipeline. `ParticleSystem` is a
31
53
  * {@link Drawable} that owns:
32
54
  *
33
- * - **SoA particle storage** one typed array per attribute (position,
34
- * velocity, scale, rotation, color, lifetime, ...), sized to a fixed
35
- * capacity at construction. User code reads/writes via
36
- * `system.posX[slot]`, `system.velX[slot]`, etc.
37
- * - **Spawn modules** — write new particles into freshly allocated slots.
38
- * - **Update modules** mutate the live range each frame (forces, color
55
+ * - **Particle storage** - one channel per attribute (position, velocity,
56
+ * scale, rotation, color, timing, ...), sized to a fixed capacity at
57
+ * construction. Modules and render modes address it by name through a
58
+ * {@link ParticleBatch}; user code brings particles into existence with
59
+ * {@link emit}.
60
+ * - **Spawn modules** - fill freshly emitted particles.
61
+ * - **Update modules** - mutate the live range each frame (forces, color
39
62
  * blends, scale curves, drag, ...). Built-in modules ship both CPU and
40
63
  * WGSL implementations; custom modules can opt into GPU acceleration by
41
64
  * implementing `wgsl()`.
42
- * - **Death modules** fire once per dying particle, before its slot is
65
+ * - **Death modules** - fire once per dying particle, before its slot is
43
66
  * recycled (sub-emitters, event hooks).
44
67
  *
45
68
  * **Auto-routing CPU vs GPU:** at first {@link update}, the system checks:
46
69
  * if a `WebGpuBackend` was supplied AND every registered update module has
47
- * `wgsl()`, the GPU path engages a composite compute pipeline runs
70
+ * `wgsl()` AND the render mode is GPU-eligible, the GPU path engages - a
71
+ * composite compute pipeline runs
48
72
  * integration plus all module bodies in one dispatch and writes directly
49
73
  * into the renderer's instance buffer (no CPU readback). Otherwise the CPU
50
74
  * path runs the existing per-module `apply()` loops.
@@ -60,21 +84,28 @@ export interface ParticleSystemOptions {
60
84
  * 1. Run every spawn module (CPU writes initial values into the spawn slot).
61
85
  * 2. Detect expiries on CPU (via `elapsed >= lifetime`); fire death modules;
62
86
  * set `lifetime[slot] = -1` sentinel + clear `alive[slot]` so the GPU
63
- * shader skips them. **No compaction** slots are recycled on next spawn.
87
+ * shader skips them. **No compaction** - slots are recycled on next spawn.
64
88
  * 3. Dispatch the composite compute pipeline. Integration + update modules
65
89
  * + pack-instances run in one pass; the instance buffer is written
66
90
  * directly. CPU SoA stays as-is for spawn writes.
67
91
  *
68
92
  * **Coordinate space:** particle positions are LOCAL to the system. The
69
- * system's `getGlobalTransform()` is applied on top during rendering both
93
+ * system's `getGlobalTransform()` is applied on top during rendering - both
70
94
  * the WebGL2 and WebGPU shaders multiply `projection * translation * rotated`.
71
95
  * Setting world-space positions on individual particles double-translates.
72
96
  * Position the system itself via `system.setPosition(...)` and emit relative
73
97
  * to `(0, 0)`.
74
98
  *
99
+ * **Pixel snapping:** {@link Drawable.pixelSnapMode} is intentionally ignored
100
+ * for particle systems. Particle instances bake their own per-particle
101
+ * transforms in the emitter/compute path rather than reading the shared
102
+ * pixel-snap transform row, so a snap mode set on the system has no effect on
103
+ * rendered output - snapping thousands of independently-moving sub-pixel
104
+ * particles to the device grid is neither meaningful nor desirable.
105
+ *
75
106
  * @example
76
- * // Backend-agnostic runs CPU on WebGL2, GPU on WebGPU automatically.
77
- * const system = new ParticleSystem(loader.get(Texture, 'spark'), {
107
+ * // Backend-agnostic - runs CPU on WebGL2, GPU on WebGPU automatically.
108
+ * const system = new ParticleSystem(loader.get('spark.png'), {
78
109
  * capacity: 8192,
79
110
  * });
80
111
  *
@@ -83,35 +114,18 @@ export interface ParticleSystemOptions {
83
114
  * system.addUpdateModule(new ColorOverLifetime(fireGradient));
84
115
  * scene.addChild(system);
85
116
  */
86
- export declare class ParticleSystem extends Drawable {
117
+ export declare class ParticleSystem extends Drawable implements ParticleEmitter {
87
118
  /** Maximum particle count this system will store. Fixed at construction. */
88
119
  readonly capacity: number;
89
- readonly posX: Float32Array;
90
- readonly posY: Float32Array;
91
- readonly velX: Float32Array;
92
- readonly velY: Float32Array;
93
- readonly scaleX: Float32Array;
94
- readonly scaleY: Float32Array;
95
- readonly rotations: Float32Array;
96
- readonly rotationSpeeds: Float32Array;
97
- readonly color: Uint32Array;
98
- readonly elapsed: Float32Array;
99
- readonly lifetime: Float32Array;
100
- readonly textureIndex: Uint16Array;
101
- /**
102
- * Number of currently live particles. In CPU mode this is exact: slots
103
- * `[0, liveCount)` are all alive after each `update()`. In GPU mode
104
- * this is a high-water mark — slots `[0, liveCount)` may contain dead
105
- * holes (filled in by future spawns); use {@link aliveCount} for the
106
- * actual alive count.
107
- */
108
- liveCount: number;
109
- /**
110
- * Per-slot alive flag (1 = alive, 0 = dead). Maintained in both CPU
111
- * and GPU mode. Custom modules iterating the live range should check
112
- * this to skip dead slots in GPU mode.
113
- */
114
- readonly alive: Uint8Array;
120
+ /**
121
+ * The simulation's channel storage. Handed to update modules and render modes
122
+ * as a {@link ParticleBatch}; never exposed as a property of the system,
123
+ * because outside those two callbacks its integrated values are not
124
+ * backend-true.
125
+ * @internal
126
+ */
127
+ readonly _storage: ParticleStorage;
128
+ private _writer;
115
129
  private readonly _spawnModules;
116
130
  private readonly _updateModules;
117
131
  private readonly _deathModules;
@@ -125,9 +139,31 @@ export declare class ParticleSystem extends Drawable {
125
139
  * In GPU mode, slots whose CPU SoA values need re-uploading to the GPU
126
140
  * (newly spawned, or just-expired with lifetime sentinel). Cleared
127
141
  * after each compute dispatch. CPU never overwrites integrated GPU
128
- * state only dirty slots flow CPU → GPU.
142
+ * state - only dirty slots flow CPU → GPU.
129
143
  */
130
144
  private readonly _gpuDirtySlots;
145
+ /**
146
+ * Spawn lifetimes, in death order per slot, of the particles that expired
147
+ * since the last batch the device staged for readback. The device's own
148
+ * record cannot carry them: by the time the shader sees the particle, the CPU
149
+ * has already overwritten its lifetime with the expiry sentinel. A slot can
150
+ * appear more than once when it is recycled and dies again before the batch
151
+ * is staged, so the lifetimes queue up per slot.
152
+ */
153
+ private _pendingDeathLifetimes;
154
+ /** Total entries across `_pendingDeathLifetimes`, which is what the dispatch reports. */
155
+ private _pendingDeathCount;
156
+ /** Whether this system has already reported that its death backlog overflowed. */
157
+ private _deathOverflowReported;
158
+ /**
159
+ * Slots handed out by `spawn()` while a recording window is open, or `null`
160
+ * when nothing is recording. Lets callers identify freshly spawned particles
161
+ * without diffing `liveCount`, which cannot see a recycled GPU slot.
162
+ */
163
+ private _spawnRecord;
164
+ private readonly _renderMode;
165
+ /** Whether {@link destroy} may destroy {@link _renderMode} - false for the shared default. */
166
+ private readonly _ownsRenderMode;
131
167
  private _texture;
132
168
  private readonly _frames;
133
169
  private readonly _textureFrame;
@@ -135,22 +171,32 @@ export declare class ParticleSystem extends Drawable {
135
171
  private readonly _texCoords;
136
172
  private _updateTexCoords;
137
173
  private _updateVertices;
138
- /** No texture particles render as solid-color quads on a 1×1 white default. */
174
+ /** No texture - particles render as solid-color quads on a 1×1 white default. */
139
175
  constructor(options?: ParticleSystemOptions);
140
- /** Single texture, no atlas every particle uses the full texture as one frame. */
176
+ /** Single texture, no atlas - every particle uses the full texture as one frame. */
141
177
  constructor(texture: Texture, options?: ParticleSystemOptions);
142
- /** Multi-frame atlas each particle's `textureIndex` selects a frame. */
178
+ /** Multi-frame atlas - each particle's `textureIndex` selects a frame. */
143
179
  constructor(texture: Texture, frames: readonly Rectangle[], options?: ParticleSystemOptions);
144
- /** Spritesheet shorthand texture + frames pulled from the sheet. */
180
+ /** Spritesheet shorthand - texture + frames pulled from the sheet. */
145
181
  constructor(spritesheet: Spritesheet, options?: ParticleSystemOptions);
182
+ /**
183
+ * The render mode this system's particles are drawn with. Fixed at
184
+ * construction via `ParticleSystemOptions.render`; the backend renderers
185
+ * read it every draw to learn the vertex layout, shader and draw model.
186
+ *
187
+ * Without that option this is the shared default mode - the same instance
188
+ * every other defaulted system draws with, so do not destroy it or mutate
189
+ * its material.
190
+ */
191
+ get renderMode(): ParticleRenderMode;
146
192
  get texture(): Texture;
147
193
  set texture(texture: Texture);
148
194
  get textureFrame(): Rectangle;
149
195
  set textureFrame(frame: Rectangle);
150
196
  /**
151
197
  * Atlas frames declared on this system, or empty when the texture is
152
- * used as a single frame. Each particle's `textureIndex[i]` selects
153
- * an entry from this list; out-of-range indices are clamped to 0.
198
+ * used as a single frame. Each particle's {@link textureIndex} selects
199
+ * an entry from this list; anything out of range shows frame 0.
154
200
  */
155
201
  get frames(): readonly Rectangle[];
156
202
  /** `true` when the system declares more than one atlas frame. */
@@ -161,7 +207,15 @@ export declare class ParticleSystem extends Drawable {
161
207
  get gpuMode(): boolean;
162
208
  /** GPU-side state, or `null` in CPU mode. */
163
209
  get gpuState(): ParticleGpuState | null;
164
- /** Actual count of live particles (slots with `alive[i] === 1`). May differ from `liveCount` in GPU mode. */
210
+ /**
211
+ * Upper bound of the slot range that can hold live particles.
212
+ *
213
+ * Exact on the CPU path: after each `update()` slots `[0, liveCount)` are all
214
+ * alive. On the GPU path it is a high-water mark whose range can contain dead
215
+ * holes that future emissions fill; {@link aliveCount} counts the live ones.
216
+ */
217
+ get liveCount(): number;
218
+ /** Actual count of live particles. May be below {@link liveCount} on the GPU path. */
165
219
  get aliveCount(): number;
166
220
  get spawnModules(): readonly SpawnModule[];
167
221
  get updateModules(): readonly UpdateModule[];
@@ -170,23 +224,83 @@ export declare class ParticleSystem extends Drawable {
170
224
  setTextureFrame(frame: Rectangle): this;
171
225
  resetTextureFrame(): this;
172
226
  addSpawnModule(mod: SpawnModule): this;
227
+ /**
228
+ * Registers an update module. Modules run in registration order, each seeing
229
+ * what the previous ones did.
230
+ *
231
+ * Modules may be added and removed at any time, including mid-flight: the
232
+ * next update rebuilds whatever the change invalidated. On the GPU path that
233
+ * is the compute program alone - live particles keep the state the device has
234
+ * been integrating. Adding a module without a `wgsl()` implementation to a
235
+ * running GPU system is the one change that cannot preserve them: the
236
+ * simulation moves to the CPU, which has no copy of the integrated state, so
237
+ * the system clears its live particles rather than continuing from stale
238
+ * values (see {@link clearParticles}).
239
+ */
173
240
  addUpdateModule(mod: UpdateModule): this;
174
241
  addDeathModule(mod: DeathModule): this;
175
242
  clearSpawnModules(): this;
176
243
  clearUpdateModules(): this;
177
244
  clearDeathModules(): this;
178
245
  /**
179
- * Allocates a particle slot and returns its index. Returns `-1` when
180
- * the system is at {@link capacity}.
246
+ * Brings one particle into existence and returns a writer for its initial
247
+ * values, or `null` when the system is at {@link capacity}.
181
248
  *
182
- * **CPU mode:** slots are dense in `[0, liveCount)`. `spawn()` returns
183
- * the next sequential slot; `liveCount++`.
249
+ * The particle starts at the spawn defaults - origin, no velocity, unit
250
+ * scale, no rotation, opaque white, frame 0, one second of life - so a caller
251
+ * writes only what it varies. The returned writer is a cursor that the next
252
+ * `emit()` rebinds, so it must not be stored.
184
253
  *
185
- * **GPU mode:** slots may have dead holes. `spawn()` finds the first
186
- * `alive[i] === 0` slot via a round-robin hint pointer (amortised O(1),
187
- * worst case O(capacity) on full systems).
254
+ * Emission is the only per-particle write that is true on every backend:
255
+ * spawn values originate on the CPU and are uploaded from there, while
256
+ * everything the simulation integrates afterwards lives wherever the
257
+ * simulation runs.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * const particle = system.emit();
262
+ *
263
+ * if (particle) {
264
+ * particle.position.set(120, 40);
265
+ * particle.velocity.set(0, -80);
266
+ * particle.lifetime = 2;
267
+ * }
268
+ * ```
269
+ */
270
+ emit(): ParticleWriter | null;
271
+ /**
272
+ * Allocates one slot at the spawn defaults and returns its index, or `-1` at
273
+ * capacity. Backs {@link emit}; the simulation's own machinery addresses
274
+ * particles by slot, the public surface does not.
275
+ * @internal
188
276
  */
189
- spawn(): number;
277
+ _spawnSlot(): number;
278
+ /**
279
+ * Begin recording the slots handed out by {@link spawn}.
280
+ *
281
+ * Callers that post-process freshly spawned particles cannot infer which
282
+ * slots those are from {@link liveCount}: in GPU mode a spawn may recycle a
283
+ * dead slot below the high-water mark, leaving the count unchanged, and the
284
+ * reused slots are scattered rather than contiguous. Pass the returned token
285
+ * back to {@link _endSpawnRecording}.
286
+ *
287
+ * @internal
288
+ */
289
+ _beginSpawnRecording(): number[] | null;
290
+ /**
291
+ * End the recording started by {@link _beginSpawnRecording} and return the
292
+ * slots allocated during it. Slots are also handed to an enclosing recording
293
+ * so a nested spawn stays visible to the outer window.
294
+ *
295
+ * @internal
296
+ */
297
+ _endSpawnRecording(previous: number[] | null): readonly number[];
298
+ /**
299
+ * Shifts the particles a sub-emitter just produced by `(x, y)`, so a child
300
+ * spawner's distributions read as offsets from the death position.
301
+ * @internal
302
+ */
303
+ _offsetSpawned(slots: readonly number[], x: number, y: number): void;
190
304
  /** Resets the system to zero live particles without destroying it. */
191
305
  clearParticles(): this;
192
306
  /**
@@ -200,12 +314,27 @@ export declare class ParticleSystem extends Drawable {
200
314
  /** @internal */
201
315
  _collect(builder: RenderPlanBuilder, seq?: number): void;
202
316
  /** Per-frame entry point. Routes to CPU or GPU pipeline based on auto-detection at first call. */
203
- update(delta: Time): this;
317
+ update(delta: Seconds): this;
204
318
  destroy(): void;
319
+ /**
320
+ * Marks the compiled program stale. The next update decides what that costs:
321
+ * a GPU system that stays GPU-eligible rebuilds only its program, everything
322
+ * else follows the transition rules in {@link _compile}.
323
+ */
324
+ private _invalidateProgram;
205
325
  private _compile;
206
326
  private _spawnCpu;
207
327
  private _spawnGpu;
328
+ private _claimGpuSlot;
208
329
  private _updateCpu;
330
+ /** Hands one death snapshot to every registered death module, in registration order. */
331
+ private _reportDeath;
209
332
  private _updateGpu;
210
- private _copySlot;
333
+ /**
334
+ * Delivers a staged batch of deaths once its readback has landed. Each record
335
+ * carries what the device integrated; the lifetime comes from the CPU, which
336
+ * is where it was written at spawn.
337
+ */
338
+ private _drainDeaths;
211
339
  }
340
+ //# sourceMappingURL=ParticleSystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ParticleSystem.d.ts","sourceRoot":"","sources":["../../src/ParticleSystem.ts"],"names":[],"mappings":"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"}