@woosh/meep-engine 3.16.0 → 3.17.1

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 (431) hide show
  1. package/README.md +8 -72
  2. package/editor/particles/effect/ParticleEffectDocument.d.ts +21 -6
  3. package/editor/particles/effect/ParticleEffectDocument.d.ts.map +1 -1
  4. package/editor/particles/effect/ParticleEffectDocument.js +38 -17
  5. package/editor/view/node-graph/NodeGraphClipboard.d.ts +186 -0
  6. package/editor/view/node-graph/NodeGraphClipboard.d.ts.map +1 -0
  7. package/editor/view/node-graph/NodeGraphClipboard.js +231 -0
  8. package/editor/view/node-graph/NodeGraphEditorView.d.ts +59 -1
  9. package/editor/view/node-graph/NodeGraphEditorView.d.ts.map +1 -1
  10. package/editor/view/node-graph/NodeGraphEditorView.js +248 -5
  11. package/editor/view/node-graph/NodeGraphView.d.ts.map +1 -1
  12. package/editor/view/node-graph/NodeGraphView.js +84 -8
  13. package/editor/view/node-graph/actions/connection_bind_actions.d.ts +23 -0
  14. package/editor/view/node-graph/actions/connection_bind_actions.d.ts.map +1 -0
  15. package/editor/view/node-graph/actions/connection_bind_actions.js +39 -0
  16. package/editor/view/particles/effect/ParticleCodeEditorView.d.ts.map +1 -1
  17. package/editor/view/particles/effect/ParticleCodeEditorView.js +4 -3
  18. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts +20 -0
  19. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts.map +1 -0
  20. package/editor/view/particles/effect/ParticleCurveEditorView.js +574 -0
  21. package/editor/view/particles/effect/ParticleEmitterInspectorView.d.ts.map +1 -1
  22. package/editor/view/particles/effect/ParticleEmitterInspectorView.js +27 -1
  23. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts +24 -0
  24. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts.map +1 -0
  25. package/editor/view/particles/effect/ParticleGradientEditorView.js +472 -0
  26. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts +5 -0
  27. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts.map +1 -1
  28. package/editor/view/particles/effect/ParticleGraphEditorView.js +61 -31
  29. package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
  30. package/editor/view/particles/effect/ParticleNodeParametersView.js +33 -0
  31. package/editor/view/particles/effect/particle-editor.css +123 -35
  32. package/package.json +1 -1
  33. package/src/core/binary/allocator/OffsetAllocator.d.ts +25 -16
  34. package/src/core/binary/allocator/OffsetAllocator.d.ts.map +1 -1
  35. package/src/core/binary/allocator/OffsetAllocator.js +10 -1
  36. package/src/core/collection/array/typed/float32_array_hash.d.ts +21 -0
  37. package/src/core/collection/array/typed/float32_array_hash.d.ts.map +1 -0
  38. package/src/core/collection/array/typed/float32_array_hash.js +38 -0
  39. package/src/core/graph/csr/CSRGraph.d.ts +8 -1
  40. package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
  41. package/src/core/graph/csr/CSRGraph.js +14 -6
  42. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts +24 -0
  43. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts.map +1 -1
  44. package/src/core/model/node-graph/visual/layout/LayoutProblem.js +27 -0
  45. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts +26 -0
  46. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts.map +1 -1
  47. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.js +28 -0
  48. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts +92 -3
  49. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts.map +1 -1
  50. package/src/core/model/node-graph/visual/layout/RouteGrid.js +405 -242
  51. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts +50 -5
  52. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts.map +1 -1
  53. package/src/core/model/node-graph/visual/layout/RouteSearch.js +160 -24
  54. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts +7 -0
  55. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts.map +1 -1
  56. package/src/core/model/node-graph/visual/layout/RouteSet.js +8 -0
  57. package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.js +9 -3
  58. package/src/core/model/node-graph/visual/layout/layout_build_problem.d.ts.map +1 -1
  59. package/src/core/model/node-graph/visual/layout/layout_build_problem.js +7 -0
  60. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts +9 -3
  61. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts.map +1 -1
  62. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.js +17 -3
  63. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts +9 -6
  64. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts.map +1 -1
  65. package/src/core/model/node-graph/visual/layout/layout_node_graph.js +49 -7
  66. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts +11 -1
  67. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts.map +1 -1
  68. package/src/core/model/node-graph/visual/layout/layout_order_layers.js +135 -16
  69. package/src/core/model/node-graph/visual/layout/route_connections.d.ts +29 -0
  70. package/src/core/model/node-graph/visual/layout/route_connections.d.ts.map +1 -1
  71. package/src/core/model/node-graph/visual/layout/route_connections.js +716 -309
  72. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts +3 -2
  73. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts.map +1 -1
  74. package/src/core/model/node-graph/visual/layout/route_grid_build.js +7 -15
  75. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts +6 -0
  76. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts.map +1 -1
  77. package/src/core/model/node-graph/visual/layout/route_nudge.js +42 -13
  78. package/src/engine/animation/AnimationUtils.js +246 -246
  79. package/src/engine/animation/Animations.js +74 -74
  80. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2078 -2078
  81. package/src/engine/ecs/EventType.d.ts +0 -1
  82. package/src/engine/ecs/EventType.js +1 -26
  83. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts +1 -1
  84. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts.map +1 -1
  85. package/src/engine/ecs/ik/ik_write_local_rotation.js +3 -7
  86. package/src/engine/ecs/parent/EntityNode.d.ts.map +1 -1
  87. package/src/engine/ecs/parent/EntityNode.js +4 -9
  88. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  89. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +360 -364
  90. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts +41 -0
  91. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts.map +1 -0
  92. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.js +40 -0
  93. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +8 -0
  94. package/src/engine/ecs/transform/t64_announce_change.d.ts +10 -7
  95. package/src/engine/ecs/transform/t64_announce_change.d.ts.map +1 -1
  96. package/src/engine/ecs/transform/t64_announce_change.js +12 -12
  97. package/src/engine/ecs/transform/t64_set_rotation_axis_angle.d.ts +25 -0
  98. package/src/engine/ecs/transform/t64_set_rotation_axis_angle.d.ts.map +1 -0
  99. package/src/engine/ecs/transform/t64_set_rotation_axis_angle.js +45 -0
  100. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts +30 -0
  101. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts.map +1 -0
  102. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.js +29 -0
  103. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
  104. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +13 -21
  105. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
  106. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +4 -8
  107. package/src/engine/graphics3/LightSystem.d.ts.map +1 -1
  108. package/src/engine/graphics3/LightSystem.js +4 -8
  109. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -1
  110. package/src/engine/graphics3/MeshSystem.js +9 -19
  111. package/src/engine/graphics3/ParticleEmitterSystem.d.ts.map +1 -1
  112. package/src/engine/graphics3/ParticleEmitterSystem.js +4 -8
  113. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -1
  114. package/src/engine/graphics3/ShadedGeometrySystem.js +4 -10
  115. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts +1 -1
  116. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts.map +1 -1
  117. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.js +4 -8
  118. package/src/engine/intelligence/behavior/ecs/OrbitingBehavior.js +218 -218
  119. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  120. package/src/engine/physics/ecs/PhysicsSystem.js +2 -5
  121. package/src/engine/sound/ecs/SoundListenerSystem.d.ts.map +1 -1
  122. package/src/engine/sound/ecs/SoundListenerSystem.js +6 -8
  123. package/src/engine/sound/simulation/AcousticSimulator.d.ts +11 -8
  124. package/src/engine/sound/simulation/AcousticSimulator.d.ts.map +1 -1
  125. package/src/engine/sound/simulation/AcousticSimulator.js +45 -36
  126. package/src/engine/sound/simulation/README.md +2 -2
  127. package/src/engine/sound/simulation/core/OcclusionSolver.d.ts.map +1 -1
  128. package/src/engine/sound/simulation/core/VolumeField.d.ts +9 -9
  129. package/src/engine/sound/simulation/core/VolumeField.d.ts.map +1 -1
  130. package/src/engine/sound/simulation/core/VolumeField.js +29 -13
  131. package/src/engine/sound/simulation/core/acoustic_occlusion_solve.d.ts +31 -0
  132. package/src/engine/sound/simulation/core/acoustic_occlusion_solve.d.ts.map +1 -0
  133. package/src/engine/sound/simulation/core/acoustic_occlusion_solve.js +134 -0
  134. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts +0 -4
  135. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts.map +1 -1
  136. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +5 -11
  137. package/src/shade/playground/particle_editor/README.md +43 -2
  138. package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
  139. package/src/shade/playground/particle_system/particle_prototype.js +4 -5
  140. package/src/shade/playground/particle_system/particle_scene.js +4 -4
  141. package/src/shade/playground/volumetrics_froxel/main.d.ts.map +1 -1
  142. package/src/shade/playground/volumetrics_froxel/main.js +6 -1
  143. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  144. package/src/shade/renderer/Renderer.js +13 -1
  145. package/src/shade/renderer/particles/DESIGN.md +104 -19
  146. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +64 -50
  147. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  148. package/src/shade/renderer/particles/GPUParticleSystem.js +176 -78
  149. package/src/shade/renderer/particles/ParticleConstants.d.ts +59 -23
  150. package/src/shade/renderer/particles/ParticleConstants.d.ts.map +1 -1
  151. package/src/shade/renderer/particles/ParticleConstants.js +66 -23
  152. package/src/shade/renderer/particles/ParticleRenderMode.d.ts +19 -0
  153. package/src/shade/renderer/particles/ParticleRenderMode.d.ts.map +1 -0
  154. package/src/shade/renderer/particles/ParticleRenderMode.js +33 -0
  155. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts.map +1 -1
  156. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.js +167 -166
  157. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts +37 -0
  158. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts.map +1 -0
  159. package/src/shade/renderer/particles/data/particle_emitter_record.js +109 -0
  160. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.d.ts +3 -3
  161. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.js +303 -303
  162. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +18 -0
  163. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -1
  164. package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +152 -133
  165. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +0 -11
  166. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  167. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +720 -621
  168. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  169. package/src/shade/renderer/particles/graph/compile_particle_graph.js +8 -3
  170. package/src/shade/renderer/particles/graph/flatten_particle_graph.d.ts.map +1 -1
  171. package/src/shade/renderer/particles/graph/flatten_particle_graph.js +244 -213
  172. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.d.ts.map +1 -1
  173. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.js +6 -0
  174. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.d.ts.map +1 -1
  175. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.js +294 -319
  176. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.d.ts.map +1 -1
  177. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.js +524 -517
  178. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts +22 -0
  179. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts.map +1 -0
  180. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.js +517 -0
  181. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  182. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +185 -159
  183. package/src/shade/renderer/particles/graph/make_particle_group.d.ts.map +1 -1
  184. package/src/shade/renderer/particles/graph/make_particle_group.js +2 -20
  185. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts +17 -0
  186. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts.map +1 -0
  187. package/src/shade/renderer/particles/graph/particle_compare_ops.js +22 -0
  188. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts +28 -8
  189. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts.map +1 -1
  190. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.js +49 -25
  191. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts +40 -0
  192. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts.map +1 -1
  193. package/src/shade/renderer/particles/graph/particle_node_parameters.js +312 -234
  194. package/src/shade/renderer/particles/graph/particle_node_presentation.d.ts.map +1 -1
  195. package/src/shade/renderer/particles/graph/particle_node_presentation.js +59 -3
  196. package/src/shade/renderer/particles/graph/particle_ramp.d.ts +230 -0
  197. package/src/shade/renderer/particles/graph/particle_ramp.d.ts.map +1 -0
  198. package/src/shade/renderer/particles/graph/particle_ramp.js +403 -0
  199. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts +7 -1
  200. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts.map +1 -1
  201. package/src/shade/renderer/particles/graph/validate_particle_graph.js +66 -10
  202. package/src/shade/renderer/particles/graph_particles.d.ts +23 -69
  203. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  204. package/src/shade/renderer/particles/graph_particles.js +105 -262
  205. package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
  206. package/src/shade/renderer/particles/graph_particles_avboit.js +3 -2
  207. package/src/shade/renderer/particles/graph_particles_billboard.d.ts +37 -0
  208. package/src/shade/renderer/particles/graph_particles_billboard.d.ts.map +1 -0
  209. package/src/shade/renderer/particles/graph_particles_billboard.js +93 -0
  210. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +51 -22
  211. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -1
  212. package/src/shade/renderer/particles/isa/InstructionStream.js +98 -38
  213. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts +49 -8
  214. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts.map +1 -1
  215. package/src/shade/renderer/particles/isa/ParticleProgram.js +95 -150
  216. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts +306 -64
  217. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts.map +1 -1
  218. package/src/shade/renderer/particles/isa/ParticleVMISA.js +505 -136
  219. package/src/shade/renderer/particles/isa/particle_assembly.d.ts +36 -12
  220. package/src/shade/renderer/particles/isa/particle_assembly.d.ts.map +1 -1
  221. package/src/shade/renderer/particles/isa/particle_assembly.js +354 -139
  222. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts +9 -0
  223. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts.map +1 -0
  224. package/src/shade/renderer/particles/isa/validate_particle_program.js +128 -0
  225. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts +23 -0
  226. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts.map +1 -1
  227. package/src/shade/renderer/particles/layout/ParticleLayout.js +68 -0
  228. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts +77 -0
  229. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts.map +1 -0
  230. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.js +219 -0
  231. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts +83 -0
  232. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts.map +1 -0
  233. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.js +283 -0
  234. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts +133 -0
  235. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts.map +1 -0
  236. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.js +334 -0
  237. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts +117 -0
  238. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts.map +1 -0
  239. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.js +70 -0
  240. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts +97 -0
  241. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts.map +1 -0
  242. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.js +462 -0
  243. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts +116 -0
  244. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts.map +1 -0
  245. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.js +687 -0
  246. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts +57 -0
  247. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts.map +1 -0
  248. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.js +860 -0
  249. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts +115 -0
  250. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts.map +1 -0
  251. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.js +264 -0
  252. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts +81 -42
  253. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts.map +1 -1
  254. package/src/shade/renderer/particles/optimizer/optimize_particle_program.js +477 -478
  255. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts +100 -0
  256. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts.map +1 -0
  257. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.js +236 -0
  258. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts +139 -59
  259. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts.map +1 -1
  260. package/src/shade/renderer/particles/optimizer/particle_program_analysis.js +764 -647
  261. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts +40 -7
  262. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts.map +1 -1
  263. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.js +376 -213
  264. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts +30 -3
  265. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts.map +1 -1
  266. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.js +337 -223
  267. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts +24 -38
  268. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  269. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +166 -206
  270. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts +99 -0
  271. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts.map +1 -0
  272. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.js +313 -0
  273. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts +3 -2
  274. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts.map +1 -1
  275. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.js +198 -197
  276. package/src/shade/renderer/particles/optimizer/pass_eliminate_dead_code.d.ts.map +1 -1
  277. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts +19 -0
  278. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  279. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +678 -561
  280. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts +22 -0
  281. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts.map +1 -0
  282. package/src/shade/renderer/particles/optimizer/pass_inline_constants.js +221 -0
  283. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.d.ts.map +1 -1
  284. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.js +63 -72
  285. package/src/shade/renderer/particles/optimizer/pass_pack_registers.d.ts.map +1 -1
  286. package/src/shade/renderer/particles/optimizer/pass_pack_registers.js +87 -86
  287. package/src/shade/renderer/particles/optimizer/pass_peephole.d.ts.map +1 -1
  288. package/src/shade/renderer/particles/optimizer/pass_peephole.js +392 -271
  289. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.d.ts.map +1 -1
  290. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.js +361 -268
  291. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts +4 -0
  292. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts.map +1 -0
  293. package/src/shade/renderer/particles/optimizer/pass_schedule.js +244 -0
  294. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.d.ts.map +1 -1
  295. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.js +154 -151
  296. package/src/shade/renderer/particles/particle_test_fixtures.d.ts +20 -0
  297. package/src/shade/renderer/particles/particle_test_fixtures.d.ts.map +1 -1
  298. package/src/shade/renderer/particles/particle_test_fixtures.js +229 -203
  299. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts +99 -25
  300. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts.map +1 -1
  301. package/src/shade/renderer/particles/runtime/EmitterRegistry.js +438 -286
  302. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts +109 -0
  303. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts.map +1 -0
  304. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.js +127 -0
  305. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +87 -102
  306. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
  307. package/src/shade/renderer/particles/runtime/ParticleEmitter.js +184 -163
  308. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts +130 -0
  309. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts.map +1 -0
  310. package/src/shade/renderer/particles/runtime/ProgramHeap.js +595 -0
  311. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts +91 -0
  312. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts.map +1 -0
  313. package/src/shade/renderer/particles/runtime/ProgramPlacement.js +106 -0
  314. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +5 -2
  315. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
  316. package/src/shade/renderer/particles/runtime/create_particle_effect.js +22 -19
  317. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts +28 -0
  318. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts.map +1 -0
  319. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.js +136 -0
  320. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts +35 -0
  321. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts.map +1 -0
  322. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.js +133 -0
  323. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts +18 -9
  324. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  325. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +20 -80
  326. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts +33 -0
  327. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts.map +1 -0
  328. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.js +114 -0
  329. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts +8 -0
  330. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  331. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +33 -101
  332. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts +45 -0
  333. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts.map +1 -0
  334. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.js +123 -0
  335. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts +51 -0
  336. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts.map +1 -0
  337. package/src/shade/renderer/particles/sort/graph_particle_sort.js +120 -0
  338. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts +6 -0
  339. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts.map +1 -0
  340. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.js +16 -0
  341. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +4 -1
  342. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  343. package/src/shade/renderer/particles/vm/ParticleVMReference.js +261 -140
  344. package/src/shade/renderer/particles/vm/chunk_particle_vm.d.ts.map +1 -1
  345. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +217 -255
  346. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts +43 -0
  347. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts.map +1 -0
  348. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.js +55 -0
  349. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts +62 -0
  350. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts.map +1 -0
  351. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.js +86 -0
  352. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts +22 -0
  353. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts.map +1 -0
  354. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.js +32 -0
  355. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts +9 -0
  356. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts.map +1 -0
  357. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.js +19 -0
  358. package/src/shade/renderer/shader/chunk/noise/psrdnoise/README.md +65 -0
  359. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts +24 -0
  360. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts.map +1 -0
  361. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.js +83 -0
  362. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts +25 -0
  363. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts.map +1 -0
  364. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.js +42 -0
  365. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts +31 -0
  366. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts.map +1 -0
  367. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.js +80 -0
  368. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts +26 -0
  369. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts.map +1 -0
  370. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.js +109 -0
  371. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts +38 -0
  372. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts.map +1 -0
  373. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.js +129 -0
  374. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts +21 -0
  375. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts.map +1 -0
  376. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.js +35 -0
  377. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts +19 -0
  378. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts.map +1 -0
  379. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.js +28 -0
  380. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts +13 -0
  381. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts.map +1 -0
  382. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.js +22 -0
  383. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts +22 -0
  384. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts.map +1 -0
  385. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.js +59 -0
  386. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts +23 -0
  387. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts.map +1 -0
  388. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.js +74 -0
  389. package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.d.ts +7 -0
  390. package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.d.ts.map +1 -1
  391. package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.js +7 -0
  392. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts +34 -1
  393. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts.map +1 -1
  394. package/src/shade/wgsl/emulator/ComputeShaderEmulator.js +41 -1
  395. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts +3 -2
  396. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts.map +1 -1
  397. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +49 -10
  398. package/src/view/minimap/dom/MinimapCameraView.d.ts.map +1 -1
  399. package/src/view/minimap/dom/MinimapCameraView.js +4 -9
  400. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts +0 -15
  401. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts.map +0 -1
  402. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts +0 -45
  403. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts.map +0 -1
  404. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts +0 -24
  405. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts.map +0 -1
  406. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts +0 -19
  407. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts.map +0 -1
  408. package/src/engine/ecs/transform/__probe.d.ts +0 -4
  409. package/src/engine/ecs/transform/__probe.d.ts.map +0 -1
  410. package/src/engine/ecs/util/hideEntityGracefully.d.ts +0 -9
  411. package/src/engine/ecs/util/hideEntityGracefully.d.ts.map +0 -1
  412. package/src/engine/sound/simulation/core/OcclusionSolver.js +0 -112
  413. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts +0 -27
  414. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts.map +0 -1
  415. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts +0 -14
  416. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +0 -1
  417. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts +0 -156
  418. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts.map +0 -1
  419. package/src/shade/renderer/particles/runtime/Emitter.d.ts +0 -219
  420. package/src/shade/renderer/particles/runtime/Emitter.d.ts.map +0 -1
  421. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts +0 -77
  422. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts.map +0 -1
  423. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts +0 -89
  424. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts.map +0 -1
  425. package/src/shade/renderer/particles/runtime/ProgramArena.js +0 -155
  426. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts +0 -15
  427. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts.map +0 -1
  428. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts +0 -34
  429. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts.map +0 -1
  430. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts +0 -32
  431. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts.map +0 -1
@@ -0,0 +1,860 @@
1
+ import { assert } from "../../../../../core/assert.js";
2
+ import { VM_ISA, VM_MAX_WIDTH, VM_OP, swizzle_selector } from "../../isa/ParticleVMISA.js";
3
+ import { vm_semantics_of } from "../particle_vm_semantics.js";
4
+ import { PV_GROUP } from "./particle_vector_groups.js";
5
+ import { PV } from "./particle_vector_graph.js";
6
+
7
+ /**
8
+ * Turn the chosen values into the groups {@link ./particle_vector_lower.js} schedules, colours and
9
+ * emits.
10
+ *
11
+ * There is less here than the job sounds like, and that is the point. A packer over a graph of lanes
12
+ * needs superword grouping, lane-order search and a demand solver, all of it re-deriving a
13
+ * vectorisation that its own lift threw away — that was measured at eight hundred lines and it still
14
+ * lost. Here a value IS a vector, one value is one instruction, and every operand reads lanes of ONE
15
+ * value, so it is a base and a swizzle, always, with nothing to arrange and nothing to copy.
16
+ *
17
+ * The copies that remain are the ones the program really has: a `BUILD`, which is a vector assembled
18
+ * out of lanes of several others. Those come from a frontend's `vec4` constructor and from a load of
19
+ * record words that different stores wrote, and each is one `MOV` per lane until something coalesces
20
+ * them away.
21
+ */
22
+
23
+ /**
24
+ * @param {import("./particle_vector_graph.js").ParticleVectorGraph} graph
25
+ * @param {import("./particle_vector_lift.js").ParticleLiftedVectorSection} section
26
+ * @param {import("./particle_vector_extract.js").ParticleValueExtraction} extraction
27
+ * @returns {import("./particle_vector_groups.js").ParticleValuePacking}
28
+ */
29
+ export function pack_particle_vectors(graph, section, extraction) {
30
+ const demand = measure_demand(graph, section, extraction);
31
+ /** @type {import("./particle_vector_groups.js").ParticleValueGroup[]} */
32
+ const groups = [];
33
+ /** @type {Map<number, number>} per class, the group that computes it */
34
+ const of = new Map();
35
+ /** @type {Map<number, {group: number, lane: number}>} */
36
+ const placement = new Map();
37
+
38
+ const add = (shape) => {
39
+ const group = {
40
+ index: groups.length,
41
+ kind: PV_GROUP.COMPUTE,
42
+ op: VM_OP.NOP,
43
+ width: 1,
44
+ span: 0,
45
+ values: [],
46
+ raw: [-1, -1, -1],
47
+ operands: [],
48
+ gather: [],
49
+ constants: [],
50
+ draw: -1,
51
+ coalition: -1,
52
+ sequence: 0,
53
+ ...shape,
54
+ };
55
+
56
+ groups.push(group);
57
+
58
+ return group;
59
+ };
60
+
61
+ /**
62
+ * A source operand: lanes of one value, read where that value lives.
63
+ *
64
+ * @param {number} slot
65
+ * @param {number} value
66
+ * @param {number} pattern
67
+ * @param {number} lanes how many lanes the instruction reads of it
68
+ * @param {number} where the reading instruction's place in the section
69
+ * @returns {import("./particle_vector_groups.js").ParticleGroupOperand}
70
+ */
71
+ const operand = (slot, value, pattern, lanes, where) => {
72
+ const literals = graph.constants_of(value);
73
+
74
+ if (literals !== null) {
75
+ const read = [];
76
+
77
+ for (let lane = 0; lane < lanes; lane++) {
78
+ read.push(literals[Math.min(swizzle_selector(pattern, lane), literals.length - 1)]);
79
+ }
80
+
81
+ // Every lane the same literal is an operand with no instruction and no slot behind it,
82
+ // which is what most literals in an effect are.
83
+ if (read.every(one => Object.is(one, read[0]))) {
84
+ return { slot, group: -1, lanes: [], reads: [], value: read[0] };
85
+ }
86
+
87
+ // And one that is not uniform is a pool run made HERE, for this operand: sequenced at the
88
+ // instruction that reads it, not where the literal first appeared. Those are the same
89
+ // place for a literal used once near the top and thousands of instructions apart for a
90
+ // noise gradient, and on `curl4` the difference is the whole register file — sixty
91
+ // three-lane gradients loaded at the front and held to their single distant use are 185
92
+ // of its 210 slots. A literal is the cheapest thing there is to recompute; it should
93
+ // never be the thing that is kept.
94
+ const run = add({
95
+ kind: PV_GROUP.CONSTANT,
96
+ op: VM_OP.LOAD_CONST,
97
+ width: read.length,
98
+ span: read.length,
99
+ values: read.map(() => -1),
100
+ constants: read,
101
+ sequence: where,
102
+ });
103
+
104
+ return {
105
+ slot,
106
+ group: run.index,
107
+ lanes: read.map((unused, lane) => lane),
108
+ reads: read.map((unused, lane) => ({ group: run.index, lane })),
109
+ value: 0,
110
+ };
111
+ }
112
+
113
+ const place = of.get(graph.find(value));
114
+
115
+ assert.defined(place, `particle vector packing: class ${value} was never computed`);
116
+
117
+ const read = [];
118
+
119
+ for (let lane = 0; lane < lanes; lane++) {
120
+ // A lane past the value's own width is one the instruction does not really read — a
121
+ // `CROSS` asked for four lanes reads three — so it is clamped rather than invented.
122
+ read.push(place.base + Math.min(swizzle_selector(pattern, lane), place.width - 1));
123
+ }
124
+
125
+ const group = place.group;
126
+
127
+ return { slot, group, lanes: read, reads: read.map(lane => ({ group, lane })), value: 0 };
128
+ };
129
+
130
+ merge_draws(graph, extraction, demand, of, add);
131
+
132
+ for (const id of extraction.order) {
133
+ if (of.has(id)) {
134
+ continue;
135
+ }
136
+
137
+ const node = graph.node(extraction.choice.get(id));
138
+ const sequence = id;
139
+ const width = demand.get(id) ?? 0;
140
+
141
+ // Nothing reads it: an instruction that need not be written at all.
142
+ if (width === 0) {
143
+ continue;
144
+ }
145
+
146
+ // A literal is not an instruction until an operand needs it in a register, and most never
147
+ // do — see `operand` above.
148
+ if (node.op === PV.CONST) {
149
+ continue;
150
+ }
151
+
152
+ if (node.op === PV.ATTR) {
153
+ const group = add({
154
+ op: VM_OP.LOAD_ATTR,
155
+ width,
156
+ span: width,
157
+ raw: [node.imm[0], width, -1],
158
+ sequence,
159
+ });
160
+
161
+ of.set(id, { group: group.index, base: 0, width: group.span });
162
+ continue;
163
+ }
164
+
165
+ if (node.op === PV.BUILD) {
166
+ const group = add({
167
+ kind: PV_GROUP.GATHER,
168
+ op: VM_OP.MOV,
169
+ width: 1,
170
+ span: width,
171
+ gather: node.children.slice(0, width).map((child, lane) => {
172
+ const literals = graph.constants_of(child);
173
+
174
+ if (literals !== null) {
175
+ return { group: -1, lane: 0, value: literals[Math.min(node.patterns[lane], literals.length - 1)] };
176
+ }
177
+
178
+ const from = of.get(graph.find(child));
179
+
180
+ assert.defined(from, `particle vector packing: class ${child} was never computed`);
181
+
182
+ return {
183
+ group: from.group,
184
+ lane: from.base + Math.min(node.patterns[lane], from.width - 1),
185
+ value: 0,
186
+ };
187
+ }),
188
+ sequence,
189
+ });
190
+
191
+ of.set(id, { group: group.index, base: 0, width: group.span });
192
+ continue;
193
+ }
194
+
195
+ const reads = narrowable(node) ? width : node.operand_width;
196
+ const group = add({
197
+ op: node.op,
198
+ width: Math.max(reads, VM_ISA.get(node.op).min_width),
199
+ span: narrowable(node) ? width : node.width,
200
+ raw: raw_fields(node),
201
+ draw: node.op === VM_OP.RANDOM ? node.imm[0] : -1,
202
+ sequence,
203
+ });
204
+
205
+ for (let source = 0; source < node.children.length; source++) {
206
+ group.operands.push(operand(
207
+ slot_of(node, source),
208
+ node.children[source],
209
+ node.patterns[source],
210
+ node.op === VM_OP.CURVE ? 1 : group.width,
211
+ sequence
212
+ ));
213
+ }
214
+
215
+ of.set(id, { group: group.index, base: 0, width: group.span });
216
+ }
217
+
218
+ for (const id of extraction.order) {
219
+ if (of.has(id)) {
220
+ placement.set(id, { group: of.get(id).group, lane: of.get(id).base });
221
+ }
222
+ }
223
+
224
+ for (const chunk of chunk_stores(section.stores)) {
225
+ const group = add({
226
+ kind: PV_GROUP.STORE,
227
+ op: VM_OP.STORE_ATTR,
228
+ width: chunk.length,
229
+ span: 0,
230
+ raw: [-1, chunk[0].word, chunk.length],
231
+ sequence: Math.max(...chunk.map(store => graph.find(store.place.value))),
232
+ });
233
+
234
+ group.operands.push(store_operand(graph, of, groups, chunk, add, group.sequence));
235
+ }
236
+
237
+ for (const kill of section.kills) {
238
+ const group = add({
239
+ kind: PV_GROUP.KILL,
240
+ op: VM_OP.KILL,
241
+ width: 1,
242
+ span: 0,
243
+ sequence: graph.find(kill.value),
244
+ });
245
+
246
+ const literals = graph.constants_of(kill.value);
247
+
248
+ if (literals !== null) {
249
+ group.operands.push({ slot: 0, group: -1, lanes: [], reads: [], value: literals[kill.lane] });
250
+ continue;
251
+ }
252
+
253
+ const from = of.get(graph.find(kill.value));
254
+
255
+ group.operands.push({
256
+ slot: 0,
257
+ group: from.group,
258
+ lanes: [from.base + kill.lane],
259
+ reads: [{ group: from.group, lane: from.base + kill.lane }],
260
+ value: 0,
261
+ });
262
+ }
263
+
264
+ return dissolve_builds({ groups, placement, coalitions: [] }, add);
265
+ }
266
+
267
+ /**
268
+ * The widest stretch of the file a coalition may claim — the runs it lays end to end, added up.
269
+ *
270
+ * A ceiling rather than a tuned number: swept over the library's effects, 4 costs `integrate` four
271
+ * instructions (a four-lane run plus a one-lane pool run is five slots) and everything from 6 upwards
272
+ * is identical. What actually limits a coalition is that the lanes one operand wants must fall inside
273
+ * ONE window of four, which no amount of room relaxes.
274
+ *
275
+ * @type {number}
276
+ */
277
+ export const PARTICLE_VECTOR_COALITION_SLOTS = 8;
278
+
279
+ /**
280
+ * Get rid of the copies by laying the runs where the copy was going to put them.
281
+ *
282
+ * A `BUILD` is a vector assembled from lanes of several values, and it is the only thing in this
283
+ * optimizer that costs an instruction without computing anything. But an operand can read any four
284
+ * consecutive slots: so if the runs it draws from are laid END TO END, and the lanes it wants happen
285
+ * to fall inside one window of four, whoever reads the `BUILD` can read those slots directly and the
286
+ * copies are simply not needed. Nothing about a destination says where it has to be — this optimizer
287
+ * chooses.
288
+ *
289
+ * That is the same thing a pass pipeline achieves by RETARGETING the instruction that feeds a copy,
290
+ * which is what `../pass_propagate_copies.js` does to the 1,227 `MOV`s a frontend emits for its
291
+ * vector constructors. Said in terms of layout rather than of rewriting, because here the layout is
292
+ * not yet decided and there is nothing to rewrite.
293
+ *
294
+ * A literal lane is a run too. Most of what a frontend's vector constructors build is three computed
295
+ * lanes and a constant `w` — `[x, y, z, 0]`, `[x, y, z, 1]` — and laying one pool run of the literals
296
+ * beside the computed ones turns four copies into a single `LOAD_CONST`. That is where the whole of
297
+ * `simplex4`'s remaining thirty copies were.
298
+ *
299
+ * Heaviest first, since a `BUILD` of four lanes is worth four copies and one of two is worth two, and
300
+ * a run may only be in one coalition.
301
+ *
302
+ * @param {import("./particle_vector_groups.js").ParticleValuePacking} packing
303
+ * @param {function(object): import("./particle_vector_groups.js").ParticleValueGroup} add
304
+ * @returns {import("./particle_vector_groups.js").ParticleValuePacking}
305
+ */
306
+ function dissolve_builds(packing, add) {
307
+ const { groups } = packing;
308
+ /** @type {number[][]} */
309
+ const coalitions = [];
310
+ /** @type {Map<number, {anchor: number, slots: number[], reads: Array<{group: number, lane: number}>}>} */
311
+ const dissolved = new Map();
312
+
313
+ const builds = groups
314
+ .filter(group => group.kind === PV_GROUP.GATHER)
315
+ .sort((a, b) => b.gather.length - a.gather.length || a.index - b.index);
316
+
317
+ for (const build of builds) {
318
+ // Where this build's lanes come from: one entry per run it reads, and one more for its
319
+ // literal lanes, whose run does not exist yet and is only made if the layout works out.
320
+ /** @type {Array<{group: number, span: number}>} */
321
+ const plan = [];
322
+ /** @type {number[]} */
323
+ const literals = [];
324
+ const wanted = build.gather.map(source => {
325
+ if (source.group < 0) {
326
+ let lane = literals.findIndex(one => Object.is(one, source.value));
327
+
328
+ if (lane < 0) {
329
+ lane = literals.push(source.value) - 1;
330
+ }
331
+
332
+ return { member: -1, lane };
333
+ }
334
+
335
+ let member = plan.findIndex(one => one.group === source.group);
336
+
337
+ if (member < 0) {
338
+ member = plan.push({ group: source.group, span: groups[source.group].span }) - 1;
339
+ }
340
+
341
+ return { member, lane: source.lane };
342
+ });
343
+ const pool = literals.length > 0 ? plan.push({ group: -1, span: literals.length }) - 1 : -1;
344
+
345
+ for (const source of wanted) {
346
+ if (source.member < 0) {
347
+ source.member = pool;
348
+ }
349
+ }
350
+
351
+ if (plan.length === 0 || plan.length > VM_MAX_WIDTH) {
352
+ continue;
353
+ }
354
+
355
+ // A build feeding a build is not something the rewrite below can follow — the reader it
356
+ // would have to retarget is another gather.
357
+ if (plan.some(one => one.group >= 0 && groups[one.group].kind === PV_GROUP.GATHER)) {
358
+ continue;
359
+ }
360
+
361
+ // A run may only be in one coalition — but a build reading a run that is already in one is
362
+ // not thereby lost. Its lanes may already lie where they need to: `simplex4` builds
363
+ // `[x, y, z, 0]`, `[y, z, 0]` and `[z, 0, 0]` from the same vector, and one layout serves all
364
+ // three. So being coalesced is a reason to JOIN, not a reason to refuse.
365
+ if (plan.some(one => one.group >= 0 && groups[one.group].coalition >= 0)) {
366
+ const joined = join_coalition(groups, coalitions, plan, wanted, literals, pool);
367
+
368
+ if (joined !== null) {
369
+ dissolved.set(build.index, joined);
370
+ }
371
+
372
+ continue;
373
+ }
374
+
375
+ // The literals' run is an instruction of its own, so it has to pay for itself. Joining an
376
+ // existing coalition costs nothing, which is why this is asked only of a fresh one.
377
+ if (build.gather.length <= (pool < 0 ? 0 : 1)) {
378
+ continue;
379
+ }
380
+
381
+ if (plan.reduce((slots, one) => slots + one.span, 0) > PARTICLE_VECTOR_COALITION_SLOTS) {
382
+ continue;
383
+ }
384
+
385
+ const chosen = arrangements(plan.map((unused, member) => member))
386
+ .map(arrangement => ({ arrangement, slots: window_of(plan, arrangement, wanted) }))
387
+ .find(candidate => candidate.slots !== null);
388
+
389
+ if (chosen === undefined) {
390
+ continue;
391
+ }
392
+
393
+ if (pool >= 0) {
394
+ plan[pool].group = add({
395
+ kind: PV_GROUP.CONSTANT,
396
+ op: VM_OP.LOAD_CONST,
397
+ width: literals.length,
398
+ span: literals.length,
399
+ values: literals.map(() => -1),
400
+ constants: literals,
401
+ sequence: build.sequence,
402
+ }).index;
403
+ }
404
+
405
+ const arrangement = chosen.arrangement.map(member => plan[member].group);
406
+ const index = coalitions.length;
407
+
408
+ coalitions.push(arrangement);
409
+
410
+ for (const group of arrangement) {
411
+ groups[group].coalition = index;
412
+ }
413
+
414
+ dissolved.set(build.index, {
415
+ anchor: arrangement[0],
416
+ slots: chosen.slots,
417
+ reads: wanted.map(source => ({ group: plan[source.member].group, lane: source.lane })),
418
+ });
419
+ }
420
+
421
+ if (dissolved.size === 0) {
422
+ return packing;
423
+ }
424
+
425
+ // Whoever read the `BUILD` now reads the runs where they lie.
426
+ for (const group of groups) {
427
+ for (const operand of group.operands) {
428
+ const gone = dissolved.get(operand.group);
429
+
430
+ if (gone === undefined) {
431
+ continue;
432
+ }
433
+
434
+ operand.reads = operand.lanes.map(lane => gone.reads[Math.min(lane, gone.reads.length - 1)]);
435
+ operand.lanes = operand.lanes.map(lane => gone.slots[Math.min(lane, gone.slots.length - 1)]);
436
+ operand.group = gone.anchor;
437
+ }
438
+
439
+ // A build may read a build — a four-wide load of a three-component attribute is a build, and
440
+ // anything assembled from it reads one. Those lanes are copies still to be made, so they take
441
+ // the run each lane ended up in rather than a window into it.
442
+ group.gather = group.gather.map(source => {
443
+ const gone = dissolved.get(source.group);
444
+
445
+ return gone === undefined
446
+ ? source
447
+ : { ...source, ...gone.reads[Math.min(source.lane, gone.reads.length - 1)] };
448
+ });
449
+ }
450
+
451
+ return renumber(groups, coalitions, dissolved);
452
+ }
453
+
454
+ /**
455
+ * Whether a `BUILD` can read the layout an existing coalition already has, without changing it.
456
+ *
457
+ * Nothing is laid out here and nothing is added: the question is only whether the lanes this build
458
+ * wants happen to fall inside one window of four of a layout that has already been decided. Its
459
+ * literal lanes have to be found in a pool run that coalition already holds — extending one would
460
+ * move everything after it, and the coalition's other readers have been told where their slots are.
461
+ *
462
+ * @param {import("./particle_vector_groups.js").ParticleValueGroup[]} groups
463
+ * @param {number[][]} coalitions
464
+ * @param {Array<{group: number, span: number}>} plan
465
+ * @param {Array<{member: number, lane: number}>} wanted
466
+ * @param {number[]} literals
467
+ * @param {number} pool which member of `plan` holds the literal lanes, or -1
468
+ * @returns {{anchor: number, slots: number[], reads: Array<{group: number, lane: number}>}|null}
469
+ */
470
+ function join_coalition(groups, coalitions, plan, wanted, literals, pool) {
471
+ const anchored = plan.find(one => one.group >= 0 && groups[one.group].coalition >= 0);
472
+ const index = groups[anchored.group].coalition;
473
+ const members = coalitions[index];
474
+
475
+ // Every run it reads has to be in that same coalition, or the slots are not one stretch at all.
476
+ if (plan.some(one => one.group >= 0 && groups[one.group].coalition !== index)) {
477
+ return null;
478
+ }
479
+
480
+ const offset = new Map();
481
+
482
+ let at = 0;
483
+
484
+ for (const group of members) {
485
+ offset.set(group, at);
486
+ at += groups[group].span;
487
+ }
488
+
489
+ /** @type {Array<{group: number, lane: number}>} where each literal already sits, if it does */
490
+ const found = literals.map(value => {
491
+ for (const group of members) {
492
+ if (groups[group].kind !== PV_GROUP.CONSTANT) {
493
+ continue;
494
+ }
495
+
496
+ const lane = groups[group].constants.findIndex(one => Object.is(one, value));
497
+
498
+ if (lane >= 0) {
499
+ return { group, lane };
500
+ }
501
+ }
502
+
503
+ return null;
504
+ });
505
+
506
+ if (pool >= 0 && found.some(one => one === null)) {
507
+ return null;
508
+ }
509
+
510
+ const reads = wanted.map(source => (source.member === pool
511
+ ? found[source.lane]
512
+ : { group: plan[source.member].group, lane: source.lane }));
513
+ const slots = reads.map(read => offset.get(read.group) + read.lane);
514
+
515
+ if (Math.max(...slots) - Math.min(...slots) >= VM_MAX_WIDTH) {
516
+ return null;
517
+ }
518
+
519
+ return { anchor: members[0], slots, reads };
520
+ }
521
+
522
+ /**
523
+ * Where a `BUILD`'s lanes land if its runs are laid out in this order, and whether that is somewhere
524
+ * one operand can reach.
525
+ *
526
+ * @param {Array<{group: number, span: number}>} plan
527
+ * @param {number[]} arrangement the members of `plan`, in the order they would be laid out
528
+ * @param {Array<{member: number, lane: number}>} wanted
529
+ * @returns {number[]|null} the slot each lane sits at, measured from the first run's base
530
+ */
531
+ function window_of(plan, arrangement, wanted) {
532
+ const offset = new Map();
533
+
534
+ let at = 0;
535
+
536
+ for (const member of arrangement) {
537
+ offset.set(member, at);
538
+ at += plan[member].span;
539
+ }
540
+
541
+ const slots = wanted.map(source => offset.get(source.member) + source.lane);
542
+
543
+ return Math.max(...slots) - Math.min(...slots) < VM_MAX_WIDTH ? slots : null;
544
+ }
545
+
546
+ /**
547
+ * Drop the groups that dissolved and close the gap in the numbering.
548
+ *
549
+ * @param {import("./particle_vector_groups.js").ParticleValueGroup[]} groups
550
+ * @param {number[][]} coalitions
551
+ * @param {Map<number, object>} dissolved
552
+ * @returns {import("./particle_vector_groups.js").ParticleValuePacking}
553
+ */
554
+ function renumber(groups, coalitions, dissolved) {
555
+ const kept = groups.filter(group => !dissolved.has(group.index));
556
+ const moved = new Map();
557
+
558
+ kept.forEach((group, index) => moved.set(group.index, index));
559
+
560
+ for (const group of kept) {
561
+ group.index = moved.get(group.index);
562
+
563
+ for (const operand of group.operands) {
564
+ if (operand.group >= 0) {
565
+ operand.group = moved.get(operand.group);
566
+ operand.reads = operand.reads.map(read => ({ group: moved.get(read.group), lane: read.lane }));
567
+ }
568
+ }
569
+
570
+ group.gather = group.gather.map(source => (source.group >= 0
571
+ ? { ...source, group: moved.get(source.group) }
572
+ : source));
573
+ }
574
+
575
+ return {
576
+ groups: kept,
577
+ placement: new Map(),
578
+ coalitions: coalitions.map(members => members.map(group => moved.get(group))),
579
+ };
580
+ }
581
+
582
+ /**
583
+ * Every order a handful of runs could be laid out in. At most four of them, so at most twenty-four.
584
+ *
585
+ * @param {number[]} members
586
+ * @returns {number[][]}
587
+ */
588
+ function arrangements(members) {
589
+ if (members.length <= 1) {
590
+ return [members];
591
+ }
592
+
593
+ const out = [];
594
+
595
+ for (let i = 0; i < members.length; i++) {
596
+ const rest = members.filter((unused, j) => j !== i);
597
+
598
+ for (const tail of arrangements(rest)) {
599
+ out.push([members[i], ...tail]);
600
+ }
601
+ }
602
+
603
+ return out;
604
+ }
605
+
606
+ /**
607
+ * The value a store writes, as one operand — or, where its words come from lanes of several values,
608
+ * the run a `BUILD` has to make first.
609
+ *
610
+ * @param {import("./particle_vector_graph.js").ParticleVectorGraph} graph
611
+ * @param {Map<number, number>} of
612
+ * @param {import("./particle_vector_groups.js").ParticleValueGroup[]} groups
613
+ * @param {Array<{word: number, place: {value: number, lane: number}}>} chunk
614
+ * @param {function(object): import("./particle_vector_groups.js").ParticleValueGroup} add
615
+ * @param {number} where the storing instruction's place in the section
616
+ * @returns {import("./particle_vector_groups.js").ParticleGroupOperand}
617
+ */
618
+ function store_operand(graph, of, groups, chunk, add, where) {
619
+ const first = graph.find(chunk[0].place.value);
620
+
621
+ assert.ok(
622
+ chunk.every(store => graph.find(store.place.value) === first),
623
+ "particle vector packing: a store chunk should have been cut where its value changes"
624
+ );
625
+
626
+ const literals = graph.constants_of(first);
627
+
628
+ if (literals !== null) {
629
+ const read = chunk.map(store => literals[Math.min(store.place.lane, literals.length - 1)]);
630
+
631
+ if (read.every(one => Object.is(one, read[0]))) {
632
+ return { slot: 0, group: -1, lanes: [], reads: [], value: read[0] };
633
+ }
634
+
635
+ const run = add({
636
+ kind: PV_GROUP.CONSTANT,
637
+ op: VM_OP.LOAD_CONST,
638
+ width: read.length,
639
+ span: read.length,
640
+ values: read.map(() => -1),
641
+ constants: read,
642
+ sequence: where,
643
+ });
644
+
645
+ return {
646
+ slot: 0,
647
+ group: run.index,
648
+ lanes: read.map((unused, lane) => lane),
649
+ reads: read.map((unused, lane) => ({ group: run.index, lane })),
650
+ value: 0,
651
+ };
652
+ }
653
+
654
+ const place = of.get(first);
655
+ const group = place.group;
656
+ const lanes = chunk.map(store => place.base + store.place.lane);
657
+
658
+ return { slot: 0, group, lanes, reads: lanes.map(lane => ({ group, lane })), value: 0 };
659
+ }
660
+
661
+ /**
662
+ * Which record words go in one `STORE_ATTR`: consecutive words, taken from one value, no more than
663
+ * the four an operand spans — and cut where the original stores cut, so that a `vec3` position and a
664
+ * `vec4` colour in adjacent words stay two stores.
665
+ *
666
+ * @param {Array<{word: number, place: {value: number, lane: number}, origin: number}>} stores
667
+ * @returns {Array<Array<{word: number, place: {value: number, lane: number}, origin: number}>>}
668
+ */
669
+ export function chunk_stores(stores) {
670
+ const chunks = [];
671
+
672
+ for (const store of stores) {
673
+ const chunk = chunks[chunks.length - 1];
674
+ const last = chunk?.[chunk.length - 1];
675
+
676
+ if (chunk !== undefined
677
+ && chunk.length < VM_MAX_WIDTH
678
+ && last.word + 1 === store.word
679
+ && last.origin === store.origin
680
+ && last.place.value === store.place.value) {
681
+ chunk.push(store);
682
+ continue;
683
+ }
684
+
685
+ chunks.push([store]);
686
+ }
687
+
688
+ return chunks;
689
+ }
690
+
691
+ /**
692
+ * @param {import("./particle_vector_graph.js").ParticleVectorNode} node
693
+ * @returns {number[]} the static operand words the opcode reads for itself
694
+ */
695
+ function raw_fields(node) {
696
+ switch (node.op) {
697
+ case VM_OP.LOAD_BUILTIN:
698
+ case VM_OP.CURVE:
699
+ return [node.imm[0], -1, -1];
700
+
701
+ default:
702
+ return [-1, -1, -1];
703
+ }
704
+ }
705
+
706
+ /**
707
+ * @param {import("./particle_vector_graph.js").ParticleVectorNode} node
708
+ * @param {number} source which of the node's sources
709
+ * @returns {number} the instruction source slot it goes in
710
+ */
711
+ function slot_of(node, source) {
712
+ // `CURVE` takes its handle in slot 0 and the value it samples in slot 1; everything else fills
713
+ // its slots in order.
714
+ return node.op === VM_OP.CURVE ? source + 1 : source;
715
+ }
716
+
717
+ /**
718
+ * How many lanes of each value anything actually reads.
719
+ *
720
+ * Walked backwards from what the section is obliged to produce, so a value nothing reads gets zero —
721
+ * which is dead-code elimination — and one whose upper lanes nothing reads gets fewer than it has,
722
+ * which is narrowing. Both fall out of the same number, and both are worth having twice over here:
723
+ * a narrower instruction is a smaller run as well as, sometimes, a cheaper one.
724
+ *
725
+ * Only the opcodes the ISA says may drop their trailing lanes are narrowed. A `RANDOM` may not — its
726
+ * width IS how many draws it takes — and neither may a reduction, which reads every lane to write
727
+ * one.
728
+ *
729
+ * @param {import("./particle_vector_graph.js").ParticleVectorGraph} graph
730
+ * @param {import("./particle_vector_lift.js").ParticleLiftedVectorSection} section
731
+ * @param {import("./particle_vector_extract.js").ParticleValueExtraction} extraction
732
+ * @returns {Map<number, number>} per class, the lanes of it that are read
733
+ */
734
+ function measure_demand(graph, section, extraction) {
735
+ /** @type {Map<number, number>} */
736
+ const demand = new Map();
737
+ const want = (value, lanes) => {
738
+ const id = graph.find(value);
739
+
740
+ demand.set(id, Math.max(demand.get(id) ?? 0, lanes));
741
+ };
742
+
743
+ for (const store of section.stores) {
744
+ want(store.place.value, store.place.lane + 1);
745
+ }
746
+
747
+ for (const kill of section.kills) {
748
+ want(kill.value, kill.lane + 1);
749
+ }
750
+
751
+ // A draw is taken whether or not it is read, so a `RANDOM` is wanted whole.
752
+ for (const draw of section.draws) {
753
+ want(draw, graph.width_of(draw));
754
+ }
755
+
756
+ for (let i = extraction.order.length - 1; i >= 0; i--) {
757
+ const id = extraction.order[i];
758
+ const wanted = demand.get(id) ?? 0;
759
+
760
+ if (wanted === 0) {
761
+ continue;
762
+ }
763
+
764
+ const node = graph.node(extraction.choice.get(id));
765
+ const width = narrowable(node) ? Math.min(wanted, node.width) : node.width;
766
+
767
+ if (node.op === PV.BUILD) {
768
+ for (let lane = 0; lane < width; lane++) {
769
+ want(node.children[lane], node.patterns[lane] + 1);
770
+ }
771
+
772
+ continue;
773
+ }
774
+
775
+ const reads = node.op === VM_OP.CURVE
776
+ ? 1
777
+ : Math.max(narrowable(node) ? width : node.operand_width, VM_ISA.get(node.op)?.min_width ?? 1);
778
+
779
+ for (let source = 0; source < node.children.length; source++) {
780
+ let highest = 0;
781
+
782
+ for (let lane = 0; lane < reads; lane++) {
783
+ highest = Math.max(highest, swizzle_selector(node.patterns[source], lane) + 1);
784
+ }
785
+
786
+ want(node.children[source], highest);
787
+ }
788
+ }
789
+
790
+ return demand;
791
+ }
792
+
793
+ /**
794
+ * @param {import("./particle_vector_graph.js").ParticleVectorNode} node
795
+ * @returns {boolean} whether dropping its trailing lanes leaves the rest unchanged
796
+ */
797
+ function narrowable(node) {
798
+ if (node.op === PV.ATTR || node.op === PV.BUILD) {
799
+ return true;
800
+ }
801
+
802
+ return vm_semantics_of(node.op).narrowable && node.width === node.operand_width;
803
+ }
804
+
805
+ /**
806
+ * Take consecutive draws in one instruction.
807
+ *
808
+ * A `RANDOM` of width n advances the generator n times and writes n slots, so two `RANDOM.1`s taking
809
+ * draws 4 and 5 compute exactly what one `RANDOM.2` taking draws 4 and 5 computes — the same numbers,
810
+ * in the same order, in two adjacent slots. The graph cannot see this, because a draw is identified by
811
+ * its index and two indices are two values; it is a fact about the instruction rather than about the
812
+ * values, so it belongs here, where instructions are chosen.
813
+ *
814
+ * Only runs that are consecutive with no gap are merged: draws 4 and 6 in one instruction would
815
+ * silently take draw 5 as well and hand the reader the wrong number.
816
+ *
817
+ * @param {import("./particle_vector_graph.js").ParticleVectorGraph} graph
818
+ * @param {import("./particle_vector_extract.js").ParticleValueExtraction} extraction
819
+ * @param {Map<number, number>} demand
820
+ * @param {Map<number, {group: number, base: number, width: number}>} of
821
+ * @param {function(object): import("./particle_vector_groups.js").ParticleValueGroup} add
822
+ */
823
+ function merge_draws(graph, extraction, demand, of, add) {
824
+ const draws = extraction.order
825
+ .filter(id => (demand.get(id) ?? 0) > 0 && graph.node(extraction.choice.get(id)).op === VM_OP.RANDOM)
826
+ .map(id => ({ id, node: graph.node(extraction.choice.get(id)) }))
827
+ .sort((a, b) => a.node.imm[0] - b.node.imm[0]);
828
+
829
+ for (let first = 0; first < draws.length;) {
830
+ let last = first;
831
+ let width = draws[first].node.width;
832
+
833
+ while (last + 1 < draws.length
834
+ && draws[last + 1].node.imm[0] === draws[first].node.imm[0] + width
835
+ && width + draws[last + 1].node.width <= VM_MAX_WIDTH) {
836
+ width += draws[last + 1].node.width;
837
+ last += 1;
838
+ }
839
+
840
+ if (last > first) {
841
+ const group = add({
842
+ op: VM_OP.RANDOM,
843
+ width,
844
+ span: width,
845
+ raw: [-1, -1, -1],
846
+ draw: draws[first].node.imm[0],
847
+ sequence: Math.min(...draws.slice(first, last + 1).map(one => one.id)),
848
+ });
849
+
850
+ let base = 0;
851
+
852
+ for (const one of draws.slice(first, last + 1)) {
853
+ of.set(one.id, { group: group.index, base, width: one.node.width });
854
+ base += one.node.width;
855
+ }
856
+ }
857
+
858
+ first = last + 1;
859
+ }
860
+ }