@woosh/meep-engine 3.16.0 → 3.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (410) hide show
  1. package/editor/particles/effect/ParticleEffectDocument.d.ts +21 -6
  2. package/editor/particles/effect/ParticleEffectDocument.d.ts.map +1 -1
  3. package/editor/particles/effect/ParticleEffectDocument.js +38 -17
  4. package/editor/view/node-graph/NodeGraphClipboard.d.ts +186 -0
  5. package/editor/view/node-graph/NodeGraphClipboard.d.ts.map +1 -0
  6. package/editor/view/node-graph/NodeGraphClipboard.js +231 -0
  7. package/editor/view/node-graph/NodeGraphEditorView.d.ts +59 -1
  8. package/editor/view/node-graph/NodeGraphEditorView.d.ts.map +1 -1
  9. package/editor/view/node-graph/NodeGraphEditorView.js +248 -5
  10. package/editor/view/node-graph/NodeGraphView.d.ts.map +1 -1
  11. package/editor/view/node-graph/NodeGraphView.js +84 -8
  12. package/editor/view/node-graph/actions/connection_bind_actions.d.ts +23 -0
  13. package/editor/view/node-graph/actions/connection_bind_actions.d.ts.map +1 -0
  14. package/editor/view/node-graph/actions/connection_bind_actions.js +39 -0
  15. package/editor/view/particles/effect/ParticleCodeEditorView.d.ts.map +1 -1
  16. package/editor/view/particles/effect/ParticleCodeEditorView.js +4 -3
  17. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts +20 -0
  18. package/editor/view/particles/effect/ParticleCurveEditorView.d.ts.map +1 -0
  19. package/editor/view/particles/effect/ParticleCurveEditorView.js +574 -0
  20. package/editor/view/particles/effect/ParticleEmitterInspectorView.d.ts.map +1 -1
  21. package/editor/view/particles/effect/ParticleEmitterInspectorView.js +27 -1
  22. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts +24 -0
  23. package/editor/view/particles/effect/ParticleGradientEditorView.d.ts.map +1 -0
  24. package/editor/view/particles/effect/ParticleGradientEditorView.js +472 -0
  25. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts +5 -0
  26. package/editor/view/particles/effect/ParticleGraphEditorView.d.ts.map +1 -1
  27. package/editor/view/particles/effect/ParticleGraphEditorView.js +61 -31
  28. package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
  29. package/editor/view/particles/effect/ParticleNodeParametersView.js +33 -0
  30. package/editor/view/particles/effect/particle-editor.css +123 -35
  31. package/package.json +1 -1
  32. package/src/core/binary/allocator/OffsetAllocator.d.ts +25 -16
  33. package/src/core/binary/allocator/OffsetAllocator.d.ts.map +1 -1
  34. package/src/core/binary/allocator/OffsetAllocator.js +10 -1
  35. package/src/core/collection/array/typed/float32_array_hash.d.ts +21 -0
  36. package/src/core/collection/array/typed/float32_array_hash.d.ts.map +1 -0
  37. package/src/core/collection/array/typed/float32_array_hash.js +38 -0
  38. package/src/core/graph/csr/CSRGraph.d.ts +8 -1
  39. package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
  40. package/src/core/graph/csr/CSRGraph.js +14 -6
  41. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts +24 -0
  42. package/src/core/model/node-graph/visual/layout/LayoutProblem.d.ts.map +1 -1
  43. package/src/core/model/node-graph/visual/layout/LayoutProblem.js +27 -0
  44. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts +26 -0
  45. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.d.ts.map +1 -1
  46. package/src/core/model/node-graph/visual/layout/NodeGraphLayoutOptions.js +28 -0
  47. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts +92 -3
  48. package/src/core/model/node-graph/visual/layout/RouteGrid.d.ts.map +1 -1
  49. package/src/core/model/node-graph/visual/layout/RouteGrid.js +405 -242
  50. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts +50 -5
  51. package/src/core/model/node-graph/visual/layout/RouteSearch.d.ts.map +1 -1
  52. package/src/core/model/node-graph/visual/layout/RouteSearch.js +160 -24
  53. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts +7 -0
  54. package/src/core/model/node-graph/visual/layout/RouteSet.d.ts.map +1 -1
  55. package/src/core/model/node-graph/visual/layout/RouteSet.js +8 -0
  56. package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.js +9 -3
  57. package/src/core/model/node-graph/visual/layout/layout_build_problem.d.ts.map +1 -1
  58. package/src/core/model/node-graph/visual/layout/layout_build_problem.js +7 -0
  59. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts +9 -3
  60. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.d.ts.map +1 -1
  61. package/src/core/model/node-graph/visual/layout/layout_insert_lanes.js +17 -3
  62. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts +9 -6
  63. package/src/core/model/node-graph/visual/layout/layout_node_graph.d.ts.map +1 -1
  64. package/src/core/model/node-graph/visual/layout/layout_node_graph.js +49 -7
  65. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts +11 -1
  66. package/src/core/model/node-graph/visual/layout/layout_order_layers.d.ts.map +1 -1
  67. package/src/core/model/node-graph/visual/layout/layout_order_layers.js +135 -16
  68. package/src/core/model/node-graph/visual/layout/route_connections.d.ts +29 -0
  69. package/src/core/model/node-graph/visual/layout/route_connections.d.ts.map +1 -1
  70. package/src/core/model/node-graph/visual/layout/route_connections.js +716 -309
  71. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts +3 -2
  72. package/src/core/model/node-graph/visual/layout/route_grid_build.d.ts.map +1 -1
  73. package/src/core/model/node-graph/visual/layout/route_grid_build.js +7 -15
  74. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts +6 -0
  75. package/src/core/model/node-graph/visual/layout/route_nudge.d.ts.map +1 -1
  76. package/src/core/model/node-graph/visual/layout/route_nudge.js +42 -13
  77. package/src/engine/animation/AnimationUtils.js +246 -246
  78. package/src/engine/animation/Animations.js +74 -74
  79. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2078 -2078
  80. package/src/engine/ecs/EventType.d.ts +0 -1
  81. package/src/engine/ecs/EventType.js +1 -26
  82. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts +1 -1
  83. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts.map +1 -1
  84. package/src/engine/ecs/ik/ik_write_local_rotation.js +3 -7
  85. package/src/engine/ecs/parent/EntityNode.d.ts.map +1 -1
  86. package/src/engine/ecs/parent/EntityNode.js +4 -9
  87. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  88. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +360 -364
  89. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts +41 -0
  90. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.d.ts.map +1 -0
  91. package/src/engine/ecs/transform/TRANSFORM64_EVENT_CHANGE.js +40 -0
  92. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +8 -0
  93. package/src/engine/ecs/transform/t64_announce_change.d.ts +10 -7
  94. package/src/engine/ecs/transform/t64_announce_change.d.ts.map +1 -1
  95. package/src/engine/ecs/transform/t64_announce_change.js +12 -12
  96. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts +30 -0
  97. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.d.ts.map +1 -0
  98. package/src/engine/ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.js +29 -0
  99. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
  100. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +13 -21
  101. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
  102. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +4 -8
  103. package/src/engine/graphics3/LightSystem.d.ts.map +1 -1
  104. package/src/engine/graphics3/LightSystem.js +4 -8
  105. package/src/engine/graphics3/MeshSystem.d.ts.map +1 -1
  106. package/src/engine/graphics3/MeshSystem.js +9 -19
  107. package/src/engine/graphics3/ParticleEmitterSystem.d.ts.map +1 -1
  108. package/src/engine/graphics3/ParticleEmitterSystem.js +4 -8
  109. package/src/engine/graphics3/ShadedGeometrySystem.d.ts.map +1 -1
  110. package/src/engine/graphics3/ShadedGeometrySystem.js +4 -10
  111. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts +1 -1
  112. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts.map +1 -1
  113. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.js +4 -8
  114. package/src/engine/intelligence/behavior/ecs/OrbitingBehavior.js +218 -218
  115. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  116. package/src/engine/physics/ecs/PhysicsSystem.js +2 -5
  117. package/src/engine/sound/ecs/SoundListenerSystem.d.ts.map +1 -1
  118. package/src/engine/sound/ecs/SoundListenerSystem.js +6 -8
  119. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts +0 -4
  120. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts.map +1 -1
  121. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +5 -11
  122. package/src/shade/playground/particle_editor/README.md +43 -2
  123. package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
  124. package/src/shade/playground/particle_system/particle_prototype.js +4 -5
  125. package/src/shade/playground/particle_system/particle_scene.js +4 -4
  126. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  127. package/src/shade/renderer/Renderer.js +13 -1
  128. package/src/shade/renderer/particles/DESIGN.md +104 -19
  129. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +64 -50
  130. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  131. package/src/shade/renderer/particles/GPUParticleSystem.js +176 -78
  132. package/src/shade/renderer/particles/ParticleConstants.d.ts +59 -23
  133. package/src/shade/renderer/particles/ParticleConstants.d.ts.map +1 -1
  134. package/src/shade/renderer/particles/ParticleConstants.js +66 -23
  135. package/src/shade/renderer/particles/ParticleRenderMode.d.ts +19 -0
  136. package/src/shade/renderer/particles/ParticleRenderMode.d.ts.map +1 -0
  137. package/src/shade/renderer/particles/ParticleRenderMode.js +33 -0
  138. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts.map +1 -1
  139. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.js +167 -166
  140. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts +37 -0
  141. package/src/shade/renderer/particles/data/particle_emitter_record.d.ts.map +1 -0
  142. package/src/shade/renderer/particles/data/particle_emitter_record.js +109 -0
  143. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.d.ts +3 -3
  144. package/src/shade/renderer/particles/graph/ParticleGroupBuilder.js +303 -303
  145. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +18 -0
  146. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -1
  147. package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +152 -133
  148. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +0 -11
  149. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  150. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +720 -621
  151. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  152. package/src/shade/renderer/particles/graph/compile_particle_graph.js +8 -3
  153. package/src/shade/renderer/particles/graph/flatten_particle_graph.d.ts.map +1 -1
  154. package/src/shade/renderer/particles/graph/flatten_particle_graph.js +244 -213
  155. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.d.ts.map +1 -1
  156. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.js +6 -0
  157. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.d.ts.map +1 -1
  158. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.js +294 -319
  159. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.d.ts.map +1 -1
  160. package/src/shade/renderer/particles/graph/groups/particle_groups_noise.js +524 -517
  161. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts +22 -0
  162. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.d.ts.map +1 -0
  163. package/src/shade/renderer/particles/graph/groups/particle_groups_psrdnoise.js +517 -0
  164. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  165. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +185 -159
  166. package/src/shade/renderer/particles/graph/make_particle_group.d.ts.map +1 -1
  167. package/src/shade/renderer/particles/graph/make_particle_group.js +2 -20
  168. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts +17 -0
  169. package/src/shade/renderer/particles/graph/particle_compare_ops.d.ts.map +1 -0
  170. package/src/shade/renderer/particles/graph/particle_compare_ops.js +22 -0
  171. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts +28 -8
  172. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.d.ts.map +1 -1
  173. package/src/shade/renderer/particles/graph/particle_graph_diagnostic.js +49 -25
  174. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts +40 -0
  175. package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts.map +1 -1
  176. package/src/shade/renderer/particles/graph/particle_node_parameters.js +312 -234
  177. package/src/shade/renderer/particles/graph/particle_node_presentation.d.ts.map +1 -1
  178. package/src/shade/renderer/particles/graph/particle_node_presentation.js +59 -3
  179. package/src/shade/renderer/particles/graph/particle_ramp.d.ts +230 -0
  180. package/src/shade/renderer/particles/graph/particle_ramp.d.ts.map +1 -0
  181. package/src/shade/renderer/particles/graph/particle_ramp.js +403 -0
  182. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts +7 -1
  183. package/src/shade/renderer/particles/graph/validate_particle_graph.d.ts.map +1 -1
  184. package/src/shade/renderer/particles/graph/validate_particle_graph.js +66 -10
  185. package/src/shade/renderer/particles/graph_particles.d.ts +23 -69
  186. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  187. package/src/shade/renderer/particles/graph_particles.js +105 -262
  188. package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
  189. package/src/shade/renderer/particles/graph_particles_avboit.js +3 -2
  190. package/src/shade/renderer/particles/graph_particles_billboard.d.ts +37 -0
  191. package/src/shade/renderer/particles/graph_particles_billboard.d.ts.map +1 -0
  192. package/src/shade/renderer/particles/graph_particles_billboard.js +93 -0
  193. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +51 -22
  194. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -1
  195. package/src/shade/renderer/particles/isa/InstructionStream.js +98 -38
  196. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts +49 -8
  197. package/src/shade/renderer/particles/isa/ParticleProgram.d.ts.map +1 -1
  198. package/src/shade/renderer/particles/isa/ParticleProgram.js +95 -150
  199. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts +306 -64
  200. package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts.map +1 -1
  201. package/src/shade/renderer/particles/isa/ParticleVMISA.js +505 -136
  202. package/src/shade/renderer/particles/isa/particle_assembly.d.ts +36 -12
  203. package/src/shade/renderer/particles/isa/particle_assembly.d.ts.map +1 -1
  204. package/src/shade/renderer/particles/isa/particle_assembly.js +354 -139
  205. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts +9 -0
  206. package/src/shade/renderer/particles/isa/validate_particle_program.d.ts.map +1 -0
  207. package/src/shade/renderer/particles/isa/validate_particle_program.js +128 -0
  208. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts +23 -0
  209. package/src/shade/renderer/particles/layout/ParticleLayout.d.ts.map +1 -1
  210. package/src/shade/renderer/particles/layout/ParticleLayout.js +68 -0
  211. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts +77 -0
  212. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.d.ts.map +1 -0
  213. package/src/shade/renderer/particles/optimizer/dag/optimize_particle_program_vector.js +219 -0
  214. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts +83 -0
  215. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.d.ts.map +1 -0
  216. package/src/shade/renderer/particles/optimizer/dag/particle_vector_extract.js +283 -0
  217. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts +133 -0
  218. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts.map +1 -0
  219. package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.js +334 -0
  220. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts +117 -0
  221. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.d.ts.map +1 -0
  222. package/src/shade/renderer/particles/optimizer/dag/particle_vector_groups.js +70 -0
  223. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts +97 -0
  224. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.d.ts.map +1 -0
  225. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.js +462 -0
  226. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts +116 -0
  227. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.d.ts.map +1 -0
  228. package/src/shade/renderer/particles/optimizer/dag/particle_vector_lower.js +687 -0
  229. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts +57 -0
  230. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.d.ts.map +1 -0
  231. package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.js +860 -0
  232. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts +115 -0
  233. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.d.ts.map +1 -0
  234. package/src/shade/renderer/particles/optimizer/dag/particle_vector_rules.js +264 -0
  235. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts +81 -42
  236. package/src/shade/renderer/particles/optimizer/optimize_particle_program.d.ts.map +1 -1
  237. package/src/shade/renderer/particles/optimizer/optimize_particle_program.js +477 -478
  238. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts +100 -0
  239. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.d.ts.map +1 -0
  240. package/src/shade/renderer/particles/optimizer/particle_integer_bounds.js +236 -0
  241. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts +139 -59
  242. package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts.map +1 -1
  243. package/src/shade/renderer/particles/optimizer/particle_program_analysis.js +764 -647
  244. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts +40 -7
  245. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts.map +1 -1
  246. package/src/shade/renderer/particles/optimizer/particle_program_equivalence.js +376 -213
  247. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts +30 -3
  248. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts.map +1 -1
  249. package/src/shade/renderer/particles/optimizer/particle_program_fuzz.js +337 -223
  250. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts +24 -38
  251. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  252. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +166 -206
  253. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts +99 -0
  254. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.d.ts.map +1 -0
  255. package/src/shade/renderer/particles/optimizer/pass_allocate_registers.js +313 -0
  256. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts +3 -2
  257. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.d.ts.map +1 -1
  258. package/src/shade/renderer/particles/optimizer/pass_dedup_constants.js +198 -197
  259. package/src/shade/renderer/particles/optimizer/pass_eliminate_dead_code.d.ts.map +1 -1
  260. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts +19 -0
  261. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  262. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +678 -561
  263. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts +22 -0
  264. package/src/shade/renderer/particles/optimizer/pass_inline_constants.d.ts.map +1 -0
  265. package/src/shade/renderer/particles/optimizer/pass_inline_constants.js +221 -0
  266. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.d.ts.map +1 -1
  267. package/src/shade/renderer/particles/optimizer/pass_narrow_widths.js +63 -72
  268. package/src/shade/renderer/particles/optimizer/pass_pack_registers.d.ts.map +1 -1
  269. package/src/shade/renderer/particles/optimizer/pass_pack_registers.js +87 -86
  270. package/src/shade/renderer/particles/optimizer/pass_peephole.d.ts.map +1 -1
  271. package/src/shade/renderer/particles/optimizer/pass_peephole.js +392 -271
  272. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.d.ts.map +1 -1
  273. package/src/shade/renderer/particles/optimizer/pass_propagate_copies.js +361 -268
  274. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts +4 -0
  275. package/src/shade/renderer/particles/optimizer/pass_schedule.d.ts.map +1 -0
  276. package/src/shade/renderer/particles/optimizer/pass_schedule.js +244 -0
  277. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.d.ts.map +1 -1
  278. package/src/shade/renderer/particles/optimizer/pass_share_subexpressions.js +154 -151
  279. package/src/shade/renderer/particles/particle_test_fixtures.d.ts +20 -0
  280. package/src/shade/renderer/particles/particle_test_fixtures.d.ts.map +1 -1
  281. package/src/shade/renderer/particles/particle_test_fixtures.js +229 -203
  282. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts +99 -25
  283. package/src/shade/renderer/particles/runtime/EmitterRegistry.d.ts.map +1 -1
  284. package/src/shade/renderer/particles/runtime/EmitterRegistry.js +438 -286
  285. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts +109 -0
  286. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.d.ts.map +1 -0
  287. package/src/shade/renderer/particles/runtime/GPUParticleEmitterContext.js +127 -0
  288. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +87 -102
  289. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
  290. package/src/shade/renderer/particles/runtime/ParticleEmitter.js +184 -163
  291. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts +26 -0
  292. package/src/shade/renderer/particles/runtime/ProgramArena.d.ts.map +1 -1
  293. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts +130 -0
  294. package/src/shade/renderer/particles/runtime/ProgramHeap.d.ts.map +1 -0
  295. package/src/shade/renderer/particles/runtime/ProgramHeap.js +595 -0
  296. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts +91 -0
  297. package/src/shade/renderer/particles/runtime/ProgramPlacement.d.ts.map +1 -0
  298. package/src/shade/renderer/particles/runtime/ProgramPlacement.js +106 -0
  299. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +5 -2
  300. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
  301. package/src/shade/renderer/particles/runtime/create_particle_effect.js +22 -19
  302. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts +28 -0
  303. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.d.ts.map +1 -0
  304. package/src/shade/renderer/particles/shaders/chunk_particle_emit_step.js +136 -0
  305. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts +35 -0
  306. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.d.ts.map +1 -0
  307. package/src/shade/renderer/particles/shaders/chunk_particle_simulate_step.js +133 -0
  308. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts +18 -9
  309. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  310. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +20 -80
  311. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts +33 -0
  312. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts.map +1 -0
  313. package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.js +114 -0
  314. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts +8 -0
  315. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  316. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +33 -101
  317. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts +45 -0
  318. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts.map +1 -0
  319. package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.js +123 -0
  320. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts +51 -0
  321. package/src/shade/renderer/particles/sort/graph_particle_sort.d.ts.map +1 -0
  322. package/src/shade/renderer/particles/sort/graph_particle_sort.js +120 -0
  323. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts +6 -0
  324. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.d.ts.map +1 -0
  325. package/src/shade/renderer/particles/vm/PARTICLE_VM_CONTEXT_STRUCT.js +16 -0
  326. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +4 -1
  327. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  328. package/src/shade/renderer/particles/vm/ParticleVMReference.js +261 -140
  329. package/src/shade/renderer/particles/vm/chunk_particle_vm.d.ts.map +1 -1
  330. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +217 -255
  331. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts +43 -0
  332. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.d.ts.map +1 -0
  333. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_fast.js +55 -0
  334. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts +62 -0
  335. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.d.ts.map +1 -0
  336. package/src/shade/renderer/particles/vm/chunk_particle_vm_registers_wide.js +86 -0
  337. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts +22 -0
  338. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.d.ts.map +1 -0
  339. package/src/shade/renderer/shader/chunk/math/chunk_mod_v3f.js +32 -0
  340. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts +9 -0
  341. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.d.ts.map +1 -0
  342. package/src/shade/renderer/shader/chunk/math/chunk_mod_v4f.js +19 -0
  343. package/src/shade/renderer/shader/chunk/noise/psrdnoise/README.md +65 -0
  344. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts +24 -0
  345. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.d.ts.map +1 -0
  346. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2.js +83 -0
  347. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts +25 -0
  348. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.d.ts.map +1 -0
  349. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_hash.js +42 -0
  350. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts +31 -0
  351. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.d.ts.map +1 -0
  352. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise2_kernel.js +80 -0
  353. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts +26 -0
  354. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.d.ts.map +1 -0
  355. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3.js +109 -0
  356. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts +38 -0
  357. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.d.ts.map +1 -0
  358. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_kernel.js +129 -0
  359. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts +21 -0
  360. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.d.ts.map +1 -0
  361. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_permute.js +35 -0
  362. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts +19 -0
  363. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.d.ts.map +1 -0
  364. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_skew.js +28 -0
  365. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts +13 -0
  366. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.d.ts.map +1 -0
  367. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_psrdnoise3_unskew.js +22 -0
  368. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts +22 -0
  369. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.d.ts.map +1 -0
  370. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise2.js +59 -0
  371. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts +23 -0
  372. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.d.ts.map +1 -0
  373. package/src/shade/renderer/shader/chunk/noise/psrdnoise/chunk_srdnoise3.js +74 -0
  374. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts +34 -1
  375. package/src/shade/wgsl/emulator/ComputeShaderEmulator.d.ts.map +1 -1
  376. package/src/shade/wgsl/emulator/ComputeShaderEmulator.js +41 -1
  377. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts +3 -2
  378. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts.map +1 -1
  379. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +49 -10
  380. package/src/view/minimap/dom/MinimapCameraView.d.ts.map +1 -1
  381. package/src/view/minimap/dom/MinimapCameraView.js +4 -9
  382. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts +0 -15
  383. package/src/core/model/node-graph/visual/layout/BoxLayoutSpec.d.ts.map +0 -1
  384. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts +0 -45
  385. package/src/core/model/node-graph/visual/layout/ConnectedBoxLayouter.d.ts.map +0 -1
  386. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts +0 -24
  387. package/src/core/model/node-graph/visual/layout/ConnectionEndpointLayoutSpec.d.ts.map +0 -1
  388. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts +0 -19
  389. package/src/core/model/node-graph/visual/layout/ConnectionLayoutSpec.d.ts.map +0 -1
  390. package/src/engine/ecs/transform/__probe.d.ts +0 -4
  391. package/src/engine/ecs/transform/__probe.d.ts.map +0 -1
  392. package/src/engine/ecs/util/hideEntityGracefully.d.ts +0 -9
  393. package/src/engine/ecs/util/hideEntityGracefully.d.ts.map +0 -1
  394. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts +0 -27
  395. package/src/shade/renderer/buffer/table/gpu_database_emulator_image.d.ts.map +0 -1
  396. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts +0 -14
  397. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +0 -1
  398. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts +0 -156
  399. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_LAYOUT.d.ts.map +0 -1
  400. package/src/shade/renderer/particles/runtime/Emitter.d.ts +0 -219
  401. package/src/shade/renderer/particles/runtime/Emitter.d.ts.map +0 -1
  402. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts +0 -77
  403. package/src/shade/renderer/particles/runtime/ParticleSpawnScheduler.d.ts.map +0 -1
  404. package/src/shade/renderer/particles/runtime/ProgramArena.js +0 -155
  405. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts +0 -15
  406. package/src/shade/renderer/particles/runtime/emitter_database_image.d.ts.map +0 -1
  407. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts +0 -34
  408. package/src/shade/renderer/particles/runtime/spawn_request_encoding.d.ts.map +0 -1
  409. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts +0 -32
  410. package/src/shade/renderer/particles/shaders/chunk_particle_context.d.ts.map +0 -1
@@ -1,213 +1,376 @@
1
- import { PARTICLE_RECORD_WORD_COUNT } from "../ParticleConstants.js";
2
- import { VM_BUILTIN } from "../isa/ParticleVMISA.js";
3
- import { execute_particle_vm } from "../vm/ParticleVMReference.js";
4
-
5
- /**
6
- * The property every optimizer pass is held to: an optimized program leaves the same record, the
7
- * same kill flag and the same RNG state as the one it was built from — to the bit.
8
- *
9
- * This is a differential harness rather than a set of expected outputs because the interesting
10
- * failures are not "the optimizer produced something odd", they are "the optimizer produced
11
- * something plausible that computes a slightly different number". Running both programs on
12
- * {@link execute_particle_vm} and comparing the whole observable state catches those without anyone
13
- * having to predict them.
14
- *
15
- * ## Bit-identical, and the two relaxations that are named rather than tolerated
16
- *
17
- * Float rewrites that look obviously fine mostly are not, so the default here is that the bits
18
- * match. Two rewrites deliberately do not, and each has a comparison mode that accepts EXACTLY what
19
- * it gives up and nothing else — a blanket tolerance would accept those and also whatever else went
20
- * wrong:
21
- *
22
- * - `absorbed_zero`, for `fold_constants.fold_multiply_by_zero` (on by default). Folding `x * 0` to
23
- * `+0` gives up the sign of that zero, and turns a `NaN` from an upstream overflow into a zero.
24
- * Those two shapes are accepted; a value that merely moved is not.
25
- * - `tolerance`, for `peephole.fuse_multiply_add` (off by default), which is a rounding difference
26
- * and so has no shape to name.
27
- *
28
- * ## Environments
29
- *
30
- * A program reads more than its constants — record words, builtins, the RNG, curve samples — and a
31
- * pass that mishandled any of those could still agree on a single run. {@link PARTICLE_ENVIRONMENTS}
32
- * varies all of them, including a record pre-filled with values a `LOAD_ATTR` can pick up.
33
- */
34
-
35
- /** Distinct, non-round values, so a builtin read from the wrong id or lane lands on a wrong number. */
36
- const BUILTINS_A = [];
37
- BUILTINS_A[VM_BUILTIN.DELTA_TIME] = [0.0125, 0, 0, 0];
38
- BUILTINS_A[VM_BUILTIN.EMITTER_AGE] = [3.5, 0, 0, 0];
39
- BUILTINS_A[VM_BUILTIN.EMITTER_POSITION] = [1.25, -2.5, 7, 1];
40
- BUILTINS_A[VM_BUILTIN.EMITTER_DIRECTION] = [0.6, -0.5, -0.8, 0];
41
- BUILTINS_A[VM_BUILTIN.EMITTER_UP] = [-0.25, 1, 0.375, 0];
42
- BUILTINS_A[VM_BUILTIN.PARTICLE_INDEX] = [11, 0, 0, 0];
43
- BUILTINS_A[VM_BUILTIN.SPAWN_FRACTION] = [0.75, 0, 0, 0];
44
- BUILTINS_A[VM_BUILTIN.TIME] = [12.5, 0, 0, 0];
45
-
46
- /** A second set, negated and shuffled, so nothing passes by holding a builtin's value constant. */
47
- const BUILTINS_B = BUILTINS_A.map(v => v.map(x => -x + 0.5));
48
-
49
- /**
50
- * A record with something in every user word — a program that loads an attribute the harness left
51
- * at zero is a program whose loads are all interchangeable.
52
- *
53
- * @returns {Uint32Array}
54
- */
55
- function seeded_record() {
56
- const record = new Uint32Array(PARTICLE_RECORD_WORD_COUNT);
57
- const floats = new Float32Array(record.buffer);
58
-
59
- for (let i = 1; i < PARTICLE_RECORD_WORD_COUNT; i++) {
60
- floats[i] = (i % 7) - 3.25 + i / 32;
61
- }
62
-
63
- return record;
64
- }
65
-
66
- /** @param {number} handle @param {number} t @returns {number} */
67
- const sample_curve = (handle, t) => Math.sin(handle + t * 3) * 0.5 + handle * 0.125;
68
-
69
- /**
70
- * The contexts a program is compared in. Seeds differ so a pass that dropped or added a draw shows
71
- * up as a diverging RNG state rather than only as a diverging record.
72
- *
73
- * @type {Array<{name: string, builtins: number[][], rng_state: number, record: () => Uint32Array}>}
74
- */
75
- export const PARTICLE_ENVIRONMENTS = [
76
- { name: "zeroed record, seed 1", builtins: BUILTINS_A, rng_state: 1, record: () => new Uint32Array(PARTICLE_RECORD_WORD_COUNT) },
77
- { name: "seeded record, seed 1", builtins: BUILTINS_A, rng_state: 1, record: seeded_record },
78
- { name: "seeded record, seed 0x9e3779b9", builtins: BUILTINS_B, rng_state: 0x9e3779b9, record: seeded_record },
79
- { name: "seeded record, seed 12345", builtins: BUILTINS_B, rng_state: 12345, record: seeded_record },
80
- ];
81
-
82
- /**
83
- * Run one section and return everything a program can be observed to have done.
84
- *
85
- * @param {object} params
86
- * @param {Uint32Array} params.words
87
- * @param {Float32Array|number[]} params.constants
88
- * @param {number} params.register_count
89
- * @param {object} params.environment one of {@link PARTICLE_ENVIRONMENTS}
90
- * @returns {{ record: Uint32Array, killed: boolean, rng_state: number }}
91
- */
92
- export function run_particle_section({ words, constants, register_count, environment }) {
93
- const record = environment.record();
94
-
95
- const result = execute_particle_vm({
96
- words,
97
- constants: constants instanceof Float32Array ? constants : new Float32Array(constants),
98
- register_count,
99
- record,
100
- builtins: environment.builtins,
101
- rng_state: environment.rng_state,
102
- sampleCurve: sample_curve,
103
- });
104
-
105
- return { record, killed: result.killed, rng_state: result.rng_state };
106
- }
107
-
108
- /**
109
- * Compare two runs. Record words are compared as raw bits first — which is the strict answer,
110
- * including the sign of a zero — and only a pair that differs is re-read as floats, so a `NaN` on
111
- * both sides counts as agreement whatever payload it carries.
112
- *
113
- * @param {{record: Uint32Array, killed: boolean, rng_state: number}} before
114
- * @param {{record: Uint32Array, killed: boolean, rng_state: number}} after
115
- * @param {object} [comparison]
116
- * @param {number} [comparison.tolerance] relative slack on record words; zero means the bits match
117
- * @param {boolean} [comparison.absorbed_zero] accept the two things `x * 0 -> +0` gives up: a `-0`
118
- * that became a `+0`, and a `NaN` that became a zero. Nothing else — a number that moved is still
119
- * a failure, and a zero that became a `NaN` is a failure in the direction that cannot happen.
120
- * @returns {string|null} what differs, or `null` when nothing does
121
- */
122
- export function diff_particle_section_result(before, after, { tolerance = 0, absorbed_zero = false } = {}) {
123
- if (before.killed !== after.killed) {
124
- return `kill flag: ${before.killed} -> ${after.killed}`;
125
- }
126
-
127
- if (before.rng_state !== after.rng_state) {
128
- return `RNG state: ${before.rng_state} -> ${after.rng_state}`;
129
- }
130
-
131
- const a = new Float32Array(before.record.buffer);
132
- const b = new Float32Array(after.record.buffer);
133
-
134
- for (let i = 0; i < before.record.length; i++) {
135
- if (before.record[i] === after.record[i]) {
136
- continue;
137
- }
138
-
139
- if (Number.isNaN(a[i]) && Number.isNaN(b[i])) {
140
- continue;
141
- }
142
-
143
- // `===` rather than `Object.is`, so the two zeros compare equal and nothing else does.
144
- if (absorbed_zero && (a[i] === b[i] || (Number.isNaN(a[i]) && b[i] === 0))) {
145
- continue;
146
- }
147
-
148
- if (tolerance > 0 && Math.abs(a[i] - b[i]) <= tolerance * Math.max(1, Math.abs(a[i]), Math.abs(b[i]))) {
149
- continue;
150
- }
151
-
152
- return `record word ${i}: ${a[i]} (0x${before.record[i].toString(16)})`
153
- + ` -> ${b[i]} (0x${after.record[i].toString(16)})`;
154
- }
155
-
156
- return null;
157
- }
158
-
159
- /**
160
- * Compare an optimized section against the original across every environment.
161
- *
162
- * The two may have different register counts and different constant pools — the optimizer shrinks
163
- * one and may extend the other — so both are passed per side rather than assumed shared.
164
- *
165
- * @param {object} params
166
- * @param {{words: Uint32Array, constants: Float32Array|number[], register_count: number}} params.before
167
- * @param {{words: Uint32Array, constants: Float32Array|number[], register_count: number}} params.after
168
- * @param {Array<object>} [params.environments]
169
- * @param {object} [params.comparison] see {@link diff_particle_section_result}
170
- * @returns {string|null} a description of the first divergence, or `null`
171
- */
172
- export function diff_particle_sections({ before, after, environments = PARTICLE_ENVIRONMENTS, comparison }) {
173
- for (const environment of environments) {
174
- const difference = diff_particle_section_result(
175
- run_particle_section({ ...before, environment }),
176
- run_particle_section({ ...after, environment }),
177
- comparison
178
- );
179
-
180
- if (difference !== null) {
181
- return `${environment.name}: ${difference}`;
182
- }
183
- }
184
-
185
- return null;
186
- }
187
-
188
- /**
189
- * Compare every section of two compiled programs.
190
- *
191
- * @param {ParticleProgram} before
192
- * @param {ParticleProgram} after
193
- * @param {object} [comparison] see {@link diff_particle_section_result}, plus `environments`
194
- * @returns {string|null}
195
- */
196
- export function diff_particle_programs(before, after, comparison = {}) {
197
- const { environments = PARTICLE_ENVIRONMENTS, ...rest } = comparison;
198
-
199
- for (const section of ["init", "update"]) {
200
- const difference = diff_particle_sections({
201
- before: { words: before[section], constants: before.constants, register_count: before.register_count },
202
- after: { words: after[section], constants: after.constants, register_count: after.register_count },
203
- environments,
204
- comparison: rest,
205
- });
206
-
207
- if (difference !== null) {
208
- return `${section}: ${difference}`;
209
- }
210
- }
211
-
212
- return null;
213
- }
1
+ import { PARTICLE_RECORD_WORD_COUNT, PARTICLE_VM_INSTRUCTION_WORDS, PARTICLE_VM_REGISTER_SLOTS } from "../ParticleConstants.js";
2
+ import { VM_BUILTIN, VM_KIND, VM_MAX_WIDTH, VM_OP } from "../isa/ParticleVMISA.js";
3
+ import { execute_particle_vm } from "../vm/ParticleVMReference.js";
4
+ import { decode_particle_instructions, particle_read_slots } from "./particle_program_analysis.js";
5
+ import { PARTICLE_ZERO_ABSORBING_OPS } from "./pass_fold_constants.js";
6
+
7
+ /**
8
+ * The property every optimizer pass is held to: an optimized program leaves the same record, the
9
+ * same kill flag and the same RNG state as the one it was built from — to the bit.
10
+ *
11
+ * This is a differential harness rather than a set of expected outputs because the interesting
12
+ * failures are not "the optimizer produced something odd", they are "the optimizer produced
13
+ * something plausible that computes a slightly different number". Running both programs on
14
+ * {@link execute_particle_vm} and comparing the whole observable state catches those without anyone
15
+ * having to predict them.
16
+ *
17
+ * ## Bit-identical, and the two relaxations that are named rather than tolerated
18
+ *
19
+ * Float rewrites that look obviously fine mostly are not, so the default here is that the bits
20
+ * match. Two rewrites deliberately do not, and each has a comparison mode that accepts EXACTLY what
21
+ * it gives up and nothing else — a blanket tolerance would accept those and also whatever else went
22
+ * wrong:
23
+ *
24
+ * - `reached_by_zero_fold`, for `fold_constants.fold_multiply_by_zero` (on by default).
25
+ * - `tolerance`, for `peephole.fuse_multiply_add` (off by default), which is a rounding difference
26
+ * and so has no shape to name.
27
+ *
28
+ * The third relaxation, `fold_constants.fold_inexact`, needs nothing here, and that absence is the
29
+ * point of it: the fold evaluates each operation the way the reference executes it, so a program
30
+ * with the transcendentals folded is bit-identical HERE and differs only against a GPU. Which is
31
+ * why this harness cannot vouch for that one — `../vm/chunk_particle_vm.spec.js` is where the two
32
+ * executors meet, and its 1e-4 tolerance is exactly the slack the option spends.
33
+ *
34
+ * ## Why the zero fold is a reachability question and not a shape one
35
+ *
36
+ * This mode used to be called `absorbed_zero` and accepted two record shapes: a `-0` that had become
37
+ * a `+0`, and a `NaN` that had become a zero. Those are what folding `x * 0` to `+0` gives up AT THE
38
+ * MULTIPLY — and the record is not the multiply. The substitution lands in a register, and the next
39
+ * instruction to read that register is free to turn it into anything:
40
+ *
41
+ * LOG r1, r0 ; r0 is zero, so r1 is -Inf
42
+ * SCALE r3, r2, r1 ; r2 is -0. -0 * -Inf is NaN; folded, r3 is +0
43
+ * CMP_GE r4, r3, r2 ; NaN >= -0 is false. +0 >= -0 is true. The record word is 0, then 1.
44
+ *
45
+ * A `0` that became a `1` is neither of the two shapes, and no per-word test on the record can name
46
+ * it — a division by the substituted zero turns the same `NaN` into an `Infinity` instead, and one
47
+ * more comparison launders that back into a finite number. Both of those came out of the fuzzer
48
+ * (`random_particle_program` seeds 148 and 2743), past the range the specs were sweeping.
49
+ *
50
+ * So what is bounded is not the VALUE but the REACH. {@link particle_multiply_reach} marks every
51
+ * record word a `MUL` or `SCALE` result can flow into, statically and without caring which multiply
52
+ * had a zero operand; the mode stops comparing those words and holds every other one to the bit.
53
+ * Over the fuzz corpus that leaves about 94% of the stored words still compared exactly, which is
54
+ * the part of the promise that survived — a pass that moved a value the fold never touched is still
55
+ * caught, and only the words genuinely downstream of a multiply are given up.
56
+ *
57
+ * ## Environments
58
+ *
59
+ * A program reads more than its constants — record words, builtins, the RNG, curve samples — and a
60
+ * pass that mishandled any of those could still agree on a single run. {@link PARTICLE_ENVIRONMENTS}
61
+ * varies all of them, including a record pre-filled with values a `LOAD_ATTR` can pick up.
62
+ */
63
+
64
+ /** Distinct, non-round values, so a builtin read from the wrong id or lane lands on a wrong number. */
65
+ const BUILTINS_A = [];
66
+ BUILTINS_A[VM_BUILTIN.DELTA_TIME] = [0.0125, 0, 0, 0];
67
+ BUILTINS_A[VM_BUILTIN.EMITTER_AGE] = [3.5, 0, 0, 0];
68
+ BUILTINS_A[VM_BUILTIN.EMITTER_POSITION] = [1.25, -2.5, 7, 1];
69
+ BUILTINS_A[VM_BUILTIN.EMITTER_DIRECTION] = [0.6, -0.5, -0.8, 0];
70
+ BUILTINS_A[VM_BUILTIN.EMITTER_UP] = [-0.25, 1, 0.375, 0];
71
+ BUILTINS_A[VM_BUILTIN.PARTICLE_INDEX] = [11, 0, 0, 0];
72
+ BUILTINS_A[VM_BUILTIN.SPAWN_FRACTION] = [0.75, 0, 0, 0];
73
+ BUILTINS_A[VM_BUILTIN.TIME] = [12.5, 0, 0, 0];
74
+
75
+ /** A second set, negated and shuffled, so nothing passes by holding a builtin's value constant. */
76
+ const BUILTINS_B = BUILTINS_A.map(v => v.map(x => -x + 0.5));
77
+
78
+ /**
79
+ * A record with something in every user word — a program that loads an attribute the harness left
80
+ * at zero is a program whose loads are all interchangeable.
81
+ *
82
+ * @returns {Uint32Array}
83
+ */
84
+ function seeded_record() {
85
+ const record = new Uint32Array(PARTICLE_RECORD_WORD_COUNT);
86
+ const floats = new Float32Array(record.buffer);
87
+
88
+ for (let i = 1; i < PARTICLE_RECORD_WORD_COUNT; i++) {
89
+ floats[i] = (i % 7) - 3.25 + i / 32;
90
+ }
91
+
92
+ return record;
93
+ }
94
+
95
+ /** @param {number} handle @param {number} t @returns {number} */
96
+ const sample_curve = (handle, t) => Math.sin(handle + t * 3) * 0.5 + handle * 0.125;
97
+
98
+ /**
99
+ * The contexts a program is compared in. Seeds differ so a pass that dropped or added a draw shows
100
+ * up as a diverging RNG state rather than only as a diverging record.
101
+ *
102
+ * @type {Array<{name: string, builtins: number[][], rng_state: number, record: () => Uint32Array}>}
103
+ */
104
+ export const PARTICLE_ENVIRONMENTS = [
105
+ { name: "zeroed record, seed 1", builtins: BUILTINS_A, rng_state: 1, record: () => new Uint32Array(PARTICLE_RECORD_WORD_COUNT) },
106
+ { name: "seeded record, seed 1", builtins: BUILTINS_A, rng_state: 1, record: seeded_record },
107
+ { name: "seeded record, seed 0x9e3779b9", builtins: BUILTINS_B, rng_state: 0x9e3779b9, record: seeded_record },
108
+ { name: "seeded record, seed 12345", builtins: BUILTINS_B, rng_state: 12345, record: seeded_record },
109
+ ];
110
+
111
+ /**
112
+ * Run one section and return everything a program can be observed to have done.
113
+ *
114
+ * @param {object} params
115
+ * @param {Uint32Array} params.words
116
+ * @param {Float32Array|number[]} params.constants
117
+ * @param {number} params.register_count
118
+ * @param {object} params.environment one of {@link PARTICLE_ENVIRONMENTS}
119
+ * @returns {{ record: Uint32Array, killed: boolean, rng_state: number }}
120
+ */
121
+ export function run_particle_section({ words, constants, register_count, environment }) {
122
+ const record = environment.record();
123
+
124
+ const result = execute_particle_vm({
125
+ words,
126
+ constants: constants instanceof Float32Array ? constants : new Float32Array(constants),
127
+ register_count,
128
+ record,
129
+ builtins: environment.builtins,
130
+ rng_state: environment.rng_state,
131
+ sampleCurve: sample_curve,
132
+ });
133
+
134
+ return { record, killed: result.killed, rng_state: result.rng_state };
135
+ }
136
+
137
+ /**
138
+ * Which record words — and whether the kill flag — the result of a zero-absorbing opcode can reach.
139
+ *
140
+ * This is the bound on `fold_constants.fold_multiply_by_zero`, and it is deliberately blunt in the
141
+ * one direction that keeps it sound: every instruction in
142
+ * {@link PARTICLE_ZERO_ABSORBING_OPS} is treated as though its result had been substituted, because
143
+ * which of them had a compile-time zero operand is a fact about the optimizer's analysis and not
144
+ * about the program. The set comes from the pass rather than being restated here, so an opcode added
145
+ * to the rewrite is an opcode this follows. Everything the marked slots flow into is marked too, and a
146
+ * store carries the mark into the record word it writes.
147
+ *
148
+ * A `LOAD_ATTR` of a marked record word marks its destination back, so a value that went out through
149
+ * the record and came back in is still followed. Slots are cleared by a write whose sources are all
150
+ * unmarked — the value there is then the same in both programs — while record words are only ever
151
+ * added to, because a store cannot un-write what an earlier one already made unequal.
152
+ *
153
+ * Static, so it over-approximates the runs: a lane that never actually held a substituted zero is
154
+ * marked anyway. That is the right direction to be wrong in, and it is not blunt in practice — see
155
+ * the 94% in the module comment.
156
+ *
157
+ * @param {Uint32Array} words a packed instruction section
158
+ * @returns {{record: Uint8Array, killed: boolean}}
159
+ */
160
+ export function particle_multiply_reach(words) {
161
+ const decoded = decode_particle_instructions(instruction_tuples(words));
162
+
163
+ /** One flag per register slot, with room for an operand run that ends on the last of them. */
164
+ const slot = new Uint8Array(PARTICLE_VM_REGISTER_SLOTS + VM_MAX_WIDTH);
165
+ const record = new Uint8Array(PARTICLE_RECORD_WORD_COUNT);
166
+
167
+ let killed = false;
168
+
169
+ for (const instruction of decoded) {
170
+ // Nothing past a `HALT` runs, so nothing past it can carry anything anywhere.
171
+ if (instruction.op === VM_OP.HALT) {
172
+ break;
173
+ }
174
+
175
+ let reached = false;
176
+
177
+ for (const read of instruction.reads) {
178
+ for (const s of particle_read_slots(read)) {
179
+ reached = reached || slot[s] === 1;
180
+ }
181
+ }
182
+
183
+ // The record operands are read straight out of the words: `LOAD_ATTR` keeps its offset and
184
+ // component count in s0 and s1, `STORE_ATTR` in s1 and s2 behind the value it stores.
185
+ if (instruction.op === VM_OP.LOAD_ATTR) {
186
+ const offset = instruction.words[1];
187
+ const components = instruction.words[2];
188
+
189
+ for (let lane = 0; lane < components; lane++) {
190
+ reached = reached || record[offset + lane] === 1;
191
+ }
192
+ }
193
+
194
+ if (instruction.op === VM_OP.STORE_ATTR) {
195
+ const offset = instruction.words[2];
196
+ const components = instruction.words[3];
197
+ const value = instruction.sources[0];
198
+ const stored = value.kind === VM_KIND.REG ? particle_read_slots(value, components) : [];
199
+
200
+ for (let lane = 0; lane < components; lane++) {
201
+ record[offset + lane] |= lane < stored.length ? slot[stored[lane]] : 0;
202
+ }
203
+
204
+ continue;
205
+ }
206
+
207
+ if (instruction.op === VM_OP.KILL) {
208
+ killed = killed || reached;
209
+ continue;
210
+ }
211
+
212
+ if (instruction.write === null) {
213
+ continue;
214
+ }
215
+
216
+ const substitutes = PARTICLE_ZERO_ABSORBING_OPS.has(instruction.op);
217
+
218
+ for (let lane = 0; lane < instruction.write.span; lane++) {
219
+ slot[instruction.write.base + lane] = substitutes || reached ? 1 : 0;
220
+ }
221
+ }
222
+
223
+ return { record, killed };
224
+ }
225
+
226
+ /**
227
+ * The reach of both sides at once. The two programs address different registers — packing moves
228
+ * them — so the sections are walked separately and only the record words are put together.
229
+ *
230
+ * @param {Uint32Array} before
231
+ * @param {Uint32Array} after
232
+ * @returns {{record: Uint8Array, killed: boolean}}
233
+ */
234
+ function combined_multiply_reach(before, after) {
235
+ const a = particle_multiply_reach(before);
236
+ const b = particle_multiply_reach(after);
237
+
238
+ for (let i = 0; i < a.record.length; i++) {
239
+ a.record[i] |= b.record[i];
240
+ }
241
+
242
+ return { record: a.record, killed: a.killed || b.killed };
243
+ }
244
+
245
+ /**
246
+ * A packed section as the four-word tuples {@link decode_particle_instructions} reads.
247
+ *
248
+ * @param {Uint32Array} words
249
+ * @returns {Uint32Array[]} views, so nothing is copied
250
+ */
251
+ function instruction_tuples(words) {
252
+ const out = [];
253
+
254
+ for (let base = 0; base < words.length; base += PARTICLE_VM_INSTRUCTION_WORDS) {
255
+ out.push(words.subarray(base, base + PARTICLE_VM_INSTRUCTION_WORDS));
256
+ }
257
+
258
+ return out;
259
+ }
260
+
261
+ /**
262
+ * Compare two runs. Record words are compared as raw bits first — which is the strict answer,
263
+ * including the sign of a zero — and only a pair that differs is re-read as floats, so a `NaN` on
264
+ * both sides counts as agreement whatever payload it carries.
265
+ *
266
+ * @param {{record: Uint32Array, killed: boolean, rng_state: number}} before
267
+ * @param {{record: Uint32Array, killed: boolean, rng_state: number}} after
268
+ * @param {object} [comparison]
269
+ * @param {number} [comparison.tolerance] relative slack on record words; zero means the bits match
270
+ * @param {{record: Uint8Array, killed: boolean}|null} [comparison.reach] what the zero fold is
271
+ * allowed to have changed, from {@link particle_multiply_reach}. A marked word is not compared at
272
+ * all — there is no shape left to hold it to, which is the whole of the argument in the module
273
+ * comment — and every other one is held to the bit.
274
+ * @returns {string|null} what differs, or `null` when nothing does
275
+ */
276
+ export function diff_particle_section_result(before, after, { tolerance = 0, reach = null } = {}) {
277
+ if (before.killed !== after.killed && !(reach !== null && reach.killed)) {
278
+ return `kill flag: ${before.killed} -> ${after.killed}`;
279
+ }
280
+
281
+ // Never relaxed. `RANDOM` has side effects, so no pass may add, drop or reorder a draw, and a
282
+ // substituted zero cannot reach the stream either — there is nothing in this ISA that makes a
283
+ // draw conditional on a value.
284
+ if (before.rng_state !== after.rng_state) {
285
+ return `RNG state: ${before.rng_state} -> ${after.rng_state}`;
286
+ }
287
+
288
+ const a = new Float32Array(before.record.buffer);
289
+ const b = new Float32Array(after.record.buffer);
290
+
291
+ for (let i = 0; i < before.record.length; i++) {
292
+ if (before.record[i] === after.record[i]) {
293
+ continue;
294
+ }
295
+
296
+ if (Number.isNaN(a[i]) && Number.isNaN(b[i])) {
297
+ continue;
298
+ }
299
+
300
+ if (reach !== null && reach.record[i] === 1) {
301
+ continue;
302
+ }
303
+
304
+ if (tolerance > 0 && Math.abs(a[i] - b[i]) <= tolerance * Math.max(1, Math.abs(a[i]), Math.abs(b[i]))) {
305
+ continue;
306
+ }
307
+
308
+ return `record word ${i}: ${a[i]} (0x${before.record[i].toString(16)})`
309
+ + ` -> ${b[i]} (0x${after.record[i].toString(16)})`;
310
+ }
311
+
312
+ return null;
313
+ }
314
+
315
+ /**
316
+ * Compare an optimized section against the original across every environment.
317
+ *
318
+ * The two may have different register counts and different constant pools — the optimizer shrinks
319
+ * one and may extend the other — so both are passed per side rather than assumed shared.
320
+ *
321
+ * @param {object} params
322
+ * @param {{words: Uint32Array, constants: Float32Array|number[], register_count: number}} params.before
323
+ * @param {{words: Uint32Array, constants: Float32Array|number[], register_count: number}} params.after
324
+ * @param {Array<object>} [params.environments]
325
+ * @param {object} [params.comparison] see {@link diff_particle_section_result}, plus
326
+ * `reached_by_zero_fold` — the reach is a property of the two SECTIONS rather than of a run, so it
327
+ * is worked out once here and handed down as `reach`
328
+ * @returns {string|null} a description of the first divergence, or `null`
329
+ */
330
+ export function diff_particle_sections({ before, after, environments = PARTICLE_ENVIRONMENTS, comparison }) {
331
+ const { reached_by_zero_fold = false, ...rest } = comparison ?? {};
332
+ const resolved = reached_by_zero_fold
333
+ ? { ...rest, reach: combined_multiply_reach(before.words, after.words) }
334
+ : rest;
335
+
336
+ for (const environment of environments) {
337
+ const difference = diff_particle_section_result(
338
+ run_particle_section({ ...before, environment }),
339
+ run_particle_section({ ...after, environment }),
340
+ resolved
341
+ );
342
+
343
+ if (difference !== null) {
344
+ return `${environment.name}: ${difference}`;
345
+ }
346
+ }
347
+
348
+ return null;
349
+ }
350
+
351
+ /**
352
+ * Compare every section of two compiled programs.
353
+ *
354
+ * @param {ParticleProgram} before
355
+ * @param {ParticleProgram} after
356
+ * @param {object} [comparison] see {@link diff_particle_sections}, plus `environments`
357
+ * @returns {string|null}
358
+ */
359
+ export function diff_particle_programs(before, after, comparison = {}) {
360
+ const { environments = PARTICLE_ENVIRONMENTS, ...rest } = comparison;
361
+
362
+ for (const section of ["init", "update"]) {
363
+ const difference = diff_particle_sections({
364
+ before: { words: before[section], constants: before.constants, register_count: before.register_count },
365
+ after: { words: after[section], constants: after.constants, register_count: after.register_count },
366
+ environments,
367
+ comparison: rest,
368
+ });
369
+
370
+ if (difference !== null) {
371
+ return `${section}: ${difference}`;
372
+ }
373
+ }
374
+
375
+ return null;
376
+ }