@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
@@ -7,40 +7,72 @@
7
7
  *
8
8
  * ## Encoding
9
9
  *
10
- * Every instruction is a fixed run of {@link PARTICLE_VM_INSTRUCTION_WORDS} `u32` words:
11
- *
12
- * [ op | width << 8, dst, s0, s1, s2 ]
13
- *
14
- * `dst` is the destination register for value-producing ops. `s0/s1/s2` are usually source register
15
- * indices, but some opcodes reinterpret them as immediates (constant-pool index, attribute word
16
- * offset + component count, curve handle, builtin id, swizzle pattern). The
17
- * `operands` array below documents, per opcode, what each slot means.
18
- *
19
- * Registers hold single floats. An instruction's `width` says how many consecutive registers the
20
- * operation spans, so a `vec3` value is three registers and `ADD` with width 3 adds three lanes.
21
- * Widths are what stop every value costing four registers whether or not it has four components:
22
- * the register file is the interpreter's dominant register cost, and roughly half the lane work in
23
- * a real effect was padding before this existed. A few opcodes ignore the field because their lane
24
- * count is fixed (see {@link VM_WIDTH}), and the reductions write a single lane whatever they read
25
- * (see {@link VM_DEST}).
26
- *
27
- * There is no control flow: conditionals are expressed with CMP + SELECT and destruction with KILL,
28
- * which keeps the interpreter a single flat `switch` inside a bounded loop — friendly to both the
29
- * WGSL emulator and real hardware.
10
+ * Every instruction is {@link PARTICLE_VM_INSTRUCTION_WORDS} `u32` words — the size of one
11
+ * `vec4<u32>`, so the four loads that fetch it are one 16-byte load by the time a shader compiler is
12
+ * done with them:
13
+ *
14
+ * [ op | width << 8 | kinds << 16 | dst << 24, s0, s1, s2 ]
15
+ *
16
+ * Word 0 packs the opcode (bits 0..7), the lane count the operation spans (8..10), the kind of each
17
+ * source (two bits per slot at 16..21) and the destination register (24..31). The other three words
18
+ * are the sources. Registers hold single floats, so a `vec3` value is three consecutive slots and an
19
+ * `ADD` with width 3 adds three lanes; widths are what stop every value costing four registers.
20
+ *
21
+ * ## Operands
22
+ *
23
+ * A source word means what its kind says — see {@link VM_KIND}:
24
+ *
25
+ * - `NONE`: not an operand the VM resolves — an unused slot, or a raw field the opcode reads for
26
+ * itself: an attribute offset, a component count, a builtin id, a curve handle, a pool index.
27
+ * - `REG`: a register operand. Bits 0..7 are the first slot, bits 8..15 a swizzle — see below.
28
+ * - `IMM`: the word IS the `f32` bit pattern of a literal, read in every lane. A scalar or a
29
+ * lane-uniform vector costs no register and no load; only a vector whose lanes differ still goes
30
+ * through the pool with `LOAD_CONST`.
31
+ *
32
+ * The kinds exist so the interpreter can resolve every source the same way, before the opcode runs
33
+ * and without consulting a table: it reads the kind, gathers the lanes, and the opcode body sees
34
+ * three four-lane values whatever they came from. A kind is encoding, not an opcode mode.
35
+ *
36
+ * ## Swizzles
37
+ *
38
+ * Every register operand carries a swizzle: four two-bit lane selectors in bits 8..15, packed as
39
+ * {@link pack_swizzle} packs them, and stored XOR the identity pattern `.xyzw`
40
+ * ({@link VM_SWIZZLE_IDENTITY}). That choice is what makes a bare slot index a register operand:
41
+ * with a zero swizzle byte lane `c` reads slot `base + c`, the run as it lies, and `r5.xxxx` is the
42
+ * same word with the selectors that name lane 0 four times. Lane `c` of an operand reads slot
43
+ * `base + selector[c]`, so an operand reaches as far into the file as its widest selector below the
44
+ * instruction's width — never further, whatever the selectors above the width say.
45
+ *
46
+ * Swizzles are why there is no `SWIZZLE` opcode and no `SCALE`: a permutation or a broadcast is a
47
+ * `MOV` whose source is swizzled, and a vector times a scalar is `MUL v, s.xxxx`. Both were
48
+ * opcodes that existed to do what an operand can now say for itself, which is the direction the
49
+ * ISA takes whenever it can — see {@link VM_OP} for the holes they left.
50
+ *
51
+ * ## Resolution before execution
52
+ *
53
+ * The interpreter reads every source lane before it writes any destination lane. Operands may
54
+ * therefore overlap the destination in any way — `MOV.2 r0, r0.yx` swaps two slots in place — and
55
+ * there is no aliasing rule for a program to break. The reductions write one lane whatever they
56
+ * read (see {@link VM_DEST}); a few opcodes fix their lane count (see {@link VM_WIDTH}).
57
+ *
58
+ * There is no control flow: conditionals are `CMP` + `SELECT` and destruction is `KILL`, which
59
+ * keeps the interpreter a flat `switch` inside a bounded loop.
30
60
  */
31
61
 
62
+ /** Words per instruction. Four, so an instruction is one `vec4<u32>` fetch. @type {number} */
63
+ export const PARTICLE_VM_INSTRUCTION_WORDS = 4;
64
+
32
65
  /**
33
- * Operand slot roles, used for documentation, assembler validation and disassembly.
66
+ * Operand slot roles: what an opcode reads a source slot as. Used for assembler validation,
67
+ * disassembly and for deriving each slot's {@link VM_KIND}.
34
68
  * @enum {string}
35
69
  */
36
70
  export const VM_OPERAND = {
37
71
  /** unused slot (must be 0) */
38
72
  NONE: "none",
39
- /** first register slot of an operand, in `[0, PARTICLE_VM_REGISTER_SLOTS)` */
73
+ /** a register operand — a slot with a swizzle, or a literal */
40
74
  REG: "reg",
41
- /** destination register index */
42
- DST: "dst",
43
- /** constant-pool index */
75
+ /** constant-pool word index */
44
76
  CONST: "const",
45
77
  /** attribute word offset within the record */
46
78
  ATTR_OFFSET: "attr_offset",
@@ -50,8 +82,6 @@ export const VM_OPERAND = {
50
82
  BUILTIN: "builtin",
51
83
  /** curve handle (row index into the animation-curve table) */
52
84
  CURVE: "curve",
53
- /** packed swizzle pattern: four 2-bit lane selectors, lane i in bits `[2i, 2i+2)` */
54
- SWIZZLE: "swizzle",
55
85
  };
56
86
 
57
87
  /**
@@ -99,7 +129,8 @@ export const VM_BUILTIN_COMPONENTS = Object.freeze({
99
129
  });
100
130
 
101
131
  /**
102
- * Opcodes. Numbers are stable wire values — never renumber, only append.
132
+ * Opcodes. Numbers are stable wire values — never renumber, only append. 19 was `SCALE` and 45 was
133
+ * `SWIZZLE`; both are spelled by an operand swizzle now and the numbers stay unused.
103
134
  * @enum {number}
104
135
  */
105
136
  export const VM_OP = {
@@ -121,7 +152,6 @@ export const VM_OP = {
121
152
  MAX: 16,
122
153
  MIX: 17,
123
154
  CLAMP: 18,
124
- SCALE: 19,
125
155
 
126
156
  NEG: 20,
127
157
  ABS: 21,
@@ -135,13 +165,17 @@ export const VM_OP = {
135
165
  POW: 29,
136
166
  STEP: 30,
137
167
  SMOOTHSTEP: 31,
168
+ RSQRT: 32,
169
+ TAN: 33,
170
+ ASIN: 34,
171
+ ACOS: 35,
172
+ ATAN2: 36,
138
173
 
139
174
  DOT: 40,
140
175
  CROSS: 41,
141
176
  LENGTH: 42,
142
177
  NORMALIZE: 43,
143
178
  DISTANCE: 44,
144
- SWIZZLE: 45,
145
179
 
146
180
  CMP_LT: 50,
147
181
  CMP_LE: 51,
@@ -183,65 +217,304 @@ export const VM_DEST = {
183
217
  };
184
218
 
185
219
  /**
186
- * Widest an operation may be. Registers hold scalars; four is what a `vec4` value needs and what
187
- * the two-bit selectors in a `SWIZZLE` pattern can address.
220
+ * Widest an operation may be. Registers hold scalars; four is what a `vec4` value needs and what a
221
+ * two-bit lane selector can address.
188
222
  * @type {number}
189
223
  */
190
224
  export const VM_MAX_WIDTH = 4;
191
225
 
226
+ // --- word 0 -----------------------------------------------------------------------------------
227
+
228
+ /** Mask selecting the opcode out of word 0. @type {number} */
229
+ export const VM_OP_MASK = 0xff;
230
+
231
+ /** Bit position of the width field in word 0. @type {number} */
232
+ export const VM_WIDTH_SHIFT = 8;
233
+
234
+ /** Mask selecting the width out of word 0, once shifted down. @type {number} */
235
+ export const VM_WIDTH_MASK = 0x7;
236
+
192
237
  /**
193
- * Bit position of the width field inside an instruction's first word.
194
- *
195
- * The word holds `op | (width << VM_WIDTH_SHIFT)`. Widths ride in the opcode's spare high bits
196
- * rather than a sixth instruction word: the code section is read through the same storage binding as
197
- * the constants and is fetched every instruction, so a 20% longer stream is the one cost the
198
- * interpreter pays on every single dispatch.
199
- *
238
+ * What a source word means. Wire values; never renumber.
239
+ * @enum {number}
240
+ */
241
+ export const VM_KIND = {
242
+ /** not an operand: an unused slot, or a raw field the opcode reads itself */
243
+ NONE: 0,
244
+ /** a register operand: base slot in bits 0..7, swizzle in bits 8..15 */
245
+ REG: 1,
246
+ /** the `f32` bit pattern of a literal, read in every lane */
247
+ IMM: 2,
248
+ };
249
+
250
+ /**
251
+ * Bit position of the first source's kind in word 0. Source `n` has its two kind bits at
252
+ * `VM_KIND_SHIFT + 2n`, so the three together are bits 16..21.
200
253
  * @type {number}
201
254
  */
202
- export const VM_WIDTH_SHIFT = 8;
255
+ export const VM_KIND_SHIFT = 16;
203
256
 
204
- /** Mask selecting the opcode out of an instruction's first word. @type {number} */
205
- export const VM_OP_MASK = 0xff;
257
+ /** Bits per kind field. @type {number} */
258
+ export const VM_KIND_BITS = 2;
206
259
 
207
- /** Mask selecting the width out of an instruction's first word, once shifted down. @type {number} */
208
- export const VM_WIDTH_MASK = 0x7;
260
+ /** Mask selecting one kind field, once shifted down. @type {number} */
261
+ export const VM_KIND_MASK = 0x3;
262
+
263
+ /** Mask selecting all three kind fields, once shifted down by {@link VM_KIND_SHIFT}. @type {number} */
264
+ const VM_KINDS_MASK = 0x3f;
265
+
266
+ /** Bit position of the destination register in word 0. @type {number} */
267
+ export const VM_DST_SHIFT = 24;
268
+
269
+ /** Mask selecting the destination register out of word 0, once shifted down. @type {number} */
270
+ export const VM_DST_MASK = 0xff;
209
271
 
210
272
  /**
211
- * Pack an opcode and its width into an instruction's first word.
273
+ * Pack word 0.
274
+ *
212
275
  * @param {number} op
213
276
  * @param {number} width lane count, `0..4`
277
+ * @param {number} dst destination register, `0..255`
278
+ * @param {number} kinds the three source kinds as one field — see {@link vm_pack_kinds}
214
279
  * @returns {number}
215
280
  */
216
- export function vm_encode_word0(op, width) {
217
- return ((op & VM_OP_MASK) | ((width & VM_WIDTH_MASK) << VM_WIDTH_SHIFT)) >>> 0;
281
+ export function vm_encode_word0(op, width, dst, kinds) {
282
+ return (
283
+ (op & VM_OP_MASK)
284
+ | ((width & VM_WIDTH_MASK) << VM_WIDTH_SHIFT)
285
+ | ((kinds & VM_KINDS_MASK) << VM_KIND_SHIFT)
286
+ | ((dst & VM_DST_MASK) << VM_DST_SHIFT)
287
+ ) >>> 0;
218
288
  }
219
289
 
220
290
  /**
221
- * @param {number} word0
222
- * @returns {number} opcode
291
+ * The three source kinds as the one field {@link vm_encode_word0} takes.
292
+ *
293
+ * @param {number} s0
294
+ * @param {number} s1
295
+ * @param {number} s2
296
+ * @returns {number}
223
297
  */
298
+ export function vm_pack_kinds(s0, s1, s2) {
299
+ return (
300
+ (s0 & VM_KIND_MASK)
301
+ | ((s1 & VM_KIND_MASK) << VM_KIND_BITS)
302
+ | ((s2 & VM_KIND_MASK) << (2 * VM_KIND_BITS))
303
+ ) >>> 0;
304
+ }
305
+
306
+ /** @param {number} word0 @returns {number} opcode */
224
307
  export function vm_decode_op(word0) {
225
308
  return word0 & VM_OP_MASK;
226
309
  }
227
310
 
311
+ /** @param {number} word0 @returns {number} lane count */
312
+ export function vm_decode_width(word0) {
313
+ return (word0 >>> VM_WIDTH_SHIFT) & VM_WIDTH_MASK;
314
+ }
315
+
316
+ /** @param {number} word0 @returns {number} destination register */
317
+ export function vm_decode_dst(word0) {
318
+ return (word0 >>> VM_DST_SHIFT) & VM_DST_MASK;
319
+ }
320
+
321
+ /** @param {number} word0 @returns {number} the three source kinds as one field */
322
+ export function vm_decode_kinds(word0) {
323
+ return (word0 >>> VM_KIND_SHIFT) & VM_KINDS_MASK;
324
+ }
325
+
228
326
  /**
229
327
  * @param {number} word0
230
- * @returns {number} lane count
328
+ * @param {number} slot source slot, `0..2`
329
+ * @returns {number} a {@link VM_KIND}
231
330
  */
232
- export function vm_decode_width(word0) {
233
- return (word0 >> VM_WIDTH_SHIFT) & VM_WIDTH_MASK;
331
+ export function vm_decode_kind(word0, slot) {
332
+ return (word0 >>> (VM_KIND_SHIFT + slot * VM_KIND_BITS)) & VM_KIND_MASK;
333
+ }
334
+
335
+ /** @returns {number} word 0 with its width replaced */
336
+ export function vm_with_width(word0, width) {
337
+ return ((word0 & ~(VM_WIDTH_MASK << VM_WIDTH_SHIFT)) | ((width & VM_WIDTH_MASK) << VM_WIDTH_SHIFT)) >>> 0;
338
+ }
339
+
340
+ /** @returns {number} word 0 with its destination replaced */
341
+ export function vm_with_dst(word0, dst) {
342
+ return ((word0 & ~(VM_DST_MASK << VM_DST_SHIFT)) | ((dst & VM_DST_MASK) << VM_DST_SHIFT)) >>> 0;
343
+ }
344
+
345
+ /** @returns {number} word 0 with one source's kind replaced */
346
+ export function vm_with_kind(word0, slot, kind) {
347
+ const shift = VM_KIND_SHIFT + slot * VM_KIND_BITS;
348
+
349
+ return ((word0 & ~(VM_KIND_MASK << shift)) | ((kind & VM_KIND_MASK) << shift)) >>> 0;
350
+ }
351
+
352
+ // --- register operands ------------------------------------------------------------------------
353
+
354
+ /**
355
+ * Pack a swizzle pattern from four lane selectors: selector `i` in bits `[2i, 2i+2)`, values
356
+ * 0=x, 1=y, 2=z, 3=w.
357
+ *
358
+ * @param {number} x
359
+ * @param {number} y
360
+ * @param {number} z
361
+ * @param {number} w
362
+ * @returns {number}
363
+ */
364
+ export function pack_swizzle(x, y, z, w) {
365
+ return (x & 3) | ((y & 3) << 2) | ((z & 3) << 4) | ((w & 3) << 6);
366
+ }
367
+
368
+ /**
369
+ * @param {number} pattern
370
+ * @param {number} lane
371
+ * @returns {number} the lane the pattern selects for output lane `lane`
372
+ */
373
+ export function swizzle_selector(pattern, lane) {
374
+ return (pattern >>> (2 * lane)) & 3;
375
+ }
376
+
377
+ /** The pattern that reads a run as it lies: `.xyzw`. @type {number} */
378
+ export const VM_SWIZZLE_IDENTITY = pack_swizzle(0, 1, 2, 3);
379
+
380
+ /** The pattern that reads lane 0 in every lane: `.xxxx`. @type {number} */
381
+ export const VM_SWIZZLE_BROADCAST = pack_swizzle(0, 0, 0, 0);
382
+
383
+ /** Mask selecting the base slot out of a register operand word. @type {number} */
384
+ export const VM_OPERAND_BASE_MASK = 0xff;
385
+
386
+ /** Bit position of the swizzle byte in a register operand word. @type {number} */
387
+ export const VM_OPERAND_SWIZZLE_SHIFT = 8;
388
+
389
+ /**
390
+ * A register operand word: a base slot read through a pattern.
391
+ *
392
+ * The pattern is stored XOR {@link VM_SWIZZLE_IDENTITY}, so the identity is a zero byte and a bare
393
+ * slot index is the run at that slot. Nothing else about the word depends on that choice; read the
394
+ * pattern back with {@link vm_operand_pattern} rather than the bits.
395
+ *
396
+ * @param {number} base first register slot
397
+ * @param {number} [pattern] a packed swizzle; the run as it lies when left out
398
+ * @returns {number}
399
+ */
400
+ export function vm_register(base, pattern = VM_SWIZZLE_IDENTITY) {
401
+ return ((base & VM_OPERAND_BASE_MASK) | (((pattern ^ VM_SWIZZLE_IDENTITY) & 0xff) << VM_OPERAND_SWIZZLE_SHIFT)) >>> 0;
402
+ }
403
+
404
+ /** @param {number} word a register operand @returns {number} its base slot */
405
+ export function vm_operand_base(word) {
406
+ return word & VM_OPERAND_BASE_MASK;
407
+ }
408
+
409
+ /** @param {number} word a register operand @returns {number} its swizzle, as {@link pack_swizzle} packs one */
410
+ export function vm_operand_pattern(word) {
411
+ return ((word >>> VM_OPERAND_SWIZZLE_SHIFT) & 0xff) ^ VM_SWIZZLE_IDENTITY;
412
+ }
413
+
414
+ /**
415
+ * @param {number} word a register operand
416
+ * @param {number} lane
417
+ * @returns {number} the slot lane `lane` of the operand reads
418
+ */
419
+ export function vm_operand_slot(word, lane) {
420
+ return vm_operand_base(word) + swizzle_selector(vm_operand_pattern(word), lane);
421
+ }
422
+
423
+ /** @returns {number} the operand moved to another base, keeping its swizzle */
424
+ export function vm_operand_with_base(word, base) {
425
+ return vm_register(base, vm_operand_pattern(word));
426
+ }
427
+
428
+ /**
429
+ * How far into the file a register operand reaches when `lanes` lanes of it are read: one past the
430
+ * widest selector among them. The selectors above `lanes` are never read and do not count.
431
+ *
432
+ * @param {number} word a register operand
433
+ * @param {number} lanes how many lanes the instruction reads of it
434
+ * @returns {number}
435
+ */
436
+ export function vm_operand_span(word, lanes) {
437
+ const pattern = vm_operand_pattern(word);
438
+
439
+ let span = 1;
440
+
441
+ for (let lane = 0; lane < lanes; lane++) {
442
+ span = Math.max(span, swizzle_selector(pattern, lane) + 1);
443
+ }
444
+
445
+ return span;
234
446
  }
235
447
 
236
448
  /**
237
- * The lane count an opcode actually operates on, given the width its instruction carries.
449
+ * The canonical spelling of a register operand read at `lanes` lanes: the base is the lowest slot
450
+ * any lane reads, and selectors above the lanes read are the identity, since they name nothing. So
451
+ * `r0.yz` is `r1`, `r0.zy` is `r1.yx`, and a broadcast of lane 3 is the run at lane 3 — one
452
+ * spelling per meaning, which is what lets two instructions be compared word for word.
238
453
  *
239
- * Opcodes whose width is fixed ignore the field entirely, so a malformed program cannot talk a
240
- * `CROSS` into reading two lanes.
454
+ * @param {number} word a register operand
455
+ * @param {number} lanes how many lanes the instruction reads of it
456
+ * @returns {number}
457
+ */
458
+ export function vm_canonical_register(word, lanes) {
459
+ const pattern = vm_operand_pattern(word);
460
+
461
+ let lowest = lanes > 0 ? 3 : 0;
462
+
463
+ for (let lane = 0; lane < lanes; lane++) {
464
+ lowest = Math.min(lowest, swizzle_selector(pattern, lane));
465
+ }
466
+
467
+ let canonical = 0;
468
+
469
+ for (let lane = 0; lane < VM_MAX_WIDTH; lane++) {
470
+ const selector = lane < lanes ? swizzle_selector(pattern, lane) - lowest : lane;
471
+
472
+ canonical |= selector << (2 * lane);
473
+ }
474
+
475
+ return vm_register(vm_operand_base(word) + lowest, canonical);
476
+ }
477
+
478
+ // --- immediates -------------------------------------------------------------------------------
479
+
480
+ /** Scratch for converting between a literal and its bits. */
481
+ const F32_SCRATCH = new Float32Array(1);
482
+
483
+ /** @type {Uint32Array} the same four bytes */
484
+ const U32_SCRATCH = new Uint32Array(F32_SCRATCH.buffer);
485
+
486
+ /** @param {number} value @returns {number} the `f32` bit pattern the value rounds to */
487
+ export function vm_f32_bits(value) {
488
+ F32_SCRATCH[0] = value;
489
+
490
+ return U32_SCRATCH[0];
491
+ }
492
+
493
+ /** @param {number} bits @returns {number} the `f32` those bits are */
494
+ export function vm_f32_value(bits) {
495
+ U32_SCRATCH[0] = bits;
496
+
497
+ return F32_SCRATCH[0];
498
+ }
499
+
500
+ /**
501
+ * A literal source operand, as `InstructionStream.emit` takes one in a source slot.
502
+ *
503
+ * @param {number} value
504
+ * @returns {{kind: number, word: number}}
505
+ */
506
+ export function vm_immediate(value) {
507
+ return { kind: VM_KIND.IMM, word: vm_f32_bits(value) };
508
+ }
509
+
510
+ // --- the opcode table -------------------------------------------------------------------------
511
+
512
+ /**
513
+ * Widths are read out of the opcode's metadata; see {@link VM_ISA}.
241
514
  *
242
515
  * @param {number} op
243
516
  * @param {number} width the instruction's width field
244
- * @returns {number}
517
+ * @returns {number} the lane count the opcode actually operates on
245
518
  */
246
519
  export function vm_operation_width(op, width) {
247
520
  const meta = VM_ISA.get(op);
@@ -278,23 +551,22 @@ export function vm_dest_width(op, width) {
278
551
  }
279
552
  }
280
553
 
281
- const D = VM_OPERAND.DST;
282
554
  const R = VM_OPERAND.REG;
283
555
  const _ = VM_OPERAND.NONE;
284
556
 
285
557
  /**
286
- * Per-opcode metadata: mnemonic + the role of each of the three operand slots `[s0, s1, s2]`.
558
+ * Per-opcode metadata: mnemonic, whether it writes a destination, and the role of each source slot.
287
559
  *
288
560
  * Keyed by opcode number. Used by the assembler for validation/disassembly and by the parity
289
561
  * meta-test to enforce that every opcode is handled by both executors.
290
- * @type {Map<number, {name: string, dst: boolean, operands: [string, string, string]}>}
562
+ * @type {Map<number, {name: string, dst: boolean, operands: [string, string, string], width: string, dest: string, default_width: number, min_width: number, scalar_operands: number[]}>}
291
563
  */
292
564
  export const VM_ISA = new Map();
293
565
 
294
566
  /**
295
567
  * @param {number} op
296
568
  * @param {string} name
297
- * @param {boolean} dst
569
+ * @param {boolean} dst whether the opcode writes a destination register
298
570
  * @param {string} [s0]
299
571
  * @param {string} [s1]
300
572
  * @param {string} [s2]
@@ -311,7 +583,7 @@ function def(op, name, dst, s0 = _, s1 = _, s2 = _, options = {}) {
311
583
  }
312
584
 
313
585
  const width = options.width ?? VM_WIDTH.VARIABLE;
314
- const dest = options.dest ?? (dst ? VM_DEST.WIDTH : VM_DEST.NONE);
586
+ const dest = dst ? (options.dest ?? VM_DEST.WIDTH) : VM_DEST.NONE;
315
587
 
316
588
  let default_width = options.default_width ?? VM_MAX_WIDTH;
317
589
 
@@ -340,12 +612,12 @@ def(VM_OP.NOP, "NOP", false, _, _, _, { width: VM_WIDTH.NONE });
340
612
  def(VM_OP.MOV, "MOV", true, R);
341
613
  def(VM_OP.LOAD_CONST, "LOAD_CONST", true, VM_OPERAND.CONST);
342
614
  def(VM_OP.LOAD_ATTR, "LOAD_ATTR", true, VM_OPERAND.ATTR_OFFSET, VM_OPERAND.COMP);
343
- // dst slot holds the SOURCE register; the width must equal the component count, since a store
344
- // cannot write lanes the register does not hold.
345
- def(VM_OP.STORE_ATTR, "STORE_ATTR", true, VM_OPERAND.ATTR_OFFSET, VM_OPERAND.COMP, _, { dest: VM_DEST.NONE });
615
+ // The value to store is an ordinary source — a register run or a literal — and the width must equal
616
+ // the component count, since a store cannot write lanes the value does not have.
617
+ def(VM_OP.STORE_ATTR, "STORE_ATTR", false, R, VM_OPERAND.ATTR_OFFSET, VM_OPERAND.COMP);
346
618
  def(VM_OP.LOAD_BUILTIN, "LOAD_BUILTIN", true, VM_OPERAND.BUILTIN);
347
- // dst slot holds the CONDITION register, read as a single lane
348
- def(VM_OP.KILL, "KILL", true, _, _, _, { width: VM_WIDTH.ONE, dest: VM_DEST.NONE });
619
+ // The condition is one lane of an ordinary source.
620
+ def(VM_OP.KILL, "KILL", false, R, _, _, { width: VM_WIDTH.ONE });
349
621
 
350
622
  def(VM_OP.ADD, "ADD", true, R, R);
351
623
  def(VM_OP.SUB, "SUB", true, R, R);
@@ -356,7 +628,6 @@ def(VM_OP.MIN, "MIN", true, R, R);
356
628
  def(VM_OP.MAX, "MAX", true, R, R);
357
629
  def(VM_OP.MIX, "MIX", true, R, R, R);
358
630
  def(VM_OP.CLAMP, "CLAMP", true, R, R, R);
359
- def(VM_OP.SCALE, "SCALE", true, R, R, _, { scalar_operands: [1] });
360
631
 
361
632
  def(VM_OP.NEG, "NEG", true, R);
362
633
  def(VM_OP.ABS, "ABS", true, R);
@@ -370,18 +641,23 @@ def(VM_OP.LOG, "LOG", true, R);
370
641
  def(VM_OP.POW, "POW", true, R, R);
371
642
  def(VM_OP.STEP, "STEP", true, R, R);
372
643
  def(VM_OP.SMOOTHSTEP, "SMOOTHSTEP", true, R, R, R);
373
-
374
- // The reductions read `width` lanes and write one. They used to read `.xyz` and broadcast the
375
- // result over all four lanes; the broadcast is now the compiler's to emit where a wider consumer
376
- // actually needs it, which is the whole point of tracking widths.
644
+ def(VM_OP.RSQRT, "RSQRT", true, R);
645
+ def(VM_OP.TAN, "TAN", true, R);
646
+ def(VM_OP.ASIN, "ASIN", true, R);
647
+ def(VM_OP.ACOS, "ACOS", true, R);
648
+ // `atan2(y, x)`: the angle of the point, with the quadrant the signs put it in — so the first
649
+ // source is the one a plot puts on the vertical axis, as it is in every shading language.
650
+ def(VM_OP.ATAN2, "ATAN2", true, R, R);
651
+
652
+ // The reductions read `width` lanes and write one. Whatever wants the result wider reads it through
653
+ // an `.xxxx` swizzle, which is what tracking widths is for.
377
654
  def(VM_OP.DOT, "DOT", true, R, R, _, { dest: VM_DEST.ONE, default_width: 3 });
378
- // A cross product is three lanes wide. It may be asked for a fourth, which it writes as zero — the
379
- // `w` the opcode used to append — but never for fewer, so `MIN_WIDTH` is a floor rather than a fix.
655
+ // A cross product is three lanes wide. It may be asked for a fourth, which it writes as zero, but
656
+ // never for fewer, so `min_width` is a floor rather than a fix.
380
657
  def(VM_OP.CROSS, "CROSS", true, R, R, _, { default_width: 3, min_width: 3 });
381
658
  def(VM_OP.LENGTH, "LENGTH", true, R, _, _, { dest: VM_DEST.ONE, default_width: 3 });
382
659
  def(VM_OP.NORMALIZE, "NORMALIZE", true, R, _, _, { default_width: 3 });
383
660
  def(VM_OP.DISTANCE, "DISTANCE", true, R, R, _, { dest: VM_DEST.ONE, default_width: 3 });
384
- def(VM_OP.SWIZZLE, "SWIZZLE", true, R, VM_OPERAND.SWIZZLE);
385
661
 
386
662
  def(VM_OP.CMP_LT, "CMP_LT", true, R, R);
387
663
  def(VM_OP.CMP_LE, "CMP_LE", true, R, R);
@@ -390,108 +666,201 @@ def(VM_OP.CMP_GE, "CMP_GE", true, R, R);
390
666
  def(VM_OP.CMP_EQ, "CMP_EQ", true, R, R);
391
667
  def(VM_OP.SELECT, "SELECT", true, R, R, R);
392
668
 
393
- // One independent uniform draw in `[0, 1)` per lane, and nothing else. `RANDOM` used to carry a
394
- // distribution mode — signed, unit sphere, unit disk — and every one of those is arithmetic over
395
- // this draw that a program can write for itself: with its constants hoisted, a unit sphere is ten
396
- // instructions and a unit disk seven. There is no principled place to stop adding distributions, so
397
- // the ISA does not start. Which means the draw count is now plainly `width`, rather than a per-mode
398
- // rule a reader has to look up.
669
+ // One independent uniform draw in `[0, 1)` per lane, and nothing else: a shaped distribution is
670
+ // arithmetic a program writes over this draw — see ../graph/particle_graph_distributions.js.
399
671
  def(VM_OP.RANDOM, "RANDOM", true);
400
672
  // `t` is one lane; the sampled value fills the destination's width.
401
673
  def(VM_OP.CURVE, "CURVE", true, VM_OPERAND.CURVE, R, _, { scalar_operands: [1] });
402
674
 
675
+ // --- packing an instruction ---------------------------------------------------------------------
676
+
403
677
  /**
678
+ * The four words of an instruction, with every register operand in its canonical spelling.
679
+ *
680
+ * Both the assembler's `InstructionStream.emit` and the assembly parser build instructions here, so
681
+ * the one encoding rule that is a choice rather than a fact — how a swizzle is spelled — is made in
682
+ * one place: selectors above the lanes an operand is read at are the identity, and a run of
683
+ * consecutive selectors is the identity at a higher base. Two instructions that mean the same thing
684
+ * then have the same words, which is what lets the optimizer compare them.
685
+ *
404
686
  * @param {number} op
405
- * @returns {string} mnemonic, or `OP_<n>` if unknown
687
+ * @param {number} width lane count, `0..4`
688
+ * @param {number} dst destination register
689
+ * @param {number[]} kinds a {@link VM_KIND} per source slot
690
+ * @param {number[]} sources the three source words
691
+ * @returns {number[]}
406
692
  */
407
- export function vm_op_name(op) {
693
+ export function vm_pack_instruction(op, width, dst, kinds, sources) {
408
694
  const meta = VM_ISA.get(op);
409
- return meta === undefined ? `OP_${op}` : meta.name;
695
+ const operation_width = vm_operation_width(op, width);
696
+ const words = [vm_encode_word0(op, width, dst, vm_pack_kinds(kinds[0], kinds[1], kinds[2])), 0, 0, 0];
697
+
698
+ for (let slot = 0; slot < 3; slot++) {
699
+ const word = sources[slot] >>> 0;
700
+
701
+ words[1 + slot] = kinds[slot] === VM_KIND.REG && meta !== undefined
702
+ ? vm_canonical_register(word, meta.scalar_operands.includes(slot) ? 1 : operation_width)
703
+ : word;
704
+ }
705
+
706
+ return words;
410
707
  }
411
708
 
412
709
  /**
413
- * Pack a swizzle pattern from four lane selectors into the 8-bit form used by the `SWIZZLE` opcode.
414
- * Selector values: 0=x, 1=y, 2=z, 3=w.
415
- * @param {number} x
416
- * @param {number} y
417
- * @param {number} z
418
- * @param {number} w
419
- * @returns {number}
710
+ * An encoded instruction, re-spelled canonically — what a pass wants after it has moved an operand.
711
+ *
712
+ * @param {ArrayLike<number>} words the four words of a single instruction
713
+ * @returns {number[]}
420
714
  */
421
- export function pack_swizzle(x, y, z, w) {
422
- return (x & 3) | ((y & 3) << 2) | ((z & 3) << 4) | ((w & 3) << 6);
715
+ export function vm_repack_instruction(words) {
716
+ const word0 = words[0];
717
+
718
+ return vm_pack_instruction(
719
+ vm_decode_op(word0),
720
+ vm_decode_width(word0),
721
+ vm_decode_dst(word0),
722
+ [vm_decode_kind(word0, 0), vm_decode_kind(word0, 1), vm_decode_kind(word0, 2)],
723
+ [words[1], words[2], words[3]]
724
+ );
423
725
  }
424
726
 
727
+ // --- reading an instruction ---------------------------------------------------------------------
728
+
729
+ /**
730
+ * @typedef {object} ParticleVMOperand
731
+ * @property {number} slot source slot, `0..2`
732
+ * @property {number} kind a {@link VM_KIND}, never `NONE`
733
+ * @property {number} word the source word as encoded
734
+ * @property {number} lanes how many lanes the instruction reads of it: the operation's width, or one
735
+ * for a slot the ISA marks scalar
736
+ * @property {number} span register slots the operand reaches from its base, for a register; `0` for a
737
+ * literal
738
+ */
739
+
425
740
  /**
426
- * How many registers a `SWIZZLE` pattern reaches into, counting from the source's base: one past
427
- * the largest lane any of its four selectors names.
741
+ * Visit every source operand of an instruction — every slot whose kind is not `NONE` — with how
742
+ * many lanes the opcode reads of it and how far into the file it reaches.
428
743
  *
429
- * A swizzle's source span has nothing to do with the instruction's width — a four-lane broadcast
430
- * reads one register and a three-lane reversal reads four — so it is the only operand whose extent
431
- * has to be read out of the pattern itself.
744
+ * The rules for the reach are not uniform and live here rather than in each caller: a source is
745
+ * read as wide as the operation runs unless the ISA marks that slot scalar, and a register operand
746
+ * reaches as far as its widest selector among the lanes read.
432
747
  *
433
- * @param {number} pattern
434
- * @returns {number}
748
+ * @param {ArrayLike<number>} words an instruction stream
749
+ * @param {number} base index of the instruction's first word
750
+ * @param {function(ParticleVMOperand):void} visit the one object is reused between calls; copy it
751
+ * to keep it
435
752
  */
436
- export function swizzle_span(pattern) {
437
- let span = 1;
753
+ export function vm_visit_operands(words, base, visit) {
754
+ const word0 = words[base];
755
+ const op = vm_decode_op(word0);
756
+ const meta = VM_ISA.get(op);
438
757
 
439
- for (let lane = 0; lane < 4; lane++) {
440
- const selector = (pattern >> (2 * lane)) & 3;
758
+ if (meta === undefined) {
759
+ return;
760
+ }
761
+
762
+ const operation_width = vm_operation_width(op, vm_decode_width(word0));
763
+ const operand = { slot: 0, kind: 0, word: 0, lanes: 0, span: 0 };
764
+
765
+ for (let slot = 0; slot < 3; slot++) {
766
+ const kind = vm_decode_kind(word0, slot);
441
767
 
442
- if (selector + 1 > span) {
443
- span = selector + 1;
768
+ if (kind === VM_KIND.NONE) {
769
+ continue;
444
770
  }
445
- }
446
771
 
447
- return span;
772
+ const word = words[base + 1 + slot];
773
+ const lanes = meta.scalar_operands.includes(slot) ? 1 : operation_width;
774
+
775
+ operand.slot = slot;
776
+ operand.kind = kind;
777
+ operand.word = word;
778
+ operand.lanes = lanes;
779
+ operand.span = kind === VM_KIND.REG ? vm_operand_span(word, lanes) : 0;
780
+ visit(operand);
781
+ }
448
782
  }
449
783
 
450
784
  /**
451
- * Visit every register operand of an instruction as the slot it starts at and how many consecutive
452
- * slots it reaches from there.
453
- *
454
- * Registers hold single floats, so an operand's base index says nothing on its own about how much
455
- * of the file it touches: a `vec3` at r0 occupies r0, r1 and r2. Anyone sizing a register file or
456
- * bounds-checking one needs the reach rather than the base, and the rules for it are not uniform —
457
- * so they live here, in one place, instead of being re-derived by each caller until they disagree:
458
- *
459
- * - a destination reaches as far as the opcode writes, which for the reductions is one lane;
460
- * - a source reaches as far as the operation runs, unless the ISA marks that slot scalar;
461
- * - a `SWIZZLE` source reaches as far as its pattern names, which the width cannot tell you;
462
- * - the source `STORE_ATTR` and `KILL` keep in the destination slot reaches as far as they read.
785
+ * Visit every run of the register file an instruction touches, as the slot it starts at and how
786
+ * many consecutive slots it reaches: the destination as far as the opcode writes, and each register
787
+ * source as far as its selectors read. What anyone sizing or bounds-checking the file wants; a
788
+ * literal is not a run of the file and is not visited.
463
789
  *
464
790
  * @param {ArrayLike<number>} words an instruction stream
465
791
  * @param {number} base index of the instruction's first word
466
- * @param {function(number, number):void} visit called `(slot, span)` per register operand
792
+ * @param {function(number, number):void} visit called `(slot, span)` per run
467
793
  */
468
- export function vm_visit_register_operands(words, base, visit) {
794
+ export function vm_visit_register_runs(words, base, visit) {
469
795
  const word0 = words[base];
470
796
  const op = vm_decode_op(word0);
471
- const meta = VM_ISA.get(op);
797
+ const dest = vm_dest_width(op, vm_decode_width(word0));
472
798
 
473
- if (meta === undefined) {
474
- return;
799
+ if (dest > 0) {
800
+ visit(vm_decode_dst(word0), dest);
475
801
  }
476
802
 
477
- const width = vm_decode_width(word0);
478
- const operation_width = vm_operation_width(op, width);
803
+ vm_visit_operands(words, base, (operand) => {
804
+ if (operand.kind === VM_KIND.REG) {
805
+ visit(vm_operand_base(operand.word), operand.span);
806
+ }
807
+ });
808
+ }
479
809
 
480
- if (meta.dst) {
481
- // For value-producing ops this is the destination; for STORE_ATTR and KILL it is a source
482
- // living in the dst slot, and its span is what the instruction READS, not what it writes.
483
- visit(words[base + 1], meta.dest === VM_DEST.NONE ? operation_width : vm_dest_width(op, width));
810
+ /**
811
+ * Whether the kind bits of an instruction agree with what its opcode reads: a register-role slot
812
+ * carries a register or a literal, every other slot carries nothing. Both the assembler and
813
+ * `ParticleProgram.validate` ask here rather than each restating the rule.
814
+ *
815
+ * @param {ArrayLike<number>} words an instruction stream
816
+ * @param {number} base index of the instruction's first word
817
+ * @returns {string|null} what is wrong, or `null` when nothing is
818
+ */
819
+ export function vm_kinds_problem(words, base) {
820
+ const word0 = words[base];
821
+ const meta = VM_ISA.get(vm_decode_op(word0));
822
+
823
+ if (meta === undefined) {
824
+ return null;
484
825
  }
485
826
 
486
827
  for (let slot = 0; slot < 3; slot++) {
487
- if (meta.operands[slot] !== VM_OPERAND.REG) {
488
- continue;
828
+ const kind = vm_decode_kind(word0, slot);
829
+ const role = meta.operands[slot];
830
+
831
+ if (kind > VM_KIND.IMM) {
832
+ return `s${slot} has operand kind ${kind}, which the ISA does not define`;
833
+ }
834
+
835
+ if (role === VM_OPERAND.REG && kind === VM_KIND.NONE) {
836
+ return `s${slot} of ${meta.name} must be a register or a literal`;
489
837
  }
490
838
 
491
- const span = op === VM_OP.SWIZZLE
492
- ? swizzle_span(words[base + 3])
493
- : (meta.scalar_operands.includes(slot) ? 1 : operation_width);
839
+ if (role !== VM_OPERAND.REG && kind !== VM_KIND.NONE) {
840
+ return `s${slot} of ${meta.name} is ${role === VM_OPERAND.NONE ? "unused" : `a ${role}`} and has no kind`;
841
+ }
842
+ }
843
+
844
+ return null;
845
+ }
494
846
 
495
- visit(words[base + 2 + slot], span);
847
+ /**
848
+ * The kinds an opcode's sources carry when every register-role slot holds a register: the default
849
+ * an assembler starts from, before any literal is marked.
850
+ *
851
+ * @param {number} op
852
+ * @returns {number} a kinds field for {@link vm_encode_word0}
853
+ */
854
+ export function vm_register_kinds(op) {
855
+ const meta = VM_ISA.get(op);
856
+
857
+ if (meta === undefined) {
858
+ return 0;
496
859
  }
860
+
861
+ return vm_pack_kinds(
862
+ meta.operands[0] === R ? VM_KIND.REG : VM_KIND.NONE,
863
+ meta.operands[1] === R ? VM_KIND.REG : VM_KIND.NONE,
864
+ meta.operands[2] === R ? VM_KIND.REG : VM_KIND.NONE
865
+ );
497
866
  }