@woosh/meep-engine 2.154.0 → 2.156.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 (487) hide show
  1. package/README.md +1 -1
  2. package/build/bundle-worker-image-decoder.js +1 -1
  3. package/build/bundle-worker-terrain.js +1 -1
  4. package/editor/view/ecs/ComponentControlView.d.ts +0 -9
  5. package/editor/view/ecs/ComponentControlView.js +2 -98
  6. package/package.json +1 -1
  7. package/src/core/binary/32BitEncoder.js +1 -1
  8. package/src/core/binary/to_half_float_uint16.js +3 -3
  9. package/src/core/bvh2/bvh3/ebvh_build_hierarchy_radix.d.ts.map +1 -1
  10. package/src/core/bvh2/bvh3/ebvh_build_hierarchy_radix.js +275 -253
  11. package/src/core/cache/Cache.d.ts.map +1 -1
  12. package/src/core/cache/Cache.js +7 -0
  13. package/src/core/cache/FrequencySketch.d.ts.map +1 -1
  14. package/src/core/cache/FrequencySketch.js +8 -4
  15. package/src/core/clipboard/obtainClipBoard.d.ts +6 -0
  16. package/src/core/clipboard/obtainClipBoard.d.ts.map +1 -0
  17. package/src/core/clipboard/obtainClipBoard.js +29 -0
  18. package/src/core/clipboard/safeClipboardReadText.d.ts +6 -0
  19. package/src/core/clipboard/safeClipboardReadText.d.ts.map +1 -0
  20. package/src/core/clipboard/safeClipboardReadText.js +55 -0
  21. package/src/core/clipboard/safeClipboardWriteText.d.ts +8 -0
  22. package/src/core/clipboard/safeClipboardWriteText.d.ts.map +1 -0
  23. package/src/core/clipboard/safeClipboardWriteText.js +23 -0
  24. package/src/core/collection/array/array_quick_sort_by_lookup_map.js +1 -1
  25. package/src/core/collection/array/array_set_diff_sorting.d.ts.map +1 -1
  26. package/src/core/collection/array/array_set_diff_sorting.js +4 -1
  27. package/src/core/collection/array/array_shuffle.d.ts.map +1 -1
  28. package/src/core/collection/array/array_shuffle.js +30 -27
  29. package/src/core/collection/array/binarySearchLowIndex.d.ts.map +1 -1
  30. package/src/core/collection/array/binarySearchLowIndex.js +4 -3
  31. package/src/core/collection/array/typed/array_buffer_hash.js +1 -1
  32. package/src/core/collection/array/typed/is_typed_array_equals.d.ts.map +1 -1
  33. package/src/core/collection/array/typed/is_typed_array_equals.js +12 -2
  34. package/src/core/collection/heap/BinaryHeap.d.ts.map +1 -1
  35. package/src/core/collection/heap/BinaryHeap.js +12 -2
  36. package/src/core/collection/queue/Deque.d.ts.map +1 -1
  37. package/src/core/collection/queue/Deque.js +10 -8
  38. package/src/core/collection/table/RowFirstTable.d.ts.map +1 -1
  39. package/src/core/collection/table/RowFirstTable.js +4 -2
  40. package/src/core/collection/table/RowFirstTableSpec.js +2 -2
  41. package/src/core/color/operations/color_lerp.d.ts.map +1 -1
  42. package/src/core/color/operations/color_lerp.js +10 -3
  43. package/src/core/color/rgb2uint32.js +1 -1
  44. package/src/core/color/rgbe9995_to_rgb.js +1 -1
  45. package/src/core/function/objectsEqual.d.ts.map +1 -1
  46. package/src/core/function/objectsEqual.js +2 -1
  47. package/src/core/geom/2d/aabb/AABB2.d.ts.map +1 -1
  48. package/src/core/geom/2d/aabb/AABB2.js +12 -11
  49. package/src/core/geom/2d/convex-hull/convex_hull_jarvis_2d.d.ts.map +1 -1
  50. package/src/core/geom/2d/convex-hull/convex_hull_jarvis_2d.js +30 -4
  51. package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.d.ts.map +1 -1
  52. package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +6 -2
  53. package/src/core/geom/2d/hash-grid/SpatialHashGrid.d.ts.map +1 -1
  54. package/src/core/geom/2d/hash-grid/SpatialHashGrid.js +388 -386
  55. package/src/core/geom/2d/hash-grid/shg_query_elements_line.d.ts.map +1 -1
  56. package/src/core/geom/2d/hash-grid/shg_query_elements_line.js +8 -3
  57. package/src/core/geom/2d/quad-tree/QuadTreeDatum.d.ts.map +1 -1
  58. package/src/core/geom/2d/quad-tree/QuadTreeDatum.js +9 -1
  59. package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.d.ts +3 -1
  60. package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.d.ts.map +1 -1
  61. package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +3 -1
  62. package/src/core/geom/2d/quad-tree-binary/QuadTree.js +714 -714
  63. package/src/core/geom/2d/r-tree/StaticR2Tree.d.ts.map +1 -1
  64. package/src/core/geom/2d/r-tree/StaticR2Tree.js +5 -4
  65. package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.d.ts.map +1 -1
  66. package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +33 -29
  67. package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.d.ts.map +1 -1
  68. package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +3 -1
  69. package/src/core/geom/3d/aabb/aabb3_signed_distance_to_aabb3.d.ts.map +1 -1
  70. package/src/core/geom/3d/aabb/aabb3_signed_distance_to_aabb3.js +10 -7
  71. package/src/core/geom/3d/aabb/aabb3_transformed_compute_plane_side.d.ts.map +1 -1
  72. package/src/core/geom/3d/aabb/aabb3_transformed_compute_plane_side.js +30 -9
  73. package/src/core/geom/3d/aabb/compute_aabb_from_points.js +3 -3
  74. package/src/core/geom/3d/box/box3_raycast.d.ts +37 -0
  75. package/src/core/geom/3d/box/box3_raycast.d.ts.map +1 -0
  76. package/src/core/geom/3d/box/box3_raycast.js +81 -0
  77. package/src/core/geom/3d/capsule/capsule_raycast.d.ts +35 -0
  78. package/src/core/geom/3d/capsule/capsule_raycast.d.ts.map +1 -0
  79. package/src/core/geom/3d/capsule/capsule_raycast.js +93 -0
  80. package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.d.ts.map +1 -1
  81. package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +4 -0
  82. package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +1 -1
  83. package/src/core/geom/3d/line/line3_compute_segment_point_distance_eikonal.d.ts.map +1 -1
  84. package/src/core/geom/3d/line/line3_compute_segment_point_distance_eikonal.js +3 -2
  85. package/src/core/geom/3d/mat4/decompose_matrix_4_array.d.ts.map +1 -1
  86. package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +12 -2
  87. package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +2 -2
  88. package/src/core/geom/3d/mat4/m4_multiply_alphatensor.d.ts +1 -1
  89. package/src/core/geom/3d/mat4/m4_multiply_alphatensor.d.ts.map +1 -1
  90. package/src/core/geom/3d/mat4/m4_multiply_alphatensor.js +19 -13
  91. package/src/core/geom/3d/octahedra/octahedral_direction_to_uv.d.ts.map +1 -1
  92. package/src/core/geom/3d/octahedra/octahedral_direction_to_uv.js +3 -2
  93. package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +3 -2
  94. package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -1
  95. package/src/core/geom/3d/shape/MeshShape3D.js +7 -0
  96. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  97. package/src/core/geom/3d/shape/UnionShape3D.js +3 -2
  98. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.d.ts.map +1 -1
  99. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +153 -148
  100. package/src/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.d.ts.map +1 -1
  101. package/src/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.js +7 -0
  102. package/src/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.d.ts.map +1 -1
  103. package/src/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.js +13 -10
  104. package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.d.ts +1 -1
  105. package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +2 -2
  106. package/src/core/geom/3d/sphere/sphere_raycast.d.ts +33 -0
  107. package/src/core/geom/3d/sphere/sphere_raycast.d.ts.map +1 -0
  108. package/src/core/geom/3d/sphere/sphere_raycast.js +47 -0
  109. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.d.ts +24 -0
  110. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.d.ts.map +1 -0
  111. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +39 -0
  112. package/src/core/geom/3d/tetrahedra/triangle/trace_triangular_depth_map.d.ts.map +1 -1
  113. package/src/core/geom/3d/tetrahedra/triangle/trace_triangular_depth_map.js +4 -2
  114. package/src/core/geom/3d/topology/bounds/computeTriangleClusterNormalBoundingCone.js +3 -3
  115. package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.js +1 -1
  116. package/src/core/geom/3d/topology/tm_vertex_compute_normal.d.ts.map +1 -1
  117. package/src/core/geom/3d/topology/tm_vertex_compute_normal.js +4 -2
  118. package/src/core/geom/3d/util/make_justified_point_grid.d.ts.map +1 -1
  119. package/src/core/geom/3d/util/make_justified_point_grid.js +18 -10
  120. package/src/core/geom/ConicRay.d.ts.map +1 -1
  121. package/src/core/geom/ConicRay.js +11 -13
  122. package/src/core/geom/packing/max-rect/removeRedundantBoxes.d.ts.map +1 -1
  123. package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +19 -4
  124. package/src/core/geom/vec3/v3_array_copy.d.ts +3 -3
  125. package/src/core/geom/vec3/v3_array_copy.d.ts.map +1 -1
  126. package/src/core/geom/vec3/v3_array_copy.js +2 -2
  127. package/src/core/geom/vec3/v3_cross.d.ts +17 -0
  128. package/src/core/geom/vec3/v3_cross.d.ts.map +1 -0
  129. package/src/core/geom/vec3/v3_cross.js +20 -0
  130. package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.d.ts.map +1 -0
  131. package/src/{engine/graphics/sh3/path_tracer/sampling → core/geom/vec3}/v3_orthonormal_matrix_from_normal.js +1 -1
  132. package/src/core/geom/vec3/v3_subtract.d.ts +16 -0
  133. package/src/core/geom/vec3/v3_subtract.d.ts.map +1 -0
  134. package/src/core/geom/vec3/v3_subtract.js +19 -0
  135. package/src/core/graph/coloring/colorizeGraph.js +2 -2
  136. package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
  137. package/src/core/graph/csr/CSRGraph.js +325 -319
  138. package/src/core/graph/layout/CircleLayout.d.ts.map +1 -1
  139. package/src/core/graph/layout/CircleLayout.js +8 -6
  140. package/src/core/graph/metis/native/refine/compute_kway_params.d.ts.map +1 -1
  141. package/src/core/graph/metis/native/refine/compute_kway_params.js +139 -138
  142. package/src/core/graph/mn_graph_coarsen.d.ts.map +1 -1
  143. package/src/core/graph/mn_graph_coarsen.js +4 -2
  144. package/src/core/graph/v2/NodeContainer.js +7 -7
  145. package/src/core/localization/LocalizationEngine.js +1 -1
  146. package/src/core/math/bell_membership_function.d.ts.map +1 -1
  147. package/src/core/math/bell_membership_function.js +3 -1
  148. package/src/core/math/complex/complex_add.d.ts +4 -4
  149. package/src/core/math/complex/complex_add.d.ts.map +1 -1
  150. package/src/core/math/complex/complex_add.js +3 -3
  151. package/src/core/math/complex/complex_div.d.ts +4 -4
  152. package/src/core/math/complex/complex_div.d.ts.map +1 -1
  153. package/src/core/math/complex/complex_div.js +3 -3
  154. package/src/core/math/complex/complex_mul.d.ts +4 -4
  155. package/src/core/math/complex/complex_mul.d.ts.map +1 -1
  156. package/src/core/math/complex/complex_mul.js +3 -3
  157. package/src/core/math/complex/complex_sub.d.ts +4 -4
  158. package/src/core/math/complex/complex_sub.d.ts.map +1 -1
  159. package/src/core/math/complex/complex_sub.js +3 -3
  160. package/src/core/math/idct_1d.d.ts +4 -4
  161. package/src/core/math/idct_1d.d.ts.map +1 -1
  162. package/src/core/math/idct_1d.js +3 -3
  163. package/src/core/math/noise/create_simplex_noise_2d.d.ts.map +1 -1
  164. package/src/core/math/noise/create_simplex_noise_2d.js +4 -2
  165. package/src/core/math/noise/sdnoise.d.ts.map +1 -1
  166. package/src/core/math/noise/sdnoise.js +12 -9
  167. package/src/core/math/physics/mie/compute_bhmie_optical_properties.d.ts.map +1 -1
  168. package/src/core/math/physics/mie/compute_bhmie_optical_properties.js +94 -50
  169. package/src/core/math/physics/mie/lorenz_mie_coefs.d.ts +3 -6
  170. package/src/core/math/physics/mie/lorenz_mie_coefs.d.ts.map +1 -1
  171. package/src/core/math/physics/mie/lorenz_mie_coefs.js +180 -157
  172. package/src/core/math/physics/mie/mie_ab_to_optical_properties.d.ts +3 -4
  173. package/src/core/math/physics/mie/mie_ab_to_optical_properties.d.ts.map +1 -1
  174. package/src/core/math/physics/mie/mie_ab_to_optical_properties.js +47 -21
  175. package/src/core/math/random/randomIntegerBetween.d.ts.map +1 -1
  176. package/src/core/math/random/randomIntegerBetween.js +4 -1
  177. package/src/core/math/solveCubic.d.ts.map +1 -1
  178. package/src/core/math/solveCubic.js +95 -82
  179. package/src/core/math/spline/computeCatmullRomSplineUniformDistance.d.ts.map +1 -1
  180. package/src/core/math/spline/computeCatmullRomSplineUniformDistance.js +13 -0
  181. package/src/core/math/statistics/softmax.js +1 -1
  182. package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts +1 -0
  183. package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts.map +1 -1
  184. package/src/core/model/node-graph/visual/NodeGraphVisualData.js +2 -1
  185. package/src/core/model/node-graph/visual/NodeVisualData.js +1 -1
  186. package/src/core/model/object/ImmutableObjectPool.d.ts +7 -0
  187. package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
  188. package/src/core/model/object/ImmutableObjectPool.js +20 -10
  189. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.d.ts.map +1 -1
  190. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.js +39 -2
  191. package/src/core/model/reactive/model/terminal/ReactiveReference.d.ts.map +1 -1
  192. package/src/core/model/reactive/model/terminal/ReactiveReference.js +2 -0
  193. package/src/core/parser/simple/readHexToken.d.ts.map +1 -1
  194. package/src/core/parser/simple/readHexToken.js +6 -0
  195. package/src/core/primitives/numbers/number_pretty_print.d.ts.map +1 -1
  196. package/src/core/primitives/numbers/number_pretty_print.js +4 -1
  197. package/src/core/primitives/strings/string_jaro_winkler.js +1 -1
  198. package/src/core/process/CompositeProcess.js +1 -1
  199. package/src/core/process/action/AsynchronousDelayAction.d.ts.map +1 -1
  200. package/src/core/process/action/AsynchronousDelayAction.js +3 -0
  201. package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
  202. package/src/core/process/executor/ConcurrentExecutor.js +3 -2
  203. package/src/core/process/task/util/randomCountTask.d.ts.map +1 -1
  204. package/src/core/process/task/util/randomCountTask.js +3 -1
  205. package/src/core/process/undo/ActionProcessor.d.ts.map +1 -1
  206. package/src/core/process/undo/ActionProcessor.js +5 -3
  207. package/src/core/process/worker/WorkerBuilder.js +3 -3
  208. package/src/engine/animation/curve/AnimationCurve.d.ts.map +1 -1
  209. package/src/engine/animation/curve/AnimationCurve.js +4 -2
  210. package/src/engine/control/first-person/DESIGN.md +1 -1
  211. package/src/engine/control/first-person/FirstPersonMotionPhase.d.ts +55 -0
  212. package/src/engine/control/first-person/FirstPersonMotionPhase.d.ts.map +1 -0
  213. package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -0
  214. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +23 -2
  215. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  216. package/src/engine/control/first-person/FirstPersonPlayerController.js +1 -1
  217. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +168 -0
  218. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  219. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +115 -0
  220. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +71 -0
  221. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  222. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +255 -55
  223. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +82 -43
  224. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
  225. package/src/engine/control/first-person/abilities/LedgeGrab.js +405 -213
  226. package/src/engine/control/first-person/abilities/Mantle.d.ts +6 -0
  227. package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
  228. package/src/engine/control/first-person/abilities/Mantle.js +104 -45
  229. package/src/engine/control/first-person/abilities/ScrambleUp.d.ts +61 -0
  230. package/src/engine/control/first-person/abilities/ScrambleUp.d.ts.map +1 -0
  231. package/src/engine/control/first-person/abilities/ScrambleUp.js +182 -0
  232. package/src/engine/control/first-person/math/jumpDynamics.d.ts +84 -0
  233. package/src/engine/control/first-person/math/jumpDynamics.d.ts.map +1 -0
  234. package/src/engine/control/first-person/math/jumpDynamics.js +108 -0
  235. package/src/engine/control/first-person/prototype_first_person_controller.js +45 -1
  236. package/src/engine/graphics/camera/testClippingPlaneComputation.js +1 -1
  237. package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.d.ts.map +1 -1
  238. package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.js +8 -0
  239. package/src/engine/graphics/ecs/path/tube/prototypeAnimatedPathMask.js +1 -1
  240. package/src/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +1 -1
  241. package/src/engine/graphics/render/buffer/buffers/prototypeNormalFrameBuffer.js +1 -1
  242. package/src/engine/graphics/render/forward_plus/plugin/ptototypeFPPlugin.js +1 -1
  243. package/src/engine/graphics/render/visibility/hiz/prototypeHiZ.js +1 -1
  244. package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +1 -1
  245. package/src/engine/graphics/shadows/testShadowMapRendering.js +1 -1
  246. package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -0
  247. package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -0
  248. package/src/engine/physics/PLAN.md +6 -5
  249. package/src/engine/physics/constraint/solve_constraints.d.ts +4 -1
  250. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  251. package/src/engine/physics/constraint/solve_constraints.js +147 -33
  252. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  253. package/src/engine/physics/ecs/PhysicsSystem.js +1750 -1747
  254. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
  255. package/src/engine/physics/gjk/gjk_epa_penetration.d.ts +12 -8
  256. package/src/engine/physics/gjk/gjk_epa_penetration.d.ts.map +1 -1
  257. package/src/engine/physics/gjk/gjk_epa_penetration.js +447 -158
  258. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -1
  259. package/src/engine/physics/narrowphase/convex_convex_manifold.js +22 -25
  260. package/src/engine/physics/narrowphase/convex_decomposition.d.ts +32 -13
  261. package/src/engine/physics/narrowphase/convex_decomposition.d.ts.map +1 -1
  262. package/src/engine/physics/narrowphase/convex_decomposition.js +61 -65
  263. package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts.map +1 -1
  264. package/src/engine/physics/narrowphase/mesh_convex_hull.js +13 -8
  265. package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
  266. package/src/engine/physics/narrowphase/refine_ray_concave.js +5 -3
  267. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
  268. package/src/engine/physics/narrowphase/refine_ray_hit.js +81 -78
  269. package/src/engine/sound/SoundEngine.d.ts.map +1 -1
  270. package/src/engine/sound/SoundEngine.js +28 -0
  271. package/src/engine/sound/dB2Volume.d.ts +1 -1
  272. package/src/engine/sound/dB2Volume.d.ts.map +1 -1
  273. package/src/engine/sound/dB2Volume.js +1 -1
  274. package/src/engine/sound/ecs/SoundController.d.ts +4 -0
  275. package/src/engine/sound/ecs/SoundController.d.ts.map +1 -1
  276. package/src/engine/sound/ecs/SoundController.js +4 -0
  277. package/src/engine/sound/ecs/SoundControllerSystem.d.ts +5 -0
  278. package/src/engine/sound/ecs/SoundControllerSystem.d.ts.map +1 -1
  279. package/src/engine/sound/ecs/SoundControllerSystem.js +5 -0
  280. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts +69 -0
  281. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts.map +1 -0
  282. package/src/engine/sound/ecs/audio/AudioEmitter.js +83 -0
  283. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts +97 -0
  284. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts.map +1 -0
  285. package/src/engine/sound/ecs/audio/AudioEmitterSystem.js +238 -0
  286. package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts +90 -0
  287. package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts.map +1 -0
  288. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +324 -0
  289. package/src/engine/sound/ecs/audio/SpatialAudioIndex.d.ts +59 -0
  290. package/src/engine/sound/ecs/audio/SpatialAudioIndex.d.ts.map +1 -0
  291. package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -0
  292. package/src/engine/sound/ecs/emitter/SoundEmitter.d.ts +16 -65
  293. package/src/engine/sound/ecs/emitter/SoundEmitter.d.ts.map +1 -1
  294. package/src/engine/sound/ecs/emitter/SoundEmitter.js +19 -224
  295. package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.d.ts +26 -29
  296. package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.d.ts.map +1 -1
  297. package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.js +168 -135
  298. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts +36 -59
  299. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts.map +1 -1
  300. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.js +154 -390
  301. package/src/engine/sound/ecs/emitter/SoundTrack.d.ts +20 -23
  302. package/src/engine/sound/ecs/emitter/SoundTrack.d.ts.map +1 -1
  303. package/src/engine/sound/ecs/emitter/SoundTrack.js +34 -152
  304. package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +993 -0
  305. package/src/engine/sound/sopra/README.md +643 -7
  306. package/src/engine/sound/sopra/SopraEngine.d.ts +229 -0
  307. package/src/engine/sound/sopra/SopraEngine.d.ts.map +1 -0
  308. package/src/engine/sound/sopra/SopraEngine.js +423 -0
  309. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.d.ts +26 -0
  310. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.d.ts.map +1 -0
  311. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -0
  312. package/src/engine/sound/sopra/asset/BufferProvider.d.ts +24 -0
  313. package/src/engine/sound/sopra/asset/BufferProvider.d.ts.map +1 -0
  314. package/src/engine/sound/sopra/asset/BufferProvider.js +29 -0
  315. package/src/engine/sound/sopra/asset/StubBufferProvider.d.ts +31 -0
  316. package/src/engine/sound/sopra/asset/StubBufferProvider.d.ts.map +1 -0
  317. package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -0
  318. package/src/engine/sound/sopra/definition/BusDefinition.d.ts +83 -0
  319. package/src/engine/sound/sopra/definition/BusDefinition.d.ts.map +1 -0
  320. package/src/engine/sound/sopra/definition/BusDefinition.js +142 -0
  321. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.d.ts +17 -0
  322. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.d.ts.map +1 -0
  323. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -0
  324. package/src/engine/sound/sopra/definition/DuckingRule.d.ts +71 -0
  325. package/src/engine/sound/sopra/definition/DuckingRule.d.ts.map +1 -0
  326. package/src/engine/sound/sopra/definition/DuckingRule.js +106 -0
  327. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.d.ts +18 -0
  328. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.d.ts.map +1 -0
  329. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -0
  330. package/src/engine/sound/sopra/definition/EventDescription.d.ts +132 -0
  331. package/src/engine/sound/sopra/definition/EventDescription.d.ts.map +1 -0
  332. package/src/engine/sound/sopra/definition/EventDescription.js +259 -0
  333. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts +17 -0
  334. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts.map +1 -0
  335. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +71 -0
  336. package/src/engine/sound/sopra/definition/MixerSnapshot.d.ts +51 -0
  337. package/src/engine/sound/sopra/definition/MixerSnapshot.d.ts.map +1 -0
  338. package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -0
  339. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.d.ts +18 -0
  340. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.d.ts.map +1 -0
  341. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -0
  342. package/src/engine/sound/sopra/definition/ParameterDefinition.d.ts +72 -0
  343. package/src/engine/sound/sopra/definition/ParameterDefinition.d.ts.map +1 -0
  344. package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -0
  345. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.d.ts +18 -0
  346. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.d.ts.map +1 -0
  347. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -0
  348. package/src/engine/sound/sopra/definition/SopraPanningModel.d.ts +14 -0
  349. package/src/engine/sound/sopra/definition/SopraPanningModel.d.ts.map +1 -0
  350. package/src/engine/sound/sopra/definition/SopraPanningModel.js +20 -0
  351. package/src/engine/sound/sopra/definition/VoiceStealMode.d.ts +10 -0
  352. package/src/engine/sound/sopra/definition/VoiceStealMode.d.ts.map +1 -0
  353. package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -0
  354. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +93 -0
  355. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -0
  356. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -0
  357. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +80 -0
  358. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -0
  359. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +181 -0
  360. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts +17 -0
  361. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts.map +1 -0
  362. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +74 -0
  363. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.d.ts +34 -0
  364. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.d.ts.map +1 -0
  365. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +100 -0
  366. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +101 -0
  367. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -0
  368. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +230 -0
  369. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.d.ts +17 -0
  370. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.d.ts.map +1 -0
  371. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -0
  372. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +103 -0
  373. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -0
  374. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -0
  375. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.d.ts +18 -0
  376. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.d.ts.map +1 -0
  377. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -0
  378. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +40 -0
  379. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -0
  380. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +91 -0
  381. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.d.ts +17 -0
  382. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.d.ts.map +1 -0
  383. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -0
  384. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.d.ts +44 -0
  385. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.d.ts.map +1 -0
  386. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -0
  387. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.d.ts +18 -0
  388. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.d.ts.map +1 -0
  389. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -0
  390. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +65 -0
  391. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -0
  392. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +131 -0
  393. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.d.ts +17 -0
  394. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.d.ts.map +1 -0
  395. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -0
  396. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts +24 -0
  397. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts.map +1 -0
  398. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -0
  399. package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts +70 -0
  400. package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts.map +1 -0
  401. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -0
  402. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.d.ts +18 -0
  403. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.d.ts.map +1 -0
  404. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -0
  405. package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts +74 -0
  406. package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts.map +1 -0
  407. package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -0
  408. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.d.ts +18 -0
  409. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.d.ts.map +1 -0
  410. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -0
  411. package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts +49 -0
  412. package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts.map +1 -0
  413. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +101 -0
  414. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.d.ts +18 -0
  415. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.d.ts.map +1 -0
  416. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -0
  417. package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.d.ts +31 -0
  418. package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.d.ts.map +1 -0
  419. package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.js +106 -0
  420. package/src/engine/sound/sopra/runtime/BusGraph.d.ts +79 -0
  421. package/src/engine/sound/sopra/runtime/BusGraph.d.ts.map +1 -0
  422. package/src/engine/sound/sopra/runtime/BusGraph.js +227 -0
  423. package/src/engine/sound/sopra/runtime/EventInstance.d.ts +144 -0
  424. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -0
  425. package/src/engine/sound/sopra/runtime/EventInstance.js +579 -0
  426. package/src/engine/sound/sopra/runtime/ParameterStore.d.ts +42 -0
  427. package/src/engine/sound/sopra/runtime/ParameterStore.d.ts.map +1 -0
  428. package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -0
  429. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.d.ts +42 -0
  430. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.d.ts.map +1 -0
  431. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -0
  432. package/src/engine/sound/sopra/runtime/Voice.d.ts +67 -0
  433. package/src/engine/sound/sopra/runtime/Voice.d.ts.map +1 -0
  434. package/src/engine/sound/sopra/runtime/Voice.js +145 -0
  435. package/src/engine/sound/sopra/runtime/VoiceManager.d.ts +38 -0
  436. package/src/engine/sound/sopra/runtime/VoiceManager.d.ts.map +1 -0
  437. package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -0
  438. package/src/engine/sound/sopra/runtime/VoicePool.d.ts +12 -0
  439. package/src/engine/sound/sopra/runtime/VoicePool.d.ts.map +1 -0
  440. package/src/engine/sound/sopra/runtime/VoicePool.js +17 -0
  441. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts +11 -0
  442. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts.map +1 -0
  443. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -0
  444. package/src/engine/sound/sopra/serialization/sopraJSON.d.ts +33 -0
  445. package/src/engine/sound/sopra/serialization/sopraJSON.d.ts.map +1 -0
  446. package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -0
  447. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts +27 -0
  448. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts.map +1 -0
  449. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -0
  450. package/src/engine/sound/sopra/util/MockAudioContext.d.ts +74 -0
  451. package/src/engine/sound/sopra/util/MockAudioContext.d.ts.map +1 -0
  452. package/src/engine/sound/sopra/util/MockAudioContext.js +215 -0
  453. package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts +15 -0
  454. package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts.map +1 -0
  455. package/src/engine/sound/sopra/util/buildAttenuationCurve.js +40 -0
  456. package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts +34 -0
  457. package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts.map +1 -0
  458. package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -0
  459. package/src/engine/sound/volume2dB.d.ts +1 -1
  460. package/src/engine/sound/volume2dB.d.ts.map +1 -1
  461. package/src/engine/sound/volume2dB.js +1 -1
  462. package/src/engine/graphics/sh3/path_tracer/sampling/v3_orthonormal_matrix_from_normal.d.ts.map +0 -1
  463. package/src/engine/physics/narrowphase/ray_shapes.d.ts +0 -66
  464. package/src/engine/physics/narrowphase/ray_shapes.d.ts.map +0 -1
  465. package/src/engine/physics/narrowphase/ray_shapes.js +0 -187
  466. package/src/engine/sound/ecs/emitter/SoundEmitterChannel.d.ts +0 -23
  467. package/src/engine/sound/ecs/emitter/SoundEmitterChannel.d.ts.map +0 -1
  468. package/src/engine/sound/ecs/emitter/SoundEmitterChannel.js +0 -32
  469. package/src/engine/sound/ecs/emitter/SoundTrackNodes.d.ts +0 -18
  470. package/src/engine/sound/ecs/emitter/SoundTrackNodes.d.ts.map +0 -1
  471. package/src/engine/sound/ecs/emitter/SoundTrackNodes.js +0 -18
  472. package/src/engine/sound/sopra/AbstractAudioClip.d.ts +0 -26
  473. package/src/engine/sound/sopra/AbstractAudioClip.d.ts.map +0 -1
  474. package/src/engine/sound/sopra/AbstractAudioClip.js +0 -29
  475. package/src/engine/sound/sopra/ContainerAudioClip.d.ts +0 -12
  476. package/src/engine/sound/sopra/ContainerAudioClip.d.ts.map +0 -1
  477. package/src/engine/sound/sopra/ContainerAudioClip.js +0 -13
  478. package/src/engine/sound/sopra/RandomContainerAudioClip.d.ts +0 -12
  479. package/src/engine/sound/sopra/RandomContainerAudioClip.d.ts.map +0 -1
  480. package/src/engine/sound/sopra/RandomContainerAudioClip.js +0 -15
  481. package/src/engine/sound/sopra/SequenceContainerAudioClip.d.ts +0 -7
  482. package/src/engine/sound/sopra/SequenceContainerAudioClip.d.ts.map +0 -1
  483. package/src/engine/sound/sopra/SequenceContainerAudioClip.js +0 -8
  484. package/src/engine/sound/sopra/SilenceAudioClip.d.ts +0 -13
  485. package/src/engine/sound/sopra/SilenceAudioClip.d.ts.map +0 -1
  486. package/src/engine/sound/sopra/SilenceAudioClip.js +0 -15
  487. /package/src/{engine/graphics/sh3/path_tracer/sampling → core/geom/vec3}/v3_orthonormal_matrix_from_normal.d.ts +0 -0
@@ -0,0 +1,238 @@
1
+ import { GameAssetType } from "../../../asset/GameAssetType.js";
2
+ import { SoundAssetLoader } from "../../../asset/loaders/SoundAssetLoader.js";
3
+ import { System } from "../../../ecs/System.js";
4
+ import { Transform } from "../../../ecs/transform/Transform.js";
5
+ import { AssetManagerBufferProvider } from "../../sopra/asset/AssetManagerBufferProvider.js";
6
+ import { EventInstanceState } from "../../sopra/runtime/EventInstance.js";
7
+ import { volume2dB } from "../../volume2dB.js";
8
+ import SoundListener from "../SoundListener.js";
9
+ import { AudioEmitter } from "./AudioEmitter.js";
10
+ import { LiveEmitterSet } from "./LiveEmitterSet.js";
11
+
12
+ /**
13
+ * Drives {@link AudioEmitter} components through the shared sopra engine — the native counterpart to
14
+ * the legacy `SoundEmitterSystem`. Each emitter owns its {@link EventDescription} directly (no track
15
+ * translation); the entity's {@link Transform} feeds position; `volume` changes plumb to the instance
16
+ * gain. Each frame it forwards the listener pose, refreshes the spatial live set, and ticks sopra.
17
+ *
18
+ * **Two play paths**, chosen once at {@link link} (routing is fixed for the lifetime of the link —
19
+ * mutating `autoplay`/`is3D` afterward does nothing without an unlink/relink):
20
+ * - **Spatially managed** (`autoplay && is3D && looping`): registered with a {@link LiveEmitterSet} and
21
+ * left *dormant* until a refresh promotes the nearest in-range emitters up to a global voice budget —
22
+ * this is what lets the system carry far more emitters than can sound at once (the 100k-bird case). A
23
+ * dormant emitter costs only a BVH leaf; no instance, no nodes, no per-frame work. NOTE: for a crowd
24
+ * of content-equal managed events to all sound, their `maxInstances` must be ≥ the live budget — they
25
+ * share one sopra polyphony bucket (keyed by event content), so a lower cap gates the budget. Only
26
+ * *looping* events are managed: the live set models a persistent source whose phase is reconstructed
27
+ * on (re-)promotion — meaningless for a finite one-shot, and a dead one-shot would otherwise keep
28
+ * re-promoting and occupying a budget slot — so finite events take the direct path.
29
+ * - **Direct** (any other `autoplay` event — 2D sounds, or finite 3D one-shots): played immediately on
30
+ * link, stopped on unlink. 2D sounds (music, ambience beds) have nothing to cull by distance; a finite
31
+ * 3D one-shot still spatializes (its instance attenuates by position) and self-releases when it ends.
32
+ *
33
+ * Non-autoplay emitters are inert: neither played nor registered, awaiting a future explicit trigger.
34
+ *
35
+ * Shares the single {@link SopraEngine} owned by {@link SoundEngine} (created on demand, idempotent),
36
+ * so it coexists with the legacy `SoundEmitterSystem` during the strangler migration.
37
+ */
38
+ export class AudioEmitterSystem extends System {
39
+ /**
40
+ * @param {AssetManager} assetManager
41
+ * @param {SoundEngine} soundEngine owns the AudioContext + master destination + sopra engine
42
+ * @param {{budget?:number, liveStickiness?:number, fadeOutSeconds?:number}} [liveEmitterSetOptions]
43
+ * forwarded to the {@link LiveEmitterSet} that manages 3D emitters
44
+ */
45
+ constructor(assetManager, soundEngine, liveEmitterSetOptions = {}) {
46
+ super();
47
+
48
+ this.dependencies = [AudioEmitter, Transform];
49
+
50
+ /**
51
+ * @type {SoundEngine}
52
+ */
53
+ this.soundEngine = soundEngine;
54
+
55
+ /**
56
+ * @type {AudioContext}
57
+ */
58
+ this.webAudioContext = soundEngine.context;
59
+
60
+ /**
61
+ * @type {AssetManager}
62
+ */
63
+ this.assetManager = assetManager;
64
+
65
+ // Self-sufficient: register the Sound asset loader if nothing has yet (so this system works
66
+ // standalone, without the legacy SoundEmitterSystem). Guarded against double-registration (D7).
67
+ if (!assetManager.hasLoaderForType(GameAssetType.Sound)) {
68
+ assetManager.registerLoader(GameAssetType.Sound, new SoundAssetLoader(soundEngine.context));
69
+ }
70
+
71
+ /**
72
+ * The shared sopra engine (idempotent create — reused if the legacy system already made it).
73
+ * @type {SopraEngine}
74
+ */
75
+ this.sopra = soundEngine.createSopra(new AssetManagerBufferProvider(assetManager));
76
+
77
+ /**
78
+ * Spatial lifecycle for 3D ("managed") emitters: BVH cull → promote nearest-in-range up to the
79
+ * budget, demote the rest. 2D emitters bypass this and play directly.
80
+ * @type {LiveEmitterSet}
81
+ */
82
+ this.liveSet = new LiveEmitterSet(this.sopra, liveEmitterSetOptions);
83
+
84
+ /**
85
+ * Per-entity records: { emitter, transform, managed, instance, onVolume }. For a managed (3D)
86
+ * emitter the playing instance lives in {@link liveSet} (record.instance stays null); for a
87
+ * direct (2D) emitter it is held here.
88
+ * @type {object[]}
89
+ */
90
+ this.data = [];
91
+ }
92
+
93
+ /**
94
+ * @param {AudioEmitter} emitter
95
+ * @param {Transform} transform
96
+ * @param {number} entity
97
+ */
98
+ link(emitter, transform, entity) {
99
+ // Spatially managed = a persistent (looping) 3D autoplay source. Finite 3D one-shots and all 2D
100
+ // sounds take the direct path; non-autoplay emitters are inert. Routing is fixed at link. See the
101
+ // class doc.
102
+ const managed = emitter.autoplay && emitter.event.is3D && emitter.event.rootClip.loops();
103
+
104
+ const record = { emitter, transform, managed, instance: null, onVolume: null };
105
+
106
+ if (managed) {
107
+ this.liveSet.add(entity, emitter.event, transform.position, volume2dB(emitter.volume.getValue()));
108
+ record.onVolume = () => this.liveSet.setGainDb(entity, volume2dB(emitter.volume.getValue()));
109
+ } else {
110
+ if (emitter.autoplay) {
111
+ record.instance = this.#play(emitter, transform);
112
+ }
113
+ record.onVolume = () => this.#applyVolume(record);
114
+ }
115
+
116
+ emitter.volume.onChanged.add(record.onVolume);
117
+
118
+ this.data[entity] = record;
119
+ }
120
+
121
+ /**
122
+ * @param {AudioEmitter} emitter
123
+ * @param {Transform} transform
124
+ * @param {number} entity
125
+ */
126
+ unlink(emitter, transform, entity) {
127
+ const record = this.data[entity];
128
+
129
+ if (record === undefined) {
130
+ return;
131
+ }
132
+
133
+ delete this.data[entity];
134
+
135
+ emitter.volume.onChanged.remove(record.onVolume);
136
+
137
+ if (record.managed) {
138
+ this.liveSet.remove(entity); // demotes (hard cut) if live, then unregisters
139
+ } else if (record.instance !== null) {
140
+ record.instance.stop();
141
+ record.instance = null;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * The currently-playing instance for an entity, or null. For a managed (3D) emitter this is the live
147
+ * instance only while it is promoted (null while dormant or out of range); for a direct (2D) emitter
148
+ * it is the instance held since link.
149
+ * @param {number} entity
150
+ * @returns {EventInstance|null}
151
+ */
152
+ instanceFor(entity) {
153
+ const record = this.data[entity];
154
+
155
+ if (record === undefined) {
156
+ return null;
157
+ }
158
+
159
+ if (record.managed) {
160
+ const instance = this.liveSet.instanceOf(entity);
161
+ return instance === undefined ? null : instance;
162
+ }
163
+
164
+ return record.instance;
165
+ }
166
+
167
+ /**
168
+ * @param {AudioEmitter} emitter
169
+ * @param {Transform} transform
170
+ * @returns {EventInstance|null}
171
+ */
172
+ #play(emitter, transform) {
173
+ const event = emitter.event;
174
+
175
+ // A looping event is a persistent source (plays until unlink); a finite event is a one-shot that
176
+ // self-releases when it finishes. oneShot ALSO arms the max-lifetime backstop, so marking a loop
177
+ // oneShot would wrongly kill it after maxLifetimeSeconds.
178
+ const oneShot = !event.rootClip.loops();
179
+
180
+ const instance = this.sopra.playEvent(event, {
181
+ busId: event.busId,
182
+ position: transform.position, // shared Vector3 — tracks the entity as it moves
183
+ oneShot
184
+ });
185
+
186
+ if (instance !== null) {
187
+ instance.setGainDb(volume2dB(emitter.volume.getValue()));
188
+ }
189
+
190
+ return instance;
191
+ }
192
+
193
+ /**
194
+ * @param {object} record
195
+ */
196
+ #applyVolume(record) {
197
+ const instance = record.instance;
198
+
199
+ if (instance !== null && instance.state !== EventInstanceState.Stopped) {
200
+ instance.setGainDb(volume2dB(record.emitter.volume.getValue()));
201
+ }
202
+ }
203
+
204
+ update(timeDelta) {
205
+ const sopra = this.sopra;
206
+
207
+ const ecd = this.entityManager.dataset;
208
+
209
+ let listenerPosition = null;
210
+
211
+ if (ecd !== null) {
212
+ const soundListener = ecd.getAnyComponent(SoundListener);
213
+
214
+ if (soundListener.entity !== -1) {
215
+ // A SoundListener entity is expected to also carry a Transform (SoundListenerSystem
216
+ // depends on both). Tolerate a misconfigured one rather than throwing every frame and
217
+ // freezing all audio: a missing Transform is treated as "no usable listener" (below).
218
+ const listenerTransform = ecd.getComponent(soundListener.entity, Transform);
219
+
220
+ if (listenerTransform !== undefined) {
221
+ listenerPosition = listenerTransform.position;
222
+ sopra.setListener(listenerPosition);
223
+ }
224
+ }
225
+ }
226
+
227
+ // Promote/demote the spatial live set (throttled internally). Refresh BEFORE the tick so a
228
+ // freshly-promoted instance spatializes on the same frame. With no usable listener the live set
229
+ // is not refreshed: never-promoted managed emitters stay dormant, while any already-live ones
230
+ // keep playing against the last known listener pose until a listener returns. The direct (2D)
231
+ // instances and the sopra tick run regardless.
232
+ if (listenerPosition !== null) {
233
+ this.liveSet.refresh(listenerPosition);
234
+ }
235
+
236
+ sopra.update(this.webAudioContext.currentTime);
237
+ }
238
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The live/dormant lifecycle for spatial audio emitters (P5.2). Wraps a {@link SpatialAudioIndex} (the
3
+ * BVH broadphase) and, on each {@link refresh}, promotes the nearest in-range emitters — up to a global
4
+ * voice budget — to live sopra {@link EventInstance}s, demoting the rest. This bounds the live-instance
5
+ * count (and therefore WebAudio nodes + the per-frame tick) to the budget, no matter how many emitters
6
+ * are registered. A registered-but-dormant emitter costs only its BVH leaf: no instance, no nodes, no
7
+ * per-frame work.
8
+ *
9
+ * "Voice slots" are the budget, allocated at promotion. Stealing is by distance: at budget, a closer
10
+ * emitter bumps a farther live one (demoted). A small stickiness keeps the live set stable so emitters
11
+ * near the budget cutoff don't flicker promote/demote each frame (rank hysteresis — the audible kind;
12
+ * range-edge churn at `distanceMax` is inaudible, gain ≈ 0 there, and is handled later with chain
13
+ * pooling).
14
+ *
15
+ * Demotion policy: a hard **cut** when an emitter is culled out of range (already inaudible — gain ≈ 0
16
+ * past `distanceMax`), a click-safe **fade** when a still-in-range live emitter loses its slot to a
17
+ * closer one (contention). Phase reconstruction on promote is P5.4. Intended for persistent (looping)
18
+ * emitters; a finite event that self-ends is dropped from the live set via its `onEnded`.
19
+ *
20
+ * Budget vs per-event polyphony — they are INDEPENDENT, composed caps. `budget` bounds the global live
21
+ * count; sopra's {@link VoiceManager} `EventDescription.maxInstances` (keyed by event CONTENT) bounds
22
+ * concurrent instances of one content-equal event. If a managed event's `maxInstances` is below the
23
+ * number of its content-equal emitters that can be live at once, sopra gates the budget: with
24
+ * `stealMode None` a promote is denied (`playEvent` → null) and the emitter stays dormant (retried each
25
+ * refresh — see the test); with `stealMode Oldest` sopra steals between them and the two layers fight
26
+ * (churn). So for spatial ambience where you want up to `budget` copies of one sound audible, give that
27
+ * event a `maxInstances` ≥ that count (this is a content/wiring decision finalised in P5.6).
28
+ */
29
+ export class LiveEmitterSet {
30
+ /**
31
+ * @param {SopraEngine} sopra
32
+ * @param {{budget?:number, liveStickiness?:number, fadeOutSeconds?:number}} [options]
33
+ */
34
+ constructor(sopra: SopraEngine, { budget, liveStickiness, fadeOutSeconds }?: {
35
+ budget?: number;
36
+ liveStickiness?: number;
37
+ fadeOutSeconds?: number;
38
+ });
39
+ /**
40
+ * @returns {number} live (sounding) emitter count
41
+ */
42
+ get liveCount(): number;
43
+ /**
44
+ * @returns {number} registered emitter count
45
+ */
46
+ get size(): number;
47
+ /**
48
+ * @param {number} entity
49
+ * @returns {boolean}
50
+ */
51
+ isLive(entity: number): boolean;
52
+ /**
53
+ * @param {number} entity
54
+ * @returns {EventInstance|undefined} the live instance, or undefined if dormant/unknown
55
+ */
56
+ instanceOf(entity: number): EventInstance | undefined;
57
+ /**
58
+ * Register an emitter (dormant until a refresh promotes it).
59
+ * @param {number} entity
60
+ * @param {EventDescription} event
61
+ * @param {Vector3} position shared position vector (tracked reactively by the spatial index)
62
+ * @param {number} [gainDb] per-emitter instance gain in dB, applied on every promotion (default 0 = unity)
63
+ */
64
+ add(entity: number, event: EventDescription, position: Vector3, gainDb?: number): void;
65
+ /**
66
+ * Set the per-emitter instance gain (dB). Updates the stored value so a future promotion replays it,
67
+ * and applies it immediately if the emitter is currently live. A no-op for an unknown entity, and for
68
+ * a fading (contention-demoted) instance — it is out of the live map, so it finishes its fade
69
+ * untouched and the new gain only takes effect on the next promotion.
70
+ * @param {number} entity
71
+ * @param {number} gainDb
72
+ */
73
+ setGainDb(entity: number, gainDb: number): void;
74
+ /**
75
+ * Unregister an emitter, demoting it first if live.
76
+ * @param {number} entity
77
+ */
78
+ remove(entity: number): void;
79
+ /**
80
+ * Recompute the live set for the current listener position: promote the nearest in-range emitters up
81
+ * to the budget, demote the rest. Cheap enough to call every tick — the BVH point-query is
82
+ * O(log n + candidates) and the diff is re-derived from {@link #live} each call (no maintained state
83
+ * to drift), so there is no reason to throttle it. Allocation-free apart from the per-candidate rank
84
+ * entries: the scratch containers are reused across calls.
85
+ * @param {Vector3} listenerPosition
86
+ */
87
+ refresh(listenerPosition: Vector3): void;
88
+ #private;
89
+ }
90
+ //# sourceMappingURL=LiveEmitterSet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiveEmitterSet.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/sound/ecs/audio/LiveEmitterSet.js"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH;IA+DI;;;OAGG;IACH,6EAFW;QAAC,MAAM,CAAC,EAAC,MAAM,CAAC;QAAC,cAAc,CAAC,EAAC,MAAM,CAAC;QAAC,cAAc,CAAC,EAAC,MAAM,CAAA;KAAC,EAO1E;IAED;;OAEG;IACH,wBAEC;IAED;;OAEG;IACH,mBAEC;IAED;;;OAGG;IACH,eAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,mBAHW,MAAM,GACJ,gBAAc,SAAS,CAInC;IAED;;;;;;OAMG;IACH,YALW,MAAM,uDAGN,MAAM,QAOhB;IAED;;;;;;;OAOG;IACH,kBAHW,MAAM,UACN,MAAM,QAehB;IAED;;;OAGG;IACH,eAFW,MAAM,QAShB;IAED;;;;;;;OAOG;IACH,yCAwDC;;CAgEJ"}
@@ -0,0 +1,324 @@
1
+ import { EventInstanceState } from "../../sopra/runtime/EventInstance.js";
2
+ import { SpatialAudioIndex } from "./SpatialAudioIndex.js";
3
+
4
+ /**
5
+ * Sort rank entries nearest-first by effective (stickiness-discounted) distance.
6
+ * @param {{effective:number}} a
7
+ * @param {{effective:number}} b
8
+ * @returns {number}
9
+ */
10
+ function compareByEffective(a, b) {
11
+ return a.effective - b.effective;
12
+ }
13
+
14
+ /**
15
+ * The live/dormant lifecycle for spatial audio emitters (P5.2). Wraps a {@link SpatialAudioIndex} (the
16
+ * BVH broadphase) and, on each {@link refresh}, promotes the nearest in-range emitters — up to a global
17
+ * voice budget — to live sopra {@link EventInstance}s, demoting the rest. This bounds the live-instance
18
+ * count (and therefore WebAudio nodes + the per-frame tick) to the budget, no matter how many emitters
19
+ * are registered. A registered-but-dormant emitter costs only its BVH leaf: no instance, no nodes, no
20
+ * per-frame work.
21
+ *
22
+ * "Voice slots" are the budget, allocated at promotion. Stealing is by distance: at budget, a closer
23
+ * emitter bumps a farther live one (demoted). A small stickiness keeps the live set stable so emitters
24
+ * near the budget cutoff don't flicker promote/demote each frame (rank hysteresis — the audible kind;
25
+ * range-edge churn at `distanceMax` is inaudible, gain ≈ 0 there, and is handled later with chain
26
+ * pooling).
27
+ *
28
+ * Demotion policy: a hard **cut** when an emitter is culled out of range (already inaudible — gain ≈ 0
29
+ * past `distanceMax`), a click-safe **fade** when a still-in-range live emitter loses its slot to a
30
+ * closer one (contention). Phase reconstruction on promote is P5.4. Intended for persistent (looping)
31
+ * emitters; a finite event that self-ends is dropped from the live set via its `onEnded`.
32
+ *
33
+ * Budget vs per-event polyphony — they are INDEPENDENT, composed caps. `budget` bounds the global live
34
+ * count; sopra's {@link VoiceManager} `EventDescription.maxInstances` (keyed by event CONTENT) bounds
35
+ * concurrent instances of one content-equal event. If a managed event's `maxInstances` is below the
36
+ * number of its content-equal emitters that can be live at once, sopra gates the budget: with
37
+ * `stealMode None` a promote is denied (`playEvent` → null) and the emitter stays dormant (retried each
38
+ * refresh — see the test); with `stealMode Oldest` sopra steals between them and the two layers fight
39
+ * (churn). So for spatial ambience where you want up to `budget` copies of one sound audible, give that
40
+ * event a `maxInstances` ≥ that count (this is a content/wiring decision finalised in P5.6).
41
+ */
42
+ export class LiveEmitterSet {
43
+
44
+ /**
45
+ * @type {SopraEngine}
46
+ */
47
+ #sopra;
48
+
49
+ /**
50
+ * @type {SpatialAudioIndex}
51
+ */
52
+ #index = new SpatialAudioIndex();
53
+
54
+ /**
55
+ * @type {number}
56
+ */
57
+ #budget;
58
+
59
+ /**
60
+ * Live-emitter distance discount in [0, 1]; a live emitter at distance d competes as `d * stickiness`
61
+ * so a newcomer must be more than that much closer to steal its slot.
62
+ * @type {number}
63
+ */
64
+ #stickiness;
65
+
66
+ /**
67
+ * Fade-out duration (seconds) when a still-in-range live emitter loses its slot to a closer one
68
+ * (contention) — a hard cut there would click. Demotion by cull (out of range) is an immediate stop.
69
+ * @type {number}
70
+ */
71
+ #fadeOutSeconds;
72
+
73
+ /**
74
+ * entity -> { event, position, gainDb, playingSince }. `gainDb` is the per-emitter instance gain
75
+ * (e.g. the owning `AudioEmitter.volume` in dB) re-applied on every promotion so it survives a
76
+ * demote→re-promote cycle; `playingSince` carries the continuous-clock phase (see {@link add}).
77
+ * @type {Map<number, {event: EventDescription, position: Vector3, gainDb: number, playingSince: number}>}
78
+ */
79
+ #emitters = new Map();
80
+
81
+ /**
82
+ * entity -> the live instance
83
+ * @type {Map<number, EventInstance>}
84
+ */
85
+ #live = new Map();
86
+
87
+ /**
88
+ * Reused candidate buffer for {@link SpatialAudioIndex.queryAudible} (grows, never shrinks; only
89
+ * `[0, count)` is valid each refresh) — keeps the cull allocation-free.
90
+ * @type {number[]}
91
+ */
92
+ #candidates = [];
93
+
94
+ /**
95
+ * Reused refresh scratch (the cull runs every tick, so it must not allocate containers per frame):
96
+ * `#ranked` collects `{entity, effective}` for in-range candidates to sort by distance; `#inRange` and
97
+ * `#target` are the in-range and nearest-budget sets; `#demoteScratch` snapshots the live entities to
98
+ * demote (so `#demote` can mutate `#live` mid-loop). All cleared at the top of each {@link refresh}.
99
+ */
100
+ #ranked = [];
101
+ #inRange = new Set();
102
+ #target = new Set();
103
+ #demoteScratch = [];
104
+
105
+ /**
106
+ * @param {SopraEngine} sopra
107
+ * @param {{budget?:number, liveStickiness?:number, fadeOutSeconds?:number}} [options]
108
+ */
109
+ constructor(sopra, { budget = 64, liveStickiness = 0.8, fadeOutSeconds = 0.15 } = {}) {
110
+ this.#sopra = sopra;
111
+ this.#budget = budget;
112
+ this.#stickiness = liveStickiness;
113
+ this.#fadeOutSeconds = fadeOutSeconds;
114
+ }
115
+
116
+ /**
117
+ * @returns {number} live (sounding) emitter count
118
+ */
119
+ get liveCount() {
120
+ return this.#live.size;
121
+ }
122
+
123
+ /**
124
+ * @returns {number} registered emitter count
125
+ */
126
+ get size() {
127
+ return this.#emitters.size;
128
+ }
129
+
130
+ /**
131
+ * @param {number} entity
132
+ * @returns {boolean}
133
+ */
134
+ isLive(entity) {
135
+ return this.#live.has(entity);
136
+ }
137
+
138
+ /**
139
+ * @param {number} entity
140
+ * @returns {EventInstance|undefined} the live instance, or undefined if dormant/unknown
141
+ */
142
+ instanceOf(entity) {
143
+ return this.#live.get(entity);
144
+ }
145
+
146
+ /**
147
+ * Register an emitter (dormant until a refresh promotes it).
148
+ * @param {number} entity
149
+ * @param {EventDescription} event
150
+ * @param {Vector3} position shared position vector (tracked reactively by the spatial index)
151
+ * @param {number} [gainDb] per-emitter instance gain in dB, applied on every promotion (default 0 = unity)
152
+ */
153
+ add(entity, event, position, gainDb = 0) {
154
+ this.#index.add(entity, event, position);
155
+ // playingSince: continuous-clock semantics — the emitter has been "playing" since it linked, so a
156
+ // later promotion resumes the loop at the phase it should be at (see #promote + EventInstance startTime).
157
+ this.#emitters.set(entity, { event, position, gainDb, playingSince: this.#sopra.currentTime });
158
+ }
159
+
160
+ /**
161
+ * Set the per-emitter instance gain (dB). Updates the stored value so a future promotion replays it,
162
+ * and applies it immediately if the emitter is currently live. A no-op for an unknown entity, and for
163
+ * a fading (contention-demoted) instance — it is out of the live map, so it finishes its fade
164
+ * untouched and the new gain only takes effect on the next promotion.
165
+ * @param {number} entity
166
+ * @param {number} gainDb
167
+ */
168
+ setGainDb(entity, gainDb) {
169
+ const record = this.#emitters.get(entity);
170
+
171
+ if (record === undefined) {
172
+ return;
173
+ }
174
+
175
+ record.gainDb = gainDb;
176
+
177
+ const instance = this.#live.get(entity);
178
+ if (instance !== undefined) {
179
+ instance.setGainDb(gainDb);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Unregister an emitter, demoting it first if live.
185
+ * @param {number} entity
186
+ */
187
+ remove(entity) {
188
+ if (this.#live.has(entity)) {
189
+ this.#demote(entity, false); // unlink = the entity is gone -> hard cut
190
+ }
191
+
192
+ this.#index.remove(entity);
193
+ this.#emitters.delete(entity);
194
+ }
195
+
196
+ /**
197
+ * Recompute the live set for the current listener position: promote the nearest in-range emitters up
198
+ * to the budget, demote the rest. Cheap enough to call every tick — the BVH point-query is
199
+ * O(log n + candidates) and the diff is re-derived from {@link #live} each call (no maintained state
200
+ * to drift), so there is no reason to throttle it. Allocation-free apart from the per-candidate rank
201
+ * entries: the scratch containers are reused across calls.
202
+ * @param {Vector3} listenerPosition
203
+ */
204
+ refresh(listenerPosition) {
205
+ const candidates = this.#candidates;
206
+ const candidateCount = this.#index.queryAudible(candidates, 0, listenerPosition);
207
+
208
+ // rank truly-in-range candidates by effective distance (live emitters get a stickiness discount)
209
+ const ranked = this.#ranked;
210
+ ranked.length = 0;
211
+ const inRange = this.#inRange;
212
+ inRange.clear();
213
+ for (let i = 0; i < candidateCount; i++) {
214
+ const entity = candidates[i];
215
+ const record = this.#emitters.get(entity);
216
+ const distance = record.position.distanceTo(listenerPosition);
217
+
218
+ // queryAudible's leaf is a conservative cube; keep only the true (spherical) in-range set
219
+ if (distance > record.event.distanceMax) {
220
+ continue;
221
+ }
222
+
223
+ inRange.add(entity);
224
+
225
+ const sticky = this.#live.has(entity) ? this.#stickiness : 1;
226
+ ranked.push({ entity, effective: distance * sticky });
227
+ }
228
+
229
+ ranked.sort(compareByEffective);
230
+
231
+ const target = this.#target;
232
+ target.clear();
233
+ const limit = Math.min(this.#budget, ranked.length);
234
+ for (let i = 0; i < limit; i++) {
235
+ target.add(ranked[i].entity);
236
+ }
237
+
238
+ // demote live emitters not in the target (snapshot first — #demote mutates #live). A demoted
239
+ // emitter still in range lost its slot to a closer one (contention) -> fade; one that left range
240
+ // is already inaudible -> hard cut.
241
+ const demote = this.#demoteScratch;
242
+ demote.length = 0;
243
+ for (const entity of this.#live.keys()) {
244
+ if (!target.has(entity)) {
245
+ demote.push(entity);
246
+ }
247
+ }
248
+ for (let i = 0; i < demote.length; i++) {
249
+ const entity = demote[i];
250
+ this.#demote(entity, inRange.has(entity));
251
+ }
252
+
253
+ // promote target emitters not yet live (re-derived from #live, so a denied/self-ended promotion
254
+ // is retried next refresh automatically)
255
+ for (const entity of target) {
256
+ if (!this.#live.has(entity)) {
257
+ this.#promote(entity);
258
+ }
259
+ }
260
+ }
261
+
262
+ /**
263
+ * @param {number} entity
264
+ */
265
+ #promote(entity) {
266
+ const record = this.#emitters.get(entity);
267
+ const event = record.event;
268
+
269
+ // a looping event is persistent; a finite one self-releases when it finishes
270
+ const oneShot = !event.rootClip.loops();
271
+
272
+ // startTime resumes the loop at the phase it should be at given real elapsed time (continuous clock)
273
+ const instance = this.#sopra.playEvent(event, {
274
+ busId: event.busId,
275
+ position: record.position,
276
+ oneShot,
277
+ startTime: record.playingSince
278
+ });
279
+
280
+ if (instance === null) {
281
+ // denied by the per-event voice limit (stealMode None) — stay dormant, retry next refresh
282
+ return;
283
+ }
284
+
285
+ if (instance.state === EventInstanceState.Stopped) {
286
+ // a finite event whose play time already elapsed under the continuous clock self-stopped
287
+ // during start (playhead past its end) — nothing to track. (Loops never hit this.)
288
+ return;
289
+ }
290
+
291
+ this.#live.set(entity, instance);
292
+
293
+ // replay the emitter's gain onto the fresh instance (it starts at the event's nominal gain)
294
+ instance.setGainDb(record.gainDb);
295
+
296
+ // if the instance ends on its own (finite content / asset-load failure), drop the stale entry
297
+ instance.onEnded.add(() => {
298
+ if (this.#live.get(entity) === instance) {
299
+ this.#live.delete(entity);
300
+ }
301
+ });
302
+ }
303
+
304
+ /**
305
+ * @param {number} entity
306
+ * @param {boolean} fade fade out (contention — still audible) vs hard stop (culled out of range)
307
+ */
308
+ #demote(entity, fade) {
309
+ const instance = this.#live.get(entity);
310
+
311
+ // delete BEFORE stopping: stop() fires onEnded synchronously and that handler keys off whether
312
+ // this entity still maps to this instance, so removing it first makes the handler a no-op. (A
313
+ // faded instance keeps playing until its audio-clock deadline; it is already out of #live, so it
314
+ // no longer occupies a budget slot, and if the same entity is re-promoted meanwhile the new
315
+ // instance is a different object — the old fade's onEnded still no-ops.)
316
+ this.#live.delete(entity);
317
+
318
+ if (fade) {
319
+ instance.fadeOutAndStop(this.#fadeOutSeconds);
320
+ } else {
321
+ instance.stop();
322
+ }
323
+ }
324
+ }