@woosh/meep-engine 2.155.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 (475) 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_orthonormal_matrix_from_normal.d.ts.map +1 -0
  125. package/src/{engine/graphics/sh3/path_tracer/sampling → core/geom/vec3}/v3_orthonormal_matrix_from_normal.js +1 -1
  126. package/src/core/graph/coloring/colorizeGraph.js +2 -2
  127. package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
  128. package/src/core/graph/csr/CSRGraph.js +325 -319
  129. package/src/core/graph/layout/CircleLayout.d.ts.map +1 -1
  130. package/src/core/graph/layout/CircleLayout.js +8 -6
  131. package/src/core/graph/metis/native/refine/compute_kway_params.d.ts.map +1 -1
  132. package/src/core/graph/metis/native/refine/compute_kway_params.js +139 -138
  133. package/src/core/graph/mn_graph_coarsen.d.ts.map +1 -1
  134. package/src/core/graph/mn_graph_coarsen.js +4 -2
  135. package/src/core/graph/v2/NodeContainer.js +7 -7
  136. package/src/core/localization/LocalizationEngine.js +1 -1
  137. package/src/core/math/bell_membership_function.d.ts.map +1 -1
  138. package/src/core/math/bell_membership_function.js +3 -1
  139. package/src/core/math/complex/complex_add.d.ts +4 -4
  140. package/src/core/math/complex/complex_add.d.ts.map +1 -1
  141. package/src/core/math/complex/complex_add.js +3 -3
  142. package/src/core/math/complex/complex_div.d.ts +4 -4
  143. package/src/core/math/complex/complex_div.d.ts.map +1 -1
  144. package/src/core/math/complex/complex_div.js +3 -3
  145. package/src/core/math/complex/complex_mul.d.ts +4 -4
  146. package/src/core/math/complex/complex_mul.d.ts.map +1 -1
  147. package/src/core/math/complex/complex_mul.js +3 -3
  148. package/src/core/math/complex/complex_sub.d.ts +4 -4
  149. package/src/core/math/complex/complex_sub.d.ts.map +1 -1
  150. package/src/core/math/complex/complex_sub.js +3 -3
  151. package/src/core/math/idct_1d.d.ts +4 -4
  152. package/src/core/math/idct_1d.d.ts.map +1 -1
  153. package/src/core/math/idct_1d.js +3 -3
  154. package/src/core/math/noise/create_simplex_noise_2d.d.ts.map +1 -1
  155. package/src/core/math/noise/create_simplex_noise_2d.js +4 -2
  156. package/src/core/math/noise/sdnoise.d.ts.map +1 -1
  157. package/src/core/math/noise/sdnoise.js +12 -9
  158. package/src/core/math/physics/mie/compute_bhmie_optical_properties.d.ts.map +1 -1
  159. package/src/core/math/physics/mie/compute_bhmie_optical_properties.js +94 -50
  160. package/src/core/math/physics/mie/lorenz_mie_coefs.d.ts +3 -6
  161. package/src/core/math/physics/mie/lorenz_mie_coefs.d.ts.map +1 -1
  162. package/src/core/math/physics/mie/lorenz_mie_coefs.js +180 -157
  163. package/src/core/math/physics/mie/mie_ab_to_optical_properties.d.ts +3 -4
  164. package/src/core/math/physics/mie/mie_ab_to_optical_properties.d.ts.map +1 -1
  165. package/src/core/math/physics/mie/mie_ab_to_optical_properties.js +47 -21
  166. package/src/core/math/random/randomIntegerBetween.d.ts.map +1 -1
  167. package/src/core/math/random/randomIntegerBetween.js +4 -1
  168. package/src/core/math/solveCubic.d.ts.map +1 -1
  169. package/src/core/math/solveCubic.js +95 -82
  170. package/src/core/math/spline/computeCatmullRomSplineUniformDistance.d.ts.map +1 -1
  171. package/src/core/math/spline/computeCatmullRomSplineUniformDistance.js +13 -0
  172. package/src/core/math/statistics/softmax.js +1 -1
  173. package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts +1 -0
  174. package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts.map +1 -1
  175. package/src/core/model/node-graph/visual/NodeGraphVisualData.js +2 -1
  176. package/src/core/model/node-graph/visual/NodeVisualData.js +1 -1
  177. package/src/core/model/object/ImmutableObjectPool.d.ts +7 -0
  178. package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
  179. package/src/core/model/object/ImmutableObjectPool.js +20 -10
  180. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.d.ts.map +1 -1
  181. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.js +39 -2
  182. package/src/core/model/reactive/model/terminal/ReactiveReference.d.ts.map +1 -1
  183. package/src/core/model/reactive/model/terminal/ReactiveReference.js +2 -0
  184. package/src/core/parser/simple/readHexToken.d.ts.map +1 -1
  185. package/src/core/parser/simple/readHexToken.js +6 -0
  186. package/src/core/primitives/numbers/number_pretty_print.d.ts.map +1 -1
  187. package/src/core/primitives/numbers/number_pretty_print.js +4 -1
  188. package/src/core/primitives/strings/string_jaro_winkler.js +1 -1
  189. package/src/core/process/CompositeProcess.js +1 -1
  190. package/src/core/process/action/AsynchronousDelayAction.d.ts.map +1 -1
  191. package/src/core/process/action/AsynchronousDelayAction.js +3 -0
  192. package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
  193. package/src/core/process/executor/ConcurrentExecutor.js +3 -2
  194. package/src/core/process/task/util/randomCountTask.d.ts.map +1 -1
  195. package/src/core/process/task/util/randomCountTask.js +3 -1
  196. package/src/core/process/undo/ActionProcessor.d.ts.map +1 -1
  197. package/src/core/process/undo/ActionProcessor.js +5 -3
  198. package/src/core/process/worker/WorkerBuilder.js +3 -3
  199. package/src/engine/animation/curve/AnimationCurve.d.ts.map +1 -1
  200. package/src/engine/animation/curve/AnimationCurve.js +4 -2
  201. package/src/engine/control/first-person/DESIGN.md +1 -1
  202. package/src/engine/control/first-person/FirstPersonMotionPhase.d.ts +55 -0
  203. package/src/engine/control/first-person/FirstPersonMotionPhase.d.ts.map +1 -0
  204. package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -0
  205. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +23 -2
  206. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  207. package/src/engine/control/first-person/FirstPersonPlayerController.js +1 -1
  208. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +168 -0
  209. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  210. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +115 -0
  211. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +71 -0
  212. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  213. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +255 -55
  214. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +82 -43
  215. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
  216. package/src/engine/control/first-person/abilities/LedgeGrab.js +405 -213
  217. package/src/engine/control/first-person/abilities/Mantle.d.ts +6 -0
  218. package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
  219. package/src/engine/control/first-person/abilities/Mantle.js +104 -45
  220. package/src/engine/control/first-person/abilities/ScrambleUp.d.ts +61 -0
  221. package/src/engine/control/first-person/abilities/ScrambleUp.d.ts.map +1 -0
  222. package/src/engine/control/first-person/abilities/ScrambleUp.js +182 -0
  223. package/src/engine/control/first-person/math/jumpDynamics.d.ts +84 -0
  224. package/src/engine/control/first-person/math/jumpDynamics.d.ts.map +1 -0
  225. package/src/engine/control/first-person/math/jumpDynamics.js +108 -0
  226. package/src/engine/control/first-person/prototype_first_person_controller.js +45 -1
  227. package/src/engine/graphics/camera/testClippingPlaneComputation.js +1 -1
  228. package/src/engine/graphics/ecs/path/tube/prototypeAnimatedPathMask.js +1 -1
  229. package/src/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +1 -1
  230. package/src/engine/graphics/render/buffer/buffers/prototypeNormalFrameBuffer.js +1 -1
  231. package/src/engine/graphics/render/forward_plus/plugin/ptototypeFPPlugin.js +1 -1
  232. package/src/engine/graphics/render/visibility/hiz/prototypeHiZ.js +1 -1
  233. package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +1 -1
  234. package/src/engine/graphics/shadows/testShadowMapRendering.js +1 -1
  235. package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -0
  236. package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -0
  237. package/src/engine/physics/PLAN.md +6 -5
  238. package/src/engine/physics/constraint/solve_constraints.d.ts +4 -1
  239. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  240. package/src/engine/physics/constraint/solve_constraints.js +147 -33
  241. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  242. package/src/engine/physics/ecs/PhysicsSystem.js +1750 -1747
  243. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
  244. package/src/engine/physics/gjk/gjk_epa_penetration.d.ts.map +1 -1
  245. package/src/engine/physics/gjk/gjk_epa_penetration.js +5 -9
  246. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -1
  247. package/src/engine/physics/narrowphase/convex_convex_manifold.js +22 -25
  248. package/src/engine/physics/narrowphase/convex_decomposition.d.ts +32 -13
  249. package/src/engine/physics/narrowphase/convex_decomposition.d.ts.map +1 -1
  250. package/src/engine/physics/narrowphase/convex_decomposition.js +61 -65
  251. package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts.map +1 -1
  252. package/src/engine/physics/narrowphase/mesh_convex_hull.js +13 -8
  253. package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
  254. package/src/engine/physics/narrowphase/refine_ray_concave.js +5 -3
  255. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
  256. package/src/engine/physics/narrowphase/refine_ray_hit.js +81 -78
  257. package/src/engine/sound/SoundEngine.d.ts.map +1 -1
  258. package/src/engine/sound/SoundEngine.js +28 -0
  259. package/src/engine/sound/dB2Volume.d.ts +1 -1
  260. package/src/engine/sound/dB2Volume.d.ts.map +1 -1
  261. package/src/engine/sound/dB2Volume.js +1 -1
  262. package/src/engine/sound/ecs/SoundController.d.ts +4 -0
  263. package/src/engine/sound/ecs/SoundController.d.ts.map +1 -1
  264. package/src/engine/sound/ecs/SoundController.js +4 -0
  265. package/src/engine/sound/ecs/SoundControllerSystem.d.ts +5 -0
  266. package/src/engine/sound/ecs/SoundControllerSystem.d.ts.map +1 -1
  267. package/src/engine/sound/ecs/SoundControllerSystem.js +5 -0
  268. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts +69 -0
  269. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts.map +1 -0
  270. package/src/engine/sound/ecs/audio/AudioEmitter.js +83 -0
  271. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts +97 -0
  272. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts.map +1 -0
  273. package/src/engine/sound/ecs/audio/AudioEmitterSystem.js +238 -0
  274. package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts +90 -0
  275. package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts.map +1 -0
  276. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +324 -0
  277. package/src/engine/sound/ecs/audio/SpatialAudioIndex.d.ts +59 -0
  278. package/src/engine/sound/ecs/audio/SpatialAudioIndex.d.ts.map +1 -0
  279. package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -0
  280. package/src/engine/sound/ecs/emitter/SoundEmitter.d.ts +16 -65
  281. package/src/engine/sound/ecs/emitter/SoundEmitter.d.ts.map +1 -1
  282. package/src/engine/sound/ecs/emitter/SoundEmitter.js +19 -224
  283. package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.d.ts +26 -29
  284. package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.d.ts.map +1 -1
  285. package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.js +168 -135
  286. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts +36 -59
  287. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts.map +1 -1
  288. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.js +154 -390
  289. package/src/engine/sound/ecs/emitter/SoundTrack.d.ts +20 -23
  290. package/src/engine/sound/ecs/emitter/SoundTrack.d.ts.map +1 -1
  291. package/src/engine/sound/ecs/emitter/SoundTrack.js +34 -152
  292. package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +993 -0
  293. package/src/engine/sound/sopra/README.md +643 -7
  294. package/src/engine/sound/sopra/SopraEngine.d.ts +229 -0
  295. package/src/engine/sound/sopra/SopraEngine.d.ts.map +1 -0
  296. package/src/engine/sound/sopra/SopraEngine.js +423 -0
  297. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.d.ts +26 -0
  298. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.d.ts.map +1 -0
  299. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -0
  300. package/src/engine/sound/sopra/asset/BufferProvider.d.ts +24 -0
  301. package/src/engine/sound/sopra/asset/BufferProvider.d.ts.map +1 -0
  302. package/src/engine/sound/sopra/asset/BufferProvider.js +29 -0
  303. package/src/engine/sound/sopra/asset/StubBufferProvider.d.ts +31 -0
  304. package/src/engine/sound/sopra/asset/StubBufferProvider.d.ts.map +1 -0
  305. package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -0
  306. package/src/engine/sound/sopra/definition/BusDefinition.d.ts +83 -0
  307. package/src/engine/sound/sopra/definition/BusDefinition.d.ts.map +1 -0
  308. package/src/engine/sound/sopra/definition/BusDefinition.js +142 -0
  309. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.d.ts +17 -0
  310. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.d.ts.map +1 -0
  311. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -0
  312. package/src/engine/sound/sopra/definition/DuckingRule.d.ts +71 -0
  313. package/src/engine/sound/sopra/definition/DuckingRule.d.ts.map +1 -0
  314. package/src/engine/sound/sopra/definition/DuckingRule.js +106 -0
  315. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.d.ts +18 -0
  316. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.d.ts.map +1 -0
  317. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -0
  318. package/src/engine/sound/sopra/definition/EventDescription.d.ts +132 -0
  319. package/src/engine/sound/sopra/definition/EventDescription.d.ts.map +1 -0
  320. package/src/engine/sound/sopra/definition/EventDescription.js +259 -0
  321. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts +17 -0
  322. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts.map +1 -0
  323. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +71 -0
  324. package/src/engine/sound/sopra/definition/MixerSnapshot.d.ts +51 -0
  325. package/src/engine/sound/sopra/definition/MixerSnapshot.d.ts.map +1 -0
  326. package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -0
  327. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.d.ts +18 -0
  328. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.d.ts.map +1 -0
  329. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -0
  330. package/src/engine/sound/sopra/definition/ParameterDefinition.d.ts +72 -0
  331. package/src/engine/sound/sopra/definition/ParameterDefinition.d.ts.map +1 -0
  332. package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -0
  333. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.d.ts +18 -0
  334. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.d.ts.map +1 -0
  335. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -0
  336. package/src/engine/sound/sopra/definition/SopraPanningModel.d.ts +14 -0
  337. package/src/engine/sound/sopra/definition/SopraPanningModel.d.ts.map +1 -0
  338. package/src/engine/sound/sopra/definition/SopraPanningModel.js +20 -0
  339. package/src/engine/sound/sopra/definition/VoiceStealMode.d.ts +10 -0
  340. package/src/engine/sound/sopra/definition/VoiceStealMode.d.ts.map +1 -0
  341. package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -0
  342. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +93 -0
  343. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -0
  344. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -0
  345. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +80 -0
  346. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -0
  347. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +181 -0
  348. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts +17 -0
  349. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts.map +1 -0
  350. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +74 -0
  351. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.d.ts +34 -0
  352. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.d.ts.map +1 -0
  353. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +100 -0
  354. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +101 -0
  355. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -0
  356. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +230 -0
  357. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.d.ts +17 -0
  358. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.d.ts.map +1 -0
  359. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -0
  360. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +103 -0
  361. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -0
  362. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -0
  363. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.d.ts +18 -0
  364. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.d.ts.map +1 -0
  365. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -0
  366. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +40 -0
  367. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -0
  368. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +91 -0
  369. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.d.ts +17 -0
  370. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.d.ts.map +1 -0
  371. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -0
  372. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.d.ts +44 -0
  373. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.d.ts.map +1 -0
  374. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -0
  375. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.d.ts +18 -0
  376. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.d.ts.map +1 -0
  377. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -0
  378. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +65 -0
  379. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -0
  380. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +131 -0
  381. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.d.ts +17 -0
  382. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.d.ts.map +1 -0
  383. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -0
  384. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts +24 -0
  385. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts.map +1 -0
  386. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -0
  387. package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts +70 -0
  388. package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts.map +1 -0
  389. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -0
  390. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.d.ts +18 -0
  391. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.d.ts.map +1 -0
  392. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -0
  393. package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts +74 -0
  394. package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts.map +1 -0
  395. package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -0
  396. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.d.ts +18 -0
  397. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.d.ts.map +1 -0
  398. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -0
  399. package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts +49 -0
  400. package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts.map +1 -0
  401. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +101 -0
  402. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.d.ts +18 -0
  403. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.d.ts.map +1 -0
  404. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -0
  405. package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.d.ts +31 -0
  406. package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.d.ts.map +1 -0
  407. package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.js +106 -0
  408. package/src/engine/sound/sopra/runtime/BusGraph.d.ts +79 -0
  409. package/src/engine/sound/sopra/runtime/BusGraph.d.ts.map +1 -0
  410. package/src/engine/sound/sopra/runtime/BusGraph.js +227 -0
  411. package/src/engine/sound/sopra/runtime/EventInstance.d.ts +144 -0
  412. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -0
  413. package/src/engine/sound/sopra/runtime/EventInstance.js +579 -0
  414. package/src/engine/sound/sopra/runtime/ParameterStore.d.ts +42 -0
  415. package/src/engine/sound/sopra/runtime/ParameterStore.d.ts.map +1 -0
  416. package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -0
  417. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.d.ts +42 -0
  418. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.d.ts.map +1 -0
  419. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -0
  420. package/src/engine/sound/sopra/runtime/Voice.d.ts +67 -0
  421. package/src/engine/sound/sopra/runtime/Voice.d.ts.map +1 -0
  422. package/src/engine/sound/sopra/runtime/Voice.js +145 -0
  423. package/src/engine/sound/sopra/runtime/VoiceManager.d.ts +38 -0
  424. package/src/engine/sound/sopra/runtime/VoiceManager.d.ts.map +1 -0
  425. package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -0
  426. package/src/engine/sound/sopra/runtime/VoicePool.d.ts +12 -0
  427. package/src/engine/sound/sopra/runtime/VoicePool.d.ts.map +1 -0
  428. package/src/engine/sound/sopra/runtime/VoicePool.js +17 -0
  429. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts +11 -0
  430. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts.map +1 -0
  431. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -0
  432. package/src/engine/sound/sopra/serialization/sopraJSON.d.ts +33 -0
  433. package/src/engine/sound/sopra/serialization/sopraJSON.d.ts.map +1 -0
  434. package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -0
  435. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts +27 -0
  436. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts.map +1 -0
  437. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -0
  438. package/src/engine/sound/sopra/util/MockAudioContext.d.ts +74 -0
  439. package/src/engine/sound/sopra/util/MockAudioContext.d.ts.map +1 -0
  440. package/src/engine/sound/sopra/util/MockAudioContext.js +215 -0
  441. package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts +15 -0
  442. package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts.map +1 -0
  443. package/src/engine/sound/sopra/util/buildAttenuationCurve.js +40 -0
  444. package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts +34 -0
  445. package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts.map +1 -0
  446. package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -0
  447. package/src/engine/sound/volume2dB.d.ts +1 -1
  448. package/src/engine/sound/volume2dB.d.ts.map +1 -1
  449. package/src/engine/sound/volume2dB.js +1 -1
  450. package/src/engine/graphics/sh3/path_tracer/sampling/v3_orthonormal_matrix_from_normal.d.ts.map +0 -1
  451. package/src/engine/physics/narrowphase/ray_shapes.d.ts +0 -66
  452. package/src/engine/physics/narrowphase/ray_shapes.d.ts.map +0 -1
  453. package/src/engine/physics/narrowphase/ray_shapes.js +0 -187
  454. package/src/engine/sound/ecs/emitter/SoundEmitterChannel.d.ts +0 -23
  455. package/src/engine/sound/ecs/emitter/SoundEmitterChannel.d.ts.map +0 -1
  456. package/src/engine/sound/ecs/emitter/SoundEmitterChannel.js +0 -32
  457. package/src/engine/sound/ecs/emitter/SoundTrackNodes.d.ts +0 -18
  458. package/src/engine/sound/ecs/emitter/SoundTrackNodes.d.ts.map +0 -1
  459. package/src/engine/sound/ecs/emitter/SoundTrackNodes.js +0 -18
  460. package/src/engine/sound/sopra/AbstractAudioClip.d.ts +0 -26
  461. package/src/engine/sound/sopra/AbstractAudioClip.d.ts.map +0 -1
  462. package/src/engine/sound/sopra/AbstractAudioClip.js +0 -29
  463. package/src/engine/sound/sopra/ContainerAudioClip.d.ts +0 -12
  464. package/src/engine/sound/sopra/ContainerAudioClip.d.ts.map +0 -1
  465. package/src/engine/sound/sopra/ContainerAudioClip.js +0 -13
  466. package/src/engine/sound/sopra/RandomContainerAudioClip.d.ts +0 -12
  467. package/src/engine/sound/sopra/RandomContainerAudioClip.d.ts.map +0 -1
  468. package/src/engine/sound/sopra/RandomContainerAudioClip.js +0 -15
  469. package/src/engine/sound/sopra/SequenceContainerAudioClip.d.ts +0 -7
  470. package/src/engine/sound/sopra/SequenceContainerAudioClip.d.ts.map +0 -1
  471. package/src/engine/sound/sopra/SequenceContainerAudioClip.js +0 -8
  472. package/src/engine/sound/sopra/SilenceAudioClip.d.ts +0 -13
  473. package/src/engine/sound/sopra/SilenceAudioClip.d.ts.map +0 -1
  474. package/src/engine/sound/sopra/SilenceAudioClip.js +0 -15
  475. /package/src/{engine/graphics/sh3/path_tracer/sampling → core/geom/vec3}/v3_orthonormal_matrix_from_normal.d.ts +0 -0
@@ -0,0 +1,364 @@
1
+ # Constraint-Solver Improvements Plan — A2 / A3 / A4
2
+
3
+ **Status:** complete — A2 baked (no flags); A3/A4 dropped on evidence; jitter goal
4
+ redirected to sleep · **Date:** 2026-06-09 · **Area:** `engine/physics` solver
5
+
6
+ > **Outcome (numbers + reasoning in `CONSTRAINT_SOLVER_BENCH_LOG.md`).** Burned in with
7
+ > **no flags/toggles** — one solver path. The Monte-Carlo **median over 150 seeds** was
8
+ > the decider (single-seed results were basin-luck):
9
+ > - **A2** (block 3×3 point-solve + symmetric sweep) — **BAKED**: −9–16% joint-solve
10
+ > time, exact ball-socket, mass-ratio-robust; residual-neutral in the median. The one
11
+ > robust win.
12
+ > - **A3** (ω under-relaxation) — **REMOVED**: raises the *median* residual, chaotic /
13
+ > bimodal in ω on the metastable scene, +46% transient penetration. A disproven
14
+ > bandaid, not shipped.
15
+ > - **A4** (contact-bias smoothing) — **REVERTED**: neutral, benefit unproven; not worth
16
+ > churning contact determinism.
17
+ >
18
+ > All config fields removed. Full physics suite **865 pass / 0 fail**; one golden band
19
+ > re-baselined for A2's (honest) slightly-livelier settle.
20
+ >
21
+ > **The settled-jitter goal is not closed by the solver — and the data shows why:** the
22
+ > residual is **basin/sleep-dominated, not convergence-dominated** (per-seed variance ≫
23
+ > any config difference), so no convergence/relaxation lever moves the median. The real
24
+ > lever is **island sleep** (item **B** below: aggregate-energy + hysteresis criterion),
25
+ > then **A1** (reduced coordinates) to remove the maximal-coordinate residual at source.
26
+ > A2 ships as a faster/more-correct foundation; B is the recommended next step.
27
+
28
+ ## 0. Goal & prime directive
29
+
30
+ Close the residual-jitter / "never sleeps" gap on coupled, over-constrained islands
31
+ (ragdolls, jointed chains) **without** resorting to "bump the iteration count". Three
32
+ systemic changes, all staying inside the existing maximal-coordinate TGS solver:
33
+
34
+ - **A4 — smooth the split-impulse position bias** near the penetration slop (kill the
35
+ bang-bang relay that drives contact-side chatter).
36
+ - **A2 — block joint point-solve + root-ordered symmetric Gauss-Seidel** (remove
37
+ intra-joint coupling error and the directional bias of one-way PGS).
38
+ - **A3 — constraint-level dissipation**: under-relaxation (SOR ω) on the joint solve +
39
+ optional compliance/damping on the locked joint DOFs (so the limit cycle *decays*
40
+ instead of persisting; replaces leaning on global velocity damping).
41
+
42
+ **Prime directive for this work:** *every significant step is gated by the benchmark
43
+ suite.* The engine is real-time and single-thread JS — a stray allocation, a polymorphic
44
+ deopt, or an extra hot-loop branch can erase the win. We do **not** discover performance
45
+ regressions after the fact: each change ships with an **a-priori expected perf delta**
46
+ and a **hard gate**. A measured regression beyond its anticipated budget **halts the
47
+ phase** until it is either optimized away or signed off with a recorded rationale.
48
+
49
+ This is the engineering counterpart to the reduced-coordinate option (A1), which is
50
+ explicitly **out of scope** here (see §8). A2/A3/A4 narrow the gap inside the current
51
+ architecture; A1 is a separate strategic bet.
52
+
53
+ ## 1. Background
54
+
55
+ The solver is already modern (see `JOLT_REVIEW.md`, `RAPIER_REVIEW.md`, `BULLET_REVIEW.md`,
56
+ `PLAN.md`): TGS, 4 substeps with per-substep position re-integration, per-substep
57
+ warm-starting for **both** contacts and joints, Catto split-impulse, persistent
58
+ feature-ID manifolds, atomic island sleeping. The jitter is therefore **not** a missing
59
+ technique — it is:
60
+
61
+ 1. **An unconverged coupled-island limit cycle.** 10 maximal-coordinate cone-twist joints
62
+ + self/ground contacts + ~4:1 mass ratio, solved by sequential impulse, leave a
63
+ residual that → 0 only in the iteration limit. The split-impulse position bias
64
+ re-derives from penetration around the 5 mm slop every substep — a relay forcing term —
65
+ while the only dissipation is blunt global velocity damping. Forcing + weak damping =
66
+ a low-amplitude limit cycle.
67
+ 2. **Above the sleep gate.** The residual spikes to ~0.7 rad/s, 7× the
68
+ `√sleepVelocitySqrThreshold ≈ 0.1` gate, so the island's sleep timer keeps resetting
69
+ and it never deactivates. (Same phenomenon the KEVA bench documents: a 5,320-body
70
+ island that "never fully quiesces, so atomic-island sleep rarely triggers".)
71
+
72
+ The quality signal is already pinned by `PhysicsSystem.ragdoll.spec.js`:
73
+ `GOLDEN.settledLinSpeed` (0.4–1.2 m/s, obs 0.73) and `GOLDEN.settledAngSpeed`
74
+ (0.4–1.2 rad/s, obs 0.75), plus the 1000-seed Monte-Carlo sweep. Those are our quality
75
+ acceptance instruments; this plan adds the *performance* acceptance instruments alongside.
76
+
77
+ ## 2. The three changes (scope summary)
78
+
79
+ | ID | Change | Primary files | Risk | A-priori perf delta (gate) |
80
+ |----|--------|---------------|------|----------------------------|
81
+ | **A4** | Smooth contact position bias near slop | `solver/solve_contacts.js` | Low | ≤ **+2%** mean/p99 on contact-heavy benches; ~0% ragdoll |
82
+ | **A2** | Block 3×3 joint point-solve; root-order joints; symmetric (alternating) sweep | `constraint/solve_constraints.js`, `island/*`, `ecs/PhysicsSystem.js` | Medium | ≤ **+10%** joint-heavy at *equal* `jointIterations`; **target net-neutral / negative** after iteration tuning; ~0% on contact-only benches |
83
+ | **A3** | SOR under-relaxation + locked-DOF compliance/damping | `constraint/solve_constraints.js`, `ecs/Joint.js`, `ecs/PhysicsSystem.js` | Low–Med | ≤ **+2%** joint-heavy |
84
+
85
+ Cumulative worst-case before tuning: **≈ +8–14%** on the joint-heavy ragdoll-field bench,
86
+ **≈ +2–3%** contact-heavy, **≈ 0%** contact-only-no-joint. After A2 enables a
87
+ `jointIterations` reduction at equal-or-better convergence (Phase 4), the joint-heavy
88
+ target is **≤ +3% or net-negative**. These are the numbers the gates enforce.
89
+
90
+ ## 3. Measurement methodology & acceptance gates
91
+
92
+ > This section is the backbone of the plan. No code change is merged until its
93
+ > measurement step is green.
94
+
95
+ ### 3.1 Benchmark suite (existing — the perf canaries)
96
+
97
+ Run from the moh root (`npx jest --config jest.conf.json -t "<name>"`, flip `test.skip`→`test`):
98
+
99
+ - `ecs/PhysicsSystem.bench.spec.js`
100
+ - *falling tower* (1000 random shapes) — broad mixed contact load
101
+ - *settling grid* (1024) — **sleep canary** (must still sleep fast)
102
+ - *KEVA tower* (5,320) — large-island contact stress (heaviest; run alone)
103
+ - *high-churn lifecycle* (10k ticks) — alloc/leak/drift detector
104
+ - *reproducibility* (8 trials bit-identical) — **determinism gate**
105
+ - `constraint/swing_twist.bench.spec.js` — joint math microbench (A2/A3 canary)
106
+ - `ecs/PhysicsMeshStack.bench.spec.js`, `ecs/PhysicsNarrowphaseRain.bench.spec.js`,
107
+ `queries/raycast.bench.spec.js` — **control benches**: should be ~0% on all three
108
+ changes; non-zero here means we touched code we didn't mean to.
109
+
110
+ ### 3.2 New benches to add (Phase 0)
111
+
112
+ `ecs/PhysicsSystem.ragdoll.bench.spec.js` (reuse `buildScene` from the ragdoll spec):
113
+
114
+ - **micro** — 1 ragdoll × 600 steps: per-tick joint-solve cost in isolation (A2/A3 signal,
115
+ low contact noise).
116
+ - **macro / field** — *N* ragdolls (start N=64, also report N=256) dropped into a shared
117
+ pile: realistic joint+contact+self-collision load. This is the **headline A2/A3 perf
118
+ bench**.
119
+ - **settle metric** — for the field scene, report **(a)** ticks-until-island-sleep and
120
+ **(b)** fraction of bodies asleep at t=10 s, plus the settled-speed peak. This is the
121
+ *quality-of-settling* number the whole effort is about; it must **improve** (more sleep,
122
+ less residual), never regress.
123
+
124
+ Follow the existing bench conventions exactly: `test.skip` by default, `performance.now()`
125
+ per `fixedUpdate`, `compute_stats`, fixed seeds, report `sys.storage.awake_count`.
126
+
127
+ ### 3.3 Quality / stability gates (must stay green on every change)
128
+
129
+ 1. `PhysicsSystem.ragdoll.spec.js` — all 3 active tests pass; `GOLDEN.settledLinSpeed`
130
+ / `settledAngSpeed` bands hold (or **improve**, with a deliberate re-baseline recorded).
131
+ 2. **Determinism** — bit-identical: the ragdoll determinism test (256 steps) **and** the
132
+ bench reproducibility trial. No `toBeCloseTo`; `===` only.
133
+ 3. **No NaN/Inf**, no fall-through (`count_fall_through == 0` on the thick-slab benches),
134
+ KEVA stack holds.
135
+ 4. **Sleep not broken** — settling grid still reaches near-full sleep in the last second.
136
+ 5. Monte-Carlo sweep (`RAGDOLL_SWEEP_RUNS=200`) — no NaN seeds; worst settled speed within
137
+ band across the seed space.
138
+
139
+ ### 3.4 Performance protocol (JS noise control)
140
+
141
+ JS timing is noisy; measurements are only trustworthy under a fixed protocol:
142
+
143
+ - **Prefer same-process A/B via config flags** (§3.6): run each bench twice in one process,
144
+ flag-off then flag-on, and report the **delta**. This cancels machine/JIT/thermal
145
+ variance — the cleanest signal and the primary acceptance measurement.
146
+ - **Warm-up**: discard the first full bench run (JIT tier-up) before timing.
147
+ - **Repetition**: 5 runs; report **median of the per-run medians** for p50 and the
148
+ **max of per-run p99** for tail.
149
+ - **Noise band**: deltas **< 3%** are reported but **not** gated (within run-to-run noise).
150
+ Gates trigger only on deltas that exceed both 3% **and** the budget.
151
+ - **Environment**: record `node -v`, OS, machine; same machine for baseline and
152
+ comparison; AC power, no competing load. Pin the Node version for the whole effort.
153
+ - KEVA (~15 min/run) is run **once per phase** (pre/post), not per-commit; the
154
+ lightweight benches (ragdoll micro/field, settling grid, swing_twist) run per-commit.
155
+
156
+ ### 3.5 Regression policy & anticipated-budget gate
157
+
158
+ The user requirement — *"if performance regresses it must be anticipated and within
159
+ expected limits"* — is operationalized as:
160
+
161
+ - Each phase has a **budget** (the per-bench numbers in §2 / the table below).
162
+ - After implementing a phase, run the protocol (§3.4). For every bench:
163
+ - delta **≤ budget** → **accept**, record the number.
164
+ - budget **< delta** within a documented stretch (e.g. before the Phase-4 iteration
165
+ cut) → **accept only with a one-line signed rationale** in the results log naming why
166
+ and the recovery plan.
167
+ - delta **> budget** with no rationale → **STOP**. Optimize (profile for alloc/deopt/
168
+ branch cost) or revert. Do not proceed to the next phase on an unexplained regression.
169
+ - Quality gate failing → **STOP** regardless of perf (correctness-first; no loosening
170
+ tolerances to pass — see project policy).
171
+
172
+ **Consolidated budget table** (filled with measured baselines in Phase 0):
173
+
174
+ | Bench | Metric | Baseline (Phase 0) | After A4 | After A2 (equal iters) | After A3 | After Phase-4 tune |
175
+ |-------|--------|--------------------|----------|------------------------|----------|--------------------|
176
+ | ragdoll field (N=64) | p50 / p99 tick | TBD | budget +0% | +≤10% | +≤2% | **≤ +3% or ↓** |
177
+ | ragdoll field — sleep% @10s | higher=better | TBD | ≥ baseline | **↑** | **↑** | **↑** |
178
+ | settling grid | p50; last-1s | TBD | +≤2% | +≤1% | +≤1% | ≤ +3% |
179
+ | KEVA tower | mean tick | TBD | +≤2% | +≤1% | +≤1% | ≤ +3% |
180
+ | falling tower | p50 / p99 | TBD | +≤2% | +≤1% | +≤1% | ≤ +3% |
181
+ | swing_twist micro | mean | TBD | ~0% | +≤15%* | +≤3% | ≤ +5% |
182
+ | MeshStack / Rain / raycast | p50 | TBD | ~0% | ~0% | ~0% | ~0% |
183
+
184
+ \* swing_twist is a pure joint-math microbench with no iteration-count amortization, so it
185
+ shows A2's per-iteration cost most starkly; the field bench (with iteration tuning) is the
186
+ real-world number.
187
+
188
+ ### 3.6 Config flags for clean A/B (and uniform-flow burn-in)
189
+
190
+ Add solver config fields (defaults preserve current behavior so Phase 0 baseline ==
191
+ current `master`), letting each change be measured in isolation and rolled back instantly:
192
+
193
+ - `contactBiasSmoothing: boolean` (A4)
194
+ - `jointBlockSolve: boolean`, `jointSweepSymmetric: boolean` (A2)
195
+ - `jointRelaxation: number` (A3, ω; default **1.0** = no relaxation)
196
+ - `jointComplianceLin: number` / `jointDampingLin: number` (A3; default **0** = rigid)
197
+
198
+ These follow the existing config-knob style (`velocityIterations`, `jointIterations`, …),
199
+ not sentinels. **Burn-in (Phase 4):** once a *boolean* is validated as strictly better,
200
+ **collapse to the single path and delete the flag** (uniform control flow — we don't keep
201
+ two joint solvers forever). The *numeric* knobs (`jointRelaxation`, compliance/damping)
202
+ stay as real tuning config with sensible defaults.
203
+
204
+ ### 3.7 Results log
205
+
206
+ Commit `CONSTRAINT_SOLVER_BENCH_LOG.md` next to this plan: one table per phase with
207
+ machine/Node, baseline vs after, delta, pass/fail, and any signed rationale. The plan is
208
+ the contract; the log is the evidence.
209
+
210
+ ## 4. Phases
211
+
212
+ ### Phase 0 — Baseline & harness (no solver changes)
213
+
214
+ **Objective:** lock a trustworthy baseline and the new benches before touching the solver.
215
+
216
+ - [ ] Add `ecs/PhysicsSystem.ragdoll.bench.spec.js` (micro + field + settle metric, §3.2).
217
+ - [ ] Add the config flags (§3.6) wired as **no-ops at default** (pure plumbing; verify
218
+ determinism + golden bands unchanged → proves the plumbing is inert).
219
+ - [ ] Run the **full** suite under the protocol (§3.4); fill the §3.5 baseline column and
220
+ seed `CONSTRAINT_SOLVER_BENCH_LOG.md`. Record `node -v`, machine.
221
+ - [ ] Confirm all §3.3 quality gates green on baseline.
222
+ - **Exit:** baseline numbers committed; flags inert; determinism intact.
223
+
224
+ ### Phase 1 — A4: smooth contact position bias
225
+
226
+ **Why first:** smallest, most contained change (one function, contact-side only), so it
227
+ validates the gate discipline before we touch the joint hot path. Independent of A2/A3.
228
+
229
+ - [ ] In `solve_contacts.js` position pass, replace the hard slop gate
230
+ (`if depth>slop: bias=-spook_a*(depth-slop)` then `MAX_POSITION_BIAS` clamp) with a
231
+ **smooth ramp** across a small band above the slop (e.g. quadratic ease-in to the
232
+ linear region, or softplus), removing the C0 kink and the saturation corner that the
233
+ contact rings on. Keep the asymptotic linear gain identical so steady-state push-out
234
+ is unchanged.
235
+ - [ ] Behind `contactBiasSmoothing` flag.
236
+ - **Expected perf:** +2–4 flops/contact/position-iter (×4 substeps). Budget **≤ +2%** on
237
+ KEVA / grid / falling tower; **~0%** ragdoll & control benches.
238
+ - [ ] **Bench gate:** flag-off vs flag-on A/B on KEVA, settling grid, falling tower,
239
+ ragdoll field, + control benches. Confirm within budget; record in log.
240
+ - [ ] **Quality gate:** §3.3, with attention to fall-through (bias change must not let
241
+ penetration leak) and settled penetration band in the ragdoll spec.
242
+ - **Exit:** within budget, quality green, logged. (Decision to default-on deferred to
243
+ Phase 4 burn-in.)
244
+
245
+ ### Phase 2 — A2: block point-solve + root-ordered symmetric Gauss-Seidel
246
+
247
+ **Objective:** remove intra-joint coupling error and one-way-sweep directional bias — the
248
+ structural joint-convergence win.
249
+
250
+ - [ ] **Block the 3 locked-linear DOFs** of each joint: form the 3×3 effective-mass
251
+ `K = (invMₐ+invM_b)I + skew(rₐ)ᵀinvIₐskew(rₐ) + skew(r_b)ᵀinvI_b skew(r_b)`,
252
+ solve the coupled impulse via a **closed-form, zero-alloc, regularized** 3×3 inverse
253
+ (guard near-singular → ε or per-DOF fallback). Keep `LIMITED`/`MOTOR`/`SPRING`
254
+ angular DOFs per-DOF (inequalities don't block cleanly). Behind `jointBlockSolve`.
255
+ *(Standard Catto block-solver technique applied to the ball-socket point.)*
256
+ - [ ] **Root-order joints** per island: BFS from a chosen root over island connectivity
257
+ (reuse `island/` data); store one pooled ordered-index array per island.
258
+ - [ ] **Symmetric sweep**: alternate forward (root→tip) / backward each joint iteration.
259
+ Behind `jointSweepSymmetric`.
260
+ - [ ] Preserve **warm-start** semantics exactly (per-substep replay); the block solve must
261
+ accumulate the same `dofImpulse` representation so warm-start and determinism hold.
262
+ - **Expected perf:** per-joint-iteration linear-block arithmetic +40–80% vs 3 scalars,
263
+ ×`jointIterations`(8)×substeps(4). Budget **≤ +10%** joint-heavy / swing_twist **≤ +15%**
264
+ at equal iterations; **~0%** on contact-only & control benches (joint path not hit — a key
265
+ canary that the change is properly scoped).
266
+ - [ ] **Bench gate:** A/B each flag *independently* then together, on ragdoll micro/field +
267
+ swing_twist + KEVA (KEVA has few joints → confirms ~0% there) + controls.
268
+ - [ ] **Quality gate:** §3.3 + **convergence improvement evidence**: settled speed should
269
+ drop and/or sleep% rise on the ragdoll field bench at equal `jointIterations`.
270
+ Determinism re-baselined deliberately (the solve order/maths changed → exact values
271
+ change; update determinism reference + golden bands in one commit, recorded).
272
+ - **Exit:** within budget, measurable convergence/sleep improvement, quality green, logged.
273
+
274
+ ### Phase 3 — A3: constraint-level dissipation
275
+
276
+ **Objective:** make the residual *decay* (targeted dissipation), built on A2's solver.
277
+
278
+ - [ ] **Under-relaxation (SOR ω<1)** on the joint velocity solve: scale the per-iteration
279
+ impulse delta by `jointRelaxation` (default 1.0). One multiply per DOF/iter.
280
+ - [ ] **Locked-DOF compliance + damping**: give the locked joint DOFs a small SPOOK
281
+ compliance with damping by **reusing the existing `SPRING`/γ regularization path**
282
+ (`solve_constraints.js`), driven by `jointComplianceLin`/`jointDampingLin`
283
+ (default 0 = rigid). Soft *with damping* (not soft alone — soft alone rings).
284
+ - [ ] Add per-joint override fields on `Joint` only if a global default proves insufficient
285
+ (prefer one global default; avoid per-joint sentinel sprawl).
286
+ - **Expected perf:** ω ~free (<0.5%); compliance adds a few flops/locked-DOF/iter. Budget
287
+ **≤ +2%** joint-heavy; ~0% elsewhere.
288
+ - [ ] **Tuning sweep:** grid `jointRelaxation ∈ {1.0, 0.9, 0.8, 0.7}` ×
289
+ `compliance/damping ∈ {off, light, medium}` measured against the **settled-speed /
290
+ sleep%** metric on the ragdoll field bench (and the Monte-Carlo sweep for robustness
291
+ across seeds). Pick the point that minimizes residual **without** making the ragdoll
292
+ visibly spongy (watch joint-anchor error / limit overshoot).
293
+ - [ ] **Bench gate** + **quality gate** (§3.3); confirm the chosen ω/compliance lands the
294
+ ragdoll into **sleep within 10 s** (the headline outcome) and tighten the golden
295
+ bands to the new, lower settled speed (recorded re-baseline).
296
+ - **Exit:** ragdoll sleeps / residual materially reduced; within budget; logged.
297
+
298
+ ### Phase 4 — Iteration tuning, burn-in, final regression sweep
299
+
300
+ **Objective:** convert A2's better convergence into a perf *win*, and clean up.
301
+
302
+ - [ ] With A2+A3 on, **reduce `jointIterations`** (8 → trial 6, 5, 4) and re-measure: find
303
+ the lowest count that holds the quality gates. This is where the joint-heavy budget
304
+ returns to **≤ +3% or net-negative**. (Note: this is *not* "bump iterations" — it's
305
+ spending A2's convergence headroom to **cut** work.)
306
+ - [ ] **Burn-in**: for each validated boolean flag, collapse to the single code path and
307
+ delete the flag (uniform control flow). Keep numeric knobs as documented config with
308
+ the chosen defaults.
309
+ - [ ] **Full final regression sweep** including KEVA and the high-churn + reproducibility
310
+ lifecycle benches; fill the final budget column; sign off any residual overage.
311
+ - [ ] Update `PLAN.md` / relevant `*_REVIEW.md` cross-references; note remaining gap vs A1.
312
+ - **Exit:** all gates green, log complete, flags burned in, defaults set.
313
+
314
+ ## 5. Risk register & rollback
315
+
316
+ | Risk | Mitigation |
317
+ |------|------------|
318
+ | Hot-loop **allocation** in the 3×3 block path (GC → tail-latency spike) | All scratch in pooled typed arrays / locals; verify via p99 + high-churn drift bench; profile allocations |
319
+ | **Deopt / megamorphism** from new branches | Keep flags monomorphic; burn-in removes branches; control benches must stay ~0% |
320
+ | Block **near-singular** `K` (degenerate config) | Regularized inverse + per-DOF fallback; covered by a degenerate-config unit test |
321
+ | **Determinism** drift (cross-platform FP from new math/order) | Re-baseline deterministically in one commit; reproducibility bench is a gate |
322
+ | A3 **over-damping** → spongy ragdoll / joint drift | Tuning sweep gates on joint-anchor error + limit overshoot, not just residual |
323
+ | Accidental scope creep into contacts (A2) | Contact-only & control benches must read ~0%; any movement = STOP & investigate |
324
+ | Each change is independently **revertible** | Config flags until Phase-4 burn-in; one phase per commit series |
325
+
326
+ ## 6. Definition of done
327
+
328
+ - Ragdoll **sleeps within 10 s** (or residual settled speed materially reduced and golden
329
+ bands re-baselined lower); Monte-Carlo sweep robust across seeds.
330
+ - Every bench within its final budget (§3.5); all quality gates (§3.3) green; determinism
331
+ bit-identical; `CONSTRAINT_SOLVER_BENCH_LOG.md` complete with machine/Node and any
332
+ signed rationales.
333
+ - Flags burned in; tuning knobs documented with defaults.
334
+
335
+ ## 7. Out of scope (referenced, not done here)
336
+
337
+ - **A1 — reduced-coordinate (Featherstone) articulation.** The exact-joint formulation;
338
+ a separate strategic pillar (parallel solver + contact-coupling layer + two-formulation
339
+ maintenance tax). A2/A3/A4 narrow the gap inside the current architecture first; A1 is
340
+ evaluated on its own once these land and are measured.
341
+ - **B — joint-aware island sleep** (aggregate-energy + hysteresis sleep criterion). A
342
+ complementary, mostly-orthogonal change to `__sleep_test` in `PhysicsSystem.js`. If
343
+ A3 already gets the ragdoll under the existing gate, B may be unnecessary; if not, B is
344
+ the cheap finisher. Track separately.
345
+
346
+ ## 8. Appendix — touch points (current `master`)
347
+
348
+ - **Config knobs** — `ecs/PhysicsSystem.js`: `substeps`(4), `velocityIterations`(4),
349
+ `positionIterations`(1), `jointIterations`(8), `sleepVelocitySqrThreshold`(0.01),
350
+ `sleepTimeThreshold`(0.5). Add A2/A3/A4 flags here.
351
+ - **A4** — `solver/solve_contacts.js`: `PENETRATION_SLOP`(0.005), `CONTACT_RELAXATION`(4.75),
352
+ `MAX_POSITION_BIAS`(3), SPOOK gains (`g_spook_a`), per-contact position-bias block.
353
+ - **A2/A3** — `constraint/solve_constraints.js`: joint velocity solve, per-DOF loop,
354
+ per-substep warm-start, `LIMITED` inequality handling, `SPRING`/γ compliance path
355
+ (reused by A3), `swing_twist_error`. Joint ordering: `island/` (union-find + builder).
356
+ - **A3 joint fields** — `ecs/Joint.js` (`DofMode`, `dofImpulse`), `constraint/DofMode.js`.
357
+ - **Benches** — `ecs/PhysicsSystem.bench.spec.js`, `constraint/swing_twist.bench.spec.js`,
358
+ `ecs/PhysicsMeshStack.bench.spec.js`, `ecs/PhysicsNarrowphaseRain.bench.spec.js`,
359
+ `queries/raycast.bench.spec.js`; **new** `ecs/PhysicsSystem.ragdoll.bench.spec.js`.
360
+ - **Quality** — `ecs/PhysicsSystem.ragdoll.spec.js` (`GOLDEN` bands, determinism,
361
+ Monte-Carlo sweep), `ecs/PhysicsSystem.bench.spec.js` reproducibility trial.
362
+
363
+ > Line numbers intentionally omitted in favor of symbol names — they drift; grep the
364
+ > symbols. Verify each touch point before editing.
@@ -834,11 +834,12 @@ correctness gain.
834
834
 
835
835
  Phasing (each phase: implement → spec → run from `H:/git/moh` → commit):
836
836
 
837
- 1. [x] **Ray-primitive helpers** — landed as `narrowphase/ray_shapes.js`
838
- (local-frame `ray_sphere_local` / `ray_box_local` / `ray_capsule_local`,
839
- not `core/geom`: the (`t`, normal, miss = `Infinity`, first-hit-from-outside)
840
- convention is raycast-specific, and the dispatch shares one ray→local
841
- transform across them). Built local-frame (unit direction `t` preserved;
837
+ 1. [x] **Ray-primitive helpers** — originally landed as `narrowphase/ray_shapes.js`,
838
+ later EXTRACTED to `core/geom/3d/{sphere,box,capsule}/{sphere3,box3,capsule3}_raycast.js`
839
+ (the (`t`, normal, miss = `Infinity`, first-hit-from-outside) convention is a
840
+ fine general raycast contract, and a ray-vs-capsule primitive was otherwise
841
+ missing engine-wide; the dispatch still shares one ray→local transform across
842
+ them). Built local-frame (unit direction ⇒ `t` preserved;
842
843
  rotate the local normal back). Triangle MT is inlined in the concave path
843
844
  (the existing `computeTriangleRayIntersection` writes a `SurfacePoint3` and
844
845
  returns no `t` — unsuited to the buffer-flyweight loop). Colocated specs.
@@ -33,6 +33,9 @@ export function swing_twist_error(dx: number, dy: number, dz: number, dw: number
33
33
  * @param {number} dt_sub substep size in seconds (the SPOOK gain is derived
34
34
  * from it, matching the contact solver's per-substep position stiffness)
35
35
  * @param {number} iters velocity iterations
36
+ * @param {boolean} [reverse] A2: traverse the joint array tip→root this call
37
+ * (the caller alternates it per substep for a symmetric Gauss-Seidel sweep, so
38
+ * chain impulses propagate both ways). Default false ⇒ original root→tip order.
36
39
  */
37
- export function solve_joints(joints: Joint[], system: PhysicsSystem, dt_sub: number, iters: number): void;
40
+ export function solve_joints(joints: Joint[], system: PhysicsSystem, dt_sub: number, iters: number, reverse?: boolean): void;
38
41
  //# sourceMappingURL=solve_constraints.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"solve_constraints.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/constraint/solve_constraints.js"],"names":[],"mappings":"AAiOA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,sCAHW,MAAM,MAAa,MAAM,MAAa,MAAM,MAAa,MAAM,OAC/D,YAAY,QAkCtB;AA0JD;;;;;;;;;;;;;GAaG;AACH,qCANW,OAAO,iCAEP,MAAM,SAEN,MAAM,QAsPhB"}
1
+ {"version":3,"file":"solve_constraints.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/constraint/solve_constraints.js"],"names":[],"mappings":"AA6RA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,sCAHW,MAAM,MAAa,MAAM,MAAa,MAAM,MAAa,MAAM,OAC/D,YAAY,QAkCtB;AA0JD;;;;;;;;;;;;;;;;GAgBG;AACH,qCATW,OAAO,iCAEP,MAAM,SAEN,MAAM,YACN,OAAO,QA2SjB"}
@@ -123,6 +123,15 @@ const ang_clo = new Float64Array(3);
123
123
  const ang_chi = new Float64Array(3);
124
124
  const ang_gamma = new Float64Array(3);
125
125
 
126
+ // A2 (block solve) scratch: the coupled 3×3 effective-mass of a fully-LOCKED
127
+ // linear point constraint and its inverse, stored as the 6 symmetric entries
128
+ // [00, 01, 02, 11, 12, 22]; plus the per-body cross products `r×aₖ` and their
129
+ // inverse-inertia images `Iw⁻¹·(r×aₖ)`.
130
+ const lin_K = new Float64Array(6);
131
+ const lin_Kinv = new Float64Array(6);
132
+ const block_c = new Float64Array(9);
133
+ const block_w = new Float64Array(9);
134
+
126
135
  /**
127
136
  * Angular effective-mass contribution of one body about a unit world axis:
128
137
  * `a · Iw⁻¹ · a`. Zero for non-dynamic / rotation-locked bodies (whose
@@ -223,6 +232,57 @@ function clamp_bias(b) {
223
232
  return b;
224
233
  }
225
234
 
235
+ /**
236
+ * A2 — accumulate one body's angular contribution to the 3×3 point-constraint
237
+ * effective-mass block `K` (frame axes): for each frame axis `aₖ`,
238
+ * `cₖ = r×aₖ` and `wₖ = Iw⁻¹·cₖ`, then `K_kl += cₖ·w_l`. Symmetric, so only the
239
+ * upper triangle is written. Rotation-locked / non-dynamic bodies (zero inverse
240
+ * inertia) add nothing. The mass term `(invMA+invMB)·I` is seeded by the caller.
241
+ *
242
+ * @param {Float64Array} ss @param {number} base
243
+ * @param {number} rx @param {number} ry @param {number} rz lever arm
244
+ * @param {Float64Array} fa frame-A world basis (rows = axes)
245
+ * @param {Float64Array} K length-6 symmetric accumulator [00,01,02,11,12,22]
246
+ */
247
+ function accumulate_point_block(ss, base, rx, ry, rz, fa, K) {
248
+ const iix = ss[base + SBS_INV_I_X], iiy = ss[base + SBS_INV_I_Y], iiz = ss[base + SBS_INV_I_Z];
249
+ if (iix === 0 && iiy === 0 && iiz === 0) return;
250
+ const qx = ss[base + SBS_QX], qy = ss[base + SBS_QY], qz = ss[base + SBS_QZ], qw = ss[base + SBS_QW];
251
+ for (let k = 0; k < 3; k++) {
252
+ const ax = fa[3 * k], ay = fa[3 * k + 1], az = fa[3 * k + 2];
253
+ const cx = ry * az - rz * ay, cy = rz * ax - rx * az, cz = rx * ay - ry * ax;
254
+ block_c[3 * k] = cx; block_c[3 * k + 1] = cy; block_c[3 * k + 2] = cz;
255
+ world_inverse_inertia_apply_raw(block_w, 3 * k, iix, iiy, iiz, qx, qy, qz, qw, cx, cy, cz);
256
+ }
257
+ K[0] += block_c[0] * block_w[0] + block_c[1] * block_w[1] + block_c[2] * block_w[2];
258
+ K[1] += block_c[0] * block_w[3] + block_c[1] * block_w[4] + block_c[2] * block_w[5];
259
+ K[2] += block_c[0] * block_w[6] + block_c[1] * block_w[7] + block_c[2] * block_w[8];
260
+ K[3] += block_c[3] * block_w[3] + block_c[4] * block_w[4] + block_c[5] * block_w[5];
261
+ K[4] += block_c[3] * block_w[6] + block_c[4] * block_w[7] + block_c[5] * block_w[8];
262
+ K[5] += block_c[6] * block_w[6] + block_c[7] * block_w[7] + block_c[8] * block_w[8];
263
+ }
264
+
265
+ /**
266
+ * Invert a symmetric positive-definite 3×3 (6-entry form) in closed form,
267
+ * writing the inverse (same 6-entry form) to `out`. Returns the determinant; a
268
+ * near-singular block (|det| < 1e-12) returns 0 and leaves `out` untouched so
269
+ * the caller falls back to the per-axis solve.
270
+ *
271
+ * @param {Float64Array} K [00,01,02,11,12,22] @param {Float64Array} out
272
+ * @returns {number} determinant
273
+ */
274
+ function invert_sym3(K, out) {
275
+ const a = K[0], b = K[1], c = K[2], d = K[3], e = K[4], f = K[5];
276
+ const A00 = d * f - e * e, A01 = c * e - b * f, A02 = b * e - c * d;
277
+ const A11 = a * f - c * c, A12 = b * c - a * e, A22 = a * d - b * b;
278
+ const det = a * A00 + b * A01 + c * A02;
279
+ if (det < 1e-12 && det > -1e-12) return 0;
280
+ const inv = 1 / det;
281
+ out[0] = A00 * inv; out[1] = A01 * inv; out[2] = A02 * inv;
282
+ out[3] = A11 * inv; out[4] = A12 * inv; out[5] = A22 * inv;
283
+ return det;
284
+ }
285
+
226
286
  /**
227
287
  * Swing-twist decomposition of the relative rotation `qD = conj(qA)⊗qB`, giving
228
288
  * the per-frame-axis angular positions used by wide-cone angular DOFs.
@@ -443,8 +503,11 @@ function fill_row(joint, dofIndex, k, keff, pos, vel, spook_locked, spook_spec,
443
503
  * @param {number} dt_sub substep size in seconds (the SPOOK gain is derived
444
504
  * from it, matching the contact solver's per-substep position stiffness)
445
505
  * @param {number} iters velocity iterations
506
+ * @param {boolean} [reverse] A2: traverse the joint array tip→root this call
507
+ * (the caller alternates it per substep for a symmetric Gauss-Seidel sweep, so
508
+ * chain impulses propagate both ways). Default false ⇒ original root→tip order.
446
509
  */
447
- export function solve_joints(joints, system, dt_sub, iters) {
510
+ export function solve_joints(joints, system, dt_sub, iters, reverse) {
448
511
  const n = joints.length;
449
512
  if (n === 0 || dt_sub <= 0) return;
450
513
 
@@ -454,7 +517,8 @@ export function solve_joints(joints, system, dt_sub, iters) {
454
517
  const storage = system.storage;
455
518
  const ss = system.__solver_state.data;
456
519
 
457
- for (let ji = 0; ji < n; ji++) {
520
+ for (let jj = 0; jj < n; jj++) {
521
+ const ji = reverse ? (n - 1 - jj) : jj;
458
522
  const joint = joints[ji];
459
523
  if (joint === undefined || joint === null) continue;
460
524
 
@@ -541,26 +605,48 @@ export function solve_joints(joints, system, dt_sub, iters) {
541
605
 
542
606
  // --- Linear DOF row setup (frame axes). Position along axis k is
543
607
  // `C · axis_k`; convention is A−B (impulse +to A / −to B). ---
608
+ let useLinBlock = false;
544
609
  if (linConstrained) {
545
- for (let k = 0; k < 3; k++) {
546
- lin_active[k] = 0;
547
- const m = mode[k];
548
- if (m === DofMode.FREE) { imp[k] = 0; continue; }
549
- const ax = fa[3 * k], ay = fa[3 * k + 1], az = fa[3 * k + 2];
550
- const keff = axis_effective_mass(ss, baseA, invMA, rAx, rAy, rAz, ax, ay, az)
551
- + (to_world ? 0 : axis_effective_mass(ss, baseB, invMB, rBx, rBy, rBz, ax, ay, az));
552
- const pos = Cx * ax + Cy * ay + Cz * az;
553
- // Relative anchor velocity along the axis (A−B), for LIMITED
554
- // bound selection. Cheap; only the projection is used.
555
- let vel = 0;
556
- if (m === DofMode.LIMITED) {
557
- const rvx = (ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * rAz - ss[baseA + SBS_AV_Z] * rAy) - (to_world ? 0 : (ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rBz - ss[baseB + SBS_AV_Z] * rBy));
558
- const rvy = (ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rAx - ss[baseA + SBS_AV_X] * rAz) - (to_world ? 0 : (ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rBx - ss[baseB + SBS_AV_X] * rBz));
559
- const rvz = (ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * rAy - ss[baseA + SBS_AV_Y] * rAx) - (to_world ? 0 : (ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rBy - ss[baseB + SBS_AV_Y] * rBx));
560
- vel = rvx * ax + rvy * ay + rvz * az;
610
+ // A2: a fully-LOCKED linear triple is a ball-socket point — solve it
611
+ // as one coupled 3×3 block (frame axes) instead of axis-by-axis,
612
+ // removing the intra-joint coupling error the sequential per-axis
613
+ // Gauss-Seidel leaves behind. Falls back to per-axis if the block is
614
+ // near-singular.
615
+ if (mode[0] === DofMode.LOCKED && mode[1] === DofMode.LOCKED && mode[2] === DofMode.LOCKED) {
616
+ lin_K[0] = lin_K[3] = lin_K[5] = invMA + invMB;
617
+ lin_K[1] = lin_K[2] = lin_K[4] = 0;
618
+ accumulate_point_block(ss, baseA, rAx, rAy, rAz, fa, lin_K);
619
+ if (!to_world) accumulate_point_block(ss, baseB, rBx, rBy, rBz, fa, lin_K);
620
+ if (invert_sym3(lin_K, lin_Kinv) !== 0) {
621
+ for (let k = 0; k < 3; k++) {
622
+ const ax = fa[3 * k], ay = fa[3 * k + 1], az = fa[3 * k + 2];
623
+ lin_bias[k] = clamp_bias(spook_locked * (Cx * ax + Cy * ay + Cz * az));
624
+ lin_active[k] = 1;
625
+ }
626
+ useLinBlock = true;
627
+ }
628
+ }
629
+ if (!useLinBlock) {
630
+ for (let k = 0; k < 3; k++) {
631
+ lin_active[k] = 0;
632
+ const m = mode[k];
633
+ if (m === DofMode.FREE) { imp[k] = 0; continue; }
634
+ const ax = fa[3 * k], ay = fa[3 * k + 1], az = fa[3 * k + 2];
635
+ const keff = axis_effective_mass(ss, baseA, invMA, rAx, rAy, rAz, ax, ay, az)
636
+ + (to_world ? 0 : axis_effective_mass(ss, baseB, invMB, rBx, rBy, rBz, ax, ay, az));
637
+ const pos = Cx * ax + Cy * ay + Cz * az;
638
+ // Relative anchor velocity along the axis (A−B), for LIMITED
639
+ // bound selection. Cheap; only the projection is used.
640
+ let vel = 0;
641
+ if (m === DofMode.LIMITED) {
642
+ const rvx = (ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * rAz - ss[baseA + SBS_AV_Z] * rAy) - (to_world ? 0 : (ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rBz - ss[baseB + SBS_AV_Z] * rBy));
643
+ const rvy = (ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rAx - ss[baseA + SBS_AV_X] * rAz) - (to_world ? 0 : (ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rBx - ss[baseB + SBS_AV_X] * rBz));
644
+ const rvz = (ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * rAy - ss[baseA + SBS_AV_Y] * rAx) - (to_world ? 0 : (ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rBy - ss[baseB + SBS_AV_Y] * rBx));
645
+ vel = rvx * ax + rvy * ay + rvz * az;
646
+ }
647
+ fill_row(joint, k, k, keff, pos, vel, spook_locked, spook_spec, dt_sub,
648
+ lin_active, lin_eff, lin_bias, lin_clo, lin_chi, lin_gamma);
561
649
  }
562
- fill_row(joint, k, k, keff, pos, vel, spook_locked, spook_spec, dt_sub,
563
- lin_active, lin_eff, lin_bias, lin_clo, lin_chi, lin_gamma);
564
650
  }
565
651
  } else {
566
652
  lin_active[0] = lin_active[1] = lin_active[2] = 0;
@@ -650,22 +736,50 @@ export function solve_joints(joints, system, dt_sub, iters) {
650
736
  // Linear rows (frame axes). The relative anchor velocity is
651
737
  // recomputed before each row so successive rows see the prior
652
738
  // impulses (Gauss-Seidel coupling).
653
- for (let k = 0; k < 3; k++) {
654
- if (!lin_active[k]) continue;
655
- const ax = fa[3 * k], ay = fa[3 * k + 1], az = fa[3 * k + 2];
739
+ if (useLinBlock) {
740
+ // A2: coupled 3×3 point solve — one relative anchor velocity,
741
+ // projected onto the frame axes, solved against Kⁱⁿᵛ in a single
742
+ // Newton step (bilateral, no clamp). The point constraint is
743
+ // satisfied exactly each iteration; only its coupling with the
744
+ // angular rows remains to iterate.
656
745
  const rvx = (ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * rAz - ss[baseA + SBS_AV_Z] * rAy) - (to_world ? 0 : (ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rBz - ss[baseB + SBS_AV_Z] * rBy));
657
746
  const rvy = (ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rAx - ss[baseA + SBS_AV_X] * rAz) - (to_world ? 0 : (ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rBx - ss[baseB + SBS_AV_X] * rBz));
658
747
  const rvz = (ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * rAy - ss[baseA + SBS_AV_Y] * rAx) - (to_world ? 0 : (ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rBy - ss[baseB + SBS_AV_Y] * rBx));
659
- const vrel = rvx * ax + rvy * ay + rvz * az;
660
- const old = imp[k];
661
- // `+ γ·old` regularises a spring row = 0 otherwise → exact).
662
- let nv = old - lin_eff[k] * (vrel + lin_bias[k] + lin_gamma[k] * old);
663
- if (nv < lin_clo[k]) nv = lin_clo[k]; else if (nv > lin_chi[k]) nv = lin_chi[k];
664
- imp[k] = nv;
665
- const d = nv - old;
666
- if (d !== 0) {
667
- apply_impulse(ss, baseA, invMA, rAx, rAy, rAz, d * ax, d * ay, d * az, +1);
668
- if (!to_world) apply_impulse(ss, baseB, invMB, rBx, rBy, rBz, d * ax, d * ay, d * az, -1);
748
+ const a0x = fa[0], a0y = fa[1], a0z = fa[2];
749
+ const a1x = fa[3], a1y = fa[4], a1z = fa[5];
750
+ const a2x = fa[6], a2y = fa[7], a2z = fa[8];
751
+ const b0 = rvx * a0x + rvy * a0y + rvz * a0z + lin_bias[0];
752
+ const b1 = rvx * a1x + rvy * a1y + rvz * a1z + lin_bias[1];
753
+ const b2 = rvx * a2x + rvy * a2y + rvz * a2z + lin_bias[2];
754
+ const d0 = -(lin_Kinv[0] * b0 + lin_Kinv[1] * b1 + lin_Kinv[2] * b2);
755
+ const d1 = -(lin_Kinv[1] * b0 + lin_Kinv[3] * b1 + lin_Kinv[4] * b2);
756
+ const d2 = -(lin_Kinv[2] * b0 + lin_Kinv[4] * b1 + lin_Kinv[5] * b2);
757
+ imp[0] += d0; imp[1] += d1; imp[2] += d2;
758
+ const Px = d0 * a0x + d1 * a1x + d2 * a2x;
759
+ const Py = d0 * a0y + d1 * a1y + d2 * a2y;
760
+ const Pz = d0 * a0z + d1 * a1z + d2 * a2z;
761
+ if (Px !== 0 || Py !== 0 || Pz !== 0) {
762
+ apply_impulse(ss, baseA, invMA, rAx, rAy, rAz, Px, Py, Pz, +1);
763
+ if (!to_world) apply_impulse(ss, baseB, invMB, rBx, rBy, rBz, Px, Py, Pz, -1);
764
+ }
765
+ } else {
766
+ for (let k = 0; k < 3; k++) {
767
+ if (!lin_active[k]) continue;
768
+ const ax = fa[3 * k], ay = fa[3 * k + 1], az = fa[3 * k + 2];
769
+ const rvx = (ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * rAz - ss[baseA + SBS_AV_Z] * rAy) - (to_world ? 0 : (ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rBz - ss[baseB + SBS_AV_Z] * rBy));
770
+ const rvy = (ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rAx - ss[baseA + SBS_AV_X] * rAz) - (to_world ? 0 : (ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rBx - ss[baseB + SBS_AV_X] * rBz));
771
+ const rvz = (ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * rAy - ss[baseA + SBS_AV_Y] * rAx) - (to_world ? 0 : (ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rBy - ss[baseB + SBS_AV_Y] * rBx));
772
+ const vrel = rvx * ax + rvy * ay + rvz * az;
773
+ const old = imp[k];
774
+ // `+ γ·old` regularises a spring row (γ = 0 otherwise → exact).
775
+ let nv = old - lin_eff[k] * (vrel + lin_bias[k] + lin_gamma[k] * old);
776
+ if (nv < lin_clo[k]) nv = lin_clo[k]; else if (nv > lin_chi[k]) nv = lin_chi[k];
777
+ imp[k] = nv;
778
+ const d = nv - old;
779
+ if (d !== 0) {
780
+ apply_impulse(ss, baseA, invMA, rAx, rAy, rAz, d * ax, d * ay, d * az, +1);
781
+ if (!to_world) apply_impulse(ss, baseB, invMB, rBx, rBy, rBz, d * ax, d * ay, d * az, -1);
782
+ }
669
783
  }
670
784
  }
671
785