@woosh/meep-engine 3.16.0 → 3.17.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 (410) hide show
  1. package/editor/particles/effect/ParticleEffectDocument.d.ts +21 -6
  2. package/editor/particles/effect/ParticleEffectDocument.d.ts.map +1 -1
  3. package/editor/particles/effect/ParticleEffectDocument.js +38 -17
  4. package/editor/view/node-graph/NodeGraphClipboard.d.ts +186 -0
  5. package/editor/view/node-graph/NodeGraphClipboard.d.ts.map +1 -0
  6. package/editor/view/node-graph/NodeGraphClipboard.js +231 -0
  7. package/editor/view/node-graph/NodeGraphEditorView.d.ts +59 -1
  8. package/editor/view/node-graph/NodeGraphEditorView.d.ts.map +1 -1
  9. package/editor/view/node-graph/NodeGraphEditorView.js +248 -5
  10. package/editor/view/node-graph/NodeGraphView.d.ts.map +1 -1
  11. package/editor/view/node-graph/NodeGraphView.js +84 -8
  12. package/editor/view/node-graph/actions/connection_bind_actions.d.ts +23 -0
  13. package/editor/view/node-graph/actions/connection_bind_actions.d.ts.map +1 -0
  14. package/editor/view/node-graph/actions/connection_bind_actions.js +39 -0
  15. package/editor/view/particles/effect/ParticleCodeEditorView.d.ts.map +1 -1
  16. package/editor/view/particles/effect/ParticleCodeEditorView.js +4 -3
  17. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts +20 -0
  18. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts.map +1 -0
  19. package/editor/view/particles/effect/ParticleCurveEditorView.js +574 -0
  20. package/editor/view/particles/effect/ParticleEmitterInspectorView.d.ts.map +1 -1
  21. package/editor/view/particles/effect/ParticleEmitterInspectorView.js +27 -1
  22. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts +24 -0
  23. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts.map +1 -0
  24. package/editor/view/particles/effect/ParticleGradientEditorView.js +472 -0
  25. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts +5 -0
  26. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts.map +1 -1
  27. package/editor/view/particles/effect/ParticleGraphEditorView.js +61 -31
  28. package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
  29. package/editor/view/particles/effect/ParticleNodeParametersView.js +33 -0
  30. package/editor/view/particles/effect/particle-editor.css +123 -35
  31. package/package.json +1 -1
  32. package/src/core/binary/allocator/OffsetAllocator.d.ts +25 -16
  33. package/src/core/binary/allocator/OffsetAllocator.d.ts.map +1 -1
  34. package/src/core/binary/allocator/OffsetAllocator.js +10 -1
  35. package/src/core/collection/array/typed/float32_array_hash.d.ts +21 -0
  36. package/src/core/collection/array/typed/float32_array_hash.d.ts.map +1 -0
  37. package/src/core/collection/array/typed/float32_array_hash.js +38 -0
  38. package/src/core/graph/csr/CSRGraph.d.ts +8 -1
  39. package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
  40. package/src/core/graph/csr/CSRGraph.js +14 -6
  41. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts +24 -0
  42. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts.map +1 -1
  43. package/src/core/model/node-graph/visual/layout/LayoutProblem.js +27 -0
  44. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts +26 -0
  45. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts.map +1 -1
  46. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.js +28 -0
  47. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts +92 -3
  48. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts.map +1 -1
  49. package/src/core/model/node-graph/visual/layout/RouteGrid.js +405 -242
  50. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts +50 -5
  51. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts.map +1 -1
  52. package/src/core/model/node-graph/visual/layout/RouteSearch.js +160 -24
  53. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts +7 -0
  54. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts.map +1 -1
  55. package/src/core/model/node-graph/visual/layout/RouteSet.js +8 -0
  56. package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.js +9 -3
  57. package/src/core/model/node-graph/visual/layout/layout_build_problem.d.ts.map +1 -1
  58. package/src/core/model/node-graph/visual/layout/layout_build_problem.js +7 -0
  59. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts +9 -3
  60. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts.map +1 -1
  61. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.js +17 -3
  62. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts +9 -6
  63. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts.map +1 -1
  64. package/src/core/model/node-graph/visual/layout/layout_node_graph.js +49 -7
  65. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts +11 -1
  66. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts.map +1 -1
  67. package/src/core/model/node-graph/visual/layout/layout_order_layers.js +135 -16
  68. package/src/core/model/node-graph/visual/layout/route_connections.d.ts +29 -0
  69. package/src/core/model/node-graph/visual/layout/route_connections.d.ts.map +1 -1
  70. package/src/core/model/node-graph/visual/layout/route_connections.js +716 -309
  71. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts +3 -2
  72. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts.map +1 -1
  73. package/src/core/model/node-graph/visual/layout/route_grid_build.js +7 -15
  74. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts +6 -0
  75. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts.map +1 -1
  76. package/src/core/model/node-graph/visual/layout/route_nudge.js +42 -13
  77. package/src/engine/animation/AnimationUtils.js +246 -246
  78. package/src/engine/animation/Animations.js +74 -74
  79. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2078 -2078
  80. package/src/engine/ecs/EventType.d.ts +0 -1
  81. package/src/engine/ecs/EventType.js +1 -26
  82. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts +1 -1
  83. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts.map +1 -1
  84. package/src/engine/ecs/ik/ik_write_local_rotation.js +3 -7
  85. package/src/engine/ecs/parent/EntityNode.d.ts.map +1 -1
  86. package/src/engine/ecs/parent/EntityNode.js +4 -9
  87. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  88. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +360 -364
  89. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts +41 -0
  90. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts.map +1 -0
  91. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.js +40 -0
  92. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +8 -0
  93. package/src/engine/ecs/transform/t64_announce_change.d.ts +10 -7
  94. package/src/engine/ecs/transform/t64_announce_change.d.ts.map +1 -1
  95. package/src/engine/ecs/transform/t64_announce_change.js +12 -12
  96. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts +30 -0
  97. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts.map +1 -0
  98. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.js +29 -0
  99. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
  100. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +13 -21
  101. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
  102. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +4 -8
  103. package/src/engine/graphics3/LightSystem.d.ts.map +1 -1
  104. package/src/engine/graphics3/LightSystem.js +4 -8
  105. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -1
  106. package/src/engine/graphics3/MeshSystem.js +9 -19
  107. package/src/engine/graphics3/ParticleEmitterSystem.d.ts.map +1 -1
  108. package/src/engine/graphics3/ParticleEmitterSystem.js +4 -8
  109. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -1
  110. package/src/engine/graphics3/ShadedGeometrySystem.js +4 -10
  111. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts +1 -1
  112. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts.map +1 -1
  113. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.js +4 -8
  114. package/src/engine/intelligence/behavior/ecs/OrbitingBehavior.js +218 -218
  115. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  116. package/src/engine/physics/ecs/PhysicsSystem.js +2 -5
  117. package/src/engine/sound/ecs/SoundListenerSystem.d.ts.map +1 -1
  118. package/src/engine/sound/ecs/SoundListenerSystem.js +6 -8
  119. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts +0 -4
  120. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts.map +1 -1
  121. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +5 -11
  122. package/src/shade/playground/particle_editor/README.md +43 -2
  123. package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
  124. package/src/shade/playground/particle_system/particle_prototype.js +4 -5
  125. package/src/shade/playground/particle_system/particle_scene.js +4 -4
  126. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  127. package/src/shade/renderer/Renderer.js +13 -1
  128. package/src/shade/renderer/particles/DESIGN.md +104 -19
  129. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +64 -50
  130. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  131. package/src/shade/renderer/particles/GPUParticleSystem.js +176 -78
  132. package/src/shade/renderer/particles/ParticleConstants.d.ts +59 -23
  133. package/src/shade/renderer/particles/ParticleConstants.d.ts.map +1 -1
  134. package/src/shade/renderer/particles/ParticleConstants.js +66 -23
  135. package/src/shade/renderer/particles/ParticleRenderMode.d.ts +19 -0
  136. package/src/shade/renderer/particles/ParticleRenderMode.d.ts.map +1 -0
  137. package/src/shade/renderer/particles/ParticleRenderMode.js +33 -0
  138. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts.map +1 -1
  139. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.js +167 -166
  140. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts +37 -0
  141. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts.map +1 -0
  142. package/src/shade/renderer/particles/data/particle_emitter_record.js +109 -0
  143. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.d.ts +3 -3
  144. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.js +303 -303
  145. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +18 -0
  146. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -1
  147. package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +152 -133
  148. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +0 -11
  149. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  150. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +720 -621
  151. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  152. package/src/shade/renderer/particles/graph/compile_particle_graph.js +8 -3
  153. package/src/shade/renderer/particles/graph/flatten_particle_graph.d.ts.map +1 -1
  154. package/src/shade/renderer/particles/graph/flatten_particle_graph.js +244 -213
  155. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.d.ts.map +1 -1
  156. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.js +6 -0
  157. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.d.ts.map +1 -1
  158. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.js +294 -319
  159. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.d.ts.map +1 -1
  160. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.js +524 -517
  161. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts +22 -0
  162. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts.map +1 -0
  163. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.js +517 -0
  164. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  165. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +185 -159
  166. package/src/shade/renderer/particles/graph/make_particle_group.d.ts.map +1 -1
  167. package/src/shade/renderer/particles/graph/make_particle_group.js +2 -20
  168. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts +17 -0
  169. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts.map +1 -0
  170. package/src/shade/renderer/particles/graph/particle_compare_ops.js +22 -0
  171. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts +28 -8
  172. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts.map +1 -1
  173. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.js +49 -25
  174. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts +40 -0
  175. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts.map +1 -1
  176. package/src/shade/renderer/particles/graph/particle_node_parameters.js +312 -234
  177. package/src/shade/renderer/particles/graph/particle_node_presentation.d.ts.map +1 -1
  178. package/src/shade/renderer/particles/graph/particle_node_presentation.js +59 -3
  179. package/src/shade/renderer/particles/graph/particle_ramp.d.ts +230 -0
  180. package/src/shade/renderer/particles/graph/particle_ramp.d.ts.map +1 -0
  181. package/src/shade/renderer/particles/graph/particle_ramp.js +403 -0
  182. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts +7 -1
  183. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts.map +1 -1
  184. package/src/shade/renderer/particles/graph/validate_particle_graph.js +66 -10
  185. package/src/shade/renderer/particles/graph_particles.d.ts +23 -69
  186. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  187. package/src/shade/renderer/particles/graph_particles.js +105 -262
  188. package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
  189. package/src/shade/renderer/particles/graph_particles_avboit.js +3 -2
  190. package/src/shade/renderer/particles/graph_particles_billboard.d.ts +37 -0
  191. package/src/shade/renderer/particles/graph_particles_billboard.d.ts.map +1 -0
  192. package/src/shade/renderer/particles/graph_particles_billboard.js +93 -0
  193. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +51 -22
  194. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -1
  195. package/src/shade/renderer/particles/isa/InstructionStream.js +98 -38
  196. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts +49 -8
  197. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts.map +1 -1
  198. package/src/shade/renderer/particles/isa/ParticleProgram.js +95 -150
  199. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts +306 -64
  200. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts.map +1 -1
  201. package/src/shade/renderer/particles/isa/ParticleVMISA.js +505 -136
  202. package/src/shade/renderer/particles/isa/particle_assembly.d.ts +36 -12
  203. package/src/shade/renderer/particles/isa/particle_assembly.d.ts.map +1 -1
  204. package/src/shade/renderer/particles/isa/particle_assembly.js +354 -139
  205. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts +9 -0
  206. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts.map +1 -0
  207. package/src/shade/renderer/particles/isa/validate_particle_program.js +128 -0
  208. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts +23 -0
  209. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts.map +1 -1
  210. package/src/shade/renderer/particles/layout/ParticleLayout.js +68 -0
  211. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts +77 -0
  212. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts.map +1 -0
  213. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.js +219 -0
  214. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts +83 -0
  215. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts.map +1 -0
  216. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.js +283 -0
  217. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts +133 -0
  218. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts.map +1 -0
  219. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.js +334 -0
  220. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts +117 -0
  221. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts.map +1 -0
  222. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.js +70 -0
  223. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts +97 -0
  224. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts.map +1 -0
  225. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.js +462 -0
  226. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts +116 -0
  227. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts.map +1 -0
  228. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.js +687 -0
  229. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts +57 -0
  230. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts.map +1 -0
  231. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.js +860 -0
  232. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts +115 -0
  233. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts.map +1 -0
  234. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.js +264 -0
  235. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts +81 -42
  236. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts.map +1 -1
  237. package/src/shade/renderer/particles/optimizer/optimize_particle_program.js +477 -478
  238. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts +100 -0
  239. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts.map +1 -0
  240. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.js +236 -0
  241. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts +139 -59
  242. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts.map +1 -1
  243. package/src/shade/renderer/particles/optimizer/particle_program_analysis.js +764 -647
  244. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts +40 -7
  245. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts.map +1 -1
  246. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.js +376 -213
  247. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts +30 -3
  248. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts.map +1 -1
  249. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.js +337 -223
  250. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts +24 -38
  251. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  252. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +166 -206
  253. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts +99 -0
  254. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts.map +1 -0
  255. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.js +313 -0
  256. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts +3 -2
  257. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts.map +1 -1
  258. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.js +198 -197
  259. package/src/shade/renderer/particles/optimizer/pass_eliminate_dead_code.d.ts.map +1 -1
  260. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts +19 -0
  261. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  262. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +678 -561
  263. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts +22 -0
  264. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts.map +1 -0
  265. package/src/shade/renderer/particles/optimizer/pass_inline_constants.js +221 -0
  266. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.d.ts.map +1 -1
  267. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.js +63 -72
  268. package/src/shade/renderer/particles/optimizer/pass_pack_registers.d.ts.map +1 -1
  269. package/src/shade/renderer/particles/optimizer/pass_pack_registers.js +87 -86
  270. package/src/shade/renderer/particles/optimizer/pass_peephole.d.ts.map +1 -1
  271. package/src/shade/renderer/particles/optimizer/pass_peephole.js +392 -271
  272. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.d.ts.map +1 -1
  273. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.js +361 -268
  274. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts +4 -0
  275. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts.map +1 -0
  276. package/src/shade/renderer/particles/optimizer/pass_schedule.js +244 -0
  277. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.d.ts.map +1 -1
  278. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.js +154 -151
  279. package/src/shade/renderer/particles/particle_test_fixtures.d.ts +20 -0
  280. package/src/shade/renderer/particles/particle_test_fixtures.d.ts.map +1 -1
  281. package/src/shade/renderer/particles/particle_test_fixtures.js +229 -203
  282. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts +99 -25
  283. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts.map +1 -1
  284. package/src/shade/renderer/particles/runtime/EmitterRegistry.js +438 -286
  285. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts +109 -0
  286. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts.map +1 -0
  287. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.js +127 -0
  288. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +87 -102
  289. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
  290. package/src/shade/renderer/particles/runtime/ParticleEmitter.js +184 -163
  291. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts +26 -0
  292. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts.map +1 -1
  293. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts +130 -0
  294. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts.map +1 -0
  295. package/src/shade/renderer/particles/runtime/ProgramHeap.js +595 -0
  296. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts +91 -0
  297. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts.map +1 -0
  298. package/src/shade/renderer/particles/runtime/ProgramPlacement.js +106 -0
  299. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +5 -2
  300. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
  301. package/src/shade/renderer/particles/runtime/create_particle_effect.js +22 -19
  302. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts +28 -0
  303. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts.map +1 -0
  304. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.js +136 -0
  305. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts +35 -0
  306. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts.map +1 -0
  307. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.js +133 -0
  308. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts +18 -9
  309. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  310. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +20 -80
  311. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts +33 -0
  312. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts.map +1 -0
  313. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.js +114 -0
  314. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts +8 -0
  315. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  316. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +33 -101
  317. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts +45 -0
  318. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts.map +1 -0
  319. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.js +123 -0
  320. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts +51 -0
  321. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts.map +1 -0
  322. package/src/shade/renderer/particles/sort/graph_particle_sort.js +120 -0
  323. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts +6 -0
  324. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts.map +1 -0
  325. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.js +16 -0
  326. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +4 -1
  327. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  328. package/src/shade/renderer/particles/vm/ParticleVMReference.js +261 -140
  329. package/src/shade/renderer/particles/vm/chunk_particle_vm.d.ts.map +1 -1
  330. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +217 -255
  331. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts +43 -0
  332. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts.map +1 -0
  333. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.js +55 -0
  334. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts +62 -0
  335. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts.map +1 -0
  336. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.js +86 -0
  337. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts +22 -0
  338. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts.map +1 -0
  339. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.js +32 -0
  340. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts +9 -0
  341. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts.map +1 -0
  342. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.js +19 -0
  343. package/src/shade/renderer/shader/chunk/noise/psrdnoise/README.md +65 -0
  344. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts +24 -0
  345. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts.map +1 -0
  346. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.js +83 -0
  347. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts +25 -0
  348. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts.map +1 -0
  349. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.js +42 -0
  350. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts +31 -0
  351. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts.map +1 -0
  352. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.js +80 -0
  353. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts +26 -0
  354. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts.map +1 -0
  355. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.js +109 -0
  356. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts +38 -0
  357. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts.map +1 -0
  358. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.js +129 -0
  359. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts +21 -0
  360. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts.map +1 -0
  361. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.js +35 -0
  362. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts +19 -0
  363. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts.map +1 -0
  364. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.js +28 -0
  365. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts +13 -0
  366. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts.map +1 -0
  367. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.js +22 -0
  368. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts +22 -0
  369. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts.map +1 -0
  370. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.js +59 -0
  371. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts +23 -0
  372. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts.map +1 -0
  373. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.js +74 -0
  374. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts +34 -1
  375. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts.map +1 -1
  376. package/src/shade/wgsl/emulator/ComputeShaderEmulator.js +41 -1
  377. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts +3 -2
  378. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts.map +1 -1
  379. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +49 -10
  380. package/src/view/minimap/dom/MinimapCameraView.d.ts.map +1 -1
  381. package/src/view/minimap/dom/MinimapCameraView.js +4 -9
  382. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts +0 -15
  383. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts.map +0 -1
  384. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts +0 -45
  385. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts.map +0 -1
  386. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts +0 -24
  387. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts.map +0 -1
  388. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts +0 -19
  389. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts.map +0 -1
  390. package/src/engine/ecs/transform/__probe.d.ts +0 -4
  391. package/src/engine/ecs/transform/__probe.d.ts.map +0 -1
  392. package/src/engine/ecs/util/hideEntityGracefully.d.ts +0 -9
  393. package/src/engine/ecs/util/hideEntityGracefully.d.ts.map +0 -1
  394. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts +0 -27
  395. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts.map +0 -1
  396. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts +0 -14
  397. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +0 -1
  398. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts +0 -156
  399. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts.map +0 -1
  400. package/src/shade/renderer/particles/runtime/Emitter.d.ts +0 -219
  401. package/src/shade/renderer/particles/runtime/Emitter.d.ts.map +0 -1
  402. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts +0 -77
  403. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts.map +0 -1
  404. package/src/shade/renderer/particles/runtime/ProgramArena.js +0 -155
  405. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts +0 -15
  406. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts.map +0 -1
  407. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts +0 -34
  408. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts.map +0 -1
  409. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts +0 -32
  410. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts.map +0 -1
@@ -1,86 +1,124 @@
1
- import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
2
1
  import { chunk_random } from "../../shader/chunk/random/chunk_random.js";
2
+ import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
3
3
  import {
4
- PARTICLE_PROGRAM_CODE_BASE_WORD,
5
- PARTICLE_PROGRAM_HEADER_WORDS,
6
- PARTICLE_VM_MAX_INSTRUCTIONS,
7
- PARTICLE_VM_REGISTER_SLOTS,
8
- } from "../ParticleConstants.js";
9
- import {
4
+ PARTICLE_VM_INSTRUCTION_WORDS,
10
5
  VM_BUILTIN,
6
+ VM_DST_MASK,
7
+ VM_DST_SHIFT,
8
+ VM_KIND,
9
+ VM_KIND_BITS,
10
+ VM_KIND_MASK,
11
+ VM_KIND_SHIFT,
11
12
  VM_OP,
12
13
  VM_OP_MASK,
14
+ VM_OPERAND_BASE_MASK,
15
+ VM_OPERAND_SWIZZLE_SHIFT,
16
+ VM_SWIZZLE_IDENTITY,
13
17
  VM_WIDTH_MASK,
14
18
  VM_WIDTH_SHIFT,
15
19
  } from "../isa/ParticleVMISA.js";
20
+ import {
21
+ PARTICLE_PROGRAM_CODE_BASE_WORD,
22
+ PARTICLE_PROGRAM_HEADER_WORDS,
23
+ PARTICLE_VM_MAX_INSTRUCTIONS,
24
+ } from "../ParticleConstants.js";
25
+ import { PARTICLE_VM_CONTEXT_STRUCT } from "./PARTICLE_VM_CONTEXT_STRUCT.js";
16
26
 
17
27
  /**
18
28
  * WGSL implementation of the particle VM — the interpreter a single simulation dispatch runs over
19
29
  * every particle. It is written to match {@link ../vm/ParticleVMReference.js} exactly; the parity
20
30
  * spec cross-checks the two through the emulator.
21
31
  *
22
- * Opcode numbers, register-file size and the instruction budget are interpolated straight from the
23
- * ISA / constants so the WGSL can never drift from the JS side.
32
+ * Opcode numbers, field positions, register-file size and the instruction budget are interpolated
33
+ * straight from the ISA / constants so the WGSL can never drift from the JS side.
34
+ *
35
+ * ## Resolve, compute, write
36
+ *
37
+ * Every instruction runs in three steps, and only the middle one knows the opcode. {@link vm_source}
38
+ * resolves each of the three sources into a four-lane value by the kind word 0 gives it — a register
39
+ * run read through its swizzle, or a literal — with the lanes past the instruction's width zero.
40
+ * The opcode body is then one expression over those values, `d = a + b`, and the lanes the opcode
41
+ * writes go back to the register file afterwards. Because every source is read before anything is
42
+ * written, a source may overlap the destination in any way; there is no aliasing rule to keep.
43
+ *
44
+ * That shape is what the operand swizzles and the literal kind bought: there is no `SWIZZLE` case,
45
+ * no `SCALE` case, no per-opcode operand loop, and the reductions sum four lanes of which the unread
46
+ * ones are zero.
47
+ *
48
+ * ## A lane per statement, never a computed component
49
+ *
50
+ * Nothing here indexes a vector by a variable. {@link vm_source} fills `v.x`, `v.y`, `v.z`, `v.w`
51
+ * one statement each behind a width test; the stores, the pool and attribute loads and the RNG draws
52
+ * do the same; the write-back is four predicated stores. The obvious loop — `for (c < lanes) {
53
+ * v[c] = ... }` — is the same program and three times slower on an RTX 4090 (5.0 ms against 1.7 ms
54
+ * for the simulate pass of a 4876-instruction effect at 20k alive): a vector written through a
55
+ * computed index is addressable memory as far as the compiler is concerned, so `a`, `b`, `c` and `d`
56
+ * all moved to local memory and every lane became a store and a reload. Spelled out, they stay in
57
+ * registers, and an instruction costs what it did under the per-opcode loops (within noise at 50k
58
+ * and 200k alive on the same effect), with the literal kind's savings on top. The register file is
59
+ * the one dynamically indexed array, as it always was.
60
+ *
61
+ * ## Registers are scalars, and they are not declared here
24
62
  *
25
- * ## Registers are scalars
63
+ * A register is one `f32` slot, not a `vec4`, and an instruction's width says how many consecutive
64
+ * slots each of its operands spans. The same count of floats as slots holds far more values than it
65
+ * did as `vec4`s, because a `vec3` costs three of them instead of four: as `vec4`s the file cost 81
66
+ * VGPRs on RDNA2 and held the shader to 10 waves against a cap of 16.
26
67
  *
27
- * `vm_reg` is an `array<f32>`, not an `array<vec4<f32>>`, and an instruction's width says how many
28
- * consecutive slots each of its operands spans. The file is dynamically indexed, so it lives in
29
- * registers rather than scratch and its size is the shader's register footprint: as `vec4`s it cost
30
- * 81 VGPRs on RDNA2 and held the shader to 10 waves against a cap of 16, and the same count of
31
- * floats as slots holds far more values, because a `vec3` costs three of them instead of four.
32
- * Spanning only the lanes an instruction needs also roughly halves both the arithmetic and the
33
- * interpreter's own size — a `vec4` op against a dynamically indexed file is twelve `v_movrel`,
34
- * where a loop over its width is emitted once.
68
+ * Where those slots LIVE is not this chunk's business. The interpreter reads and writes them through
69
+ * `vm_reg_read` and `vm_reg_write`, which a backing chunk supplies — the same external-symbol seam
70
+ * `particle_vm_sample_curve` uses. There are two backings and they are the two interpreters:
71
+ *
72
+ * - {@link ./chunk_particle_vm_registers_fast.js} — a `var<private>` array of
73
+ * {@link ../ParticleConstants.js PARTICLE_VM_FAST_REGISTER_SLOTS}. Dynamically indexed, so it
74
+ * lives in registers rather than scratch, and its size is the shader's register footprint. This
75
+ * is the interpreter everything runs on.
76
+ * - {@link ./chunk_particle_vm_registers_wide.js} — a storage buffer of
77
+ * {@link ../ParticleConstants.js PARTICLE_VM_REGISTER_SLOTS}, which is every slot the ISA can
78
+ * name. Slow, and the fallback for a program the fast file cannot hold.
79
+ *
80
+ * The seam is deliberately narrow: two functions, no opcode knows which backing it has, and the
81
+ * measurements below were taken on the fast one and describe it.
35
82
  *
36
83
  * ## The builtin context stays a struct
37
84
  *
38
85
  * `vm_load_builtin`'s `switch` looks like something an `array<f32, 8 * 4>` indexed by builtin id
39
86
  * would delete outright — the shape the JS reference's `builtins` already has. It is measured, not
40
- * assumed, and the array loses: on RDNA2 (gfx1030, amdspv) the register file stops living in
41
- * registers the moment a SECOND dynamically indexed array exists, whatever its size. Same
42
- * interpreter, one builtin load:
43
- *
44
- * | builtin context | VGPRs | scratch | waves |
45
- * |-----------------------------|-------|-----------|-------|
46
- * | struct + switch (this) | 53 | 0 | 16 |
47
- * | `array<f32, 32>` by id | 46 | 64 dwords | 16 |
48
- * | `array<vec4<f32>, 8>` | 47 | 64 dwords | 16 |
49
- * | context atop a 64-slot file | 78 | 0 | 12 |
87
+ * assumed, and the array loses: on RDNA2 (gfx1030, amdspv, simulate's wave-uniform form, a 48-slot
88
+ * file) a SECOND dynamically indexed array costs sixteen VGPRs whatever its size — as much as growing
89
+ * the register file by a third — for 28 more instructions and nothing back. Same interpreter, one
90
+ * builtin load:
50
91
  *
51
- * 64 dwords is `vm_reg` plus the context spilled together: the promotion pass gives up on both
52
- * rather than picking one, and every register access becomes memory traffic. Folding the context
53
- * into the register file instead keeps it out of scratch and costs a quarter of the occupancy. So
54
- * the id stays a `switch` over named fields — a select chain fed from the registers the setup
55
- * wrote — and only the per-lane pick was worth deleting.
92
+ * | builtin context | VGPRs | scratch | instructions |
93
+ * |-----------------------------|-------|---------|--------------|
94
+ * | struct + switch (this) | 80 | 0 | 917 |
95
+ * | `array<f32, 32>` by id | 96 | 0 | 945 |
96
+ * | `array<vec4<f32>, 8>` | 96 | 0 | 945 |
97
+ * | this atop a 64-slot file | 96 | 0 | 925 |
56
98
  *
57
- * Dispatch uses a `switch` over the opcode — a jump table on real hardware and (since the emulator
58
- * gained switch support) directly emulator-runnable. Register-file writes are the only per-instruction
59
- * state mutation, which the emulator handles reliably; the RNG state (RANDOM opcode) is a running
60
- * read-modify-write, and now that the emulator wraps u32 arithmetic at 32 bits it runs through the
61
- * interpreter loop too and is cross-checked against the reference by the parity spec.
99
+ * Under the per-opcode interpreter this replaced it was worse still: the promotion pass gave up on
100
+ * both arrays rather than picking one and spilled the register file to scratch. So the id stays a
101
+ * `switch` over named fields.
62
102
  *
63
- * The RNG is the engine's house generator: `RANDOM` draws through `random()`, over the shared
64
- * `rnd_state`. The VM used to carry its own PCG and its own state word, which by the end differed
65
- * from `random()` in nothing but the names — an implementation fork with no reason to exist.
103
+ * Dispatch uses a `switch` over the opcode — a jump table on real hardware and directly
104
+ * emulator-runnable. Register-file writes are the only per-instruction state mutation; the RNG
105
+ * state (`RANDOM`) is a running read-modify-write over the shared `rnd_state`, which the emulator
106
+ * wraps at 32 bits and the parity spec holds to the reference draw for draw.
66
107
  *
67
108
  * ## The RNG has no distributions
68
109
  *
69
- * `RANDOM` is one uniform draw per lane, and that is the whole opcode. It used to switch over a
70
- * distribution mode — signed, unit sphere, unit disk — and each of those is arithmetic a program
71
- * can write over this draw for itself: with their constants hoisted, a unit sphere is ten
72
- * instructions and a unit disk seven, bit-identical to what the switch computed, because they are
73
- * the same expression. A sampler is a useful thing to have and there is no principled place to stop
74
- * adding them, which is the argument for the VM having none: they belong in a library over the
75
- * instruction set, not inside it — see ../graph/particle_graph_distributions.js.
110
+ * `RANDOM` is one uniform draw per lane, and that is the whole opcode. A shaped distribution is
111
+ * arithmetic a program writes over the draw — see ../graph/particle_graph_distributions.js.
76
112
  *
77
113
  * ## Contract
78
114
  *
79
- * - Register file, kill flag and the builtin context are module-scope `var<private>`, as is the
80
- * shared `rnd_state` the RNG runs on. Call `particle_vm_reset()` at the start of each particle,
81
- * seed `rnd_state`, and populate `vm_ctx` before invoking {@link particle_vm_execute}.
115
+ * - The enclosing shader must include exactly one register backing chunk — see above. Nothing in
116
+ * here declares `vm_reg_read` / `vm_reg_write`.
117
+ * - Kill flag and the builtin context are module-scope `var<private>`, as is the shared `rnd_state`
118
+ * the RNG runs on. Call `particle_vm_reset()` at the start of each particle, seed `rnd_state`, and
119
+ * populate `vm_ctx` before invoking {@link particle_vm_execute}.
82
120
  * - `particle_vm_execute(program, code_offset, instruction_count, constant_offset, pool,
83
- * record_base)` runs one section. `program` is the packed arena buffer — header, constant pool,
121
+ * record_base)` runs one section. `program` is the packed program buffer — header, constant pool,
84
122
  * code, see `ParticleConstants.PARTICLE_PROGRAM_HEADER_WORDS` — so code and data are one binding.
85
123
  * Attributes are read/written as f32 bit patterns inside `pool` at `record_base +
86
124
  * attribute_offset`.
@@ -94,36 +132,21 @@ import {
94
132
  * f32`. The enclosing shader must define it (production: animation-curve backed; tests: a stub).
95
133
  */
96
134
 
97
- const R = PARTICLE_VM_REGISTER_SLOTS;
98
-
99
135
  const source = /* language=wgsl */ `
100
- const PARTICLE_VM_REGISTER_SLOTS : u32 = ${R}u;
101
136
  const PARTICLE_VM_MAX_INSTRUCTIONS : u32 = ${PARTICLE_VM_MAX_INSTRUCTIONS}u;
137
+ const PARTICLE_VM_INSTRUCTION_WORDS : u32 = ${PARTICLE_VM_INSTRUCTION_WORDS}u;
102
138
  const PARTICLE_PROGRAM_HEADER_WORDS : u32 = ${PARTICLE_PROGRAM_HEADER_WORDS}u;
103
139
  const PARTICLE_PROGRAM_CODE_BASE_WORD : u32 = ${PARTICLE_PROGRAM_CODE_BASE_WORD}u;
104
140
 
105
- // Per-invocation builtin inputs the host assembles before running a program.
106
- struct ParticleVMContext {
107
- delta_time : f32,
108
- emitter_age : f32,
109
- time : f32,
110
- spawn_fraction : f32,
111
- particle_index : f32,
112
- emitter_position : vec3<f32>,
113
- emitter_direction : vec3<f32>,
114
- emitter_up : vec3<f32>,
115
- }
116
-
117
- // One f32 per slot. A value of width w occupies w consecutive slots, and an operand names the first.
118
- var<private> vm_reg : array<f32, ${R}>;
119
141
  var<private> vm_killed : bool;
120
- var<private> vm_ctx : ParticleVMContext;
142
+ var<private> vm_ctx : ${PARTICLE_VM_CONTEXT_STRUCT.wgsl_ref};
121
143
 
122
- // Zero the register file and reset the kill flag. Call once per particle before executing a section.
144
+ // Ready the interpreter for one particle: clear the kill flag, and nothing else. Neither backing
145
+ // resets its register file — the private one is zero at the start of every invocation by language
146
+ // rule, the storage one is undefined until written — so there is nothing to call. The flag is reset
147
+ // because a wide invocation walks many particles in a grid-stride loop, and the second must not
148
+ // inherit the first's verdict.
123
149
  fn particle_vm_reset() {
124
- for (var i = 0u; i < PARTICLE_VM_REGISTER_SLOTS; i = i + 1u) {
125
- vm_reg[i] = 0.0;
126
- }
127
150
  vm_killed = false;
128
151
  }
129
152
 
@@ -143,38 +166,29 @@ fn vm_load_builtin(id : u32) -> vec4<f32> {
143
166
  }
144
167
  }
145
168
 
146
- // Sum of products over \`width\` slots of each operand. The reductions are all this plus a square root.
147
- fn vm_dot_slots(a : u32, b : u32, width : u32) -> f32 {
148
- var sum = 0.0;
149
- for (var c = 0u; c < width; c = c + 1u) {
150
- sum = sum + vm_reg[a + c] * vm_reg[b + c];
151
- }
152
- return sum;
153
- }
154
-
155
- // Read an attribute (f32 bits) out of the record into \`width\` slots. Slots past the attribute's
156
- // component count read zero, which is how a narrow attribute reaches a wider operation for free.
157
- fn vm_load_attr(pool : ptr<storage, array<u32>, read_write>, base : u32, off : u32, comp : u32, dst : u32, width : u32) {
158
- for (var c = 0u; c < width; c = c + 1u) {
159
- if (c < comp) {
160
- vm_reg[dst + c] = bitcast<f32>((*pool)[base + off + c]);
161
- } else {
162
- vm_reg[dst + c] = 0.0;
163
- }
164
- }
165
- }
166
-
167
- // One record word per component, and the width is the component count — a store cannot write slots
168
- // the value does not have.
169
- fn vm_store_attr(pool : ptr<storage, array<u32>, read_write>, base : u32, off : u32, comp : u32, src : u32) {
170
- for (var c = 0u; c < comp; c = c + 1u) {
171
- (*pool)[base + off + c] = bitcast<u32>(vm_reg[src + c]);
169
+ // One source, resolved by the kind word 0 gives it: a register run read through its swizzle, or a
170
+ // literal in every lane. Lanes past \`lanes\` are zero, so a reduction may sum all four. The only
171
+ // place the kinds and swizzles are read; every opcode below sees a value.
172
+ fn vm_source(kind : u32, word : u32, lanes : u32) -> vec4<f32> {
173
+ // One lane per statement, each behind its own width test: a component written through a
174
+ // computed index would put the vector in local memory, and that is the whole cost of the VM.
175
+ var v = vec4<f32>(0.0);
176
+ if (kind == ${VM_KIND.REG}u) {
177
+ let base = word & ${VM_OPERAND_BASE_MASK}u;
178
+ let swizzle = ((word >> ${VM_OPERAND_SWIZZLE_SHIFT}u) & 0xffu) ^ ${VM_SWIZZLE_IDENTITY}u;
179
+ v.x = vm_reg_read(base + (swizzle & 3u));
180
+ if (lanes > 1u) { v.y = vm_reg_read(base + ((swizzle >> 2u) & 3u)); }
181
+ if (lanes > 2u) { v.z = vm_reg_read(base + ((swizzle >> 4u) & 3u)); }
182
+ if (lanes > 3u) { v.w = vm_reg_read(base + ((swizzle >> 6u) & 3u)); }
183
+ } else if (kind == ${VM_KIND.IMM}u) {
184
+ v = select(vec4<f32>(0.0), vec4<f32>(bitcast<f32>(word)), vec4<bool>(true, lanes > 1u, lanes > 2u, lanes > 3u));
172
185
  }
186
+ return v;
173
187
  }
174
188
 
175
189
  // Execute one program section against a particle record living in pool at record_base.
176
190
  //
177
- // \`program\` is the packed arena buffer: a header, the constant pool, then the code. The header's
191
+ // \`program\` is the packed program buffer: a header, the constant pool, then the code. The header's
178
192
  // first word says where the code starts; \`code_offset\` is a word offset into the code section and
179
193
  // \`constant_offset\` a WORD index into the pool — both as the emitter record carries them.
180
194
  fn particle_vm_execute(
@@ -187,184 +201,132 @@ fn particle_vm_execute(
187
201
  ) {
188
202
  let count = min(instruction_count, PARTICLE_VM_MAX_INSTRUCTIONS);
189
203
  let code_base = (*program)[PARTICLE_PROGRAM_CODE_BASE_WORD] + code_offset;
204
+ let pool_base = PARTICLE_PROGRAM_HEADER_WORDS + constant_offset;
190
205
 
191
206
  for (var pc = 0u; pc < count; pc = pc + 1u) {
192
- let ib = code_base + pc * 5u;
207
+ let ib = code_base + pc * PARTICLE_VM_INSTRUCTION_WORDS;
193
208
  let word0 = (*program)[ib];
209
+ let s0 = (*program)[ib + 1u];
210
+ let s1 = (*program)[ib + 2u];
211
+ let s2 = (*program)[ib + 3u];
212
+
194
213
  let op = word0 & ${VM_OP_MASK}u;
195
- // Lane count this instruction spans. The opcodes whose count is fixed ignore it.
214
+ // Lane count this instruction spans. The opcodes whose count is fixed have it encoded so.
196
215
  let w = (word0 >> ${VM_WIDTH_SHIFT}u) & ${VM_WIDTH_MASK}u;
197
- let dst = (*program)[ib + 1u];
198
- let s0 = (*program)[ib + 2u];
199
- let s1 = (*program)[ib + 3u];
200
- let s2 = (*program)[ib + 4u];
216
+ let dst = (word0 >> ${VM_DST_SHIFT}u) & ${VM_DST_MASK}u;
217
+
218
+ let a = vm_source((word0 >> ${VM_KIND_SHIFT}u) & ${VM_KIND_MASK}u, s0, w);
219
+ let b = vm_source((word0 >> ${VM_KIND_SHIFT + VM_KIND_BITS}u) & ${VM_KIND_MASK}u, s1, w);
220
+ let c = vm_source((word0 >> ${VM_KIND_SHIFT + 2 * VM_KIND_BITS}u) & ${VM_KIND_MASK}u, s2, w);
221
+
222
+ var d = vec4<f32>(0.0);
223
+ // How many lanes of d reach the register file: the width, one for a reduction, none for an
224
+ // opcode whose effect is elsewhere.
225
+ var lanes = w;
201
226
 
202
227
  switch op {
203
228
  case ${VM_OP.HALT}u: { return; }
204
- case ${VM_OP.NOP}u: {}
205
- case ${VM_OP.MOV}u: {
206
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = vm_reg[s0 + c]; }
207
- }
229
+ case ${VM_OP.NOP}u: { lanes = 0u; }
230
+ case ${VM_OP.MOV}u: { d = a; }
208
231
  case ${VM_OP.LOAD_CONST}u: {
209
- let cb = PARTICLE_PROGRAM_HEADER_WORDS + constant_offset + s0;
210
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = bitcast<f32>((*program)[cb + c]); }
211
- }
212
- case ${VM_OP.LOAD_ATTR}u: { vm_load_attr(pool, record_base, s0, s1, dst, w); }
213
- case ${VM_OP.STORE_ATTR}u: { vm_store_attr(pool, record_base, s0, s1, dst); }
214
- case ${VM_OP.LOAD_BUILTIN}u: {
215
- // The lane is a dynamic index into a vec4 VALUE, not into memory: no second array,
216
- // and five instructions fewer than the switch that used to pick it.
217
- let v = vm_load_builtin(s0);
218
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = v[c]; }
232
+ let k = pool_base + s0;
233
+ d.x = bitcast<f32>((*program)[k]);
234
+ if (w > 1u) { d.y = bitcast<f32>((*program)[k + 1u]); }
235
+ if (w > 2u) { d.z = bitcast<f32>((*program)[k + 2u]); }
236
+ if (w > 3u) { d.w = bitcast<f32>((*program)[k + 3u]); }
237
+ }
238
+ case ${VM_OP.LOAD_ATTR}u: {
239
+ // Lanes past the attribute's component count read zero, which is how a narrow
240
+ // attribute reaches a wider operation for free.
241
+ let k = record_base + s0;
242
+ let n = min(w, s1);
243
+ d.x = bitcast<f32>((*pool)[k]);
244
+ if (n > 1u) { d.y = bitcast<f32>((*pool)[k + 1u]); }
245
+ if (n > 2u) { d.z = bitcast<f32>((*pool)[k + 2u]); }
246
+ if (n > 3u) { d.w = bitcast<f32>((*pool)[k + 3u]); }
247
+ }
248
+ case ${VM_OP.STORE_ATTR}u: {
249
+ // One record word per component; the width is the component count.
250
+ let k = record_base + s1;
251
+ (*pool)[k] = bitcast<u32>(a.x);
252
+ if (s2 > 1u) { (*pool)[k + 1u] = bitcast<u32>(a.y); }
253
+ if (s2 > 2u) { (*pool)[k + 2u] = bitcast<u32>(a.z); }
254
+ if (s2 > 3u) { (*pool)[k + 3u] = bitcast<u32>(a.w); }
255
+ lanes = 0u;
256
+ }
257
+ case ${VM_OP.LOAD_BUILTIN}u: { d = vm_load_builtin(s0); }
258
+ case ${VM_OP.KILL}u: {
259
+ if (a.x > 0.0) { vm_killed = true; }
260
+ lanes = 0u;
219
261
  }
220
- case ${VM_OP.KILL}u: { if (vm_reg[dst] > 0.0) { vm_killed = true; } }
221
262
 
222
- case ${VM_OP.ADD}u: {
223
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = vm_reg[s0 + c] + vm_reg[s1 + c]; }
224
- }
225
- case ${VM_OP.SUB}u: {
226
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = vm_reg[s0 + c] - vm_reg[s1 + c]; }
227
- }
228
- case ${VM_OP.MUL}u: {
229
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = vm_reg[s0 + c] * vm_reg[s1 + c]; }
230
- }
231
- case ${VM_OP.DIV}u: {
232
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = vm_reg[s0 + c] / vm_reg[s1 + c]; }
233
- }
234
- case ${VM_OP.MAD}u: {
235
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = vm_reg[s0 + c] * vm_reg[s1 + c] + vm_reg[s2 + c]; }
236
- }
237
- case ${VM_OP.MIN}u: {
238
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = min(vm_reg[s0 + c], vm_reg[s1 + c]); }
239
- }
240
- case ${VM_OP.MAX}u: {
241
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = max(vm_reg[s0 + c], vm_reg[s1 + c]); }
242
- }
243
- case ${VM_OP.MIX}u: {
244
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = mix(vm_reg[s0 + c], vm_reg[s1 + c], vm_reg[s2 + c]); }
245
- }
246
- case ${VM_OP.CLAMP}u: {
247
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = clamp(vm_reg[s0 + c], vm_reg[s1 + c], vm_reg[s2 + c]); }
248
- }
249
- case ${VM_OP.SCALE}u: {
250
- let k = vm_reg[s1];
251
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = vm_reg[s0 + c] * k; }
252
- }
263
+ case ${VM_OP.ADD}u: { d = a + b; }
264
+ case ${VM_OP.SUB}u: { d = a - b; }
265
+ case ${VM_OP.MUL}u: { d = a * b; }
266
+ case ${VM_OP.DIV}u: { d = a / b; }
267
+ case ${VM_OP.MAD}u: { d = a * b + c; }
268
+ case ${VM_OP.MIN}u: { d = min(a, b); }
269
+ case ${VM_OP.MAX}u: { d = max(a, b); }
270
+ case ${VM_OP.MIX}u: { d = mix(a, b, c); }
271
+ case ${VM_OP.CLAMP}u: { d = clamp(a, b, c); }
253
272
 
254
- case ${VM_OP.NEG}u: {
255
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = -vm_reg[s0 + c]; }
256
- }
257
- case ${VM_OP.ABS}u: {
258
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = abs(vm_reg[s0 + c]); }
259
- }
260
- case ${VM_OP.FLOOR}u: {
261
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = floor(vm_reg[s0 + c]); }
262
- }
263
- case ${VM_OP.FRACT}u: {
264
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = fract(vm_reg[s0 + c]); }
265
- }
266
- case ${VM_OP.SQRT}u: {
267
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = sqrt(vm_reg[s0 + c]); }
268
- }
269
- case ${VM_OP.SIN}u: {
270
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = sin(vm_reg[s0 + c]); }
271
- }
272
- case ${VM_OP.COS}u: {
273
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = cos(vm_reg[s0 + c]); }
274
- }
275
- case ${VM_OP.EXP}u: {
276
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = exp(vm_reg[s0 + c]); }
277
- }
278
- case ${VM_OP.LOG}u: {
279
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = log(vm_reg[s0 + c]); }
280
- }
281
- case ${VM_OP.POW}u: {
282
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = pow(vm_reg[s0 + c], vm_reg[s1 + c]); }
283
- }
284
- case ${VM_OP.STEP}u: {
285
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = step(vm_reg[s0 + c], vm_reg[s1 + c]); }
286
- }
287
- case ${VM_OP.SMOOTHSTEP}u: {
288
- for (var c = 0u; c < w; c = c + 1u) {
289
- vm_reg[dst + c] = smoothstep(vm_reg[s0 + c], vm_reg[s1 + c], vm_reg[s2 + c]);
290
- }
291
- }
273
+ case ${VM_OP.NEG}u: { d = -a; }
274
+ case ${VM_OP.ABS}u: { d = abs(a); }
275
+ case ${VM_OP.FLOOR}u: { d = floor(a); }
276
+ case ${VM_OP.FRACT}u: { d = fract(a); }
277
+ case ${VM_OP.SQRT}u: { d = sqrt(a); }
278
+ case ${VM_OP.SIN}u: { d = sin(a); }
279
+ case ${VM_OP.COS}u: { d = cos(a); }
280
+ case ${VM_OP.EXP}u: { d = exp(a); }
281
+ case ${VM_OP.LOG}u: { d = log(a); }
282
+ case ${VM_OP.POW}u: { d = pow(a, b); }
283
+ case ${VM_OP.STEP}u: { d = step(a, b); }
284
+ case ${VM_OP.SMOOTHSTEP}u: { d = smoothstep(a, b, c); }
285
+ case ${VM_OP.RSQRT}u: { d = inverseSqrt(a); }
286
+ case ${VM_OP.TAN}u: { d = tan(a); }
287
+ case ${VM_OP.ASIN}u: { d = asin(a); }
288
+ case ${VM_OP.ACOS}u: { d = acos(a); }
289
+ case ${VM_OP.ATAN2}u: { d = atan2(a, b); }
292
290
 
293
- // The reductions write ONE slot. Whatever wants the result wider gets an explicit
294
- // broadcast from the compiler; the implicit fill-all-four is gone.
295
- case ${VM_OP.DOT}u: { vm_reg[dst] = vm_dot_slots(s0, s1, w); }
296
- case ${VM_OP.CROSS}u: {
297
- // Both operands are read out before either is written: the destination is allowed to
298
- // be one of them.
299
- let ax = vm_reg[s0]; let ay = vm_reg[s0 + 1u]; let az = vm_reg[s0 + 2u];
300
- let bx = vm_reg[s1]; let by = vm_reg[s1 + 1u]; let bz = vm_reg[s1 + 2u];
301
- vm_reg[dst] = ay * bz - az * by;
302
- vm_reg[dst + 1u] = az * bx - ax * bz;
303
- vm_reg[dst + 2u] = ax * by - ay * bx;
304
- // A fourth slot, if asked for, is the zero the opcode used to append.
305
- for (var c = 3u; c < w; c = c + 1u) { vm_reg[dst + c] = 0.0; }
306
- }
307
- case ${VM_OP.LENGTH}u: { vm_reg[dst] = sqrt(vm_dot_slots(s0, s0, w)); }
291
+ // The reductions write ONE lane; the lanes past the width are zero and add nothing.
292
+ case ${VM_OP.DOT}u: { d.x = dot(a, b); lanes = 1u; }
293
+ case ${VM_OP.CROSS}u: { d = vec4<f32>(cross(a.xyz, b.xyz), 0.0); }
294
+ case ${VM_OP.LENGTH}u: { d.x = sqrt(dot(a, a)); lanes = 1u; }
308
295
  case ${VM_OP.NORMALIZE}u: {
309
- let len = sqrt(vm_dot_slots(s0, s0, w));
310
- let inv = select(0.0, 1.0 / len, len > 0.0);
311
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = vm_reg[s0 + c] * inv; }
296
+ let len = sqrt(dot(a, a));
297
+ d = a * select(0.0, 1.0 / len, len > 0.0);
312
298
  }
313
299
  case ${VM_OP.DISTANCE}u: {
314
- var sum = 0.0;
315
- for (var c = 0u; c < w; c = c + 1u) {
316
- let e = vm_reg[s0 + c] - vm_reg[s1 + c];
317
- sum = sum + e * e;
318
- }
319
- vm_reg[dst] = sqrt(sum);
320
- }
321
- case ${VM_OP.SWIZZLE}u: {
322
- // Every selected slot is read before any is written, so an in-place swizzle — which
323
- // is what a broadcast is — reads the value it meant to.
324
- let l0 = vm_reg[s0 + (s1 & 3u)];
325
- let l1 = vm_reg[s0 + ((s1 >> 2u) & 3u)];
326
- let l2 = vm_reg[s0 + ((s1 >> 4u) & 3u)];
327
- let l3 = vm_reg[s0 + ((s1 >> 6u) & 3u)];
328
- if (w > 0u) { vm_reg[dst] = l0; }
329
- if (w > 1u) { vm_reg[dst + 1u] = l1; }
330
- if (w > 2u) { vm_reg[dst + 2u] = l2; }
331
- if (w > 3u) { vm_reg[dst + 3u] = l3; }
300
+ let e = a - b;
301
+ d.x = sqrt(dot(e, e));
302
+ lanes = 1u;
332
303
  }
333
304
 
334
- case ${VM_OP.CMP_LT}u: {
335
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = select(0.0, 1.0, vm_reg[s0 + c] < vm_reg[s1 + c]); }
336
- }
337
- case ${VM_OP.CMP_LE}u: {
338
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = select(0.0, 1.0, vm_reg[s0 + c] <= vm_reg[s1 + c]); }
339
- }
340
- case ${VM_OP.CMP_GT}u: {
341
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = select(0.0, 1.0, vm_reg[s0 + c] > vm_reg[s1 + c]); }
342
- }
343
- case ${VM_OP.CMP_GE}u: {
344
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = select(0.0, 1.0, vm_reg[s0 + c] >= vm_reg[s1 + c]); }
345
- }
346
- case ${VM_OP.CMP_EQ}u: {
347
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = select(0.0, 1.0, vm_reg[s0 + c] == vm_reg[s1 + c]); }
348
- }
349
- case ${VM_OP.SELECT}u: {
350
- for (var c = 0u; c < w; c = c + 1u) {
351
- vm_reg[dst + c] = select(vm_reg[s0 + c], vm_reg[s1 + c], vm_reg[s2 + c] > 0.5);
352
- }
353
- }
305
+ case ${VM_OP.CMP_LT}u: { d = select(vec4<f32>(0.0), vec4<f32>(1.0), a < b); }
306
+ case ${VM_OP.CMP_LE}u: { d = select(vec4<f32>(0.0), vec4<f32>(1.0), a <= b); }
307
+ case ${VM_OP.CMP_GT}u: { d = select(vec4<f32>(0.0), vec4<f32>(1.0), a > b); }
308
+ case ${VM_OP.CMP_GE}u: { d = select(vec4<f32>(0.0), vec4<f32>(1.0), a >= b); }
309
+ case ${VM_OP.CMP_EQ}u: { d = select(vec4<f32>(0.0), vec4<f32>(1.0), a == b); }
310
+ case ${VM_OP.SELECT}u: { d = select(a, b, c > vec4<f32>(0.5)); }
354
311
 
355
312
  // \`random()\` off the shared \`rnd_state\`, which has to match
356
313
  // u32_to_unit_f32(pcg_next()) in ParticleVMReference.js bit-for-bit; the parity spec
357
- // holds them to it. One draw per lane, so the draw count is the width.
314
+ // holds them to it. One draw per lane, in lane order, so the draw count is the width.
358
315
  case ${VM_OP.RANDOM}u: {
359
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = random(); }
360
- }
361
- case ${VM_OP.CURVE}u: {
362
- let v = particle_vm_sample_curve(s0, vm_reg[s1]);
363
- for (var c = 0u; c < w; c = c + 1u) { vm_reg[dst + c] = v; }
316
+ d.x = random();
317
+ if (w > 1u) { d.y = random(); }
318
+ if (w > 2u) { d.z = random(); }
319
+ if (w > 3u) { d.w = random(); }
364
320
  }
321
+ case ${VM_OP.CURVE}u: { d = vec4<f32>(particle_vm_sample_curve(s0, b.x)); }
365
322
 
366
- default: {}
323
+ default: { lanes = 0u; }
367
324
  }
325
+
326
+ if (lanes > 0u) { vm_reg_write(dst, d.x); }
327
+ if (lanes > 1u) { vm_reg_write(dst + 1u, d.y); }
328
+ if (lanes > 2u) { vm_reg_write(dst + 2u, d.z); }
329
+ if (lanes > 3u) { vm_reg_write(dst + 3u, d.w); }
368
330
  }
369
331
  }
370
332
  `;
@@ -373,4 +335,4 @@ fn particle_vm_execute(
373
335
  * The particle VM interpreter.
374
336
  * @type {CodeChunk}
375
337
  */
376
- export const chunk_particle_vm = CodeChunk.from(source, [chunk_random]);
338
+ export const chunk_particle_vm = CodeChunk.from(source, [chunk_random, PARTICLE_VM_CONTEXT_STRUCT.declaration_chunk]);
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The fast register backing for {@link ./chunk_particle_vm.js}: the VM's register file as a
3
+ * `var<private>` array of {@link ../ParticleConstants.js PARTICLE_VM_FAST_REGISTER_SLOTS} floats.
4
+ *
5
+ * This is the interpreter the system runs on. The array is dynamically indexed — an operand's base
6
+ * comes out of the instruction word — which is what keeps it in registers rather than scratch, and
7
+ * therefore what makes the file's size the shader's register footprint outright. Every program pays
8
+ * that footprint, not only the ones with a wide working set, so the slot count is a whole-system
9
+ * cost and not a per-effect one. See {@link ../ParticleConstants.js} for what it was measured at.
10
+ *
11
+ * The file is zero when a program starts, and the language is what says so: WGSL zero-initialises a
12
+ * `var<private>` declared without an initializer, once per invocation, and a fast pass runs exactly
13
+ * one program per invocation ({@link ../shaders/shader_particle_simulate.js},
14
+ * {@link ../shaders/shader_particle_emit.js}). Nothing in the compiler's output reads a slot it has
15
+ * not written — the graph allocator assigns a register at the instruction that defines the value — so
16
+ * that zero is not what makes correct programs work. It is what makes an INCORRECT program
17
+ * deterministic, and that matters because it is the behaviour {@link ./ParticleVMReference.js}
18
+ * defines and the parity spec holds the two to. The wide backing cannot offer the same guarantee and
19
+ * does not try; see {@link ./chunk_particle_vm_registers_wide.js}.
20
+ *
21
+ * Nothing here clears the file. A `vm_reg_clear` used to zero it in a loop on top of the
22
+ * initialisation above, which could only ever be a second pass over the same slots; it outlived its
23
+ * reason when {@link ../../../wgsl/emulator/ComputeShaderEmulator.js} learned to reset private state
24
+ * per dispatch, and an empty one outlived its reason with it. Removed 2026-09-06.
25
+ *
26
+ * Measured before removing, compiled to RDNA2 (gfx1030): the two zeroing passes folded into one, so
27
+ * the loop cost 2 instructions of 917 at unchanged VGPRs — what a compiler that sees through it
28
+ * charges. One pass is about 22 instructions there (893 with no zeroing, 915 with the mandatory
29
+ * one), which is what a driver that does not see through it charges again on every particle.
30
+ *
31
+ * A fast pass that ran more than one program per invocation — the grid-stride shape the wide passes
32
+ * use — would not inherit the guarantee, since the second program would start on the first's
33
+ * registers. That pass would zero the file itself, in the loop it needs and where its cost is
34
+ * visible, rather than every pass carrying a call for it.
35
+ *
36
+ * The enclosing shader includes this chunk or the wide one, never both — they define the same two
37
+ * symbols.
38
+ *
39
+ * @type {CodeChunk}
40
+ */
41
+ export const chunk_particle_vm_registers_fast: CodeChunk;
42
+ import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
43
+ //# sourceMappingURL=chunk_particle_vm_registers_fast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk_particle_vm_registers_fast.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,+CAFU,SAAS,CAahB;0BAtDuB,oCAAoC"}