@woosh/meep-engine 2.156.0 → 2.157.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 (654) hide show
  1. package/README.md +1 -3
  2. package/editor/view/ecs/components/common/AutoCanvasView.js +100 -53
  3. package/editor/view/ecs/components/common/TextController.js +59 -0
  4. package/editor/view/node-graph/NodeGraphCamera.js +90 -0
  5. package/editor/view/node-graph/NodeGraphEditorView.js +121 -22
  6. package/editor/view/node-graph/NodeGraphSelection.js +89 -0
  7. package/editor/view/node-graph/NodeGraphView.js +669 -453
  8. package/editor/view/node-graph/NodeView.js +211 -135
  9. package/editor/view/node-graph/actions/ConnectionCreateAction.js +53 -0
  10. package/editor/view/node-graph/actions/ConnectionDeleteAction.js +36 -0
  11. package/editor/view/node-graph/actions/NodeDeleteAction.js +88 -0
  12. package/editor/view/node-graph/actions/NodeParameterSetAction.js +52 -0
  13. package/editor/view/node-graph/actions/NodesMoveAction.js +41 -0
  14. package/editor/view/node-graph/actions/SelectionSetAction.js +60 -0
  15. package/editor/view/node-graph/connection_wire_geometry.js +107 -0
  16. package/package.json +1 -1
  17. package/samples/generation/SampleGenerator0.js +8 -1
  18. package/src/core/binary/reinterpret_float32_as_uint32.d.ts +7 -0
  19. package/src/core/binary/reinterpret_float32_as_uint32.d.ts.map +1 -0
  20. package/src/core/binary/reinterpret_float32_as_uint32.js +13 -0
  21. package/src/core/binary/reinterpret_uint32_as_float32.d.ts +7 -0
  22. package/src/core/binary/reinterpret_uint32_as_float32.d.ts.map +1 -0
  23. package/src/core/binary/reinterpret_uint32_as_float32.js +14 -0
  24. package/src/core/bvh2/bvh3/ebvh_build_for_geometry_incremental.d.ts.map +1 -1
  25. package/src/core/bvh2/bvh3/ebvh_build_for_geometry_incremental.js +1 -3
  26. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_sphere.d.ts +12 -0
  27. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_sphere.d.ts.map +1 -0
  28. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_sphere.js +92 -0
  29. package/src/core/bvh8/BVH8.d.ts +127 -0
  30. package/src/core/bvh8/BVH8.d.ts.map +1 -0
  31. package/src/core/bvh8/BVH8.js +436 -0
  32. package/src/core/bvh8/NOTES.md +63 -0
  33. package/src/core/bvh8/build/BVH8Converter.d.ts +59 -0
  34. package/src/core/bvh8/build/BVH8Converter.d.ts.map +1 -0
  35. package/src/core/bvh8/build/BVH8Converter.js +588 -0
  36. package/src/core/bvh8/build/NodeProxy.d.ts +66 -0
  37. package/src/core/bvh8/build/NodeProxy.d.ts.map +1 -0
  38. package/src/core/bvh8/build/NodeProxy.js +308 -0
  39. package/src/core/bvh8/build/TriangleCluster.d.ts +29 -0
  40. package/src/core/bvh8/build/TriangleCluster.d.ts.map +1 -0
  41. package/src/core/bvh8/build/TriangleCluster.js +123 -0
  42. package/src/core/bvh8/build/aabb3_compute_merge_cost.d.ts +8 -0
  43. package/src/core/bvh8/build/aabb3_compute_merge_cost.d.ts.map +1 -0
  44. package/src/core/bvh8/build/aabb3_compute_merge_cost.js +29 -0
  45. package/src/core/bvh8/build/aabb3_from_triangle_by_index.d.ts +10 -0
  46. package/src/core/bvh8/build/aabb3_from_triangle_by_index.d.ts.map +1 -0
  47. package/src/core/bvh8/build/aabb3_from_triangle_by_index.js +18 -0
  48. package/src/core/bvh8/build/bvh8_build_for_geometry.d.ts +10 -0
  49. package/src/core/bvh8/build/bvh8_build_for_geometry.d.ts.map +1 -0
  50. package/src/core/bvh8/build/bvh8_build_for_geometry.js +303 -0
  51. package/src/core/bvh8/build/bvh8_from_proxy.d.ts +9 -0
  52. package/src/core/bvh8/build/bvh8_from_proxy.d.ts.map +1 -0
  53. package/src/core/bvh8/build/bvh8_from_proxy.js +256 -0
  54. package/src/core/bvh8/build/byte.d.ts +7 -0
  55. package/src/core/bvh8/build/byte.d.ts.map +1 -0
  56. package/src/core/bvh8/build/byte.js +10 -0
  57. package/src/core/bvh8/build/encode_bounds_e.d.ts +9 -0
  58. package/src/core/bvh8/build/encode_bounds_e.d.ts.map +1 -0
  59. package/src/core/bvh8/build/encode_bounds_e.js +12 -0
  60. package/src/core/bvh8/bvh8_convert_to_dot.d.ts +11 -0
  61. package/src/core/bvh8/bvh8_convert_to_dot.d.ts.map +1 -0
  62. package/src/core/bvh8/bvh8_convert_to_dot.js +133 -0
  63. package/src/core/bvh8/bvh8_count_primitives.d.ts +22 -0
  64. package/src/core/bvh8/bvh8_count_primitives.d.ts.map +1 -0
  65. package/src/core/bvh8/bvh8_count_primitives.js +98 -0
  66. package/src/core/bvh8/bvh8_geometry_validate.d.ts +16 -0
  67. package/src/core/bvh8/bvh8_geometry_validate.d.ts.map +1 -0
  68. package/src/core/bvh8/bvh8_geometry_validate.js +149 -0
  69. package/src/core/bvh8/bvh8_geometry_validate_indirect.d.ts +16 -0
  70. package/src/core/bvh8/bvh8_geometry_validate_indirect.d.ts.map +1 -0
  71. package/src/core/bvh8/bvh8_geometry_validate_indirect.js +177 -0
  72. package/src/core/bvh8/bvh8_get_node_bounds.d.ts +9 -0
  73. package/src/core/bvh8/bvh8_get_node_bounds.d.ts.map +1 -0
  74. package/src/core/bvh8/bvh8_get_node_bounds.js +35 -0
  75. package/src/core/bvh8/bvh8_get_node_child_bounds.d.ts +10 -0
  76. package/src/core/bvh8/bvh8_get_node_child_bounds.d.ts.map +1 -0
  77. package/src/core/bvh8/bvh8_get_node_child_bounds.js +53 -0
  78. package/src/core/bvh8/bvh8_node_child_surface_area.d.ts +9 -0
  79. package/src/core/bvh8/bvh8_node_child_surface_area.d.ts.map +1 -0
  80. package/src/core/bvh8/bvh8_node_child_surface_area.js +18 -0
  81. package/src/core/bvh8/bvh8_node_count_triangles.d.ts +8 -0
  82. package/src/core/bvh8/bvh8_node_count_triangles.d.ts.map +1 -0
  83. package/src/core/bvh8/bvh8_node_count_triangles.js +28 -0
  84. package/src/core/bvh8/bvh8_quality.d.ts +8 -0
  85. package/src/core/bvh8/bvh8_quality.d.ts.map +1 -0
  86. package/src/core/bvh8/bvh8_quality.js +73 -0
  87. package/src/core/bvh8/bvh8_validate_structure.d.ts +15 -0
  88. package/src/core/bvh8/bvh8_validate_structure.d.ts.map +1 -0
  89. package/src/core/bvh8/bvh8_validate_structure.js +87 -0
  90. package/src/core/collection/Uint32MinHeap.d.ts +56 -0
  91. package/src/core/collection/Uint32MinHeap.d.ts.map +1 -0
  92. package/src/core/collection/Uint32MinHeap.js +109 -0
  93. package/src/core/collection/list/FilteredListProjection.js +1 -1
  94. package/src/{engine/physics/island → core/collection/union-find}/union_find.d.ts +8 -5
  95. package/src/core/collection/union-find/union_find.d.ts.map +1 -0
  96. package/src/{engine/physics/island → core/collection/union-find}/union_find.js +8 -5
  97. package/src/core/dom/isImageBitmap.d.ts +7 -0
  98. package/src/core/dom/isImageBitmap.d.ts.map +1 -0
  99. package/src/core/dom/isImageBitmap.js +12 -0
  100. package/src/core/function/frameThrottle.d.ts +8 -0
  101. package/src/core/function/frameThrottle.d.ts.map +1 -0
  102. package/src/core/function/frameThrottle.js +23 -0
  103. package/src/{engine/physics/narrowphase/clip_against_axis_uv.d.ts → core/geom/2d/polygon/polygon2_clip_axis_halfplane.d.ts} +3 -3
  104. package/src/core/geom/2d/polygon/polygon2_clip_axis_halfplane.d.ts.map +1 -0
  105. package/src/{engine/physics/narrowphase/clip_against_axis_uv.js → core/geom/2d/polygon/polygon2_clip_axis_halfplane.js} +51 -51
  106. package/src/{engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts → core/geom/3d/aabb/aabb3_transform_oriented_inverse.d.ts} +9 -7
  107. package/src/core/geom/3d/aabb/aabb3_transform_oriented_inverse.d.ts.map +1 -0
  108. package/src/{engine/physics/narrowphase/decomposition/aabb_world_to_local.js → core/geom/3d/aabb/aabb3_transform_oriented_inverse.js} +9 -7
  109. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.d.ts +12 -0
  110. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.d.ts.map +1 -0
  111. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.js +46 -0
  112. package/src/core/geom/3d/box/box3_projected_half_extent.d.ts +28 -0
  113. package/src/core/geom/3d/box/box3_projected_half_extent.d.ts.map +1 -0
  114. package/src/core/geom/3d/box/box3_projected_half_extent.js +35 -0
  115. package/src/core/geom/3d/frustum/read_cluster_frustum_corners.js +1 -1
  116. package/src/core/geom/3d/frustum/read_frustum_corner.d.ts +9 -0
  117. package/src/core/geom/3d/frustum/read_frustum_corner.d.ts.map +1 -0
  118. package/src/core/geom/3d/frustum/read_frustum_corner.js +14 -0
  119. package/src/core/geom/3d/gjk/gjk.d.ts.map +1 -0
  120. package/src/{engine/physics → core/geom/3d}/gjk/gjk.js +430 -372
  121. package/src/{engine/physics → core/geom/3d}/gjk/gjk_epa_penetration.d.ts +8 -5
  122. package/src/core/geom/3d/gjk/gjk_epa_penetration.d.ts.map +1 -0
  123. package/src/{engine/physics → core/geom/3d}/gjk/gjk_epa_penetration.js +520 -544
  124. package/src/{engine/physics → core/geom/3d}/gjk/minkowski_support.d.ts +5 -4
  125. package/src/core/geom/3d/gjk/minkowski_support.d.ts.map +1 -0
  126. package/src/{engine/physics → core/geom/3d}/gjk/minkowski_support.js +71 -70
  127. package/src/{engine/physics → core/geom/3d}/gjk/mpr.d.ts +3 -3
  128. package/src/core/geom/3d/gjk/mpr.d.ts.map +1 -0
  129. package/src/{engine/physics → core/geom/3d}/gjk/mpr.js +368 -362
  130. package/src/{engine/physics/integration/quat_integrate.d.ts → core/geom/3d/quaternion/quat3_integrate.d.ts} +2 -2
  131. package/src/core/geom/3d/quaternion/quat3_integrate.d.ts.map +1 -0
  132. package/src/{engine/physics/integration/quat_integrate.js → core/geom/3d/quaternion/quat3_integrate.js} +1 -1
  133. package/src/{engine/physics/narrowphase/PosedShape.d.ts → core/geom/3d/shape/PosedShape3D.d.ts} +9 -8
  134. package/src/{engine/physics/narrowphase/PosedShape.d.ts.map → core/geom/3d/shape/PosedShape3D.d.ts.map} +1 -1
  135. package/src/{engine/physics/narrowphase/PosedShape.js → core/geom/3d/shape/PosedShape3D.js} +10 -9
  136. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  137. package/src/core/geom/3d/shape/TransformedShape3D.js +15 -11
  138. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +1 -1
  139. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +1 -1
  140. package/src/core/math/complex/complex_add.d.ts +1 -1
  141. package/src/core/math/complex/complex_add.d.ts.map +1 -1
  142. package/src/core/math/complex/complex_add.js +12 -3
  143. package/src/core/math/complex/complex_div.d.ts +1 -1
  144. package/src/core/math/complex/complex_div.d.ts.map +1 -1
  145. package/src/core/math/complex/complex_div.js +11 -4
  146. package/src/core/math/complex/complex_mul.d.ts +1 -1
  147. package/src/core/math/complex/complex_mul.d.ts.map +1 -1
  148. package/src/core/math/complex/complex_mul.js +10 -3
  149. package/src/core/math/complex/complex_sub.d.ts +1 -1
  150. package/src/core/math/complex/complex_sub.d.ts.map +1 -1
  151. package/src/core/math/complex/complex_sub.js +12 -3
  152. package/src/{engine/physics/fluid/solver/optimal_sor_omega.d.ts → core/math/linalg/sor_optimal_omega.d.ts} +4 -3
  153. package/src/core/math/linalg/sor_optimal_omega.d.ts.map +1 -0
  154. package/src/{engine/physics/fluid/solver/optimal_sor_omega.js → core/math/linalg/sor_optimal_omega.js} +4 -3
  155. package/src/core/math/lookup/ParameterLookupTable.d.ts +123 -0
  156. package/src/core/math/lookup/ParameterLookupTable.d.ts.map +1 -0
  157. package/src/core/math/lookup/ParameterLookupTable.js +495 -0
  158. package/src/core/math/lookup/ParameterLookupTableFlags.d.ts +5 -0
  159. package/src/core/math/lookup/ParameterLookupTableFlags.d.ts.map +1 -0
  160. package/src/core/math/lookup/ParameterLookupTableFlags.js +6 -0
  161. package/src/core/math/physics/kinematics/computeInterceptPoint.d.ts.map +1 -0
  162. package/src/{engine/physics → core/math/physics/kinematics}/computeInterceptPoint.js +79 -79
  163. package/src/core/math/physics/mie/ri_air.d.ts.map +1 -1
  164. package/src/core/math/physics/mie/ri_air.js +1 -3
  165. package/src/core/math/physics/mie/ri_ammonium_sulfate.d.ts.map +1 -1
  166. package/src/core/math/physics/mie/ri_ammonium_sulfate.js +1 -3
  167. package/src/core/math/physics/mie/ri_brine.d.ts.map +1 -1
  168. package/src/core/math/physics/mie/ri_brine.js +1 -3
  169. package/src/core/math/physics/mie/ri_dust.d.ts.map +1 -1
  170. package/src/core/math/physics/mie/ri_dust.js +1 -3
  171. package/src/core/math/physics/mie/ri_pollen.d.ts.map +1 -1
  172. package/src/core/math/physics/mie/ri_pollen.js +1 -3
  173. package/src/core/math/physics/mie/ri_smoke.d.ts.map +1 -1
  174. package/src/core/math/physics/mie/ri_smoke.js +1 -3
  175. package/src/core/math/physics/mie/ri_soot.d.ts.map +1 -1
  176. package/src/core/math/physics/mie/ri_soot.js +1 -3
  177. package/src/core/math/physics/mie/ri_water.d.ts.map +1 -1
  178. package/src/core/math/physics/mie/ri_water.js +1 -3
  179. package/src/core/math/random/random_pick_weighted_index.d.ts +10 -0
  180. package/src/core/math/random/random_pick_weighted_index.d.ts.map +1 -0
  181. package/src/core/math/random/random_pick_weighted_index.js +26 -0
  182. package/src/core/model/node-graph/NodeGraph.d.ts +9 -0
  183. package/src/core/model/node-graph/NodeGraph.d.ts.map +1 -1
  184. package/src/core/model/node-graph/NodeGraph.js +38 -0
  185. package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts +23 -0
  186. package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts.map +1 -1
  187. package/src/core/model/node-graph/visual/NodeGraphVisualData.js +54 -0
  188. package/src/core/path/convertPathToURL.d.ts +9 -0
  189. package/src/core/path/convertPathToURL.d.ts.map +1 -0
  190. package/src/core/path/convertPathToURL.js +107 -0
  191. package/src/core/process/worker/WorkerBuilder.js +1 -1
  192. package/src/core/process/worker/extractTransferables.js +1 -1
  193. package/src/engine/animation/curve/draw/build_tangent_editor.d.ts.map +1 -1
  194. package/src/engine/animation/curve/draw/build_tangent_editor.js +8 -1
  195. package/src/engine/animation/curve/editor/createKeyframeDraggableAspect.d.ts.map +1 -1
  196. package/src/engine/animation/curve/editor/createKeyframeDraggableAspect.js +11 -5
  197. package/src/engine/asset/Asset.d.ts.map +1 -1
  198. package/src/engine/asset/Asset.js +16 -6
  199. package/src/engine/asset/AssetManager.d.ts +61 -52
  200. package/src/engine/asset/AssetManager.d.ts.map +1 -1
  201. package/src/engine/asset/AssetManager.js +1411 -1045
  202. package/src/engine/asset/AssetRequest.d.ts +1 -1
  203. package/src/engine/asset/AssetRequest.d.ts.map +1 -1
  204. package/src/engine/asset/AssetRequest.js +1 -1
  205. package/src/engine/asset/AssetRequestScope.d.ts.map +1 -1
  206. package/src/engine/asset/AssetRequestScope.js +7 -0
  207. package/src/engine/asset/PendingAsset.d.ts +32 -1
  208. package/src/engine/asset/PendingAsset.d.ts.map +1 -1
  209. package/src/engine/asset/PendingAsset.js +108 -61
  210. package/src/engine/asset/loaders/ArrayBufferLoader.js +2 -2
  211. package/src/engine/asset/loaders/AssetLoader.d.ts.map +1 -1
  212. package/src/engine/asset/loaders/AssetLoader.js +19 -2
  213. package/src/engine/asset/loaders/GLTFAssetLoader.d.ts.map +1 -1
  214. package/src/engine/asset/loaders/GLTFAssetLoader.js +123 -114
  215. package/src/engine/asset/loaders/JavascriptAssetLoader.d.ts +1 -1
  216. package/src/engine/asset/loaders/JavascriptAssetLoader.d.ts.map +1 -1
  217. package/src/engine/asset/loaders/JavascriptAssetLoader.js +31 -47
  218. package/src/engine/asset/loaders/JsonAssetLoader.js +1 -1
  219. package/src/engine/asset/loaders/SVGAssetLoader.js +2 -2
  220. package/src/engine/asset/loaders/SoundAssetLoader.js +1 -1
  221. package/src/engine/asset/loaders/TextAssetLoader.js +2 -2
  222. package/src/{core → engine/asset/loaders}/font/FontAsset.d.ts +1 -1
  223. package/src/engine/asset/loaders/font/FontAsset.d.ts.map +1 -0
  224. package/src/{core → engine/asset/loaders}/font/FontAsset.js +21 -21
  225. package/src/{core → engine/asset/loaders}/font/FontAssetLoader.d.ts +1 -1
  226. package/src/engine/asset/loaders/font/FontAssetLoader.d.ts.map +1 -0
  227. package/src/{core → engine/asset/loaders}/font/FontAssetLoader.js +20 -20
  228. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts +1 -1
  229. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts.map +1 -1
  230. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +11 -20
  231. package/src/engine/asset/loaders/texture/TextureAssetLoader.d.ts.map +1 -1
  232. package/src/engine/asset/loaders/texture/TextureAssetLoader.js +8 -2
  233. package/src/engine/asset/preloader/AssetPreloader.js +1 -1
  234. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +1 -1
  235. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +1 -1
  236. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +19 -22
  237. package/src/engine/graphics/FrameThrottle.d.ts +1 -7
  238. package/src/engine/graphics/FrameThrottle.d.ts.map +1 -1
  239. package/src/engine/graphics/FrameThrottle.js +2 -24
  240. package/src/{core/geom/3d/shape/util → engine/graphics/debug}/shape_to_visual_entity.d.ts +1 -1
  241. package/src/engine/graphics/debug/shape_to_visual_entity.d.ts.map +1 -0
  242. package/src/{core/geom/3d/shape/util → engine/graphics/debug}/shape_to_visual_entity.js +159 -159
  243. package/src/{core/geom/3d/tetrahedra → engine/graphics/debug}/visualize_tetrahedral_mesh.d.ts +1 -1
  244. package/src/engine/graphics/debug/visualize_tetrahedral_mesh.d.ts.map +1 -0
  245. package/src/{core/geom/3d/tetrahedra → engine/graphics/debug}/visualize_tetrahedral_mesh.js +46 -46
  246. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.d.ts +1 -1
  247. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.d.ts.map +1 -1
  248. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js +22 -32
  249. package/src/engine/graphics/particles/particular/engine/emitter/serde/ParameterLookupTableSerializationAdapter.d.ts.map +1 -1
  250. package/src/engine/graphics/particles/particular/engine/emitter/serde/ParameterLookupTableSerializationAdapter.js +2 -76
  251. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.d.ts.map +1 -1
  252. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +2 -427
  253. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.d.ts +1 -4
  254. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.d.ts.map +1 -1
  255. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.js +2 -6
  256. package/src/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +1 -1
  257. package/src/engine/graphics/render/forward_plus/read_frustum_corner.d.ts +1 -8
  258. package/src/engine/graphics/render/forward_plus/read_frustum_corner.d.ts.map +1 -1
  259. package/src/engine/graphics/render/forward_plus/read_frustum_corner.js +2 -14
  260. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.d.ts +1 -11
  261. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.d.ts.map +1 -1
  262. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.js +2 -46
  263. package/src/engine/graphics/sh3/prototypeSH3Probe.js +1 -1
  264. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.d.ts +27 -0
  265. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.d.ts.map +1 -0
  266. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.js +81 -0
  267. package/src/engine/graphics/texture/isImageBitmap.d.ts +1 -6
  268. package/src/engine/graphics/texture/isImageBitmap.d.ts.map +1 -1
  269. package/src/engine/graphics/texture/isImageBitmap.js +2 -12
  270. package/src/{core/process/action → engine/intelligence/behavior/util}/AsynchronousDelayAction.d.ts +2 -2
  271. package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.d.ts.map +1 -0
  272. package/src/{core/process/action → engine/intelligence/behavior/util}/AsynchronousDelayAction.js +55 -55
  273. package/src/engine/network/NetworkSession.d.ts +12 -1
  274. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  275. package/src/engine/network/NetworkSession.js +52 -1
  276. package/src/engine/network/README.md +45 -0
  277. package/src/engine/network/convertPathToURL.d.ts +1 -8
  278. package/src/engine/network/convertPathToURL.d.ts.map +1 -1
  279. package/src/engine/network/convertPathToURL.js +2 -107
  280. package/src/engine/network/core/quantize/quantize_float.d.ts.map +1 -1
  281. package/src/engine/network/core/quantize/quantize_float.js +7 -0
  282. package/src/engine/network/core/quantize/quantize_position.d.ts.map +1 -1
  283. package/src/engine/network/core/quantize/quantize_position.js +12 -1
  284. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  285. package/src/engine/network/orchestrator/NetworkPeer.js +15 -1
  286. package/src/engine/network/replication/Replicator.d.ts +8 -0
  287. package/src/engine/network/replication/Replicator.d.ts.map +1 -1
  288. package/src/engine/network/replication/Replicator.js +48 -0
  289. package/src/engine/network/transport/Channel.d.ts.map +1 -1
  290. package/src/engine/network/transport/Channel.js +46 -12
  291. package/src/engine/network/transport/ReliableCommandPipeline.d.ts +16 -0
  292. package/src/engine/network/transport/ReliableCommandPipeline.d.ts.map +1 -1
  293. package/src/engine/network/transport/ReliableCommandPipeline.js +29 -0
  294. package/src/engine/network/transport/adapters/NodeUDPTransport.d.ts.map +1 -1
  295. package/src/engine/network/transport/adapters/NodeUDPTransport.js +7 -1
  296. package/src/engine/network/transport/fragments/packet_size.d.ts +5 -5
  297. package/src/engine/network/transport/fragments/packet_size.d.ts.map +1 -1
  298. package/src/engine/network/transport/fragments/packet_size.js +5 -5
  299. package/src/engine/physics/BULLET_REVIEW.md +1 -1
  300. package/src/engine/physics/JOLT_REVIEW.md +2 -2
  301. package/src/engine/physics/PLAN.md +1094 -945
  302. package/src/engine/physics/RAPIER_REVIEW.md +2 -2
  303. package/src/engine/physics/body/BodyStorage.d.ts +2 -12
  304. package/src/engine/physics/body/BodyStorage.d.ts.map +1 -1
  305. package/src/engine/physics/body/BodyStorage.js +406 -452
  306. package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -1
  307. package/src/engine/physics/body/SolverBodyState.js +12 -3
  308. package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts +28 -3
  309. package/src/engine/physics/broadphase/compute_fat_world_aabb.d.ts.map +1 -1
  310. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +60 -24
  311. package/src/engine/physics/broadphase/generate_pairs.d.ts +9 -5
  312. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  313. package/src/engine/physics/broadphase/generate_pairs.js +52 -37
  314. package/src/engine/physics/ccd/linear_sweep.d.ts +15 -5
  315. package/src/engine/physics/ccd/linear_sweep.d.ts.map +1 -1
  316. package/src/engine/physics/ccd/linear_sweep.js +122 -40
  317. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  318. package/src/engine/physics/constraint/solve_constraints.js +830 -805
  319. package/src/engine/physics/contact/ManifoldStore.d.ts +91 -16
  320. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  321. package/src/engine/physics/contact/ManifoldStore.js +204 -60
  322. package/src/engine/physics/ecs/BodyKind.d.ts +7 -3
  323. package/src/engine/physics/ecs/BodyKind.d.ts.map +1 -1
  324. package/src/engine/physics/ecs/BodyKind.js +29 -25
  325. package/src/engine/physics/ecs/Collider.d.ts +7 -0
  326. package/src/engine/physics/ecs/Collider.d.ts.map +1 -1
  327. package/src/engine/physics/ecs/Collider.js +7 -0
  328. package/src/engine/physics/ecs/ColliderSerializationAdapter.js +1 -1
  329. package/src/engine/physics/ecs/PhysicsSystem.d.ts +110 -6
  330. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  331. package/src/engine/physics/ecs/PhysicsSystem.js +467 -45
  332. package/src/engine/physics/ecs/RigidBody.d.ts +20 -5
  333. package/src/engine/physics/ecs/RigidBody.d.ts.map +1 -1
  334. package/src/engine/physics/ecs/RigidBody.js +307 -286
  335. package/src/engine/physics/ecs/RigidBodyFlags.d.ts +6 -3
  336. package/src/engine/physics/ecs/RigidBodyFlags.d.ts.map +1 -1
  337. package/src/engine/physics/ecs/RigidBodyFlags.js +31 -28
  338. package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts +12 -4
  339. package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts.map +1 -1
  340. package/src/engine/physics/ecs/RigidBodySerializationAdapter.js +19 -5
  341. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.d.ts +10 -0
  342. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.d.ts.map +1 -0
  343. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -0
  344. package/src/engine/physics/ecs/find_non_finite_physics_state.d.ts +28 -0
  345. package/src/engine/physics/ecs/find_non_finite_physics_state.d.ts.map +1 -0
  346. package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -0
  347. package/src/engine/physics/events/ContactEventBuffer.d.ts +11 -0
  348. package/src/engine/physics/events/ContactEventBuffer.d.ts.map +1 -1
  349. package/src/engine/physics/events/ContactEventBuffer.js +40 -0
  350. package/src/engine/physics/events/diff_manifolds.d.ts +30 -13
  351. package/src/engine/physics/events/diff_manifolds.d.ts.map +1 -1
  352. package/src/engine/physics/events/diff_manifolds.js +87 -50
  353. package/src/engine/physics/fluid/FluidField.d.ts +45 -17
  354. package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
  355. package/src/engine/physics/fluid/FluidField.js +53 -23
  356. package/src/engine/physics/fluid/FluidSimulator.d.ts +141 -5
  357. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  358. package/src/engine/physics/fluid/FluidSimulator.js +336 -43
  359. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +114 -0
  360. package/src/engine/physics/fluid/ecs/FluidComponent.d.ts +4 -3
  361. package/src/engine/physics/fluid/ecs/FluidComponent.d.ts.map +1 -1
  362. package/src/engine/physics/fluid/ecs/FluidComponent.js +4 -3
  363. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
  364. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.d.ts +41 -0
  365. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.d.ts.map +1 -0
  366. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.js +124 -0
  367. package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts +27 -8
  368. package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts.map +1 -1
  369. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +67 -18
  370. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.d.ts +42 -0
  371. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.d.ts.map +1 -0
  372. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +136 -0
  373. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.d.ts +37 -0
  374. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.d.ts.map +1 -0
  375. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +169 -0
  376. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.d.ts +36 -0
  377. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.d.ts.map +1 -0
  378. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +100 -0
  379. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.d.ts +6 -0
  380. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.d.ts.map +1 -1
  381. package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.js +6 -0
  382. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.d.ts +7 -2
  383. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.d.ts.map +1 -1
  384. package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.js +17 -12
  385. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.d.ts +42 -0
  386. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.d.ts.map +1 -0
  387. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -0
  388. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts +32 -22
  389. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts.map +1 -1
  390. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +43 -26
  391. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.d.ts +31 -0
  392. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.d.ts.map +1 -0
  393. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -0
  394. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts +26 -19
  395. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts.map +1 -1
  396. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +46 -42
  397. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts +38 -10
  398. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts.map +1 -1
  399. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +158 -75
  400. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +22 -17
  401. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
  402. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -96
  403. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +30 -1
  404. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  405. package/src/engine/physics/inertia/world_inverse_inertia.js +160 -116
  406. package/src/engine/physics/integration/integrate_position.js +97 -97
  407. package/src/engine/physics/island/IslandBuilder.d.ts +49 -8
  408. package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -1
  409. package/src/engine/physics/island/IslandBuilder.js +93 -14
  410. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  411. package/src/engine/physics/narrowphase/box_box_manifold.js +683 -673
  412. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
  413. package/src/engine/physics/narrowphase/box_triangle_contact.js +899 -749
  414. package/src/engine/physics/narrowphase/capsule_contacts.d.ts +27 -0
  415. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  416. package/src/engine/physics/narrowphase/capsule_contacts.js +624 -459
  417. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -1
  418. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +58 -38
  419. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -1
  420. package/src/engine/physics/narrowphase/compute_penetration.js +369 -325
  421. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts +3 -1
  422. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -1
  423. package/src/engine/physics/narrowphase/convex_convex_manifold.js +568 -422
  424. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +6 -3
  425. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -1
  426. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +66 -10
  427. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +4 -1
  428. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -1
  429. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +97 -94
  430. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
  431. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  432. package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1739
  433. package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts +14 -7
  434. package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts.map +1 -1
  435. package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -69
  436. package/src/engine/physics/persistence/solver_caches.d.ts +20 -0
  437. package/src/engine/physics/persistence/solver_caches.d.ts.map +1 -0
  438. package/src/engine/physics/persistence/solver_caches.js +309 -0
  439. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -1
  440. package/src/engine/physics/queries/overlap_shape.js +187 -184
  441. package/src/engine/physics/queries/raycast.d.ts +3 -2
  442. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  443. package/src/engine/physics/queries/raycast.js +37 -11
  444. package/src/engine/physics/queries/shape_cast.d.ts +18 -5
  445. package/src/engine/physics/queries/shape_cast.d.ts.map +1 -1
  446. package/src/engine/physics/queries/shape_cast.js +417 -393
  447. package/src/engine/physics/solver/solve_contacts.d.ts +22 -6
  448. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  449. package/src/engine/physics/solver/solve_contacts.js +1482 -1338
  450. package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -1
  451. package/src/engine/physics/vehicle/RaycastVehicle.js +344 -339
  452. package/src/engine/ui/DraggableAspect.d.ts +12 -3
  453. package/src/engine/ui/DraggableAspect.d.ts.map +1 -1
  454. package/src/engine/ui/DraggableAspect.js +115 -83
  455. package/src/generation/COORDINATES.md +54 -0
  456. package/src/generation/GridTaskGroup.js +2 -2
  457. package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -0
  458. package/src/generation/automata/CaveGeneratorCellularAutomata.d.ts +9 -1
  459. package/src/generation/automata/CaveGeneratorCellularAutomata.d.ts.map +1 -1
  460. package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -59
  461. package/src/generation/automata/CellularAutomata.d.ts +6 -3
  462. package/src/generation/automata/CellularAutomata.d.ts.map +1 -1
  463. package/src/generation/automata/CellularAutomata.js +22 -19
  464. package/src/generation/filtering/CellFilter.d.ts +17 -0
  465. package/src/generation/filtering/CellFilter.d.ts.map +1 -1
  466. package/src/generation/filtering/CellFilter.js +117 -77
  467. package/src/generation/filtering/CellFilterCellMatcher.d.ts.map +1 -1
  468. package/src/generation/filtering/CellFilterCellMatcher.js +2 -0
  469. package/src/generation/filtering/boolean/CellFilterLiteralBoolean.d.ts +5 -0
  470. package/src/generation/filtering/boolean/CellFilterLiteralBoolean.d.ts.map +1 -1
  471. package/src/generation/filtering/boolean/CellFilterLiteralBoolean.js +15 -0
  472. package/src/generation/filtering/core/CellFilterBinaryOperation.d.ts +0 -1
  473. package/src/generation/filtering/core/CellFilterBinaryOperation.d.ts.map +1 -1
  474. package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -50
  475. package/src/generation/filtering/core/CellFilterOperationTertiary.d.ts +0 -1
  476. package/src/generation/filtering/core/CellFilterOperationTertiary.d.ts.map +1 -1
  477. package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -59
  478. package/src/generation/filtering/core/CellFilterUnaryOperation.d.ts +0 -1
  479. package/src/generation/filtering/core/CellFilterUnaryOperation.d.ts.map +1 -1
  480. package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -33
  481. package/src/generation/filtering/numeric/CellFilterCache.d.ts +1 -0
  482. package/src/generation/filtering/numeric/CellFilterCache.d.ts.map +1 -1
  483. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.d.ts +3 -2
  484. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.d.ts.map +1 -1
  485. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.js +9 -35
  486. package/src/generation/filtering/numeric/complex/CellFilterCurvature.d.ts +0 -1
  487. package/src/generation/filtering/numeric/complex/CellFilterCurvature.d.ts.map +1 -1
  488. package/src/generation/filtering/numeric/complex/CellFilterCurvature.js +19 -43
  489. package/src/generation/filtering/numeric/complex/CellFilterFXAA.d.ts +0 -1
  490. package/src/generation/filtering/numeric/complex/CellFilterFXAA.d.ts.map +1 -1
  491. package/src/generation/filtering/numeric/complex/CellFilterFXAA.js +2 -6
  492. package/src/generation/filtering/numeric/complex/CellFilterGaussianBlur.d.ts.map +1 -1
  493. package/src/generation/filtering/numeric/complex/CellFilterGaussianBlur.js +9 -12
  494. package/src/generation/filtering/numeric/complex/CellFilterSimplexNoise.d.ts.map +1 -1
  495. package/src/generation/filtering/numeric/complex/CellFilterSimplexNoise.js +2 -1
  496. package/src/generation/filtering/numeric/complex/CellFilterSobel.d.ts +0 -1
  497. package/src/generation/filtering/numeric/complex/CellFilterSobel.d.ts.map +1 -1
  498. package/src/generation/filtering/numeric/complex/CellFilterSobel.js +2 -6
  499. package/src/generation/filtering/numeric/math/CellFilterInverseLerp.d.ts +5 -4
  500. package/src/generation/filtering/numeric/math/CellFilterInverseLerp.d.ts.map +1 -1
  501. package/src/generation/filtering/numeric/math/CellFilterInverseLerp.js +5 -4
  502. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.d.ts +17 -0
  503. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.d.ts.map +1 -0
  504. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -0
  505. package/src/generation/filtering/numeric/sampling/AbstractCellFilterSampleGridLayer.d.ts.map +1 -1
  506. package/src/generation/filtering/numeric/sampling/AbstractCellFilterSampleGridLayer.js +7 -1
  507. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.d.ts.map +1 -1
  508. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +7 -10
  509. package/src/generation/filtering/numeric/util/sampler_from_filter.d.ts.map +1 -1
  510. package/src/generation/filtering/numeric/util/sampler_from_filter.js +2 -1
  511. package/src/generation/grid/GridData.d.ts.map +1 -1
  512. package/src/generation/grid/GridData.js +14 -1
  513. package/src/generation/grid/actions/ContinuousGridCellAction.d.ts +10 -3
  514. package/src/generation/grid/actions/ContinuousGridCellAction.d.ts.map +1 -1
  515. package/src/generation/grid/actions/ContinuousGridCellAction.js +18 -3
  516. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainHeight.d.ts +11 -1
  517. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainHeight.d.ts.map +1 -1
  518. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainHeight.js +13 -3
  519. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainObstacle.d.ts +1 -1
  520. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainObstacle.js +2 -2
  521. package/src/generation/grid/actions/ContinuousGridCellActionWriteObstacle.d.ts +1 -1
  522. package/src/generation/grid/actions/ContinuousGridCellActionWriteObstacle.d.ts.map +1 -1
  523. package/src/generation/grid/actions/ContinuousGridCellActionWriteObstacle.js +4 -6
  524. package/src/generation/grid/coords/grid_to_texel.d.ts +9 -0
  525. package/src/generation/grid/coords/grid_to_texel.d.ts.map +1 -0
  526. package/src/generation/grid/coords/grid_to_texel.js +10 -0
  527. package/src/generation/grid/coords/texel_to_grid.d.ts +9 -0
  528. package/src/generation/grid/coords/texel_to_grid.d.ts.map +1 -0
  529. package/src/generation/grid/coords/texel_to_grid.js +10 -0
  530. package/src/generation/grid/generation/GridTaskApplyActionToCells.d.ts +2 -2
  531. package/src/generation/grid/generation/GridTaskApplyActionToCells.d.ts.map +1 -1
  532. package/src/generation/grid/generation/GridTaskApplyActionToCells.js +10 -6
  533. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.d.ts.map +1 -1
  534. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +20 -21
  535. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.d.ts +7 -0
  536. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.d.ts.map +1 -1
  537. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +18 -10
  538. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.d.ts.map +1 -1
  539. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +16 -7
  540. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.d.ts +5 -3
  541. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.d.ts.map +1 -1
  542. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +26 -23
  543. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.d.ts.map +1 -1
  544. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +10 -1
  545. package/src/generation/grid/generation/grid/select/CellSupplierBestN.d.ts.map +1 -1
  546. package/src/generation/grid/generation/grid/select/CellSupplierBestN.js +4 -0
  547. package/src/generation/grid/generation/road/GridTaskGenerateRoads.d.ts +15 -8
  548. package/src/generation/grid/generation/road/GridTaskGenerateRoads.d.ts.map +1 -1
  549. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +89 -92
  550. package/src/generation/markers/GridActionRuleSet.d.ts.map +1 -1
  551. package/src/generation/markers/GridActionRuleSet.js +10 -2
  552. package/src/generation/markers/GridCellActionPlaceMarker.d.ts +11 -0
  553. package/src/generation/markers/GridCellActionPlaceMarker.d.ts.map +1 -1
  554. package/src/generation/markers/GridCellActionPlaceMarker.js +20 -3
  555. package/src/generation/markers/GridCellActionPlaceMarkerGroup.d.ts +3 -1
  556. package/src/generation/markers/GridCellActionPlaceMarkerGroup.d.ts.map +1 -1
  557. package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +9 -2
  558. package/src/generation/markers/MarkerNode.d.ts +8 -3
  559. package/src/generation/markers/MarkerNode.d.ts.map +1 -1
  560. package/src/generation/markers/MarkerNode.js +12 -5
  561. package/src/generation/markers/actions/MarkerNodeActionEntityPlacement.js +1 -1
  562. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessor.d.ts +1 -1
  563. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessor.d.ts.map +1 -1
  564. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessor.js +1 -1
  565. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorClingToTerrain.d.ts +1 -1
  566. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorClingToTerrain.d.ts.map +1 -1
  567. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorClingToTerrain.js +1 -1
  568. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.d.ts +1 -1
  569. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.d.ts.map +1 -1
  570. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.js +2 -2
  571. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorSequence.d.ts +1 -1
  572. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorSequence.d.ts.map +1 -1
  573. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorSequence.js +2 -2
  574. package/src/generation/markers/actions/probability/MarkerNodeActionSelectWeighted.d.ts.map +1 -1
  575. package/src/generation/markers/actions/probability/MarkerNodeActionSelectWeighted.js +6 -4
  576. package/src/generation/markers/actions/probability/MarkerNodeActionWeightedElement.d.ts.map +1 -1
  577. package/src/generation/markers/actions/probability/MarkerNodeActionWeightedElement.js +1 -3
  578. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.d.ts.map +1 -1
  579. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +12 -11
  580. package/src/generation/markers/matcher/MarkerNodeMatcherAnd.js +2 -2
  581. package/src/generation/markers/transform/MarkerNodeTransformer.d.ts +4 -1
  582. package/src/generation/markers/transform/MarkerNodeTransformer.d.ts.map +1 -1
  583. package/src/generation/markers/transform/MarkerNodeTransformer.js +4 -1
  584. package/src/generation/markers/transform/MarkerNodeTransformerAddPositionYFromFilter.d.ts.map +1 -1
  585. package/src/generation/markers/transform/MarkerNodeTransformerAddPositionYFromFilter.js +1 -3
  586. package/src/generation/markers/transform/MarkerNodeTransformerOffsetPosition.d.ts +5 -0
  587. package/src/generation/markers/transform/MarkerNodeTransformerOffsetPosition.d.ts.map +1 -1
  588. package/src/generation/markers/transform/MarkerNodeTransformerOffsetPosition.js +15 -0
  589. package/src/generation/markers/transform/MarkerNodeTransformerRecordProperty.d.ts.map +1 -1
  590. package/src/generation/markers/transform/MarkerNodeTransformerRecordProperty.js +1 -3
  591. package/src/generation/markers/transform/MarkerNodeTransformerYRotateByFilter.d.ts.map +1 -1
  592. package/src/generation/markers/transform/MarkerNodeTransformerYRotateByFilter.js +2 -4
  593. package/src/generation/markers/transform/MarkerNodeTransformerYRotateByFilterGradient.d.ts.map +1 -1
  594. package/src/generation/markers/transform/MarkerNodeTransformerYRotateByFilterGradient.js +1 -3
  595. package/src/generation/placement/GridCellPlacementRule.d.ts.map +1 -1
  596. package/src/generation/placement/GridCellPlacementRule.js +1 -3
  597. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.d.ts.map +1 -1
  598. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +8 -10
  599. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.d.ts.map +1 -1
  600. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +6 -4
  601. package/src/generation/placement/action/random/weighted/WeightedGridCellAction.d.ts.map +1 -1
  602. package/src/generation/placement/action/random/weighted/WeightedGridCellAction.js +1 -3
  603. package/src/generation/rules/CellMatcher.d.ts +3 -1
  604. package/src/generation/rules/CellMatcher.d.ts.map +1 -1
  605. package/src/generation/rules/CellMatcher.js +3 -1
  606. package/src/generation/rules/CellMatcherFromFilter.d.ts.map +1 -1
  607. package/src/generation/rules/CellMatcherFromFilter.js +1 -3
  608. package/src/generation/rules/CellMatcherLayerBitMaskTest.d.ts.map +1 -1
  609. package/src/generation/rules/CellMatcherLayerBitMaskTest.js +6 -20
  610. package/src/generation/test_support/executeTaskTreeSync.d.ts +9 -0
  611. package/src/generation/test_support/executeTaskTreeSync.d.ts.map +1 -0
  612. package/src/generation/test_support/executeTaskTreeSync.js +78 -0
  613. package/src/generation/theme/TerrainLayerRuleAggregator.d.ts +2 -1
  614. package/src/generation/theme/TerrainLayerRuleAggregator.d.ts.map +1 -1
  615. package/src/generation/theme/TerrainLayerRuleAggregator.js +9 -6
  616. package/src/generation/theme/Theme.d.ts +1 -1
  617. package/src/generation/theme/Theme.d.ts.map +1 -1
  618. package/src/generation/theme/Theme.js +2 -2
  619. package/src/generation/theme/ThemeEngine.d.ts +3 -3
  620. package/src/generation/theme/ThemeEngine.d.ts.map +1 -1
  621. package/src/generation/theme/ThemeEngine.js +26 -16
  622. package/src/generation/theme/cell/CellProcessingRule.d.ts +3 -3
  623. package/src/generation/theme/cell/CellProcessingRule.d.ts.map +1 -1
  624. package/src/generation/theme/cell/CellProcessingRule.js +6 -10
  625. package/src/generation/theme/cell/CellProcessingRuleSet.d.ts +1 -1
  626. package/src/generation/theme/cell/CellProcessingRuleSet.d.ts.map +1 -1
  627. package/src/generation/theme/cell/CellProcessingRuleSet.js +2 -2
  628. package/src/view/common/ListView.js +1 -1
  629. package/src/view/elements/BottomLeftResizeHandleView.d.ts.map +1 -1
  630. package/src/view/elements/BottomLeftResizeHandleView.js +13 -5
  631. package/src/core/font/FontAsset.d.ts.map +0 -1
  632. package/src/core/font/FontAssetLoader.d.ts.map +0 -1
  633. package/src/core/geom/3d/shape/util/shape_to_visual_entity.d.ts.map +0 -1
  634. package/src/core/geom/3d/tetrahedra/visualize_tetrahedral_mesh.d.ts.map +0 -1
  635. package/src/core/process/action/AsynchronousDelayAction.d.ts.map +0 -1
  636. package/src/engine/physics/computeInterceptPoint.d.ts.map +0 -1
  637. package/src/engine/physics/fluid/solver/optimal_sor_omega.d.ts.map +0 -1
  638. package/src/engine/physics/gjk/gjk.d.ts.map +0 -1
  639. package/src/engine/physics/gjk/gjk_epa_penetration.d.ts.map +0 -1
  640. package/src/engine/physics/gjk/minkowski_support.d.ts.map +0 -1
  641. package/src/engine/physics/gjk/mpr.d.ts.map +0 -1
  642. package/src/engine/physics/integration/quat_integrate.d.ts.map +0 -1
  643. package/src/engine/physics/island/union_find.d.ts.map +0 -1
  644. package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +0 -1
  645. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +0 -1
  646. package/src/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.d.ts +0 -21
  647. package/src/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.d.ts.map +0 -1
  648. package/src/generation/grid/generation/discrete/layer/GridTaskDistanceToMarkers.js +0 -68
  649. package/src/generation/grid/generation/grid/GridTaskGridAlignedNodeGenerator.d.ts +0 -10
  650. package/src/generation/grid/generation/grid/GridTaskGridAlignedNodeGenerator.d.ts.map +0 -1
  651. package/src/generation/grid/generation/grid/GridTaskGridAlignedNodeGenerator.js +0 -17
  652. /package/src/{engine/physics → core/geom/3d}/gjk/NOTES.md +0 -0
  653. /package/src/{engine/physics → core/geom/3d}/gjk/gjk.d.ts +0 -0
  654. /package/src/{engine/physics → core/math/physics/kinematics}/computeInterceptPoint.d.ts +0 -0
@@ -1,1338 +1,1482 @@
1
- import { v3_quat3_apply } from "../../../core/geom/vec3/v3_quat3_apply.js";
2
- import { v3_quat3_apply_inverse } from "../../../core/geom/vec3/v3_quat3_apply_inverse.js";
3
- import { body_id_index } from "../body/BodyStorage.js";
4
- import {
5
- SBS_AV_X,
6
- SBS_AV_Y,
7
- SBS_AV_Z,
8
- SBS_INV_I_X,
9
- SBS_INV_I_Y,
10
- SBS_INV_I_Z,
11
- SBS_INV_MASS,
12
- SBS_LV_X,
13
- SBS_LV_Y,
14
- SBS_LV_Z,
15
- SBS_QW,
16
- SBS_QX,
17
- SBS_QY,
18
- SBS_QZ,
19
- SBS_STRIDE,
20
- } from "../body/SolverBodyState.js";
21
- import { CONTACT_STRIDE } from "../contact/ManifoldStore.js";
22
- import { is_sensor } from "../ecs/is_sensor.js";
23
- import { world_inverse_inertia_apply_raw } from "../inertia/world_inverse_inertia.js";
24
- import { redetect_pair_geometry } from "../narrowphase/narrowphase_step.js";
25
- import { friction_cone_clamp } from "./friction_cone.js";
26
-
27
- /**
28
- * # TGS split-impulse contact solver (staged)
29
- *
30
- * Temporal Gauss-Seidel with Catto-2018 split impulse. The solver runs as a
31
- * sequence of stages driven by `PhysicsSystem.fixedUpdate`, which owns the
32
- * substep loop (it has to velocity/position integration spans every awake
33
- * body, not just contacts):
34
- *
35
- * prepare_contacts(manifolds, system, h) // once per outer step
36
- * for each substep:
37
- * (system integrates gravity by h)
38
- * redetect_concave_contacts(manifolds, system) // concave: fresh narrowphase
39
- * refresh_contacts(manifolds, system) // convex: analytic re-derive
40
- * warm_start_contacts(manifolds, system) // replay impulse — per substep!
41
- * solve_velocity(manifolds, system, iters) // non-penetration + friction
42
- * solve_position(manifolds, system, pos_iters)
43
- * (system integrates position by h, folding pseudo-velocity)
44
- * apply_restitution(manifolds, system) // once, after the loop
45
- *
46
- * The three concerns are fully decoupled (Phases 1–2) and substepping
47
- * (Phase 3) re-runs only the velocity + position solve per substep —
48
- * narrowphase runs once per outer step. Each substep re-derives the
49
- * current penetration analytically from the bodies' moved poses and the
50
- * contact anchors captured at prepare time, so the position correction
51
- * adapts as the stack settles (the mechanism behind TGS stack stability)
52
- * without paying for narrowphase N times.
53
- *
54
- * Why staged module functions sharing module scratch, rather than one call:
55
- * the substep loop interleaves solver stages with whole-body integration
56
- * that lives in the system. The stages communicate through module-scoped
57
- * scratch + `g_*` state set by `prepare_contacts`; they must be called in
58
- * order, prepare first, within a single outer step. Single-threaded and
59
- * deterministic, so the shared state is safe.
60
- *
61
- * Contacts from all islands are flattened into one scratch array. Islands
62
- * are independent (they share no bodies), so a single flat Gauss-Seidel
63
- * sweep gives the same result as per-island sweeps the island partition
64
- * is still built and used by the sleep test, just not needed here.
65
- */
66
-
67
- /**
68
- * A pair is "sensor-only" when either side {@link is_sensor} (the body OR its
69
- * primary collider carries the IsSensor flag). The manifold still exists (so
70
- * Begin/Stay/End events fire from the manifold-diff pass) but no impulse
71
- * is applied.
72
- *
73
- * @param {RigidBody} rbA
74
- * @param {Collider} colA
75
- * @param {RigidBody} rbB
76
- * @param {Collider} colB
77
- * @returns {boolean}
78
- */
79
- function pair_is_sensor(rbA, colA, rbB, colB) {
80
- return is_sensor(rbA, colA) || is_sensor(rbB, colB);
81
- }
82
-
83
- /**
84
- * Velocity-iteration count per substep. With substepping the per-substep
85
- * count can be lower than a single-step PGS solver would need, because the
86
- * outer loop revisits the contact set `substeps` times.
87
- * @type {number}
88
- */
89
- const DEFAULT_VELOCITY_ITERATIONS = 10;
90
-
91
- /**
92
- * Position-iteration count per substep (split-impulse pseudo-velocity pass).
93
- * @type {number}
94
- */
95
- const DEFAULT_POSITION_ITERATIONS = 2;
96
-
97
- /**
98
- * Penetration allowed without applying position correction. Eliminates
99
- * micro-jitter at near-zero overlap.
100
- * @type {number}
101
- */
102
- const PENETRATION_SLOP = 0.005;
103
-
104
- /**
105
- * SPOOK contact stiffness `k`. Effectively infinite for rigid-body
106
- * contact: what the solver actually sees is the regularization
107
- * `eps = 4 / (h² · k · (1 + 4d))`, negligible at `k = 1e12` but in place
108
- * as a continuous compliance dial for future soft contacts. Lacoursière
109
- * 2007; same formulation as cannon-es / AgX.
110
- * @type {number}
111
- */
112
- const CONTACT_STIFFNESS = 1e12;
113
-
114
- /**
115
- * SPOOK contact relaxation `d`. Chosen so `a = 4 / (h(1 + 4d)) = 0.2 / h`,
116
- * numerically matching the prior Baumgarte β = 0.2 gain. `4/(1+4d)=0.2`
117
- * `d = 4.75`. Note `h` here is the SUBSTEP size: position correction is
118
- * applied per substep, so the gain is derived from the substep `dt`.
119
- * @type {number}
120
- */
121
- const CONTACT_RELAXATION = 4.75;
122
-
123
- /**
124
- * Maximum magnitude of the position-correction velocity bias, in m/s.
125
- * Belt-and-braces against inflated EPA depths (PLAN.md caveat) driving the
126
- * bias to tens of m/s. Removed once closed-form triangle solvers land.
127
- * @type {number}
128
- */
129
- const MAX_POSITION_BIAS = 3;
130
-
131
- /**
132
- * Velocity below which restitution is suppressed (no micro-bounce buzz on
133
- * resting stacks).
134
- * @type {number}
135
- */
136
- const RESTITUTION_VELOCITY_THRESHOLD = 1.0;
137
-
138
- /**
139
- * Per-contact pre-step scratch stride: 23 doubles.
140
- * 0..2 : localWA (A's contact witness, in A's LOCAL frame — constant)
141
- * 3..5 : localWB (B's contact witness, in B's LOCAL frame constant)
142
- * 6..8 : rA (lever from A's COM to the contact midpoint — refreshed)
143
- * 9..11: rB (lever from B's COM to the contact midpoint — refreshed)
144
- * 12..14: t1 (tangent 1, unit, world — constant)
145
- * 15..17: t2 (tangent 2, unit, world constant)
146
- * 18 : m_eff_n
147
- * 19 : m_eff_t1
148
- * 20 : m_eff_t2
149
- * 21 : rest_bias (restitution: `e · vn_approach`, 0; 0 if not bouncing)
150
- * 22 : bias_position (depth-correction bias refreshed each substep)
151
- *
152
- * `localWA` / `localWB` are the per-body contact witnesses expressed in body
153
- * local frames at prepare time. Each substep, `refresh_contacts` rotates
154
- * them back to world by the body's current pose to recover the moved contact
155
- * points, re-derives the current penetration depth (anchored on the trusted
156
- * prepare-time depth via a delta, so it's sign-robust), and rebuilds the
157
- * impulse lever arms `rA` / `rB` and the position bias.
158
- * @type {number}
159
- */
160
- const PRE_STRIDE = 23;
161
-
162
- /** Per-contact index list 4 uint32 per contact: slot, idx, idxA, idxB. */
163
- const INDEX_STRIDE = 4;
164
-
165
- /**
166
- * Per-body pseudo-velocity stride: 3 linear + 3 angular doubles. Owned by
167
- * PhysicsSystem (`system.__pseudo_velocity`); zeroed each substep before
168
- * the position pass and folded into the pose by `integrate_position`.
169
- * @type {number}
170
- */
171
- const PSEUDO_STRIDE = 6;
172
-
173
- let scratch_pre = new Float64Array(64 * PRE_STRIDE);
174
- let scratch_idx = new Uint32Array(64 * INDEX_STRIDE);
175
- let scratch_mu = new Float64Array(64);
176
- let scratch_pos_jn = new Float64Array(64);
177
-
178
- /**
179
- * Per-contact maximum normal impulse seen across the whole outer step's
180
- * velocity solving (all substeps, all iterations). Reset in
181
- * {@link prepare_contacts}, updated in {@link solve_velocity}, read by
182
- * {@link apply_restitution}.
183
- *
184
- * Restitution must fire whenever a contact *was* compressive at some point
185
- * in the step, even if its accumulated impulse later relaxes back to ~0 — a
186
- * transient collision (ball bouncing, head-on hit) under per-substep
187
- * warm-start ends the loop with `j_n ≈ 0` because there's no sustained load
188
- * to hold the impulse up. Gating on this running max (Box2D-v3
189
- * `maxNormalImpulse`) rather than the end-of-loop `j_n` is what makes
190
- * bounces fire.
191
- * @type {Float64Array}
192
- */
193
- let scratch_max_jn = new Float64Array(64);
194
-
195
- /**
196
- * Per-contact flag: 1 if the contact's pair involves a concave body, else 0.
197
- * Concave contacts take the per-substep re-detection path
198
- * ({@link redetect_concave_contacts})their feature genuinely changes as
199
- * the body rocks, so the analytic refresh that freezes it would pump energy.
200
- * Convex contacts (flag 0) take the cheap analytic {@link refresh_contacts}.
201
- * @type {Uint8Array}
202
- */
203
- let scratch_concave = new Uint8Array(64);
204
-
205
- /**
206
- * Distinct concave-involved manifold slots touched this step, with the body
207
- * indices needed to fetch their collider lists for re-detection. Parallel
208
- * arrays, `g_concave_slot_count` valid entries, filled by
209
- * {@link prepare_contacts}.
210
- */
211
- let concave_slot = new Uint32Array(32);
212
- let concave_slot_idxA = new Uint32Array(32);
213
- let concave_slot_idxB = new Uint32Array(32);
214
- let g_concave_slot_count = 0;
215
-
216
- /**
217
- * Shared cross-stage state, set by {@link prepare_contacts} and read by the
218
- * per-substep stages within the same outer step. Single-threaded, so plain
219
- * module variables are safe.
220
- */
221
- let g_contact_count = 0;
222
- let g_spook_a = 0;
223
- let g_spook_eps = 0;
224
-
225
- /** Scratch for re-deriving contact world points in {@link refresh_contacts}. */
226
- const scratch_cp = new Float64Array(6);
227
-
228
- function ensure_capacity(n) {
229
- if (scratch_pre.length < n * PRE_STRIDE) {
230
- scratch_pre = new Float64Array(n * PRE_STRIDE * 2);
231
- }
232
- if (scratch_idx.length < n * INDEX_STRIDE) {
233
- scratch_idx = new Uint32Array(n * INDEX_STRIDE * 2);
234
- }
235
- if (scratch_mu.length < n) {
236
- scratch_mu = new Float64Array(n * 2);
237
- }
238
- if (scratch_pos_jn.length < n) {
239
- scratch_pos_jn = new Float64Array(n * 2);
240
- }
241
- if (scratch_max_jn.length < n) {
242
- scratch_max_jn = new Float64Array(n * 2);
243
- }
244
- if (scratch_concave.length < n) {
245
- scratch_concave = new Uint8Array(n * 2);
246
- }
247
- }
248
-
249
- function ensure_concave_slot_capacity(n) {
250
- if (concave_slot.length < n) {
251
- concave_slot = new Uint32Array(n * 2);
252
- concave_slot_idxA = new Uint32Array(n * 2);
253
- concave_slot_idxB = new Uint32Array(n * 2);
254
- }
255
- }
256
-
257
- /**
258
- * Build an orthonormal tangent basis perpendicular to a unit normal.
259
- *
260
- * @param {Float64Array} out 6 floats: t1.xyz then t2.xyz
261
- * @param {number} off
262
- * @param {number} nx
263
- * @param {number} ny
264
- * @param {number} nz
265
- */
266
- function build_tangents(out, off, nx, ny, nz) {
267
- const ax = nx < 0 ? -nx : nx;
268
- const ay = ny < 0 ? -ny : ny;
269
- const az = nz < 0 ? -nz : nz;
270
-
271
- let rx, ry, rz;
272
-
273
- if (ax <= ay && ax <= az) {
274
- rx = 1;
275
- ry = 0;
276
- rz = 0;
277
- } else if (ay <= az) {
278
- rx = 0;
279
- ry = 1;
280
- rz = 0;
281
- } else {
282
- rx = 0;
283
- ry = 0;
284
- rz = 1;
285
- }
286
-
287
- let t1x = ny * rz - nz * ry;
288
- let t1y = nz * rx - nx * rz;
289
- let t1z = nx * ry - ny * rx;
290
-
291
- const inv = 1 / Math.sqrt(t1x * t1x + t1y * t1y + t1z * t1z);
292
-
293
- t1x *= inv;
294
- t1y *= inv;
295
- t1z *= inv;
296
-
297
- const t2x = ny * t1z - nz * t1y;
298
- const t2y = nz * t1x - nx * t1z;
299
- const t2z = nx * t1y - ny * t1x;
300
-
301
- out[off] = t1x;
302
- out[off + 1] = t1y;
303
- out[off + 2] = t1z;
304
-
305
- out[off + 3] = t2x;
306
- out[off + 4] = t2y;
307
- out[off + 5] = t2z;
308
- }
309
-
310
- // Friction / restitution are no longer combined here: the narrowphase combines
311
- // the specific source-collider pair's coefficients per contact (so compound
312
- // bodies with mixed-material colliders are honoured) and stores the result in
313
- // the manifold (CONTACT_STRIDE offsets 14 / 15). prepare_contacts reads them
314
- // per contact. See engine/physics/contact/combine_material.js.
315
-
316
- const scratch_clamp = new Float64Array(2);
317
- const scratch_inertia_a = new Float64Array(3);
318
- const scratch_inertia_b = new Float64Array(3);
319
-
320
- /**
321
- * Apply an impulse `P` at body-relative offset `r` to persistent velocity
322
- * (Δv = P/m, Δω = I⁻¹·(r × P)), reading and writing the body's
323
- * {@link SolverBodyState} span instead of its `RigidBody` / `Transform`
324
- * objects — flat typed-array access, no dereference, on the hot path.
325
- *
326
- * @param {Float64Array} ss solver-body-state data array
327
- * @param {number} base `body_index * SBS_STRIDE`
328
- * @param {number} invM
329
- * @param {number} rx
330
- * @param {number} ry
331
- * @param {number} rz
332
- * @param {number} Px
333
- * @param {number} Py
334
- * @param {number} Pz
335
- * @param {number} sign +1 for body A, -1 for body B
336
- * @param {Float64Array} scratch_inertia
337
- */
338
- function apply_impulse_to_body(
339
- ss, base, invM,
340
- rx, ry, rz,
341
- Px, Py, Pz,
342
- sign,
343
- scratch_inertia
344
- ) {
345
-
346
- if (invM === 0) {
347
- return;
348
- }
349
-
350
- const sPx = sign * Px;
351
- const sPy = sign * Py;
352
- const sPz = sign * Pz;
353
-
354
- ss[base + SBS_LV_X] += sPx * invM;
355
- ss[base + SBS_LV_Y] += sPy * invM;
356
- ss[base + SBS_LV_Z] += sPz * invM;
357
-
358
- const tx = ry * sPz - rz * sPy;
359
- const ty = rz * sPx - rx * sPz;
360
- const tz = rx * sPy - ry * sPx;
361
-
362
- world_inverse_inertia_apply_raw(
363
- scratch_inertia, 0,
364
- ss[base + SBS_INV_I_X], ss[base + SBS_INV_I_Y], ss[base + SBS_INV_I_Z],
365
- ss[base + SBS_QX], ss[base + SBS_QY], ss[base + SBS_QZ], ss[base + SBS_QW],
366
- tx, ty, tz
367
- );
368
-
369
- ss[base + SBS_AV_X] += scratch_inertia[0];
370
- ss[base + SBS_AV_Y] += scratch_inertia[1];
371
- ss[base + SBS_AV_Z] += scratch_inertia[2];
372
- }
373
-
374
- /**
375
- * Apply a position-pass impulse `P` at body-relative offset `r` to the
376
- * body's pseudo-velocity (linear + angular) in the `pseudo_velocity` flat
377
- * buffer. Mirrors {@link apply_impulse_to_body} but the result is consumed
378
- * by `integrate_position` the same substep and never persists. Inverse
379
- * inertia / orientation are read from the body's {@link SolverBodyState} span.
380
- *
381
- * @param {Float64Array} pseudo_velocity stride = {@link PSEUDO_STRIDE}
382
- * @param {number} pbase offset = `body_index * PSEUDO_STRIDE`
383
- * @param {Float64Array} ss solver-body-state data array
384
- * @param {number} sbase `body_index * SBS_STRIDE`
385
- * @param {number} invM
386
- * @param {number} rx
387
- * @param {number} ry
388
- * @param {number} rz
389
- * @param {number} Px
390
- * @param {number} Py
391
- * @param {number} Pz
392
- * @param {number} sign +1 for body A, -1 for body B
393
- * @param {Float64Array} scratch_inertia
394
- */
395
- function apply_impulse_to_pseudo(
396
- pseudo_velocity, pbase,
397
- ss, sbase, invM,
398
- rx, ry, rz,
399
- Px, Py, Pz,
400
- sign,
401
- scratch_inertia
402
- ) {
403
- if (invM === 0) {
404
- return;
405
- }
406
-
407
- const sPx = sign * Px;
408
- const sPy = sign * Py;
409
- const sPz = sign * Pz;
410
-
411
- pseudo_velocity[pbase] += sPx * invM;
412
- pseudo_velocity[pbase + 1] += sPy * invM;
413
- pseudo_velocity[pbase + 2] += sPz * invM;
414
-
415
- const tx = ry * sPz - rz * sPy;
416
- const ty = rz * sPx - rx * sPz;
417
- const tz = rx * sPy - ry * sPx;
418
-
419
- world_inverse_inertia_apply_raw(
420
- scratch_inertia, 0,
421
- ss[sbase + SBS_INV_I_X], ss[sbase + SBS_INV_I_Y], ss[sbase + SBS_INV_I_Z],
422
- ss[sbase + SBS_QX], ss[sbase + SBS_QY], ss[sbase + SBS_QZ], ss[sbase + SBS_QW],
423
- tx, ty, tz
424
- );
425
-
426
- pseudo_velocity[pbase + 3] += scratch_inertia[0];
427
- pseudo_velocity[pbase + 4] += scratch_inertia[1];
428
- pseudo_velocity[pbase + 5] += scratch_inertia[2];
429
- }
430
-
431
- /**
432
- * Quadratic-form contribution of one body to the constraint effective mass
433
- * along a unit axis: `(r × axis)^T · I⁻¹_world · (r × axis)`. Reads the body's
434
- * inverse-inertia diagonal + orientation from its {@link SolverBodyState} span.
435
- * Non-dynamic bodies carry a zero inverse-inertia diagonal there, so the
436
- * zero-guard returns 0 for them — matching the object path's `kind` guard.
437
- *
438
- * @param {Float64Array} ss solver-body-state data array
439
- * @param {number} base `body_index * SBS_STRIDE`
440
- * @param {number} rx
441
- * @param {number} ry
442
- * @param {number} rz
443
- * @param {number} ax
444
- * @param {number} ay
445
- * @param {number} az
446
- * @param {Float64Array} scratch_inertia
447
- * @returns {number}
448
- */
449
- function angular_jacobian_contribution(
450
- ss, base,
451
- rx, ry, rz,
452
- ax, ay, az,
453
- scratch_inertia
454
- ) {
455
- const iix = ss[base + SBS_INV_I_X];
456
- const iiy = ss[base + SBS_INV_I_Y];
457
- const iiz = ss[base + SBS_INV_I_Z];
458
-
459
- if (iix === 0 && iiy === 0 && iiz === 0) {
460
- return 0;
461
- }
462
-
463
- const rxax = ry * az - rz * ay;
464
- const rxay = rz * ax - rx * az;
465
- const rxaz = rx * ay - ry * ax;
466
-
467
- world_inverse_inertia_apply_raw(
468
- scratch_inertia, 0,
469
- iix, iiy, iiz,
470
- ss[base + SBS_QX], ss[base + SBS_QY], ss[base + SBS_QZ], ss[base + SBS_QW],
471
- rxax, rxay, rxaz
472
- );
473
-
474
- return rxax * scratch_inertia[0]
475
- + rxay * scratch_inertia[1]
476
- + rxaz * scratch_inertia[2];
477
- }
478
-
479
- /**
480
- * Flat count of all touched contacts across every island, plus the slot
481
- * list to iterate. Islands are concatenated densely in `contact_data`;
482
- * `contact_offsets[island_count]` is the end of the last island.
483
- *
484
- * @param {PhysicsSystem} system
485
- * @returns {{slot_list: Uint32Array, total_slots: number}|null}
486
- */
487
- function island_slot_range(system) {
488
- const islands = system.islands;
489
-
490
- if (islands === undefined || islands === null) {
491
- return null;
492
- }
493
-
494
- const island_count = islands.island_count;
495
-
496
- if (island_count === 0) {
497
- return null;
498
- }
499
-
500
- return {
501
- slot_list: islands.contact_data,
502
- total_slots: islands.contact_offsets[island_count],
503
- };
504
- }
505
-
506
- /**
507
- * Stage 1 — prepare the contact constraints for an outer step.
508
- *
509
- * Packs every touched, non-sensor contact into the flat scratch arrays:
510
- * local-frame witness anchors, tangent basis, effective masses, friction,
511
- * the restitution approach velocity, and the warm-start replay (applied
512
- * once here, not per substep). Computes the SPOOK gains from the SUBSTEP
513
- * size `dt_sub` because the position correction runs once per substep.
514
- *
515
- * @param {ManifoldStore} manifolds
516
- * @param {PhysicsSystem} system
517
- * @param {number} dt_sub substep size `dt / substeps`
518
- * @returns {number} number of contacts prepared (also stored module-side)
519
- */
520
- export function prepare_contacts(manifolds, system, dt_sub) {
521
- g_contact_count = 0;
522
- // Reset BEFORE the early returns below: on a step with awake bodies but zero
523
- // contacts/islands, an early return must not leave a stale count from a prior
524
- // step that redetect_concave_contacts would then act on against evicted slots.
525
- g_concave_slot_count = 0;
526
-
527
- if (dt_sub <= 0) {
528
- return 0;
529
- }
530
-
531
- const range = island_slot_range(system);
532
-
533
- if (range === null) {
534
- return 0;
535
- }
536
-
537
- const slot_list = range.slot_list;
538
- const total_slots = range.total_slots;
539
-
540
- let contact_total = 0;
541
-
542
- for (let i = 0; i < total_slots; i++) {
543
- contact_total += manifolds.contact_count(slot_list[i]);
544
- }
545
-
546
- if (contact_total === 0) {
547
- return 0;
548
- }
549
-
550
- ensure_capacity(contact_total);
551
-
552
- const denom = 1 + 4 * CONTACT_RELAXATION;
553
- g_spook_a = 4 / (dt_sub * denom);
554
- g_spook_eps = 4 / (dt_sub * dt_sub * CONTACT_STIFFNESS * denom);
555
-
556
- const data = manifolds.data_buffer;
557
- const pre = scratch_pre;
558
- const idx = scratch_idx;
559
- const mus = scratch_mu;
560
- const pos_jn = scratch_pos_jn;
561
- const ss = system.__solver_state.data;
562
-
563
- let c = 0;
564
- for (let i = 0; i < total_slots; i++) {
565
- const slot = slot_list[i];
566
-
567
- const idxA = body_id_index(manifolds.bodyA(slot));
568
- const idxB = body_id_index(manifolds.bodyB(slot));
569
-
570
- const baseA = idxA * SBS_STRIDE;
571
- const baseB = idxB * SBS_STRIDE;
572
-
573
- const rbA = system.__bodies[idxA];
574
- const rbB = system.__bodies[idxB];
575
-
576
- const trA = system.__transforms[idxA];
577
- const trB = system.__transforms[idxB];
578
-
579
- const colA = system.__primary_collider(idxA);
580
- const colB = system.__primary_collider(idxB);
581
-
582
- if (colA === null || colB === null) {
583
- continue;
584
- }
585
-
586
- if (pair_is_sensor(rbA, colA, rbB, colB)) {
587
- continue;
588
- }
589
-
590
- // A pair is "concave" when either side's shape is non-convex. Those
591
- // contacts take the per-substep re-detection path (the contact
592
- // feature moves as the body rocks); convex pairs keep the cheap
593
- // analytic refresh. Recorded once per slot for redetect_concave_contacts.
594
- const slot_concave = (colA.shape.is_convex === false) || (colB.shape.is_convex === false);
595
-
596
- if (slot_concave) {
597
- ensure_concave_slot_capacity(g_concave_slot_count + 1);
598
- concave_slot[g_concave_slot_count] = slot;
599
- concave_slot_idxA[g_concave_slot_count] = idxA;
600
- concave_slot_idxB[g_concave_slot_count] = idxB;
601
- g_concave_slot_count++;
602
- }
603
-
604
- const invMA = ss[baseA + SBS_INV_MASS];
605
- const invMB = ss[baseB + SBS_INV_MASS];
606
-
607
- const cc = manifolds.contact_count(slot);
608
- const slot_off = manifolds.slot_data_offset(slot);
609
-
610
- const pAx = trA.position.x, pAy = trA.position.y, pAz = trA.position.z;
611
- const pBx = trB.position.x, pBy = trB.position.y, pBz = trB.position.z;
612
- const qA = trA.rotation, qB = trB.rotation;
613
-
614
- for (let k = 0; k < cc; k++) {
615
- const off = slot_off + k * CONTACT_STRIDE;
616
-
617
- // Per-contact combined materials (stamped by the narrowphase from
618
- // this contact's specific source-collider pair).
619
- const friction_combined = data[off + 14];
620
- const restitution_combined = data[off + 15];
621
-
622
- const wax = data[off], way = data[off + 1], waz = data[off + 2];
623
- const wbx = data[off + 3], wby = data[off + 4], wbz = data[off + 5];
624
- const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
625
-
626
- const px = (wax + wbx) * 0.5;
627
- const py = (way + wby) * 0.5;
628
- const pz = (waz + wbz) * 0.5;
629
-
630
- const rax = px - pAx, ray = py - pAy, raz = pz - pAz;
631
- const rbx = px - pBx, rby = py - pBy, rbz = pz - pBz;
632
-
633
- const pre_off = c * PRE_STRIDE;
634
-
635
- // Per-body witness anchors in LOCAL frame (constant across the
636
- // outer step). localW = R⁻¹ · (witness − COM).
637
- v3_quat3_apply_inverse(pre, pre_off, wax - pAx, way - pAy, waz - pAz, qA[0], qA[1], qA[2], qA[3]);
638
- v3_quat3_apply_inverse(pre, pre_off + 3, wbx - pBx, wby - pBy, wbz - pBz, qB[0], qB[1], qB[2], qB[3]);
639
-
640
- // Lever arms from COM to the contact midpoint (refreshed each
641
- // substep; seeded here from prepare-time pose).
642
- pre[pre_off + 6] = rax;
643
- pre[pre_off + 7] = ray;
644
- pre[pre_off + 8] = raz;
645
- pre[pre_off + 9] = rbx;
646
- pre[pre_off + 10] = rby;
647
- pre[pre_off + 11] = rbz;
648
-
649
- build_tangents(pre, pre_off + 12, nx, ny, nz);
650
- const t1x = pre[pre_off + 12], t1y = pre[pre_off + 13], t1z = pre[pre_off + 14];
651
- const t2x = pre[pre_off + 15], t2y = pre[pre_off + 16], t2z = pre[pre_off + 17];
652
-
653
- const k_n = invMA + invMB
654
- + angular_jacobian_contribution(ss, baseA, rax, ray, raz, nx, ny, nz, scratch_inertia_a)
655
- + angular_jacobian_contribution(ss, baseB, rbx, rby, rbz, nx, ny, nz, scratch_inertia_b);
656
- const k_t1 = invMA + invMB
657
- + angular_jacobian_contribution(ss, baseA, rax, ray, raz, t1x, t1y, t1z, scratch_inertia_a)
658
- + angular_jacobian_contribution(ss, baseB, rbx, rby, rbz, t1x, t1y, t1z, scratch_inertia_b);
659
- const k_t2 = invMA + invMB
660
- + angular_jacobian_contribution(ss, baseA, rax, ray, raz, t2x, t2y, t2z, scratch_inertia_a)
661
- + angular_jacobian_contribution(ss, baseB, rbx, rby, rbz, t2x, t2y, t2z, scratch_inertia_b);
662
-
663
- const k_n_eff = k_n + g_spook_eps;
664
- pre[pre_off + 18] = k_n_eff > 0 ? 1 / k_n_eff : 0;
665
- pre[pre_off + 19] = k_t1 > 0 ? 1 / k_t1 : 0;
666
- pre[pre_off + 20] = k_t2 > 0 ? 1 / k_t2 : 0;
667
-
668
- // Restitution approach velocity (captured once, this is the
669
- // closing speed entering the step). n is B A; vn < 0 closing.
670
- const vAx_at = ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * raz - ss[baseA + SBS_AV_Z] * ray;
671
- const vAy_at = ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rax - ss[baseA + SBS_AV_X] * raz;
672
- const vAz_at = ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * ray - ss[baseA + SBS_AV_Y] * rax;
673
-
674
- const vBx_at = ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rbz - ss[baseB + SBS_AV_Z] * rby;
675
- const vBy_at = ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rbx - ss[baseB + SBS_AV_X] * rbz;
676
- const vBz_at = ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rby - ss[baseB + SBS_AV_Y] * rbx;
677
-
678
- const vn_pre = (vAx_at - vBx_at) * nx + (vAy_at - vBy_at) * ny + (vAz_at - vBz_at) * nz;
679
-
680
- let rest_bias = 0;
681
- if (vn_pre < -RESTITUTION_VELOCITY_THRESHOLD) {
682
- rest_bias = restitution_combined * vn_pre;
683
- }
684
- pre[pre_off + 21] = rest_bias;
685
- pre[pre_off + 22] = 0; // bias_position filled by refresh_contacts
686
-
687
- mus[c] = friction_combined;
688
- pos_jn[c] = 0;
689
- scratch_max_jn[c] = 0;
690
- scratch_concave[c] = slot_concave ? 1 : 0;
691
-
692
- idx[c * INDEX_STRIDE] = slot;
693
- idx[c * INDEX_STRIDE + 1] = k;
694
- idx[c * INDEX_STRIDE + 2] = idxA;
695
- idx[c * INDEX_STRIDE + 3] = idxB;
696
-
697
- // Warm-start is NOT applied here: under TGS it must be replayed
698
- // *every substep* (see warm_start_contacts) so that, per substep,
699
- // the cached impulse balances exactly one substep of gravity.
700
- // Applying the cached impulse once against a full frame of
701
- // gravity (the non-substepped pattern) over-pushes resting
702
- // contacts and jitters / explodes deep stacks.
703
-
704
- c++;
705
- }
706
- }
707
-
708
- g_contact_count = c;
709
- return c;
710
- }
711
-
712
- /**
713
- * Stage 1b (per substep) warm-start: replay the cached accumulated
714
- * impulses `(j_n, j_t1, j_t2)` onto persistent velocity using the current
715
- * (refreshed) lever arms and tangents. Run once per substep, after
716
- * {@link refresh_contacts} and before {@link solve_velocity}.
717
- *
718
- * Per-substep warm-start is the crux of stable TGS: the stored impulse is a
719
- * per-substep quantity ( the impulse to counter one substep of gravity), so
720
- * replaying it each substep balances that substep's `integrate_velocity_gravity`
721
- * and a resting contact holds at zero velocity. `solve_velocity` then only
722
- * has to correct the residual, which converges in a few iterations even for
723
- * deep chains because each substep carries just `h` of gravity.
724
- *
725
- * @param {ManifoldStore} manifolds
726
- * @param {PhysicsSystem} system
727
- */
728
- export function warm_start_contacts(manifolds, system) {
729
- const count = g_contact_count;
730
- if (count === 0) return;
731
-
732
- const data = manifolds.data_buffer;
733
- const pre = scratch_pre;
734
- const idx = scratch_idx;
735
- const ss = system.__solver_state.data;
736
-
737
- for (let ci = 0; ci < count; ci++) {
738
- const slot = idx[ci * INDEX_STRIDE];
739
- const cidx = idx[ci * INDEX_STRIDE + 1];
740
-
741
- const baseA = idx[ci * INDEX_STRIDE + 2] * SBS_STRIDE;
742
- const baseB = idx[ci * INDEX_STRIDE + 3] * SBS_STRIDE;
743
-
744
- const invMA = ss[baseA + SBS_INV_MASS];
745
- const invMB = ss[baseB + SBS_INV_MASS];
746
-
747
- const pre_off = ci * PRE_STRIDE;
748
-
749
- const rax = pre[pre_off + 6], ray = pre[pre_off + 7], raz = pre[pre_off + 8];
750
- const rbx = pre[pre_off + 9], rby = pre[pre_off + 10], rbz = pre[pre_off + 11];
751
-
752
- const t1x = pre[pre_off + 12], t1y = pre[pre_off + 13], t1z = pre[pre_off + 14];
753
- const t2x = pre[pre_off + 15], t2y = pre[pre_off + 16], t2z = pre[pre_off + 17];
754
-
755
- const slot_off = manifolds.slot_data_offset(slot);
756
- const off = slot_off + cidx * CONTACT_STRIDE;
757
- const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
758
-
759
- const j_n = data[off + 10];
760
- const j_t1 = data[off + 11];
761
- const j_t2 = data[off + 12];
762
-
763
- const Px = nx * j_n + t1x * j_t1 + t2x * j_t2;
764
- const Py = ny * j_n + t1y * j_t1 + t2y * j_t2;
765
- const Pz = nz * j_n + t1z * j_t1 + t2z * j_t2;
766
-
767
- apply_impulse_to_body(ss, baseA, invMA, rax, ray, raz, Px, Py, Pz, +1, scratch_inertia_a);
768
- apply_impulse_to_body(ss, baseB, invMB, rbx, rby, rbz, Px, Py, Pz, -1, scratch_inertia_b);
769
- }
770
- }
771
-
772
- /**
773
- * Stage 2 (per substep) — re-derive each contact's geometry from the
774
- * bodies' current poses and the local witness anchors captured at prepare.
775
- *
776
- * For each contact:
777
- * - rotate the stored local witnesses back to world by the current pose to
778
- * get the moved contact points `cpA`, `cpB`;
779
- * - current penetration `depth_now = depth0 − Δseparation`, where
780
- * `Δseparation = (cpA − wA − (cpB − wB)) · n` is the change since prepare.
781
- * Anchoring on the trusted prepare-time depth makes the sign convention
782
- * irrelevant only the analytic delta uses the anchors;
783
- * - rebuild the impulse levers `rA`/`rB` from the moved midpoint;
784
- * - recompute the position-correction bias from `depth_now`.
785
- *
786
- * The contact normal and tangents are held fixed for the outer step (valid
787
- * for the small per-step rotation), so they are not recomputed here.
788
- *
789
- * @param {ManifoldStore} manifolds
790
- * @param {Transform[]} transforms
791
- */
792
- export function refresh_contacts(manifolds, transforms) {
793
- const count = g_contact_count;
794
-
795
- if (count === 0) {
796
- return;
797
- }
798
-
799
- const data = manifolds.data_buffer;
800
- const pre = scratch_pre;
801
- const idx = scratch_idx;
802
- const cp = scratch_cp;
803
- const spook_a = g_spook_a;
804
-
805
- for (let ci = 0; ci < count; ci++) {
806
- // Concave contacts are refreshed by redetect_concave_contacts (fresh
807
- // narrowphase geometry each substep), not by the analytic rotation of
808
- // frozen anchors their feature moves as the body rocks.
809
- if (scratch_concave[ci] === 1) {
810
- continue;
811
- }
812
-
813
- const slot = idx[ci * INDEX_STRIDE];
814
- const cidx = idx[ci * INDEX_STRIDE + 1];
815
- const idxA = idx[ci * INDEX_STRIDE + 2];
816
- const idxB = idx[ci * INDEX_STRIDE + 3];
817
-
818
- const trA = transforms[idxA];
819
- const trB = transforms[idxB];
820
-
821
- const slot_off = manifolds.slot_data_offset(slot);
822
- const off = slot_off + cidx * CONTACT_STRIDE;
823
-
824
- const wax = data[off], way = data[off + 1], waz = data[off + 2];
825
- const wbx = data[off + 3], wby = data[off + 4], wbz = data[off + 5];
826
- const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
827
-
828
- const depth0 = data[off + 9];
829
-
830
- const pre_off = ci * PRE_STRIDE;
831
-
832
- const pAx = trA.position[0];
833
- const pAy = trA.position[1];
834
- const pAz = trA.position[2];
835
-
836
- const pBx = trB.position[0];
837
- const pBy = trB.position[1];
838
- const pBz = trB.position[2];
839
-
840
- const qA = trA.rotation;
841
- const qB = trB.rotation;
842
-
843
- // Current world contact points: COM + R · localWitness.
844
- v3_quat3_apply(cp, 0, pre[pre_off], pre[pre_off + 1], pre[pre_off + 2], qA[0], qA[1], qA[2], qA[3]);
845
- v3_quat3_apply(cp, 3, pre[pre_off + 3], pre[pre_off + 4], pre[pre_off + 5], qB[0], qB[1], qB[2], qB[3]);
846
-
847
- const cpAx = pAx + cp[0], cpAy = pAy + cp[1], cpAz = pAz + cp[2];
848
- const cpBx = pBx + cp[3], cpBy = pBy + cp[4], cpBz = pBz + cp[5];
849
-
850
- // Penetration re-derived as a delta from the trusted prepare depth.
851
- // Δsep = ((cpA wA) (cpB − wB)) · n.
852
- const dsep = ((cpAx - wax) - (cpBx - wbx)) * nx
853
- + ((cpAy - way) - (cpBy - wby)) * ny
854
- + ((cpAz - waz) - (cpBz - wbz)) * nz;
855
-
856
- const depth_now = depth0 - dsep;
857
-
858
- // Impulse levers from each COM to the current contact midpoint.
859
- const mx = (cpAx + cpBx) * 0.5;
860
- const my = (cpAy + cpBy) * 0.5;
861
- const mz = (cpAz + cpBz) * 0.5;
862
-
863
- pre[pre_off + 6] = mx - pAx;
864
- pre[pre_off + 7] = my - pAy;
865
- pre[pre_off + 8] = mz - pAz;
866
- pre[pre_off + 9] = mx - pBx;
867
- pre[pre_off + 10] = my - pBy;
868
- pre[pre_off + 11] = mz - pBz;
869
-
870
- let bias_position = 0;
871
-
872
- if (depth_now > PENETRATION_SLOP) {
873
- bias_position = -spook_a * (depth_now - PENETRATION_SLOP);
874
-
875
- if (bias_position < -MAX_POSITION_BIAS) {
876
- bias_position = -MAX_POSITION_BIAS;
877
- }
878
- }
879
-
880
- pre[pre_off + 22] = bias_position;
881
- }
882
- }
883
-
884
- /**
885
- * Stage 2b (per substep) — the concave counterpart of {@link refresh_contacts}.
886
- *
887
- * For each concave-involved slot, re-runs the narrowphase geometry at the
888
- * current substep pose ({@link redetect_pair_geometry}, which rewrites the
889
- * manifold's witness / normal / depth in place), then re-derives the solver
890
- * scratch (lever arms, tangent basis, effective masses, position bias) for
891
- * that slot's contacts from the fresh geometry. This is the whole point of
892
- * the concave path: a body rocking on a mesh changes which triangle (and
893
- * normal) it rests on within a single outer step, and freezing that as the
894
- * convex analytic refresh does — pumps energy in. Re-detecting gives a
895
- * correct per-substep normal so the body settles.
896
- *
897
- * Cost is ~one narrowphase dispatch per concave slot per substep — acceptable
898
- * because concave-involved pairs are rare (the common concave case, a convex
899
- * body on static terrain, is convex on the moving side and never lands here).
900
- * The contact count is held fixed (redetect updates geometry only), so the
901
- * scratch stays aligned with prepare.
902
- *
903
- * Must run before {@link refresh_contacts} / {@link warm_start_contacts} each
904
- * substep. `rest_bias` (captured at prepare) and the `scratch_max_jn`
905
- * restitution gate are preserved.
906
- *
907
- * @param {ManifoldStore} manifolds
908
- * @param {PhysicsSystem} system reads `__body_collider_lists`, `__bodies`,
909
- * `__transforms`.
910
- */
911
- export function redetect_concave_contacts(manifolds, system) {
912
- const ns = g_concave_slot_count;
913
-
914
- if (ns === 0) {
915
- return;
916
- }
917
-
918
- const lists = system.__body_collider_lists;
919
-
920
- // 1. Re-detect fresh geometry into the manifold for each concave slot.
921
- for (let s = 0; s < ns; s++) {
922
- redetect_pair_geometry(manifolds, concave_slot[s], lists[concave_slot_idxA[s]], lists[concave_slot_idxB[s]]);
923
- }
924
-
925
- // 2. Re-derive the solver scratch for every concave contact from the
926
- // fresh manifold geometry (lever arms, tangents, effective masses,
927
- // position bias). Mirrors prepare's per-contact setup but reads the
928
- // just-updated witness / normal / depth instead of the prepare-time
929
- // values; no local-frame anchors are needed since we re-detect rather
930
- // than rotate frozen anchors.
931
- const count = g_contact_count;
932
- const data = manifolds.data_buffer;
933
- const pre = scratch_pre;
934
- const idx = scratch_idx;
935
- const spook_a = g_spook_a;
936
- const spook_eps = g_spook_eps;
937
- const ss = system.__solver_state.data;
938
-
939
- for (let ci = 0; ci < count; ci++) {
940
- if (scratch_concave[ci] === 0) {
941
- continue;
942
- }
943
-
944
- const slot = idx[ci * INDEX_STRIDE];
945
- const cidx = idx[ci * INDEX_STRIDE + 1];
946
- const idxA = idx[ci * INDEX_STRIDE + 2];
947
- const idxB = idx[ci * INDEX_STRIDE + 3];
948
-
949
- const baseA = idxA * SBS_STRIDE;
950
- const baseB = idxB * SBS_STRIDE;
951
-
952
- const trA = system.__transforms[idxA];
953
- const trB = system.__transforms[idxB];
954
-
955
- const invMA = ss[baseA + SBS_INV_MASS];
956
- const invMB = ss[baseB + SBS_INV_MASS];
957
-
958
- const slot_off = manifolds.slot_data_offset(slot);
959
- const off = slot_off + cidx * CONTACT_STRIDE;
960
-
961
- const wax = data[off], way = data[off + 1], waz = data[off + 2];
962
- const wbx = data[off + 3], wby = data[off + 4], wbz = data[off + 5];
963
- const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
964
- const depth = data[off + 9];
965
-
966
- const px = (wax + wbx) * 0.5;
967
- const py = (way + wby) * 0.5;
968
- const pz = (waz + wbz) * 0.5;
969
-
970
- const rax = px - trA.position.x, ray = py - trA.position.y, raz = pz - trA.position.z;
971
- const rbx = px - trB.position.x, rby = py - trB.position.y, rbz = pz - trB.position.z;
972
-
973
- const pre_off = ci * PRE_STRIDE;
974
- pre[pre_off + 6] = rax;
975
- pre[pre_off + 7] = ray;
976
- pre[pre_off + 8] = raz;
977
- pre[pre_off + 9] = rbx;
978
- pre[pre_off + 10] = rby;
979
- pre[pre_off + 11] = rbz;
980
-
981
- build_tangents(pre, pre_off + 12, nx, ny, nz);
982
- const t1x = pre[pre_off + 12], t1y = pre[pre_off + 13], t1z = pre[pre_off + 14];
983
- const t2x = pre[pre_off + 15], t2y = pre[pre_off + 16], t2z = pre[pre_off + 17];
984
-
985
- const k_n = invMA + invMB
986
- + angular_jacobian_contribution(ss, baseA, rax, ray, raz, nx, ny, nz, scratch_inertia_a)
987
- + angular_jacobian_contribution(ss, baseB, rbx, rby, rbz, nx, ny, nz, scratch_inertia_b);
988
- const k_t1 = invMA + invMB
989
- + angular_jacobian_contribution(ss, baseA, rax, ray, raz, t1x, t1y, t1z, scratch_inertia_a)
990
- + angular_jacobian_contribution(ss, baseB, rbx, rby, rbz, t1x, t1y, t1z, scratch_inertia_b);
991
- const k_t2 = invMA + invMB
992
- + angular_jacobian_contribution(ss, baseA, rax, ray, raz, t2x, t2y, t2z, scratch_inertia_a)
993
- + angular_jacobian_contribution(ss, baseB, rbx, rby, rbz, t2x, t2y, t2z, scratch_inertia_b);
994
-
995
- const k_n_eff = k_n + spook_eps;
996
- pre[pre_off + 18] = k_n_eff > 0 ? 1 / k_n_eff : 0;
997
- pre[pre_off + 19] = k_t1 > 0 ? 1 / k_t1 : 0;
998
- pre[pre_off + 20] = k_t2 > 0 ? 1 / k_t2 : 0;
999
-
1000
- let bias_position = 0;
1001
-
1002
- if (depth > PENETRATION_SLOP) {
1003
- bias_position = -spook_a * (depth - PENETRATION_SLOP);
1004
-
1005
- if (bias_position < -MAX_POSITION_BIAS) {
1006
- bias_position = -MAX_POSITION_BIAS;
1007
- }
1008
-
1009
- }
1010
-
1011
- pre[pre_off + 22] = bias_position;
1012
- }
1013
- }
1014
-
1015
- /**
1016
- * Stage 3 (per substep) — velocity iterations enforcing pure
1017
- * non-penetration (`vn → 0`) plus Coulomb-disk friction. No bias: depth
1018
- * correction is the position pass, restitution is the one-shot pass.
1019
- *
1020
- * @param {ManifoldStore} manifolds
1021
- * @param {PhysicsSystem} system
1022
- * @param {number} iters
1023
- */
1024
- export function solve_velocity(manifolds, system, iters) {
1025
- const contact_count = g_contact_count;
1026
- if (contact_count === 0) return;
1027
-
1028
- const data = manifolds.data_buffer;
1029
- const pre = scratch_pre;
1030
- const idx = scratch_idx;
1031
- const mus = scratch_mu;
1032
- const ss = system.__solver_state.data;
1033
-
1034
- for (let iter = 0; iter < iters; iter++) {
1035
- for (let ci = 0; ci < contact_count; ci++) {
1036
-
1037
- const slot = idx[ci * INDEX_STRIDE];
1038
- const cidx = idx[ci * INDEX_STRIDE + 1];
1039
-
1040
- const baseA = idx[ci * INDEX_STRIDE + 2] * SBS_STRIDE;
1041
- const baseB = idx[ci * INDEX_STRIDE + 3] * SBS_STRIDE;
1042
-
1043
- const invMA = ss[baseA + SBS_INV_MASS];
1044
- const invMB = ss[baseB + SBS_INV_MASS];
1045
-
1046
- const pre_off = ci * PRE_STRIDE;
1047
-
1048
- const rax = pre[pre_off + 6], ray = pre[pre_off + 7], raz = pre[pre_off + 8];
1049
- const rbx = pre[pre_off + 9], rby = pre[pre_off + 10], rbz = pre[pre_off + 11];
1050
-
1051
- const t1x = pre[pre_off + 12], t1y = pre[pre_off + 13], t1z = pre[pre_off + 14];
1052
- const t2x = pre[pre_off + 15], t2y = pre[pre_off + 16], t2z = pre[pre_off + 17];
1053
-
1054
- const m_eff_n = pre[pre_off + 18];
1055
- const m_eff_t1 = pre[pre_off + 19];
1056
- const m_eff_t2 = pre[pre_off + 20];
1057
-
1058
- const slot_off = manifolds.slot_data_offset(slot);
1059
- const off = slot_off + cidx * CONTACT_STRIDE;
1060
- const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
1061
-
1062
- const vAx_at = ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * raz - ss[baseA + SBS_AV_Z] * ray;
1063
- const vAy_at = ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rax - ss[baseA + SBS_AV_X] * raz;
1064
- const vAz_at = ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * ray - ss[baseA + SBS_AV_Y] * rax;
1065
-
1066
- const vBx_at = ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rbz - ss[baseB + SBS_AV_Z] * rby;
1067
- const vBy_at = ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rbx - ss[baseB + SBS_AV_X] * rbz;
1068
- const vBz_at = ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rby - ss[baseB + SBS_AV_Y] * rbx;
1069
-
1070
- const dvx = vAx_at - vBx_at;
1071
- const dvy = vAy_at - vBy_at;
1072
- const dvz = vAz_at - vBz_at;
1073
-
1074
- // --- Normal impulse (non-penetration: drive vn → 0) ---
1075
- const vn = dvx * nx + dvy * ny + dvz * nz;
1076
- const j_n_accum = data[off + 10];
1077
- const lambda_n = -m_eff_n * vn;
1078
- const sum_n = j_n_accum + lambda_n;
1079
- const new_j_n = sum_n > 0 ? sum_n : 0;
1080
- const delta_j_n = new_j_n - j_n_accum;
1081
- data[off + 10] = new_j_n;
1082
-
1083
- if (new_j_n > scratch_max_jn[ci]) {
1084
- scratch_max_jn[ci] = new_j_n;
1085
- }
1086
-
1087
- if (delta_j_n !== 0) {
1088
- const Pnx = nx * delta_j_n;
1089
- const Pny = ny * delta_j_n;
1090
- const Pnz = nz * delta_j_n;
1091
-
1092
- apply_impulse_to_body(ss, baseA, invMA, rax, ray, raz, Pnx, Pny, Pnz, +1, scratch_inertia_a);
1093
- apply_impulse_to_body(ss, baseB, invMB, rbx, rby, rbz, Pnx, Pny, Pnz, -1, scratch_inertia_b);
1094
- }
1095
-
1096
- // --- Friction impulse (Coulomb disk in tangent plane) ---
1097
- // Re-read velocity from the solver state: the normal impulse above
1098
- // just mutated it, and friction must see the corrected velocity.
1099
- const vAx2 = ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * raz - ss[baseA + SBS_AV_Z] * ray;
1100
- const vAy2 = ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rax - ss[baseA + SBS_AV_X] * raz;
1101
- const vAz2 = ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * ray - ss[baseA + SBS_AV_Y] * rax;
1102
-
1103
- const vBx2 = ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rbz - ss[baseB + SBS_AV_Z] * rby;
1104
- const vBy2 = ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rbx - ss[baseB + SBS_AV_X] * rbz;
1105
- const vBz2 = ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rby - ss[baseB + SBS_AV_Y] * rbx;
1106
-
1107
- const dvx2 = vAx2 - vBx2;
1108
- const dvy2 = vAy2 - vBy2;
1109
- const dvz2 = vAz2 - vBz2;
1110
-
1111
- const vt1 = dvx2 * t1x + dvy2 * t1y + dvz2 * t1z;
1112
- const vt2 = dvx2 * t2x + dvy2 * t2y + dvz2 * t2z;
1113
-
1114
- const j_t1_accum = data[off + 11];
1115
- const j_t2_accum = data[off + 12];
1116
-
1117
- const lambda_t1 = -m_eff_t1 * vt1;
1118
- const lambda_t2 = -m_eff_t2 * vt2;
1119
-
1120
- const want_t1 = j_t1_accum + lambda_t1;
1121
- const want_t2 = j_t2_accum + lambda_t2;
1122
-
1123
- const max_friction = mus[ci] * new_j_n;
1124
-
1125
- friction_cone_clamp(scratch_clamp, 0, want_t1, want_t2, max_friction);
1126
-
1127
- const new_j_t1 = scratch_clamp[0];
1128
- const new_j_t2 = scratch_clamp[1];
1129
-
1130
- const delta_t1 = new_j_t1 - j_t1_accum;
1131
- const delta_t2 = new_j_t2 - j_t2_accum;
1132
-
1133
- data[off + 11] = new_j_t1;
1134
- data[off + 12] = new_j_t2;
1135
-
1136
- if (delta_t1 !== 0 || delta_t2 !== 0) {
1137
-
1138
- const Ptx = t1x * delta_t1 + t2x * delta_t2;
1139
- const Pty = t1y * delta_t1 + t2y * delta_t2;
1140
- const Ptz = t1z * delta_t1 + t2z * delta_t2;
1141
-
1142
- apply_impulse_to_body(ss, baseA, invMA, rax, ray, raz, Ptx, Pty, Ptz, +1, scratch_inertia_a);
1143
- apply_impulse_to_body(ss, baseB, invMB, rbx, rby, rbz, Ptx, Pty, Ptz, -1, scratch_inertia_b);
1144
-
1145
- }
1146
- }
1147
- }
1148
- }
1149
-
1150
- /**
1151
- * Stage 4 (once, after the substep loop) — one-shot restitution
1152
- * (Box2D-v3 `b2ApplyRestitution`, Catto 2018). Drives `vn → -e · vn_approach`
1153
- * exactly once per closing contact, gated on (a) the contact having been
1154
- * closing faster than the threshold at prepare, and (b) a compressive
1155
- * normal impulse having formed during the velocity solve. The added impulse
1156
- * accumulates into the same normal-impulse slot so it composes with
1157
- * warm-start next frame.
1158
- *
1159
- * @param {ManifoldStore} manifolds
1160
- * @param {PhysicsSystem} system
1161
- */
1162
- export function apply_restitution(manifolds, system) {
1163
- const contact_count = g_contact_count;
1164
- if (contact_count === 0) return;
1165
-
1166
- const data = manifolds.data_buffer;
1167
- const pre = scratch_pre;
1168
- const idx = scratch_idx;
1169
- const ss = system.__solver_state.data;
1170
-
1171
- for (let ci = 0; ci < contact_count; ci++) {
1172
- const pre_off = ci * PRE_STRIDE;
1173
- const rest_bias = pre[pre_off + 21];
1174
- if (rest_bias === 0) continue;
1175
-
1176
- const slot = idx[ci * INDEX_STRIDE];
1177
- const cidx = idx[ci * INDEX_STRIDE + 1];
1178
- // Gate on the running max normal impulse, not the end-of-loop value:
1179
- // a transient collision relaxes back to j_n ≈ 0 under per-substep
1180
- // warm-start, but it WAS compressive, so it should still bounce.
1181
- if (scratch_max_jn[ci] <= 0) continue;
1182
-
1183
- const slot_off = manifolds.slot_data_offset(slot);
1184
- const off = slot_off + cidx * CONTACT_STRIDE;
1185
-
1186
- const j_n_accum = data[off + 10];
1187
-
1188
- const baseA = idx[ci * INDEX_STRIDE + 2] * SBS_STRIDE;
1189
- const baseB = idx[ci * INDEX_STRIDE + 3] * SBS_STRIDE;
1190
- const invMA = ss[baseA + SBS_INV_MASS];
1191
- const invMB = ss[baseB + SBS_INV_MASS];
1192
-
1193
- const rax = pre[pre_off + 6], ray = pre[pre_off + 7], raz = pre[pre_off + 8];
1194
- const rbx = pre[pre_off + 9], rby = pre[pre_off + 10], rbz = pre[pre_off + 11];
1195
- const m_eff_n = pre[pre_off + 18];
1196
- const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
1197
-
1198
- const vAx_at = ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * raz - ss[baseA + SBS_AV_Z] * ray;
1199
- const vAy_at = ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rax - ss[baseA + SBS_AV_X] * raz;
1200
- const vAz_at = ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * ray - ss[baseA + SBS_AV_Y] * rax;
1201
- const vBx_at = ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rbz - ss[baseB + SBS_AV_Z] * rby;
1202
- const vBy_at = ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rbx - ss[baseB + SBS_AV_X] * rbz;
1203
- const vBz_at = ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rby - ss[baseB + SBS_AV_Y] * rbx;
1204
- const vn = (vAx_at - vBx_at) * nx + (vAy_at - vBy_at) * ny + (vAz_at - vBz_at) * nz;
1205
-
1206
- const lambda = -m_eff_n * (vn + rest_bias);
1207
- const new_j_n = (j_n_accum + lambda) > 0 ? (j_n_accum + lambda) : 0;
1208
- const delta = new_j_n - j_n_accum;
1209
- data[off + 10] = new_j_n;
1210
-
1211
- if (delta !== 0) {
1212
- const Px = nx * delta, Py = ny * delta, Pz = nz * delta;
1213
- apply_impulse_to_body(ss, baseA, invMA, rax, ray, raz, Px, Py, Pz, +1, scratch_inertia_a);
1214
- apply_impulse_to_body(ss, baseB, invMB, rbx, rby, rbz, Px, Py, Pz, -1, scratch_inertia_b);
1215
- }
1216
- }
1217
- }
1218
-
1219
- /**
1220
- * Stage 5 (per substep) — split-impulse position correction. Normal-only
1221
- * (friction in the pseudo-velocity pass is ill-defined). Reads the per-body
1222
- * pseudo-velocity, applies a clamped normal impulse driven by the refreshed
1223
- * `bias_position`, and writes the increment back into the pseudo buffer. The
1224
- * pose integrator folds pseudo-velocity into `pos += v · dt` and discards it.
1225
- *
1226
- * The pseudo buffer must be zeroed by the caller before this stage each
1227
- * substep (it's a per-substep correction). The position accumulator
1228
- * `scratch_pos_jn` is likewise reset here per substep.
1229
- *
1230
- * @param {ManifoldStore} manifolds
1231
- * @param {PhysicsSystem} system
1232
- * @param {number} pos_iters
1233
- */
1234
- export function solve_position(manifolds, system, pos_iters) {
1235
- const contact_count = g_contact_count;
1236
- if (contact_count === 0) return;
1237
-
1238
- const data = manifolds.data_buffer;
1239
- const pre = scratch_pre;
1240
- const idx = scratch_idx;
1241
- const pos_jn = scratch_pos_jn;
1242
- const pseudoVel = system.__pseudo_velocity;
1243
- const ss = system.__solver_state.data;
1244
-
1245
- // Reset the position-impulse accumulator for this substep.
1246
- for (let ci = 0; ci < contact_count; ci++) pos_jn[ci] = 0;
1247
-
1248
- for (let iter = 0; iter < pos_iters; iter++) {
1249
- for (let ci = 0; ci < contact_count; ci++) {
1250
- const pre_off = ci * PRE_STRIDE;
1251
- const bias_p = pre[pre_off + 22];
1252
- if (bias_p === 0) continue;
1253
-
1254
- const slot = idx[ci * INDEX_STRIDE];
1255
- const cidx = idx[ci * INDEX_STRIDE + 1];
1256
- const idxA = idx[ci * INDEX_STRIDE + 2];
1257
- const idxB = idx[ci * INDEX_STRIDE + 3];
1258
- const sbaseA = idxA * SBS_STRIDE;
1259
- const sbaseB = idxB * SBS_STRIDE;
1260
- const invMA = ss[sbaseA + SBS_INV_MASS];
1261
- const invMB = ss[sbaseB + SBS_INV_MASS];
1262
-
1263
- const rax = pre[pre_off + 6], ray = pre[pre_off + 7], raz = pre[pre_off + 8];
1264
- const rbx = pre[pre_off + 9], rby = pre[pre_off + 10], rbz = pre[pre_off + 11];
1265
- const m_eff_n = pre[pre_off + 18];
1266
-
1267
- const slot_off = manifolds.slot_data_offset(slot);
1268
- const off = slot_off + cidx * CONTACT_STRIDE;
1269
- const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
1270
-
1271
- const baseA = idxA * PSEUDO_STRIDE;
1272
- const baseB = idxB * PSEUDO_STRIDE;
1273
-
1274
- const pslA_x = pseudoVel[baseA], pslA_y = pseudoVel[baseA + 1], pslA_z = pseudoVel[baseA + 2];
1275
- const psaA_x = pseudoVel[baseA + 3], psaA_y = pseudoVel[baseA + 4], psaA_z = pseudoVel[baseA + 5];
1276
- const pslB_x = pseudoVel[baseB], pslB_y = pseudoVel[baseB + 1], pslB_z = pseudoVel[baseB + 2];
1277
- const psaB_x = pseudoVel[baseB + 3], psaB_y = pseudoVel[baseB + 4], psaB_z = pseudoVel[baseB + 5];
1278
-
1279
- const psvAx_at = pslA_x + psaA_y * raz - psaA_z * ray;
1280
- const psvAy_at = pslA_y + psaA_z * rax - psaA_x * raz;
1281
- const psvAz_at = pslA_z + psaA_x * ray - psaA_y * rax;
1282
- const psvBx_at = pslB_x + psaB_y * rbz - psaB_z * rby;
1283
- const psvBy_at = pslB_y + psaB_z * rbx - psaB_x * rbz;
1284
- const psvBz_at = pslB_z + psaB_x * rby - psaB_y * rbx;
1285
-
1286
- const psdvx = psvAx_at - psvBx_at;
1287
- const psdvy = psvAy_at - psvBy_at;
1288
- const psdvz = psvAz_at - psvBz_at;
1289
- const psvn = psdvx * nx + psdvy * ny + psdvz * nz;
1290
-
1291
- const jn_accum = pos_jn[ci];
1292
- const lambda = -m_eff_n * (psvn + bias_p);
1293
- const sum = jn_accum + lambda;
1294
- const new_jn = sum > 0 ? sum : 0;
1295
- const delta = new_jn - jn_accum;
1296
- pos_jn[ci] = new_jn;
1297
-
1298
- if (delta !== 0) {
1299
- const Px = nx * delta;
1300
- const Py = ny * delta;
1301
- const Pz = nz * delta;
1302
- apply_impulse_to_pseudo(pseudoVel, baseA, ss, sbaseA, invMA, rax, ray, raz, Px, Py, Pz, +1, scratch_inertia_a);
1303
- apply_impulse_to_pseudo(pseudoVel, baseB, ss, sbaseB, invMB, rbx, rby, rbz, Px, Py, Pz, -1, scratch_inertia_b);
1304
- }
1305
- }
1306
- }
1307
- }
1308
-
1309
- /**
1310
- * Convenience single-step driver: prepare → refresh → velocity →
1311
- * restitution position, all at the full `dt` (one substep). Equivalent to
1312
- * the Phase-2 solver. The substepped path in `PhysicsSystem.fixedUpdate`
1313
- * calls the stages directly; this entry point exists for callers/tests that
1314
- * want a one-shot solve.
1315
- *
1316
- * The position pass writes `system.__pseudo_velocity`; the caller must zero
1317
- * that buffer before this call and fold it into the pose afterwards.
1318
- *
1319
- * @param {ManifoldStore} manifolds
1320
- * @param {PhysicsSystem} system
1321
- * @param {number} dt
1322
- * @param {number} [iters]
1323
- * @param {number} [pos_iters]
1324
- */
1325
- export function solve_contacts(manifolds, system, dt,
1326
- iters = DEFAULT_VELOCITY_ITERATIONS,
1327
- pos_iters = DEFAULT_POSITION_ITERATIONS) {
1328
- if (dt <= 0) return;
1329
- if (prepare_contacts(manifolds, system, dt) === 0) return;
1330
- redetect_concave_contacts(manifolds, system);
1331
- refresh_contacts(manifolds, system.__transforms);
1332
- warm_start_contacts(manifolds, system);
1333
- solve_velocity(manifolds, system, iters);
1334
- apply_restitution(manifolds, system);
1335
- solve_position(manifolds, system, pos_iters);
1336
- }
1337
-
1338
- export { DEFAULT_VELOCITY_ITERATIONS, DEFAULT_POSITION_ITERATIONS };
1
+ import { v3_quat3_apply } from "../../../core/geom/vec3/v3_quat3_apply.js";
2
+ import { v3_quat3_apply_inverse } from "../../../core/geom/vec3/v3_quat3_apply_inverse.js";
3
+ import { body_id_index } from "../body/BodyStorage.js";
4
+ import {
5
+ SBS_AV_X,
6
+ SBS_AV_Y,
7
+ SBS_AV_Z,
8
+ SBS_INV_I_X,
9
+ SBS_INV_I_Y,
10
+ SBS_INV_I_Z,
11
+ SBS_INV_MASS,
12
+ SBS_LV_X,
13
+ SBS_LV_Y,
14
+ SBS_LV_Z,
15
+ SBS_QW,
16
+ SBS_QX,
17
+ SBS_QY,
18
+ SBS_QZ,
19
+ SBS_STRIDE,
20
+ } from "../body/SolverBodyState.js";
21
+ import { CONTACT_STRIDE } from "../contact/ManifoldStore.js";
22
+ import { BodyKind } from "../ecs/BodyKind.js";
23
+ import { is_sensor } from "../ecs/is_sensor.js";
24
+ import { angular_effective_mass_raw, world_inverse_inertia_apply_raw } from "../inertia/world_inverse_inertia.js";
25
+ import { redetect_pair_geometry } from "../narrowphase/narrowphase_step.js";
26
+ import { friction_cone_clamp } from "./friction_cone.js";
27
+
28
+ /**
29
+ * # TGS split-impulse contact solver (staged)
30
+ *
31
+ * Temporal Gauss-Seidel with Catto-2018 split impulse. The solver runs as a
32
+ * sequence of stages driven by `PhysicsSystem.fixedUpdate`, which owns the
33
+ * substep loop (it has to — velocity/position integration spans every awake
34
+ * body, not just contacts):
35
+ *
36
+ * prepare_contacts(manifolds, system, h) // once per outer step
37
+ * for each substep:
38
+ * (system integrates gravity by h)
39
+ * redetect_concave_contacts(manifolds, system) // concave: fresh narrowphase
40
+ * refresh_contacts(manifolds, system) // convex: analytic re-derive
41
+ * warm_start_contacts(manifolds, system) // replay impulse — per substep!
42
+ * solve_velocity(manifolds, system, iters) // non-penetration + friction
43
+ * solve_position(manifolds, system, pos_iters)
44
+ * (system integrates position by h, folding pseudo-velocity)
45
+ * apply_restitution(manifolds, system) // once, after the loop
46
+ *
47
+ * The three concerns are fully decoupled (Phases 1–2) and substepping
48
+ * (Phase 3) re-runs only the velocity + position solve per substep
49
+ * narrowphase runs once per outer step. Each substep re-derives the
50
+ * current penetration analytically from the bodies' moved poses and the
51
+ * contact anchors captured at prepare time, so the position correction
52
+ * adapts as the stack settles (the mechanism behind TGS stack stability)
53
+ * without paying for narrowphase N times.
54
+ *
55
+ * Why staged module functions sharing module scratch, rather than one call:
56
+ * the substep loop interleaves solver stages with whole-body integration
57
+ * that lives in the system. The stages communicate through module-scoped
58
+ * scratch + `g_*` state set by `prepare_contacts`; they must be called in
59
+ * order, prepare first, within a single outer step. Single-threaded and
60
+ * deterministic, so the shared state is safe.
61
+ *
62
+ * Contacts from all islands are flattened into one scratch array. Islands
63
+ * are independent (they share no bodies), so a single flat Gauss-Seidel
64
+ * sweep gives the same result as per-island sweeps the island partition
65
+ * is still built and used by the sleep test, just not needed here.
66
+ */
67
+
68
+ /**
69
+ * A pair is "sensor-only" when either side {@link is_sensor} (the body OR its
70
+ * primary collider carries the IsSensor flag). The manifold still exists (so
71
+ * Begin/Stay/End events fire from the manifold-diff pass) but no impulse
72
+ * is applied.
73
+ *
74
+ * @param {RigidBody} rbA
75
+ * @param {Collider} colA
76
+ * @param {RigidBody} rbB
77
+ * @param {Collider} colB
78
+ * @returns {boolean}
79
+ */
80
+ function pair_is_sensor(rbA, colA, rbB, colB) {
81
+ return is_sensor(rbA, colA) || is_sensor(rbB, colB);
82
+ }
83
+
84
+ /**
85
+ * Velocity-iteration count per substep. With substepping the per-substep
86
+ * count can be lower than a single-step PGS solver would need, because the
87
+ * outer loop revisits the contact set `substeps` times.
88
+ * @type {number}
89
+ */
90
+ const DEFAULT_VELOCITY_ITERATIONS = 10;
91
+
92
+ /**
93
+ * Position-iteration count per substep (split-impulse pseudo-velocity pass).
94
+ * @type {number}
95
+ */
96
+ const DEFAULT_POSITION_ITERATIONS = 2;
97
+
98
+ /**
99
+ * Penetration allowed without applying position correction. Eliminates
100
+ * micro-jitter at near-zero overlap.
101
+ * @type {number}
102
+ */
103
+ const PENETRATION_SLOP = 0.005;
104
+
105
+ /**
106
+ * SPOOK contact stiffness `k`. Effectively infinite for rigid-body
107
+ * contact: what the solver actually sees is the regularization
108
+ * `eps = 4 / (h² · k · (1 + 4d))`, negligible at `k = 1e12` but in place
109
+ * as a continuous compliance dial for future soft contacts. Lacoursière
110
+ * 2007; same formulation as cannon-es / AgX.
111
+ * @type {number}
112
+ */
113
+ const CONTACT_STIFFNESS = 1e12;
114
+
115
+ /**
116
+ * SPOOK contact relaxation `d`. Chosen so `a = 4 / (h(1 + 4d)) = 0.2 / h`,
117
+ * numerically matching the prior Baumgarte β = 0.2 gain. `4/(1+4d)=0.2`
118
+ * `d = 4.75`. Note `h` here is the SUBSTEP size: position correction is
119
+ * applied per substep, so the gain is derived from the substep `dt`.
120
+ * @type {number}
121
+ */
122
+ const CONTACT_RELAXATION = 4.75;
123
+
124
+ /**
125
+ * Maximum magnitude of the position-correction velocity bias, in m/s.
126
+ * Belt-and-braces against inflated EPA depths (PLAN.md caveat) driving the
127
+ * bias to tens of m/s. Removed once closed-form triangle solvers land.
128
+ * @type {number}
129
+ */
130
+ const MAX_POSITION_BIAS = 3;
131
+
132
+ /**
133
+ * Velocity below which restitution is suppressed (no micro-bounce buzz on
134
+ * resting stacks).
135
+ * @type {number}
136
+ */
137
+ const RESTITUTION_VELOCITY_THRESHOLD = 1.0;
138
+
139
+ /**
140
+ * Box2D-v3 soft-constraint scales (`b2MakeSoft`) for a damped spring of
141
+ * frequency `hertz` and damping ratio `zeta` discretised at substep `h`:
142
+ *
143
+ * omega = 2π·hertz; a1 = + h·ω; a2 = h·ω·a1
144
+ * mass_scale = a2 / (1 + a2)
145
+ * impulse_scale = 1 / (1 + a2)
146
+ *
147
+ * `hertz = 0` means HARD: mass_scale 1, impulse_scale 0 — the row reduces
148
+ * bit-exactly to the legacy formulation (no decay, full velocity gain).
149
+ * The bias rate (ω/a1) is deliberately NOT used: penetration recovery
150
+ * stays in the split-impulse position pass (the hybrid scheme — velocity
151
+ * rows never inject positional energy, so no relax pass is needed).
152
+ *
153
+ * Writes `[mass_scale, impulse_scale]` into `out`.
154
+ */
155
+ function make_soft(out, hertz, zeta, h) {
156
+ if (hertz <= 0) {
157
+ out[0] = 1;
158
+ out[1] = 0;
159
+ out[2] = 0;
160
+ return;
161
+ }
162
+ const omega = 2 * Math.PI * hertz;
163
+ const a1 = 2 * zeta + h * omega;
164
+ const a2 = h * omega * a1;
165
+ const a3 = 1 / (1 + a2);
166
+ out[0] = a2 * a3;
167
+ out[1] = a3;
168
+ out[2] = omega / a1;
169
+ }
170
+
171
+ /** Whether this step's contacts are SOFT (system.contactHertz > 0) —
172
+ * set by prepare_contacts; read by the per-substep geometry passes
173
+ * (separation bookkeeping) and by the system loop (position pass vs
174
+ * relax pass). */
175
+ export let g_contacts_soft = false;
176
+
177
+ /** Per-prepare soft scales: dynamic-dynamic pairs. [massScale, impulseScale, biasRate] */
178
+ const g_soft_dyn = new Float64Array([1, 0, 0]);
179
+ /** Per-prepare soft scales: pairs with a non-dynamic side (2× hertz —
180
+ * Box2D's staticSoftness, "stiffer so bodies don't get pushed through the
181
+ * ground"). */
182
+ const g_soft_static = new Float64Array([1, 0, 0]);
183
+
184
+ /**
185
+ * Per-contact pre-step scratch stride: 23 doubles.
186
+ * 0..2 : localWA (A's contact witness, in A's LOCAL frame — constant)
187
+ * 3..5 : localWB (B's contact witness, in B's LOCAL frame — constant)
188
+ * 6..8 : rA (lever from A's COM to the contact midpoint refreshed)
189
+ * 9..11: rB (lever from B's COM to the contact midpoint refreshed)
190
+ * 12..14: t1 (tangent 1, unit, world — constant)
191
+ * 15..17: t2 (tangent 2, unit, world — constant)
192
+ * 18 : m_eff_n
193
+ * 19 : m_eff_t1
194
+ * 20 : m_eff_t2
195
+ * 21 : rest_bias (restitution: `e · vn_approach`, ≤ 0; 0 if not bouncing)
196
+ * 22 : bias_position (depth-correction bias refreshed each substep)
197
+ * 23 : soft_mass_scale (soft-contact velocity scale 1 when hard)
198
+ * 24 : soft_imp_scale (soft-contact impulse-decay scale 0 when hard)
199
+ * 25 : soft_bias_rate (penetration-bias rate 0 when hard)
200
+ *
201
+ * `localWA` / `localWB` are the per-body contact witnesses expressed in body
202
+ * local frames at prepare time. Each substep, `refresh_contacts` rotates
203
+ * them back to world by the body's current pose to recover the moved contact
204
+ * points, re-derives the current penetration depth (anchored on the trusted
205
+ * prepare-time depth via a delta, so it's sign-robust), and rebuilds the
206
+ * impulse lever arms `rA` / `rB` and the position bias.
207
+ * @type {number}
208
+ */
209
+ const PRE_STRIDE = 26;
210
+
211
+ /** Per-contact index list — 4 uint32 per contact: slot, idx, idxA, idxB. */
212
+ const INDEX_STRIDE = 4;
213
+
214
+ /**
215
+ * Per-body pseudo-velocity stride: 3 linear + 3 angular doubles. Owned by
216
+ * PhysicsSystem (`system.__pseudo_velocity`); zeroed each substep before
217
+ * the position pass and folded into the pose by `integrate_position`.
218
+ * @type {number}
219
+ */
220
+ const PSEUDO_STRIDE = 6;
221
+
222
+ let scratch_pre = new Float64Array(64 * PRE_STRIDE);
223
+ let scratch_idx = new Uint32Array(64 * INDEX_STRIDE);
224
+ let scratch_mu = new Float64Array(64);
225
+ let scratch_pos_jn = new Float64Array(64);
226
+
227
+ /**
228
+ * Per-contact maximum normal impulse seen across the whole outer step's
229
+ * velocity solving (all substeps, all iterations). Reset in
230
+ * {@link prepare_contacts}, updated in {@link solve_velocity}, read by
231
+ * {@link apply_restitution}.
232
+ *
233
+ * Restitution must fire whenever a contact *was* compressive at some point
234
+ * in the step, even if its accumulated impulse later relaxes back to ~0 — a
235
+ * transient collision (ball bouncing, head-on hit) under per-substep
236
+ * warm-start ends the loop with `j_n ≈ 0` because there's no sustained load
237
+ * to hold the impulse up. Gating on this running max (Box2D-v3
238
+ * `maxNormalImpulse`) rather than the end-of-loop `j_n` is what makes
239
+ * bounces fire.
240
+ * @type {Float64Array}
241
+ */
242
+ let scratch_max_jn = new Float64Array(64);
243
+
244
+ /**
245
+ * Per-contact flag: 1 if the contact's pair involves a concave body, else 0.
246
+ * Concave contacts take the per-substep re-detection path
247
+ * ({@link redetect_concave_contacts}) — their feature genuinely changes as
248
+ * the body rocks, so the analytic refresh that freezes it would pump energy.
249
+ * Convex contacts (flag 0) take the cheap analytic {@link refresh_contacts}.
250
+ * @type {Uint8Array}
251
+ */
252
+ let scratch_concave = new Uint8Array(64);
253
+
254
+ /**
255
+ * Distinct concave-involved manifold slots touched this step, with the body
256
+ * indices needed to fetch their collider lists for re-detection. Parallel
257
+ * arrays, `g_concave_slot_count` valid entries, filled by
258
+ * {@link prepare_contacts}.
259
+ */
260
+ let concave_slot = new Uint32Array(32);
261
+ let concave_slot_idxA = new Uint32Array(32);
262
+ let concave_slot_idxB = new Uint32Array(32);
263
+ let g_concave_slot_count = 0;
264
+
265
+ /**
266
+ * Shared cross-stage state, set by {@link prepare_contacts} and read by the
267
+ * per-substep stages within the same outer step. Single-threaded, so plain
268
+ * module variables are safe.
269
+ */
270
+ let g_contact_count = 0;
271
+ let g_spook_a = 0;
272
+ let g_spook_eps = 0;
273
+
274
+ /** Scratch for re-deriving contact world points in {@link refresh_contacts}. */
275
+ const scratch_cp = new Float64Array(6);
276
+
277
+ function ensure_capacity(n) {
278
+ if (scratch_pre.length < n * PRE_STRIDE) {
279
+ scratch_pre = new Float64Array(n * PRE_STRIDE * 2);
280
+ }
281
+ if (scratch_idx.length < n * INDEX_STRIDE) {
282
+ scratch_idx = new Uint32Array(n * INDEX_STRIDE * 2);
283
+ }
284
+ if (scratch_mu.length < n) {
285
+ scratch_mu = new Float64Array(n * 2);
286
+ }
287
+ if (scratch_pos_jn.length < n) {
288
+ scratch_pos_jn = new Float64Array(n * 2);
289
+ }
290
+ if (scratch_max_jn.length < n) {
291
+ scratch_max_jn = new Float64Array(n * 2);
292
+ }
293
+ if (scratch_concave.length < n) {
294
+ scratch_concave = new Uint8Array(n * 2);
295
+ }
296
+ }
297
+
298
+ function ensure_concave_slot_capacity(n) {
299
+ if (concave_slot.length < n) {
300
+ concave_slot = new Uint32Array(n * 2);
301
+ concave_slot_idxA = new Uint32Array(n * 2);
302
+ concave_slot_idxB = new Uint32Array(n * 2);
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Build an orthonormal tangent basis perpendicular to a unit normal.
308
+ *
309
+ * @param {Float64Array} out 6 floats: t1.xyz then t2.xyz
310
+ * @param {number} off
311
+ * @param {number} nx
312
+ * @param {number} ny
313
+ * @param {number} nz
314
+ */
315
+ function build_tangents(out, off, nx, ny, nz) {
316
+ const ax = nx < 0 ? -nx : nx;
317
+ const ay = ny < 0 ? -ny : ny;
318
+ const az = nz < 0 ? -nz : nz;
319
+
320
+ let rx, ry, rz;
321
+
322
+ if (ax <= ay && ax <= az) {
323
+ rx = 1;
324
+ ry = 0;
325
+ rz = 0;
326
+ } else if (ay <= az) {
327
+ rx = 0;
328
+ ry = 1;
329
+ rz = 0;
330
+ } else {
331
+ rx = 0;
332
+ ry = 0;
333
+ rz = 1;
334
+ }
335
+
336
+ let t1x = ny * rz - nz * ry;
337
+ let t1y = nz * rx - nx * rz;
338
+ let t1z = nx * ry - ny * rx;
339
+
340
+ const inv = 1 / Math.sqrt(t1x * t1x + t1y * t1y + t1z * t1z);
341
+
342
+ t1x *= inv;
343
+ t1y *= inv;
344
+ t1z *= inv;
345
+
346
+ const t2x = ny * t1z - nz * t1y;
347
+ const t2y = nz * t1x - nx * t1z;
348
+ const t2z = nx * t1y - ny * t1x;
349
+
350
+ out[off] = t1x;
351
+ out[off + 1] = t1y;
352
+ out[off + 2] = t1z;
353
+
354
+ out[off + 3] = t2x;
355
+ out[off + 4] = t2y;
356
+ out[off + 5] = t2z;
357
+ }
358
+
359
+ // Friction / restitution are no longer combined here: the narrowphase combines
360
+ // the specific source-collider pair's coefficients per contact (so compound
361
+ // bodies with mixed-material colliders are honoured) and stores the result in
362
+ // the manifold (CONTACT_STRIDE offsets 14 / 15). prepare_contacts reads them
363
+ // per contact. See engine/physics/contact/combine_material.js.
364
+
365
+ const scratch_clamp = new Float64Array(2);
366
+ const scratch_inertia_a = new Float64Array(3);
367
+ const scratch_inertia_b = new Float64Array(3);
368
+
369
+ /**
370
+ * Apply an impulse `P` at body-relative offset `r` to persistent velocity
371
+ * (Δv = P/m, Δω = I⁻¹·(r × P)), reading and writing the body's
372
+ * {@link SolverBodyState} span instead of its `RigidBody` / `Transform`
373
+ * objects — flat typed-array access, no dereference, on the hot path.
374
+ *
375
+ * @param {Float64Array} ss solver-body-state data array
376
+ * @param {number} base `body_index * SBS_STRIDE`
377
+ * @param {number} invM
378
+ * @param {number} rx
379
+ * @param {number} ry
380
+ * @param {number} rz
381
+ * @param {number} Px
382
+ * @param {number} Py
383
+ * @param {number} Pz
384
+ * @param {number} sign +1 for body A, -1 for body B
385
+ * @param {Float64Array} scratch_inertia
386
+ */
387
+ function apply_impulse_to_body(
388
+ ss, base, invM,
389
+ rx, ry, rz,
390
+ Px, Py, Pz,
391
+ sign,
392
+ scratch_inertia
393
+ ) {
394
+
395
+ if (invM === 0) {
396
+ return;
397
+ }
398
+
399
+ const sPx = sign * Px;
400
+ const sPy = sign * Py;
401
+ const sPz = sign * Pz;
402
+
403
+ ss[base + SBS_LV_X] += sPx * invM;
404
+ ss[base + SBS_LV_Y] += sPy * invM;
405
+ ss[base + SBS_LV_Z] += sPz * invM;
406
+
407
+ const tx = ry * sPz - rz * sPy;
408
+ const ty = rz * sPx - rx * sPz;
409
+ const tz = rx * sPy - ry * sPx;
410
+
411
+ world_inverse_inertia_apply_raw(
412
+ scratch_inertia, 0,
413
+ ss[base + SBS_INV_I_X], ss[base + SBS_INV_I_Y], ss[base + SBS_INV_I_Z],
414
+ ss[base + SBS_QX], ss[base + SBS_QY], ss[base + SBS_QZ], ss[base + SBS_QW],
415
+ tx, ty, tz
416
+ );
417
+
418
+ ss[base + SBS_AV_X] += scratch_inertia[0];
419
+ ss[base + SBS_AV_Y] += scratch_inertia[1];
420
+ ss[base + SBS_AV_Z] += scratch_inertia[2];
421
+ }
422
+
423
+ /**
424
+ * Apply a position-pass impulse `P` at body-relative offset `r` to the
425
+ * body's pseudo-velocity (linear + angular) in the `pseudo_velocity` flat
426
+ * buffer. Mirrors {@link apply_impulse_to_body} but the result is consumed
427
+ * by `integrate_position` the same substep and never persists. Inverse
428
+ * inertia / orientation are read from the body's {@link SolverBodyState} span.
429
+ *
430
+ * @param {Float64Array} pseudo_velocity stride = {@link PSEUDO_STRIDE}
431
+ * @param {number} pbase offset = `body_index * PSEUDO_STRIDE`
432
+ * @param {Float64Array} ss solver-body-state data array
433
+ * @param {number} sbase `body_index * SBS_STRIDE`
434
+ * @param {number} invM
435
+ * @param {number} rx
436
+ * @param {number} ry
437
+ * @param {number} rz
438
+ * @param {number} Px
439
+ * @param {number} Py
440
+ * @param {number} Pz
441
+ * @param {number} sign +1 for body A, -1 for body B
442
+ * @param {Float64Array} scratch_inertia
443
+ */
444
+ function apply_impulse_to_pseudo(
445
+ pseudo_velocity, pbase,
446
+ ss, sbase, invM,
447
+ rx, ry, rz,
448
+ Px, Py, Pz,
449
+ sign,
450
+ scratch_inertia
451
+ ) {
452
+ if (invM === 0) {
453
+ return;
454
+ }
455
+
456
+ const sPx = sign * Px;
457
+ const sPy = sign * Py;
458
+ const sPz = sign * Pz;
459
+
460
+ pseudo_velocity[pbase] += sPx * invM;
461
+ pseudo_velocity[pbase + 1] += sPy * invM;
462
+ pseudo_velocity[pbase + 2] += sPz * invM;
463
+
464
+ const tx = ry * sPz - rz * sPy;
465
+ const ty = rz * sPx - rx * sPz;
466
+ const tz = rx * sPy - ry * sPx;
467
+
468
+ world_inverse_inertia_apply_raw(
469
+ scratch_inertia, 0,
470
+ ss[sbase + SBS_INV_I_X], ss[sbase + SBS_INV_I_Y], ss[sbase + SBS_INV_I_Z],
471
+ ss[sbase + SBS_QX], ss[sbase + SBS_QY], ss[sbase + SBS_QZ], ss[sbase + SBS_QW],
472
+ tx, ty, tz
473
+ );
474
+
475
+ pseudo_velocity[pbase + 3] += scratch_inertia[0];
476
+ pseudo_velocity[pbase + 4] += scratch_inertia[1];
477
+ pseudo_velocity[pbase + 5] += scratch_inertia[2];
478
+ }
479
+
480
+ /**
481
+ * Quadratic-form contribution of one body to the constraint effective mass
482
+ * along a unit axis: `(r × axis)^T · I⁻¹_world · (r × axis)`. Reads the body's
483
+ * inverse-inertia diagonal + orientation from its {@link SolverBodyState} span.
484
+ * Non-dynamic bodies carry a zero inverse-inertia diagonal there, so the
485
+ * zero-guard returns 0 for them — matching the object path's `kind` guard.
486
+ *
487
+ * @param {Float64Array} ss solver-body-state data array
488
+ * @param {number} base `body_index * SBS_STRIDE`
489
+ * @param {number} rx
490
+ * @param {number} ry
491
+ * @param {number} rz
492
+ * @param {number} ax
493
+ * @param {number} ay
494
+ * @param {number} az
495
+ * @param {Float64Array} scratch_inertia
496
+ * @returns {number}
497
+ */
498
+ function angular_jacobian_contribution(
499
+ ss, base,
500
+ rx, ry, rz,
501
+ ax, ay, az,
502
+ scratch_inertia
503
+ ) {
504
+ const iix = ss[base + SBS_INV_I_X];
505
+ const iiy = ss[base + SBS_INV_I_Y];
506
+ const iiz = ss[base + SBS_INV_I_Z];
507
+
508
+ if (iix === 0 && iiy === 0 && iiz === 0) {
509
+ return 0;
510
+ }
511
+
512
+ return angular_effective_mass_raw(
513
+ iix, iiy, iiz,
514
+ ss[base + SBS_QX], ss[base + SBS_QY], ss[base + SBS_QZ], ss[base + SBS_QW],
515
+ rx, ry, rz,
516
+ ax, ay, az,
517
+ scratch_inertia
518
+ );
519
+ }
520
+
521
+ /**
522
+ * Flat count of all touched contacts across every island, plus the slot
523
+ * list to iterate. Islands are concatenated densely in `contact_data`;
524
+ * `contact_offsets[island_count]` is the end of the last island.
525
+ *
526
+ * @param {PhysicsSystem} system
527
+ * @returns {{slot_list: Uint32Array, total_slots: number}|null}
528
+ */
529
+ function island_slot_range(system) {
530
+ const islands = system.islands;
531
+
532
+ if (islands === undefined || islands === null) {
533
+ return null;
534
+ }
535
+
536
+ const island_count = islands.island_count;
537
+
538
+ if (island_count === 0) {
539
+ return null;
540
+ }
541
+
542
+ // Reused result object this helper runs for every solver stage of
543
+ // every substep, and the step's contract is allocation-free steady
544
+ // state. Callers consume it immediately and never retain it.
545
+ scratch_slot_range.slot_list = islands.contact_data;
546
+ scratch_slot_range.total_slots = islands.contact_offsets[island_count];
547
+ return scratch_slot_range;
548
+ }
549
+
550
+ /** Reused result of {@link island_slot_range}. */
551
+ const scratch_slot_range = { slot_list: null, total_slots: 0 };
552
+
553
+ /**
554
+ * Stage 1 prepare the contact constraints for an outer step.
555
+ *
556
+ * Packs every touched, non-sensor contact into the flat scratch arrays:
557
+ * local-frame witness anchors, tangent basis, effective masses, friction,
558
+ * the restitution approach velocity, and the warm-start replay (applied
559
+ * once here, not per substep). Computes the SPOOK gains from the SUBSTEP
560
+ * size `dt_sub` because the position correction runs once per substep.
561
+ *
562
+ * @param {ManifoldStore} manifolds
563
+ * @param {PhysicsSystem} system
564
+ * @param {number} dt_sub substep size `dt / substeps`
565
+ * @returns {number} number of contacts prepared (also stored module-side)
566
+ */
567
+ export function prepare_contacts(manifolds, system, dt_sub) {
568
+ g_contact_count = 0;
569
+ // Reset BEFORE the early returns below: on a step with awake bodies but zero
570
+ // contacts/islands, an early return must not leave a stale count from a prior
571
+ // step that redetect_concave_contacts would then act on against evicted slots.
572
+ g_concave_slot_count = 0;
573
+
574
+ if (dt_sub <= 0) {
575
+ return 0;
576
+ }
577
+
578
+ // Soft-contact scales for this step (hertz clamped to an eighth of the
579
+ // substep rate, per Box2D v3; system.contactHertz = 0 keeps every row
580
+ // hard bit-exact legacy behaviour).
581
+ {
582
+ const hertz_limit = 0.125 / dt_sub;
583
+ const hertz = system.contactHertz < hertz_limit ? system.contactHertz : hertz_limit;
584
+ make_soft(g_soft_dyn, hertz, system.contactDampingRatio, dt_sub);
585
+ make_soft(g_soft_static, 2 * hertz, system.contactDampingRatio, dt_sub);
586
+ g_contacts_soft = hertz > 0;
587
+ }
588
+
589
+ const range = island_slot_range(system);
590
+
591
+ if (range === null) {
592
+ return 0;
593
+ }
594
+
595
+ const slot_list = range.slot_list;
596
+ const total_slots = range.total_slots;
597
+
598
+ let contact_total = 0;
599
+
600
+ for (let i = 0; i < total_slots; i++) {
601
+ contact_total += manifolds.contact_count(slot_list[i]);
602
+ }
603
+
604
+ if (contact_total === 0) {
605
+ return 0;
606
+ }
607
+
608
+ ensure_capacity(contact_total);
609
+
610
+ const denom = 1 + 4 * CONTACT_RELAXATION;
611
+ g_spook_a = 4 / (dt_sub * denom);
612
+ g_spook_eps = 4 / (dt_sub * dt_sub * CONTACT_STIFFNESS * denom);
613
+
614
+ const data = manifolds.data_buffer;
615
+ const pre = scratch_pre;
616
+ const idx = scratch_idx;
617
+ const mus = scratch_mu;
618
+ const pos_jn = scratch_pos_jn;
619
+ const ss = system.__solver_state.data;
620
+
621
+ let c = 0;
622
+ for (let i = 0; i < total_slots; i++) {
623
+ const slot = slot_list[i];
624
+
625
+ const idxA = body_id_index(manifolds.bodyA(slot));
626
+ const idxB = body_id_index(manifolds.bodyB(slot));
627
+
628
+ const baseA = idxA * SBS_STRIDE;
629
+ const baseB = idxB * SBS_STRIDE;
630
+
631
+ const rbA = system.__bodies[idxA];
632
+ const rbB = system.__bodies[idxB];
633
+
634
+ const trA = system.__transforms[idxA];
635
+ const trB = system.__transforms[idxB];
636
+
637
+ const colA = system.__primary_collider(idxA);
638
+ const colB = system.__primary_collider(idxB);
639
+
640
+ if (colA === null || colB === null) {
641
+ continue;
642
+ }
643
+
644
+ if (pair_is_sensor(rbA, colA, rbB, colB)) {
645
+ continue;
646
+ }
647
+
648
+ // A pair is "concave" when either side's shape is non-convex. Those
649
+ // contacts take the per-substep re-detection path (the contact
650
+ // feature moves as the body rocks); convex pairs keep the cheap
651
+ // analytic refresh. Recorded once per slot for redetect_concave_contacts.
652
+ const slot_concave = (colA.shape.is_convex === false) || (colB.shape.is_convex === false);
653
+
654
+ if (slot_concave) {
655
+ ensure_concave_slot_capacity(g_concave_slot_count + 1);
656
+ concave_slot[g_concave_slot_count] = slot;
657
+ concave_slot_idxA[g_concave_slot_count] = idxA;
658
+ concave_slot_idxB[g_concave_slot_count] = idxB;
659
+ g_concave_slot_count++;
660
+ }
661
+
662
+ const invMA = ss[baseA + SBS_INV_MASS];
663
+ const invMB = ss[baseB + SBS_INV_MASS];
664
+
665
+ const cc = manifolds.contact_count(slot);
666
+ const slot_off = manifolds.slot_data_offset(slot);
667
+
668
+ const pAx = trA.position.x, pAy = trA.position.y, pAz = trA.position.z;
669
+ const pBx = trB.position.x, pBy = trB.position.y, pBz = trB.position.z;
670
+ const qA = trA.rotation, qB = trB.rotation;
671
+
672
+ for (let k = 0; k < cc; k++) {
673
+ const off = slot_off + k * CONTACT_STRIDE;
674
+
675
+ // Per-contact combined materials (stamped by the narrowphase from
676
+ // this contact's specific source-collider pair).
677
+ const friction_combined = data[off + 14];
678
+ const restitution_combined = data[off + 15];
679
+
680
+ const wax = data[off], way = data[off + 1], waz = data[off + 2];
681
+ const wbx = data[off + 3], wby = data[off + 4], wbz = data[off + 5];
682
+ const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
683
+
684
+ const px = (wax + wbx) * 0.5;
685
+ const py = (way + wby) * 0.5;
686
+ const pz = (waz + wbz) * 0.5;
687
+
688
+ const rax = px - pAx, ray = py - pAy, raz = pz - pAz;
689
+ const rbx = px - pBx, rby = py - pBy, rbz = pz - pBz;
690
+
691
+ const pre_off = c * PRE_STRIDE;
692
+
693
+ // Per-body witness anchors in LOCAL frame (constant across the
694
+ // outer step). localW = R⁻¹ · (witness − COM).
695
+ v3_quat3_apply_inverse(pre, pre_off, wax - pAx, way - pAy, waz - pAz, qA[0], qA[1], qA[2], qA[3]);
696
+ v3_quat3_apply_inverse(pre, pre_off + 3, wbx - pBx, wby - pBy, wbz - pBz, qB[0], qB[1], qB[2], qB[3]);
697
+
698
+ // Lever arms from COM to the contact midpoint (refreshed each
699
+ // substep; seeded here from prepare-time pose).
700
+ pre[pre_off + 6] = rax;
701
+ pre[pre_off + 7] = ray;
702
+ pre[pre_off + 8] = raz;
703
+ pre[pre_off + 9] = rbx;
704
+ pre[pre_off + 10] = rby;
705
+ pre[pre_off + 11] = rbz;
706
+
707
+ build_tangents(pre, pre_off + 12, nx, ny, nz);
708
+ const t1x = pre[pre_off + 12], t1y = pre[pre_off + 13], t1z = pre[pre_off + 14];
709
+ const t2x = pre[pre_off + 15], t2y = pre[pre_off + 16], t2z = pre[pre_off + 17];
710
+
711
+ const k_n = invMA + invMB
712
+ + angular_jacobian_contribution(ss, baseA, rax, ray, raz, nx, ny, nz, scratch_inertia_a)
713
+ + angular_jacobian_contribution(ss, baseB, rbx, rby, rbz, nx, ny, nz, scratch_inertia_b);
714
+ const k_t1 = invMA + invMB
715
+ + angular_jacobian_contribution(ss, baseA, rax, ray, raz, t1x, t1y, t1z, scratch_inertia_a)
716
+ + angular_jacobian_contribution(ss, baseB, rbx, rby, rbz, t1x, t1y, t1z, scratch_inertia_b);
717
+ const k_t2 = invMA + invMB
718
+ + angular_jacobian_contribution(ss, baseA, rax, ray, raz, t2x, t2y, t2z, scratch_inertia_a)
719
+ + angular_jacobian_contribution(ss, baseB, rbx, rby, rbz, t2x, t2y, t2z, scratch_inertia_b);
720
+
721
+ const k_n_eff = k_n + g_spook_eps;
722
+ pre[pre_off + 18] = k_n_eff > 0 ? 1 / k_n_eff : 0;
723
+ pre[pre_off + 19] = k_t1 > 0 ? 1 / k_t1 : 0;
724
+ pre[pre_off + 20] = k_t2 > 0 ? 1 / k_t2 : 0;
725
+
726
+ // Restitution approach velocity (captured once, this is the
727
+ // closing speed entering the step). n is B → A; vn < 0 closing.
728
+ const vAx_at = ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * raz - ss[baseA + SBS_AV_Z] * ray;
729
+ const vAy_at = ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rax - ss[baseA + SBS_AV_X] * raz;
730
+ const vAz_at = ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * ray - ss[baseA + SBS_AV_Y] * rax;
731
+
732
+ const vBx_at = ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rbz - ss[baseB + SBS_AV_Z] * rby;
733
+ const vBy_at = ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rbx - ss[baseB + SBS_AV_X] * rbz;
734
+ const vBz_at = ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rby - ss[baseB + SBS_AV_Y] * rbx;
735
+
736
+ const vn_pre = (vAx_at - vBx_at) * nx + (vAy_at - vBy_at) * ny + (vAz_at - vBz_at) * nz;
737
+
738
+ let rest_bias = 0;
739
+ if (vn_pre < -RESTITUTION_VELOCITY_THRESHOLD) {
740
+ rest_bias = restitution_combined * vn_pre;
741
+ }
742
+ pre[pre_off + 21] = rest_bias;
743
+ pre[pre_off + 22] = 0; // bias_position — filled by refresh_contacts
744
+
745
+ // Soft scales: a pair with a non-dynamic side uses the stiffer
746
+ // static softness (Box2D's staticSoftness, 2× hertz).
747
+ const soft = (rbA.kind !== BodyKind.Dynamic || rbB.kind !== BodyKind.Dynamic)
748
+ ? g_soft_static
749
+ : g_soft_dyn;
750
+ pre[pre_off + 23] = soft[0];
751
+ pre[pre_off + 24] = soft[1];
752
+ pre[pre_off + 25] = soft[2];
753
+
754
+ mus[c] = friction_combined;
755
+ pos_jn[c] = 0;
756
+ scratch_max_jn[c] = 0;
757
+ scratch_concave[c] = slot_concave ? 1 : 0;
758
+
759
+ idx[c * INDEX_STRIDE] = slot;
760
+ idx[c * INDEX_STRIDE + 1] = k;
761
+ idx[c * INDEX_STRIDE + 2] = idxA;
762
+ idx[c * INDEX_STRIDE + 3] = idxB;
763
+
764
+ // Warm-start is NOT applied here: under TGS it must be replayed
765
+ // *every substep* (see warm_start_contacts) so that, per substep,
766
+ // the cached impulse balances exactly one substep of gravity.
767
+ // Applying the cached impulse once against a full frame of
768
+ // gravity (the non-substepped pattern) over-pushes resting
769
+ // contacts and jitters / explodes deep stacks.
770
+
771
+ c++;
772
+ }
773
+ }
774
+
775
+ g_contact_count = c;
776
+ return c;
777
+ }
778
+
779
+ /**
780
+ * Stage 1b (per substep) warm-start: replay the cached accumulated
781
+ * impulses `(j_n, j_t1, j_t2)` onto persistent velocity using the current
782
+ * (refreshed) lever arms and tangents. Run once per substep, after
783
+ * {@link refresh_contacts} and before {@link solve_velocity}.
784
+ *
785
+ * Per-substep warm-start is the crux of stable TGS: the stored impulse is a
786
+ * per-substep quantity (≈ the impulse to counter one substep of gravity), so
787
+ * replaying it each substep balances that substep's `integrate_velocity_gravity`
788
+ * and a resting contact holds at zero velocity. `solve_velocity` then only
789
+ * has to correct the residual, which converges in a few iterations even for
790
+ * deep chains because each substep carries just `h` of gravity.
791
+ *
792
+ * @param {ManifoldStore} manifolds
793
+ * @param {PhysicsSystem} system
794
+ */
795
+ export function warm_start_contacts(manifolds, system) {
796
+ const count = g_contact_count;
797
+ if (count === 0) return;
798
+
799
+ const data = manifolds.data_buffer;
800
+ const pre = scratch_pre;
801
+ const idx = scratch_idx;
802
+ const ss = system.__solver_state.data;
803
+
804
+ for (let ci = 0; ci < count; ci++) {
805
+ const slot = idx[ci * INDEX_STRIDE];
806
+ const cidx = idx[ci * INDEX_STRIDE + 1];
807
+
808
+ const baseA = idx[ci * INDEX_STRIDE + 2] * SBS_STRIDE;
809
+ const baseB = idx[ci * INDEX_STRIDE + 3] * SBS_STRIDE;
810
+
811
+ const invMA = ss[baseA + SBS_INV_MASS];
812
+ const invMB = ss[baseB + SBS_INV_MASS];
813
+
814
+ const pre_off = ci * PRE_STRIDE;
815
+
816
+ const rax = pre[pre_off + 6], ray = pre[pre_off + 7], raz = pre[pre_off + 8];
817
+ const rbx = pre[pre_off + 9], rby = pre[pre_off + 10], rbz = pre[pre_off + 11];
818
+
819
+ const t1x = pre[pre_off + 12], t1y = pre[pre_off + 13], t1z = pre[pre_off + 14];
820
+ const t2x = pre[pre_off + 15], t2y = pre[pre_off + 16], t2z = pre[pre_off + 17];
821
+
822
+ const slot_off = manifolds.slot_data_offset(slot);
823
+ const off = slot_off + cidx * CONTACT_STRIDE;
824
+ const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
825
+
826
+ const j_n = data[off + 10];
827
+ const j_t1 = data[off + 11];
828
+ const j_t2 = data[off + 12];
829
+
830
+ const Px = nx * j_n + t1x * j_t1 + t2x * j_t2;
831
+ const Py = ny * j_n + t1y * j_t1 + t2y * j_t2;
832
+ const Pz = nz * j_n + t1z * j_t1 + t2z * j_t2;
833
+
834
+ apply_impulse_to_body(ss, baseA, invMA, rax, ray, raz, Px, Py, Pz, +1, scratch_inertia_a);
835
+ apply_impulse_to_body(ss, baseB, invMB, rbx, rby, rbz, Px, Py, Pz, -1, scratch_inertia_b);
836
+ }
837
+ }
838
+
839
+ /**
840
+ * Stage 2 (per substep) — re-derive each contact's geometry from the
841
+ * bodies' current poses and the local witness anchors captured at prepare.
842
+ *
843
+ * For each contact:
844
+ * - rotate the stored local witnesses back to world by the current pose to
845
+ * get the moved contact points `cpA`, `cpB`;
846
+ * - current penetration `depth_now = depth0 − Δseparation`, where
847
+ * `Δseparation = (cpA wA (cpB wB)) · n` is the change since prepare.
848
+ * Anchoring on the trusted prepare-time depth makes the sign convention
849
+ * irrelevant — only the analytic delta uses the anchors;
850
+ * - rebuild the impulse levers `rA`/`rB` from the moved midpoint;
851
+ * - recompute the position-correction bias from `depth_now`.
852
+ *
853
+ * The contact normal and tangents are held fixed for the outer step (valid
854
+ * for the small per-step rotation), so they are not recomputed here.
855
+ *
856
+ * @param {ManifoldStore} manifolds
857
+ * @param {Transform[]} transforms
858
+ */
859
+ export function refresh_contacts(manifolds, transforms) {
860
+ const count = g_contact_count;
861
+
862
+ if (count === 0) {
863
+ return;
864
+ }
865
+
866
+ const data = manifolds.data_buffer;
867
+ const pre = scratch_pre;
868
+ const idx = scratch_idx;
869
+ const cp = scratch_cp;
870
+ const spook_a = g_spook_a;
871
+
872
+ for (let ci = 0; ci < count; ci++) {
873
+ // Concave contacts are refreshed by redetect_concave_contacts (fresh
874
+ // narrowphase geometry each substep), not by the analytic rotation of
875
+ // frozen anchors their feature moves as the body rocks.
876
+ if (scratch_concave[ci] === 1) {
877
+ continue;
878
+ }
879
+
880
+ const slot = idx[ci * INDEX_STRIDE];
881
+ const cidx = idx[ci * INDEX_STRIDE + 1];
882
+ const idxA = idx[ci * INDEX_STRIDE + 2];
883
+ const idxB = idx[ci * INDEX_STRIDE + 3];
884
+
885
+ const trA = transforms[idxA];
886
+ const trB = transforms[idxB];
887
+
888
+ const slot_off = manifolds.slot_data_offset(slot);
889
+ const off = slot_off + cidx * CONTACT_STRIDE;
890
+
891
+ const wax = data[off], way = data[off + 1], waz = data[off + 2];
892
+ const wbx = data[off + 3], wby = data[off + 4], wbz = data[off + 5];
893
+ const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
894
+
895
+ const depth0 = data[off + 9];
896
+
897
+ const pre_off = ci * PRE_STRIDE;
898
+
899
+ const pAx = trA.position[0];
900
+ const pAy = trA.position[1];
901
+ const pAz = trA.position[2];
902
+
903
+ const pBx = trB.position[0];
904
+ const pBy = trB.position[1];
905
+ const pBz = trB.position[2];
906
+
907
+ const qA = trA.rotation;
908
+ const qB = trB.rotation;
909
+
910
+ // Current world contact points: COM + R · localWitness.
911
+ v3_quat3_apply(cp, 0, pre[pre_off], pre[pre_off + 1], pre[pre_off + 2], qA[0], qA[1], qA[2], qA[3]);
912
+ v3_quat3_apply(cp, 3, pre[pre_off + 3], pre[pre_off + 4], pre[pre_off + 5], qB[0], qB[1], qB[2], qB[3]);
913
+
914
+ const cpAx = pAx + cp[0], cpAy = pAy + cp[1], cpAz = pAz + cp[2];
915
+ const cpBx = pBx + cp[3], cpBy = pBy + cp[4], cpBz = pBz + cp[5];
916
+
917
+ // Penetration re-derived as a delta from the trusted prepare depth.
918
+ // Δsep = ((cpA − wA) − (cpB − wB)) · n.
919
+ const dsep = ((cpAx - wax) - (cpBx - wbx)) * nx
920
+ + ((cpAy - way) - (cpBy - wby)) * ny
921
+ + ((cpAz - waz) - (cpBz - wbz)) * nz;
922
+
923
+ const depth_now = depth0 - dsep;
924
+
925
+ // Impulse levers from each COM to the current contact midpoint.
926
+ const mx = (cpAx + cpBx) * 0.5;
927
+ const my = (cpAy + cpBy) * 0.5;
928
+ const mz = (cpAz + cpBz) * 0.5;
929
+
930
+ pre[pre_off + 6] = mx - pAx;
931
+ pre[pre_off + 7] = my - pAy;
932
+ pre[pre_off + 8] = mz - pAz;
933
+ pre[pre_off + 9] = mx - pBx;
934
+ pre[pre_off + 10] = my - pBy;
935
+ pre[pre_off + 11] = mz - pBz;
936
+
937
+ let bias_position = 0;
938
+
939
+ if (g_contacts_soft) {
940
+ // Soft mode: the position pass is OFF for contacts; lane 22
941
+ // instead carries the (slop-adjusted) SEPARATION for the
942
+ // velocity-bias term, negative while penetrating.
943
+ pre[pre_off + 22] = depth_now > PENETRATION_SLOP ? -(depth_now - PENETRATION_SLOP) : 0;
944
+ continue;
945
+ }
946
+ if (depth_now > PENETRATION_SLOP) {
947
+ bias_position = -spook_a * (depth_now - PENETRATION_SLOP);
948
+
949
+ if (bias_position < -MAX_POSITION_BIAS) {
950
+ bias_position = -MAX_POSITION_BIAS;
951
+ }
952
+ }
953
+
954
+ pre[pre_off + 22] = bias_position;
955
+ }
956
+ }
957
+
958
+ /**
959
+ * Stage 2b (per substep) the concave counterpart of {@link refresh_contacts}.
960
+ *
961
+ * For each concave-involved slot, re-runs the narrowphase geometry at the
962
+ * current substep pose ({@link redetect_pair_geometry}, which rewrites the
963
+ * manifold's witness / normal / depth in place), then re-derives the solver
964
+ * scratch (lever arms, tangent basis, effective masses, position bias) for
965
+ * that slot's contacts from the fresh geometry. This is the whole point of
966
+ * the concave path: a body rocking on a mesh changes which triangle (and
967
+ * normal) it rests on within a single outer step, and freezing that — as the
968
+ * convex analytic refresh does pumps energy in. Re-detecting gives a
969
+ * correct per-substep normal so the body settles.
970
+ *
971
+ * Cost is ~one narrowphase dispatch per concave slot per substep acceptable
972
+ * because concave-involved pairs are rare (the common concave case, a convex
973
+ * body on static terrain, is convex on the moving side and never lands here).
974
+ * The contact count is held fixed (redetect updates geometry only), so the
975
+ * scratch stays aligned with prepare.
976
+ *
977
+ * Must run before {@link refresh_contacts} / {@link warm_start_contacts} each
978
+ * substep. `rest_bias` (captured at prepare) and the `scratch_max_jn`
979
+ * restitution gate are preserved.
980
+ *
981
+ * @param {ManifoldStore} manifolds
982
+ * @param {PhysicsSystem} system reads `__body_collider_lists`, `__bodies`,
983
+ * `__transforms`.
984
+ */
985
+ export function redetect_concave_contacts(manifolds, system) {
986
+ const ns = g_concave_slot_count;
987
+
988
+ if (ns === 0) {
989
+ return;
990
+ }
991
+
992
+ const lists = system.__body_collider_lists;
993
+
994
+ // 1. Re-detect fresh geometry into the manifold for each concave slot.
995
+ for (let s = 0; s < ns; s++) {
996
+ redetect_pair_geometry(manifolds, concave_slot[s], lists[concave_slot_idxA[s]], lists[concave_slot_idxB[s]]);
997
+ }
998
+
999
+ // 2. Re-derive the solver scratch for every concave contact from the
1000
+ // fresh manifold geometry (lever arms, tangents, effective masses,
1001
+ // position bias). Mirrors prepare's per-contact setup but reads the
1002
+ // just-updated witness / normal / depth instead of the prepare-time
1003
+ // values; no local-frame anchors are needed since we re-detect rather
1004
+ // than rotate frozen anchors.
1005
+ const count = g_contact_count;
1006
+ const data = manifolds.data_buffer;
1007
+ const pre = scratch_pre;
1008
+ const idx = scratch_idx;
1009
+ const spook_a = g_spook_a;
1010
+ const spook_eps = g_spook_eps;
1011
+ const ss = system.__solver_state.data;
1012
+
1013
+ for (let ci = 0; ci < count; ci++) {
1014
+ if (scratch_concave[ci] === 0) {
1015
+ continue;
1016
+ }
1017
+
1018
+ const slot = idx[ci * INDEX_STRIDE];
1019
+ const cidx = idx[ci * INDEX_STRIDE + 1];
1020
+ const idxA = idx[ci * INDEX_STRIDE + 2];
1021
+ const idxB = idx[ci * INDEX_STRIDE + 3];
1022
+
1023
+ const baseA = idxA * SBS_STRIDE;
1024
+ const baseB = idxB * SBS_STRIDE;
1025
+
1026
+ const trA = system.__transforms[idxA];
1027
+ const trB = system.__transforms[idxB];
1028
+
1029
+ const invMA = ss[baseA + SBS_INV_MASS];
1030
+ const invMB = ss[baseB + SBS_INV_MASS];
1031
+
1032
+ const slot_off = manifolds.slot_data_offset(slot);
1033
+ const off = slot_off + cidx * CONTACT_STRIDE;
1034
+
1035
+ const wax = data[off], way = data[off + 1], waz = data[off + 2];
1036
+ const wbx = data[off + 3], wby = data[off + 4], wbz = data[off + 5];
1037
+ const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
1038
+ const depth = data[off + 9];
1039
+
1040
+ const px = (wax + wbx) * 0.5;
1041
+ const py = (way + wby) * 0.5;
1042
+ const pz = (waz + wbz) * 0.5;
1043
+
1044
+ const rax = px - trA.position.x, ray = py - trA.position.y, raz = pz - trA.position.z;
1045
+ const rbx = px - trB.position.x, rby = py - trB.position.y, rbz = pz - trB.position.z;
1046
+
1047
+ const pre_off = ci * PRE_STRIDE;
1048
+ pre[pre_off + 6] = rax;
1049
+ pre[pre_off + 7] = ray;
1050
+ pre[pre_off + 8] = raz;
1051
+ pre[pre_off + 9] = rbx;
1052
+ pre[pre_off + 10] = rby;
1053
+ pre[pre_off + 11] = rbz;
1054
+
1055
+ build_tangents(pre, pre_off + 12, nx, ny, nz);
1056
+ const t1x = pre[pre_off + 12], t1y = pre[pre_off + 13], t1z = pre[pre_off + 14];
1057
+ const t2x = pre[pre_off + 15], t2y = pre[pre_off + 16], t2z = pre[pre_off + 17];
1058
+
1059
+ const k_n = invMA + invMB
1060
+ + angular_jacobian_contribution(ss, baseA, rax, ray, raz, nx, ny, nz, scratch_inertia_a)
1061
+ + angular_jacobian_contribution(ss, baseB, rbx, rby, rbz, nx, ny, nz, scratch_inertia_b);
1062
+ const k_t1 = invMA + invMB
1063
+ + angular_jacobian_contribution(ss, baseA, rax, ray, raz, t1x, t1y, t1z, scratch_inertia_a)
1064
+ + angular_jacobian_contribution(ss, baseB, rbx, rby, rbz, t1x, t1y, t1z, scratch_inertia_b);
1065
+ const k_t2 = invMA + invMB
1066
+ + angular_jacobian_contribution(ss, baseA, rax, ray, raz, t2x, t2y, t2z, scratch_inertia_a)
1067
+ + angular_jacobian_contribution(ss, baseB, rbx, rby, rbz, t2x, t2y, t2z, scratch_inertia_b);
1068
+
1069
+ const k_n_eff = k_n + spook_eps;
1070
+ pre[pre_off + 18] = k_n_eff > 0 ? 1 / k_n_eff : 0;
1071
+ pre[pre_off + 19] = k_t1 > 0 ? 1 / k_t1 : 0;
1072
+ pre[pre_off + 20] = k_t2 > 0 ? 1 / k_t2 : 0;
1073
+
1074
+ if (g_contacts_soft) {
1075
+ // Soft mode: lane 22 carries separation (see refresh_contacts).
1076
+ pre[pre_off + 22] = depth > PENETRATION_SLOP ? -(depth - PENETRATION_SLOP) : 0;
1077
+ } else {
1078
+ let bias_position = 0;
1079
+
1080
+ if (depth > PENETRATION_SLOP) {
1081
+ bias_position = -spook_a * (depth - PENETRATION_SLOP);
1082
+
1083
+ if (bias_position < -MAX_POSITION_BIAS) {
1084
+ bias_position = -MAX_POSITION_BIAS;
1085
+ }
1086
+
1087
+ }
1088
+
1089
+ pre[pre_off + 22] = bias_position;
1090
+ }
1091
+ }
1092
+ }
1093
+
1094
+ /**
1095
+ * Stage 3 (per substep) — velocity iterations enforcing pure
1096
+ * non-penetration (`vn → 0`) plus Coulomb-disk friction. No bias: depth
1097
+ * correction is the position pass, restitution is the one-shot pass.
1098
+ *
1099
+ * @param {ManifoldStore} manifolds
1100
+ * @param {PhysicsSystem} system
1101
+ * @param {number} iters
1102
+ */
1103
+ export function solve_velocity(manifolds, system, iters, use_bias = true) {
1104
+ const contact_count = g_contact_count;
1105
+ if (contact_count === 0) return;
1106
+
1107
+ const data = manifolds.data_buffer;
1108
+ const pre = scratch_pre;
1109
+ const idx = scratch_idx;
1110
+ const mus = scratch_mu;
1111
+ const ss = system.__solver_state.data;
1112
+
1113
+ for (let iter = 0; iter < iters; iter++) {
1114
+ for (let ci = 0; ci < contact_count; ci++) {
1115
+
1116
+ const slot = idx[ci * INDEX_STRIDE];
1117
+ const cidx = idx[ci * INDEX_STRIDE + 1];
1118
+
1119
+ const baseA = idx[ci * INDEX_STRIDE + 2] * SBS_STRIDE;
1120
+ const baseB = idx[ci * INDEX_STRIDE + 3] * SBS_STRIDE;
1121
+
1122
+ const invMA = ss[baseA + SBS_INV_MASS];
1123
+ const invMB = ss[baseB + SBS_INV_MASS];
1124
+
1125
+ const pre_off = ci * PRE_STRIDE;
1126
+
1127
+ const rax = pre[pre_off + 6], ray = pre[pre_off + 7], raz = pre[pre_off + 8];
1128
+ const rbx = pre[pre_off + 9], rby = pre[pre_off + 10], rbz = pre[pre_off + 11];
1129
+
1130
+ const t1x = pre[pre_off + 12], t1y = pre[pre_off + 13], t1z = pre[pre_off + 14];
1131
+ const t2x = pre[pre_off + 15], t2y = pre[pre_off + 16], t2z = pre[pre_off + 17];
1132
+
1133
+ const m_eff_n = pre[pre_off + 18];
1134
+ const m_eff_t1 = pre[pre_off + 19];
1135
+ const m_eff_t2 = pre[pre_off + 20];
1136
+
1137
+ const slot_off = manifolds.slot_data_offset(slot);
1138
+ const off = slot_off + cidx * CONTACT_STRIDE;
1139
+ const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
1140
+
1141
+ const vAx_at = ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * raz - ss[baseA + SBS_AV_Z] * ray;
1142
+ const vAy_at = ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rax - ss[baseA + SBS_AV_X] * raz;
1143
+ const vAz_at = ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * ray - ss[baseA + SBS_AV_Y] * rax;
1144
+
1145
+ const vBx_at = ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rbz - ss[baseB + SBS_AV_Z] * rby;
1146
+ const vBy_at = ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rbx - ss[baseB + SBS_AV_X] * rbz;
1147
+ const vBz_at = ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rby - ss[baseB + SBS_AV_Y] * rbx;
1148
+
1149
+ const dvx = vAx_at - vBx_at;
1150
+ const dvy = vAy_at - vBy_at;
1151
+ const dvz = vAz_at - vBz_at;
1152
+
1153
+ // --- Normal impulse (non-penetration: drive vn 0) ---
1154
+ // Soft form (Box2D v3, bias-free hybrid): the mass scale damps
1155
+ // the velocity gain and the impulse-decay term bleeds the
1156
+ // accumulator a critically-over-damped spring that converges
1157
+ // asymptotically instead of ringing (the hard form's resting
1158
+ // limit cycle). soft lanes are (1, 0) when contactHertz = 0,
1159
+ // which reduces this row bit-exactly to the legacy hard form.
1160
+ // Bias pass (use_bias): soft scales + a penetration-bias push
1161
+ // (massScale*biasRate*separation, clamped like the old position
1162
+ // bias). Relax pass (!use_bias): scales (1, 0), zero bias —
1163
+ // strips the momentum the bias injected, Box2D-v3 substep
1164
+ // order. Hard mode (contactHertz = 0): lanes are (1, 0, 0), so
1165
+ // the bias pass IS the legacy row bit for bit and no relax runs.
1166
+ let soft_mass_scale = 1;
1167
+ let soft_imp_scale = 0;
1168
+ let velocity_bias = 0;
1169
+ if (use_bias) {
1170
+ soft_mass_scale = pre[pre_off + 23];
1171
+ soft_imp_scale = pre[pre_off + 24];
1172
+ if (soft_imp_scale !== 0) {
1173
+ const sep = pre[pre_off + 22]; // separation in soft mode
1174
+ if (sep < 0) {
1175
+ const push = soft_mass_scale * pre[pre_off + 25] * sep;
1176
+ velocity_bias = push > -MAX_POSITION_BIAS ? push : -MAX_POSITION_BIAS;
1177
+ }
1178
+ }
1179
+ }
1180
+ const vn = dvx * nx + dvy * ny + dvz * nz;
1181
+ const j_n_accum = data[off + 10];
1182
+ const lambda_n = -m_eff_n * (soft_mass_scale * vn + velocity_bias) - soft_imp_scale * j_n_accum;
1183
+ const sum_n = j_n_accum + lambda_n;
1184
+ const new_j_n = sum_n > 0 ? sum_n : 0;
1185
+ const delta_j_n = new_j_n - j_n_accum;
1186
+ data[off + 10] = new_j_n;
1187
+
1188
+ if (new_j_n > scratch_max_jn[ci]) {
1189
+ scratch_max_jn[ci] = new_j_n;
1190
+ }
1191
+
1192
+ if (delta_j_n !== 0) {
1193
+ const Pnx = nx * delta_j_n;
1194
+ const Pny = ny * delta_j_n;
1195
+ const Pnz = nz * delta_j_n;
1196
+
1197
+ apply_impulse_to_body(ss, baseA, invMA, rax, ray, raz, Pnx, Pny, Pnz, +1, scratch_inertia_a);
1198
+ apply_impulse_to_body(ss, baseB, invMB, rbx, rby, rbz, Pnx, Pny, Pnz, -1, scratch_inertia_b);
1199
+ }
1200
+
1201
+ // --- Friction impulse (Coulomb disk in tangent plane) ---
1202
+ // Re-read velocity from the solver state: the normal impulse above
1203
+ // just mutated it, and friction must see the corrected velocity.
1204
+ const vAx2 = ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * raz - ss[baseA + SBS_AV_Z] * ray;
1205
+ const vAy2 = ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rax - ss[baseA + SBS_AV_X] * raz;
1206
+ const vAz2 = ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * ray - ss[baseA + SBS_AV_Y] * rax;
1207
+
1208
+ const vBx2 = ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rbz - ss[baseB + SBS_AV_Z] * rby;
1209
+ const vBy2 = ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rbx - ss[baseB + SBS_AV_X] * rbz;
1210
+ const vBz2 = ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rby - ss[baseB + SBS_AV_Y] * rbx;
1211
+
1212
+ const dvx2 = vAx2 - vBx2;
1213
+ const dvy2 = vAy2 - vBy2;
1214
+ const dvz2 = vAz2 - vBz2;
1215
+
1216
+ const vt1 = dvx2 * t1x + dvy2 * t1y + dvz2 * t1z;
1217
+ const vt2 = dvx2 * t2x + dvy2 * t2y + dvz2 * t2z;
1218
+
1219
+ const j_t1_accum = data[off + 11];
1220
+ const j_t2_accum = data[off + 12];
1221
+
1222
+ const lambda_t1 = -m_eff_t1 * vt1;
1223
+ const lambda_t2 = -m_eff_t2 * vt2;
1224
+
1225
+ const want_t1 = j_t1_accum + lambda_t1;
1226
+ const want_t2 = j_t2_accum + lambda_t2;
1227
+
1228
+ const max_friction = mus[ci] * new_j_n;
1229
+
1230
+ friction_cone_clamp(scratch_clamp, 0, want_t1, want_t2, max_friction);
1231
+
1232
+ const new_j_t1 = scratch_clamp[0];
1233
+ const new_j_t2 = scratch_clamp[1];
1234
+
1235
+ const delta_t1 = new_j_t1 - j_t1_accum;
1236
+ const delta_t2 = new_j_t2 - j_t2_accum;
1237
+
1238
+ data[off + 11] = new_j_t1;
1239
+ data[off + 12] = new_j_t2;
1240
+
1241
+ if (delta_t1 !== 0 || delta_t2 !== 0) {
1242
+
1243
+ const Ptx = t1x * delta_t1 + t2x * delta_t2;
1244
+ const Pty = t1y * delta_t1 + t2y * delta_t2;
1245
+ const Ptz = t1z * delta_t1 + t2z * delta_t2;
1246
+
1247
+ apply_impulse_to_body(ss, baseA, invMA, rax, ray, raz, Ptx, Pty, Ptz, +1, scratch_inertia_a);
1248
+ apply_impulse_to_body(ss, baseB, invMB, rbx, rby, rbz, Ptx, Pty, Ptz, -1, scratch_inertia_b);
1249
+
1250
+ }
1251
+ }
1252
+ }
1253
+ }
1254
+
1255
+ /**
1256
+ * Stage 4 (once, after the substep loop) — one-shot restitution
1257
+ * (Box2D-v3 `b2ApplyRestitution`, Catto 2018). Drives `vn → -e · vn_approach`
1258
+ * exactly once per closing contact, gated on (a) the contact having been
1259
+ * closing faster than the threshold at prepare, and (b) a compressive
1260
+ * normal impulse having formed during the velocity solve. The added impulse
1261
+ * accumulates into the same normal-impulse slot so it composes with
1262
+ * warm-start next frame.
1263
+ *
1264
+ * @param {ManifoldStore} manifolds
1265
+ * @param {PhysicsSystem} system
1266
+ */
1267
+ export function apply_restitution(manifolds, system) {
1268
+ const contact_count = g_contact_count;
1269
+ if (contact_count === 0) return;
1270
+
1271
+ const data = manifolds.data_buffer;
1272
+ const pre = scratch_pre;
1273
+ const idx = scratch_idx;
1274
+ const ss = system.__solver_state.data;
1275
+
1276
+ for (let ci = 0; ci < contact_count; ci++) {
1277
+ const pre_off = ci * PRE_STRIDE;
1278
+ const rest_bias = pre[pre_off + 21];
1279
+ if (rest_bias === 0) continue;
1280
+
1281
+ const slot = idx[ci * INDEX_STRIDE];
1282
+ const cidx = idx[ci * INDEX_STRIDE + 1];
1283
+ // Gate on the running max normal impulse, not the end-of-loop value:
1284
+ // a transient collision relaxes back to j_n 0 under per-substep
1285
+ // warm-start, but it WAS compressive, so it should still bounce.
1286
+ if (scratch_max_jn[ci] <= 0) continue;
1287
+
1288
+ const slot_off = manifolds.slot_data_offset(slot);
1289
+ const off = slot_off + cidx * CONTACT_STRIDE;
1290
+
1291
+ const j_n_accum = data[off + 10];
1292
+
1293
+ const baseA = idx[ci * INDEX_STRIDE + 2] * SBS_STRIDE;
1294
+ const baseB = idx[ci * INDEX_STRIDE + 3] * SBS_STRIDE;
1295
+ const invMA = ss[baseA + SBS_INV_MASS];
1296
+ const invMB = ss[baseB + SBS_INV_MASS];
1297
+
1298
+ const rax = pre[pre_off + 6], ray = pre[pre_off + 7], raz = pre[pre_off + 8];
1299
+ const rbx = pre[pre_off + 9], rby = pre[pre_off + 10], rbz = pre[pre_off + 11];
1300
+ const m_eff_n = pre[pre_off + 18];
1301
+ const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
1302
+
1303
+ const vAx_at = ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * raz - ss[baseA + SBS_AV_Z] * ray;
1304
+ const vAy_at = ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rax - ss[baseA + SBS_AV_X] * raz;
1305
+ const vAz_at = ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * ray - ss[baseA + SBS_AV_Y] * rax;
1306
+ const vBx_at = ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rbz - ss[baseB + SBS_AV_Z] * rby;
1307
+ const vBy_at = ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rbx - ss[baseB + SBS_AV_X] * rbz;
1308
+ const vBz_at = ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rby - ss[baseB + SBS_AV_Y] * rbx;
1309
+ const vn = (vAx_at - vBx_at) * nx + (vAy_at - vBy_at) * ny + (vAz_at - vBz_at) * nz;
1310
+
1311
+ const lambda = -m_eff_n * (vn + rest_bias);
1312
+ const new_j_n = (j_n_accum + lambda) > 0 ? (j_n_accum + lambda) : 0;
1313
+ const delta = new_j_n - j_n_accum;
1314
+ data[off + 10] = new_j_n;
1315
+
1316
+ if (delta !== 0) {
1317
+ const Px = nx * delta, Py = ny * delta, Pz = nz * delta;
1318
+ apply_impulse_to_body(ss, baseA, invMA, rax, ray, raz, Px, Py, Pz, +1, scratch_inertia_a);
1319
+ apply_impulse_to_body(ss, baseB, invMB, rbx, rby, rbz, Px, Py, Pz, -1, scratch_inertia_b);
1320
+ }
1321
+ }
1322
+ }
1323
+
1324
+ /**
1325
+ * Stage 5 (per substep) — split-impulse position correction. Normal-only
1326
+ * (friction in the pseudo-velocity pass is ill-defined). Reads the per-body
1327
+ * pseudo-velocity, applies a clamped normal impulse driven by the refreshed
1328
+ * `bias_position`, and writes the increment back into the pseudo buffer. The
1329
+ * pose integrator folds pseudo-velocity into `pos += v · dt` and discards it.
1330
+ *
1331
+ * The pseudo buffer must be zeroed by the caller before this stage each
1332
+ * substep (it's a per-substep correction). The position accumulator
1333
+ * `scratch_pos_jn` is likewise reset here per substep.
1334
+ *
1335
+ * @param {ManifoldStore} manifolds
1336
+ * @param {PhysicsSystem} system
1337
+ * @param {number} pos_iters
1338
+ */
1339
+ export function solve_position(manifolds, system, pos_iters) {
1340
+ const contact_count = g_contact_count;
1341
+ if (contact_count === 0) return;
1342
+
1343
+ const data = manifolds.data_buffer;
1344
+ const pre = scratch_pre;
1345
+ const idx = scratch_idx;
1346
+ const pos_jn = scratch_pos_jn;
1347
+ const pseudoVel = system.__pseudo_velocity;
1348
+ const ss = system.__solver_state.data;
1349
+
1350
+ // Reset the position-impulse accumulator for this substep.
1351
+ for (let ci = 0; ci < contact_count; ci++) pos_jn[ci] = 0;
1352
+
1353
+ for (let iter = 0; iter < pos_iters; iter++) {
1354
+ for (let ci = 0; ci < contact_count; ci++) {
1355
+ const pre_off = ci * PRE_STRIDE;
1356
+ const bias_p = pre[pre_off + 22];
1357
+ if (bias_p === 0) continue;
1358
+
1359
+ const slot = idx[ci * INDEX_STRIDE];
1360
+ const cidx = idx[ci * INDEX_STRIDE + 1];
1361
+ const idxA = idx[ci * INDEX_STRIDE + 2];
1362
+ const idxB = idx[ci * INDEX_STRIDE + 3];
1363
+ const sbaseA = idxA * SBS_STRIDE;
1364
+ const sbaseB = idxB * SBS_STRIDE;
1365
+ const invMA = ss[sbaseA + SBS_INV_MASS];
1366
+ const invMB = ss[sbaseB + SBS_INV_MASS];
1367
+
1368
+ const rax = pre[pre_off + 6], ray = pre[pre_off + 7], raz = pre[pre_off + 8];
1369
+ const rbx = pre[pre_off + 9], rby = pre[pre_off + 10], rbz = pre[pre_off + 11];
1370
+ const m_eff_n = pre[pre_off + 18];
1371
+
1372
+ const slot_off = manifolds.slot_data_offset(slot);
1373
+ const off = slot_off + cidx * CONTACT_STRIDE;
1374
+ const nx = data[off + 6], ny = data[off + 7], nz = data[off + 8];
1375
+
1376
+ const baseA = idxA * PSEUDO_STRIDE;
1377
+ const baseB = idxB * PSEUDO_STRIDE;
1378
+
1379
+ const pslA_x = pseudoVel[baseA], pslA_y = pseudoVel[baseA + 1], pslA_z = pseudoVel[baseA + 2];
1380
+ const psaA_x = pseudoVel[baseA + 3], psaA_y = pseudoVel[baseA + 4], psaA_z = pseudoVel[baseA + 5];
1381
+ const pslB_x = pseudoVel[baseB], pslB_y = pseudoVel[baseB + 1], pslB_z = pseudoVel[baseB + 2];
1382
+ const psaB_x = pseudoVel[baseB + 3], psaB_y = pseudoVel[baseB + 4], psaB_z = pseudoVel[baseB + 5];
1383
+
1384
+ const psvAx_at = pslA_x + psaA_y * raz - psaA_z * ray;
1385
+ const psvAy_at = pslA_y + psaA_z * rax - psaA_x * raz;
1386
+ const psvAz_at = pslA_z + psaA_x * ray - psaA_y * rax;
1387
+ const psvBx_at = pslB_x + psaB_y * rbz - psaB_z * rby;
1388
+ const psvBy_at = pslB_y + psaB_z * rbx - psaB_x * rbz;
1389
+ const psvBz_at = pslB_z + psaB_x * rby - psaB_y * rbx;
1390
+
1391
+ const psdvx = psvAx_at - psvBx_at;
1392
+ const psdvy = psvAy_at - psvBy_at;
1393
+ const psdvz = psvAz_at - psvBz_at;
1394
+ const psvn = psdvx * nx + psdvy * ny + psdvz * nz;
1395
+
1396
+ const jn_accum = pos_jn[ci];
1397
+ const lambda = -m_eff_n * (psvn + bias_p);
1398
+ const sum = jn_accum + lambda;
1399
+ const new_jn = sum > 0 ? sum : 0;
1400
+ const delta = new_jn - jn_accum;
1401
+ pos_jn[ci] = new_jn;
1402
+
1403
+ if (delta !== 0) {
1404
+ const Px = nx * delta;
1405
+ const Py = ny * delta;
1406
+ const Pz = nz * delta;
1407
+ apply_impulse_to_pseudo(pseudoVel, baseA, ss, sbaseA, invMA, rax, ray, raz, Px, Py, Pz, +1, scratch_inertia_a);
1408
+ apply_impulse_to_pseudo(pseudoVel, baseB, ss, sbaseB, invMB, rbx, rby, rbz, Px, Py, Pz, -1, scratch_inertia_b);
1409
+ }
1410
+ }
1411
+ }
1412
+ }
1413
+
1414
+ /**
1415
+ * Convenience single-step driver: gather → prepare → refresh → velocity →
1416
+ * position → restitution → scatter, all at the full `dt` (one substep). The
1417
+ * substepped path in `PhysicsSystem.fixedUpdate` calls the stages directly;
1418
+ * this entry point exists for callers/tests that want a one-shot solve.
1419
+ *
1420
+ * Self-contained: the solver stages read and write the data-oriented
1421
+ * {@link SolverBodyState}, so the driver mirrors every body it will touch
1422
+ * (the awake set plus the island contacts' endpoints) before solving and
1423
+ * scatters the solved velocities back onto the `RigidBody` components after
1424
+ * — without this a standalone call would solve against whatever stale state
1425
+ * the LAST `fixedUpdate` gathered and discard its own result.
1426
+ *
1427
+ * Stage order matches `fixedUpdate`: restitution after the position pass.
1428
+ * (Within a single call the two are independent — position writes
1429
+ * pseudo-velocity, restitution writes real velocity — so the order is
1430
+ * consistency, not behaviour.)
1431
+ *
1432
+ * The position pass writes `system.__pseudo_velocity`; the caller must zero
1433
+ * that buffer before this call and fold it into the pose afterwards.
1434
+ *
1435
+ * @param {ManifoldStore} manifolds
1436
+ * @param {PhysicsSystem} system
1437
+ * @param {number} dt
1438
+ * @param {number} [iters]
1439
+ * @param {number} [pos_iters]
1440
+ */
1441
+ export function solve_contacts(manifolds, system, dt,
1442
+ iters = DEFAULT_VELOCITY_ITERATIONS,
1443
+ pos_iters = DEFAULT_POSITION_ITERATIONS) {
1444
+ if (dt <= 0) return;
1445
+
1446
+ const storage = system.storage;
1447
+ const bodies = system.__bodies;
1448
+ const transforms = system.__transforms;
1449
+ const solver_state = system.__solver_state;
1450
+
1451
+ solver_state.begin(storage.high_water_mark);
1452
+ const awake_count = storage.awake_count;
1453
+ for (let i = 0; i < awake_count; i++) {
1454
+ const idx = storage.awake_at(i);
1455
+ solver_state.gather(idx, bodies[idx], transforms[idx]);
1456
+ }
1457
+ const range = island_slot_range(system);
1458
+ if (range !== null) {
1459
+ const slot_list = range.slot_list;
1460
+ const total_slots = range.total_slots;
1461
+ for (let i = 0; i < total_slots; i++) {
1462
+ const slot = slot_list[i];
1463
+ const ia = body_id_index(manifolds.bodyA(slot));
1464
+ const ib = body_id_index(manifolds.bodyB(slot));
1465
+ solver_state.gather(ia, bodies[ia], transforms[ia]);
1466
+ solver_state.gather(ib, bodies[ib], transforms[ib]);
1467
+ }
1468
+ }
1469
+
1470
+ if (prepare_contacts(manifolds, system, dt) !== 0) {
1471
+ redetect_concave_contacts(manifolds, system);
1472
+ refresh_contacts(manifolds, system.__transforms);
1473
+ warm_start_contacts(manifolds, system);
1474
+ solve_velocity(manifolds, system, iters);
1475
+ solve_position(manifolds, system, pos_iters);
1476
+ apply_restitution(manifolds, system);
1477
+ }
1478
+
1479
+ solver_state.scatter(bodies);
1480
+ }
1481
+
1482
+ export { DEFAULT_VELOCITY_ITERATIONS, DEFAULT_POSITION_ITERATIONS };