@woosh/meep-engine 2.165.6 → 2.165.7

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 (901) hide show
  1. package/editor/tools/v2/GizmoNode.d.ts +21 -0
  2. package/editor/tools/v2/GizmoNode.d.ts.map +1 -0
  3. package/editor/tools/v2/TransformControls.d.ts +87 -20
  4. package/editor/tools/v2/TransformControls.d.ts.map +1 -0
  5. package/editor/tools/v2/TransformControlsGizmo.d.ts +22 -0
  6. package/editor/tools/v2/TransformControlsGizmo.d.ts.map +1 -0
  7. package/editor/tools/v2/TransformControlsPlane.d.ts +6 -0
  8. package/editor/tools/v2/TransformControlsPlane.d.ts.map +1 -0
  9. package/editor/tools/v2/TranslateHelperGeometry.d.ts +6 -0
  10. package/editor/tools/v2/TranslateHelperGeometry.d.ts.map +1 -0
  11. package/package.json +1 -1
  12. package/src/core/binary/data_view/DataType2DataViewReaders.d.ts +8 -12
  13. package/src/core/binary/data_view/DataType2DataViewReaders.d.ts.map +1 -1
  14. package/src/core/binary/data_view/DataType2DataViewWriters.d.ts +8 -12
  15. package/src/core/binary/data_view/DataType2DataViewWriters.d.ts.map +1 -1
  16. package/src/core/binary/data_view/DataViewStructAccessor.d.ts +39 -18
  17. package/src/core/binary/type/BinaryDataType.d.ts +15 -15
  18. package/src/core/binary/type/DataType2TypedArrayConstructorMapping.d.ts +7 -11
  19. package/src/core/binary/type/DataType2TypedArrayConstructorMapping.d.ts.map +1 -1
  20. package/src/core/binary/type/DataTypeByteSizes.d.ts +7 -13
  21. package/src/core/binary/type/DataTypeByteSizes.d.ts.map +1 -1
  22. package/src/core/binary/type/DataTypeIndices.d.ts +8 -11
  23. package/src/core/binary/type/DataTypeIndices.d.ts.map +1 -1
  24. package/src/core/cache/Cache.d.ts +226 -56
  25. package/src/core/cache/Cache.d.ts.map +1 -1
  26. package/src/core/cache/LoadingCache.d.ts +72 -23
  27. package/src/core/collection/Stack.d.ts +2 -2
  28. package/src/core/collection/array/array_copy.d.ts +12 -7
  29. package/src/core/collection/array/computeHashArray.d.ts +9 -1
  30. package/src/core/collection/array/isArrayEqualStrict.d.ts +8 -1
  31. package/src/core/collection/array/isArraysEqualWithComparator.d.ts +10 -1
  32. package/src/core/collection/array/iterator/ArrayIteratorRandom.d.ts +3 -1
  33. package/src/core/collection/array/iterator/ArrayIteratorRandom.d.ts.map +1 -1
  34. package/src/core/collection/list/List.d.ts +334 -45
  35. package/src/core/collection/list/List.d.ts.map +1 -1
  36. package/src/core/collection/list/ListForwarder.d.ts +37 -18
  37. package/src/core/collection/map/HashMap.d.ts +199 -40
  38. package/src/core/collection/queue/Deque.d.ts +113 -37
  39. package/src/core/collection/set/ArraySet.d.ts +87 -26
  40. package/src/core/collection/set/HashSet.d.ts +66 -33
  41. package/src/core/collection/set/HashSet.d.ts.map +1 -1
  42. package/src/core/collection/table/bind/TableRecord.d.ts +21 -20
  43. package/src/core/color/Color.d.ts +1 -2
  44. package/src/core/color/Color.d.ts.map +1 -1
  45. package/src/core/events/signal/Signal.d.ts +210 -57
  46. package/src/core/events/signal/signal_filter.d.ts +1 -1
  47. package/src/core/fsm/simple/SimpleStateMachine.d.ts +83 -27
  48. package/src/core/fsm/simple/SimpleStateMachineDescription.d.ts +90 -21
  49. package/src/core/geom/2d/aabb/AABB2.d.ts +335 -51
  50. package/src/core/geom/2d/quad-tree/QuadTreeDatum.d.ts +1 -6
  51. package/src/core/geom/2d/quad-tree/QuadTreeDatum.d.ts.map +1 -1
  52. package/src/core/geom/2d/quad-tree/QuadTreeNode.d.ts +1 -2
  53. package/src/core/geom/2d/quad-tree/QuadTreeNode.d.ts.map +1 -1
  54. package/src/core/geom/3d/SurfacePoint3.d.ts +62 -17
  55. package/src/core/geom/3d/aabb/aabb3_build_corners.d.ts +13 -1
  56. package/src/core/geom/3d/frustum/read_three_planes_to_array.d.ts +7 -3
  57. package/src/core/geom/3d/line/line3_compute_nearest_point_to_point.d.ts +7 -6
  58. package/src/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.d.ts +16 -6
  59. package/src/core/geom/Quaternion.d.ts +1 -2
  60. package/src/core/geom/Quaternion.d.ts.map +1 -1
  61. package/src/core/geom/Vector1.d.ts +1 -2
  62. package/src/core/geom/Vector1.d.ts.map +1 -1
  63. package/src/core/geom/Vector2.d.ts +1 -2
  64. package/src/core/geom/Vector2.d.ts.map +1 -1
  65. package/src/core/geom/Vector3.d.ts +1 -2
  66. package/src/core/geom/Vector3.d.ts.map +1 -1
  67. package/src/core/geom/Vector4.d.ts +1 -1
  68. package/src/core/geom/Vector4.d.ts.map +1 -1
  69. package/src/core/graph/v2/Graph.d.ts +4 -4
  70. package/src/core/graph/v2/Graph.d.ts.map +1 -1
  71. package/src/core/json/resolvePathByArray.d.ts +10 -1
  72. package/src/core/math/clamp.d.ts +9 -1
  73. package/src/core/math/epsilonEquals.d.ts +9 -1
  74. package/src/core/math/interval/NumericInterval.d.ts +1 -2
  75. package/src/core/math/interval/NumericInterval.d.ts.map +1 -1
  76. package/src/core/math/noise/create_simplex_noise_2d.d.ts +7 -1
  77. package/src/core/math/random/seededRandom.d.ts +3 -1
  78. package/src/core/math/remap.d.ts +12 -5
  79. package/src/core/math/sign.d.ts +10 -1
  80. package/src/core/model/BooleanVector3.d.ts +77 -28
  81. package/src/core/model/BooleanVector3.d.ts.map +1 -1
  82. package/src/core/model/BoundedValue.d.ts +5 -5
  83. package/src/core/model/BoundedValue.d.ts.map +1 -1
  84. package/src/core/model/ModuleRegistry.d.ts +45 -3
  85. package/src/core/model/ObservedBoolean.d.ts +1 -2
  86. package/src/core/model/ObservedBoolean.d.ts.map +1 -1
  87. package/src/core/model/ObservedEnum.d.ts +91 -7
  88. package/src/core/model/ObservedEnum.d.ts.map +1 -1
  89. package/src/core/model/ObservedString.d.ts +1 -2
  90. package/src/core/model/ObservedString.d.ts.map +1 -1
  91. package/src/core/model/ObservedValue.d.ts +1 -1
  92. package/src/core/model/ObservedValue.d.ts.map +1 -1
  93. package/src/core/model/node-graph/Connection.d.ts +57 -12
  94. package/src/core/model/node-graph/NodeGraph.d.ts +2 -2
  95. package/src/core/model/node-graph/node/NodeDescription.d.ts +166 -15
  96. package/src/core/model/node-graph/node/NodeDescription.d.ts.map +1 -1
  97. package/src/core/model/node-graph/node/NodeInstance.d.ts +181 -22
  98. package/src/core/model/node-graph/node/NodeInstance.d.ts.map +1 -1
  99. package/src/core/model/node-graph/node/NodeInstancePortReference.d.ts +82 -3
  100. package/src/core/model/node-graph/node/Port.d.ts +72 -3
  101. package/src/core/model/node-graph/node/Port.d.ts.map +1 -1
  102. package/src/core/model/node-graph/node/PortDirection.d.ts +7 -5
  103. package/src/core/model/node-graph/type/DataType.d.ts +61 -4
  104. package/src/core/model/node-graph/type/ParametricDataType.d.ts +0 -1
  105. package/src/core/model/node-graph/type/ParametricDataType.d.ts.map +1 -1
  106. package/src/core/model/object/ImmutableObjectPool.d.ts +1 -2
  107. package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
  108. package/src/core/model/object/invokeObjectEquals.d.ts +9 -5
  109. package/src/core/model/object/invokeObjectHash.d.ts +7 -5
  110. package/src/core/model/object/objectDeepEquals.d.ts +10 -1
  111. package/src/core/model/object/objectKeyByValue.d.ts +10 -1
  112. package/src/core/model/object/read_property.d.ts +11 -1
  113. package/src/core/model/object/write_property.d.ts +10 -1
  114. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.d.ts +6 -0
  115. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.d.ts.map +1 -1
  116. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.js +12 -12
  117. package/src/core/model/reactive/js/compileReactiveToJS.d.ts.map +1 -1
  118. package/src/core/model/reactive/js/compileReactiveToJS.js +35 -1
  119. package/src/core/model/reactive/model/ReactiveBinaryExpression.d.ts +1 -3
  120. package/src/core/model/reactive/model/ReactiveBinaryExpression.d.ts.map +1 -1
  121. package/src/core/model/reactive/model/ReactiveExpression.d.ts +63 -10
  122. package/src/core/model/reactive/model/ReactiveExpression.d.ts.map +1 -1
  123. package/src/core/model/reactive/model/ReactiveUnaryExpression.d.ts +0 -12
  124. package/src/core/model/reactive/model/ReactiveUnaryExpression.d.ts.map +1 -1
  125. package/src/core/model/reactive/model/arithmetic/ReactiveAdd.d.ts +1 -1
  126. package/src/core/model/reactive/model/arithmetic/ReactiveAdd.d.ts.map +1 -1
  127. package/src/core/model/reactive/model/arithmetic/ReactiveDivide.d.ts +1 -1
  128. package/src/core/model/reactive/model/arithmetic/ReactiveDivide.d.ts.map +1 -1
  129. package/src/core/model/reactive/model/arithmetic/ReactiveMultiply.d.ts +1 -1
  130. package/src/core/model/reactive/model/arithmetic/ReactiveMultiply.d.ts.map +1 -1
  131. package/src/core/model/reactive/model/arithmetic/ReactiveNegate.d.ts +1 -1
  132. package/src/core/model/reactive/model/arithmetic/ReactiveNegate.d.ts.map +1 -1
  133. package/src/core/model/reactive/model/arithmetic/ReactiveSubtract.d.ts +1 -1
  134. package/src/core/model/reactive/model/arithmetic/ReactiveSubtract.d.ts.map +1 -1
  135. package/src/core/model/reactive/model/comparative/ReactiveEquals.d.ts +1 -1
  136. package/src/core/model/reactive/model/comparative/ReactiveEquals.d.ts.map +1 -1
  137. package/src/core/model/reactive/model/comparative/ReactiveGreaterThan.d.ts +1 -1
  138. package/src/core/model/reactive/model/comparative/ReactiveGreaterThan.d.ts.map +1 -1
  139. package/src/core/model/reactive/model/comparative/ReactiveGreaterThanOrEqual.d.ts +1 -1
  140. package/src/core/model/reactive/model/comparative/ReactiveGreaterThanOrEqual.d.ts.map +1 -1
  141. package/src/core/model/reactive/model/comparative/ReactiveLessThan.d.ts +1 -1
  142. package/src/core/model/reactive/model/comparative/ReactiveLessThan.d.ts.map +1 -1
  143. package/src/core/model/reactive/model/comparative/ReactiveLessThanOrEqual.d.ts +1 -1
  144. package/src/core/model/reactive/model/comparative/ReactiveLessThanOrEqual.d.ts.map +1 -1
  145. package/src/core/model/reactive/model/comparative/ReactiveNotEquals.d.ts +1 -1
  146. package/src/core/model/reactive/model/comparative/ReactiveNotEquals.d.ts.map +1 -1
  147. package/src/core/model/reactive/model/logic/ReactiveAnd.d.ts +41 -5
  148. package/src/core/model/reactive/model/logic/ReactiveAnd.d.ts.map +1 -1
  149. package/src/core/model/reactive/model/logic/ReactiveNot.d.ts +1 -1
  150. package/src/core/model/reactive/model/logic/ReactiveNot.d.ts.map +1 -1
  151. package/src/core/model/reactive/model/logic/ReactiveOr.d.ts +2 -1
  152. package/src/core/model/reactive/model/logic/ReactiveOr.d.ts.map +1 -1
  153. package/src/core/model/reactive/model/terminal/ReactiveLiteralBoolean.d.ts +2 -1
  154. package/src/core/model/reactive/model/terminal/ReactiveLiteralBoolean.d.ts.map +1 -1
  155. package/src/core/model/reactive/model/terminal/ReactiveLiteralNumber.d.ts +2 -3
  156. package/src/core/model/reactive/model/terminal/ReactiveLiteralNumber.d.ts.map +1 -1
  157. package/src/core/model/reactive/model/terminal/ReactiveLiteralString.d.ts +2 -4
  158. package/src/core/model/reactive/model/terminal/ReactiveLiteralString.d.ts.map +1 -1
  159. package/src/core/model/reactive/model/terminal/ReactiveReference.d.ts +68 -11
  160. package/src/core/model/reactive/model/terminal/ReactiveReference.d.ts.map +1 -1
  161. package/src/core/model/stat/LinearModifier.d.ts +79 -24
  162. package/src/core/parser/simple/DataType.d.ts +13 -5
  163. package/src/core/parser/simple/Token.d.ts +52 -13
  164. package/src/core/parser/simple/readArrayLiteral.d.ts +1 -0
  165. package/src/core/parser/simple/readArrayLiteral.d.ts.map +1 -1
  166. package/src/core/parser/simple/readBooleanToken.d.ts +1 -0
  167. package/src/core/parser/simple/readBooleanToken.d.ts.map +1 -1
  168. package/src/core/parser/simple/readHexToken.d.ts +1 -0
  169. package/src/core/parser/simple/readHexToken.d.ts.map +1 -1
  170. package/src/core/parser/simple/readIdentifierToken.d.ts +1 -0
  171. package/src/core/parser/simple/readIdentifierToken.d.ts.map +1 -1
  172. package/src/core/parser/simple/readNumberToken.d.ts +1 -0
  173. package/src/core/parser/simple/readNumberToken.d.ts.map +1 -1
  174. package/src/core/parser/simple/readReferenceToken.d.ts +1 -0
  175. package/src/core/parser/simple/readReferenceToken.d.ts.map +1 -1
  176. package/src/core/parser/simple/readStringToken.d.ts +1 -0
  177. package/src/core/parser/simple/readStringToken.d.ts.map +1 -1
  178. package/src/core/parser/simple/readUnsignedIntegerToken.d.ts +1 -0
  179. package/src/core/parser/simple/readUnsignedIntegerToken.d.ts.map +1 -1
  180. package/src/core/process/BaseProcess.d.ts +17 -5
  181. package/src/core/process/BaseProcess.d.ts.map +1 -1
  182. package/src/core/process/CompositeProcess.d.ts +0 -1
  183. package/src/core/process/CompositeProcess.d.ts.map +1 -1
  184. package/src/core/process/action/AsynchronousAction.d.ts +3 -3
  185. package/src/core/process/executor/ConcurrentExecutor.d.ts +3 -3
  186. package/src/core/process/filter/MutableFilter.d.ts +1 -1
  187. package/src/core/process/filter/MutableFilter.d.ts.map +1 -1
  188. package/src/core/process/task/Task.d.ts +1 -1
  189. package/src/core/process/task/Task.d.ts.map +1 -1
  190. package/src/core/process/task/TaskGroup.d.ts +3 -3
  191. package/src/core/process/task/TaskSignal.d.ts +8 -6
  192. package/src/core/process/undo/Action.d.ts +31 -9
  193. package/src/core/process/undo/ActionGroup.d.ts +20 -5
  194. package/src/core/process/undo/ActionProcessor.d.ts +163 -27
  195. package/src/core/process/undo/ActionProcessor.d.ts.map +1 -1
  196. package/src/engine/Engine.d.ts +178 -42
  197. package/src/engine/Engine.d.ts.map +1 -1
  198. package/src/engine/EngineConfiguration.d.ts +74 -20
  199. package/src/engine/achievements/AchievementGateway.d.ts +17 -5
  200. package/src/engine/achievements/AchievementManager.d.ts +0 -1
  201. package/src/engine/achievements/AchievementManager.d.ts.map +1 -1
  202. package/src/engine/animation/Tween.d.ts +1 -1
  203. package/src/engine/animation/clip/AnimationClip.d.ts +24 -4
  204. package/src/engine/animation/clip/AnimationClipBinding.d.ts +35 -14
  205. package/src/engine/animation/curve/actionProcessorOperations/curveActions.d.ts +2 -2
  206. package/src/engine/animation/curve/actionProcessorOperations/curveActions.d.ts.map +1 -1
  207. package/src/engine/animation/curve/editor/createKeyframeMarker.d.ts +1 -1
  208. package/src/engine/animation/curve/editor/createKeyframeMarker.d.ts.map +1 -1
  209. package/src/engine/animation/keyed2/AnimationTrackPlayback.d.ts +1 -1
  210. package/src/engine/animation/keyed2/behavior/AnimationBehavior.d.ts +0 -1
  211. package/src/engine/animation/keyed2/behavior/AnimationBehavior.d.ts.map +1 -1
  212. package/src/engine/asset/Asset.d.ts +47 -7
  213. package/src/engine/asset/AssetManager.d.ts +258 -60
  214. package/src/engine/asset/AssetRequestScope.d.ts +27 -3
  215. package/src/engine/asset/AssetTransformer.d.ts +16 -5
  216. package/src/engine/asset/CORS/CrossOriginConfig.d.ts +8 -5
  217. package/src/engine/asset/CORS/CrossOriginConfig.d.ts.map +1 -1
  218. package/src/engine/asset/CORS/CrossOriginKind.d.ts +5 -4
  219. package/src/engine/asset/loaders/AssetLoader.d.ts +50 -12
  220. package/src/engine/asset/loaders/GLTFAssetLoader.d.ts +71 -6
  221. package/src/engine/asset/loaders/image/ImageRGBADataAsset.d.ts +0 -1
  222. package/src/engine/asset/loaders/image/ImageRGBADataAsset.d.ts.map +1 -1
  223. package/src/engine/asset/loaders/material/StaticMaterialCache.d.ts +30 -7
  224. package/src/engine/asset/loaders/material/computeMaterialEquality.d.ts +8 -3
  225. package/src/engine/asset/loaders/material/computeMaterialHash.d.ts +7 -3
  226. package/src/engine/asset/loaders/material/traverseMaterialTextures.d.ts +8 -3
  227. package/src/engine/asset/loaders/texture/TextureAssetLoader.d.ts +6 -6
  228. package/src/engine/asset/preloader/AssetPreloader.d.ts +6 -15
  229. package/src/engine/asset/preloader/AssetPreloader.d.ts.map +1 -1
  230. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +13 -13
  231. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  232. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +14 -4
  233. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  234. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts +1 -1
  235. package/src/engine/ecs/Entity.d.ts +228 -41
  236. package/src/engine/ecs/EntityBlueprint.d.ts +43 -17
  237. package/src/engine/ecs/EntityComponentDataset.d.ts +580 -60
  238. package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
  239. package/src/engine/ecs/EntityManager.d.ts +10 -9
  240. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  241. package/src/engine/ecs/EntityObserver.d.ts +80 -13
  242. package/src/engine/ecs/System.d.ts +162 -33
  243. package/src/engine/ecs/animation/AnimationSerializationAdapter.d.ts +0 -1
  244. package/src/engine/ecs/animation/AnimationSerializationAdapter.d.ts.map +1 -1
  245. package/src/engine/ecs/attachment/AttachmentSystem.d.ts +1 -1
  246. package/src/engine/ecs/attachment/AttachmentSystem.d.ts.map +1 -1
  247. package/src/engine/ecs/components/PropertySetSerializationAdapter.d.ts +0 -1
  248. package/src/engine/ecs/components/PropertySetSerializationAdapter.d.ts.map +1 -1
  249. package/src/engine/ecs/components/SerializationMetadataSerializationAdapter.d.ts +0 -1
  250. package/src/engine/ecs/components/SerializationMetadataSerializationAdapter.d.ts.map +1 -1
  251. package/src/engine/ecs/components/TagSerializationAdapter.d.ts +0 -1
  252. package/src/engine/ecs/components/TagSerializationAdapter.d.ts.map +1 -1
  253. package/src/engine/ecs/dynamic_actions/rules/DynamicRuleDescriptionTable.d.ts +1 -1
  254. package/src/engine/ecs/dynamic_actions/rules/DynamicRuleDescriptionTable.d.ts.map +1 -1
  255. package/src/engine/ecs/fow/FogOfWar.d.ts +1 -1
  256. package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts +1 -1
  257. package/src/engine/ecs/fow/FogOfWarSystem.d.ts +1 -6
  258. package/src/engine/ecs/fow/FogOfWarSystem.d.ts.map +1 -1
  259. package/src/engine/ecs/fow/serialization/FogOfWarRevealerSerializationAdapter.d.ts +0 -1
  260. package/src/engine/ecs/fow/serialization/FogOfWarRevealerSerializationAdapter.d.ts.map +1 -1
  261. package/src/engine/ecs/fow/serialization/FogOfWarSerializationAdapter.d.ts +0 -1
  262. package/src/engine/ecs/fow/serialization/FogOfWarSerializationAdapter.d.ts.map +1 -1
  263. package/src/engine/ecs/gui/GUIElement.d.ts +115 -5
  264. package/src/engine/ecs/gui/GUIElementSerializationAdapter.d.ts +0 -1
  265. package/src/engine/ecs/gui/GUIElementSerializationAdapter.d.ts.map +1 -1
  266. package/src/engine/ecs/gui/GUIElementSystem.d.ts +59 -8
  267. package/src/engine/ecs/gui/GUIElementSystem.d.ts.map +1 -1
  268. package/src/engine/ecs/gui/hud/HeadsUpDisplaySerializationAdapter.d.ts +0 -1
  269. package/src/engine/ecs/gui/hud/HeadsUpDisplaySerializationAdapter.d.ts.map +1 -1
  270. package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.d.ts +42 -6
  271. package/src/engine/ecs/gui/menu/radial/RadialContextMenu.d.ts +23 -22
  272. package/src/engine/ecs/gui/parallax/GuiElementParallaxSystem.d.ts +1 -1
  273. package/src/engine/ecs/gui/position/ViewportPositionSerializationAdapter.d.ts +0 -1
  274. package/src/engine/ecs/gui/position/ViewportPositionSerializationAdapter.d.ts.map +1 -1
  275. package/src/engine/ecs/gui/position/ViewportPositionSystem.d.ts +1 -1
  276. package/src/engine/ecs/gui/view/ViewEntity.d.ts +1 -2
  277. package/src/engine/ecs/gui/view/ViewEntity.d.ts.map +1 -1
  278. package/src/engine/ecs/guid/UUIDSerializationAdapter.d.ts +0 -1
  279. package/src/engine/ecs/guid/UUIDSerializationAdapter.d.ts.map +1 -1
  280. package/src/engine/ecs/ik/InverseKinematicsSerializationAdapter.d.ts +0 -1
  281. package/src/engine/ecs/ik/InverseKinematicsSerializationAdapter.d.ts.map +1 -1
  282. package/src/engine/ecs/ik/InverseKinematicsSystem.d.ts +1 -1
  283. package/src/engine/ecs/ik/InverseKinematicsSystem.d.ts.map +1 -1
  284. package/src/engine/ecs/name/NameSerializationAdapter.d.ts +0 -1
  285. package/src/engine/ecs/name/NameSerializationAdapter.d.ts.map +1 -1
  286. package/src/engine/ecs/parent/ChildEntities.d.ts +23 -3
  287. package/src/engine/ecs/parent/EntityNode.d.ts +157 -25
  288. package/src/engine/ecs/parent/ParentEntity.d.ts +31 -11
  289. package/src/engine/ecs/parent/ParentEntitySystem.d.ts +44 -11
  290. package/src/engine/ecs/renderable/RenderSystem.d.ts +65 -7
  291. package/src/engine/ecs/renderable/Renderable.d.ts +95 -25
  292. package/src/engine/ecs/speaker/VoiceSystem.d.ts +3 -3
  293. package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
  294. package/src/engine/ecs/storage/binary/BinaryClassSerializationAdapter.d.ts +86 -21
  295. package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.d.ts +1 -1
  296. package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.d.ts.map +1 -1
  297. package/src/engine/ecs/system/AbstractContextSystem.d.ts +1 -1
  298. package/src/engine/ecs/system_validate_class.d.ts +1 -1
  299. package/src/engine/ecs/system_validate_class.d.ts.map +1 -1
  300. package/src/engine/ecs/systems/AnimationSystem.d.ts +41 -6
  301. package/src/engine/ecs/systems/AnimationSystem.d.ts.map +1 -1
  302. package/src/engine/ecs/systems/ScriptSystem.d.ts +9 -5
  303. package/src/engine/ecs/systems/ScriptSystem.d.ts.map +1 -1
  304. package/src/engine/ecs/systems/TimerSystem.d.ts +1 -1
  305. package/src/engine/ecs/systems/TimerSystem.d.ts.map +1 -1
  306. package/src/engine/ecs/team/TeamSerializationAdapter.d.ts +0 -1
  307. package/src/engine/ecs/team/TeamSerializationAdapter.d.ts.map +1 -1
  308. package/src/engine/ecs/terrain/ecs/OffsetScaleTransform2D.d.ts +7 -6
  309. package/src/engine/ecs/terrain/ecs/Terrain.d.ts +389 -17
  310. package/src/engine/ecs/terrain/ecs/TerrainSystem.d.ts +65 -8
  311. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSerializationAdapter.d.ts +0 -1
  312. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSerializationAdapter.d.ts.map +1 -1
  313. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts +1 -1
  314. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  315. package/src/engine/ecs/terrain/serialization/TerrainSerializationAdapter.d.ts +0 -1
  316. package/src/engine/ecs/terrain/serialization/TerrainSerializationAdapter.d.ts.map +1 -1
  317. package/src/engine/ecs/terrain/tiles/TerrainTile.d.ts +2 -2
  318. package/src/engine/ecs/terrain/tiles/TerrainTile.d.ts.map +1 -1
  319. package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts +2 -2
  320. package/src/engine/ecs/tooltip/TooltipComponentSerializationAdapter.d.ts +0 -1
  321. package/src/engine/ecs/tooltip/TooltipComponentSerializationAdapter.d.ts.map +1 -1
  322. package/src/engine/ecs/tooltip/TooltipComponentSystem.d.ts +1 -1
  323. package/src/engine/ecs/transform/TransformSerializationAdapter.d.ts +0 -1
  324. package/src/engine/ecs/transform/TransformSerializationAdapter.d.ts.map +1 -1
  325. package/src/engine/ecs/transform-attachment/TransformAttachment.d.ts +116 -19
  326. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts +58 -6
  327. package/src/engine/graphics/GraphicsEngine.d.ts +220 -41
  328. package/src/engine/graphics/camera/CameraShake.d.ts +6 -6
  329. package/src/engine/graphics/camera/CameraShake.d.ts.map +1 -1
  330. package/src/engine/graphics/camera/CameraShakeBehavior.d.ts +1 -2
  331. package/src/engine/graphics/camera/CameraShakeBehavior.d.ts.map +1 -1
  332. package/src/engine/graphics/camera/CameraShakeTraumaBehavior.d.ts +1 -2
  333. package/src/engine/graphics/camera/CameraShakeTraumaBehavior.d.ts.map +1 -1
  334. package/src/engine/graphics/camera/camera_compute_distance_to_fit_length.d.ts +8 -1
  335. package/src/engine/graphics/camera/makeScreenScissorFrustum.d.ts +8 -4
  336. package/src/engine/graphics/composit/CompositLayer.d.ts +1 -1
  337. package/src/engine/graphics/ecs/animation/AnimationControllerSerializationAdapter.d.ts +0 -1
  338. package/src/engine/graphics/ecs/animation/AnimationControllerSerializationAdapter.d.ts.map +1 -1
  339. package/src/engine/graphics/ecs/animation/AnimationControllerSystem.d.ts +1 -2
  340. package/src/engine/graphics/ecs/animation/AnimationControllerSystem.d.ts.map +1 -1
  341. package/src/engine/graphics/ecs/animation/animator/AnimationGraphSystem.d.ts +1 -1
  342. package/src/engine/graphics/ecs/animation/animator/graph/AnimationGraphSerializationAdapter.d.ts +0 -1
  343. package/src/engine/graphics/ecs/animation/animator/graph/AnimationGraphSerializationAdapter.d.ts.map +1 -1
  344. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionSerializationAdapter.d.ts +0 -1
  345. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionSerializationAdapter.d.ts.map +1 -1
  346. package/src/engine/graphics/ecs/camera/Camera.d.ts +113 -18
  347. package/src/engine/graphics/ecs/camera/Camera.d.ts.map +1 -1
  348. package/src/engine/graphics/ecs/camera/CameraSystem.d.ts +74 -10
  349. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.d.ts +0 -1
  350. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.d.ts.map +1 -1
  351. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts +90 -33
  352. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSerializationAdapter.d.ts +0 -1
  353. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSerializationAdapter.d.ts.map +1 -1
  354. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts +34 -5
  355. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraLander.d.ts +0 -1
  356. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraLander.d.ts.map +1 -1
  357. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraLanderSystem.d.ts +1 -1
  358. package/src/engine/graphics/ecs/decal/v2/Decal.d.ts +52 -15
  359. package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.d.ts +0 -1
  360. package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.d.ts.map +1 -1
  361. package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.d.ts +70 -24
  362. package/src/engine/graphics/ecs/highlight/Highlight.d.ts +61 -16
  363. package/src/engine/graphics/ecs/highlight/HighlightDefinition.d.ts +57 -19
  364. package/src/engine/graphics/ecs/highlight/HighlightSerializationAdapter.d.ts +0 -1
  365. package/src/engine/graphics/ecs/highlight/HighlightSerializationAdapter.d.ts.map +1 -1
  366. package/src/engine/graphics/ecs/highlight/plugin/OutlineRenderPlugin.d.ts +0 -1
  367. package/src/engine/graphics/ecs/highlight/plugin/OutlineRenderPlugin.d.ts.map +1 -1
  368. package/src/engine/graphics/ecs/highlight/system/MeshHighlightSystem.d.ts +57 -8
  369. package/src/engine/graphics/ecs/highlight/system/MeshHighlightSystem.d.ts.map +1 -1
  370. package/src/engine/graphics/ecs/highlight/system/RenderableHighlightSystem.d.ts +62 -8
  371. package/src/engine/graphics/ecs/highlight/system/RenderableHighlightSystem.d.ts.map +1 -1
  372. package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.d.ts +68 -8
  373. package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.d.ts.map +1 -1
  374. package/src/engine/graphics/ecs/light/Light.d.ts +79 -19
  375. package/src/engine/graphics/ecs/light/Light.d.ts.map +1 -1
  376. package/src/engine/graphics/ecs/light/LightSerializationAdapter.d.ts +0 -1
  377. package/src/engine/graphics/ecs/light/LightSerializationAdapter.d.ts.map +1 -1
  378. package/src/engine/graphics/ecs/light/LightSystem.d.ts +123 -11
  379. package/src/engine/graphics/ecs/light/LightType.d.ts +8 -6
  380. package/src/engine/graphics/ecs/mesh/Mesh.d.ts +176 -30
  381. package/src/engine/graphics/ecs/mesh/MeshEvents.d.ts +6 -12
  382. package/src/engine/graphics/ecs/mesh/MeshSystem.d.ts +99 -7
  383. package/src/engine/graphics/ecs/mesh/serialization/MeshSerializationAdapter.d.ts +0 -1
  384. package/src/engine/graphics/ecs/mesh/serialization/MeshSerializationAdapter.d.ts.map +1 -1
  385. package/src/engine/graphics/ecs/mesh-v2/DrawMode.d.ts +8 -6
  386. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.d.ts +147 -32
  387. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.d.ts.map +1 -1
  388. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +37 -0
  389. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGAnimationPlayback.d.ts +6 -5
  390. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.d.ts +162 -25
  391. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshAnimationController.d.ts +40 -12
  392. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshAnimationControllerSystem.d.ts +31 -11
  393. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshEvents.d.ts +5 -12
  394. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshHighlightSystem.d.ts +36 -7
  395. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.d.ts +63 -8
  396. package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.d.ts +9 -4
  397. package/src/engine/graphics/ecs/path/PathDisplay.d.ts +21 -9
  398. package/src/engine/graphics/ecs/path/PathDisplayEvents.d.ts +6 -3
  399. package/src/engine/graphics/ecs/path/PathDisplaySpec.d.ts +28 -11
  400. package/src/engine/graphics/ecs/path/PathDisplaySystem.d.ts +55 -10
  401. package/src/engine/graphics/ecs/path/PathDisplaySystem.d.ts.map +1 -1
  402. package/src/engine/graphics/ecs/path/PathDisplayType.d.ts +8 -6
  403. package/src/engine/graphics/ecs/path/entity/EntityPathMarkerDefinition.d.ts +39 -8
  404. package/src/engine/graphics/ecs/path/entity/EntityPathStyle.d.ts +38 -12
  405. package/src/engine/graphics/ecs/path/highlight/PathDisplayHighlightSystem.d.ts +31 -7
  406. package/src/engine/graphics/ecs/path/ribbon/RibbonPathStyle.d.ts +24 -7
  407. package/src/engine/graphics/ecs/path/tube/BasicMaterialDefinition.d.ts +4 -3
  408. package/src/engine/graphics/ecs/path/tube/CapType.d.ts +7 -5
  409. package/src/engine/graphics/ecs/path/tube/MatcapMaterialDefinition.d.ts +11 -3
  410. package/src/engine/graphics/ecs/path/tube/PathNormalType.d.ts +6 -4
  411. package/src/engine/graphics/ecs/path/tube/StandardMaterialDefinition.d.ts +9 -4
  412. package/src/engine/graphics/ecs/path/tube/TubeMaterialType.d.ts +7 -6
  413. package/src/engine/graphics/ecs/path/tube/TubePathStyle.d.ts +114 -35
  414. package/src/engine/graphics/ecs/path/tube/TubePathStyle.d.ts.map +1 -1
  415. package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts +85 -6
  416. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts +1 -1
  417. package/src/engine/graphics/ecs/water/WaterSerializationAdapter.d.ts +0 -1
  418. package/src/engine/graphics/ecs/water/WaterSerializationAdapter.d.ts.map +1 -1
  419. package/src/engine/graphics/ecs/water/WaterSystem.d.ts +1 -1
  420. package/src/engine/graphics/geometry/AttributeGroupSpec.d.ts +75 -25
  421. package/src/engine/graphics/geometry/AttributeSpec.d.ts +85 -12
  422. package/src/engine/graphics/geometry/instancing/InstancedMeshGroup.d.ts +1 -2
  423. package/src/engine/graphics/geometry/instancing/InstancedMeshGroup.d.ts.map +1 -1
  424. package/src/engine/graphics/impostors/octahedral/grid/OctahedralUvEncoder.d.ts +6 -4
  425. package/src/engine/graphics/material/manager/MaterialManager.d.ts +12 -0
  426. package/src/engine/graphics/material/manager/MaterialManager.d.ts.map +1 -1
  427. package/src/engine/graphics/material/manager/MaterialManager.js +78 -41
  428. package/src/engine/graphics/particles/ecs/ParticleEmitterSystem.d.ts +55 -7
  429. package/src/engine/graphics/particles/ecs/ParticleEmitterSystem.d.ts.map +1 -1
  430. package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.d.ts +9 -9
  431. package/src/engine/graphics/particles/node-based/editor/ParticleSpecificationEditorView.d.ts +1 -2
  432. package/src/engine/graphics/particles/node-based/editor/ParticleSpecificationEditorView.d.ts.map +1 -1
  433. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts +0 -1
  434. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts.map +1 -1
  435. package/src/engine/graphics/particles/node-based/nodes/ParticleDataTypes.d.ts +10 -9
  436. package/src/engine/graphics/particles/node-based/nodes/ParticleDataTypes.d.ts.map +1 -1
  437. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts +0 -1
  438. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts.map +1 -1
  439. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts +0 -1
  440. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts.map +1 -1
  441. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts +0 -1
  442. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts.map +1 -1
  443. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts +0 -1
  444. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts.map +1 -1
  445. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts +0 -1
  446. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts.map +1 -1
  447. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts +0 -1
  448. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts.map +1 -1
  449. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts +0 -1
  450. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts.map +1 -1
  451. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts +0 -1
  452. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts.map +1 -1
  453. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts +0 -1
  454. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts.map +1 -1
  455. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts +0 -1
  456. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts.map +1 -1
  457. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts +0 -1
  458. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts.map +1 -1
  459. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts +0 -1
  460. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts.map +1 -1
  461. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts +0 -1
  462. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts.map +1 -1
  463. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts +0 -1
  464. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts.map +1 -1
  465. package/src/engine/graphics/particles/node-based/particle/ParticleAttributeSpecification.d.ts +18 -18
  466. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +252 -2
  467. package/src/engine/graphics/particles/particular/engine/emitter/serde/ParameterLookupTableSerializationAdapter.d.ts +0 -1
  468. package/src/engine/graphics/particles/particular/engine/emitter/serde/ParameterLookupTableSerializationAdapter.d.ts.map +1 -1
  469. package/src/engine/graphics/particles/particular/engine/emitter/serde/ParticleEmitterSerializationAdapter.d.ts +0 -1
  470. package/src/engine/graphics/particles/particular/engine/emitter/serde/ParticleEmitterSerializationAdapter.d.ts.map +1 -1
  471. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.d.ts +2 -15
  472. package/src/engine/graphics/particles/particular/engine/parameter/ParameterTrack.d.ts +5 -1
  473. package/src/engine/graphics/particles/particular/engine/parameter/ParameterTrack.d.ts.map +1 -1
  474. package/src/engine/graphics/particles/particular/engine/parameter/ParameterTrackSet.d.ts +1 -1
  475. package/src/engine/graphics/particles/particular/engine/parameter/ParameterTrackSet.d.ts.map +1 -1
  476. package/src/engine/graphics/particles/particular/engine/parameter/ParticleParameter.d.ts +5 -1
  477. package/src/engine/graphics/particles/particular/engine/parameter/ParticleParameter.d.ts.map +1 -1
  478. package/src/engine/graphics/particles/particular/engine/utils/volume/AttributeValue.d.ts +76 -16
  479. package/src/engine/graphics/particles/particular/engine/utils/volume/SamplingFunctionKind.d.ts +6 -4
  480. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts +92 -8
  481. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
  482. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.js +78 -37
  483. package/src/engine/graphics/render/buffer/simple-fx/taa/TemporalSupersamplingRenderPlugin.d.ts +0 -3
  484. package/src/engine/graphics/render/buffer/simple-fx/taa/TemporalSupersamplingRenderPlugin.d.ts.map +1 -1
  485. package/src/engine/graphics/render/forward_plus/model/Decal.d.ts +1 -1
  486. package/src/engine/graphics/render/forward_plus/model/Decal.d.ts.map +1 -1
  487. package/src/engine/graphics/render/forward_plus/plugin/ForwardPlusRenderingPlugin.d.ts +27 -5
  488. package/src/engine/graphics/render/forward_plus/plugin/MaterialTransformer.d.ts.map +1 -1
  489. package/src/engine/graphics/render/forward_plus/plugin/MaterialTransformer.js +4 -9
  490. package/src/engine/graphics/render/forward_plus/plugin/forwardPlusMaterialMarker.d.ts +16 -0
  491. package/src/engine/graphics/render/forward_plus/plugin/forwardPlusMaterialMarker.d.ts.map +1 -0
  492. package/src/engine/graphics/render/forward_plus/plugin/forwardPlusMaterialMarker.js +18 -0
  493. package/src/engine/graphics/render/forward_plus/plugin/isForwardPlusLightingSkipped.d.ts +16 -0
  494. package/src/engine/graphics/render/forward_plus/plugin/isForwardPlusLightingSkipped.d.ts.map +1 -0
  495. package/src/engine/graphics/render/forward_plus/plugin/isForwardPlusLightingSkipped.js +31 -0
  496. package/src/engine/graphics/render/frame_graph/FrameGraph.d.ts +1 -1
  497. package/src/engine/graphics/render/frame_graph/FrameGraph.d.ts.map +1 -1
  498. package/src/engine/graphics/render/frame_graph/resource/TextureResourceDescriptor.d.ts +1 -1
  499. package/src/engine/graphics/render/gizmo/Gizmo.d.ts +7 -3
  500. package/src/engine/graphics/render/gizmo/GizmoRenderingPlugin.d.ts +5 -5
  501. package/src/engine/graphics/render/gizmo/GizmoRenderingPlugin.d.ts.map +1 -1
  502. package/src/engine/graphics/render/gizmo/GizmoShapeRenderingInterface.d.ts +72 -13
  503. package/src/engine/graphics/render/layers/RenderLayer.d.ts +78 -22
  504. package/src/engine/graphics/render/layers/RenderLayerManager.d.ts +72 -17
  505. package/src/engine/graphics/render/layers/RenderLayerState.d.ts +18 -3
  506. package/src/engine/graphics/render/view/CameraView.d.ts +3 -3
  507. package/src/engine/graphics/render/view/CameraViewManager.d.ts +2 -2
  508. package/src/engine/graphics/render/visibility/IncrementalDeltaSet.d.ts +143 -24
  509. package/src/engine/graphics/sh3/lpg/serialization/LightProbeGridSerializationAdapter.d.ts +0 -1
  510. package/src/engine/graphics/sh3/lpg/serialization/LightProbeGridSerializationAdapter.d.ts.map +1 -1
  511. package/src/engine/graphics/sh3/lpv/PathTracerProbeRenderer.d.ts +3 -1
  512. package/src/engine/graphics/sh3/lpv/PathTracerProbeRenderer.d.ts.map +1 -1
  513. package/src/engine/graphics/sh3/lpv/serialization/LightProbeVolumeSerializationAdapter.d.ts +0 -1
  514. package/src/engine/graphics/sh3/lpv/serialization/LightProbeVolumeSerializationAdapter.d.ts.map +1 -1
  515. package/src/engine/graphics/sh3/path_tracer/material/StandardTexture.d.ts +1 -2
  516. package/src/engine/graphics/sh3/path_tracer/material/StandardTexture.d.ts.map +1 -1
  517. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts +3 -3
  518. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
  519. package/src/engine/graphics/texture/cubemap/load_environment_map.d.ts +13 -8
  520. package/src/engine/graphics/texture/sampler/Sampler2D.d.ts +1 -1
  521. package/src/engine/graphics/texture/sampler/serialization/Sampler2DSerializationAdapter.d.ts +0 -1
  522. package/src/engine/graphics/texture/sampler/serialization/Sampler2DSerializationAdapter.d.ts.map +1 -1
  523. package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts +1 -3
  524. package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts.map +1 -1
  525. package/src/engine/graphics/three/cloneObject3D.d.ts +7 -3
  526. package/src/engine/graphics/trail/tube/TubeXPlugin.d.ts +0 -1
  527. package/src/engine/graphics/trail/tube/TubeXPlugin.d.ts.map +1 -1
  528. package/src/engine/graphics/trail/x/RibbonXMaterialSpec.d.ts +13 -3
  529. package/src/engine/graphics/trail/x/RibbonXPlugin.d.ts +0 -3
  530. package/src/engine/graphics/trail/x/RibbonXPlugin.d.ts.map +1 -1
  531. package/src/engine/graphics/util/promiseMaterialLoaded.d.ts +7 -3
  532. package/src/engine/grid/grid2transform/GridPosition2TransformSerializationAdapter.d.ts +0 -1
  533. package/src/engine/grid/grid2transform/GridPosition2TransformSerializationAdapter.d.ts.map +1 -1
  534. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts +1 -1
  535. package/src/engine/grid/obstacle/GridObstacleSerializationAdapter.d.ts +0 -1
  536. package/src/engine/grid/obstacle/GridObstacleSerializationAdapter.d.ts.map +1 -1
  537. package/src/engine/grid/position/GridPositionSerializationAdapter.d.ts +0 -1
  538. package/src/engine/grid/position/GridPositionSerializationAdapter.d.ts.map +1 -1
  539. package/src/engine/grid/position/GridPositionSystem.d.ts +1 -1
  540. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts +1 -1
  541. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +717 -0
  542. package/src/engine/input/analog/apply_radial_dead_zone.d.ts +23 -0
  543. package/src/engine/input/analog/apply_radial_dead_zone.d.ts.map +1 -0
  544. package/src/engine/input/analog/apply_radial_dead_zone.js +38 -0
  545. package/src/engine/input/devices/GamepadDevice.d.ts +140 -23
  546. package/src/engine/input/devices/GamepadDevice.d.ts.map +1 -1
  547. package/src/engine/input/devices/GamepadDevice.js +202 -16
  548. package/src/engine/input/devices/InputDeviceSwitch.d.ts +44 -11
  549. package/src/engine/input/devices/InputDeviceSwitch.d.ts.map +1 -1
  550. package/src/engine/input/devices/KeyCodes.d.ts +112 -1
  551. package/src/engine/input/devices/KeyboardDevice.d.ts +2 -3
  552. package/src/engine/input/devices/KeyboardDevice.d.ts.map +1 -1
  553. package/src/engine/input/devices/PointerDevice.d.ts +96 -31
  554. package/src/engine/input/devices/PointerDevice.d.ts.map +1 -1
  555. package/src/engine/input/devices/events/KeyboardEvents.d.ts +5 -4
  556. package/src/engine/input/devices/events/MouseEvents.d.ts +18 -12
  557. package/src/engine/input/devices/events/TouchEvents.d.ts +12 -6
  558. package/src/engine/input/devices/gamepad/GamepadAxes.d.ts +5 -1
  559. package/src/engine/input/devices/gamepad/GamepadAxes.d.ts.map +1 -1
  560. package/src/engine/input/devices/gamepad/GamepadAxes.js +5 -1
  561. package/src/engine/input/devices/gamepad/GamepadHandle.d.ts +130 -26
  562. package/src/engine/input/devices/gamepad/GamepadHandle.d.ts.map +1 -1
  563. package/src/engine/input/devices/gamepad/GamepadHandle.js +60 -34
  564. package/src/engine/input/devices/gamepad/GamepadStandardMapping.d.ts +20 -0
  565. package/src/engine/input/devices/gamepad/GamepadStandardMapping.d.ts.map +1 -0
  566. package/src/engine/input/devices/gamepad/GamepadStandardMapping.js +20 -0
  567. package/src/engine/input/devices/gamepad/GamepadStick.d.ts +93 -0
  568. package/src/engine/input/devices/gamepad/GamepadStick.d.ts.map +1 -0
  569. package/src/engine/input/devices/gamepad/GamepadStick.js +165 -0
  570. package/src/engine/input/devices/gamepad/GamepadStickCalibration.d.ts +84 -0
  571. package/src/engine/input/devices/gamepad/GamepadStickCalibration.d.ts.map +1 -0
  572. package/src/engine/input/devices/gamepad/GamepadStickCalibration.js +119 -0
  573. package/src/engine/input/devices/gamepad/GamepadStickCalibrator.d.ts +92 -0
  574. package/src/engine/input/devices/gamepad/GamepadStickCalibrator.d.ts.map +1 -0
  575. package/src/engine/input/devices/gamepad/GamepadStickCalibrator.js +250 -0
  576. package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.d.ts +17 -0
  577. package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.d.ts.map +1 -0
  578. package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.js +30 -0
  579. package/src/engine/input/devices/touch/TouchDevice.d.ts +3 -3
  580. package/src/engine/input/ecs/components/Input.d.ts +49 -6
  581. package/src/engine/input/ecs/components/InputController.d.ts +33 -3
  582. package/src/engine/input/ecs/ism/InputBinding.d.ts +40 -4
  583. package/src/engine/input/ecs/systems/InputControllerSystem.d.ts +28 -6
  584. package/src/engine/input/ecs/systems/InputSystem.d.ts +59 -6
  585. package/src/engine/intelligence/behavior/Behavior.d.ts +58 -16
  586. package/src/engine/intelligence/behavior/Behavior.d.ts.map +1 -1
  587. package/src/engine/intelligence/behavior/BehaviorStatus.d.ts +10 -8
  588. package/src/engine/intelligence/behavior/SelectorBehavior.d.ts +0 -1
  589. package/src/engine/intelligence/behavior/SelectorBehavior.d.ts.map +1 -1
  590. package/src/engine/intelligence/behavior/composite/ParallelBehavior.d.ts +0 -8
  591. package/src/engine/intelligence/behavior/composite/ParallelBehavior.d.ts.map +1 -1
  592. package/src/engine/intelligence/behavior/composite/ParallelBehaviorSerializationAdapter.d.ts +0 -1
  593. package/src/engine/intelligence/behavior/composite/ParallelBehaviorSerializationAdapter.d.ts.map +1 -1
  594. package/src/engine/intelligence/behavior/composite/SequenceBehavior.d.ts +0 -1
  595. package/src/engine/intelligence/behavior/composite/SequenceBehavior.d.ts.map +1 -1
  596. package/src/engine/intelligence/behavior/composite/SequenceBehaviorSerializationAdapter.d.ts +0 -1
  597. package/src/engine/intelligence/behavior/composite/SequenceBehaviorSerializationAdapter.d.ts.map +1 -1
  598. package/src/engine/intelligence/behavior/decorator/AbstractDecoratorBehavior.d.ts +0 -1
  599. package/src/engine/intelligence/behavior/decorator/AbstractDecoratorBehavior.d.ts.map +1 -1
  600. package/src/engine/intelligence/behavior/decorator/IgnoreFailureBehavior.d.ts +1 -2
  601. package/src/engine/intelligence/behavior/decorator/IgnoreFailureBehavior.d.ts.map +1 -1
  602. package/src/engine/intelligence/behavior/decorator/InvertStatusBehavior.d.ts +1 -2
  603. package/src/engine/intelligence/behavior/decorator/InvertStatusBehavior.d.ts.map +1 -1
  604. package/src/engine/intelligence/behavior/decorator/InvertStatusBehaviorSerializationAdapter.d.ts +0 -1
  605. package/src/engine/intelligence/behavior/decorator/InvertStatusBehaviorSerializationAdapter.d.ts.map +1 -1
  606. package/src/engine/intelligence/behavior/decorator/OverrideContextBehavior.d.ts +1 -1
  607. package/src/engine/intelligence/behavior/decorator/OverrideContextBehavior.d.ts.map +1 -1
  608. package/src/engine/intelligence/behavior/decorator/RepeatBehavior.d.ts +1 -2
  609. package/src/engine/intelligence/behavior/decorator/RepeatBehavior.d.ts.map +1 -1
  610. package/src/engine/intelligence/behavior/decorator/RepeatBehaviorSerializationAdapter.d.ts +0 -1
  611. package/src/engine/intelligence/behavior/decorator/RepeatBehaviorSerializationAdapter.d.ts.map +1 -1
  612. package/src/engine/intelligence/behavior/decorator/RepeatUntilSuccessBehavior.d.ts +1 -2
  613. package/src/engine/intelligence/behavior/decorator/RepeatUntilSuccessBehavior.d.ts.map +1 -1
  614. package/src/engine/intelligence/behavior/ecs/BehaviorComponentSerializationAdapter.d.ts +0 -1
  615. package/src/engine/intelligence/behavior/ecs/BehaviorComponentSerializationAdapter.d.ts.map +1 -1
  616. package/src/engine/intelligence/behavior/ecs/BehaviorSystem.d.ts +1 -1
  617. package/src/engine/intelligence/behavior/ecs/DieBehavior.d.ts +1 -2
  618. package/src/engine/intelligence/behavior/ecs/DieBehavior.d.ts.map +1 -1
  619. package/src/engine/intelligence/behavior/ecs/EntityBehavior.d.ts +0 -1
  620. package/src/engine/intelligence/behavior/ecs/EntityBehavior.d.ts.map +1 -1
  621. package/src/engine/intelligence/behavior/ecs/KillBehavior.d.ts +1 -2
  622. package/src/engine/intelligence/behavior/ecs/KillBehavior.d.ts.map +1 -1
  623. package/src/engine/intelligence/behavior/ecs/OrbitingBehavior.d.ts +1 -2
  624. package/src/engine/intelligence/behavior/ecs/OrbitingBehavior.d.ts.map +1 -1
  625. package/src/engine/intelligence/behavior/ecs/SendEventBehavior.d.ts +1 -2
  626. package/src/engine/intelligence/behavior/ecs/SendEventBehavior.d.ts.map +1 -1
  627. package/src/engine/intelligence/behavior/ecs/WaitForEventBehavior.d.ts +1 -2
  628. package/src/engine/intelligence/behavior/ecs/WaitForEventBehavior.d.ts.map +1 -1
  629. package/src/engine/intelligence/behavior/ecs/WaitForEventBehaviorSerializationAdapter.d.ts +0 -1
  630. package/src/engine/intelligence/behavior/ecs/WaitForEventBehaviorSerializationAdapter.d.ts.map +1 -1
  631. package/src/engine/intelligence/behavior/primitive/ActionBehavior.d.ts +1 -2
  632. package/src/engine/intelligence/behavior/primitive/ActionBehavior.d.ts.map +1 -1
  633. package/src/engine/intelligence/behavior/primitive/FailingBehavior.d.ts +1 -3
  634. package/src/engine/intelligence/behavior/primitive/FailingBehavior.d.ts.map +1 -1
  635. package/src/engine/intelligence/behavior/primitive/FailingBehaviorSerializationAdapter.d.ts +0 -1
  636. package/src/engine/intelligence/behavior/primitive/FailingBehaviorSerializationAdapter.d.ts.map +1 -1
  637. package/src/engine/intelligence/behavior/primitive/PromiseBehavior.d.ts +2 -4
  638. package/src/engine/intelligence/behavior/primitive/PromiseBehavior.d.ts.map +1 -1
  639. package/src/engine/intelligence/behavior/primitive/SucceedingBehavior.d.ts +1 -3
  640. package/src/engine/intelligence/behavior/primitive/SucceedingBehavior.d.ts.map +1 -1
  641. package/src/engine/intelligence/behavior/primitive/SucceedingBehaviorSerializationAdapter.d.ts +0 -1
  642. package/src/engine/intelligence/behavior/primitive/SucceedingBehaviorSerializationAdapter.d.ts.map +1 -1
  643. package/src/engine/intelligence/behavior/selector/WeightedRandomBehavior.d.ts +1 -2
  644. package/src/engine/intelligence/behavior/selector/WeightedRandomBehavior.d.ts.map +1 -1
  645. package/src/engine/intelligence/behavior/util/BranchBehavior.d.ts +1 -3
  646. package/src/engine/intelligence/behavior/util/BranchBehavior.d.ts.map +1 -1
  647. package/src/engine/intelligence/behavior/util/ConditionBehavior.d.ts +1 -2
  648. package/src/engine/intelligence/behavior/util/ConditionBehavior.d.ts.map +1 -1
  649. package/src/engine/intelligence/behavior/util/DelayBehavior.d.ts +1 -3
  650. package/src/engine/intelligence/behavior/util/DelayBehavior.d.ts.map +1 -1
  651. package/src/engine/intelligence/behavior/util/LogMessageBehavior.d.ts +1 -2
  652. package/src/engine/intelligence/behavior/util/LogMessageBehavior.d.ts.map +1 -1
  653. package/src/engine/intelligence/behavior/util/RandomDelayBehavior.d.ts +4 -4
  654. package/src/engine/intelligence/behavior/util/RandomDelayBehavior.d.ts.map +1 -1
  655. package/src/engine/intelligence/behavior/util/RotationBehavior.d.ts +1 -2
  656. package/src/engine/intelligence/behavior/util/RotationBehavior.d.ts.map +1 -1
  657. package/src/engine/intelligence/blackboard/AbstractBlackboard.d.ts +58 -15
  658. package/src/engine/intelligence/blackboard/AbstractBlackboard.d.ts.map +1 -1
  659. package/src/engine/intelligence/blackboard/Blackboard.d.ts +79 -11
  660. package/src/engine/intelligence/blackboard/Blackboard.d.ts.map +1 -1
  661. package/src/engine/intelligence/blackboard/BlackboardSerializationAdapter.d.ts +0 -1
  662. package/src/engine/intelligence/blackboard/BlackboardSerializationAdapter.d.ts.map +1 -1
  663. package/src/engine/intelligence/blackboard/BlackboardValue.d.ts +1 -0
  664. package/src/engine/intelligence/blackboard/BlackboardValue.d.ts.map +1 -1
  665. package/src/engine/intelligence/controller/graph/StateGraphSystem.d.ts +1 -3
  666. package/src/engine/intelligence/controller/graph/StateGraphSystem.d.ts.map +1 -1
  667. package/src/engine/interpolation/InterpolationSystem.d.ts +1 -5
  668. package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -1
  669. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +0 -1
  670. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -1
  671. package/src/engine/knowledge/database/StaticKnowledgeDataTable.d.ts +140 -9
  672. package/src/engine/knowledge/database/StaticKnowledgeDataTableDescriptor.d.ts +26 -7
  673. package/src/engine/knowledge/database/StaticKnowledgeDatabase.d.ts +2 -3
  674. package/src/engine/knowledge/database/StaticKnowledgeDatabase.d.ts.map +1 -1
  675. package/src/engine/navigation/ecs/components/InterpolationType.d.ts +8 -4
  676. package/src/engine/navigation/ecs/components/Path.d.ts +167 -47
  677. package/src/engine/navigation/ecs/components/PathEvents.d.ts +8 -6
  678. package/src/engine/navigation/ecs/components/PathSerializationAdapter.d.ts +0 -1
  679. package/src/engine/navigation/ecs/components/PathSerializationAdapter.d.ts.map +1 -1
  680. package/src/engine/navigation/ecs/path_following/PathFollower.d.ts +85 -45
  681. package/src/engine/navigation/ecs/path_following/PathFollowerEventType.d.ts +5 -3
  682. package/src/engine/navigation/ecs/path_following/PathFollowerFlags.d.ts +14 -20
  683. package/src/engine/navigation/ecs/path_following/PathFollowerSerializationAdapter.d.ts +0 -1
  684. package/src/engine/navigation/ecs/path_following/PathFollowerSerializationAdapter.d.ts.map +1 -1
  685. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.d.ts +31 -6
  686. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.d.ts.map +1 -1
  687. package/src/engine/network/adapters/TransformReplicationAdapter.d.ts +0 -1
  688. package/src/engine/network/adapters/TransformReplicationAdapter.d.ts.map +1 -1
  689. package/src/engine/network/ecs/NetworkSystem.d.ts +1 -1
  690. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.d.ts +0 -1
  691. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.d.ts.map +1 -1
  692. package/src/engine/options/Option.d.ts +3 -3
  693. package/src/engine/options/OptionsView.d.ts +1 -2
  694. package/src/engine/options/OptionsView.d.ts.map +1 -1
  695. package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -1
  696. package/src/engine/physics/body/SolverBodyState.js +10 -1
  697. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  698. package/src/engine/physics/constraint/solve_constraints.js +840 -830
  699. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +3 -3
  700. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -1
  701. package/src/engine/physics/ecs/ColliderSerializationAdapter.d.ts +0 -1
  702. package/src/engine/physics/ecs/ColliderSerializationAdapter.d.ts.map +1 -1
  703. package/src/engine/physics/ecs/JointSerializationAdapter.d.ts +0 -1
  704. package/src/engine/physics/ecs/JointSerializationAdapter.d.ts.map +1 -1
  705. package/src/engine/physics/ecs/PhysicsSystem.d.ts +14 -2
  706. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  707. package/src/engine/physics/ecs/PhysicsSystem.js +2246 -2172
  708. package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts +0 -1
  709. package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts.map +1 -1
  710. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +12 -8
  711. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts.map +1 -1
  712. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +8 -4
  713. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts.map +1 -1
  714. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  715. package/src/engine/physics/integration/integrate_velocity.js +25 -2
  716. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  717. package/src/engine/physics/narrowphase/capsule_contacts.js +622 -617
  718. package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -1
  719. package/src/engine/physics/narrowphase/convex_convex_manifold.js +632 -568
  720. package/src/engine/physics/solver/solve_contacts.d.ts +6 -5
  721. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  722. package/src/engine/physics/solver/solve_contacts.js +1506 -1482
  723. package/src/engine/platform/EnginePlatform.d.ts +24 -14
  724. package/src/engine/platform/WebEnginePlatform.d.ts +20 -5
  725. package/src/engine/platform/WebEnginePlatform.d.ts.map +1 -1
  726. package/src/engine/plugin/EnginePlugin.d.ts +36 -3
  727. package/src/engine/plugin/EnginePlugin.d.ts.map +1 -1
  728. package/src/engine/plugin/EnginePluginManager.d.ts +85 -16
  729. package/src/engine/plugin/EnginePluginManager.d.ts.map +1 -1
  730. package/src/engine/plugin/PluginReferenceContext.d.ts +1 -1
  731. package/src/engine/reference/v2/Reference.d.ts +55 -17
  732. package/src/engine/reference/v2/Reference.d.ts.map +1 -1
  733. package/src/engine/save/GameStateLoader.d.ts +0 -1
  734. package/src/engine/save/GameStateLoader.d.ts.map +1 -1
  735. package/src/engine/save/Storage.d.ts +75 -15
  736. package/src/engine/save/storage/InMemoryStorage.d.ts +0 -1
  737. package/src/engine/save/storage/InMemoryStorage.d.ts.map +1 -1
  738. package/src/engine/save/storage/IndexedDBStorage.d.ts +0 -2
  739. package/src/engine/save/storage/IndexedDBStorage.d.ts.map +1 -1
  740. package/src/engine/scene/Scene.d.ts +65 -7
  741. package/src/engine/scene/SceneManager.d.ts +125 -25
  742. package/src/engine/simulation/Ticker.d.ts +1 -2
  743. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  744. package/src/engine/sound/SoundEngine.d.ts +46 -3
  745. package/src/engine/sound/ecs/SoundController.d.ts +0 -1
  746. package/src/engine/sound/ecs/SoundController.d.ts.map +1 -1
  747. package/src/engine/sound/ecs/SoundControllerSystem.d.ts +1 -2
  748. package/src/engine/sound/ecs/SoundControllerSystem.d.ts.map +1 -1
  749. package/src/engine/sound/ecs/SoundListener.d.ts +0 -1
  750. package/src/engine/sound/ecs/SoundListener.d.ts.map +1 -1
  751. package/src/engine/sound/ecs/SoundListenerSystem.d.ts +1 -1
  752. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts +1 -1
  753. package/src/engine/sound/ecs/emitter/SoundEmitter.d.ts +1 -1
  754. package/src/engine/sound/ecs/emitter/SoundEmitter.d.ts.map +1 -1
  755. package/src/engine/sound/ecs/emitter/SoundEmitterSerializationAdapter.d.ts +0 -1
  756. package/src/engine/sound/ecs/emitter/SoundEmitterSerializationAdapter.d.ts.map +1 -1
  757. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts +1 -1
  758. package/src/engine/sound/ecs/emitter/SoundTrack.d.ts +1 -2
  759. package/src/engine/sound/ecs/emitter/SoundTrack.d.ts.map +1 -1
  760. package/src/engine/sound/simulation/core/AcousticMediumSerializationAdapter.d.ts +0 -1
  761. package/src/engine/sound/simulation/core/AcousticMediumSerializationAdapter.d.ts.map +1 -1
  762. package/src/engine/sound/simulation/definition/AcousticDirectivitySerializationAdapter.d.ts +0 -1
  763. package/src/engine/sound/simulation/definition/AcousticDirectivitySerializationAdapter.d.ts.map +1 -1
  764. package/src/engine/sound/simulation/definition/AcousticMaterialSerializationAdapter.d.ts +0 -1
  765. package/src/engine/sound/simulation/definition/AcousticMaterialSerializationAdapter.d.ts.map +1 -1
  766. package/src/engine/sound/simulation/ecs/AcousticBodySerializationAdapter.d.ts +0 -1
  767. package/src/engine/sound/simulation/ecs/AcousticBodySerializationAdapter.d.ts.map +1 -1
  768. package/src/engine/sound/simulation/ecs/AcousticProbeFieldSystem.d.ts +1 -1
  769. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.d.ts +1 -1
  770. package/src/engine/sound/simulation/ecs/AcousticVolumeSerializationAdapter.d.ts +0 -1
  771. package/src/engine/sound/simulation/ecs/AcousticVolumeSerializationAdapter.d.ts.map +1 -1
  772. package/src/engine/sound/simulation/ecs/AcousticVolumeSystem.d.ts +1 -1
  773. package/src/engine/sound/simulation/probe/AcousticProbeFieldSerializationAdapter.d.ts +0 -1
  774. package/src/engine/sound/simulation/probe/AcousticProbeFieldSerializationAdapter.d.ts.map +1 -1
  775. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.d.ts +0 -1
  776. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.d.ts.map +1 -1
  777. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.d.ts +0 -1
  778. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.d.ts.map +1 -1
  779. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts +0 -1
  780. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts.map +1 -1
  781. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.d.ts +0 -1
  782. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.d.ts.map +1 -1
  783. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.d.ts +0 -1
  784. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.d.ts.map +1 -1
  785. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts +0 -1
  786. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts.map +1 -1
  787. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.d.ts +0 -1
  788. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.d.ts.map +1 -1
  789. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.d.ts +0 -1
  790. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.d.ts.map +1 -1
  791. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.d.ts +0 -1
  792. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.d.ts.map +1 -1
  793. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.d.ts +0 -1
  794. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.d.ts.map +1 -1
  795. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.d.ts +0 -1
  796. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.d.ts.map +1 -1
  797. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.d.ts +0 -1
  798. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.d.ts.map +1 -1
  799. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.d.ts +0 -1
  800. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.d.ts.map +1 -1
  801. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.d.ts +0 -1
  802. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.d.ts.map +1 -1
  803. package/src/engine/sound/sopra/runtime/EventInstance.d.ts +1 -2
  804. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
  805. package/src/engine/sound/sopra/runtime/ParameterStore.d.ts +1 -2
  806. package/src/engine/sound/sopra/runtime/ParameterStore.d.ts.map +1 -1
  807. package/src/engine/ui/bindings/DomElementProcessorManager.d.ts +0 -3
  808. package/src/engine/ui/bindings/DomElementProcessorManager.d.ts.map +1 -1
  809. package/src/engine/ui/modal/ModalStack.d.ts +3 -3
  810. package/src/engine/ui/notification/AnimatedObjectEmitter.d.ts +2 -2
  811. package/src/engine/ui/notification/ViewEmitter.d.ts +2 -2
  812. package/src/generation/filtering/CellFilter.d.ts +1 -1
  813. package/src/generation/filtering/CellFilter.d.ts.map +1 -1
  814. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.d.ts +3 -1
  815. package/src/generation/markers/actions/placement/MarkerNodeEntityProcessorRandomRotation.d.ts.map +1 -1
  816. package/src/generation/markers/actions/probability/MarkerNodeActionSelectWeighted.d.ts +3 -1
  817. package/src/generation/markers/actions/probability/MarkerNodeActionSelectWeighted.d.ts.map +1 -1
  818. package/src/generation/markers/transform/MarkerNodeTransformRotateRandom.d.ts +3 -1
  819. package/src/generation/markers/transform/MarkerNodeTransformRotateRandom.d.ts.map +1 -1
  820. package/src/generation/markers/transform/MarkerNodeTransformerRecordPropertyClosure.d.ts +3 -1
  821. package/src/generation/markers/transform/MarkerNodeTransformerRecordPropertyClosure.d.ts.map +1 -1
  822. package/src/generation/markers/transform/MarkerNodeTransformerRecordUniqueRandomEnum.d.ts +3 -1
  823. package/src/generation/markers/transform/MarkerNodeTransformerRecordUniqueRandomEnum.d.ts.map +1 -1
  824. package/src/generation/placement/action/util/CellMatcherWithinAABB.d.ts +1 -1
  825. package/src/generation/placement/action/util/CellMatcherWithinAABB.d.ts.map +1 -1
  826. package/src/misc/makeMatcapSelectionOption.d.ts +11 -13
  827. package/src/view/View.d.ts +313 -30
  828. package/src/view/asset/AssetLoaderStatusView.d.ts +1 -1
  829. package/src/view/asset/PreloaderView.d.ts +1 -1
  830. package/src/view/common/LabelView.d.ts +2 -1
  831. package/src/view/common/LabelView.d.ts.map +1 -1
  832. package/src/view/common/ListView.d.ts +2 -2
  833. package/src/view/common/ListView.d.ts.map +1 -1
  834. package/src/view/common/TabbedView.d.ts +1 -1
  835. package/src/view/common/VirtualListView.d.ts +3 -3
  836. package/src/view/common/VirtualListView.d.ts.map +1 -1
  837. package/src/view/common/dnd/Draggable.d.ts +2 -2
  838. package/src/view/common/dnd/DropTarget.d.ts +6 -6
  839. package/src/view/controller/GuiController.d.ts +1 -2
  840. package/src/view/controller/GuiController.d.ts.map +1 -1
  841. package/src/view/controller/controls/BooleanVector3Control.d.ts +1 -2
  842. package/src/view/controller/controls/BooleanVector3Control.d.ts.map +1 -1
  843. package/src/view/controller/controls/NativeListController.d.ts +1 -1
  844. package/src/view/controller/controls/Vector1Control.d.ts +1 -1
  845. package/src/view/currency/CompositeCurrencyLabelView.d.ts +1 -1
  846. package/src/view/currency/CurrencyDenominationLabelView.d.ts +1 -1
  847. package/src/view/elements/BottomLeftResizeHandleView.d.ts +1 -1
  848. package/src/view/elements/CanvasView.d.ts +1 -2
  849. package/src/view/elements/CanvasView.d.ts.map +1 -1
  850. package/src/view/elements/CheckboxView.d.ts +1 -1
  851. package/src/view/elements/CompassArrowView.d.ts +1 -2
  852. package/src/view/elements/CompassArrowView.d.ts.map +1 -1
  853. package/src/view/elements/ConfirmationDialogView.d.ts +1 -1
  854. package/src/view/elements/DropDownSelectionView.d.ts +1 -1
  855. package/src/view/elements/EmptyView.d.ts +1 -2
  856. package/src/view/elements/EmptyView.d.ts.map +1 -1
  857. package/src/view/elements/Group.d.ts +1 -2
  858. package/src/view/elements/Group.d.ts.map +1 -1
  859. package/src/view/elements/MeshPreview.d.ts +1 -1
  860. package/src/view/elements/SimpleWindow.d.ts +1 -1
  861. package/src/view/elements/button/ButtonView.d.ts +1 -1
  862. package/src/view/elements/image/ImageView.d.ts +1 -2
  863. package/src/view/elements/image/ImageView.d.ts.map +1 -1
  864. package/src/view/elements/image/SvgImageView.d.ts +1 -1
  865. package/src/view/elements/label/LabeledValueView.d.ts +1 -1
  866. package/src/view/elements/navigation/ViewStack.d.ts +1 -8
  867. package/src/view/elements/navigation/ViewStack.d.ts.map +1 -1
  868. package/src/view/elements/notify/ToastLogView.d.ts +1 -1
  869. package/src/view/elements/progress/RectangularPieProgressView.d.ts +1 -1
  870. package/src/view/elements/progress/SmoothProgressBar.d.ts +1 -1
  871. package/src/view/elements/progress/segmented/SegmentedResourceBarView.d.ts +1 -1
  872. package/src/view/elements/radial/RadialMenu.d.ts +1 -1
  873. package/src/view/elements/radial/RadialMenuElement.d.ts +1 -1
  874. package/src/view/elements/radial/RadialMenuElementDefinition.d.ts +125 -39
  875. package/src/view/elements/radial/RadialProgressView.d.ts +1 -1
  876. package/src/view/elements/radial/RadialText.d.ts +1 -1
  877. package/src/view/elements/tiles2d/Tile.d.ts +1 -1
  878. package/src/view/elements/tiles2d/TileGrid.d.ts +4 -5
  879. package/src/view/elements/tiles2d/TileGrid.d.ts.map +1 -1
  880. package/src/view/elements/video/VideoView.d.ts +2 -2
  881. package/src/view/elements/video/VideoView.d.ts.map +1 -1
  882. package/src/view/elements/windrose/WindRoseDiagram.d.ts +1 -1
  883. package/src/view/game/achievements/AchievementNotificationView.d.ts +1 -1
  884. package/src/view/math/FunctionGraphView.d.ts +1 -1
  885. package/src/view/minimap/Minimap.d.ts +1 -1
  886. package/src/view/minimap/dom/MinimapCameraView.d.ts +1 -1
  887. package/src/view/minimap/dom/MinimapMarkerView.d.ts +1 -1
  888. package/src/view/minimap/dom/MinimapTerrainView.d.ts +1 -1
  889. package/src/view/task/TaskProgressView.d.ts +1 -1
  890. package/src/view/tooltip/DomTooltipObserver.d.ts +2 -2
  891. package/src/view/tooltip/TooltipView.d.ts +1 -1
  892. package/src/view/tooltip/gml/parser/parseTooltipString.d.ts +1 -0
  893. package/src/view/tooltip/gml/parser/parseTooltipString.d.ts.map +1 -1
  894. package/src/view/tooltip/gml/parser/readReferenceToken.d.ts +1 -0
  895. package/src/view/tooltip/gml/parser/readReferenceToken.d.ts.map +1 -1
  896. package/src/view/tooltip/gml/parser/readReferenceValueToken.d.ts +2 -1
  897. package/src/view/tooltip/gml/parser/readReferenceValueToken.d.ts.map +1 -1
  898. package/src/view/tooltip/gml/parser/readStyleToken.d.ts +1 -0
  899. package/src/view/tooltip/gml/parser/readStyleToken.d.ts.map +1 -1
  900. package/editor/ecs/component/TypeSchema.d.ts +0 -38
  901. package/editor/tools/v2/BlenderCameraOrientationGizmo.d.ts +0 -50
@@ -1,2172 +1,2246 @@
1
- import { assert } from "../../../core/assert.js";
2
- import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
3
- import { BVH } from "../../../core/bvh2/bvh3/BVH.js";
4
- import { bvh_query_user_data_intersects_aabb } from "../../../core/bvh2/bvh3/query/bvh_query_user_data_intersects_aabb.js";
5
- import { returnTrue } from "../../../core/function/returnTrue.js";
6
- import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
7
- import Vector3 from "../../../core/geom/Vector3.js";
8
- import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js";
9
- import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
10
- import { System } from "../../ecs/System.js";
11
- import { Transform } from "../../ecs/transform/Transform.js";
12
- import { Interpolated } from "../../interpolation/Interpolated.js";
13
- import { body_id_index, BodyStorage } from "../body/BodyStorage.js";
14
- import { SBS_STRIDE, SolverBodyState } from "../body/SolverBodyState.js";
15
- import { compute_world_aabb, fatten_world_aabb } from "../broadphase/compute_fat_world_aabb.js";
16
- import { generate_pairs } from "../broadphase/generate_pairs.js";
17
- import { PairList } from "../broadphase/PairList.js";
18
- import { ccd_resolve } from "../ccd/linear_sweep.js";
19
- import { solve_joints } from "../constraint/solve_constraints.js";
20
- import { ManifoldStore } from "../contact/ManifoldStore.js";
21
- import { ContactEventBuffer, ContactEventKind } from "../events/ContactEventBuffer.js";
22
- import { diff_manifolds } from "../events/diff_manifolds.js";
23
- import { world_inverse_inertia_apply } from "../inertia/world_inverse_inertia.js";
24
- import { integrate_position } from "../integration/integrate_position.js";
25
- import { integrate_velocity_forces, integrate_velocity_gravity } from "../integration/integrate_velocity.js";
26
- import { IslandBuilder } from "../island/IslandBuilder.js";
27
- import { narrowphase_step } from "../narrowphase/narrowphase_step.js";
28
- import { overlap_shape as overlap_shape_query } from "../queries/overlap_shape.js";
29
- import { raycast as raycast_query } from "../queries/raycast.js";
30
- import { shape_cast as shape_cast_query } from "../queries/shape_cast.js";
31
- import {
32
- apply_restitution,
33
- prepare_contacts,
34
- redetect_concave_contacts,
35
- refresh_contacts,
36
- solve_position,
37
- solve_velocity,
38
- warm_start_contacts,
39
- } from "../solver/solve_contacts.js";
40
- import { BodyKind } from "./BodyKind.js";
41
- import { Collider, COLLIDER_UNBOUND } from "./Collider.js";
42
- import { find_non_finite_physics_state } from "./find_non_finite_physics_state.js";
43
- import { JOINT_UNALLOCATED, JOINT_WORLD } from "./Joint.js";
44
- import { PhysicsEvents } from "./PhysicsEvents.js";
45
- import { RIGID_BODY_UNALLOCATED, RigidBody } from "./RigidBody.js";
46
- import { RigidBodyFlags } from "./RigidBodyFlags.js";
47
- import { SleepState } from "./SleepState.js";
48
-
49
- /**
50
- * Scratch for {@link applyImpulseAt}'s angular delta calculation.
51
- * @type {Float64Array}
52
- */
53
- const scratch_angular_delta = new Float64Array(3);
54
-
55
- /**
56
- * Scratch for {@link applyImpulseAt}'s LockRot*-masked inverse-inertia
57
- * diagonal (the same mask {@link SolverBodyState#gather} applies).
58
- * @type {{x: number, y: number, z: number}}
59
- */
60
- const scratch_locked_inertia = { x: 0, y: 0, z: 0 };
61
-
62
- /**
63
- * Reusable scratch buffer for world-AABB construction so the link path is
64
- * allocation-free in steady state.
65
- * @type {Float64Array}
66
- */
67
- const scratch_world_aabb = new Float64Array(6);
68
-
69
- /**
70
- * Reusable scratch buffer for the local AABB returned by
71
- * {@link AbstractShape3D#compute_bounding_box}.
72
- * @type {Float64Array}
73
- */
74
- const scratch_local_aabb = new Float64Array(6);
75
-
76
- /**
77
- * Candidate buffer for {@link PhysicsSystem#__wake_sleepers_overlapping_leaf}.
78
- * Grows by doubling: a large removed support (a level floor) can overlap many
79
- * sleeping bodies, all of which must wake.
80
- * @type {Uint32Array}
81
- */
82
- let scratch_wake_candidates = new Uint32Array(64);
83
-
84
- /**
85
- * Scratch for a leaf's currently-stored fat AABB during the stage-2
86
- * containment test (refit skip).
87
- * @type {Float64Array}
88
- */
89
- const scratch_stored_aabb = new Float64Array(6);
90
-
91
- /**
92
- * Scratch boxes for {@link PhysicsSystem#__swept_proximity} — the mover's
93
- * velocity-swept tight AABB and the sleeper's tight AABB.
94
- * @type {Float64Array}
95
- */
96
- const scratch_prox_mover = new Float64Array(6);
97
- const scratch_prox_sleeper = new Float64Array(6);
98
-
99
- /**
100
- * Rigid-body physics system.
101
- *
102
- * v1 scope: pool + active list + two BVHs (static / dynamic), Transform sync
103
- * contract, and the user-facing API surface (gravity, force / impulse, layer
104
- * filtering, wake / sleep, contact filter). The per-step simulation pipeline
105
- * (broadphase pair generation, narrowphase, solver, islands, sleep test,
106
- * contact-event emission) is built on top of this skeleton in subsequent
107
- * iterations.
108
- *
109
- * Dependency tuple is `(RigidBody, Collider, Transform)` — every body has
110
- * exactly one collider on the same entity. Compound bodies via child collider
111
- * entities are an extension point handled by a follow-up sub-observer.
112
- *
113
- * @author Alex Goldring
114
- * @copyright Company Named Limited (c) 2026
115
- */
116
- export class PhysicsSystem extends System {
117
-
118
- constructor() {
119
- super();
120
-
121
- this.dependencies = [RigidBody, Transform];
122
-
123
- this.components_used = [
124
- ResourceAccessSpecification.from(RigidBody, ResourceAccessKind.Read | ResourceAccessKind.Write),
125
- ResourceAccessSpecification.from(Collider, ResourceAccessKind.Read),
126
- ResourceAccessSpecification.from(Transform, ResourceAccessKind.Read | ResourceAccessKind.Write),
127
- ];
128
-
129
- /**
130
- * @type {BodyStorage}
131
- * @readonly
132
- */
133
- this.storage = new BodyStorage();
134
-
135
- /**
136
- * @type {BVH}
137
- */
138
- this.staticBvh = new BVH();
139
-
140
- /**
141
- * @type {BVH}
142
- */
143
- this.dynamicBvh = new BVH();
144
-
145
- /**
146
- * Persistent contact-manifold cache. One slot per active pair.
147
- * @type {ManifoldStore}
148
- * @readonly
149
- */
150
- this.manifolds = new ManifoldStore();
151
-
152
- /**
153
- * Per-frame list of broadphase-overlapping pairs, each in
154
- * entity-canonical role order (A = the smaller entity id — see
155
- * generate_pairs). Cleared at the top of each step; purely
156
- * intra-step state.
157
- * @type {PairList}
158
- */
159
- this.pairs = new PairList();
160
-
161
- /**
162
- * Per-frame contact-event buffer. Populated by the manifold diff
163
- * pass at end-of-step and consumed by the dispatch pass.
164
- * @type {ContactEventBuffer}
165
- */
166
- this.contactEvents = new ContactEventBuffer();
167
-
168
- /**
169
- * Per-frame island partitioning of the awake-body + contact graph.
170
- * Rebuilt after narrowphase, consumed by the solver (and, in the
171
- * follow-up slice, by the per-island atomic sleep test). Bodies
172
- * static and kinematic act as constraint anchors and do not enlarge
173
- * islands.
174
- * @type {IslandBuilder}
175
- */
176
- this.islands = new IslandBuilder();
177
-
178
- /**
179
- * Data-oriented mirror of the per-body solver hot state (velocity,
180
- * inverse mass / inertia, orientation), packed into one ArrayBuffer
181
- * and indexed by body slot. Gathered from the `RigidBody` /
182
- * `Transform` components once per step (after islands are built),
183
- * mutated in place by the TGS substep loop, and the persistent
184
- * velocity scattered back at the end. Keeps the solver's hottest inner
185
- * loop free of component-object dereferences.
186
- * @type {SolverBodyState}
187
- */
188
- this.__solver_state = new SolverBodyState();
189
-
190
- /**
191
- * Velocity-squared threshold below which a body is eligible to start
192
- * accumulating sleep time. Combined linear + angular kinetic-ish
193
- * metric: `vx²+vy²+vz² + ωx²+ωy²+ωz²`. Default 0.01 corresponds to
194
- * ~0.1 m/s linear or ~0.1 rad/s angular.
195
- * @type {number}
196
- */
197
- this.sleepVelocitySqrThreshold = 0.01;
198
-
199
- /**
200
- * Seconds of below-threshold motion before a body is moved to the
201
- * sleeping set. Box2D default is 0.5 s.
202
- * @type {number}
203
- */
204
- this.sleepTimeThreshold = 0.5;
205
-
206
- /**
207
- * Number of TGS substeps per `fixedUpdate`. Each substep re-runs the
208
- * velocity + position solve at `dt / substeps` against contacts whose
209
- * penetration is re-derived analytically from the bodies' moved poses
210
- * narrowphase still runs once per outer step. Higher counts buy
211
- * stack stability, high-mass-ratio robustness, and smoother
212
- * trajectories at a near-linear solver cost (sleeping islands are
213
- * unaffected they never enter the loop).
214
- *
215
- * `1` reproduces the non-substepped (single-step) solve.
216
- * @type {number}
217
- */
218
- this.substeps = 4;
219
-
220
- /**
221
- * Velocity iterations per substep. Lower than a single-step solver
222
- * would need, because the substep loop revisits the contact set
223
- * `substeps` times.
224
- * @type {number}
225
- */
226
- this.velocityIterations = 4;
227
-
228
- /**
229
- * Position (split-impulse) iterations per substep.
230
- * @type {number}
231
- */
232
- this.positionIterations = 1;
233
-
234
- /**
235
- * Contact softness frequency, Hz (Box2D-v3 soft step, hybrid form:
236
- * the velocity rows get the hertz/ζ-derived mass + impulse-decay
237
- * scales; penetration recovery stays in the split-impulse position
238
- * pass). The decay term is what lets resting stacks converge
239
- * asymptotically instead of ringing in the hard form's limit cycle
240
- * just above the sleep threshold. Clamped per step to an eighth of
241
- * the substep rate. `0` = hard contacts (bit-exact legacy rows).
242
- * Pairs with a non-dynamic side use this frequency (stiffer, so
243
- * piles don't sag into the ground). @type {number}
244
- */
245
- this.contactHertz = 30;
246
-
247
- /**
248
- * Contact damping ratio ζ for the soft scales — heavily over-damped
249
- * by design (Box2D v3 default), so contacts never oscillate.
250
- * @type {number}
251
- */
252
- this.contactDampingRatio = 10;
253
-
254
- /**
255
- * Reusable contact-event payload. Listeners must copy any fields they
256
- * intend to retain past their own scope. Reset before each dispatch.
257
- * @private
258
- */
259
- this.__contact_payload = {
260
- entityA: -1,
261
- entityB: -1,
262
- kind: 0,
263
- depth: 0,
264
- normal: new Vector3(),
265
- point: new Vector3(),
266
- };
267
-
268
- /**
269
- * World gravity, m/s². Applied each step scaled by per-body gravityScale.
270
- * @readonly
271
- * @type {Vector3}
272
- */
273
- this.gravity = new Vector3(0, -9.81, 0);
274
-
275
- /**
276
- * Optional global contact filter. Called for each surviving broadphase
277
- * pair; returning `false` discards the pair.
278
- * @type {((entityA: number, entityB: number, colliderA: Collider, colliderB: Collider) => boolean) | null}
279
- * @private
280
- */
281
- this.__contact_filter = null;
282
-
283
- /**
284
- * Per-body component side-tables indexed by body index (NOT packed id).
285
- * Sparse arrays populated at link, cleared at unlink. The hot loop
286
- * iterates `storage.awake_at(i)` and dereferences these.
287
- * @type {RigidBody[]}
288
- */
289
- this.__bodies = [];
290
-
291
- /**
292
- * @type {Transform[]}
293
- * @readonly
294
- */
295
- this.__transforms = [];
296
-
297
- /**
298
- * Per-body list of attached colliders. Each entry stores the
299
- * Collider component, its world Transform, the entity that owns
300
- * it (the body entity for same-entity attachments, a child entity
301
- * for compound bodies), and the BVH leaf id assigned at attach
302
- * time. A body may have zero or more attached colliders.
303
- *
304
- * @type {Array<Array<{collider: Collider, transform: Transform, entity: number, bvhNode: number}>>}
305
- */
306
- this.__body_collider_lists = [];
307
-
308
- /**
309
- * Live {@link Joint} (6-DOF constraint) instances, in a sparse array
310
- * indexed by joint id. Solved alongside contacts inside the TGS
311
- * substep loop. Holes (unlinked joints) are `undefined`.
312
- * @type {Joint[]}
313
- */
314
- this.__joints = [];
315
-
316
- /**
317
- * Lowest free index in {@link __joints} for slot reuse on link.
318
- * @private
319
- * @type {number}
320
- */
321
- this.__joint_free = [];
322
-
323
- /**
324
- * Live joints in CANONICAL SOLVE ORDER: ascending `(entityA,
325
- * entityB)`, equal keys in link order. The id-indexed
326
- * {@link __joints} registry reflects link/unlink HISTORY (slot
327
- * reuse), which two engines holding identical joints need not
328
- * share the joint solver's Gauss-Seidel sweep must consume
329
- * joints in an order derivable from state alone or the engines
330
- * diverge. Maintained by {@link link_joint} / {@link unlink_joint};
331
- * consumed by the substep loop. Multiple joints between the SAME
332
- * entity pair keep their relative link order reconstruction
333
- * replays links in the same order, so that too is state-derived.
334
- * @private
335
- * @type {Joint[]}
336
- */
337
- this.__joints_sorted = [];
338
-
339
- /**
340
- * Velocity iterations per substep for the joint solver. Joints (and
341
- * especially joint chains) want a few more iterations than contacts to
342
- * propagate impulses along the chain; cheap because joints are far
343
- * fewer than contacts.
344
- * @type {number}
345
- */
346
- this.jointIterations = 8;
347
-
348
- /**
349
- * Per-body pseudo-velocity for the Catto split-impulse position
350
- * pass (TGS Phase 1). Flat layout, 6 doubles per body slot index:
351
- * `[lin.x, lin.y, lin.z, ang.x, ang.y, ang.z]`. Sized to
352
- * `storage.high_water_mark * 6` at the top of each fixedUpdate
353
- * and zeroed in place so unwritten slots contribute nothing to
354
- * `integrate_position`.
355
- *
356
- * The solver writes during its position pass; `integrate_position`
357
- * reads and folds into the pose update on the same tick, then
358
- * the next tick's zero-pass wipes the state. It NEVER lands in
359
- * `linearVelocity` / `angularVelocity` — that's the point of
360
- * split impulse: depth correction does not contaminate persistent
361
- * velocity (so a `restitution = 0` impact stops cleanly).
362
- *
363
- * @type {Float64Array}
364
- */
365
- this.__pseudo_velocity = new Float64Array(0);
366
-
367
- /**
368
- * Master switch for the continuous-collision pass. When false the
369
- * {@link RigidBodyFlags.CCD} flag is ignored and no swept queries run.
370
- * @type {boolean}
371
- */
372
- this.ccdEnabled = true;
373
-
374
- /**
375
- * Start-of-step world positions for CCD-flagged bodies — 3 doubles per
376
- * body slot index (`[x, y, z]`). Captured in Stage 1 before the substep
377
- * loop integrates poses; the CCD pass ({@link ccd_resolve}) sweeps from
378
- * here to the final pose. Grows to `storage.high_water_mark * 3`; only
379
- * CCD-flagged slots are written each step.
380
- * @type {Float64Array}
381
- */
382
- this.__ccd_start_pos = new Float64Array(0);
383
-
384
-
385
- /**
386
- * Optional shared interpolation log to produce per-step pose snapshots
387
- * into (the {@link InterpolationSystem}'s log). When set and only then —
388
- * each fixedUpdate restores every awake {@link Interpolated} body's live
389
- * components from the previous tick (undoing render-time interpolation so
390
- * the sim reads authoritative state), then records the post-step state
391
- * under the current `entityManager.fixedStepTick`. Null on a headless /
392
- * non-rendering world, where the producer work is skipped entirely.
393
- * @type {InterpolationLog|null}
394
- */
395
- this.interpolationLog = null;
396
-
397
- /**
398
- * Reusable decode buffer for restoring interpolated snapshots.
399
- * @private
400
- * @type {BinaryBuffer}
401
- */
402
- this.__interp_scratch = new BinaryBuffer();
403
- this.__interp_scratch.fromArrayBuffer(new ArrayBuffer(256));
404
- }
405
-
406
- /**
407
- * Symmetric layer/mask check + optional user callback. Called per
408
- * candidate pair during broadphase. Returns `true` to accept the pair.
409
- *
410
- * Layer/mask rule: pair (A, B) collides iff
411
- * `(A.layer & B.mask) !== 0 && (B.layer & A.mask) !== 0`.
412
- * The user's contact-filter callback is consulted only for pairs that
413
- * pass the bitmask test (cheap gate first).
414
- *
415
- * @private
416
- * @param {number} idA packed body id
417
- * @param {number} idB packed body id
418
- * @returns {boolean}
419
- */
420
- __pair_filter(idA, idB) {
421
- const idxA = body_id_index(idA);
422
- const idxB = body_id_index(idB);
423
-
424
- const rbA = this.__bodies[idxA];
425
- const rbB = this.__bodies[idxB];
426
-
427
- if (rbA === undefined || rbB === undefined) {
428
- return false;
429
- }
430
-
431
- // Layer/mask gate (symmetric).
432
- if (((rbA.layer & rbB.mask) | 0) === 0) {
433
- return false;
434
- }
435
- if (((rbB.layer & rbA.mask) | 0) === 0) {
436
- return false;
437
- }
438
-
439
- // User callback gate, if installed.
440
- const fn = this.__contact_filter;
441
- if (fn !== null) {
442
- const entA = this.storage.entity_at(idxA);
443
- const entB = this.storage.entity_at(idxB);
444
- const colA = this.__primary_collider(idxA);
445
- const colB = this.__primary_collider(idxB);
446
- if (!fn(entA, entB, colA, colB)) return false;
447
- }
448
- return true;
449
- }
450
-
451
- /**
452
- * First attached collider of a body, or `null` if none. Used for
453
- * body-level checks that aren't yet per-collider: the sensor flag, the
454
- * concave-shape dispatch flag, and the contact-filter callback's
455
- * `colliderA` / `colliderB` arguments. Contact **materials** (friction /
456
- * restitution) are NOT read here — the narrowphase tracks the specific
457
- * source collider on each side of every contact and stamps the combined
458
- * coefficients into the manifold per contact, so mixed-material compound
459
- * bodies are accurate. (Per-contact source colliders for the filter
460
- * callback / sensor flag remain a follow-up.)
461
- *
462
- * @private
463
- * @param {number} body_idx
464
- * @returns {Collider|null}
465
- */
466
- __primary_collider(body_idx) {
467
- const list = this.__body_collider_lists[body_idx];
468
- return (list !== undefined && list.length > 0) ? list[0].collider : null;
469
- }
470
-
471
- /**
472
- * Resize {@link __pseudo_velocity} to cover every live body slot and
473
- * zero its contents. Called at the top of each fixedUpdate so the
474
- * split-impulse position pass starts from a clean state — bodies the
475
- * solver doesn't touch contribute zero pseudo-velocity to
476
- * {@link integrate_position}.
477
- *
478
- * Doubles on growth like the other physics scratches; never shrinks.
479
- *
480
- * @private
481
- */
482
- __reset_pseudo_velocity() {
483
- const required = this.storage.high_water_mark * 6;
484
- if (this.__pseudo_velocity.length < required) {
485
- this.__pseudo_velocity = new Float64Array(required * 2);
486
- } else if (required > 0) {
487
- this.__pseudo_velocity.fill(0, 0, required);
488
- }
489
- }
490
-
491
- /**
492
- * Replace the world gravity vector. Effective on the next step.
493
- * @param {Vector3|{x:number,y:number,z:number}} v
494
- */
495
- setGravity(v) {
496
- this.gravity.set(v.x, v.y, v.z);
497
- }
498
-
499
- /**
500
- * Install (or remove with `null`) the contact filter callback.
501
- * @param {((entityA:number, entityB:number, colliderA:Collider, colliderB:Collider) => boolean) | null} fn
502
- */
503
- setContactFilter(fn) {
504
- this.__contact_filter = fn;
505
- }
506
-
507
- /**
508
- * @returns {((entityA:number, entityB:number, colliderA:Collider, colliderB:Collider) => boolean) | null}
509
- */
510
- getContactFilter() {
511
- return this.__contact_filter;
512
- }
513
-
514
- /**
515
- * @private
516
- * @param {RigidBody} rb
517
- * @param {Collider} collider
518
- * @param {Transform} transform
519
- * @returns {number} BVH node id
520
- */
521
- __insert_into_broadphase(rb, collider, transform) {
522
- const shape = collider.shape;
523
-
524
- assert.notNull(shape, 'Collider.shape must be set before attaching');
525
-
526
- shape.compute_bounding_box(scratch_local_aabb);
527
-
528
- const p = transform.position;
529
- const q = transform.rotation;
530
-
531
- aabb3_transform_oriented(
532
- scratch_world_aabb, 0,
533
- scratch_local_aabb[0], scratch_local_aabb[1], scratch_local_aabb[2],
534
- scratch_local_aabb[3], scratch_local_aabb[4], scratch_local_aabb[5],
535
- p.x, p.y, p.z,
536
- q.x, q.y, q.z, q.w
537
- );
538
-
539
- const bvh = rb.kind === BodyKind.Static ? this.staticBvh : this.dynamicBvh;
540
- const node = bvh.allocate_node();
541
-
542
- bvh.node_set_aabb_primitive(
543
- node,
544
- scratch_world_aabb[0], scratch_world_aabb[1], scratch_world_aabb[2],
545
- scratch_world_aabb[3], scratch_world_aabb[4], scratch_world_aabb[5]
546
- );
547
- bvh.node_set_user_data(node, rb._bodyId);
548
- bvh.insert_leaf(node);
549
-
550
- return node;
551
- }
552
-
553
- /**
554
- * @private
555
- * @param {RigidBody} rb
556
- * @param {number} node
557
- */
558
- __remove_from_broadphase(rb, node) {
559
- const bvh = rb.kind === BodyKind.Static ? this.staticBvh : this.dynamicBvh;
560
- bvh.remove_leaf(node);
561
- bvh.release_node(node);
562
- }
563
-
564
- /**
565
- * Wake every sleeping dynamic body whose broadphase leaf overlaps the
566
- * given leaf's AABB. Called BEFORE removing a NON-Dynamic body's leaf
567
- * (unlink / collider detach): statics and kinematics join no sleep group,
568
- * their sleepers' manifolds are dormant-frozen, and the broadphase never
569
- * revisits sleeping bodies — so without this, a pile resting on the
570
- * removed support would sleep in mid-air forever.
571
- *
572
- * Dynamic removals don't need it: a sleeping pile and its dynamic support
573
- * slept as one island and share a sleep group, which unlink already
574
- * dissolves by waking the group.
575
- *
576
- * @private
577
- * @param {BVH} bvh tree currently holding `node`
578
- * @param {number} node leaf about to be removed
579
- */
580
- __wake_sleepers_overlapping_leaf(bvh, node) {
581
- bvh.node_get_aabb(node, scratch_world_aabb);
582
-
583
- let n;
584
- for (;;) {
585
- n = bvh_query_user_data_intersects_aabb(scratch_wake_candidates, 0, this.dynamicBvh, scratch_world_aabb);
586
- if (n <= scratch_wake_candidates.length) break;
587
- // TRUE count returned, overflow writes dropped grow and re-query.
588
- scratch_wake_candidates = new Uint32Array(Math.max(n, scratch_wake_candidates.length * 2));
589
- }
590
-
591
- for (let i = 0; i < n; i++) {
592
- const body_id = scratch_wake_candidates[i];
593
- if (!this.storage.is_valid(body_id)) continue;
594
- const rb = this.__bodies[body_id_index(body_id)];
595
- if (rb === undefined) continue;
596
- if (rb.sleepState !== SleepState.Sleeping) continue;
597
- this.__wake_body(rb);
598
- }
599
- }
600
-
601
- /**
602
- * Lifecycle entry: invoked when an entity gains the (RigidBody, Transform)
603
- * tuple. Allocates the body's slot in storage and seeds an empty collider
604
- * list. Colliders are attached separately via {@link attach_collider} (the
605
- * paired {@link ColliderObserverSystem} drives this from the dataset; in
606
- * tests, call manually).
607
- *
608
- * @param {RigidBody} rigidBody
609
- * @param {Transform} transform
610
- * @param {number} entity
611
- */
612
- link(rigidBody, transform, entity) {
613
- const packed = this.storage.allocate(entity);
614
- rigidBody._bodyId = packed;
615
-
616
- const index = body_id_index(packed);
617
- this.storage.set_kind(index, rigidBody.kind);
618
- this.storage.set_flags(index, rigidBody.flags);
619
-
620
- this.__bodies[index] = rigidBody;
621
- this.__transforms[index] = transform;
622
- this.__body_collider_lists[index] = [];
623
-
624
- // The component's sleep fields are AUTHORITATIVE persistent state
625
- // (see RigidBody.sleepState): a body restored from a snapshot — or
626
- // re-linked after an unlink — resumes exactly as drowsy or asleep
627
- // as it was, instead of being forced awake to re-settle. That keeps
628
- // a reconstructed world on the original's wake/sleep schedule (the
629
- // reconstruction-determinism contract). A freshly-constructed
630
- // component defaults to Awake, so plain spawns are unchanged.
631
- //
632
- // A restored sleeper arrives as a sleep-group SINGLETON (the group
633
- // links are world-instance-tied body indices, reset here); group
634
- // membership is re-established by the contact-state import or, for
635
- // a body slept in isolation, is already correct.
636
- rigidBody.sleep_group_next = -1;
637
- rigidBody.sleep_group_prev = -1;
638
-
639
- // Static bodies do not need to live in the active list they never move.
640
- if (rigidBody.kind === BodyKind.Static
641
- || (rigidBody.kind === BodyKind.Dynamic && rigidBody.sleepState === SleepState.Sleeping)) {
642
- this.storage.mark_sleeping(index);
643
- }
644
- }
645
-
646
- /**
647
- * Detach every collider attached to this body, free its slot, and clear
648
- * the side-tables.
649
- *
650
- * @param {RigidBody} rigidBody
651
- * @param {Transform} transform
652
- * @param {number} entity
653
- */
654
- unlink(rigidBody, transform, entity) {
655
- const packed = rigidBody._bodyId;
656
-
657
- assert.equal(this.storage.is_valid(packed), true, 'unlink: stale or absent body id');
658
-
659
- // If the body is sleeping inside a multi-member sleep group, dissolve
660
- // the group by waking every member first. Otherwise the surviving
661
- // members would hold dangling indices in their sleep-group chain
662
- // pointing at a slot that has just been freed.
663
- if (rigidBody.sleep_group_next !== -1) {
664
- this.__wake_body(rigidBody);
665
- }
666
-
667
- const index = body_id_index(packed);
668
- const list = this.__body_collider_lists[index];
669
- if (list !== undefined) {
670
- // A removed static / kinematic body may have been the support
671
- // under sleeping bodies wake them before the leaves disappear
672
- // (their AABBs are the query keys).
673
- const wake_neighbours = rigidBody.kind !== BodyKind.Dynamic;
674
- const bvh = rigidBody.kind === BodyKind.Static ? this.staticBvh : this.dynamicBvh;
675
- for (let i = 0; i < list.length; i++) {
676
- const entry = list[i];
677
- if (entry.bvhNode !== COLLIDER_UNBOUND) {
678
- if (wake_neighbours) {
679
- this.__wake_sleepers_overlapping_leaf(bvh, entry.bvhNode);
680
- }
681
- this.__remove_from_broadphase(rigidBody, entry.bvhNode);
682
- }
683
- entry.collider._bvhNode = COLLIDER_UNBOUND;
684
- entry.collider._bodyId = -1;
685
- }
686
- }
687
-
688
- this.__bodies[index] = undefined;
689
- this.__transforms[index] = undefined;
690
- this.__body_collider_lists[index] = undefined;
691
-
692
- // Purge joints referencing the freed body. The solver already skips
693
- // them via the generation-checked is_valid, but the 8-bit generation
694
- // wraps after 256 reuse cycles of one slot (~seconds under a pooled
695
- // spawner) — at which point a stale joint handle ALIASES an unrelated
696
- // new body and silently reactivates against it. Removing the joint at
697
- // the source closes the ABA window outright.
698
- const joints = this.__joints;
699
- for (let i = 0; i < joints.length; i++) {
700
- const joint = joints[i];
701
- if (joint === undefined || joint === null) continue;
702
- if (joint._bodyIdA === packed || joint._bodyIdB === packed) {
703
- this.unlink_joint(joint);
704
- }
705
- }
706
-
707
- this.storage.free(packed);
708
- rigidBody._bodyId = RIGID_BODY_UNALLOCATED;
709
- }
710
-
711
- /**
712
- * Attach a collider to an existing body. The collider can live on the
713
- * same entity as the body (single-collider body) or on a child entity
714
- * (compound body). The world transform passed here is the collider's
715
- * own for a same-entity collider it is the body's Transform; for a
716
- * child collider it is the child entity's Transform.
717
- *
718
- * Idempotent — re-attaching the same collider is a no-op.
719
- *
720
- * @param {number} body_entity entity that owns the body
721
- * @param {Collider} collider
722
- * @param {Transform} transform world transform of the collider
723
- * @param {number} [collider_entity] entity owning the collider (defaults to body_entity)
724
- */
725
- attach_collider(body_entity, collider, transform, collider_entity = body_entity) {
726
- // Find the body by walking the storage entity table. The body must
727
- // have been allocated via `link` before any colliders are attached.
728
- const body_index = this.__find_body_index_by_entity(body_entity);
729
- assert.notEqual(body_index, -1, `attach_collider: no body found for entity ${body_entity}`);
730
-
731
- const rb = this.__bodies[body_index];
732
- // Idempotent: skip if collider already attached.
733
- if (collider._bvhNode !== COLLIDER_UNBOUND) return;
734
-
735
- const node = this.__insert_into_broadphase(rb, collider, transform);
736
-
737
- collider._bvhNode = node;
738
- collider._bodyId = rb._bodyId;
739
- this.__body_collider_lists[body_index].push({
740
- collider, transform, entity: collider_entity, bvhNode: node,
741
- });
742
- }
743
-
744
- /**
745
- * Reverse of {@link attach_collider}. Idempotent.
746
- *
747
- * @param {number} body_entity
748
- * @param {Collider} collider
749
- */
750
- detach_collider(body_entity, collider) {
751
- if (collider._bvhNode === COLLIDER_UNBOUND) return;
752
-
753
- const body_index = this.__find_body_index_by_entity(body_entity);
754
- if (body_index === -1) return;
755
-
756
- const rb = this.__bodies[body_index];
757
- // Same rule as unlink: removing a static / kinematic leaf may pull
758
- // the support from under sleeping bodies — wake them first.
759
- if (rb.kind !== BodyKind.Dynamic) {
760
- const bvh = rb.kind === BodyKind.Static ? this.staticBvh : this.dynamicBvh;
761
- this.__wake_sleepers_overlapping_leaf(bvh, collider._bvhNode);
762
- }
763
- this.__remove_from_broadphase(rb, collider._bvhNode);
764
-
765
- const list = this.__body_collider_lists[body_index];
766
- for (let i = 0; i < list.length; i++) {
767
- if (list[i].collider === collider) {
768
- list.splice(i, 1);
769
- break;
770
- }
771
- }
772
-
773
- collider._bvhNode = COLLIDER_UNBOUND;
774
- collider._bodyId = -1;
775
- }
776
-
777
- /**
778
- * Resolve an entity to its body index, or -1 if no live body owns it.
779
- * O(1) via {@link BodyStorage#index_of_entity}'s entity index map used
780
- * on the collider attach / detach and joint link paths.
781
- *
782
- * @private
783
- * @param {number} entity
784
- * @returns {number} body index or -1
785
- */
786
- __find_body_index_by_entity(entity) {
787
- return this.storage.index_of_entity(entity);
788
- }
789
-
790
- /**
791
- * Register a {@link Joint} (6-DOF constraint). Resolves the joint's
792
- * entities to packed body ids and adds it to the active set, where it is
793
- * solved alongside contacts in the TGS substep loop. Body A must be a
794
- * linked body; body B is either a linked body or {@link JOINT_WORLD}
795
- * (anchoring A to a fixed world point — `localAnchorB` is then a world
796
- * coordinate).
797
- *
798
- * @param {Joint} joint
799
- */
800
- link_joint(joint) {
801
- const idxA = this.__find_body_index_by_entity(joint.entityA);
802
- assert.notEqual(idxA, -1, `link_joint: no body for entityA ${joint.entityA}`);
803
- joint._bodyIdA = this.__bodies[idxA]._bodyId;
804
-
805
- if (joint.entityB === JOINT_WORLD) {
806
- joint._bodyIdB = JOINT_WORLD;
807
- } else {
808
- const idxB = this.__find_body_index_by_entity(joint.entityB);
809
- assert.notEqual(idxB, -1, `link_joint: no body for entityB ${joint.entityB}`);
810
- joint._bodyIdB = this.__bodies[idxB]._bodyId;
811
- }
812
-
813
- // A (re-)linked joint starts cold: the accumulated warm-start impulse
814
- // belongs to whatever configuration the joint solved BEFORE it was
815
- // unlinked replaying it against the new bodies is a phantom kick.
816
- joint.dofImpulse.fill(0);
817
-
818
- // Reuse a freed slot if available so joint ids stay dense-ish.
819
- let id;
820
- if (this.__joint_free.length > 0) {
821
- id = this.__joint_free.pop();
822
- } else {
823
- id = this.__joints.length;
824
- }
825
- joint._jointId = id;
826
- this.__joints[id] = joint;
827
-
828
- // Insert at the upper bound of the joint's (entityA, entityB) key in
829
- // the canonical solve-order list equal keys keep link order.
830
- const sorted = this.__joints_sorted;
831
- let pos = sorted.length;
832
- while (pos > 0) {
833
- const prev = sorted[pos - 1];
834
- if (prev.entityA < joint.entityA
835
- || (prev.entityA === joint.entityA && prev.entityB <= joint.entityB)) {
836
- break;
837
- }
838
- pos--;
839
- }
840
- sorted.splice(pos, 0, joint);
841
- }
842
-
843
- /**
844
- * Remove a previously {@link link_joint}'d joint from the active set.
845
- * Idempotent.
846
- * @param {Joint} joint
847
- */
848
- unlink_joint(joint) {
849
- const id = joint._jointId;
850
- if (id === JOINT_UNALLOCATED || this.__joints[id] !== joint) return;
851
- this.__joints[id] = undefined;
852
- this.__joint_free.push(id);
853
- joint._jointId = JOINT_UNALLOCATED;
854
-
855
- const sorted = this.__joints_sorted;
856
- const pos = sorted.indexOf(joint);
857
- if (pos !== -1) sorted.splice(pos, 1);
858
- }
859
-
860
- /**
861
- * Resolve a packed body id to its entity, or `-1` if the id is stale.
862
- * @param {number} packed_body_id
863
- * @returns {number}
864
- */
865
- entityOf(packed_body_id) {
866
- if (!this.storage.is_valid(packed_body_id)) return -1;
867
- return this.storage.entity_at(body_id_index(packed_body_id));
868
- }
869
-
870
- /**
871
- * Number of live bodies (regardless of awake/sleeping state).
872
- * @returns {number}
873
- */
874
- get bodyCount() {
875
- return this.storage.size;
876
- }
877
-
878
- /**
879
- * Apply an instantaneous change of momentum at the body's centre of mass.
880
- * Linear-only — see {@link applyImpulseAt} for an off-centre impulse that
881
- * also produces angular response.
882
- *
883
- * Wakes the body if it is asleep.
884
- *
885
- * @param {RigidBody} rigidBody
886
- * @param {Vector3|{x:number,y:number,z:number}} impulse
887
- */
888
- applyImpulse(rigidBody, impulse) {
889
- if (rigidBody.kind !== BodyKind.Dynamic) {
890
- return;
891
- }
892
- const inv_m = rigidBody.mass > 0 ? 1 / rigidBody.mass : 0;
893
- rigidBody.linearVelocity.addScaled(impulse, inv_m);
894
-
895
- this.__wake_body(rigidBody);
896
- }
897
-
898
- /**
899
- * Apply an instantaneous change of momentum at a specific world-space point.
900
- * Off-centre impulses produce both linear (Δv = P/m) and angular
901
- * (Δω = I_w⁻¹·(r × P)) response.
902
- *
903
- * Wakes the body if it is asleep.
904
- *
905
- * @param {RigidBody} rigidBody
906
- * @param {Transform} transform body's current world Transform (used for r and I_w)
907
- * @param {Vector3|{x:number,y:number,z:number}} impulse
908
- * @param {Vector3|{x:number,y:number,z:number}} worldPoint
909
- */
910
- applyImpulseAt(rigidBody, transform, impulse, worldPoint) {
911
- if (rigidBody.kind !== BodyKind.Dynamic) {
912
- return;
913
- }
914
- const inv_m = rigidBody.mass > 0 ? 1 / rigidBody.mass : 0;
915
-
916
- rigidBody.linearVelocity.set(
917
- rigidBody.linearVelocity.x + impulse.x * inv_m,
918
- rigidBody.linearVelocity.y + impulse.y * inv_m,
919
- rigidBody.linearVelocity.z + impulse.z * inv_m
920
- );
921
-
922
- const rx = worldPoint.x - transform.position.x;
923
- const ry = worldPoint.y - transform.position.y;
924
- const rz = worldPoint.z - transform.position.z;
925
-
926
- // Δω = I_w⁻¹ · (r × P)
927
- const tx = ry * impulse.z - rz * impulse.y;
928
- const ty = rz * impulse.x - rx * impulse.z;
929
- const tz = rx * impulse.y - ry * impulse.x;
930
-
931
- // LockRot* masks the BODY-LOCAL inverse-inertia diagonal here exactly
932
- // as the solver gather does, so a user impulse cannot spin a locked
933
- // DOF either.
934
- const flags = rigidBody.flags;
935
- const ii = rigidBody.inverseInertiaLocal;
936
- scratch_locked_inertia.x = (flags & RigidBodyFlags.LockRotX) !== 0 ? 0 : ii.x;
937
- scratch_locked_inertia.y = (flags & RigidBodyFlags.LockRotY) !== 0 ? 0 : ii.y;
938
- scratch_locked_inertia.z = (flags & RigidBodyFlags.LockRotZ) !== 0 ? 0 : ii.z;
939
- world_inverse_inertia_apply(scratch_angular_delta, 0, scratch_locked_inertia, transform.rotation, tx, ty, tz);
940
-
941
- rigidBody.angularVelocity.set(
942
- rigidBody.angularVelocity.x + scratch_angular_delta[0],
943
- rigidBody.angularVelocity.y + scratch_angular_delta[1],
944
- rigidBody.angularVelocity.z + scratch_angular_delta[2]
945
- );
946
-
947
- this.__wake_body(rigidBody);
948
- }
949
-
950
- /**
951
- * Accumulate a continuous torque (world-space) for integration on the
952
- * next fixedUpdate. Pairs with {@link applyForce} for the rotational case.
953
- *
954
- * Wakes the body if asleep.
955
- *
956
- * @param {RigidBody} rigidBody
957
- * @param {Vector3|{x:number,y:number,z:number}} torque
958
- */
959
- applyTorque(rigidBody, torque) {
960
- if (rigidBody.kind !== BodyKind.Dynamic) {
961
- return;
962
- }
963
- rigidBody.accumulatedTorque.set(
964
- rigidBody.accumulatedTorque.x + torque.x,
965
- rigidBody.accumulatedTorque.y + torque.y,
966
- rigidBody.accumulatedTorque.z + torque.z
967
- );
968
- this.__wake_body(rigidBody);
969
- }
970
-
971
- /**
972
- * Apply a continuous force at a specific world-space point. The force
973
- * generates both a linear acceleration (F/m) and a torque (r × F) about
974
- * the body's centre of mass.
975
- *
976
- * Wakes the body if asleep.
977
- *
978
- * @param {RigidBody} rigidBody
979
- * @param {Transform} transform body's current world Transform
980
- * @param {Vector3|{x:number,y:number,z:number}} force
981
- * @param {Vector3|{x:number,y:number,z:number}} worldPoint
982
- */
983
- applyForceAt(rigidBody, transform, force, worldPoint) {
984
- if (rigidBody.kind !== BodyKind.Dynamic) {
985
- return;
986
- }
987
- rigidBody.accumulatedForce.set(
988
- rigidBody.accumulatedForce.x + force.x,
989
- rigidBody.accumulatedForce.y + force.y,
990
- rigidBody.accumulatedForce.z + force.z
991
- );
992
-
993
- const rx = worldPoint.x - transform.position.x;
994
- const ry = worldPoint.y - transform.position.y;
995
- const rz = worldPoint.z - transform.position.z;
996
-
997
- rigidBody.accumulatedTorque.set(
998
- rigidBody.accumulatedTorque.x + (ry * force.z - rz * force.y),
999
- rigidBody.accumulatedTorque.y + (rz * force.x - rx * force.z),
1000
- rigidBody.accumulatedTorque.z + (rx * force.y - ry * force.x)
1001
- );
1002
-
1003
- this.__wake_body(rigidBody);
1004
- }
1005
-
1006
- /**
1007
- * Accumulate a continuous force to be integrated next fixedUpdate step.
1008
- * Wakes the body if asleep.
1009
- *
1010
- * @param {RigidBody} rigidBody
1011
- * @param {Vector3|{x:number,y:number,z:number}} force
1012
- */
1013
- applyForce(rigidBody, force) {
1014
- if (rigidBody.kind !== BodyKind.Dynamic) {
1015
- return;
1016
- }
1017
- rigidBody.accumulatedForce.add(force);
1018
- this.__wake_body(rigidBody);
1019
- }
1020
-
1021
- /**
1022
- * Replace the linear velocity. Wakes the body if asleep.
1023
- *
1024
- * @param {RigidBody} rigidBody
1025
- * @param {Vector3|{x:number,y:number,z:number}} v
1026
- */
1027
- setLinearVelocity(rigidBody, v) {
1028
- rigidBody.linearVelocity.copy(v);
1029
- if (rigidBody.kind === BodyKind.Dynamic) {
1030
- this.__wake_body(rigidBody);
1031
- }
1032
- }
1033
-
1034
- /**
1035
- * Teleport a body to a new pose, bypassing integration: writes the body's
1036
- * Transform directly and wakes it. For an interpolated body this also flags a
1037
- * render `snap` on its {@link Interpolated} component, so the producer keeps
1038
- * this pose (rather than restoring the previous tick over it) and the
1039
- * renderer shows the new pose without sliding across the jump.
1040
- *
1041
- * This is the authoritative way to reposition an interpolated body — a raw
1042
- * `Transform` write would be undone by the per-step restore. Velocity is left
1043
- * as-is; zero it via {@link setLinearVelocity} if the teleport should also
1044
- * stop the body.
1045
- *
1046
- * Works for STATIC bodies too: their broadphase leaves (which the step
1047
- * never refits) are re-homed to the new pose, and sleeping bodies at both
1048
- * the old and the new location are woken — a moved support drops what
1049
- * rested on it, and whatever the static moved into must react.
1050
- *
1051
- * @param {RigidBody} rigidBody
1052
- * @param {Vector3|{x:number,y:number,z:number}} position world position
1053
- * @param {Quaternion|{x:number,y:number,z:number,w:number}} rotation world unit-quaternion rotation
1054
- */
1055
- setPose(rigidBody, position, rotation) {
1056
- const idx = body_id_index(rigidBody._bodyId);
1057
- const transform = this.__transforms[idx];
1058
- if (transform === undefined) {
1059
- return;
1060
- }
1061
-
1062
- transform.position.set(position.x, position.y, position.z);
1063
- transform.rotation.set(rotation.x, rotation.y, rotation.z, rotation.w);
1064
-
1065
- if (rigidBody.kind === BodyKind.Static) {
1066
- // The step never refits a static's leaves (stage 2 covers awake
1067
- // bodies only), so a moved static would keep its stale broadphase
1068
- // AABB forever: nothing collides at the new location, ghost pairs
1069
- // at the old one. Re-home each leaf now, and wake the sleepers at
1070
- // BOTH locations — anything resting on the support that just
1071
- // moved away, and anything the support just moved into.
1072
- const list = this.__body_collider_lists[idx];
1073
- if (list !== undefined) {
1074
- for (let i = 0; i < list.length; i++) {
1075
- const entry = list[i];
1076
- if (entry.bvhNode === COLLIDER_UNBOUND) continue;
1077
- this.__wake_sleepers_overlapping_leaf(this.staticBvh, entry.bvhNode);
1078
- this.__remove_from_broadphase(rigidBody, entry.bvhNode);
1079
- const node = this.__insert_into_broadphase(rigidBody, entry.collider, entry.transform);
1080
- entry.bvhNode = node;
1081
- entry.collider._bvhNode = node;
1082
- this.__wake_sleepers_overlapping_leaf(this.staticBvh, node);
1083
- }
1084
- }
1085
- } else {
1086
- this.__wake_body(rigidBody);
1087
- }
1088
-
1089
- // Flag a snap on the body's Interpolated component, if any, so the
1090
- // interpolation producer (restore) and consumer (blend) treat this pose
1091
- // as authoritative this frame instead of interpolating across the jump.
1092
- const em = this.entityManager;
1093
- if (em !== null && em !== undefined) {
1094
- const dataset = em.dataset;
1095
- if (dataset !== null && dataset !== undefined) {
1096
- const entity = this.storage.entity_at(idx);
1097
- const interpolated = dataset.getComponent(entity, Interpolated);
1098
- if (interpolated !== undefined && interpolated !== null) {
1099
- interpolated.snap = true;
1100
- }
1101
- }
1102
- }
1103
- }
1104
-
1105
- /**
1106
- * Force the body awake. Static bodies are ignored.
1107
- * @param {RigidBody} rigidBody
1108
- */
1109
- wake(rigidBody) {
1110
- this.__wake_body(rigidBody);
1111
- }
1112
-
1113
- /**
1114
- * Force the body asleep. Dynamic bodies will not re-enter the active list
1115
- * until a wake event occurs.
1116
- * @param {RigidBody} rigidBody
1117
- */
1118
- sleep(rigidBody) {
1119
- if (rigidBody.kind !== BodyKind.Dynamic) {
1120
- return;
1121
- }
1122
- if (rigidBody.sleepState === SleepState.Sleeping) {
1123
- return;
1124
- }
1125
- // An unlinked (or stale) body has no storage slot — indexing with its
1126
- // dead id would corrupt the awake-list bookkeeping of whatever body
1127
- // now owns that index.
1128
- if (!this.storage.is_valid(rigidBody._bodyId)) {
1129
- return;
1130
- }
1131
- rigidBody.sleepState = SleepState.Sleeping;
1132
- const index = body_id_index(rigidBody._bodyId);
1133
- this.storage.mark_sleeping(index);
1134
- }
1135
-
1136
- /**
1137
- * Wake a body and atomically wake every other body it was last sleeping
1138
- * with (its "sleep group"). Sleep groups are circular doubly-linked lists
1139
- * threaded through every member of an island when it sleeps atomically;
1140
- * waking any one member walks the chain and wakes the rest in the same
1141
- * call.
1142
- *
1143
- * Without this, a 100-block stack hit at the base would wake one block
1144
- * per frame as the broadphase propagated awareness up the stack — a
1145
- * visible ~1.6 s wave at 60 fps. Atomic wake eliminates the wave.
1146
- *
1147
- * No-op for non-dynamic bodies. Idempotent for already-awake bodies.
1148
- * @private
1149
- * @param {RigidBody} rb
1150
- */
1151
- __wake_body(rb) {
1152
- if (rb.kind !== BodyKind.Dynamic) return;
1153
- if (rb.sleepState === SleepState.Awake) return;
1154
- // Same stale-id guard as sleep(): an unlinked body's dead id must not
1155
- // index into (and corrupt) another body's awake-list slot.
1156
- if (!this.storage.is_valid(rb._bodyId)) return;
1157
- const index = body_id_index(rb._bodyId);
1158
-
1159
- // Remember the next-in-chain before clearing the body's own pointers;
1160
- // the rest of the group is reached by walking forward from there.
1161
- const start_next = rb.sleep_group_next;
1162
-
1163
- rb.sleepState = SleepState.Awake;
1164
- rb.sleep_timer = 0;
1165
- rb.sleep_group_next = -1;
1166
- rb.sleep_group_prev = -1;
1167
- this.storage.mark_awake(index);
1168
-
1169
- if (start_next === -1 || start_next === index) return;
1170
-
1171
- // Walk the (now-broken) chain forward until we loop back. The chain
1172
- // is circular so we know when to stop; defensive `-1` guards against
1173
- // corruption from a body being unlinked mid-sleep-group.
1174
- let cur = start_next;
1175
- while (cur !== -1 && cur !== index) {
1176
- const cur_rb = this.__bodies[cur];
1177
- if (cur_rb === undefined) break;
1178
- const nxt = cur_rb.sleep_group_next;
1179
- cur_rb.sleepState = SleepState.Awake;
1180
- cur_rb.sleep_timer = 0;
1181
- cur_rb.sleep_group_next = -1;
1182
- cur_rb.sleep_group_prev = -1;
1183
- this.storage.mark_awake(cur);
1184
- cur = nxt;
1185
- }
1186
- }
1187
-
1188
- /**
1189
- * Atomically put every body in a contiguous range of island members to
1190
- * sleep. Members are threaded into a circular doubly-linked list so any
1191
- * future `wake` on any member walks the chain and revives them all.
1192
- *
1193
- * Velocities are zeroed because the body is by definition at rest at
1194
- * this point the alternative (storing residual velocities for "softer"
1195
- * wake) is what Bullet does, but for a deterministic game-physics target
1196
- * fully resetting is simpler and avoids drift while sleeping.
1197
- *
1198
- * @private
1199
- * @param {Uint32Array} member_array view (or full array) of body indices
1200
- * @param {number} start
1201
- * @param {number} end
1202
- */
1203
- __atomic_sleep_island_range(member_array, start, end) {
1204
- const count = end - start;
1205
- if (count === 0) return;
1206
- const bodies = this.__bodies;
1207
- const storage = this.storage;
1208
-
1209
- if (count === 1) {
1210
- const idx = member_array[start];
1211
- const rb = bodies[idx];
1212
- if (rb === undefined) return;
1213
- rb.sleep_group_next = -1;
1214
- rb.sleep_group_prev = -1;
1215
- rb.sleepState = SleepState.Sleeping;
1216
- rb.linearVelocity[0] = 0;
1217
- rb.linearVelocity[1] = 0;
1218
- rb.linearVelocity[2] = 0;
1219
- rb.angularVelocity[0] = 0;
1220
- rb.angularVelocity[1] = 0;
1221
- rb.angularVelocity[2] = 0;
1222
- storage.mark_sleeping(idx);
1223
- return;
1224
- }
1225
-
1226
- for (let i = 0; i < count; i++) {
1227
- const idx = member_array[start + i];
1228
- const rb = bodies[idx];
1229
- if (rb === undefined) continue;
1230
- const next_idx = member_array[start + ((i + 1) % count)];
1231
- const prev_idx = member_array[start + ((i - 1 + count) % count)];
1232
- rb.sleep_group_next = next_idx;
1233
- rb.sleep_group_prev = prev_idx;
1234
- rb.sleepState = SleepState.Sleeping;
1235
- rb.linearVelocity[0] = 0;
1236
- rb.linearVelocity[1] = 0;
1237
- rb.linearVelocity[2] = 0;
1238
- rb.angularVelocity[0] = 0;
1239
- rb.angularVelocity[1] = 0;
1240
- rb.angularVelocity[2] = 0;
1241
- storage.mark_sleeping(idx);
1242
- }
1243
- }
1244
-
1245
- /**
1246
- * Broadphase raycast against both BVHs. Fills `result` with the nearest
1247
- * hit and returns `true` on hit, `false` on miss.
1248
- *
1249
- * Narrowphase refinement against the actual shape geometry is a
1250
- * follow-up — for now `result.t` is the distance to the leaf's
1251
- * inflated AABB and `result.normal` is the AABB face normal. Both are
1252
- * exact for AABB-shaped colliders.
1253
- *
1254
- * @param {Ray3} ray origin + unit direction + `tMax`
1255
- * @param {PhysicsSurfacePoint} result populated on hit; untouched on miss
1256
- * @param {(entity:number, collider:Collider)=>boolean} [filter] defaults
1257
- * to {@link returnTrue} (accept every candidate)
1258
- * @returns {boolean}
1259
- */
1260
- raycast(ray, result, filter = returnTrue) {
1261
- return raycast_query(this, ray, result, filter);
1262
- }
1263
-
1264
- /**
1265
- * Sweep a convex shape along a ray and find the first body it
1266
- * would hit. The shape starts at `ray.origin` oriented by
1267
- * `rotation` and translates along `ray.direction` for up to
1268
- * `ray.tMax` units. Returns the nearest impact within that
1269
- * interval.
1270
- *
1271
- * The "swept AABB" broadphase finds candidate bodies whose BVH
1272
- * leaves overlap the shape's swept volume; the narrowphase then
1273
- * bisects [0, t] on GJK overlap to find the time-of-impact for
1274
- * each candidate. Best-t early termination skips candidates that
1275
- * can't tighten the answer.
1276
- *
1277
- * @param {Ray3} ray origin + unit direction + `tMax`
1278
- * @param {AbstractShape3D} shape
1279
- * @param {{x:number,y:number,z:number,w:number}} rotation
1280
- * @param {PhysicsSurfacePoint} result populated on hit; untouched on miss
1281
- * @param {(entity:number, collider:Collider)=>boolean} [filter]
1282
- * @returns {boolean}
1283
- */
1284
- shapeCast(ray, shape, rotation, result, filter = returnTrue) {
1285
- return shape_cast_query(this, ray, shape, rotation, result, filter);
1286
- }
1287
-
1288
- /**
1289
- * Speculative overlap query: find all bodies whose collider would
1290
- * overlap the given convex shape placed at the given world pose,
1291
- * without mutating the simulation. Intended for kinematic /
1292
- * character controllers that need to test "would I collide if I
1293
- * moved here?" before committing the move.
1294
- *
1295
- * Pipeline:
1296
- * 1. The shape's world AABB is computed from `position` + `rotation`.
1297
- * 2. Both broadphase trees (static + dynamic) are queried for
1298
- * bodies whose leaf AABB overlaps that envelope.
1299
- * 3. Each candidate is GJK-tested in world frame. Convex
1300
- * candidates run one GJK call; concave candidates (heightmap,
1301
- * mesh) run per-triangle GJK via the decomposition path.
1302
- * 4. The optional `filter` is consulted before the GJK test
1303
- * useful for skipping the caller's own body, allies, sensors,
1304
- * etc.
1305
- *
1306
- * The output buffer is filled with overlapping bodies' `body_id`
1307
- * values (uint32 with packed generation), starting at
1308
- * `output_offset`. The caller is responsible for sizing the buffer;
1309
- * IDs past its end are dropped silently and the count caps at the
1310
- * available space.
1311
- *
1312
- * The query shape must be convex. Concave query shapes throw —
1313
- * they're typically static terrain and not used as kinematic
1314
- * probes; the M×N triangle-pair cost wouldn't be worth the rare
1315
- * use case.
1316
- *
1317
- * @param {AbstractShape3D} shape convex query shape, in its local frame
1318
- * @param {{x:number,y:number,z:number}} position world position of
1319
- * the query shape
1320
- * @param {{x:number,y:number,z:number,w:number}} rotation world
1321
- * rotation (unit quaternion)
1322
- * @param {Uint32Array|number[]} output buffer to receive body_ids
1323
- * @param {number} output_offset starting index in output
1324
- * @param {(entity:number, collider:Collider)=>boolean} [filter]
1325
- * defaults to {@link returnTrue}
1326
- * @returns {number} number of overlapping bodies written
1327
- */
1328
- overlap(shape, position, rotation, output, output_offset, filter = returnTrue) {
1329
- return overlap_shape_query(this, shape, position, rotation, output, output_offset, filter);
1330
- }
1331
-
1332
- /**
1333
- * Wake propagation through contact pairs. Runs BEFORE broadphase so a
1334
- * body woken here participates in THIS frame's broadphase / narrowphase
1335
- * / islands end to end it never simulates a step without its support
1336
- * contacts (free-fall penetration pop).
1337
- *
1338
- * Reads ONLY persistent manifold state (the slots whose pair the
1339
- * broadphase touched last frame — `prev_touched`), never the transient
1340
- * pair list: manifold state is part of the engine's exportable contact
1341
- * state, so a reconstructed engine wakes on exactly the original's
1342
- * schedule.
1343
- *
1344
- * A sleeper wakes when:
1345
- * - its pair holds REAL contacts (`contact_count > 0`), unless the
1346
- * awake side is a PARKED non-Dynamic (a stationary kinematic platform
1347
- * or a static sleepState of either is never updated — transmits no
1348
- * motion; the moment it gets velocity, the riders wake); or
1349
- * - the pair has no contacts yet but the awake side's velocity-swept
1350
- * tight AABB just REACHED the sleeper's tight AABB (false→true edge
1351
- * of {@link __swept_proximity}) one precautionary wake so an
1352
- * incoming body never spends its first touching frame against a
1353
- * non-integrating sleeper, over-embedding by v·dt. Edge-triggered on
1354
- * a pure function of body state: pair CREATION time (the old
1355
- * trigger) depends on broadphase fat-box history, which a
1356
- * reconstructed engine does not share; and level-triggered proximity
1357
- * would re-wake a pile next to a hovering body every frame. Slot
1358
- * existence alone wakes nothing.
1359
- * @private
1360
- * @param {number} dt step size — extent of the predicate's velocity sweep
1361
- */
1362
- __wake_pairs(dt) {
1363
- const bodies = this.__bodies;
1364
- const storage = this.storage;
1365
- const manifolds = this.manifolds;
1366
- const live_count = manifolds.count;
1367
- for (let i = 0; i < live_count; i++) {
1368
- const slot = manifolds.live_at(i);
1369
- // "Was in last frame's broadphase": acquire set touched, the
1370
- // end-of-step advance rolled it to prev_touched. (A dormant
1371
- // slot can carry a stale prev_touched from its sleep frame —
1372
- // the both-asleep guard below skips it.)
1373
- if (!manifolds.was_touched_prev(slot)) continue;
1374
-
1375
- const idA = manifolds.bodyA(slot);
1376
- const idB = manifolds.bodyB(slot);
1377
- // Stale-slot guards: a body may have been unlinked since (and
1378
- // its index reused by an unrelated body).
1379
- if (!storage.is_valid(idA) || !storage.is_valid(idB)) continue;
1380
- const a = bodies[body_id_index(idA)];
1381
- const b = bodies[body_id_index(idB)];
1382
- if (a === undefined || b === undefined) continue;
1383
-
1384
- const aSleep = a.sleepState === SleepState.Sleeping;
1385
- const bSleep = b.sleepState === SleepState.Sleeping;
1386
- if (aSleep === bSleep) continue; // both awake, or a dormant pair
1387
-
1388
- if (manifolds.contact_count(slot) > 0) {
1389
- const mover = aSleep ? b : a;
1390
- if (mover.kind !== BodyKind.Dynamic) {
1391
- const lv = mover.linearVelocity;
1392
- const av = mover.angularVelocity;
1393
- if (lv[0] === 0 && lv[1] === 0 && lv[2] === 0
1394
- && av[0] === 0 && av[1] === 0 && av[2] === 0) {
1395
- continue; // parked platform / static — transmits no motion
1396
- }
1397
- }
1398
- this.__wake_body(aSleep ? a : b);
1399
- } else {
1400
- // No contacts: pre-contact precautionary wake on the
1401
- // proximity EDGE. The predicate needs no parked exemption
1402
- // a zero-velocity mover's swept box is its tight box, so it
1403
- // only fires on actual tight-box overlap, once.
1404
- const mover_idx = body_id_index(aSleep ? idB : idA);
1405
- const sleeper_idx = body_id_index(aSleep ? idA : idB);
1406
- const prox = this.__swept_proximity(mover_idx, sleeper_idx, dt);
1407
- const had_prox = manifolds.is_prox(slot);
1408
- manifolds.set_prox(slot, prox);
1409
- if (prox && !had_prox) {
1410
- this.__wake_body(aSleep ? a : b);
1411
- }
1412
- }
1413
- }
1414
- }
1415
-
1416
- /**
1417
- * Stage-0 pre-contact wake predicate: does any of `mover`'s collider
1418
- * tight AABBs, swept directionally by the mover's linear velocity over
1419
- * `dt`, overlap any of `sleeper`'s collider tight AABBs?
1420
- *
1421
- * A pure function of CURRENT body state (poses, shapes, velocity) — no
1422
- * broadphase or pad history so every engine holding the same bodies
1423
- * evaluates it identically. Conservative containment: the swept box is
1424
- * inside the mover's stage-2 fat box (pad = 2·|v|·dt + slack ⊇ v·dt)
1425
- * and the sleeper's tight box is inside its stored leaf, so whenever
1426
- * the predicate is true the broadphase has the pair THIS frame in any
1427
- * engine the manifold slot the wake stage needs is guaranteed to
1428
- * exist on the frame the edge fires.
1429
- *
1430
- * Linear sweep only (matching the broadphase pad): a purely angular
1431
- * approach is woken one frame later by its first actual contact.
1432
- *
1433
- * @private
1434
- * @param {number} mover_idx body index of the awake side
1435
- * @param {number} sleeper_idx body index of the sleeping side
1436
- * @param {number} dt
1437
- * @returns {boolean}
1438
- */
1439
- __swept_proximity(mover_idx, sleeper_idx, dt) {
1440
- const mover_list = this.__body_collider_lists[mover_idx];
1441
- const sleeper_list = this.__body_collider_lists[sleeper_idx];
1442
- if (mover_list === undefined || sleeper_list === undefined) return false;
1443
-
1444
- const lv = this.__bodies[mover_idx].linearVelocity;
1445
- const px = lv[0] * dt;
1446
- const py = lv[1] * dt;
1447
- const pz = lv[2] * dt;
1448
-
1449
- for (let i = 0; i < mover_list.length; i++) {
1450
- const m = mover_list[i];
1451
- if (m.bvhNode === COLLIDER_UNBOUND) continue;
1452
- compute_world_aabb(scratch_prox_mover, 0, m.collider.shape, m.transform);
1453
- if (px < 0) scratch_prox_mover[0] += px; else scratch_prox_mover[3] += px;
1454
- if (py < 0) scratch_prox_mover[1] += py; else scratch_prox_mover[4] += py;
1455
- if (pz < 0) scratch_prox_mover[2] += pz; else scratch_prox_mover[5] += pz;
1456
-
1457
- for (let k = 0; k < sleeper_list.length; k++) {
1458
- const s = sleeper_list[k];
1459
- if (s.bvhNode === COLLIDER_UNBOUND) continue;
1460
- compute_world_aabb(scratch_prox_sleeper, 0, s.collider.shape, s.transform);
1461
- if (scratch_prox_mover[0] <= scratch_prox_sleeper[3]
1462
- && scratch_prox_mover[3] >= scratch_prox_sleeper[0]
1463
- && scratch_prox_mover[1] <= scratch_prox_sleeper[4]
1464
- && scratch_prox_mover[4] >= scratch_prox_sleeper[1]
1465
- && scratch_prox_mover[2] <= scratch_prox_sleeper[5]
1466
- && scratch_prox_mover[5] >= scratch_prox_sleeper[2]) {
1467
- return true;
1468
- }
1469
- }
1470
- }
1471
- return false;
1472
- }
1473
-
1474
- /**
1475
- * Wake propagation across joints: if a joint connects an awake body to a
1476
- * sleeping one, wake the sleeper so the constraint stays coupled (a joint
1477
- * with one body asleep and one awake would otherwise be skipped by the
1478
- * solver, letting the awake side drift). A common trigger is a
1479
- * kinematic/motor-driven body pulling on a sleeping chain.
1480
- *
1481
- * Bodies that slept together as one island share a sleep group, so the
1482
- * usual atomic wake already revives the whole chain when any member is
1483
- * hit; this catches the cases that atomic wake doesn't (joint spanning
1484
- * separate groups, kinematic driver).
1485
- * @private
1486
- */
1487
- __wake_joints() {
1488
- const joints = this.__joints;
1489
- const n = joints.length;
1490
- if (n === 0) return;
1491
- const bodies = this.__bodies;
1492
- const storage = this.storage;
1493
- for (let i = 0; i < n; i++) {
1494
- const joint = joints[i];
1495
- if (joint === undefined || joint === null) continue;
1496
- if (joint._bodyIdB === JOINT_WORLD) continue;
1497
- if (!storage.is_valid(joint._bodyIdA) || !storage.is_valid(joint._bodyIdB)) continue;
1498
- const a = bodies[body_id_index(joint._bodyIdA)];
1499
- const b = bodies[body_id_index(joint._bodyIdB)];
1500
- if (a === undefined || b === undefined) continue;
1501
- const aSleep = a.sleepState === SleepState.Sleeping;
1502
- const bSleep = b.sleepState === SleepState.Sleeping;
1503
- if (aSleep === bSleep) continue; // both awake or both asleep — leave as is
1504
-
1505
- // The "awake" side must be capable of pulling the sleeper. A
1506
- // static anchor's sleepState is never updated (it reads Awake
1507
- // forever), so without this rule a body jointed to a static could
1508
- // never sleep — perpetual mismatch wake. A kinematic driver
1509
- // counts only while it is actually moving: a parked motor leaves
1510
- // its chain asleep, and starting it wakes the chain (same motion
1511
- // rule as __wake_pairs).
1512
- const mover = aSleep ? b : a;
1513
- if (mover.kind !== BodyKind.Dynamic) {
1514
- const lv = mover.linearVelocity;
1515
- const av = mover.angularVelocity;
1516
- if (lv[0] === 0 && lv[1] === 0 && lv[2] === 0
1517
- && av[0] === 0 && av[1] === 0 && av[2] === 0) {
1518
- continue;
1519
- }
1520
- }
1521
- if (aSleep) this.__wake_body(a); else this.__wake_body(b);
1522
- }
1523
- }
1524
-
1525
- /**
1526
- * Per-island atomic sleep test. Walks each island once and applies the
1527
- * decision uniformly across all members:
1528
- *
1529
- * - If any member carries {@link RigidBodyFlags.DisableSleep}, the
1530
- * entire island is exempt; every member's sleep_timer is reset.
1531
- * - If `max(|v|² + |ω|²)` across all members is below
1532
- * {@link sleepVelocitySqrThreshold}, every member's sleep_timer is
1533
- * incremented by `dt`. When the smallest member's timer crosses
1534
- * {@link sleepTimeThreshold}, the whole island sleeps atomically in
1535
- * the same step (members get threaded into a sleep-group chain so
1536
- * {@link __wake_body} can wake them all in one call).
1537
- * - Otherwise the island has at least one active member, so every
1538
- * member's timer is reset.
1539
- *
1540
- * This is the design-plan atomic-island sleep — replaces the per-body
1541
- * approximation that lived in this slot during the previous slice.
1542
- * Weakly-connected piles no longer chatter awake when a single member
1543
- * blips above threshold; piles fall asleep and wake up as one.
1544
- *
1545
- * @private
1546
- * @param {number} dt
1547
- */
1548
- __sleep_test(dt) {
1549
- const threshold_sqr = this.sleepVelocitySqrThreshold;
1550
- const time_threshold = this.sleepTimeThreshold;
1551
- const bodies = this.__bodies;
1552
- const islands = this.islands;
1553
- const island_count = islands.island_count;
1554
- const body_offsets = islands.body_offsets;
1555
- const body_data = islands.body_data;
1556
-
1557
- for (let isl = 0; isl < island_count; isl++) {
1558
- const start = body_offsets[isl];
1559
- const end = body_offsets[isl + 1];
1560
- if (end === start) continue;
1561
-
1562
- // Pass 1: find max v² + check DisableSleep across the island.
1563
- let max_v_sqr = 0;
1564
- let any_disable_sleep = false;
1565
- for (let i = start; i < end; i++) {
1566
- const idx = body_data[i];
1567
- const rb = bodies[idx];
1568
- if (rb === undefined) continue;
1569
- if ((rb.flags & RigidBodyFlags.DisableSleep) !== 0) {
1570
- any_disable_sleep = true;
1571
- break;
1572
- }
1573
- const lv = rb.linearVelocity;
1574
- const av = rb.angularVelocity;
1575
- const v_sqr = lv[0] * lv[0] + lv[1] * lv[1] + lv[2] * lv[2]
1576
- + av[0] * av[0] + av[1] * av[1] + av[2] * av[2];
1577
- if (v_sqr > max_v_sqr) max_v_sqr = v_sqr;
1578
- }
1579
-
1580
- if (any_disable_sleep) {
1581
- // Whole island is exempt reset every member's timer.
1582
- for (let i = start; i < end; i++) {
1583
- const rb = bodies[body_data[i]];
1584
- if (rb !== undefined) rb.sleep_timer = 0;
1585
- }
1586
- continue;
1587
- }
1588
-
1589
- if (max_v_sqr < threshold_sqr) {
1590
- // Island is at rest — increment every member's timer; if the
1591
- // slowest-stabilising member has crossed the time threshold,
1592
- // every member has (they were incremented together this step),
1593
- // so atomic-sleep the island.
1594
- let min_timer = Infinity;
1595
- for (let i = start; i < end; i++) {
1596
- const rb = bodies[body_data[i]];
1597
- if (rb === undefined) continue;
1598
- rb.sleep_timer += dt;
1599
- if (rb.sleep_timer < min_timer) min_timer = rb.sleep_timer;
1600
- }
1601
- if (min_timer >= time_threshold) {
1602
- this.__atomic_sleep_island_range(body_data, start, end);
1603
- }
1604
- } else {
1605
- // At least one member is active — reset every timer.
1606
- for (let i = start; i < end; i++) {
1607
- const rb = bodies[body_data[i]];
1608
- if (rb !== undefined) rb.sleep_timer = 0;
1609
- }
1610
- }
1611
- }
1612
- }
1613
-
1614
- /**
1615
- * Dispatch every buffered contact event to the entities involved, via the
1616
- * dataset's per-entity event channel — `dataset.sendEvent(entity, name,
1617
- * payload)`, once for each of the pair's entities. This is the only
1618
- * contact-event path: a system with no dataset attached has nowhere to
1619
- * deliver and emits nothing.
1620
- *
1621
- * Payload is a reused scratch object; listeners must copy anything they
1622
- * intend to retain past the listener body.
1623
- * @private
1624
- */
1625
- __dispatch_contact_events() {
1626
- const events = this.contactEvents;
1627
- const n = events.count;
1628
- if (n === 0) return;
1629
-
1630
- const ecd = (this.entityManager !== null && this.entityManager !== undefined)
1631
- ? this.entityManager.dataset
1632
- : null;
1633
- // No dataset nowhere to deliver. The buffer is cleared by
1634
- // diff_manifolds each step regardless, so nothing leaks.
1635
- if (ecd === null || ecd === undefined) return;
1636
-
1637
- const manifolds = this.manifolds;
1638
- const data = manifolds.data_buffer;
1639
-
1640
- const payload = this.__contact_payload;
1641
-
1642
- for (let i = 0; i < n; i++) {
1643
- const kind = events.kind_at(i);
1644
- const entA = events.entityA_at(i);
1645
- const entB = events.entityB_at(i);
1646
- const slot = events.slot_at(i);
1647
-
1648
- // Use the deepest contact of the manifold as the representative
1649
- // point/normal/depth for the event. v1: contact 0 only.
1650
- const slot_off = manifolds.slot_data_offset(slot);
1651
- const has_contact = manifolds.contact_count(slot) > 0;
1652
-
1653
- // Scratch payload — write the point/normal components by index to
1654
- // skip the Vector3 change-signal dispatch (nothing observes them).
1655
- const pt = payload.point;
1656
- const nm = payload.normal;
1657
- if (has_contact) {
1658
- const wax = data[slot_off], way = data[slot_off + 1], waz = data[slot_off + 2];
1659
- const wbx = data[slot_off + 3], wby = data[slot_off + 4], wbz = data[slot_off + 5];
1660
- pt[0] = (wax + wbx) * 0.5;
1661
- pt[1] = (way + wby) * 0.5;
1662
- pt[2] = (waz + wbz) * 0.5;
1663
- nm[0] = data[slot_off + 6];
1664
- nm[1] = data[slot_off + 7];
1665
- nm[2] = data[slot_off + 8];
1666
- payload.depth = data[slot_off + 9];
1667
-
1668
- // The event pair is entity-canonical (min, max); the slot's
1669
- // stored normal points from the slot's OWN body B toward its
1670
- // body A — an assignment that follows packed-id order, i.e.
1671
- // slot allocation history. Mirror the normal when the
1672
- // canonicalization swapped the pair, so the payload contract
1673
- // ("normal points from entityB toward entityA") holds
1674
- // identically in every engine regardless of slot history.
1675
- const slot_idA = manifolds.bodyA(slot);
1676
- const slot_entA = this.storage.is_valid(slot_idA)
1677
- ? this.storage.entity_at(body_id_index(slot_idA))
1678
- : -1;
1679
- if (slot_entA !== entA) {
1680
- nm[0] = -nm[0];
1681
- nm[1] = -nm[1];
1682
- nm[2] = -nm[2];
1683
- }
1684
- } else {
1685
- pt[0] = 0;
1686
- pt[1] = 0;
1687
- pt[2] = 0;
1688
- nm[0] = 0;
1689
- nm[1] = 0;
1690
- nm[2] = 0;
1691
- payload.depth = 0;
1692
- }
1693
- payload.entityA = entA;
1694
- payload.entityB = entB;
1695
-
1696
- let event_name;
1697
- if (kind === ContactEventKind.Begin) {
1698
- event_name = PhysicsEvents.ContactBegin;
1699
- } else if (kind === ContactEventKind.Stay) {
1700
- event_name = PhysicsEvents.ContactStay;
1701
- } else {
1702
- event_name = PhysicsEvents.ContactEnd;
1703
- }
1704
-
1705
- if (entA >= 0) ecd.sendEvent(entA, event_name, payload);
1706
- if (entB >= 0) ecd.sendEvent(entB, event_name, payload);
1707
- }
1708
- }
1709
-
1710
- /**
1711
- * Producer restore pass. At the top of a fixed step, reset every awake
1712
- * {@link Interpolated} body's live components to their authoritative state
1713
- * from the previous tick's snapshot, undoing any render-time interpolation
1714
- * the {@link InterpolationSystem} wrote between frames so the sim integrates
1715
- * from truth, not an interpolated pose. A body with no previous snapshot
1716
- * (first step ever, or just woken) is left as-is — its live state is already
1717
- * authoritative. No-op unless {@link interpolationLog} is wired.
1718
- * @private
1719
- */
1720
- __interp_restore() {
1721
- const log = this.interpolationLog;
1722
- const em = this.entityManager;
1723
- if (log === null || em === null || em === undefined) return;
1724
- const dataset = em.dataset;
1725
- if (dataset === null || dataset === undefined) return;
1726
-
1727
- const prev_tick = em.fixedStepTick - 1;
1728
- if (prev_tick < 0) return;
1729
-
1730
- const storage = this.storage;
1731
- const count = storage.awake_count;
1732
- const scratch = this.__interp_scratch;
1733
-
1734
- for (let i = 0; i < count; i++) {
1735
- const idx = storage.awake_at(i);
1736
- const entity = storage.entity_at(idx);
1737
- const interpolated = dataset.getComponent(entity, Interpolated);
1738
- if (interpolated === undefined || interpolated === null) continue;
1739
- // A teleported body (snap set, e.g. via setPose) keeps its live pose
1740
- // this step — restoring the previous tick would undo the teleport.
1741
- if (interpolated.snap) continue;
1742
- const key = interpolated.key;
1743
- if (key < 0) continue;
1744
-
1745
- const interpolands = interpolated.interpolands;
1746
- for (let k = 0; k < interpolands.length; k++) {
1747
- const ip = interpolands[k];
1748
- scratch.position = 0;
1749
- // Snap to the previous tick's snapshot (both offsets equal → t irrelevant).
1750
- const ok = log.interpolate(scratch, key, ip.type_id, prev_tick, prev_tick, 0, ip.interpolation_adapter);
1751
- if (!ok) continue;
1752
- const target = dataset.getComponent(entity, ip.component_class);
1753
- if (target === undefined || target === null) continue;
1754
- scratch.position = 0;
1755
- ip.serialization_adapter.deserialize(scratch, target);
1756
- }
1757
- }
1758
- }
1759
-
1760
- /**
1761
- * Producer — record pass. At the end of a fixed step, snapshot every awake
1762
- * {@link Interpolated} body's live components into the shared log under the
1763
- * current `entityManager.fixedStepTick`. The render-time
1764
- * {@link InterpolationSystem} blends consecutive ticks from these snapshots.
1765
- * Only awake (moving) bodies are recorded, so the log stays sparse. No-op
1766
- * unless {@link interpolationLog} is wired.
1767
- * @private
1768
- */
1769
- __interp_record() {
1770
- const log = this.interpolationLog;
1771
- const em = this.entityManager;
1772
- if (log === null || em === null || em === undefined) return;
1773
- const dataset = em.dataset;
1774
- if (dataset === null || dataset === undefined) return;
1775
-
1776
- const tick = em.fixedStepTick;
1777
- const storage = this.storage;
1778
- const count = storage.awake_count;
1779
-
1780
- log.begin_tick(tick);
1781
- for (let i = 0; i < count; i++) {
1782
- const idx = storage.awake_at(i);
1783
- const entity = storage.entity_at(idx);
1784
- const interpolated = dataset.getComponent(entity, Interpolated);
1785
- if (interpolated === undefined || interpolated === null) continue;
1786
- const key = interpolated.key;
1787
- if (key < 0) continue;
1788
-
1789
- const interpolands = interpolated.interpolands;
1790
- for (let k = 0; k < interpolands.length; k++) {
1791
- const ip = interpolands[k];
1792
- const target = dataset.getComponent(entity, ip.component_class);
1793
- if (target === undefined || target === null) continue;
1794
- const buf = log.begin_record(key, ip.type_id);
1795
- ip.serialization_adapter.serialize(buf, target);
1796
- log.end_record();
1797
- }
1798
- }
1799
- log.end_tick();
1800
- }
1801
-
1802
- fixedUpdate(dt) {
1803
- // Producer: restore authoritative pose (undo render interpolation)
1804
- // before the sim reads any Transform this step.
1805
- this.__interp_restore();
1806
-
1807
- const gx = this.gravity.x;
1808
- const gy = this.gravity.y;
1809
- const gz = this.gravity.z;
1810
-
1811
- const storage = this.storage;
1812
- const bodies = this.__bodies;
1813
- const transforms = this.__transforms;
1814
- const joints = this.__joints;
1815
- const manifolds = this.manifolds;
1816
-
1817
- // Stage 0: wake propagation — through the persistent manifolds
1818
- // (last frame's broadphase-touched pairs), then through joints.
1819
- // Runs before everything else so a just-woken body is part of this
1820
- // frame's awake set end to end (forces, refit, broadphase,
1821
- // narrowphase, islands, solve): waking later would give it one
1822
- // unsupported step (gravity with no contacts penetration pop).
1823
- // Reads only manifold + body state — both part of the exportable
1824
- // world state never the transient pair list, so a reconstructed
1825
- // engine wakes on the original's schedule.
1826
- this.__wake_pairs(dt);
1827
- this.__wake_joints();
1828
-
1829
- const count = storage.awake_count;
1830
-
1831
- // Stage 1: consume the per-frame force / torque accumulators into
1832
- // velocity at the full `dt`, exactly once (a user force is a per-frame
1833
- // budget that must land in full regardless of substep count). Gravity
1834
- // is applied per substep below, so the trajectory integrates at the
1835
- // substep rate and — crucially — each substep's gravity is balanced by
1836
- // that substep's contact warm-start, keeping resting stacks at zero
1837
- // velocity.
1838
- for (let i = 0; i < count; i++) {
1839
- const idx = storage.awake_at(i);
1840
-
1841
- const rb = bodies[idx];
1842
- const tr = transforms[idx];
1843
-
1844
- integrate_velocity_forces(rb, tr, dt);
1845
- }
1846
-
1847
- // Stage 2: refit each awake body's collider leaves at the current
1848
- // pose, padded by the swept extent for the body's velocity. The fat
1849
- // margin uses the post-force velocity; this frame's gravity increment
1850
- // is a sub-millimetre slack difference, safely inside the margin.
1851
- const lists = this.__body_collider_lists;
1852
- for (let i = 0; i < count; i++) {
1853
-
1854
- const idx = storage.awake_at(i);
1855
- const rb = bodies[idx];
1856
- const list = lists[idx];
1857
-
1858
- if (list === undefined) {
1859
- continue;
1860
- }
1861
-
1862
- const lv = rb.linearVelocity;
1863
-
1864
- const list_length = list.length;
1865
-
1866
- for (let k = 0; k < list_length; k++) {
1867
-
1868
- const entry = list[k];
1869
-
1870
- // Refit skip — the fat margin's whole purpose: while the
1871
- // body's FATTENED box (tight + velocity pad) stays inside the
1872
- // leaf's stored AABB, the stored box still covers everything
1873
- // the body can reach this step, so the set + refit-to-root is
1874
- // pure waste. A resting field pays only this containment test
1875
- // per leaf per frame (at v ≈ 0 the fat box is the tight box
1876
- // plus the constant slack, which the stored box includes).
1877
- //
1878
- // The containment probe MUST be the fat box, not the tight
1879
- // one: the leaf is how the broadphase and every swept query
1880
- // discover this body. A body whose velocity grew while its
1881
- // tight box still sat inside the stored leaf (freshly linked
1882
- // with a launch velocity, or accelerated by an impulse) would
1883
- // otherwise keep a leaf covering none of the path it travels
1884
- // this step — invisible to pair generation and to other
1885
- // bodies' CCD sweeps for the whole step.
1886
- compute_world_aabb(scratch_world_aabb, 0, entry.collider.shape, entry.transform);
1887
- fatten_world_aabb(scratch_world_aabb, 0, lv[0], lv[1], lv[2], dt);
1888
- const node = entry.bvhNode;
1889
- this.dynamicBvh.node_get_aabb(node, scratch_stored_aabb);
1890
- if (scratch_world_aabb[0] >= scratch_stored_aabb[0]
1891
- && scratch_world_aabb[1] >= scratch_stored_aabb[1]
1892
- && scratch_world_aabb[2] >= scratch_stored_aabb[2]
1893
- && scratch_world_aabb[3] <= scratch_stored_aabb[3]
1894
- && scratch_world_aabb[4] <= scratch_stored_aabb[4]
1895
- && scratch_world_aabb[5] <= scratch_stored_aabb[5]) {
1896
- continue;
1897
- }
1898
-
1899
- this.dynamicBvh.node_move_aabb(node, scratch_world_aabb);
1900
- }
1901
- }
1902
-
1903
- // Stage 3: broadphase pair generation. The fat AABBs cover the full
1904
- // outer-step motion, so the pair set stays valid across all substeps
1905
- // broadphase runs once.
1906
- generate_pairs(
1907
- storage,
1908
- this.dynamicBvh,
1909
- this.staticBvh,
1910
- manifolds,
1911
- lists,
1912
- this.pairs,
1913
- this.__pair_filter,
1914
- this,
1915
- );
1916
-
1917
- // Stage 5: narrowphase — once per outer step. The substep loop below
1918
- // re-derives each contact's penetration analytically from the moved
1919
- // poses rather than re-running geometry.
1920
- narrowphase_step(this.pairs, manifolds, this.__body_collider_lists);
1921
-
1922
- // Stage 6: partition awake bodies + touched contacts into islands.
1923
- // Consumed by the solver (flattened contact list) and the sleep test.
1924
- this.islands.build(storage, manifolds, bodies, this.__body_collider_lists, joints);
1925
-
1926
- // Stage 7: TGS substep loop.
1927
- //
1928
- // prepare_contacts captures per-contact anchors / effective masses /
1929
- // approach velocity (no warm-start — that's per-substep). Each substep
1930
- // integrates gravity by `h`, re-derives contact geometry from the
1931
- // current poses, replays warm-start, solves velocity (non-penetration
1932
- // + friction) and position (pseudo-velocity), and integrates the pose
1933
- // by `h`. Per-substep gravity + per-substep warm-start balance exactly
1934
- // at a resting contact (each cancels `h` of the other), so stacks hold
1935
- // at zero velocity and sleep; the position correction adapts as bodies
1936
- // separate between substeps — the TGS stack-stability mechanism —
1937
- // without re-running narrowphase. Restitution is applied once after
1938
- // the loop.
1939
- // Clamp the substep count: 0 (or a negative / fractional value) would
1940
- // feed h = dt / N = Infinity into prepare_contacts and a broken loop
1941
- // bound into the solver.
1942
- const N = this.substeps > 1 ? this.substeps | 0 : 1;
1943
- const h = dt / N;
1944
- const count_after_wake = storage.awake_count;
1945
-
1946
- // CCD: capture start-of-step positions for flagged bodies over the
1947
- // post-wake awake set (poses are unchanged until the substep loop below
1948
- // integrates them). The CCD pass after the solver sweeps from here to
1949
- // each body's final pose. Reads the primary collider's transform so the
1950
- // start matches the end the resolve pass reads. Zero-cost when no body
1951
- // is flagged.
1952
- const ccd_on = this.ccdEnabled;
1953
-
1954
- if (ccd_on) {
1955
-
1956
- const ccd_need = storage.high_water_mark * 3;
1957
-
1958
- if (this.__ccd_start_pos.length < ccd_need) {
1959
- this.__ccd_start_pos = new Float64Array(ccd_need);
1960
- }
1961
-
1962
- const ccd_start = this.__ccd_start_pos;
1963
-
1964
- for (let i = 0; i < count_after_wake; i++) {
1965
- const idx = storage.awake_at(i);
1966
- const rb = bodies[idx];
1967
-
1968
- if (rb.kind !== BodyKind.Dynamic) {
1969
- continue;
1970
- }
1971
- if ((rb.flags & RigidBodyFlags.CCD) === 0) {
1972
- continue;
1973
- }
1974
-
1975
- const list = this.__body_collider_lists[idx];
1976
-
1977
- if (list === undefined || list.length === 0) {
1978
- continue;
1979
- }
1980
-
1981
- const cp = list[0].transform.position;
1982
- const cb = idx * 3;
1983
-
1984
- ccd_start[cb] = cp[0];
1985
- ccd_start[cb + 1] = cp[1];
1986
- ccd_start[cb + 2] = cp[2];
1987
- }
1988
- }
1989
-
1990
- // Size the pseudo-velocity buffer ONCE (it may reallocate on growth),
1991
- // then capture the reference. Inside the loop we only zero its live
1992
- // region per substep re-capturing is unnecessary since it won't
1993
- // reallocate again this step.
1994
- this.__reset_pseudo_velocity();
1995
- const pseudoVel = this.__pseudo_velocity;
1996
- const pseudo_len = storage.high_water_mark * 6;
1997
-
1998
- // Gather the data-oriented solver state for every body the substep loop
1999
- // will touch: the post-wake awake set (all dynamics, at their post-force
2000
- // velocity) plus the static / kinematic anchors and jointed partners
2001
- // referenced by this step's contacts and joints. From here the substep
2002
- // loop reads / writes velocity + orientation through `ss_data` with no
2003
- // component-object dereference; persistent velocity is scattered back
2004
- // onto the RigidBodies after the solve.
2005
- const solver_state = this.__solver_state;
2006
- solver_state.begin(storage.high_water_mark);
2007
-
2008
- for (let i = 0; i < count_after_wake; i++) {
2009
- const idx = storage.awake_at(i);
2010
- solver_state.gather(idx, bodies[idx], transforms[idx]);
2011
- }
2012
-
2013
- const island_contacts = this.islands.contact_data;
2014
- const island_contact_total = this.islands.contact_offsets[this.islands.island_count];
2015
-
2016
- for (let i = 0; i < island_contact_total; i++) {
2017
- const slot = island_contacts[i];
2018
-
2019
- const ia = body_id_index(manifolds.bodyA(slot));
2020
- const ib = body_id_index(manifolds.bodyB(slot));
2021
-
2022
- solver_state.gather(ia, bodies[ia], transforms[ia]);
2023
- solver_state.gather(ib, bodies[ib], transforms[ib]);
2024
- }
2025
-
2026
- const joint_count = joints.length;
2027
- for (let i = 0; i < joint_count; i++) {
2028
- const joint = joints[i];
2029
-
2030
- if (joint === undefined || joint === null) {
2031
- continue;
2032
- }
2033
-
2034
- if (!storage.is_valid(joint._bodyIdA)) {
2035
- continue;
2036
- }
2037
-
2038
- const ia = body_id_index(joint._bodyIdA);
2039
-
2040
- solver_state.gather(ia, bodies[ia], transforms[ia]);
2041
-
2042
- if (joint._bodyIdB !== JOINT_WORLD && storage.is_valid(joint._bodyIdB)) {
2043
-
2044
- const ib = body_id_index(joint._bodyIdB);
2045
-
2046
- solver_state.gather(ib, bodies[ib], transforms[ib]);
2047
-
2048
- }
2049
- }
2050
-
2051
- const ss_data = solver_state.data;
2052
-
2053
- prepare_contacts(manifolds, this, h);
2054
-
2055
- for (let s = 0; s < N; s++) {
2056
-
2057
- // Gravity (+ damping) for this substep.
2058
- for (let i = 0; i < count_after_wake; i++) {
2059
- const idx = storage.awake_at(i);
2060
- integrate_velocity_gravity(ss_data, idx * SBS_STRIDE, bodies[idx], gx, gy, gz, h);
2061
- }
2062
-
2063
- // Re-derive contact geometry at the current poses: concave pairs
2064
- // re-run narrowphase (fresh feature/normal as the body rocks),
2065
- // convex pairs rotate frozen anchors analytically. Then replay
2066
- // the per-substep warm-start and solve velocity.
2067
- redetect_concave_contacts(manifolds, this);
2068
- refresh_contacts(manifolds, this.__transforms);
2069
- warm_start_contacts(manifolds, this);
2070
- solve_velocity(manifolds, this, this.velocityIterations);
2071
-
2072
- // Joints share the substep: warm-start + velocity-solve the 6-DOF
2073
- // constraints on real velocity, coupled with the contacts above
2074
- // (a body touched by both sees one substep of Gauss-Seidel across
2075
- // contacts then joints). Position correction for locked DOFs is a
2076
- // SPOOK bias inside this solve, so no separate joint position pass.
2077
- // Solved in the entity-canonical order (NOT joint-id order, which
2078
- // encodes link/unlink history) see __joints_sorted.
2079
- if (this.__joints_sorted.length > 0) {
2080
- // Alternate the joint sweep direction on odd substeps so the chain
2081
- // is solved root→tip and tip→root across the step (symmetric
2082
- // Gauss-Seidel), cancelling the one-direction propagation bias.
2083
- solve_joints(this.__joints_sorted, this, h, this.jointIterations, (s & 1) === 1);
2084
- }
2085
-
2086
- // Position correction writes pseudo-velocity (zeroed first so it
2087
- // is a fresh per-substep correction), folded into the pose by the
2088
- // position integrate and then discarded. SOFT contacts skip it:
2089
- // their penetration recovery is the velocity-bias term, and the
2090
- // relax pass below strips the momentum that bias injected
2091
- // (Box2D-v3 substep order).
2092
- const contacts_soft = this.contactHertz > 0;
2093
- pseudoVel.fill(0, 0, pseudo_len);
2094
- if (!contacts_soft) {
2095
- solve_position(manifolds, this, this.positionIterations);
2096
- }
2097
-
2098
- for (let i = 0; i < count_after_wake; i++) {
2099
- const idx = storage.awake_at(i);
2100
- const rb = bodies[idx];
2101
- const tr = transforms[idx];
2102
- const base = idx * 6;
2103
- integrate_position(ss_data, idx * SBS_STRIDE, rb, tr, h,
2104
- pseudoVel[base], pseudoVel[base + 1], pseudoVel[base + 2],
2105
- pseudoVel[base + 3], pseudoVel[base + 4], pseudoVel[base + 5]);
2106
- }
2107
-
2108
- // Relax pass (soft contacts only): one biasless sweep over the
2109
- // contact rows AFTER position integration, removing the real
2110
- // momentum the velocity-bias push injected this substep
2111
- // (Box2D-v3 substep order: solve -> integrate -> relax).
2112
- if (contacts_soft) {
2113
- solve_velocity(manifolds, this, 1, false);
2114
- }
2115
- }
2116
-
2117
- // Stage 8: one-shot restitution, after the substep loop, keyed off
2118
- // the approach velocity captured at prepare time.
2119
- apply_restitution(manifolds, this);
2120
-
2121
- // Scatter the solved persistent linear / angular velocity back onto the
2122
- // RigidBody components (pose was written through to the Transforms each
2123
- // substep). After this the bodies are authoritative again for CCD,
2124
- // interpolation recording, and the sleep test below.
2125
- solver_state.scatter(bodies);
2126
-
2127
- // Stage 8.5: continuous collision — sweep CCD-flagged fast movers along
2128
- // their net step translation and stop them at the first blocker, so they
2129
- // can't tunnel through thin geometry between discrete steps. Runs on the
2130
- // final post-solve poses, before the sleep test sees the clamped
2131
- // velocities. No-op when no awake body is flagged.
2132
- if (ccd_on) {
2133
- ccd_resolve(this);
2134
- }
2135
-
2136
- // Producer: record the post-step authoritative pose of every awake
2137
- // interpolated body under this step's tick. Before the sleep test, so a
2138
- // body that settles this step still records its final pose for the last
2139
- // interpolation interval.
2140
- this.__interp_record();
2141
-
2142
- // Stage 9: sleep test.
2143
- this.__sleep_test(dt);
2144
-
2145
- // Stage 10: diff manifolds against the previous frame and dispatch
2146
- // Begin / Stay / End events. MUST run before advance_frame, which
2147
- // rolls the touched flags.
2148
- diff_manifolds(manifolds, storage, this.contactEvents);
2149
- this.__dispatch_contact_events();
2150
-
2151
- // Stage 11 (end-of-step): roll touched prev_touched and the
2152
- // had-contacts flag, evict slots whose pair has not been touched
2153
- // within the grace window. Dormant pairs (both bodies asleep) are
2154
- // frozen contacts and warm-start impulses survive the nap.
2155
- manifolds.advance_frame(storage);
2156
-
2157
- // Dev tripwire (assert.* is compiled out of prod): no NaN / Infinity
2158
- // may survive a step in body state or contact manifolds. NaN is
2159
- // self-concealing downstream — it fails every depth comparison and
2160
- // sleep threshold so it must fail loudly here, at the step boundary.
2161
- assert.equal(find_non_finite_physics_state(this), "", "non-finite physics state after fixedUpdate");
2162
- }
2163
- }
2164
-
2165
- /**
2166
- * @readonly
2167
- * @type {boolean}
2168
- */
2169
- PhysicsSystem.prototype.isPhysicsSystem = true;
2170
-
2171
- // Re-export for convenience.
2172
- export { BodyKind, RigidBodyFlags };
1
+ import { assert } from "../../../core/assert.js";
2
+ import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
3
+ import { BVH } from "../../../core/bvh2/bvh3/BVH.js";
4
+ import { bvh_query_user_data_intersects_aabb } from "../../../core/bvh2/bvh3/query/bvh_query_user_data_intersects_aabb.js";
5
+ import { returnTrue } from "../../../core/function/returnTrue.js";
6
+ import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
7
+ import Vector3 from "../../../core/geom/Vector3.js";
8
+ import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js";
9
+ import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
10
+ import { System } from "../../ecs/System.js";
11
+ import { Transform } from "../../ecs/transform/Transform.js";
12
+ import { Interpolated } from "../../interpolation/Interpolated.js";
13
+ import { body_id_index, BodyStorage } from "../body/BodyStorage.js";
14
+ import { SBS_STRIDE, SolverBodyState } from "../body/SolverBodyState.js";
15
+ import { compute_world_aabb, fatten_world_aabb } from "../broadphase/compute_fat_world_aabb.js";
16
+ import { generate_pairs } from "../broadphase/generate_pairs.js";
17
+ import { PairList } from "../broadphase/PairList.js";
18
+ import { ccd_resolve } from "../ccd/linear_sweep.js";
19
+ import { solve_joints } from "../constraint/solve_constraints.js";
20
+ import { ManifoldStore } from "../contact/ManifoldStore.js";
21
+ import { ContactEventBuffer, ContactEventKind } from "../events/ContactEventBuffer.js";
22
+ import { diff_manifolds } from "../events/diff_manifolds.js";
23
+ import { world_inverse_inertia_apply } from "../inertia/world_inverse_inertia.js";
24
+ import { integrate_position } from "../integration/integrate_position.js";
25
+ import { integrate_velocity_forces, integrate_velocity_gravity } from "../integration/integrate_velocity.js";
26
+ import { IslandBuilder } from "../island/IslandBuilder.js";
27
+ import { narrowphase_step } from "../narrowphase/narrowphase_step.js";
28
+ import { overlap_shape as overlap_shape_query } from "../queries/overlap_shape.js";
29
+ import { raycast as raycast_query } from "../queries/raycast.js";
30
+ import { shape_cast as shape_cast_query } from "../queries/shape_cast.js";
31
+ import {
32
+ apply_restitution,
33
+ prepare_contacts,
34
+ redetect_concave_contacts,
35
+ refresh_contacts,
36
+ solve_position,
37
+ solve_velocity,
38
+ warm_start_contacts,
39
+ } from "../solver/solve_contacts.js";
40
+ import { BodyKind } from "./BodyKind.js";
41
+ import { Collider, COLLIDER_UNBOUND } from "./Collider.js";
42
+ import { find_non_finite_physics_state } from "./find_non_finite_physics_state.js";
43
+ import { JOINT_UNALLOCATED, JOINT_WORLD } from "./Joint.js";
44
+ import { PhysicsEvents } from "./PhysicsEvents.js";
45
+ import { RIGID_BODY_UNALLOCATED, RigidBody } from "./RigidBody.js";
46
+ import { RigidBodyFlags } from "./RigidBodyFlags.js";
47
+ import { SleepState } from "./SleepState.js";
48
+ import { is_sensor } from "./is_sensor.js";
49
+
50
+ /**
51
+ * Scratch for {@link applyImpulseAt}'s angular delta calculation.
52
+ * @type {Float64Array}
53
+ */
54
+ const scratch_angular_delta = new Float64Array(3);
55
+
56
+ /**
57
+ * Scratch for {@link applyImpulseAt}'s LockRot*-masked inverse-inertia
58
+ * diagonal (the same mask {@link SolverBodyState#gather} applies).
59
+ * @type {{x: number, y: number, z: number}}
60
+ */
61
+ const scratch_locked_inertia = { x: 0, y: 0, z: 0 };
62
+
63
+ /**
64
+ * Reusable scratch buffer for world-AABB construction so the link path is
65
+ * allocation-free in steady state.
66
+ * @type {Float64Array}
67
+ */
68
+ const scratch_world_aabb = new Float64Array(6);
69
+
70
+ /**
71
+ * Reusable scratch buffer for the local AABB returned by
72
+ * {@link AbstractShape3D#compute_bounding_box}.
73
+ * @type {Float64Array}
74
+ */
75
+ const scratch_local_aabb = new Float64Array(6);
76
+
77
+ /**
78
+ * Candidate buffer for {@link PhysicsSystem#__wake_sleepers_overlapping_leaf}.
79
+ * Grows by doubling: a large removed support (a level floor) can overlap many
80
+ * sleeping bodies, all of which must wake.
81
+ * @type {Uint32Array}
82
+ */
83
+ let scratch_wake_candidates = new Uint32Array(64);
84
+
85
+ /**
86
+ * Scratch for a leaf's currently-stored fat AABB during the stage-2
87
+ * containment test (refit skip).
88
+ * @type {Float64Array}
89
+ */
90
+ const scratch_stored_aabb = new Float64Array(6);
91
+
92
+ /**
93
+ * Scratch boxes for {@link PhysicsSystem#__swept_proximity} — the mover's
94
+ * velocity-swept tight AABB and the sleeper's tight AABB.
95
+ * @type {Float64Array}
96
+ */
97
+ const scratch_prox_mover = new Float64Array(6);
98
+ const scratch_prox_sleeper = new Float64Array(6);
99
+
100
+ /**
101
+ * Rigid-body physics system.
102
+ *
103
+ * v1 scope: pool + active list + two BVHs (static / dynamic), Transform sync
104
+ * contract, and the user-facing API surface (gravity, force / impulse, layer
105
+ * filtering, wake / sleep, contact filter). The per-step simulation pipeline
106
+ * (broadphase pair generation, narrowphase, solver, islands, sleep test,
107
+ * contact-event emission) is built on top of this skeleton in subsequent
108
+ * iterations.
109
+ *
110
+ * Dependency tuple is `(RigidBody, Collider, Transform)` every body has
111
+ * exactly one collider on the same entity. Compound bodies via child collider
112
+ * entities are an extension point handled by a follow-up sub-observer.
113
+ *
114
+ * @author Alex Goldring
115
+ * @copyright Company Named Limited (c) 2026
116
+ */
117
+ export class PhysicsSystem extends System {
118
+
119
+ constructor() {
120
+ super();
121
+
122
+ this.dependencies = [RigidBody, Transform];
123
+
124
+ this.components_used = [
125
+ ResourceAccessSpecification.from(RigidBody, ResourceAccessKind.Read | ResourceAccessKind.Write),
126
+ ResourceAccessSpecification.from(Collider, ResourceAccessKind.Read),
127
+ ResourceAccessSpecification.from(Transform, ResourceAccessKind.Read | ResourceAccessKind.Write),
128
+ ];
129
+
130
+ /**
131
+ * @type {BodyStorage}
132
+ * @readonly
133
+ */
134
+ this.storage = new BodyStorage();
135
+
136
+ /**
137
+ * @type {BVH}
138
+ */
139
+ this.staticBvh = new BVH();
140
+
141
+ /**
142
+ * @type {BVH}
143
+ */
144
+ this.dynamicBvh = new BVH();
145
+
146
+ /**
147
+ * Persistent contact-manifold cache. One slot per active pair.
148
+ * @type {ManifoldStore}
149
+ * @readonly
150
+ */
151
+ this.manifolds = new ManifoldStore();
152
+
153
+ /**
154
+ * Per-frame list of broadphase-overlapping pairs, each in
155
+ * entity-canonical role order (A = the smaller entity id see
156
+ * generate_pairs). Cleared at the top of each step; purely
157
+ * intra-step state.
158
+ * @type {PairList}
159
+ */
160
+ this.pairs = new PairList();
161
+
162
+ /**
163
+ * Per-frame contact-event buffer. Populated by the manifold diff
164
+ * pass at end-of-step and consumed by the dispatch pass.
165
+ * @type {ContactEventBuffer}
166
+ */
167
+ this.contactEvents = new ContactEventBuffer();
168
+
169
+ /**
170
+ * Per-frame island partitioning of the awake-body + contact graph.
171
+ * Rebuilt after narrowphase, consumed by the solver (and, in the
172
+ * follow-up slice, by the per-island atomic sleep test). Bodies
173
+ * static and kinematic act as constraint anchors and do not enlarge
174
+ * islands.
175
+ * @type {IslandBuilder}
176
+ */
177
+ this.islands = new IslandBuilder();
178
+
179
+ /**
180
+ * Data-oriented mirror of the per-body solver hot state (velocity,
181
+ * inverse mass / inertia, orientation), packed into one ArrayBuffer
182
+ * and indexed by body slot. Gathered from the `RigidBody` /
183
+ * `Transform` components once per step (after islands are built),
184
+ * mutated in place by the TGS substep loop, and the persistent
185
+ * velocity scattered back at the end. Keeps the solver's hottest inner
186
+ * loop free of component-object dereferences.
187
+ * @type {SolverBodyState}
188
+ */
189
+ this.__solver_state = new SolverBodyState();
190
+
191
+ /**
192
+ * Velocity-squared threshold below which a body is eligible to start
193
+ * accumulating sleep time. Combined linear + angular kinetic-ish
194
+ * metric: `vx²+vy²+vz² + ωx²+ωy²+ωz²`. Default 0.01 corresponds to
195
+ * ~0.1 m/s linear or ~0.1 rad/s angular.
196
+ * @type {number}
197
+ */
198
+ this.sleepVelocitySqrThreshold = 0.01;
199
+
200
+ /**
201
+ * Seconds of below-threshold motion before a body is moved to the
202
+ * sleeping set. Box2D default is 0.5 s.
203
+ * @type {number}
204
+ */
205
+ this.sleepTimeThreshold = 0.5;
206
+
207
+ /**
208
+ * Number of TGS substeps per `fixedUpdate`. Each substep re-runs the
209
+ * velocity + position solve at `dt / substeps` against contacts whose
210
+ * penetration is re-derived analytically from the bodies' moved poses
211
+ * narrowphase still runs once per outer step. Higher counts buy
212
+ * stack stability, high-mass-ratio robustness, and smoother
213
+ * trajectories at a near-linear solver cost (sleeping islands are
214
+ * unaffected — they never enter the loop).
215
+ *
216
+ * `1` reproduces the non-substepped (single-step) solve.
217
+ * @type {number}
218
+ */
219
+ this.substeps = 4;
220
+
221
+ /**
222
+ * Velocity iterations per substep. Lower than a single-step solver
223
+ * would need, because the substep loop revisits the contact set
224
+ * `substeps` times.
225
+ * @type {number}
226
+ */
227
+ this.velocityIterations = 4;
228
+
229
+ /**
230
+ * Position (split-impulse) iterations per substep.
231
+ * @type {number}
232
+ */
233
+ this.positionIterations = 1;
234
+
235
+ /**
236
+ * Contact softness frequency, Hz (Box2D-v3 soft step, hybrid form:
237
+ * the velocity rows get the hertz/ζ-derived mass + impulse-decay
238
+ * scales; penetration recovery stays in the split-impulse position
239
+ * pass). The decay term is what lets resting stacks converge
240
+ * asymptotically instead of ringing in the hard form's limit cycle
241
+ * just above the sleep threshold. Clamped per step to an eighth of
242
+ * the substep rate. `0` = hard contacts (bit-exact legacy rows).
243
+ * Pairs with a non-dynamic side use this frequency (stiffer, so
244
+ * piles don't sag into the ground). @type {number}
245
+ */
246
+ this.contactHertz = 30;
247
+
248
+ /**
249
+ * Contact damping ratio ζ for the soft scales — heavily over-damped
250
+ * by design (Box2D v3 default), so contacts never oscillate.
251
+ * @type {number}
252
+ */
253
+ this.contactDampingRatio = 10;
254
+
255
+ /**
256
+ * Reusable contact-event payload. Listeners must copy any fields they
257
+ * intend to retain past their own scope. Reset before each dispatch.
258
+ * @private
259
+ */
260
+ this.__contact_payload = {
261
+ entityA: -1,
262
+ entityB: -1,
263
+ kind: 0,
264
+ depth: 0,
265
+ normal: new Vector3(),
266
+ point: new Vector3(),
267
+ };
268
+
269
+ /**
270
+ * World gravity, m/s². Applied each step scaled by per-body gravityScale.
271
+ * @readonly
272
+ * @type {Vector3}
273
+ */
274
+ this.gravity = new Vector3(0, -9.81, 0);
275
+
276
+ /**
277
+ * Optional global contact filter. Called for each surviving broadphase
278
+ * pair; returning `false` discards the pair.
279
+ * @type {((entityA: number, entityB: number, colliderA: Collider, colliderB: Collider) => boolean) | null}
280
+ * @private
281
+ */
282
+ this.__contact_filter = null;
283
+
284
+ /**
285
+ * Per-body component side-tables indexed by body index (NOT packed id).
286
+ * Sparse arrays — populated at link, cleared at unlink. The hot loop
287
+ * iterates `storage.awake_at(i)` and dereferences these.
288
+ * @type {RigidBody[]}
289
+ */
290
+ this.__bodies = [];
291
+
292
+ /**
293
+ * @type {Transform[]}
294
+ * @readonly
295
+ */
296
+ this.__transforms = [];
297
+
298
+ /**
299
+ * Per-body list of attached colliders. Each entry stores the
300
+ * Collider component, its world Transform, the entity that owns
301
+ * it (the body entity for same-entity attachments, a child entity
302
+ * for compound bodies), and the BVH leaf id assigned at attach
303
+ * time. A body may have zero or more attached colliders.
304
+ *
305
+ * @type {Array<Array<{collider: Collider, transform: Transform, entity: number, bvhNode: number}>>}
306
+ */
307
+ this.__body_collider_lists = [];
308
+
309
+ /**
310
+ * Live {@link Joint} (6-DOF constraint) instances, in a sparse array
311
+ * indexed by joint id. Solved alongside contacts inside the TGS
312
+ * substep loop. Holes (unlinked joints) are `undefined`.
313
+ * @type {Joint[]}
314
+ */
315
+ this.__joints = [];
316
+
317
+ /**
318
+ * Lowest free index in {@link __joints} for slot reuse on link.
319
+ * @private
320
+ * @type {number}
321
+ */
322
+ this.__joint_free = [];
323
+
324
+ /**
325
+ * Live joints in CANONICAL SOLVE ORDER: ascending `(entityA,
326
+ * entityB)`, equal keys in link order. The id-indexed
327
+ * {@link __joints} registry reflects link/unlink HISTORY (slot
328
+ * reuse), which two engines holding identical joints need not
329
+ * share the joint solver's Gauss-Seidel sweep must consume
330
+ * joints in an order derivable from state alone or the engines
331
+ * diverge. Maintained by {@link link_joint} / {@link unlink_joint};
332
+ * consumed by the substep loop. Multiple joints between the SAME
333
+ * entity pair keep their relative link order reconstruction
334
+ * replays links in the same order, so that too is state-derived.
335
+ * @private
336
+ * @type {Joint[]}
337
+ */
338
+ this.__joints_sorted = [];
339
+
340
+ /**
341
+ * Velocity iterations per substep for the joint solver. Joints (and
342
+ * especially joint chains) want a few more iterations than contacts to
343
+ * propagate impulses along the chain; cheap because joints are far
344
+ * fewer than contacts.
345
+ * @type {number}
346
+ */
347
+ this.jointIterations = 8;
348
+
349
+ /**
350
+ * Per-body pseudo-velocity for the Catto split-impulse position
351
+ * pass (TGS Phase 1). Flat layout, 6 doubles per body slot index:
352
+ * `[lin.x, lin.y, lin.z, ang.x, ang.y, ang.z]`. Sized to
353
+ * `storage.high_water_mark * 6` at the top of each fixedUpdate
354
+ * and zeroed in place so unwritten slots contribute nothing to
355
+ * `integrate_position`.
356
+ *
357
+ * The solver writes during its position pass; `integrate_position`
358
+ * reads and folds into the pose update on the same tick, then
359
+ * the next tick's zero-pass wipes the state. It NEVER lands in
360
+ * `linearVelocity` / `angularVelocity` that's the point of
361
+ * split impulse: depth correction does not contaminate persistent
362
+ * velocity (so a `restitution = 0` impact stops cleanly).
363
+ *
364
+ * @type {Float64Array}
365
+ */
366
+ this.__pseudo_velocity = new Float64Array(0);
367
+
368
+ /**
369
+ * Master switch for the continuous-collision pass. When false the
370
+ * {@link RigidBodyFlags.CCD} flag is ignored and no swept queries run.
371
+ * @type {boolean}
372
+ */
373
+ this.ccdEnabled = true;
374
+
375
+ /**
376
+ * Start-of-step world positions for CCD-flagged bodies 3 doubles per
377
+ * body slot index (`[x, y, z]`). Captured in Stage 1 before the substep
378
+ * loop integrates poses; the CCD pass ({@link ccd_resolve}) sweeps from
379
+ * here to the final pose. Grows to `storage.high_water_mark * 3`; only
380
+ * CCD-flagged slots are written each step.
381
+ * @type {Float64Array}
382
+ */
383
+ this.__ccd_start_pos = new Float64Array(0);
384
+
385
+
386
+ /**
387
+ * Optional shared interpolation log to produce per-step pose snapshots
388
+ * into (the {@link InterpolationSystem}'s log). When set — and only then —
389
+ * each fixedUpdate restores every awake {@link Interpolated} body's live
390
+ * components from the previous tick (undoing render-time interpolation so
391
+ * the sim reads authoritative state), then records the post-step state
392
+ * under the current `entityManager.fixedStepTick`. Null on a headless /
393
+ * non-rendering world, where the producer work is skipped entirely.
394
+ * @type {InterpolationLog|null}
395
+ */
396
+ this.interpolationLog = null;
397
+
398
+ /**
399
+ * Reusable decode buffer for restoring interpolated snapshots.
400
+ * @private
401
+ * @type {BinaryBuffer}
402
+ */
403
+ this.__interp_scratch = new BinaryBuffer();
404
+ this.__interp_scratch.fromArrayBuffer(new ArrayBuffer(256));
405
+
406
+ /**
407
+ * Body indices snapshotted by the LAST {@link __interp_record} — i.e. the awake
408
+ * Interpolated set as it was at the end of the previous tick. {@link __interp_restore}
409
+ * iterates THIS set (not the current awake set) so a body that slept on the previous
410
+ * tick recorded before it slept, but absent from the current awake set — is still
411
+ * restored off its render-polluted pose. Sparse: O(interpolated moving bodies).
412
+ * @private
413
+ * @type {Uint32Array}
414
+ */
415
+ this.__interp_recorded = new Uint32Array(16);
416
+ /** @private @type {number} valid entries in {@link __interp_recorded} */
417
+ this.__interp_recorded_count = 0;
418
+ }
419
+
420
+ /**
421
+ * Symmetric layer/mask check + optional user callback. Called per
422
+ * candidate pair during broadphase. Returns `true` to accept the pair.
423
+ *
424
+ * Layer/mask rule: pair (A, B) collides iff
425
+ * `(A.layer & B.mask) !== 0 && (B.layer & A.mask) !== 0`.
426
+ * The user's contact-filter callback is consulted only for pairs that
427
+ * pass the bitmask test (cheap gate first).
428
+ *
429
+ * @private
430
+ * @param {number} idA packed body id
431
+ * @param {number} idB packed body id
432
+ * @returns {boolean}
433
+ */
434
+ __pair_filter(idA, idB) {
435
+ const idxA = body_id_index(idA);
436
+ const idxB = body_id_index(idB);
437
+
438
+ const rbA = this.__bodies[idxA];
439
+ const rbB = this.__bodies[idxB];
440
+
441
+ if (rbA === undefined || rbB === undefined) {
442
+ return false;
443
+ }
444
+
445
+ // Layer/mask gate (symmetric).
446
+ if (((rbA.layer & rbB.mask) | 0) === 0) {
447
+ return false;
448
+ }
449
+ if (((rbB.layer & rbA.mask) | 0) === 0) {
450
+ return false;
451
+ }
452
+
453
+ // User callback gate, if installed.
454
+ const fn = this.__contact_filter;
455
+ if (fn !== null) {
456
+ const entA = this.storage.entity_at(idxA);
457
+ const entB = this.storage.entity_at(idxB);
458
+ const colA = this.__primary_collider(idxA);
459
+ const colB = this.__primary_collider(idxB);
460
+ if (!fn(entA, entB, colA, colB)) return false;
461
+ }
462
+ return true;
463
+ }
464
+
465
+ /**
466
+ * First attached collider of a body, or `null` if none. Used for
467
+ * body-level checks that aren't yet per-collider: the sensor flag, the
468
+ * concave-shape dispatch flag, and the contact-filter callback's
469
+ * `colliderA` / `colliderB` arguments. Contact **materials** (friction /
470
+ * restitution) are NOT read here — the narrowphase tracks the specific
471
+ * source collider on each side of every contact and stamps the combined
472
+ * coefficients into the manifold per contact, so mixed-material compound
473
+ * bodies are accurate. (Per-contact source colliders for the filter
474
+ * callback / sensor flag remain a follow-up.)
475
+ *
476
+ * @private
477
+ * @param {number} body_idx
478
+ * @returns {Collider|null}
479
+ */
480
+ __primary_collider(body_idx) {
481
+ const list = this.__body_collider_lists[body_idx];
482
+ return (list !== undefined && list.length > 0) ? list[0].collider : null;
483
+ }
484
+
485
+ /**
486
+ * Resize {@link __pseudo_velocity} to cover every live body slot and
487
+ * zero its contents. Called at the top of each fixedUpdate so the
488
+ * split-impulse position pass starts from a clean state — bodies the
489
+ * solver doesn't touch contribute zero pseudo-velocity to
490
+ * {@link integrate_position}.
491
+ *
492
+ * Doubles on growth like the other physics scratches; never shrinks.
493
+ *
494
+ * @private
495
+ */
496
+ __reset_pseudo_velocity() {
497
+ const required = this.storage.high_water_mark * 6;
498
+ if (this.__pseudo_velocity.length < required) {
499
+ this.__pseudo_velocity = new Float64Array(required * 2);
500
+ } else if (required > 0) {
501
+ this.__pseudo_velocity.fill(0, 0, required);
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Replace the world gravity vector. Effective on the next step.
507
+ * @param {Vector3|{x:number,y:number,z:number}} v
508
+ */
509
+ setGravity(v) {
510
+ this.gravity.set(v.x, v.y, v.z);
511
+ }
512
+
513
+ /**
514
+ * Install (or remove with `null`) the contact filter callback.
515
+ * @param {((entityA:number, entityB:number, colliderA:Collider, colliderB:Collider) => boolean) | null} fn
516
+ */
517
+ setContactFilter(fn) {
518
+ this.__contact_filter = fn;
519
+ }
520
+
521
+ /**
522
+ * @returns {((entityA:number, entityB:number, colliderA:Collider, colliderB:Collider) => boolean) | null}
523
+ */
524
+ getContactFilter() {
525
+ return this.__contact_filter;
526
+ }
527
+
528
+ /**
529
+ * @private
530
+ * @param {RigidBody} rb
531
+ * @param {Collider} collider
532
+ * @param {Transform} transform
533
+ * @returns {number} BVH node id
534
+ */
535
+ __insert_into_broadphase(rb, collider, transform) {
536
+ const shape = collider.shape;
537
+
538
+ assert.notNull(shape, 'Collider.shape must be set before attaching');
539
+
540
+ shape.compute_bounding_box(scratch_local_aabb);
541
+
542
+ const p = transform.position;
543
+ const q = transform.rotation;
544
+
545
+ aabb3_transform_oriented(
546
+ scratch_world_aabb, 0,
547
+ scratch_local_aabb[0], scratch_local_aabb[1], scratch_local_aabb[2],
548
+ scratch_local_aabb[3], scratch_local_aabb[4], scratch_local_aabb[5],
549
+ p.x, p.y, p.z,
550
+ q.x, q.y, q.z, q.w
551
+ );
552
+
553
+ const bvh = rb.kind === BodyKind.Static ? this.staticBvh : this.dynamicBvh;
554
+ const node = bvh.allocate_node();
555
+
556
+ bvh.node_set_aabb_primitive(
557
+ node,
558
+ scratch_world_aabb[0], scratch_world_aabb[1], scratch_world_aabb[2],
559
+ scratch_world_aabb[3], scratch_world_aabb[4], scratch_world_aabb[5]
560
+ );
561
+ bvh.node_set_user_data(node, rb._bodyId);
562
+ bvh.insert_leaf(node);
563
+
564
+ return node;
565
+ }
566
+
567
+ /**
568
+ * @private
569
+ * @param {RigidBody} rb
570
+ * @param {number} node
571
+ */
572
+ __remove_from_broadphase(rb, node) {
573
+ const bvh = rb.kind === BodyKind.Static ? this.staticBvh : this.dynamicBvh;
574
+ bvh.remove_leaf(node);
575
+ bvh.release_node(node);
576
+ }
577
+
578
+ /**
579
+ * Wake every sleeping dynamic body whose broadphase leaf overlaps the
580
+ * given leaf's AABB. Called BEFORE removing a NON-Dynamic body's leaf
581
+ * (unlink / collider detach): statics and kinematics join no sleep group,
582
+ * their sleepers' manifolds are dormant-frozen, and the broadphase never
583
+ * revisits sleeping bodies — so without this, a pile resting on the
584
+ * removed support would sleep in mid-air forever.
585
+ *
586
+ * Dynamic removals don't need it: a sleeping pile and its dynamic support
587
+ * slept as one island and share a sleep group, which unlink already
588
+ * dissolves by waking the group.
589
+ *
590
+ * @private
591
+ * @param {BVH} bvh tree currently holding `node`
592
+ * @param {number} node leaf about to be removed
593
+ */
594
+ __wake_sleepers_overlapping_leaf(bvh, node) {
595
+ bvh.node_get_aabb(node, scratch_world_aabb);
596
+
597
+ let n;
598
+ for (;;) {
599
+ n = bvh_query_user_data_intersects_aabb(scratch_wake_candidates, 0, this.dynamicBvh, scratch_world_aabb);
600
+ if (n <= scratch_wake_candidates.length) break;
601
+ // TRUE count returned, overflow writes dropped — grow and re-query.
602
+ scratch_wake_candidates = new Uint32Array(Math.max(n, scratch_wake_candidates.length * 2));
603
+ }
604
+
605
+ for (let i = 0; i < n; i++) {
606
+ const body_id = scratch_wake_candidates[i];
607
+ if (!this.storage.is_valid(body_id)) continue;
608
+ const rb = this.__bodies[body_id_index(body_id)];
609
+ if (rb === undefined) continue;
610
+ if (rb.sleepState !== SleepState.Sleeping) continue;
611
+ this.__wake_body(rb);
612
+ }
613
+ }
614
+
615
+ /**
616
+ * Lifecycle entry: invoked when an entity gains the (RigidBody, Transform)
617
+ * tuple. Allocates the body's slot in storage and seeds an empty collider
618
+ * list. Colliders are attached separately via {@link attach_collider} (the
619
+ * paired {@link ColliderObserverSystem} drives this from the dataset; in
620
+ * tests, call manually).
621
+ *
622
+ * @param {RigidBody} rigidBody
623
+ * @param {Transform} transform
624
+ * @param {number} entity
625
+ */
626
+ link(rigidBody, transform, entity) {
627
+ const packed = this.storage.allocate(entity);
628
+ rigidBody._bodyId = packed;
629
+
630
+ const index = body_id_index(packed);
631
+ this.storage.set_kind(index, rigidBody.kind);
632
+ this.storage.set_flags(index, rigidBody.flags);
633
+
634
+ this.__bodies[index] = rigidBody;
635
+ this.__transforms[index] = transform;
636
+ this.__body_collider_lists[index] = [];
637
+
638
+ // The component's sleep fields are AUTHORITATIVE persistent state
639
+ // (see RigidBody.sleepState): a body restored from a snapshotor
640
+ // re-linked after an unlink — resumes exactly as drowsy or asleep
641
+ // as it was, instead of being forced awake to re-settle. That keeps
642
+ // a reconstructed world on the original's wake/sleep schedule (the
643
+ // reconstruction-determinism contract). A freshly-constructed
644
+ // component defaults to Awake, so plain spawns are unchanged.
645
+ //
646
+ // A restored sleeper arrives as a sleep-group SINGLETON (the group
647
+ // links are world-instance-tied body indices, reset here); group
648
+ // membership is re-established by the contact-state import or, for
649
+ // a body slept in isolation, is already correct.
650
+ rigidBody.sleep_group_next = -1;
651
+ rigidBody.sleep_group_prev = -1;
652
+
653
+ // Static bodies do not need to live in the active list — they never move.
654
+ if (rigidBody.kind === BodyKind.Static
655
+ || (rigidBody.kind === BodyKind.Dynamic && rigidBody.sleepState === SleepState.Sleeping)) {
656
+ this.storage.mark_sleeping(index);
657
+ }
658
+ }
659
+
660
+ /**
661
+ * Detach every collider attached to this body, free its slot, and clear
662
+ * the side-tables.
663
+ *
664
+ * @param {RigidBody} rigidBody
665
+ * @param {Transform} transform
666
+ * @param {number} entity
667
+ */
668
+ unlink(rigidBody, transform, entity) {
669
+ const packed = rigidBody._bodyId;
670
+
671
+ assert.equal(this.storage.is_valid(packed), true, 'unlink: stale or absent body id');
672
+
673
+ // If the body is sleeping inside a multi-member sleep group, dissolve
674
+ // the group by waking every member first. Otherwise the surviving
675
+ // members would hold dangling indices in their sleep-group chain
676
+ // pointing at a slot that has just been freed.
677
+ if (rigidBody.sleep_group_next !== -1) {
678
+ this.__wake_body(rigidBody);
679
+ }
680
+
681
+ const index = body_id_index(packed);
682
+ const list = this.__body_collider_lists[index];
683
+ if (list !== undefined) {
684
+ // A removed static / kinematic body may have been the support
685
+ // under sleeping bodies — wake them before the leaves disappear
686
+ // (their AABBs are the query keys).
687
+ const wake_neighbours = rigidBody.kind !== BodyKind.Dynamic;
688
+ const bvh = rigidBody.kind === BodyKind.Static ? this.staticBvh : this.dynamicBvh;
689
+ for (let i = 0; i < list.length; i++) {
690
+ const entry = list[i];
691
+ if (entry.bvhNode !== COLLIDER_UNBOUND) {
692
+ if (wake_neighbours) {
693
+ this.__wake_sleepers_overlapping_leaf(bvh, entry.bvhNode);
694
+ }
695
+ this.__remove_from_broadphase(rigidBody, entry.bvhNode);
696
+ }
697
+ entry.collider._bvhNode = COLLIDER_UNBOUND;
698
+ entry.collider._bodyId = -1;
699
+ }
700
+ }
701
+
702
+ this.__bodies[index] = undefined;
703
+ this.__transforms[index] = undefined;
704
+ this.__body_collider_lists[index] = undefined;
705
+
706
+ // Purge joints referencing the freed body. The solver already skips
707
+ // them via the generation-checked is_valid, but the 8-bit generation
708
+ // wraps after 256 reuse cycles of one slot (~seconds under a pooled
709
+ // spawner) — at which point a stale joint handle ALIASES an unrelated
710
+ // new body and silently reactivates against it. Removing the joint at
711
+ // the source closes the ABA window outright.
712
+ const joints = this.__joints;
713
+ for (let i = 0; i < joints.length; i++) {
714
+ const joint = joints[i];
715
+ if (joint === undefined || joint === null) continue;
716
+ if (joint._bodyIdA === packed || joint._bodyIdB === packed) {
717
+ this.unlink_joint(joint);
718
+ }
719
+ }
720
+
721
+ this.storage.free(packed);
722
+ rigidBody._bodyId = RIGID_BODY_UNALLOCATED;
723
+ }
724
+
725
+ /**
726
+ * Attach a collider to an existing body. The collider can live on the
727
+ * same entity as the body (single-collider body) or on a child entity
728
+ * (compound body). The world transform passed here is the collider's
729
+ * own — for a same-entity collider it is the body's Transform; for a
730
+ * child collider it is the child entity's Transform.
731
+ *
732
+ * Idempotent re-attaching the same collider is a no-op.
733
+ *
734
+ * @param {number} body_entity entity that owns the body
735
+ * @param {Collider} collider
736
+ * @param {Transform} transform world transform of the collider
737
+ * @param {number} [collider_entity] entity owning the collider (defaults to body_entity)
738
+ */
739
+ attach_collider(body_entity, collider, transform, collider_entity = body_entity) {
740
+ // Find the body by walking the storage entity table. The body must
741
+ // have been allocated via `link` before any colliders are attached.
742
+ const body_index = this.__find_body_index_by_entity(body_entity);
743
+ assert.notEqual(body_index, -1, `attach_collider: no body found for entity ${body_entity}`);
744
+
745
+ const rb = this.__bodies[body_index];
746
+ // Idempotent: skip if collider already attached.
747
+ if (collider._bvhNode !== COLLIDER_UNBOUND) return;
748
+
749
+ const node = this.__insert_into_broadphase(rb, collider, transform);
750
+
751
+ collider._bvhNode = node;
752
+ collider._bodyId = rb._bodyId;
753
+ this.__body_collider_lists[body_index].push({
754
+ collider, transform, entity: collider_entity, bvhNode: node,
755
+ });
756
+ }
757
+
758
+ /**
759
+ * Reverse of {@link attach_collider}. Idempotent.
760
+ *
761
+ * @param {number} body_entity
762
+ * @param {Collider} collider
763
+ */
764
+ detach_collider(body_entity, collider) {
765
+ if (collider._bvhNode === COLLIDER_UNBOUND) return;
766
+
767
+ const body_index = this.__find_body_index_by_entity(body_entity);
768
+ if (body_index === -1) return;
769
+
770
+ const rb = this.__bodies[body_index];
771
+ // Same rule as unlink: removing a static / kinematic leaf may pull
772
+ // the support from under sleeping bodies — wake them first.
773
+ if (rb.kind !== BodyKind.Dynamic) {
774
+ const bvh = rb.kind === BodyKind.Static ? this.staticBvh : this.dynamicBvh;
775
+ this.__wake_sleepers_overlapping_leaf(bvh, collider._bvhNode);
776
+ } else if (rb.sleepState === SleepState.Sleeping) {
777
+ // A sleeping DYNAMIC support: unlike unlink, detach_collider dissolves no sleep group,
778
+ // so removing the collider would strand the body (and, via the sleep-group chain that
779
+ // __wake_body walks, the whole pile resting on it) asleep and frozen in mid-air. Keyed
780
+ // on sleep state, not sleep_group_next, so a LONE sleeper (group === -1) wakes too.
781
+ this.__wake_body(rb);
782
+ }
783
+ this.__remove_from_broadphase(rb, collider._bvhNode);
784
+
785
+ const list = this.__body_collider_lists[body_index];
786
+ for (let i = 0; i < list.length; i++) {
787
+ if (list[i].collider === collider) {
788
+ list.splice(i, 1);
789
+ break;
790
+ }
791
+ }
792
+
793
+ collider._bvhNode = COLLIDER_UNBOUND;
794
+ collider._bodyId = -1;
795
+ }
796
+
797
+ /**
798
+ * Resolve an entity to its body index, or -1 if no live body owns it.
799
+ * O(1) via {@link BodyStorage#index_of_entity}'s entity → index map — used
800
+ * on the collider attach / detach and joint link paths.
801
+ *
802
+ * @private
803
+ * @param {number} entity
804
+ * @returns {number} body index or -1
805
+ */
806
+ __find_body_index_by_entity(entity) {
807
+ return this.storage.index_of_entity(entity);
808
+ }
809
+
810
+ /**
811
+ * Register a {@link Joint} (6-DOF constraint). Resolves the joint's
812
+ * entities to packed body ids and adds it to the active set, where it is
813
+ * solved alongside contacts in the TGS substep loop. Body A must be a
814
+ * linked body; body B is either a linked body or {@link JOINT_WORLD}
815
+ * (anchoring A to a fixed world point `localAnchorB` is then a world
816
+ * coordinate).
817
+ *
818
+ * @param {Joint} joint
819
+ */
820
+ link_joint(joint) {
821
+ const idxA = this.__find_body_index_by_entity(joint.entityA);
822
+ assert.notEqual(idxA, -1, `link_joint: no body for entityA ${joint.entityA}`);
823
+ joint._bodyIdA = this.__bodies[idxA]._bodyId;
824
+
825
+ if (joint.entityB === JOINT_WORLD) {
826
+ joint._bodyIdB = JOINT_WORLD;
827
+ } else {
828
+ const idxB = this.__find_body_index_by_entity(joint.entityB);
829
+ assert.notEqual(idxB, -1, `link_joint: no body for entityB ${joint.entityB}`);
830
+ joint._bodyIdB = this.__bodies[idxB]._bodyId;
831
+ }
832
+
833
+ // A (re-)linked joint starts cold: the accumulated warm-start impulse
834
+ // belongs to whatever configuration the joint solved BEFORE it was
835
+ // unlinked replaying it against the new bodies is a phantom kick.
836
+ joint.dofImpulse.fill(0);
837
+
838
+ // Reuse a freed slot if available so joint ids stay dense-ish.
839
+ let id;
840
+ if (this.__joint_free.length > 0) {
841
+ id = this.__joint_free.pop();
842
+ } else {
843
+ id = this.__joints.length;
844
+ }
845
+ joint._jointId = id;
846
+ this.__joints[id] = joint;
847
+
848
+ // Insert at the upper bound of the joint's (entityA, entityB) key in
849
+ // the canonical solve-order list — equal keys keep link order.
850
+ const sorted = this.__joints_sorted;
851
+ let pos = sorted.length;
852
+ while (pos > 0) {
853
+ const prev = sorted[pos - 1];
854
+ if (prev.entityA < joint.entityA
855
+ || (prev.entityA === joint.entityA && prev.entityB <= joint.entityB)) {
856
+ break;
857
+ }
858
+ pos--;
859
+ }
860
+ sorted.splice(pos, 0, joint);
861
+ }
862
+
863
+ /**
864
+ * Remove a previously {@link link_joint}'d joint from the active set.
865
+ * Idempotent.
866
+ * @param {Joint} joint
867
+ */
868
+ unlink_joint(joint) {
869
+ const id = joint._jointId;
870
+ if (id === JOINT_UNALLOCATED || this.__joints[id] !== joint) return;
871
+ this.__joints[id] = undefined;
872
+ this.__joint_free.push(id);
873
+ joint._jointId = JOINT_UNALLOCATED;
874
+
875
+ const sorted = this.__joints_sorted;
876
+ const pos = sorted.indexOf(joint);
877
+ if (pos !== -1) sorted.splice(pos, 1);
878
+ }
879
+
880
+ /**
881
+ * Resolve a packed body id to its entity, or `-1` if the id is stale.
882
+ * @param {number} packed_body_id
883
+ * @returns {number}
884
+ */
885
+ entityOf(packed_body_id) {
886
+ if (!this.storage.is_valid(packed_body_id)) return -1;
887
+ return this.storage.entity_at(body_id_index(packed_body_id));
888
+ }
889
+
890
+ /**
891
+ * Number of live bodies (regardless of awake/sleeping state).
892
+ * @returns {number}
893
+ */
894
+ get bodyCount() {
895
+ return this.storage.size;
896
+ }
897
+
898
+ /**
899
+ * Apply an instantaneous change of momentum at the body's centre of mass.
900
+ * Linear-only see {@link applyImpulseAt} for an off-centre impulse that
901
+ * also produces angular response.
902
+ *
903
+ * Wakes the body if it is asleep.
904
+ *
905
+ * @param {RigidBody} rigidBody
906
+ * @param {Vector3|{x:number,y:number,z:number}} impulse
907
+ */
908
+ applyImpulse(rigidBody, impulse) {
909
+ if (rigidBody.kind !== BodyKind.Dynamic) {
910
+ return;
911
+ }
912
+ const inv_m = rigidBody.mass > 0 ? 1 / rigidBody.mass : 0;
913
+ rigidBody.linearVelocity.addScaled(impulse, inv_m);
914
+
915
+ this.__wake_body(rigidBody);
916
+ }
917
+
918
+ /**
919
+ * Apply an instantaneous change of momentum at a specific world-space point.
920
+ * Off-centre impulses produce both linear (Δv = P/m) and angular
921
+ * (Δω = I_w⁻¹·(r × P)) response.
922
+ *
923
+ * Wakes the body if it is asleep.
924
+ *
925
+ * @param {RigidBody} rigidBody
926
+ * @param {Transform} transform body's current world Transform (used for r and I_w)
927
+ * @param {Vector3|{x:number,y:number,z:number}} impulse
928
+ * @param {Vector3|{x:number,y:number,z:number}} worldPoint
929
+ */
930
+ applyImpulseAt(rigidBody, transform, impulse, worldPoint) {
931
+ if (rigidBody.kind !== BodyKind.Dynamic) {
932
+ return;
933
+ }
934
+ const inv_m = rigidBody.mass > 0 ? 1 / rigidBody.mass : 0;
935
+
936
+ rigidBody.linearVelocity.set(
937
+ rigidBody.linearVelocity.x + impulse.x * inv_m,
938
+ rigidBody.linearVelocity.y + impulse.y * inv_m,
939
+ rigidBody.linearVelocity.z + impulse.z * inv_m
940
+ );
941
+
942
+ const rx = worldPoint.x - transform.position.x;
943
+ const ry = worldPoint.y - transform.position.y;
944
+ const rz = worldPoint.z - transform.position.z;
945
+
946
+ // Δω = I_w⁻¹ · (r × P)
947
+ const tx = ry * impulse.z - rz * impulse.y;
948
+ const ty = rz * impulse.x - rx * impulse.z;
949
+ const tz = rx * impulse.y - ry * impulse.x;
950
+
951
+ // LockRot* masks the BODY-LOCAL inverse-inertia diagonal here exactly
952
+ // as the solver gather does, so a user impulse cannot spin a locked
953
+ // DOF either.
954
+ const flags = rigidBody.flags;
955
+ const ii = rigidBody.inverseInertiaLocal;
956
+ scratch_locked_inertia.x = (flags & RigidBodyFlags.LockRotX) !== 0 ? 0 : ii.x;
957
+ scratch_locked_inertia.y = (flags & RigidBodyFlags.LockRotY) !== 0 ? 0 : ii.y;
958
+ scratch_locked_inertia.z = (flags & RigidBodyFlags.LockRotZ) !== 0 ? 0 : ii.z;
959
+ world_inverse_inertia_apply(scratch_angular_delta, 0, scratch_locked_inertia, transform.rotation, tx, ty, tz);
960
+
961
+ rigidBody.angularVelocity.set(
962
+ rigidBody.angularVelocity.x + scratch_angular_delta[0],
963
+ rigidBody.angularVelocity.y + scratch_angular_delta[1],
964
+ rigidBody.angularVelocity.z + scratch_angular_delta[2]
965
+ );
966
+
967
+ this.__wake_body(rigidBody);
968
+ }
969
+
970
+ /**
971
+ * Accumulate a continuous torque (world-space) for integration on the
972
+ * next fixedUpdate. Pairs with {@link applyForce} for the rotational case.
973
+ *
974
+ * Wakes the body if asleep.
975
+ *
976
+ * @param {RigidBody} rigidBody
977
+ * @param {Vector3|{x:number,y:number,z:number}} torque
978
+ */
979
+ applyTorque(rigidBody, torque) {
980
+ if (rigidBody.kind !== BodyKind.Dynamic) {
981
+ return;
982
+ }
983
+ rigidBody.accumulatedTorque.set(
984
+ rigidBody.accumulatedTorque.x + torque.x,
985
+ rigidBody.accumulatedTorque.y + torque.y,
986
+ rigidBody.accumulatedTorque.z + torque.z
987
+ );
988
+ this.__wake_body(rigidBody);
989
+ }
990
+
991
+ /**
992
+ * Apply a continuous force at a specific world-space point. The force
993
+ * generates both a linear acceleration (F/m) and a torque (r × F) about
994
+ * the body's centre of mass.
995
+ *
996
+ * Wakes the body if asleep.
997
+ *
998
+ * @param {RigidBody} rigidBody
999
+ * @param {Transform} transform body's current world Transform
1000
+ * @param {Vector3|{x:number,y:number,z:number}} force
1001
+ * @param {Vector3|{x:number,y:number,z:number}} worldPoint
1002
+ */
1003
+ applyForceAt(rigidBody, transform, force, worldPoint) {
1004
+ if (rigidBody.kind !== BodyKind.Dynamic) {
1005
+ return;
1006
+ }
1007
+ rigidBody.accumulatedForce.set(
1008
+ rigidBody.accumulatedForce.x + force.x,
1009
+ rigidBody.accumulatedForce.y + force.y,
1010
+ rigidBody.accumulatedForce.z + force.z
1011
+ );
1012
+
1013
+ const rx = worldPoint.x - transform.position.x;
1014
+ const ry = worldPoint.y - transform.position.y;
1015
+ const rz = worldPoint.z - transform.position.z;
1016
+
1017
+ rigidBody.accumulatedTorque.set(
1018
+ rigidBody.accumulatedTorque.x + (ry * force.z - rz * force.y),
1019
+ rigidBody.accumulatedTorque.y + (rz * force.x - rx * force.z),
1020
+ rigidBody.accumulatedTorque.z + (rx * force.y - ry * force.x)
1021
+ );
1022
+
1023
+ this.__wake_body(rigidBody);
1024
+ }
1025
+
1026
+ /**
1027
+ * Accumulate a continuous force to be integrated next fixedUpdate step.
1028
+ * Wakes the body if asleep.
1029
+ *
1030
+ * @param {RigidBody} rigidBody
1031
+ * @param {Vector3|{x:number,y:number,z:number}} force
1032
+ */
1033
+ applyForce(rigidBody, force) {
1034
+ if (rigidBody.kind !== BodyKind.Dynamic) {
1035
+ return;
1036
+ }
1037
+ rigidBody.accumulatedForce.add(force);
1038
+ this.__wake_body(rigidBody);
1039
+ }
1040
+
1041
+ /**
1042
+ * Replace the linear velocity. Wakes the body if asleep.
1043
+ *
1044
+ * @param {RigidBody} rigidBody
1045
+ * @param {Vector3|{x:number,y:number,z:number}} v
1046
+ */
1047
+ setLinearVelocity(rigidBody, v) {
1048
+ rigidBody.linearVelocity.copy(v);
1049
+ if (rigidBody.kind === BodyKind.Dynamic) {
1050
+ this.__wake_body(rigidBody);
1051
+ }
1052
+ }
1053
+
1054
+ /**
1055
+ * Teleport a body to a new pose, bypassing integration: writes the body's
1056
+ * Transform directly and wakes it. For an interpolated body this also flags a
1057
+ * render `snap` on its {@link Interpolated} component, so the producer keeps
1058
+ * this pose (rather than restoring the previous tick over it) and the
1059
+ * renderer shows the new pose without sliding across the jump.
1060
+ *
1061
+ * This is the authoritative way to reposition an interpolated body — a raw
1062
+ * `Transform` write would be undone by the per-step restore. Velocity is left
1063
+ * as-is; zero it via {@link setLinearVelocity} if the teleport should also
1064
+ * stop the body.
1065
+ *
1066
+ * Works for STATIC bodies too: their broadphase leaves (which the step
1067
+ * never refits) are re-homed to the new pose, and sleeping bodies at both
1068
+ * the old and the new location are woken — a moved support drops what
1069
+ * rested on it, and whatever the static moved into must react.
1070
+ *
1071
+ * @param {RigidBody} rigidBody
1072
+ * @param {Vector3|{x:number,y:number,z:number}} position world position
1073
+ * @param {Quaternion|{x:number,y:number,z:number,w:number}} rotation world unit-quaternion rotation
1074
+ */
1075
+ setPose(rigidBody, position, rotation) {
1076
+ const idx = body_id_index(rigidBody._bodyId);
1077
+ const transform = this.__transforms[idx];
1078
+ if (transform === undefined) {
1079
+ return;
1080
+ }
1081
+
1082
+ transform.position.set(position.x, position.y, position.z);
1083
+ transform.rotation.set(rotation.x, rotation.y, rotation.z, rotation.w);
1084
+
1085
+ if (rigidBody.kind !== BodyKind.Dynamic) {
1086
+ // Static AND Kinematic: the body itself never sleeps and __wake_body is a no-op for it,
1087
+ // so teleporting a support must instead wake what rested on it (old pose) and what it
1088
+ // moved into (new pose) — a zero-velocity teleport never trips __wake_pairs' velocity-
1089
+ // based parked exemption. A moved static also keeps a stale broadphase AABB forever
1090
+ // (stage 2 refits awake bodies only), so re-home its leaves; doing the same for a
1091
+ // kinematic is harmless (stage 2 would refit it anyway). The leaves of a static live in
1092
+ // staticBvh, a kinematic's in dynamicBvh — pick the tree that actually holds them.
1093
+ const bvh = rigidBody.kind === BodyKind.Static ? this.staticBvh : this.dynamicBvh;
1094
+ const list = this.__body_collider_lists[idx];
1095
+ if (list !== undefined) {
1096
+ for (let i = 0; i < list.length; i++) {
1097
+ const entry = list[i];
1098
+ if (entry.bvhNode === COLLIDER_UNBOUND) continue;
1099
+ this.__wake_sleepers_overlapping_leaf(bvh, entry.bvhNode);
1100
+ this.__remove_from_broadphase(rigidBody, entry.bvhNode);
1101
+ const node = this.__insert_into_broadphase(rigidBody, entry.collider, entry.transform);
1102
+ entry.bvhNode = node;
1103
+ entry.collider._bvhNode = node;
1104
+ this.__wake_sleepers_overlapping_leaf(bvh, node);
1105
+ }
1106
+ }
1107
+ } else {
1108
+ this.__wake_body(rigidBody);
1109
+ }
1110
+
1111
+ // Flag a snap on the body's Interpolated component, if any, so the
1112
+ // interpolation producer (restore) and consumer (blend) treat this pose
1113
+ // as authoritative this frame instead of interpolating across the jump.
1114
+ //
1115
+ // KNOWN BUG (deferred): `snap` is a single flag with two independent consumers —
1116
+ // __interp_restore (skips restore) and InterpolationSystem.update (skips blend AND
1117
+ // CLEARS it). The renderer's update runs every render cycle while a fixed step only
1118
+ // runs when the accumulator crosses the step size, so a render frame landing between
1119
+ // this setPose and the next fixedUpdate clears `snap` before __interp_restore reads
1120
+ // it — restore then overwrites the teleported Transform with the pre-teleport tick's
1121
+ // snapshot and the teleport is silently reverted in the SIMULATION. This only bites
1122
+ // once InterpolationSystem is wired (the app does not wire it yet). The robust fix is
1123
+ // to make the teleport authoritative independent of the shared flag — either overwrite
1124
+ // the log's latest-tick record for this body (needs new InterpolationLog API), or give
1125
+ // the producer and consumer independent snap state each clears itself.
1126
+ const em = this.entityManager;
1127
+ if (em !== null && em !== undefined) {
1128
+ const dataset = em.dataset;
1129
+ if (dataset !== null && dataset !== undefined) {
1130
+ const entity = this.storage.entity_at(idx);
1131
+ const interpolated = dataset.getComponent(entity, Interpolated);
1132
+ if (interpolated !== undefined && interpolated !== null) {
1133
+ interpolated.snap = true;
1134
+ }
1135
+ }
1136
+ }
1137
+ }
1138
+
1139
+ /**
1140
+ * Force the body awake. Static bodies are ignored.
1141
+ * @param {RigidBody} rigidBody
1142
+ */
1143
+ wake(rigidBody) {
1144
+ this.__wake_body(rigidBody);
1145
+ }
1146
+
1147
+ /**
1148
+ * Force the body asleep. Dynamic bodies will not re-enter the active list
1149
+ * until a wake event occurs.
1150
+ * @param {RigidBody} rigidBody
1151
+ */
1152
+ sleep(rigidBody) {
1153
+ if (rigidBody.kind !== BodyKind.Dynamic) {
1154
+ return;
1155
+ }
1156
+ if (rigidBody.sleepState === SleepState.Sleeping) {
1157
+ return;
1158
+ }
1159
+ // An unlinked (or stale) body has no storage slot — indexing with its
1160
+ // dead id would corrupt the awake-list bookkeeping of whatever body
1161
+ // now owns that index.
1162
+ if (!this.storage.is_valid(rigidBody._bodyId)) {
1163
+ return;
1164
+ }
1165
+ rigidBody.sleepState = SleepState.Sleeping;
1166
+ const index = body_id_index(rigidBody._bodyId);
1167
+ this.storage.mark_sleeping(index);
1168
+ }
1169
+
1170
+ /**
1171
+ * Wake a body and atomically wake every other body it was last sleeping
1172
+ * with (its "sleep group"). Sleep groups are circular doubly-linked lists
1173
+ * threaded through every member of an island when it sleeps atomically;
1174
+ * waking any one member walks the chain and wakes the rest in the same
1175
+ * call.
1176
+ *
1177
+ * Without this, a 100-block stack hit at the base would wake one block
1178
+ * per frame as the broadphase propagated awareness up the stack — a
1179
+ * visible ~1.6 s wave at 60 fps. Atomic wake eliminates the wave.
1180
+ *
1181
+ * No-op for non-dynamic bodies. Idempotent for already-awake bodies.
1182
+ * @private
1183
+ * @param {RigidBody} rb
1184
+ */
1185
+ __wake_body(rb) {
1186
+ if (rb.kind !== BodyKind.Dynamic) return;
1187
+ if (rb.sleepState === SleepState.Awake) return;
1188
+ // Same stale-id guard as sleep(): an unlinked body's dead id must not
1189
+ // index into (and corrupt) another body's awake-list slot.
1190
+ if (!this.storage.is_valid(rb._bodyId)) return;
1191
+ const index = body_id_index(rb._bodyId);
1192
+
1193
+ // Remember the next-in-chain before clearing the body's own pointers;
1194
+ // the rest of the group is reached by walking forward from there.
1195
+ const start_next = rb.sleep_group_next;
1196
+
1197
+ rb.sleepState = SleepState.Awake;
1198
+ rb.sleep_timer = 0;
1199
+ rb.sleep_group_next = -1;
1200
+ rb.sleep_group_prev = -1;
1201
+ this.storage.mark_awake(index);
1202
+
1203
+ if (start_next === -1 || start_next === index) return;
1204
+
1205
+ // Walk the (now-broken) chain forward until we loop back. The chain
1206
+ // is circular so we know when to stop; defensive `-1` guards against
1207
+ // corruption from a body being unlinked mid-sleep-group.
1208
+ let cur = start_next;
1209
+ while (cur !== -1 && cur !== index) {
1210
+ const cur_rb = this.__bodies[cur];
1211
+ if (cur_rb === undefined) break;
1212
+ const nxt = cur_rb.sleep_group_next;
1213
+ cur_rb.sleepState = SleepState.Awake;
1214
+ cur_rb.sleep_timer = 0;
1215
+ cur_rb.sleep_group_next = -1;
1216
+ cur_rb.sleep_group_prev = -1;
1217
+ this.storage.mark_awake(cur);
1218
+ cur = nxt;
1219
+ }
1220
+ }
1221
+
1222
+ /**
1223
+ * Atomically put every body in a contiguous range of island members to
1224
+ * sleep. Members are threaded into a circular doubly-linked list so any
1225
+ * future `wake` on any member walks the chain and revives them all.
1226
+ *
1227
+ * Velocities are zeroed because the body is by definition at rest at
1228
+ * this point — the alternative (storing residual velocities for "softer"
1229
+ * wake) is what Bullet does, but for a deterministic game-physics target
1230
+ * fully resetting is simpler and avoids drift while sleeping.
1231
+ *
1232
+ * @private
1233
+ * @param {Uint32Array} member_array view (or full array) of body indices
1234
+ * @param {number} start
1235
+ * @param {number} end
1236
+ */
1237
+ __atomic_sleep_island_range(member_array, start, end) {
1238
+ const count = end - start;
1239
+ if (count === 0) return;
1240
+ const bodies = this.__bodies;
1241
+ const storage = this.storage;
1242
+
1243
+ if (count === 1) {
1244
+ const idx = member_array[start];
1245
+ const rb = bodies[idx];
1246
+ if (rb === undefined) return;
1247
+ rb.sleep_group_next = -1;
1248
+ rb.sleep_group_prev = -1;
1249
+ rb.sleepState = SleepState.Sleeping;
1250
+ rb.linearVelocity[0] = 0;
1251
+ rb.linearVelocity[1] = 0;
1252
+ rb.linearVelocity[2] = 0;
1253
+ rb.angularVelocity[0] = 0;
1254
+ rb.angularVelocity[1] = 0;
1255
+ rb.angularVelocity[2] = 0;
1256
+ storage.mark_sleeping(idx);
1257
+ return;
1258
+ }
1259
+
1260
+ for (let i = 0; i < count; i++) {
1261
+ const idx = member_array[start + i];
1262
+ const rb = bodies[idx];
1263
+ if (rb === undefined) continue;
1264
+ const next_idx = member_array[start + ((i + 1) % count)];
1265
+ const prev_idx = member_array[start + ((i - 1 + count) % count)];
1266
+ rb.sleep_group_next = next_idx;
1267
+ rb.sleep_group_prev = prev_idx;
1268
+ rb.sleepState = SleepState.Sleeping;
1269
+ rb.linearVelocity[0] = 0;
1270
+ rb.linearVelocity[1] = 0;
1271
+ rb.linearVelocity[2] = 0;
1272
+ rb.angularVelocity[0] = 0;
1273
+ rb.angularVelocity[1] = 0;
1274
+ rb.angularVelocity[2] = 0;
1275
+ storage.mark_sleeping(idx);
1276
+ }
1277
+ }
1278
+
1279
+ /**
1280
+ * Broadphase raycast against both BVHs. Fills `result` with the nearest
1281
+ * hit and returns `true` on hit, `false` on miss.
1282
+ *
1283
+ * Narrowphase refinement against the actual shape geometry is a
1284
+ * follow-up for now `result.t` is the distance to the leaf's
1285
+ * inflated AABB and `result.normal` is the AABB face normal. Both are
1286
+ * exact for AABB-shaped colliders.
1287
+ *
1288
+ * @param {Ray3} ray origin + unit direction + `tMax`
1289
+ * @param {PhysicsSurfacePoint} result populated on hit; untouched on miss
1290
+ * @param {(entity:number, collider:Collider)=>boolean} [filter] defaults
1291
+ * to {@link returnTrue} (accept every candidate)
1292
+ * @returns {boolean}
1293
+ */
1294
+ raycast(ray, result, filter = returnTrue) {
1295
+ return raycast_query(this, ray, result, filter);
1296
+ }
1297
+
1298
+ /**
1299
+ * Sweep a convex shape along a ray and find the first body it
1300
+ * would hit. The shape starts at `ray.origin` oriented by
1301
+ * `rotation` and translates along `ray.direction` for up to
1302
+ * `ray.tMax` units. Returns the nearest impact within that
1303
+ * interval.
1304
+ *
1305
+ * The "swept AABB" broadphase finds candidate bodies whose BVH
1306
+ * leaves overlap the shape's swept volume; the narrowphase then
1307
+ * bisects [0, t] on GJK overlap to find the time-of-impact for
1308
+ * each candidate. Best-t early termination skips candidates that
1309
+ * can't tighten the answer.
1310
+ *
1311
+ * @param {Ray3} ray origin + unit direction + `tMax`
1312
+ * @param {AbstractShape3D} shape
1313
+ * @param {{x:number,y:number,z:number,w:number}} rotation
1314
+ * @param {PhysicsSurfacePoint} result populated on hit; untouched on miss
1315
+ * @param {(entity:number, collider:Collider)=>boolean} [filter]
1316
+ * @returns {boolean}
1317
+ */
1318
+ shapeCast(ray, shape, rotation, result, filter = returnTrue) {
1319
+ return shape_cast_query(this, ray, shape, rotation, result, filter);
1320
+ }
1321
+
1322
+ /**
1323
+ * Speculative overlap query: find all bodies whose collider would
1324
+ * overlap the given convex shape placed at the given world pose,
1325
+ * without mutating the simulation. Intended for kinematic /
1326
+ * character controllers that need to test "would I collide if I
1327
+ * moved here?" before committing the move.
1328
+ *
1329
+ * Pipeline:
1330
+ * 1. The shape's world AABB is computed from `position` + `rotation`.
1331
+ * 2. Both broadphase trees (static + dynamic) are queried for
1332
+ * bodies whose leaf AABB overlaps that envelope.
1333
+ * 3. Each candidate is GJK-tested in world frame. Convex
1334
+ * candidates run one GJK call; concave candidates (heightmap,
1335
+ * mesh) run per-triangle GJK via the decomposition path.
1336
+ * 4. The optional `filter` is consulted before the GJK test —
1337
+ * useful for skipping the caller's own body, allies, sensors,
1338
+ * etc.
1339
+ *
1340
+ * The output buffer is filled with overlapping bodies' `body_id`
1341
+ * values (uint32 with packed generation), starting at
1342
+ * `output_offset`. The caller is responsible for sizing the buffer;
1343
+ * IDs past its end are dropped silently and the count caps at the
1344
+ * available space.
1345
+ *
1346
+ * The query shape must be convex. Concave query shapes throw —
1347
+ * they're typically static terrain and not used as kinematic
1348
+ * probes; the M×N triangle-pair cost wouldn't be worth the rare
1349
+ * use case.
1350
+ *
1351
+ * @param {AbstractShape3D} shape convex query shape, in its local frame
1352
+ * @param {{x:number,y:number,z:number}} position world position of
1353
+ * the query shape
1354
+ * @param {{x:number,y:number,z:number,w:number}} rotation world
1355
+ * rotation (unit quaternion)
1356
+ * @param {Uint32Array|number[]} output buffer to receive body_ids
1357
+ * @param {number} output_offset starting index in output
1358
+ * @param {(entity:number, collider:Collider)=>boolean} [filter]
1359
+ * defaults to {@link returnTrue}
1360
+ * @returns {number} number of overlapping bodies written
1361
+ */
1362
+ overlap(shape, position, rotation, output, output_offset, filter = returnTrue) {
1363
+ return overlap_shape_query(this, shape, position, rotation, output, output_offset, filter);
1364
+ }
1365
+
1366
+ /**
1367
+ * Wake propagation through contact pairs. Runs BEFORE broadphase so a
1368
+ * body woken here participates in THIS frame's broadphase / narrowphase
1369
+ * / islands end to end it never simulates a step without its support
1370
+ * contacts (free-fall penetration pop).
1371
+ *
1372
+ * Reads ONLY persistent manifold state (the slots whose pair the
1373
+ * broadphase touched last frame — `prev_touched`), never the transient
1374
+ * pair list: manifold state is part of the engine's exportable contact
1375
+ * state, so a reconstructed engine wakes on exactly the original's
1376
+ * schedule.
1377
+ *
1378
+ * A sleeper wakes when:
1379
+ * - its pair holds REAL contacts (`contact_count > 0`), unless the
1380
+ * awake side is a PARKED non-Dynamic (a stationary kinematic platform
1381
+ * or a static — sleepState of either is never updated — transmits no
1382
+ * motion; the moment it gets velocity, the riders wake); or
1383
+ * - the pair has no contacts yet but the awake side's velocity-swept
1384
+ * tight AABB just REACHED the sleeper's tight AABB (false→true edge
1385
+ * of {@link __swept_proximity}) one precautionary wake so an
1386
+ * incoming body never spends its first touching frame against a
1387
+ * non-integrating sleeper, over-embedding by v·dt. Edge-triggered on
1388
+ * a pure function of body state: pair CREATION time (the old
1389
+ * trigger) depends on broadphase fat-box history, which a
1390
+ * reconstructed engine does not share; and level-triggered proximity
1391
+ * would re-wake a pile next to a hovering body every frame. Slot
1392
+ * existence alone wakes nothing.
1393
+ * @private
1394
+ * @param {number} dt step size extent of the predicate's velocity sweep
1395
+ */
1396
+ __wake_pairs(dt) {
1397
+ const bodies = this.__bodies;
1398
+ const storage = this.storage;
1399
+ const manifolds = this.manifolds;
1400
+ const live_count = manifolds.count;
1401
+ for (let i = 0; i < live_count; i++) {
1402
+ const slot = manifolds.live_at(i);
1403
+ // "Was in last frame's broadphase": acquire set touched, the
1404
+ // end-of-step advance rolled it to prev_touched. (A dormant
1405
+ // slot can carry a stale prev_touched from its sleep frame —
1406
+ // the both-asleep guard below skips it.)
1407
+ if (!manifolds.was_touched_prev(slot)) continue;
1408
+
1409
+ const idA = manifolds.bodyA(slot);
1410
+ const idB = manifolds.bodyB(slot);
1411
+ // Stale-slot guards: a body may have been unlinked since (and
1412
+ // its index reused by an unrelated body).
1413
+ if (!storage.is_valid(idA) || !storage.is_valid(idB)) continue;
1414
+ const idxA = body_id_index(idA);
1415
+ const idxB = body_id_index(idB);
1416
+ const a = bodies[idxA];
1417
+ const b = bodies[idxB];
1418
+ if (a === undefined || b === undefined) continue;
1419
+
1420
+ const aSleep = a.sleepState === SleepState.Sleeping;
1421
+ const bSleep = b.sleepState === SleepState.Sleeping;
1422
+ if (aSleep === bSleep) continue; // both awake, or a dormant pair
1423
+
1424
+ // A sensor transmits no force and is excluded from sleep islands (is_sensor,
1425
+ // IslandBuilder, the contact solver). It must not wake a sleeper either — otherwise a
1426
+ // moving trigger volume re-wakes everything it overlaps every frame, holding whole
1427
+ // islands permanently awake. Skip the pair, mirroring IslandBuilder.__body_is_sensor.
1428
+ if (is_sensor(a, this.__primary_collider(idxA)) || is_sensor(b, this.__primary_collider(idxB))) {
1429
+ continue;
1430
+ }
1431
+
1432
+ if (manifolds.contact_count(slot) > 0) {
1433
+ const mover = aSleep ? b : a;
1434
+ if (mover.kind !== BodyKind.Dynamic) {
1435
+ const lv = mover.linearVelocity;
1436
+ const av = mover.angularVelocity;
1437
+ if (lv[0] === 0 && lv[1] === 0 && lv[2] === 0
1438
+ && av[0] === 0 && av[1] === 0 && av[2] === 0) {
1439
+ continue; // parked platform / static — transmits no motion
1440
+ }
1441
+ }
1442
+ this.__wake_body(aSleep ? a : b);
1443
+ } else {
1444
+ // No contacts: pre-contact precautionary wake on the
1445
+ // proximity EDGE. The predicate needs no parked exemption —
1446
+ // a zero-velocity mover's swept box is its tight box, so it
1447
+ // only fires on actual tight-box overlap, once.
1448
+ const mover_idx = body_id_index(aSleep ? idB : idA);
1449
+ const sleeper_idx = body_id_index(aSleep ? idA : idB);
1450
+ const prox = this.__swept_proximity(mover_idx, sleeper_idx, dt);
1451
+ const had_prox = manifolds.is_prox(slot);
1452
+ manifolds.set_prox(slot, prox);
1453
+ if (prox && !had_prox) {
1454
+ this.__wake_body(aSleep ? a : b);
1455
+ }
1456
+ }
1457
+ }
1458
+ }
1459
+
1460
+ /**
1461
+ * Stage-0 pre-contact wake predicate: does any of `mover`'s collider
1462
+ * tight AABBs, swept directionally by the mover's linear velocity over
1463
+ * `dt`, overlap any of `sleeper`'s collider tight AABBs?
1464
+ *
1465
+ * A pure function of CURRENT body state (poses, shapes, velocity) — no
1466
+ * broadphase or pad history — so every engine holding the same bodies
1467
+ * evaluates it identically. Conservative containment: the swept box is
1468
+ * inside the mover's stage-2 fat box (pad = 2·|v|·dt + slack ⊇ v·dt)
1469
+ * and the sleeper's tight box is inside its stored leaf, so whenever
1470
+ * the predicate is true the broadphase has the pair THIS frame in any
1471
+ * engine — the manifold slot the wake stage needs is guaranteed to
1472
+ * exist on the frame the edge fires.
1473
+ *
1474
+ * Linear sweep only (matching the broadphase pad): a purely angular
1475
+ * approach is woken one frame later by its first actual contact.
1476
+ *
1477
+ * @private
1478
+ * @param {number} mover_idx body index of the awake side
1479
+ * @param {number} sleeper_idx body index of the sleeping side
1480
+ * @param {number} dt
1481
+ * @returns {boolean}
1482
+ */
1483
+ __swept_proximity(mover_idx, sleeper_idx, dt) {
1484
+ const mover_list = this.__body_collider_lists[mover_idx];
1485
+ const sleeper_list = this.__body_collider_lists[sleeper_idx];
1486
+ if (mover_list === undefined || sleeper_list === undefined) return false;
1487
+
1488
+ const lv = this.__bodies[mover_idx].linearVelocity;
1489
+ const px = lv[0] * dt;
1490
+ const py = lv[1] * dt;
1491
+ const pz = lv[2] * dt;
1492
+
1493
+ for (let i = 0; i < mover_list.length; i++) {
1494
+ const m = mover_list[i];
1495
+ if (m.bvhNode === COLLIDER_UNBOUND) continue;
1496
+ compute_world_aabb(scratch_prox_mover, 0, m.collider.shape, m.transform);
1497
+ if (px < 0) scratch_prox_mover[0] += px; else scratch_prox_mover[3] += px;
1498
+ if (py < 0) scratch_prox_mover[1] += py; else scratch_prox_mover[4] += py;
1499
+ if (pz < 0) scratch_prox_mover[2] += pz; else scratch_prox_mover[5] += pz;
1500
+
1501
+ for (let k = 0; k < sleeper_list.length; k++) {
1502
+ const s = sleeper_list[k];
1503
+ if (s.bvhNode === COLLIDER_UNBOUND) continue;
1504
+ compute_world_aabb(scratch_prox_sleeper, 0, s.collider.shape, s.transform);
1505
+ if (scratch_prox_mover[0] <= scratch_prox_sleeper[3]
1506
+ && scratch_prox_mover[3] >= scratch_prox_sleeper[0]
1507
+ && scratch_prox_mover[1] <= scratch_prox_sleeper[4]
1508
+ && scratch_prox_mover[4] >= scratch_prox_sleeper[1]
1509
+ && scratch_prox_mover[2] <= scratch_prox_sleeper[5]
1510
+ && scratch_prox_mover[5] >= scratch_prox_sleeper[2]) {
1511
+ return true;
1512
+ }
1513
+ }
1514
+ }
1515
+ return false;
1516
+ }
1517
+
1518
+ /**
1519
+ * Wake propagation across joints: if a joint connects an awake body to a
1520
+ * sleeping one, wake the sleeper so the constraint stays coupled (a joint
1521
+ * with one body asleep and one awake would otherwise be skipped by the
1522
+ * solver, letting the awake side drift). A common trigger is a
1523
+ * kinematic/motor-driven body pulling on a sleeping chain.
1524
+ *
1525
+ * Bodies that slept together as one island share a sleep group, so the
1526
+ * usual atomic wake already revives the whole chain when any member is
1527
+ * hit; this catches the cases that atomic wake doesn't (joint spanning
1528
+ * separate groups, kinematic driver).
1529
+ * @private
1530
+ */
1531
+ __wake_joints() {
1532
+ const joints = this.__joints;
1533
+ const n = joints.length;
1534
+ if (n === 0) return;
1535
+ const bodies = this.__bodies;
1536
+ const storage = this.storage;
1537
+ for (let i = 0; i < n; i++) {
1538
+ const joint = joints[i];
1539
+ if (joint === undefined || joint === null) continue;
1540
+ if (joint._bodyIdB === JOINT_WORLD) continue;
1541
+ if (!storage.is_valid(joint._bodyIdA) || !storage.is_valid(joint._bodyIdB)) continue;
1542
+ const a = bodies[body_id_index(joint._bodyIdA)];
1543
+ const b = bodies[body_id_index(joint._bodyIdB)];
1544
+ if (a === undefined || b === undefined) continue;
1545
+ const aSleep = a.sleepState === SleepState.Sleeping;
1546
+ const bSleep = b.sleepState === SleepState.Sleeping;
1547
+ if (aSleep === bSleep) continue; // both awake or both asleep — leave as is
1548
+
1549
+ // The "awake" side must be capable of pulling the sleeper. A
1550
+ // static anchor's sleepState is never updated (it reads Awake
1551
+ // forever), so without this rule a body jointed to a static could
1552
+ // never sleep — perpetual mismatch wake. A kinematic driver
1553
+ // counts only while it is actually moving: a parked motor leaves
1554
+ // its chain asleep, and starting it wakes the chain (same motion
1555
+ // rule as __wake_pairs).
1556
+ const mover = aSleep ? b : a;
1557
+ if (mover.kind !== BodyKind.Dynamic) {
1558
+ const lv = mover.linearVelocity;
1559
+ const av = mover.angularVelocity;
1560
+ if (lv[0] === 0 && lv[1] === 0 && lv[2] === 0
1561
+ && av[0] === 0 && av[1] === 0 && av[2] === 0) {
1562
+ continue;
1563
+ }
1564
+ }
1565
+ if (aSleep) this.__wake_body(a); else this.__wake_body(b);
1566
+ }
1567
+ }
1568
+
1569
+ /**
1570
+ * Per-island atomic sleep test. Walks each island once and applies the
1571
+ * decision uniformly across all members:
1572
+ *
1573
+ * - If any member carries {@link RigidBodyFlags.DisableSleep}, the
1574
+ * entire island is exempt; every member's sleep_timer is reset.
1575
+ * - If `max(|v|² + |ω|²)` across all members is below
1576
+ * {@link sleepVelocitySqrThreshold}, every member's sleep_timer is
1577
+ * incremented by `dt`. When the smallest member's timer crosses
1578
+ * {@link sleepTimeThreshold}, the whole island sleeps atomically in
1579
+ * the same step (members get threaded into a sleep-group chain so
1580
+ * {@link __wake_body} can wake them all in one call).
1581
+ * - Otherwise the island has at least one active member, so every
1582
+ * member's timer is reset.
1583
+ *
1584
+ * This is the design-plan atomic-island sleep — replaces the per-body
1585
+ * approximation that lived in this slot during the previous slice.
1586
+ * Weakly-connected piles no longer chatter awake when a single member
1587
+ * blips above threshold; piles fall asleep and wake up as one.
1588
+ *
1589
+ * @private
1590
+ * @param {number} dt
1591
+ */
1592
+ __sleep_test(dt) {
1593
+ const threshold_sqr = this.sleepVelocitySqrThreshold;
1594
+ const time_threshold = this.sleepTimeThreshold;
1595
+ const bodies = this.__bodies;
1596
+ const islands = this.islands;
1597
+ const island_count = islands.island_count;
1598
+ const body_offsets = islands.body_offsets;
1599
+ const body_data = islands.body_data;
1600
+
1601
+ for (let isl = 0; isl < island_count; isl++) {
1602
+ const start = body_offsets[isl];
1603
+ const end = body_offsets[isl + 1];
1604
+ if (end === start) continue;
1605
+
1606
+ // Pass 1: find max + check DisableSleep across the island.
1607
+ let max_v_sqr = 0;
1608
+ let any_disable_sleep = false;
1609
+ for (let i = start; i < end; i++) {
1610
+ const idx = body_data[i];
1611
+ const rb = bodies[idx];
1612
+ if (rb === undefined) continue;
1613
+ if ((rb.flags & RigidBodyFlags.DisableSleep) !== 0) {
1614
+ any_disable_sleep = true;
1615
+ break;
1616
+ }
1617
+ const lv = rb.linearVelocity;
1618
+ const av = rb.angularVelocity;
1619
+ const v_sqr = lv[0] * lv[0] + lv[1] * lv[1] + lv[2] * lv[2]
1620
+ + av[0] * av[0] + av[1] * av[1] + av[2] * av[2];
1621
+ if (v_sqr > max_v_sqr) max_v_sqr = v_sqr;
1622
+ }
1623
+
1624
+ if (any_disable_sleep) {
1625
+ // Whole island is exempt — reset every member's timer.
1626
+ for (let i = start; i < end; i++) {
1627
+ const rb = bodies[body_data[i]];
1628
+ if (rb !== undefined) rb.sleep_timer = 0;
1629
+ }
1630
+ continue;
1631
+ }
1632
+
1633
+ if (max_v_sqr < threshold_sqr) {
1634
+ // Island is at rest increment every member's timer; if the
1635
+ // slowest-stabilising member has crossed the time threshold,
1636
+ // every member has (they were incremented together this step),
1637
+ // so atomic-sleep the island.
1638
+ let min_timer = Infinity;
1639
+ for (let i = start; i < end; i++) {
1640
+ const rb = bodies[body_data[i]];
1641
+ if (rb === undefined) continue;
1642
+ rb.sleep_timer += dt;
1643
+ if (rb.sleep_timer < min_timer) min_timer = rb.sleep_timer;
1644
+ }
1645
+ if (min_timer >= time_threshold) {
1646
+ this.__atomic_sleep_island_range(body_data, start, end);
1647
+ }
1648
+ } else {
1649
+ // At least one member is active reset every timer.
1650
+ for (let i = start; i < end; i++) {
1651
+ const rb = bodies[body_data[i]];
1652
+ if (rb !== undefined) rb.sleep_timer = 0;
1653
+ }
1654
+ }
1655
+ }
1656
+ }
1657
+
1658
+ /**
1659
+ * Dispatch every buffered contact event to the entities involved, via the
1660
+ * dataset's per-entity event channel `dataset.sendEvent(entity, name,
1661
+ * payload)`, once for each of the pair's entities. This is the only
1662
+ * contact-event path: a system with no dataset attached has nowhere to
1663
+ * deliver and emits nothing.
1664
+ *
1665
+ * Payload is a reused scratch object; listeners must copy anything they
1666
+ * intend to retain past the listener body.
1667
+ * @private
1668
+ */
1669
+ __dispatch_contact_events() {
1670
+ const events = this.contactEvents;
1671
+ const n = events.count;
1672
+ if (n === 0) return;
1673
+
1674
+ const ecd = (this.entityManager !== null && this.entityManager !== undefined)
1675
+ ? this.entityManager.dataset
1676
+ : null;
1677
+ // No dataset → nowhere to deliver. The buffer is cleared by
1678
+ // diff_manifolds each step regardless, so nothing leaks.
1679
+ if (ecd === null || ecd === undefined) return;
1680
+
1681
+ const manifolds = this.manifolds;
1682
+ const data = manifolds.data_buffer;
1683
+
1684
+ const payload = this.__contact_payload;
1685
+
1686
+ for (let i = 0; i < n; i++) {
1687
+ const kind = events.kind_at(i);
1688
+ const entA = events.entityA_at(i);
1689
+ const entB = events.entityB_at(i);
1690
+ const slot = events.slot_at(i);
1691
+
1692
+ // Use the deepest contact of the manifold as the representative
1693
+ // point/normal/depth for the event. v1: contact 0 only.
1694
+ const slot_off = manifolds.slot_data_offset(slot);
1695
+ // Gate on the SAME predicate diff_manifolds used to decide this event, not on
1696
+ // contact_count alone: an untouched slot (pair left broadphase range, or a body was
1697
+ // removed) keeps its last-narrowphased contact_count — non-zero — because nothing
1698
+ // clears the data slab for it. Re-deriving from contact_count would stamp that stale
1699
+ // geometry into an End payload (a live penetration reported for bodies now far apart);
1700
+ // matching the diff (is_touched && count>0) yields a zeroed payload, as the in-range
1701
+ // separation path already does via clear_contacts.
1702
+ const has_contact = manifolds.is_touched(slot) && manifolds.contact_count(slot) > 0;
1703
+
1704
+ // Scratch payload — write the point/normal components by index to
1705
+ // skip the Vector3 change-signal dispatch (nothing observes them).
1706
+ const pt = payload.point;
1707
+ const nm = payload.normal;
1708
+ if (has_contact) {
1709
+ const wax = data[slot_off], way = data[slot_off + 1], waz = data[slot_off + 2];
1710
+ const wbx = data[slot_off + 3], wby = data[slot_off + 4], wbz = data[slot_off + 5];
1711
+ pt[0] = (wax + wbx) * 0.5;
1712
+ pt[1] = (way + wby) * 0.5;
1713
+ pt[2] = (waz + wbz) * 0.5;
1714
+ nm[0] = data[slot_off + 6];
1715
+ nm[1] = data[slot_off + 7];
1716
+ nm[2] = data[slot_off + 8];
1717
+ payload.depth = data[slot_off + 9];
1718
+
1719
+ // The event pair is entity-canonical (min, max); the slot's
1720
+ // stored normal points from the slot's OWN body B toward its
1721
+ // body A — an assignment that follows packed-id order, i.e.
1722
+ // slot allocation history. Mirror the normal when the
1723
+ // canonicalization swapped the pair, so the payload contract
1724
+ // ("normal points from entityB toward entityA") holds
1725
+ // identically in every engine regardless of slot history.
1726
+ const slot_idA = manifolds.bodyA(slot);
1727
+ const slot_entA = this.storage.is_valid(slot_idA)
1728
+ ? this.storage.entity_at(body_id_index(slot_idA))
1729
+ : -1;
1730
+ if (slot_entA !== entA) {
1731
+ nm[0] = -nm[0];
1732
+ nm[1] = -nm[1];
1733
+ nm[2] = -nm[2];
1734
+ }
1735
+ } else {
1736
+ pt[0] = 0;
1737
+ pt[1] = 0;
1738
+ pt[2] = 0;
1739
+ nm[0] = 0;
1740
+ nm[1] = 0;
1741
+ nm[2] = 0;
1742
+ payload.depth = 0;
1743
+ }
1744
+ payload.entityA = entA;
1745
+ payload.entityB = entB;
1746
+
1747
+ let event_name;
1748
+ if (kind === ContactEventKind.Begin) {
1749
+ event_name = PhysicsEvents.ContactBegin;
1750
+ } else if (kind === ContactEventKind.Stay) {
1751
+ event_name = PhysicsEvents.ContactStay;
1752
+ } else {
1753
+ event_name = PhysicsEvents.ContactEnd;
1754
+ }
1755
+
1756
+ if (entA >= 0) ecd.sendEvent(entA, event_name, payload);
1757
+ if (entB >= 0) ecd.sendEvent(entB, event_name, payload);
1758
+ }
1759
+ }
1760
+
1761
+ /**
1762
+ * Producer restore pass. At the top of a fixed step, reset every awake
1763
+ * {@link Interpolated} body's live components to their authoritative state
1764
+ * from the previous tick's snapshot, undoing any render-time interpolation
1765
+ * the {@link InterpolationSystem} wrote between frames so the sim integrates
1766
+ * from truth, not an interpolated pose. A body with no previous snapshot
1767
+ * (first step ever, or just woken) is left as-is — its live state is already
1768
+ * authoritative. No-op unless {@link interpolationLog} is wired.
1769
+ * @private
1770
+ */
1771
+ __interp_restore() {
1772
+ const log = this.interpolationLog;
1773
+ const em = this.entityManager;
1774
+ if (log === null || em === null || em === undefined) return;
1775
+ const dataset = em.dataset;
1776
+ if (dataset === null || dataset === undefined) return;
1777
+
1778
+ const prev_tick = em.fixedStepTick - 1;
1779
+ if (prev_tick < 0) return;
1780
+
1781
+ const storage = this.storage;
1782
+ // Iterate the set snapshotted at prev_tick (the previous __interp_record), NOT the current
1783
+ // awake set: a body that fell asleep on prev_tick was awake when it was recorded (record runs
1784
+ // before the sleep test) so it carries a prev_tick snapshot, yet it is no longer in the awake
1785
+ // set. Restoring only the awake set would leave that just-slept body holding the render-blended
1786
+ // pose the last frame wrote — which the sim then reads via __swept_proximity and integrates
1787
+ // from if it wakes this step. A body woken THIS step has no prev_tick record, so log.interpolate
1788
+ // returns false and it is correctly left on its (authoritative) live pose.
1789
+ const recorded = this.__interp_recorded;
1790
+ const count = this.__interp_recorded_count;
1791
+ const scratch = this.__interp_scratch;
1792
+
1793
+ for (let i = 0; i < count; i++) {
1794
+ const idx = recorded[i];
1795
+ const entity = storage.entity_at(idx);
1796
+ if (entity < 0) continue; // slot freed since it was recorded
1797
+ const interpolated = dataset.getComponent(entity, Interpolated);
1798
+ if (interpolated === undefined || interpolated === null) continue;
1799
+ // A teleported body (snap set, e.g. via setPose) keeps its live pose
1800
+ // this step — restoring the previous tick would undo the teleport.
1801
+ if (interpolated.snap) continue;
1802
+ const key = interpolated.key;
1803
+ if (key < 0) continue;
1804
+
1805
+ const interpolands = interpolated.interpolands;
1806
+ for (let k = 0; k < interpolands.length; k++) {
1807
+ const ip = interpolands[k];
1808
+ scratch.position = 0;
1809
+ // Snap to the previous tick's snapshot (both offsets equal → t irrelevant).
1810
+ const ok = log.interpolate(scratch, key, ip.type_id, prev_tick, prev_tick, 0, ip.interpolation_adapter);
1811
+ if (!ok) continue;
1812
+ const target = dataset.getComponent(entity, ip.component_class);
1813
+ if (target === undefined || target === null) continue;
1814
+ scratch.position = 0;
1815
+ ip.serialization_adapter.deserialize(scratch, target);
1816
+ }
1817
+ }
1818
+ }
1819
+
1820
+ /**
1821
+ * Producer record pass. At the end of a fixed step, snapshot every awake
1822
+ * {@link Interpolated} body's live components into the shared log under the
1823
+ * current `entityManager.fixedStepTick`. The render-time
1824
+ * {@link InterpolationSystem} blends consecutive ticks from these snapshots.
1825
+ * Only awake (moving) bodies are recorded, so the log stays sparse. No-op
1826
+ * unless {@link interpolationLog} is wired.
1827
+ * @private
1828
+ */
1829
+ __interp_record() {
1830
+ const log = this.interpolationLog;
1831
+ const em = this.entityManager;
1832
+ if (log === null || em === null || em === undefined) return;
1833
+ const dataset = em.dataset;
1834
+ if (dataset === null || dataset === undefined) return;
1835
+
1836
+ const tick = em.fixedStepTick;
1837
+ const storage = this.storage;
1838
+ const count = storage.awake_count;
1839
+
1840
+ // Rebuild the "recorded this tick" set that __interp_restore will iterate next tick.
1841
+ let recorded = this.__interp_recorded;
1842
+ let recorded_count = 0;
1843
+
1844
+ log.begin_tick(tick);
1845
+ for (let i = 0; i < count; i++) {
1846
+ const idx = storage.awake_at(i);
1847
+ const entity = storage.entity_at(idx);
1848
+ const interpolated = dataset.getComponent(entity, Interpolated);
1849
+ if (interpolated === undefined || interpolated === null) continue;
1850
+ const key = interpolated.key;
1851
+ if (key < 0) continue;
1852
+
1853
+ if (recorded_count === recorded.length) {
1854
+ const grown = new Uint32Array(recorded.length * 2);
1855
+ grown.set(recorded);
1856
+ recorded = grown;
1857
+ this.__interp_recorded = grown;
1858
+ }
1859
+ recorded[recorded_count++] = idx;
1860
+
1861
+ const interpolands = interpolated.interpolands;
1862
+ for (let k = 0; k < interpolands.length; k++) {
1863
+ const ip = interpolands[k];
1864
+ const target = dataset.getComponent(entity, ip.component_class);
1865
+ if (target === undefined || target === null) continue;
1866
+ const buf = log.begin_record(key, ip.type_id);
1867
+ ip.serialization_adapter.serialize(buf, target);
1868
+ log.end_record();
1869
+ }
1870
+ }
1871
+ log.end_tick();
1872
+
1873
+ this.__interp_recorded_count = recorded_count;
1874
+ }
1875
+
1876
+ fixedUpdate(dt) {
1877
+ // Producer: restore authoritative pose (undo render interpolation)
1878
+ // before the sim reads any Transform this step.
1879
+ this.__interp_restore();
1880
+
1881
+ const gx = this.gravity.x;
1882
+ const gy = this.gravity.y;
1883
+ const gz = this.gravity.z;
1884
+
1885
+ const storage = this.storage;
1886
+ const bodies = this.__bodies;
1887
+ const transforms = this.__transforms;
1888
+ const joints = this.__joints;
1889
+ const manifolds = this.manifolds;
1890
+
1891
+ // Stage 0: wake propagation — through the persistent manifolds
1892
+ // (last frame's broadphase-touched pairs), then through joints.
1893
+ // Runs before everything else so a just-woken body is part of this
1894
+ // frame's awake set end to end (forces, refit, broadphase,
1895
+ // narrowphase, islands, solve): waking later would give it one
1896
+ // unsupported step (gravity with no contacts → penetration pop).
1897
+ // Reads only manifold + body state — both part of the exportable
1898
+ // world state — never the transient pair list, so a reconstructed
1899
+ // engine wakes on the original's schedule.
1900
+ this.__wake_pairs(dt);
1901
+ this.__wake_joints();
1902
+
1903
+ const count = storage.awake_count;
1904
+
1905
+ // Stage 1: consume the per-frame force / torque accumulators into
1906
+ // velocity at the full `dt`, exactly once (a user force is a per-frame
1907
+ // budget that must land in full regardless of substep count). Gravity
1908
+ // is applied per substep below, so the trajectory integrates at the
1909
+ // substep rate and — crucially — each substep's gravity is balanced by
1910
+ // that substep's contact warm-start, keeping resting stacks at zero
1911
+ // velocity.
1912
+ for (let i = 0; i < count; i++) {
1913
+ const idx = storage.awake_at(i);
1914
+
1915
+ const rb = bodies[idx];
1916
+ const tr = transforms[idx];
1917
+
1918
+ integrate_velocity_forces(rb, tr, dt);
1919
+ }
1920
+
1921
+ // Stage 2: refit each awake body's collider leaves at the current
1922
+ // pose, padded by the swept extent for the body's velocity. The fat
1923
+ // margin uses the post-force velocity; this frame's gravity increment
1924
+ // is a sub-millimetre slack difference, safely inside the margin.
1925
+ const lists = this.__body_collider_lists;
1926
+ for (let i = 0; i < count; i++) {
1927
+
1928
+ const idx = storage.awake_at(i);
1929
+ const rb = bodies[idx];
1930
+ const list = lists[idx];
1931
+
1932
+ if (list === undefined) {
1933
+ continue;
1934
+ }
1935
+
1936
+ const lv = rb.linearVelocity;
1937
+
1938
+ const list_length = list.length;
1939
+
1940
+ for (let k = 0; k < list_length; k++) {
1941
+
1942
+ const entry = list[k];
1943
+
1944
+ // Refit skip — the fat margin's whole purpose: while the
1945
+ // body's FATTENED box (tight + velocity pad) stays inside the
1946
+ // leaf's stored AABB, the stored box still covers everything
1947
+ // the body can reach this step, so the set + refit-to-root is
1948
+ // pure waste. A resting field pays only this containment test
1949
+ // per leaf per frame (at v ≈ 0 the fat box is the tight box
1950
+ // plus the constant slack, which the stored box includes).
1951
+ //
1952
+ // The containment probe MUST be the fat box, not the tight
1953
+ // one: the leaf is how the broadphase and every swept query
1954
+ // discover this body. A body whose velocity grew while its
1955
+ // tight box still sat inside the stored leaf (freshly linked
1956
+ // with a launch velocity, or accelerated by an impulse) would
1957
+ // otherwise keep a leaf covering none of the path it travels
1958
+ // this step invisible to pair generation and to other
1959
+ // bodies' CCD sweeps for the whole step.
1960
+ compute_world_aabb(scratch_world_aabb, 0, entry.collider.shape, entry.transform);
1961
+ fatten_world_aabb(scratch_world_aabb, 0, lv[0], lv[1], lv[2], dt);
1962
+ const node = entry.bvhNode;
1963
+ this.dynamicBvh.node_get_aabb(node, scratch_stored_aabb);
1964
+ if (scratch_world_aabb[0] >= scratch_stored_aabb[0]
1965
+ && scratch_world_aabb[1] >= scratch_stored_aabb[1]
1966
+ && scratch_world_aabb[2] >= scratch_stored_aabb[2]
1967
+ && scratch_world_aabb[3] <= scratch_stored_aabb[3]
1968
+ && scratch_world_aabb[4] <= scratch_stored_aabb[4]
1969
+ && scratch_world_aabb[5] <= scratch_stored_aabb[5]) {
1970
+ continue;
1971
+ }
1972
+
1973
+ this.dynamicBvh.node_move_aabb(node, scratch_world_aabb);
1974
+ }
1975
+ }
1976
+
1977
+ // Stage 3: broadphase pair generation. The fat AABBs cover the full
1978
+ // outer-step motion, so the pair set stays valid across all substeps
1979
+ // — broadphase runs once.
1980
+ generate_pairs(
1981
+ storage,
1982
+ this.dynamicBvh,
1983
+ this.staticBvh,
1984
+ manifolds,
1985
+ lists,
1986
+ this.pairs,
1987
+ this.__pair_filter,
1988
+ this,
1989
+ );
1990
+
1991
+ // Stage 5: narrowphase once per outer step. The substep loop below
1992
+ // re-derives each contact's penetration analytically from the moved
1993
+ // poses rather than re-running geometry.
1994
+ narrowphase_step(this.pairs, manifolds, this.__body_collider_lists);
1995
+
1996
+ // Stage 6: partition awake bodies + touched contacts into islands.
1997
+ // Consumed by the solver (flattened contact list) and the sleep test.
1998
+ this.islands.build(storage, manifolds, bodies, this.__body_collider_lists, joints);
1999
+
2000
+ // Stage 7: TGS substep loop.
2001
+ //
2002
+ // prepare_contacts captures per-contact anchors / effective masses /
2003
+ // approach velocity (no warm-start that's per-substep). Each substep
2004
+ // integrates gravity by `h`, re-derives contact geometry from the
2005
+ // current poses, replays warm-start, solves velocity (non-penetration
2006
+ // + friction) and position (pseudo-velocity), and integrates the pose
2007
+ // by `h`. Per-substep gravity + per-substep warm-start balance exactly
2008
+ // at a resting contact (each cancels `h` of the other), so stacks hold
2009
+ // at zero velocity and sleep; the position correction adapts as bodies
2010
+ // separate between substeps — the TGS stack-stability mechanism —
2011
+ // without re-running narrowphase. Restitution is applied once after
2012
+ // the loop.
2013
+ // Clamp the substep count: 0 (or a negative / fractional value) would
2014
+ // feed h = dt / N = Infinity into prepare_contacts and a broken loop
2015
+ // bound into the solver.
2016
+ const N = this.substeps > 1 ? this.substeps | 0 : 1;
2017
+ const h = dt / N;
2018
+ const count_after_wake = storage.awake_count;
2019
+
2020
+ // CCD: capture start-of-step positions for flagged bodies over the
2021
+ // post-wake awake set (poses are unchanged until the substep loop below
2022
+ // integrates them). The CCD pass after the solver sweeps from here to
2023
+ // each body's final pose. Reads the primary collider's transform so the
2024
+ // start matches the end the resolve pass reads. Zero-cost when no body
2025
+ // is flagged.
2026
+ const ccd_on = this.ccdEnabled;
2027
+
2028
+ if (ccd_on) {
2029
+
2030
+ const ccd_need = storage.high_water_mark * 3;
2031
+
2032
+ if (this.__ccd_start_pos.length < ccd_need) {
2033
+ this.__ccd_start_pos = new Float64Array(ccd_need);
2034
+ }
2035
+
2036
+ const ccd_start = this.__ccd_start_pos;
2037
+
2038
+ for (let i = 0; i < count_after_wake; i++) {
2039
+ const idx = storage.awake_at(i);
2040
+ const rb = bodies[idx];
2041
+
2042
+ if (rb.kind !== BodyKind.Dynamic) {
2043
+ continue;
2044
+ }
2045
+ if ((rb.flags & RigidBodyFlags.CCD) === 0) {
2046
+ continue;
2047
+ }
2048
+
2049
+ const list = this.__body_collider_lists[idx];
2050
+
2051
+ if (list === undefined || list.length === 0) {
2052
+ continue;
2053
+ }
2054
+
2055
+ const cp = list[0].transform.position;
2056
+ const cb = idx * 3;
2057
+
2058
+ ccd_start[cb] = cp[0];
2059
+ ccd_start[cb + 1] = cp[1];
2060
+ ccd_start[cb + 2] = cp[2];
2061
+ }
2062
+ }
2063
+
2064
+ // Size the pseudo-velocity buffer ONCE (it may reallocate on growth),
2065
+ // then capture the reference. Inside the loop we only zero its live
2066
+ // region per substep — re-capturing is unnecessary since it won't
2067
+ // reallocate again this step.
2068
+ this.__reset_pseudo_velocity();
2069
+ const pseudoVel = this.__pseudo_velocity;
2070
+ const pseudo_len = storage.high_water_mark * 6;
2071
+
2072
+ // Gather the data-oriented solver state for every body the substep loop
2073
+ // will touch: the post-wake awake set (all dynamics, at their post-force
2074
+ // velocity) plus the static / kinematic anchors and jointed partners
2075
+ // referenced by this step's contacts and joints. From here the substep
2076
+ // loop reads / writes velocity + orientation through `ss_data` with no
2077
+ // component-object dereference; persistent velocity is scattered back
2078
+ // onto the RigidBodies after the solve.
2079
+ const solver_state = this.__solver_state;
2080
+ solver_state.begin(storage.high_water_mark);
2081
+
2082
+ for (let i = 0; i < count_after_wake; i++) {
2083
+ const idx = storage.awake_at(i);
2084
+ solver_state.gather(idx, bodies[idx], transforms[idx]);
2085
+ }
2086
+
2087
+ const island_contacts = this.islands.contact_data;
2088
+ const island_contact_total = this.islands.contact_offsets[this.islands.island_count];
2089
+
2090
+ for (let i = 0; i < island_contact_total; i++) {
2091
+ const slot = island_contacts[i];
2092
+
2093
+ const ia = body_id_index(manifolds.bodyA(slot));
2094
+ const ib = body_id_index(manifolds.bodyB(slot));
2095
+
2096
+ solver_state.gather(ia, bodies[ia], transforms[ia]);
2097
+ solver_state.gather(ib, bodies[ib], transforms[ib]);
2098
+ }
2099
+
2100
+ const joint_count = joints.length;
2101
+ for (let i = 0; i < joint_count; i++) {
2102
+ const joint = joints[i];
2103
+
2104
+ if (joint === undefined || joint === null) {
2105
+ continue;
2106
+ }
2107
+
2108
+ if (!storage.is_valid(joint._bodyIdA)) {
2109
+ continue;
2110
+ }
2111
+
2112
+ const ia = body_id_index(joint._bodyIdA);
2113
+
2114
+ solver_state.gather(ia, bodies[ia], transforms[ia]);
2115
+
2116
+ if (joint._bodyIdB !== JOINT_WORLD && storage.is_valid(joint._bodyIdB)) {
2117
+
2118
+ const ib = body_id_index(joint._bodyIdB);
2119
+
2120
+ solver_state.gather(ib, bodies[ib], transforms[ib]);
2121
+
2122
+ }
2123
+ }
2124
+
2125
+ const ss_data = solver_state.data;
2126
+
2127
+ prepare_contacts(manifolds, this, h);
2128
+
2129
+ for (let s = 0; s < N; s++) {
2130
+
2131
+ // Gravity (+ damping) for this substep.
2132
+ for (let i = 0; i < count_after_wake; i++) {
2133
+ const idx = storage.awake_at(i);
2134
+ integrate_velocity_gravity(ss_data, idx * SBS_STRIDE, bodies[idx], gx, gy, gz, h);
2135
+ }
2136
+
2137
+ // Re-derive contact geometry at the current poses: concave pairs
2138
+ // re-run narrowphase (fresh feature/normal as the body rocks),
2139
+ // convex pairs rotate frozen anchors analytically. Then replay
2140
+ // the per-substep warm-start and solve velocity.
2141
+ redetect_concave_contacts(manifolds, this);
2142
+ refresh_contacts(manifolds, this.__transforms);
2143
+ warm_start_contacts(manifolds, this);
2144
+ solve_velocity(manifolds, this, this.velocityIterations);
2145
+
2146
+ // Joints share the substep: warm-start + velocity-solve the 6-DOF
2147
+ // constraints on real velocity, coupled with the contacts above
2148
+ // (a body touched by both sees one substep of Gauss-Seidel across
2149
+ // contacts then joints). Position correction for locked DOFs is a
2150
+ // SPOOK bias inside this solve, so no separate joint position pass.
2151
+ // Solved in the entity-canonical order (NOT joint-id order, which
2152
+ // encodes link/unlink history) see __joints_sorted.
2153
+ if (this.__joints_sorted.length > 0) {
2154
+ // Alternate the joint sweep direction on odd substeps so the chain
2155
+ // is solved root→tip and tip→root across the step (symmetric
2156
+ // Gauss-Seidel), cancelling the one-direction propagation bias.
2157
+ solve_joints(this.__joints_sorted, this, h, this.jointIterations, (s & 1) === 1);
2158
+ }
2159
+
2160
+ // Position correction writes pseudo-velocity (zeroed first so it
2161
+ // is a fresh per-substep correction), folded into the pose by the
2162
+ // position integrate and then discarded. SOFT contacts skip it:
2163
+ // their penetration recovery is the velocity-bias term, and the
2164
+ // relax pass below strips the momentum that bias injected
2165
+ // (Box2D-v3 substep order).
2166
+ const contacts_soft = this.contactHertz > 0;
2167
+ pseudoVel.fill(0, 0, pseudo_len);
2168
+ if (!contacts_soft) {
2169
+ solve_position(manifolds, this, this.positionIterations);
2170
+ }
2171
+
2172
+ for (let i = 0; i < count_after_wake; i++) {
2173
+ const idx = storage.awake_at(i);
2174
+ const rb = bodies[idx];
2175
+ const tr = transforms[idx];
2176
+ const base = idx * 6;
2177
+ integrate_position(ss_data, idx * SBS_STRIDE, rb, tr, h,
2178
+ pseudoVel[base], pseudoVel[base + 1], pseudoVel[base + 2],
2179
+ pseudoVel[base + 3], pseudoVel[base + 4], pseudoVel[base + 5]);
2180
+ }
2181
+
2182
+ // Relax pass (soft contacts only): one biasless sweep over the
2183
+ // contact rows AFTER position integration, removing the real
2184
+ // momentum the velocity-bias push injected this substep
2185
+ // (Box2D-v3 substep order: solve -> integrate -> relax).
2186
+ if (contacts_soft) {
2187
+ solve_velocity(manifolds, this, 1, false);
2188
+ }
2189
+ }
2190
+
2191
+ // Stage 8: one-shot restitution, after the substep loop, keyed off
2192
+ // the approach velocity captured at prepare time.
2193
+ apply_restitution(manifolds, this);
2194
+
2195
+ // Scatter the solved persistent linear / angular velocity back onto the
2196
+ // RigidBody components (pose was written through to the Transforms each
2197
+ // substep). After this the bodies are authoritative again for CCD,
2198
+ // interpolation recording, and the sleep test below.
2199
+ solver_state.scatter(bodies);
2200
+
2201
+ // Stage 8.5: continuous collision — sweep CCD-flagged fast movers along
2202
+ // their net step translation and stop them at the first blocker, so they
2203
+ // can't tunnel through thin geometry between discrete steps. Runs on the
2204
+ // final post-solve poses, before the sleep test sees the clamped
2205
+ // velocities. No-op when no awake body is flagged.
2206
+ if (ccd_on) {
2207
+ ccd_resolve(this);
2208
+ }
2209
+
2210
+ // Producer: record the post-step authoritative pose of every awake
2211
+ // interpolated body under this step's tick. Before the sleep test, so a
2212
+ // body that settles this step still records its final pose for the last
2213
+ // interpolation interval.
2214
+ this.__interp_record();
2215
+
2216
+ // Stage 9: sleep test.
2217
+ this.__sleep_test(dt);
2218
+
2219
+ // Stage 10: diff manifolds against the previous frame and dispatch
2220
+ // Begin / Stay / End events. MUST run before advance_frame, which
2221
+ // rolls the touched flags.
2222
+ diff_manifolds(manifolds, storage, this.contactEvents);
2223
+ this.__dispatch_contact_events();
2224
+
2225
+ // Stage 11 (end-of-step): roll touched → prev_touched and the
2226
+ // had-contacts flag, evict slots whose pair has not been touched
2227
+ // within the grace window. Dormant pairs (both bodies asleep) are
2228
+ // frozen — contacts and warm-start impulses survive the nap.
2229
+ manifolds.advance_frame(storage);
2230
+
2231
+ // Dev tripwire (assert.* is compiled out of prod): no NaN / Infinity
2232
+ // may survive a step in body state or contact manifolds. NaN is
2233
+ // self-concealing downstream — it fails every depth comparison and
2234
+ // sleep threshold — so it must fail loudly here, at the step boundary.
2235
+ assert.equal(find_non_finite_physics_state(this), "", "non-finite physics state after fixedUpdate");
2236
+ }
2237
+ }
2238
+
2239
+ /**
2240
+ * @readonly
2241
+ * @type {boolean}
2242
+ */
2243
+ PhysicsSystem.prototype.isPhysicsSystem = true;
2244
+
2245
+ // Re-export for convenience.
2246
+ export { BodyKind, RigidBodyFlags };