@woosh/meep-engine 2.169.4 → 2.170.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1559) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/editor/Editor.d.ts +161 -65
  3. package/editor/Editor.d.ts.map +1 -1
  4. package/editor/Editor.js +669 -794
  5. package/editor/EditorCameraNavigation.d.ts +21 -0
  6. package/editor/EditorCameraNavigation.d.ts.map +1 -0
  7. package/editor/EditorCameraNavigation.js +191 -0
  8. package/editor/EditorKeyMap.d.ts +28 -0
  9. package/editor/EditorKeyMap.d.ts.map +1 -0
  10. package/editor/EditorKeyMap.js +182 -0
  11. package/editor/REWORK_PLAN_2026_08.md +692 -0
  12. package/editor/SelectionVisualizer.d.ts +48 -19
  13. package/editor/SelectionVisualizer.d.ts.map +1 -1
  14. package/editor/SelectionVisualizer.js +163 -156
  15. package/editor/actions/concrete/ActionUpdateTexture.d.ts +11 -11
  16. package/editor/actions/concrete/ArrayCopyAction.d.ts +19 -19
  17. package/editor/actions/concrete/ArrayCopyAction.d.ts.map +1 -1
  18. package/editor/actions/concrete/ArrayCopyAction.js +7 -7
  19. package/editor/actions/concrete/ComponentAddAction.d.ts +14 -14
  20. package/editor/actions/concrete/ComponentAddAction.d.ts.map +1 -1
  21. package/editor/actions/concrete/ComponentAddAction.js +6 -5
  22. package/editor/actions/concrete/ComponentRemoveAction.d.ts +10 -10
  23. package/editor/actions/concrete/ComponentRemoveAction.d.ts.map +1 -1
  24. package/editor/actions/concrete/ComponentRemoveAction.js +10 -12
  25. package/editor/actions/concrete/EntityCreateAction.d.ts +17 -17
  26. package/editor/actions/concrete/EntityCreateAction.d.ts.map +1 -1
  27. package/editor/actions/concrete/EntityCreateAction.js +11 -14
  28. package/editor/actions/concrete/EntityRemoveAction.d.ts +10 -10
  29. package/editor/actions/concrete/EntityRemoveAction.d.ts.map +1 -1
  30. package/editor/actions/concrete/EntityRemoveAction.js +2 -1
  31. package/editor/actions/concrete/ModifyPatchSampler2DAction.d.ts +46 -46
  32. package/editor/actions/concrete/ModifyPatchTextureArray2DAction.d.ts +37 -37
  33. package/editor/actions/concrete/ModifyPatchTextureArray2DAction.d.ts.map +1 -1
  34. package/editor/actions/concrete/ModifyPatchTextureArray2DAction.js +3 -1
  35. package/editor/actions/concrete/PaintTerrainOverlayAction.d.ts +22 -22
  36. package/editor/actions/concrete/PaintTerrainOverlayAction.d.ts.map +1 -1
  37. package/editor/actions/concrete/PaintTerrainOverlayAction.js +3 -3
  38. package/editor/actions/concrete/PatchTerrainHeightAction.d.ts +31 -18
  39. package/editor/actions/concrete/PatchTerrainHeightAction.d.ts.map +1 -1
  40. package/editor/actions/concrete/PatchTerrainHeightAction.js +28 -9
  41. package/editor/actions/concrete/SelectionAddAction.d.ts +18 -18
  42. package/editor/actions/concrete/SelectionAddAction.d.ts.map +1 -1
  43. package/editor/actions/concrete/SelectionAddAction.js +2 -1
  44. package/editor/actions/concrete/SelectionClearAction.d.ts +8 -8
  45. package/editor/actions/concrete/SelectionClearAction.d.ts.map +1 -1
  46. package/editor/actions/concrete/SelectionClearAction.js +1 -4
  47. package/editor/actions/concrete/SelectionRemoveAction.d.ts +9 -9
  48. package/editor/actions/concrete/SelectionRemoveAction.d.ts.map +1 -1
  49. package/editor/actions/concrete/SelectionRemoveAction.js +2 -1
  50. package/editor/actions/concrete/TransformModifyAction.d.ts +16 -16
  51. package/editor/actions/concrete/TransformModifyAction.d.ts.map +1 -1
  52. package/editor/actions/concrete/TransformModifyAction.js +8 -8
  53. package/editor/actions/concrete/WriteGridValueAction.d.ts +13 -14
  54. package/editor/actions/concrete/WriteGridValueAction.d.ts.map +1 -1
  55. package/editor/actions/concrete/WriteGridValueAction.js +3 -3
  56. package/editor/camera/camera_framing.d.ts +30 -0
  57. package/editor/camera/camera_framing.d.ts.map +1 -0
  58. package/editor/camera/camera_framing.js +98 -0
  59. package/editor/clipboard.d.ts +19 -0
  60. package/editor/clipboard.d.ts.map +1 -0
  61. package/editor/clipboard.js +184 -0
  62. package/editor/ecs/EditorEntity.d.ts +15 -15
  63. package/editor/ecs/EditorEntitySystem.d.ts +7 -7
  64. package/editor/ecs/EditorEntitySystem.d.ts.map +1 -1
  65. package/editor/ecs/EditorEntitySystem.js +1 -6
  66. package/editor/ecs/component/FieldDescriptor.d.ts +26 -26
  67. package/editor/ecs/component/FieldDescriptor.d.ts.map +1 -1
  68. package/editor/ecs/component/FieldDescriptor.js +20 -22
  69. package/editor/ecs/component/FieldValueAdapter.d.ts +6 -6
  70. package/editor/ecs/component/FieldValueAdapter.d.ts.map +1 -1
  71. package/editor/ecs/component/FieldValueAdapter.js +2 -5
  72. package/editor/ecs/component/TypeEditor.d.ts +24 -24
  73. package/editor/ecs/component/createFieldEditor.d.ts +8 -8
  74. package/editor/ecs/component/createObjectEditor.d.ts +13 -13
  75. package/editor/ecs/component/editors/ColorEditor.d.ts +6 -6
  76. package/editor/ecs/component/editors/HTMLElementEditor.d.ts +6 -6
  77. package/editor/ecs/component/editors/HTMLElementEditor.d.ts.map +1 -1
  78. package/editor/ecs/component/editors/ImagePathEditor.d.ts +6 -6
  79. package/editor/ecs/component/editors/LargeStringEditor.d.ts +15 -15
  80. package/editor/ecs/component/editors/NumericIntervalEditor.d.ts +6 -6
  81. package/editor/ecs/component/editors/NumericIntervalEditor.d.ts.map +1 -1
  82. package/editor/ecs/component/editors/ObservedBooleanEditor.d.ts +6 -6
  83. package/editor/ecs/component/editors/ObservedEnumEditor.d.ts +6 -6
  84. package/editor/ecs/component/editors/ObservedIntegerEditor.d.ts +6 -6
  85. package/editor/ecs/component/editors/ObservedIntegerEditor.d.ts.map +1 -1
  86. package/editor/ecs/component/editors/ObservedStringEditor.d.ts +6 -6
  87. package/editor/ecs/component/editors/ObservedStringEditor.d.ts.map +1 -1
  88. package/editor/ecs/component/editors/Sampler2DEditor.d.ts +6 -6
  89. package/editor/ecs/component/editors/collection/ListEditor.d.ts +7 -7
  90. package/editor/ecs/component/editors/common/BitFlagsEditor.d.ts +11 -11
  91. package/editor/ecs/component/editors/common/EnumEditor.d.ts +7 -7
  92. package/editor/ecs/component/editors/common/makeV3_editor.d.ts +14 -14
  93. package/editor/ecs/component/editors/common/makeV3_editor.d.ts.map +1 -1
  94. package/editor/ecs/component/editors/common/noEditor.d.ts +2 -2
  95. package/editor/ecs/component/editors/common/two_way_sync.d.ts +22 -0
  96. package/editor/ecs/component/editors/common/two_way_sync.d.ts.map +1 -0
  97. package/editor/ecs/component/editors/ecs/GridObstacleEditor.d.ts +6 -6
  98. package/editor/ecs/component/editors/ecs/GridObstacleEditor.js +1 -1
  99. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.d.ts +19 -66
  100. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.d.ts.map +1 -1
  101. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +35 -239
  102. package/editor/ecs/component/editors/ecs/ParameterTrackEditor.d.ts +11 -11
  103. package/editor/ecs/component/editors/ecs/ParameterTrackEditor.d.ts.map +1 -1
  104. package/editor/ecs/component/editors/ecs/ParameterTrackEditor.js +17 -17
  105. package/editor/ecs/component/editors/ecs/ParameterTrackSetEditor.d.ts +13 -13
  106. package/editor/ecs/component/editors/ecs/ParticleEmitterEditor.d.ts +51 -51
  107. package/editor/ecs/component/editors/ecs/ParticleEmitterEditor.js +1 -1
  108. package/editor/ecs/component/editors/ecs/ParticleEmitterLayerEditor.d.ts +46 -47
  109. package/editor/ecs/component/editors/ecs/ParticleEmitterLayerEditor.d.ts.map +1 -1
  110. package/editor/ecs/component/editors/ecs/SimulationStepDefinitionEditor.d.ts +15 -15
  111. package/editor/ecs/component/editors/ecs/Trail2DEditor.d.ts +29 -29
  112. package/editor/ecs/component/editors/ecs/Trail3DEditor.d.ts +42 -0
  113. package/editor/ecs/component/editors/ecs/Trail3DEditor.d.ts.map +1 -0
  114. package/editor/ecs/component/editors/ecs/TransformEditor.d.ts +19 -19
  115. package/editor/ecs/component/editors/ecs/buildGridObstaclePreview.d.ts +8 -0
  116. package/editor/ecs/component/editors/ecs/buildGridObstaclePreview.d.ts.map +1 -0
  117. package/editor/ecs/component/editors/ecs/buildGridObstaclePreview.js +19 -0
  118. package/editor/ecs/component/editors/ecs/terrain/SplatMappingEditor.d.ts +18 -18
  119. package/editor/ecs/component/editors/ecs/terrain/TerrainEditor.d.ts +38 -38
  120. package/editor/ecs/component/editors/ecs/terrain/TerrainLayerEditor.d.ts +15 -15
  121. package/editor/ecs/component/editors/ecs/terrain/TerrainLayersEditor.d.ts +18 -18
  122. package/editor/ecs/component/editors/ecs/terrain/TerrainOverlayEditor.d.ts +17 -17
  123. package/editor/ecs/component/editors/geom/QuaternionEditor.d.ts +5 -5
  124. package/editor/ecs/component/editors/geom/QuaternionEditor.d.ts.map +1 -1
  125. package/editor/ecs/component/editors/geom/Vector1Editor.d.ts +6 -6
  126. package/editor/ecs/component/editors/geom/Vector1Editor.d.ts.map +1 -1
  127. package/editor/ecs/component/editors/geom/Vector2Editor.d.ts +5 -5
  128. package/editor/ecs/component/editors/geom/Vector3Editor.d.ts +5 -5
  129. package/editor/ecs/component/editors/geom/Vector4Editor.d.ts +5 -5
  130. package/editor/ecs/component/editors/primitive/ArrayEditor.d.ts +7 -7
  131. package/editor/ecs/component/editors/primitive/BooleanEditor.d.ts +6 -6
  132. package/editor/ecs/component/editors/primitive/FunctionEditor.d.ts +6 -6
  133. package/editor/ecs/component/editors/primitive/NumberEditor.d.ts +7 -7
  134. package/editor/ecs/component/editors/primitive/NumberEditor.d.ts.map +1 -1
  135. package/editor/ecs/component/editors/primitive/ObjectEditor.d.ts +5 -5
  136. package/editor/ecs/component/editors/primitive/StringEditor.d.ts +6 -6
  137. package/editor/ecs/component/editors/three/BufferGeometryEditor.d.ts +6 -6
  138. package/editor/ecs/component/editors/three/MaterialEditor.d.ts +6 -6
  139. package/editor/ecs/component/editors/three/MeshEditor.d.ts +6 -6
  140. package/editor/ecs/component/editors/three/TextureEditor.d.ts +6 -6
  141. package/editor/ecs/component/findNearestRegisteredType.d.ts +7 -7
  142. package/editor/ecs/component/registerEngineComponentEditors.d.ts +8 -0
  143. package/editor/ecs/component/registerEngineComponentEditors.d.ts.map +1 -0
  144. package/editor/ecs/component/registerEngineComponentEditors.js +85 -0
  145. package/editor/enableEditor.d.ts +13 -13
  146. package/editor/enableEditor.d.ts.map +1 -1
  147. package/editor/enableEditor.js +14 -4
  148. package/editor/library/MeshLibrary.d.ts +12 -12
  149. package/editor/library/MeshLibrary.d.ts.map +1 -1
  150. package/editor/library/MeshLibrary.js +4 -7
  151. package/editor/particles/lut_ops.d.ts +34 -0
  152. package/editor/particles/lut_ops.d.ts.map +1 -0
  153. package/editor/particles/lut_ops.js +148 -0
  154. package/editor/particles/particleEditEvents.d.ts +11 -0
  155. package/editor/particles/particleEditEvents.d.ts.map +1 -0
  156. package/editor/particles/particleEditEvents.js +12 -0
  157. package/editor/particles/rebuildParticleEmitter.d.ts +11 -0
  158. package/editor/particles/rebuildParticleEmitter.d.ts.map +1 -0
  159. package/editor/particles/rebuildParticleEmitter.js +49 -0
  160. package/editor/persistence/EditorLayoutStore.d.ts +30 -0
  161. package/editor/persistence/EditorLayoutStore.d.ts.map +1 -0
  162. package/editor/persistence/EditorLayoutStore.js +71 -0
  163. package/editor/persistence/SceneMetadata.d.ts +38 -0
  164. package/editor/persistence/SceneMetadata.d.ts.map +1 -0
  165. package/editor/persistence/SceneMetadata.js +52 -0
  166. package/editor/persistence/SceneSlotManager.d.ts +40 -0
  167. package/editor/persistence/SceneSlotManager.d.ts.map +1 -0
  168. package/editor/persistence/SceneSlotManager.js +144 -0
  169. package/editor/persistence/buildNewSceneDataset.d.ts +10 -0
  170. package/editor/persistence/buildNewSceneDataset.d.ts.map +1 -0
  171. package/editor/persistence/buildNewSceneDataset.js +67 -0
  172. package/editor/persistence/files.d.ts +24 -0
  173. package/editor/persistence/files.d.ts.map +1 -0
  174. package/editor/persistence/files.js +68 -0
  175. package/editor/persistence/scene_import.d.ts +45 -0
  176. package/editor/persistence/scene_import.d.ts.map +1 -0
  177. package/editor/persistence/scene_import.js +143 -0
  178. package/editor/persistence/scene_serialization.d.ts +36 -0
  179. package/editor/persistence/scene_serialization.d.ts.map +1 -0
  180. package/editor/persistence/scene_serialization.js +79 -0
  181. package/editor/persistence/string_buffer_codec.d.ts +20 -0
  182. package/editor/persistence/string_buffer_codec.d.ts.map +1 -0
  183. package/editor/persistence/string_buffer_codec.js +41 -0
  184. package/editor/process/DisableGameUIProcess.d.ts +7 -7
  185. package/editor/process/DisableGameUIProcess.d.ts.map +1 -1
  186. package/editor/process/DisableGameUIProcess.js +1 -5
  187. package/editor/process/EditorProcess.d.ts +18 -18
  188. package/editor/process/GridDisplayProcess.d.ts +16 -0
  189. package/editor/process/GridDisplayProcess.d.ts.map +1 -0
  190. package/editor/process/GridDisplayProcess.js +53 -0
  191. package/editor/process/ObstacleGridDisplayProcess.d.ts +12 -12
  192. package/editor/process/ObstacleGridDisplayProcess.d.ts.map +1 -1
  193. package/editor/process/ObstacleGridDisplayProcess.js +7 -9
  194. package/editor/process/ParticleEmitterRebuildProcess.d.ts +66 -0
  195. package/editor/process/ParticleEmitterRebuildProcess.d.ts.map +1 -0
  196. package/editor/process/ParticleEmitterRebuildProcess.js +217 -0
  197. package/editor/process/ProcessEngine.d.ts +29 -29
  198. package/editor/process/ProcessEngine.d.ts.map +1 -1
  199. package/editor/process/ProcessEngine.js +11 -4
  200. package/editor/process/SymbolicDisplayProcess.d.ts +18 -18
  201. package/editor/process/SymbolicDisplayProcess.js +2 -2
  202. package/editor/process/symbolic/ComponentSymbolicDisplay.d.ts +10 -10
  203. package/editor/process/symbolic/SymbolicDisplayInternalAPI.d.ts +67 -64
  204. package/editor/process/symbolic/SymbolicDisplayInternalAPI.d.ts.map +1 -1
  205. package/editor/process/symbolic/SymbolicDisplayInternalAPI.js +42 -45
  206. package/editor/process/symbolic/buildThreeJSHelperEntity.d.ts +8 -8
  207. package/editor/process/symbolic/make3DSymbolicDisplay.d.ts +9 -9
  208. package/editor/process/symbolic/makeCameraSymbolicDisplay.d.ts +6 -6
  209. package/editor/process/symbolic/makeGridPositionSymbolDisplay.d.ts +5 -5
  210. package/editor/process/symbolic/makeHelperBoxGeometry.d.ts +2 -2
  211. package/editor/process/symbolic/makeHelperSphereGeometry.d.ts +8 -8
  212. package/editor/process/symbolic/makeLightSymbolicDisplay.d.ts +6 -6
  213. package/editor/process/symbolic/makeParticleEmitterSymbolicDisplay.d.ts +5 -5
  214. package/editor/process/symbolic/makeParticleEmitterSymbolicDisplay.d.ts.map +1 -1
  215. package/editor/process/symbolic/makeParticleEmitterSymbolicDisplay.js +21 -1
  216. package/editor/process/symbolic/makePathSymbolicDisplay.d.ts +6 -6
  217. package/editor/process/symbolic/makePositionedIconDisplaySymbol.d.ts +8 -8
  218. package/editor/process/symbolic/makePositionedIconDisplaySymbol.d.ts.map +1 -1
  219. package/editor/process/symbolic/makePositionedIconDisplaySymbol.js +3 -0
  220. package/editor/process/symbolic/makeSocketsSymbolicDisplay.d.ts +6 -6
  221. package/editor/process/symbolic/makeSolidArrowGeometry.d.ts +25 -25
  222. package/editor/process/symbolic/makeSoundEmitterSymbolicDisplay.d.ts +5 -5
  223. package/editor/process/symbolic/synchronizeTransform.d.ts +11 -11
  224. package/editor/prototypeEditorShell.js +102 -0
  225. package/editor/style/controls.scss +168 -0
  226. package/editor/style/editor.scss +313 -0
  227. package/editor/style/inspector.scss +223 -0
  228. package/editor/templates/entity_templates.d.ts +19 -0
  229. package/editor/templates/entity_templates.d.ts.map +1 -0
  230. package/editor/templates/entity_templates.js +167 -0
  231. package/editor/tools/GridPaintTool.d.ts +17 -16
  232. package/editor/tools/GridPaintTool.d.ts.map +1 -1
  233. package/editor/tools/GridPaintTool.js +13 -14
  234. package/editor/tools/SelectionTool.d.ts +25 -26
  235. package/editor/tools/SelectionTool.d.ts.map +1 -1
  236. package/editor/tools/SelectionTool.js +46 -13
  237. package/editor/tools/TransformToolV2.d.ts +76 -0
  238. package/editor/tools/TransformToolV2.d.ts.map +1 -0
  239. package/editor/tools/TransformToolV2.js +396 -0
  240. package/editor/tools/engine/Tool.d.ts +87 -63
  241. package/editor/tools/engine/Tool.d.ts.map +1 -1
  242. package/editor/tools/engine/Tool.js +72 -28
  243. package/editor/tools/engine/ToolEngine.d.ts +21 -22
  244. package/editor/tools/engine/ToolEngine.d.ts.map +1 -1
  245. package/editor/tools/engine/ToolEngine.js +52 -54
  246. package/editor/tools/engine/ToolState.d.ts +6 -6
  247. package/editor/tools/paint/TerrainHeightPaintTool.d.ts +17 -17
  248. package/editor/tools/paint/TerrainHeightPaintTool.d.ts.map +1 -1
  249. package/editor/tools/paint/TerrainHeightPaintTool.js +12 -7
  250. package/editor/tools/paint/TerrainPaintTool.d.ts +81 -30
  251. package/editor/tools/paint/TerrainPaintTool.d.ts.map +1 -1
  252. package/editor/tools/paint/TerrainPaintTool.js +58 -21
  253. package/editor/tools/paint/TerrainTexturePaintTool.d.ts +18 -17
  254. package/editor/tools/paint/TerrainTexturePaintTool.d.ts.map +1 -1
  255. package/editor/tools/paint/TerrainTexturePaintTool.js +75 -12
  256. package/editor/tools/v2/BlenderCameraOrientationGizmo.js +24 -26
  257. package/editor/tools/v2/GizmoNode.d.ts.map +1 -1
  258. package/editor/tools/v2/GizmoNode.js +14 -13
  259. package/editor/tools/v2/TransformControls.d.ts.map +1 -1
  260. package/editor/tools/v2/TransformControls.js +18 -45
  261. package/editor/tools/v2/TransformControlsGizmo.d.ts.map +1 -1
  262. package/editor/tools/v2/TransformControlsGizmo.js +3 -6
  263. package/editor/tools/v2/TransformControlsPlane.d.ts.map +1 -1
  264. package/editor/tools/v2/TransformControlsPlane.js +3 -5
  265. package/editor/tools/v2/TransformMode.d.ts +6 -6
  266. package/editor/view/ecs/ComponentControlView.d.ts +23 -23
  267. package/editor/view/ecs/ComponentControlView.d.ts.map +1 -1
  268. package/editor/view/ecs/ComponentControlView.js +4 -4
  269. package/editor/view/ecs/EntityEditor.d.ts +24 -29
  270. package/editor/view/ecs/EntityEditor.d.ts.map +1 -1
  271. package/editor/view/ecs/EntityEditor.js +34 -10
  272. package/editor/view/ecs/EntityList.js +2 -3
  273. package/editor/view/ecs/HierarchicalEntityListView.d.ts +24 -7
  274. package/editor/view/ecs/HierarchicalEntityListView.d.ts.map +1 -1
  275. package/editor/view/ecs/HierarchicalEntityListView.js +339 -191
  276. package/editor/view/ecs/components/DatGuiController.js +2 -216
  277. package/editor/view/ecs/components/color/GaugeView.d.ts +12 -12
  278. package/editor/view/ecs/components/common/AutoCanvasView.d.ts +29 -19
  279. package/editor/view/ecs/components/common/AutoCanvasView.d.ts.map +1 -1
  280. package/editor/view/ecs/components/common/LineView.d.ts +11 -11
  281. package/editor/view/ecs/components/common/LineView.d.ts.map +1 -1
  282. package/editor/view/ecs/components/common/LineView.js +2 -1
  283. package/editor/view/ecs/components/common/NumberController.d.ts +17 -17
  284. package/editor/view/ecs/components/common/TextController.d.ts +21 -0
  285. package/editor/view/ecs/components/common/TextController.d.ts.map +1 -0
  286. package/editor/view/library/MeshLibraryView.d.ts +17 -0
  287. package/editor/view/library/MeshLibraryView.d.ts.map +1 -0
  288. package/editor/view/library/MeshLibraryView.js +35 -20
  289. package/editor/view/node-graph/NodeGraphCamera.d.ts +48 -0
  290. package/editor/view/node-graph/NodeGraphCamera.d.ts.map +1 -0
  291. package/editor/view/node-graph/NodeGraphCamera.js +88 -90
  292. package/editor/view/node-graph/NodeGraphEditorView.d.ts +25 -2
  293. package/editor/view/node-graph/NodeGraphEditorView.d.ts.map +1 -1
  294. package/editor/view/node-graph/NodeGraphSelection.d.ts +56 -0
  295. package/editor/view/node-graph/NodeGraphSelection.d.ts.map +1 -0
  296. package/editor/view/node-graph/NodeGraphView.d.ts +35 -18
  297. package/editor/view/node-graph/NodeGraphView.d.ts.map +1 -1
  298. package/editor/view/node-graph/NodeGraphView.js +666 -669
  299. package/editor/view/node-graph/NodeView.d.ts +3 -2
  300. package/editor/view/node-graph/NodeView.d.ts.map +1 -1
  301. package/editor/view/node-graph/PortView.d.ts +2 -2
  302. package/editor/view/node-graph/PortView.d.ts.map +1 -1
  303. package/editor/view/node-graph/PortView.js +2 -2
  304. package/editor/view/node-graph/actions/ConnectionCreateAction.d.ts +34 -0
  305. package/editor/view/node-graph/actions/ConnectionCreateAction.d.ts.map +1 -0
  306. package/editor/view/node-graph/actions/ConnectionCreateAction.js +53 -53
  307. package/editor/view/node-graph/actions/ConnectionDeleteAction.d.ts +26 -0
  308. package/editor/view/node-graph/actions/ConnectionDeleteAction.d.ts.map +1 -0
  309. package/editor/view/node-graph/actions/NodeDeleteAction.d.ts +47 -0
  310. package/editor/view/node-graph/actions/NodeDeleteAction.d.ts.map +1 -0
  311. package/editor/view/node-graph/actions/NodeDeleteAction.js +88 -88
  312. package/editor/view/node-graph/actions/NodeParameterSetAction.d.ts +34 -0
  313. package/editor/view/node-graph/actions/NodeParameterSetAction.d.ts.map +1 -0
  314. package/editor/view/node-graph/actions/NodeParameterSetAction.js +52 -52
  315. package/editor/view/node-graph/actions/NodesMoveAction.d.ts +38 -0
  316. package/editor/view/node-graph/actions/NodesMoveAction.d.ts.map +1 -0
  317. package/editor/view/node-graph/actions/SelectionSetAction.d.ts +38 -0
  318. package/editor/view/node-graph/actions/SelectionSetAction.d.ts.map +1 -0
  319. package/editor/view/node-graph/actions/SelectionSetAction.js +60 -60
  320. package/editor/view/node-graph/connection_wire_geometry.d.ts +46 -0
  321. package/editor/view/node-graph/connection_wire_geometry.d.ts.map +1 -0
  322. package/editor/view/particles/ColorLutGradientView.d.ts +19 -0
  323. package/editor/view/particles/ColorLutGradientView.d.ts.map +1 -0
  324. package/editor/view/particles/ColorLutGradientView.js +291 -0
  325. package/editor/view/particles/ScalarLutCurveView.d.ts +19 -0
  326. package/editor/view/particles/ScalarLutCurveView.d.ts.map +1 -0
  327. package/editor/view/particles/ScalarLutCurveView.js +279 -0
  328. package/editor/view/prepareMeshLibrary.d.ts +7 -0
  329. package/editor/view/prepareMeshLibrary.d.ts.map +1 -0
  330. package/editor/view/prepareMeshLibrary.js +34 -14
  331. package/editor/view/shell/EditorShellView.d.ts +18 -0
  332. package/editor/view/shell/EditorShellView.d.ts.map +1 -0
  333. package/editor/view/shell/EditorShellView.js +442 -0
  334. package/editor/view/shell/SceneTreePanelView.d.ts +17 -0
  335. package/editor/view/shell/SceneTreePanelView.d.ts.map +1 -0
  336. package/editor/view/shell/SceneTreePanelView.js +119 -0
  337. package/editor/view/shell/ViewportPanelView.d.ts +18 -0
  338. package/editor/view/shell/ViewportPanelView.d.ts.map +1 -0
  339. package/editor/view/shell/ViewportPanelView.js +64 -0
  340. package/editor/view/tools/ToolSettingsView.d.ts +21 -10
  341. package/editor/view/tools/ToolSettingsView.d.ts.map +1 -1
  342. package/editor/view/tools/ToolSettingsView.js +114 -100
  343. package/editor/view/tools/ToolView.d.ts +12 -12
  344. package/editor/view/v2/SplitView.js +2 -104
  345. package/package.json +7 -1
  346. package/samples/terrain/editor.js +9 -13
  347. package/src/core/binary/BitSet.d.ts.map +1 -1
  348. package/src/core/binary/BitSet.js +7 -7
  349. package/src/core/binary/type/Field.d.ts +2 -2
  350. package/src/core/binary/type/Field.d.ts.map +1 -1
  351. package/src/core/binary/type/Field.js +3 -2
  352. package/src/core/binary/type/Type.d.ts +5 -5
  353. package/src/core/binary/type/Type.d.ts.map +1 -1
  354. package/src/core/binary/type/Type.js +18 -15
  355. package/src/core/binary/type/TypeBuilder.d.ts +1 -1
  356. package/src/core/binary/type/TypeBuilder.d.ts.map +1 -1
  357. package/src/core/binary/type/TypeBuilder.js +2 -1
  358. package/src/core/binary/type/TypeRegistry.d.ts.map +1 -1
  359. package/src/core/binary/type/TypeRegistry.js +1 -3
  360. package/src/core/cache/Cache.d.ts +8 -8
  361. package/src/core/cache/Cache.d.ts.map +1 -1
  362. package/src/core/cache/Cache.js +14 -15
  363. package/src/core/cache/CacheV2.d.ts.map +1 -1
  364. package/src/core/cache/CacheV2.js +15 -20
  365. package/src/core/cache/LoadingCache.d.ts.map +1 -1
  366. package/src/core/cache/LoadingCache.js +3 -2
  367. package/src/core/collection/CuckooFilter.d.ts +23 -23
  368. package/src/core/collection/CuckooFilter.d.ts.map +1 -1
  369. package/src/core/collection/CuckooFilter.js +27 -26
  370. package/src/core/collection/PairUint32Map.d.ts +1 -1
  371. package/src/core/collection/PairUint32Map.d.ts.map +1 -1
  372. package/src/core/collection/PairUint32Map.js +322 -321
  373. package/src/core/collection/RingBuffer.d.ts +5 -5
  374. package/src/core/collection/RingBuffer.d.ts.map +1 -1
  375. package/src/core/collection/RingBuffer.js +18 -18
  376. package/src/core/collection/Uint32Map.d.ts +5 -5
  377. package/src/core/collection/Uint32Map.d.ts.map +1 -1
  378. package/src/core/collection/Uint32Map.js +346 -345
  379. package/src/core/collection/Uint32MinHeap.d.ts +4 -4
  380. package/src/core/collection/Uint32MinHeap.d.ts.map +1 -1
  381. package/src/core/collection/Uint32MinHeap.js +109 -109
  382. package/src/core/collection/array/iterator/ArrayIteratorRandom.d.ts.map +1 -1
  383. package/src/core/collection/array/iterator/ArrayIteratorRandom.js +3 -8
  384. package/src/core/collection/heap/FibonacciHeap.d.ts +6 -6
  385. package/src/core/collection/heap/FibonacciHeap.d.ts.map +1 -1
  386. package/src/core/collection/heap/FibonacciHeap.js +586 -586
  387. package/src/core/collection/heap/IndexedFloatMaxHeap.d.ts +1 -1
  388. package/src/core/collection/heap/IndexedFloatMaxHeap.d.ts.map +1 -1
  389. package/src/core/collection/heap/IndexedFloatMaxHeap.js +261 -261
  390. package/src/core/collection/heap/IndexedFloatMinHeap.d.ts +1 -1
  391. package/src/core/collection/heap/IndexedFloatMinHeap.d.ts.map +1 -1
  392. package/src/core/collection/heap/IndexedFloatMinHeap.js +268 -268
  393. package/src/core/collection/heap/Uint32Heap.d.ts +6 -6
  394. package/src/core/collection/heap/Uint32Heap.d.ts.map +1 -1
  395. package/src/core/collection/heap/Uint32Heap.js +7 -7
  396. package/src/core/collection/heap/Uint32Heap4.d.ts +6 -6
  397. package/src/core/collection/heap/Uint32Heap4.d.ts.map +1 -1
  398. package/src/core/collection/heap/Uint32Heap4.js +490 -490
  399. package/src/core/collection/list/FilteredListProjection.d.ts +7 -7
  400. package/src/core/collection/list/FilteredListProjection.d.ts.map +1 -1
  401. package/src/core/collection/list/FilteredListProjection.js +12 -12
  402. package/src/core/collection/list/SortedListProjection.d.ts +2 -2
  403. package/src/core/collection/list/SortedListProjection.d.ts.map +1 -1
  404. package/src/core/collection/list/SortedListProjection.js +6 -6
  405. package/src/core/collection/table/RowFirstTable.d.ts +5 -5
  406. package/src/core/collection/table/RowFirstTable.d.ts.map +1 -1
  407. package/src/core/collection/table/RowFirstTable.js +6 -7
  408. package/src/core/collection/table/RowFirstTableSpec.d.ts +10 -2
  409. package/src/core/collection/table/RowFirstTableSpec.d.ts.map +1 -1
  410. package/src/core/collection/table/RowFirstTableSpec.js +10 -1
  411. package/src/core/function/FunctionCompiler.d.ts.map +1 -1
  412. package/src/core/function/FunctionCompiler.js +13 -15
  413. package/src/core/geom/3d/shape/BoxShape3D.d.ts.map +1 -1
  414. package/src/core/geom/3d/shape/BoxShape3D.js +171 -175
  415. package/src/core/geom/3d/shape/CapsuleShape3D.d.ts.map +1 -1
  416. package/src/core/geom/3d/shape/CapsuleShape3D.js +238 -242
  417. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  418. package/src/core/geom/3d/shape/ConvexHullShape3D.js +43 -44
  419. package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
  420. package/src/core/geom/3d/shape/CylinderShape3D.js +220 -224
  421. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
  422. package/src/core/geom/3d/shape/HeightMapShape3D.js +58 -61
  423. package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -1
  424. package/src/core/geom/3d/shape/MeshShape3D.js +76 -80
  425. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  426. package/src/core/geom/3d/shape/PosedShape3D.js +160 -159
  427. package/src/core/geom/3d/shape/SphereShape3D.d.ts.map +1 -1
  428. package/src/core/geom/3d/shape/SphereShape3D.js +143 -146
  429. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  430. package/src/core/geom/3d/shape/TransformedShape3D.js +20 -24
  431. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -1
  432. package/src/core/geom/3d/shape/Triangle3D.js +293 -297
  433. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  434. package/src/core/geom/3d/shape/UnionShape3D.js +15 -19
  435. package/src/core/geom/3d/tetrahedra/TetrahedralMesh.d.ts +15 -15
  436. package/src/core/geom/3d/tetrahedra/TetrahedralMesh.d.ts.map +1 -1
  437. package/src/core/geom/3d/tetrahedra/TetrahedralMesh.js +21 -21
  438. package/src/core/geom/3d/topology/simplify/EdgeCollapseCandidate.d.ts.map +1 -1
  439. package/src/core/geom/3d/topology/simplify/EdgeCollapseCandidate.js +36 -36
  440. package/src/core/geom/packing/max-rect/MaxRectanglesPacker.d.ts +6 -6
  441. package/src/core/geom/packing/max-rect/MaxRectanglesPacker.d.ts.map +1 -1
  442. package/src/core/geom/packing/max-rect/MaxRectanglesPacker.js +8 -8
  443. package/src/core/graph/layout/Connection.d.ts +2 -2
  444. package/src/core/graph/layout/Connection.d.ts.map +1 -1
  445. package/src/core/graph/layout/Connection.js +6 -6
  446. package/src/core/graph/metis/native/MetisGraph.d.ts.map +1 -1
  447. package/src/core/graph/metis/native/MetisGraph.js +232 -231
  448. package/src/core/graph/metis/native/bisection/BisectionScratch.d.ts +3 -3
  449. package/src/core/graph/metis/native/bisection/BisectionScratch.d.ts.map +1 -1
  450. package/src/core/graph/metis/native/bisection/BisectionScratch.js +104 -103
  451. package/src/core/localization/Localization.d.ts.map +1 -1
  452. package/src/core/localization/Localization.js +35 -37
  453. package/src/core/model/BooleanVector3.d.ts +1 -1
  454. package/src/core/model/BooleanVector3.d.ts.map +1 -1
  455. package/src/core/model/BooleanVector3.js +2 -2
  456. package/src/core/model/ObservedValue.d.ts +2 -2
  457. package/src/core/model/ObservedValue.d.ts.map +1 -1
  458. package/src/core/model/ObservedValue.js +8 -6
  459. package/src/core/model/object/ImmutableObjectPool.d.ts +5 -5
  460. package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
  461. package/src/core/model/object/ImmutableObjectPool.js +6 -7
  462. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.d.ts +5 -5
  463. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.d.ts.map +1 -1
  464. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.js +93 -96
  465. package/src/core/model/reactive/model/terminal/ReactiveReference.d.ts +4 -4
  466. package/src/core/model/reactive/model/terminal/ReactiveReference.d.ts.map +1 -1
  467. package/src/core/model/reactive/model/terminal/ReactiveReference.js +6 -6
  468. package/src/core/model/stat/LinearModifier.d.ts +10 -10
  469. package/src/core/model/stat/LinearModifier.d.ts.map +1 -1
  470. package/src/core/model/stat/LinearModifier.js +12 -12
  471. package/src/core/process/Future.d.ts +1 -1
  472. package/src/core/process/Future.d.ts.map +1 -1
  473. package/src/core/process/Future.js +6 -7
  474. package/src/core/process/action/AsynchronousActionSequence.d.ts +5 -5
  475. package/src/core/process/action/AsynchronousActionSequence.d.ts.map +1 -1
  476. package/src/core/process/action/AsynchronousActionSequence.js +9 -9
  477. package/src/core/process/executor/profile/ConcurrentExecutorProfiler.d.ts.map +1 -1
  478. package/src/core/process/executor/profile/ConcurrentExecutorProfiler.js +19 -20
  479. package/src/core/process/filter/MutableFilter.d.ts.map +1 -1
  480. package/src/core/process/filter/MutableFilter.js +1 -6
  481. package/src/core/process/task/RemainingTimeEstimator.d.ts +1 -1
  482. package/src/core/process/task/RemainingTimeEstimator.d.ts.map +1 -1
  483. package/src/core/process/task/RemainingTimeEstimator.js +2 -1
  484. package/src/core/process/undo/ActionProcessor.d.ts +36 -17
  485. package/src/core/process/undo/ActionProcessor.d.ts.map +1 -1
  486. package/src/core/process/undo/ActionProcessor.js +131 -136
  487. package/src/core/science/units/UnitMatrix.d.ts.map +1 -1
  488. package/src/core/science/units/UnitMatrix.js +249 -251
  489. package/src/engine/EngineHarness.d.ts +5 -5
  490. package/src/engine/EngineHarness.d.ts.map +1 -1
  491. package/src/engine/EngineHarness.js +6 -6
  492. package/src/engine/achievements/Achievement.d.ts.map +1 -1
  493. package/src/engine/achievements/Achievement.js +35 -35
  494. package/src/engine/achievements/AchievementManager.d.ts.map +1 -1
  495. package/src/engine/achievements/AchievementManager.js +33 -35
  496. package/src/engine/achievements/gateway/StorageAchievementGateway.d.ts +1 -1
  497. package/src/engine/achievements/gateway/StorageAchievementGateway.d.ts.map +1 -1
  498. package/src/engine/achievements/gateway/StorageAchievementGateway.js +2 -2
  499. package/src/engine/animation/EntityAnimation.d.ts +5 -5
  500. package/src/engine/animation/EntityAnimation.d.ts.map +1 -1
  501. package/src/engine/animation/EntityAnimation.js +6 -6
  502. package/src/engine/animation/curve/editor/CurveEditorView.d.ts +2 -2
  503. package/src/engine/animation/curve/editor/CurveEditorView.d.ts.map +1 -1
  504. package/src/engine/animation/curve/editor/CurveEditorView.js +3 -3
  505. package/src/engine/animation/curve/editor/DragHandler.d.ts +1 -1
  506. package/src/engine/animation/curve/editor/DragHandler.d.ts.map +1 -1
  507. package/src/engine/animation/curve/editor/DragHandler.js +2 -2
  508. package/src/engine/animation/curve/editor/OperationRouter.d.ts +1 -1
  509. package/src/engine/animation/curve/editor/OperationRouter.d.ts.map +1 -1
  510. package/src/engine/animation/curve/editor/OperationRouter.js +2 -2
  511. package/src/engine/animation/keyed2/AnimationTrack.d.ts +10 -10
  512. package/src/engine/animation/keyed2/AnimationTrack.d.ts.map +1 -1
  513. package/src/engine/animation/keyed2/AnimationTrack.js +27 -27
  514. package/src/engine/animation/keyed2/AnimationTrackPlayback.d.ts +4 -4
  515. package/src/engine/animation/keyed2/AnimationTrackPlayback.d.ts.map +1 -1
  516. package/src/engine/animation/keyed2/AnimationTrackPlayback.js +6 -9
  517. package/src/engine/asset/Asset.d.ts +10 -10
  518. package/src/engine/asset/Asset.d.ts.map +1 -1
  519. package/src/engine/asset/Asset.js +13 -14
  520. package/src/engine/asset/AssetRequest.d.ts +14 -14
  521. package/src/engine/asset/AssetRequest.d.ts.map +1 -1
  522. package/src/engine/asset/AssetRequest.js +18 -18
  523. package/src/engine/asset/PendingAsset.d.ts +5 -5
  524. package/src/engine/asset/PendingAsset.d.ts.map +1 -1
  525. package/src/engine/asset/PendingAsset.js +108 -108
  526. package/src/engine/asset/loaders/GLTFAssetLoader.d.ts +2 -2
  527. package/src/engine/asset/loaders/GLTFAssetLoader.d.ts.map +1 -1
  528. package/src/engine/asset/loaders/GLTFAssetLoader.js +16 -18
  529. package/src/engine/asset/loaders/JavascriptAssetLoader.d.ts.map +1 -1
  530. package/src/engine/asset/loaders/JavascriptAssetLoader.js +30 -31
  531. package/src/engine/asset/loaders/gltf/extensions/MSFT_texture_dds.d.ts.map +1 -1
  532. package/src/engine/asset/loaders/gltf/extensions/MSFT_texture_dds.js +2 -2
  533. package/src/engine/asset/loaders/material/StaticMaterialCache.d.ts.map +1 -1
  534. package/src/engine/asset/loaders/material/StaticMaterialCache.js +9 -12
  535. package/src/engine/control/StatefulController.d.ts +4 -4
  536. package/src/engine/control/StatefulController.d.ts.map +1 -1
  537. package/src/engine/control/StatefulController.js +6 -6
  538. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  539. package/src/engine/control/first-person/FirstPersonPlayerController.js +31 -33
  540. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  541. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2052 -2028
  542. package/src/engine/control/first-person/abilities/Ability.d.ts.map +1 -1
  543. package/src/engine/control/first-person/abilities/Ability.js +117 -119
  544. package/src/engine/control/first-person/abilities/AbilitySet.d.ts.map +1 -1
  545. package/src/engine/control/first-person/abilities/AbilitySet.js +183 -185
  546. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
  547. package/src/engine/control/first-person/abilities/LedgeGrab.js +404 -405
  548. package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
  549. package/src/engine/control/first-person/abilities/Mantle.js +24 -23
  550. package/src/engine/control/first-person/abilities/ScrambleUp.d.ts.map +1 -1
  551. package/src/engine/control/first-person/abilities/ScrambleUp.js +181 -182
  552. package/src/engine/control/first-person/abilities/Slide.d.ts.map +1 -1
  553. package/src/engine/control/first-person/abilities/Slide.js +164 -166
  554. package/src/engine/control/first-person/abilities/WallJump.d.ts.map +1 -1
  555. package/src/engine/control/first-person/abilities/WallJump.js +136 -139
  556. package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -1
  557. package/src/engine/control/first-person/abilities/WallRun.js +7 -9
  558. package/src/engine/control/first-person/collision/KinematicMover.d.ts +7 -13
  559. package/src/engine/control/first-person/collision/KinematicMover.d.ts.map +1 -1
  560. package/src/engine/control/first-person/collision/KinematicMover.js +11 -10
  561. package/src/engine/control/first-person/composer/EyeOffsetStack.d.ts.map +1 -1
  562. package/src/engine/control/first-person/composer/EyeOffsetStack.js +58 -60
  563. package/src/engine/control/first-person/mastery/BreathRhythmEvaluator.d.ts +2 -2
  564. package/src/engine/control/first-person/mastery/BreathRhythmEvaluator.d.ts.map +1 -1
  565. package/src/engine/control/first-person/mastery/BreathRhythmEvaluator.js +133 -133
  566. package/src/engine/control/first-person/mastery/FootAsymmetryTurnEvaluator.d.ts.map +1 -1
  567. package/src/engine/control/first-person/mastery/FootAsymmetryTurnEvaluator.js +106 -109
  568. package/src/engine/control/first-person/mastery/MasteryEvaluator.d.ts.map +1 -1
  569. package/src/engine/control/first-person/mastery/MasteryEvaluator.js +43 -45
  570. package/src/engine/control/first-person/mastery/MasteryScore.d.ts +6 -6
  571. package/src/engine/control/first-person/mastery/MasteryScore.d.ts.map +1 -1
  572. package/src/engine/control/first-person/mastery/MasteryScore.js +100 -100
  573. package/src/engine/control/first-person/mastery/MasterySet.d.ts.map +1 -1
  574. package/src/engine/control/first-person/mastery/MasterySet.js +84 -86
  575. package/src/engine/control/first-person/mastery/SlideInitiationTimingEvaluator.d.ts.map +1 -1
  576. package/src/engine/control/first-person/mastery/SlideInitiationTimingEvaluator.js +80 -83
  577. package/src/engine/control/first-person/mastery/StrideTimingJumpEvaluator.d.ts.map +1 -1
  578. package/src/engine/control/first-person/mastery/StrideTimingJumpEvaluator.js +106 -109
  579. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts.map +1 -1
  580. package/src/engine/control/first-person/sensors/FirstPersonSensors.js +45 -40
  581. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts.map +1 -1
  582. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.js +35 -39
  583. package/src/engine/development/performance/monitor/PeriodicConsolePrinter.d.ts +1 -1
  584. package/src/engine/development/performance/monitor/PeriodicConsolePrinter.d.ts.map +1 -1
  585. package/src/engine/development/performance/monitor/PeriodicConsolePrinter.js +2 -2
  586. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +531 -0
  587. package/src/engine/ecs/EntityManager.d.ts +16 -0
  588. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  589. package/src/engine/ecs/EntityManager.js +81 -0
  590. package/src/engine/ecs/animation/Animation.d.ts.map +1 -1
  591. package/src/engine/ecs/animation/Animation.js +8 -8
  592. package/src/engine/ecs/async/SystemWorkerHost.d.ts +98 -0
  593. package/src/engine/ecs/async/SystemWorkerHost.d.ts.map +1 -0
  594. package/src/engine/ecs/async/SystemWorkerHost.js +329 -0
  595. package/src/engine/ecs/async/SystemWorkerLoopback.d.ts +80 -0
  596. package/src/engine/ecs/async/SystemWorkerLoopback.d.ts.map +1 -0
  597. package/src/engine/ecs/async/SystemWorkerLoopback.js +143 -0
  598. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +246 -0
  599. package/src/engine/ecs/async/WorkerSystem.d.ts +173 -0
  600. package/src/engine/ecs/async/WorkerSystem.d.ts.map +1 -0
  601. package/src/engine/ecs/async/WorkerSystem.js +702 -0
  602. package/src/engine/ecs/async/WorkerSystemProtocol.d.ts +62 -0
  603. package/src/engine/ecs/async/WorkerSystemProtocol.d.ts.map +1 -0
  604. package/src/engine/ecs/async/WorkerSystemProtocol.js +150 -0
  605. package/src/engine/ecs/binding/EntityProxyScope.d.ts.map +1 -1
  606. package/src/engine/ecs/binding/EntityProxyScope.js +11 -12
  607. package/src/engine/ecs/components/PropertySet.d.ts.map +1 -1
  608. package/src/engine/ecs/components/PropertySet.js +2 -2
  609. package/src/engine/ecs/components/TagSerializationUpgrader_0_1.d.ts.map +1 -1
  610. package/src/engine/ecs/components/TagSerializationUpgrader_0_1.js +2 -6
  611. package/src/engine/ecs/components/Timer.d.ts +7 -7
  612. package/src/engine/ecs/components/Timer.d.ts.map +1 -1
  613. package/src/engine/ecs/components/Timer.js +18 -18
  614. package/src/engine/ecs/dynamic_actions/DataScope.d.ts.map +1 -1
  615. package/src/engine/ecs/dynamic_actions/DataScope.js +6 -8
  616. package/src/engine/ecs/evaluation/PointFitnessBinary.d.ts.map +1 -1
  617. package/src/engine/ecs/evaluation/PointFitnessBinary.js +10 -13
  618. package/src/engine/ecs/evaluation/PointFitnessConstant.d.ts.map +1 -1
  619. package/src/engine/ecs/evaluation/PointFitnessConstant.js +5 -9
  620. package/src/engine/ecs/evaluation/world/ReadTerrainLayerWeightPointFitness.d.ts.map +1 -1
  621. package/src/engine/ecs/evaluation/world/ReadTerrainLayerWeightPointFitness.js +5 -9
  622. package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts.map +1 -1
  623. package/src/engine/ecs/fow/FogOfWarRevealerSystem.js +6 -8
  624. package/src/engine/ecs/fow/FogOfWarSystem.d.ts +2 -2
  625. package/src/engine/ecs/fow/FogOfWarSystem.d.ts.map +1 -1
  626. package/src/engine/ecs/fow/FogOfWarSystem.js +5 -6
  627. package/src/engine/ecs/fow/FogOfWarVisibilityPredicate.d.ts.map +1 -1
  628. package/src/engine/ecs/fow/FogOfWarVisibilityPredicate.js +11 -14
  629. package/src/engine/ecs/fow/serialization/FogOfWarSerializationUpdater_0_1.d.ts.map +1 -1
  630. package/src/engine/ecs/fow/serialization/FogOfWarSerializationUpdater_0_1.js +2 -6
  631. package/src/engine/ecs/gui/position/ViewportPositionSystem.d.ts.map +1 -1
  632. package/src/engine/ecs/gui/position/ViewportPositionSystem.js +5 -6
  633. package/src/engine/ecs/gui/view/LocalizedLabelViewEntity.d.ts.map +1 -1
  634. package/src/engine/ecs/gui/view/LocalizedLabelViewEntity.js +2 -2
  635. package/src/engine/ecs/parent/ChildEntities.d.ts.map +1 -1
  636. package/src/engine/ecs/parent/ChildEntities.js +6 -8
  637. package/src/engine/ecs/parent/FaceEditor.d.ts.map +1 -1
  638. package/src/engine/ecs/parent/FaceEditor.js +42 -41
  639. package/src/engine/ecs/parent/ParentEntity.d.ts.map +1 -1
  640. package/src/engine/ecs/parent/ParentEntity.js +4 -9
  641. package/src/engine/ecs/parent/ParentEntitySystem.d.ts.map +1 -1
  642. package/src/engine/ecs/parent/ParentEntitySystem.js +2 -2
  643. package/src/engine/ecs/sockets/AttachmentSocket.d.ts.map +1 -1
  644. package/src/engine/ecs/sockets/AttachmentSocket.js +12 -14
  645. package/src/engine/ecs/sockets/AttachmentSockets.d.ts.map +1 -1
  646. package/src/engine/ecs/sockets/AttachmentSockets.js +5 -7
  647. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts.map +1 -1
  648. package/src/engine/ecs/sockets/BoneAttachmentSocket.js +1 -5
  649. package/src/engine/ecs/speaker/VoiceSystem.d.ts +5 -5
  650. package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
  651. package/src/engine/ecs/speaker/VoiceSystem.js +42 -45
  652. package/src/engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter.d.ts.map +1 -1
  653. package/src/engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter.js +17 -19
  654. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts +11 -0
  655. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -0
  656. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +260 -0
  657. package/src/engine/ecs/system/AbstractContextSystem.d.ts +8 -8
  658. package/src/engine/ecs/system/AbstractContextSystem.d.ts.map +1 -1
  659. package/src/engine/ecs/system/AbstractContextSystem.js +14 -14
  660. package/src/engine/ecs/systems/AnimationSystem.d.ts.map +1 -1
  661. package/src/engine/ecs/systems/AnimationSystem.js +2 -2
  662. package/src/engine/ecs/systems/ScriptSystem.d.ts.map +1 -1
  663. package/src/engine/ecs/systems/ScriptSystem.js +3 -6
  664. package/src/engine/ecs/terrain/ecs/Terrain.d.ts +27 -27
  665. package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
  666. package/src/engine/ecs/terrain/ecs/Terrain.js +35 -38
  667. package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.d.ts.map +1 -1
  668. package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +68 -31
  669. package/src/engine/ecs/terrain/ecs/makeTerrainWorkerProxy.d.ts.map +1 -1
  670. package/src/engine/ecs/terrain/ecs/makeTerrainWorkerProxy.js +4 -2
  671. package/src/engine/ecs/terrain/ecs/splat/SplatMapping.d.ts +5 -5
  672. package/src/engine/ecs/terrain/ecs/splat/SplatMapping.d.ts.map +1 -1
  673. package/src/engine/ecs/terrain/ecs/splat/SplatMapping.js +6 -7
  674. package/src/engine/ecs/terrain/overlay/TerrainOverlay.d.ts +9 -9
  675. package/src/engine/ecs/terrain/overlay/TerrainOverlay.d.ts.map +1 -1
  676. package/src/engine/ecs/terrain/overlay/TerrainOverlay.js +13 -12
  677. package/src/engine/ecs/terrain/serialization/TerrainSerializationUpgrader_0_1.d.ts.map +1 -1
  678. package/src/engine/ecs/terrain/serialization/TerrainSerializationUpgrader_0_1.js +2 -6
  679. package/src/engine/ecs/terrain/serialization/TerrainSerializationUpgrader_1_2.d.ts.map +1 -1
  680. package/src/engine/ecs/terrain/serialization/TerrainSerializationUpgrader_1_2.js +2 -6
  681. package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts +44 -3
  682. package/src/engine/ecs/terrain/tiles/TerrainTileManager.d.ts.map +1 -1
  683. package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +81 -17
  684. package/src/engine/ecs/terrain/util/obtainTerrain.d.ts.map +1 -1
  685. package/src/engine/ecs/terrain/util/obtainTerrain.js +2 -0
  686. package/src/engine/ecs/tooltip/TooltipComponentSystem.d.ts +20 -20
  687. package/src/engine/ecs/tooltip/TooltipComponentSystem.d.ts.map +1 -1
  688. package/src/engine/ecs/tooltip/TooltipComponentSystem.js +36 -38
  689. package/src/engine/ecs/transform/TransformSerializationUpgrader_0_1.d.ts.map +1 -1
  690. package/src/engine/ecs/transform/TransformSerializationUpgrader_0_1.js +2 -6
  691. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
  692. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +34 -41
  693. package/src/engine/graphics/camera/CameraShake.d.ts.map +1 -1
  694. package/src/engine/graphics/camera/CameraShake.js +12 -16
  695. package/src/engine/graphics/camera/CameraShakeBehavior.d.ts +2 -2
  696. package/src/engine/graphics/camera/CameraShakeBehavior.d.ts.map +1 -1
  697. package/src/engine/graphics/camera/CameraShakeBehavior.js +3 -4
  698. package/src/engine/graphics/camera/CameraShakeTraumaBehavior.d.ts +2 -2
  699. package/src/engine/graphics/camera/CameraShakeTraumaBehavior.d.ts.map +1 -1
  700. package/src/engine/graphics/camera/CameraShakeTraumaBehavior.js +3 -5
  701. package/src/engine/graphics/composit/CompositLayer.d.ts.map +1 -1
  702. package/src/engine/graphics/composit/CompositLayer.js +6 -6
  703. package/src/engine/graphics/composit/LayerCompositer.d.ts.map +1 -1
  704. package/src/engine/graphics/composit/LayerCompositer.js +17 -19
  705. package/src/engine/graphics/debug/VisualSymbolLine.d.ts.map +1 -1
  706. package/src/engine/graphics/debug/VisualSymbolLine.js +11 -13
  707. package/src/engine/graphics/ecs/animation/AnimationControllerSystem.d.ts.map +1 -1
  708. package/src/engine/graphics/ecs/animation/AnimationControllerSystem.js +2 -5
  709. package/src/engine/graphics/ecs/animation/AnimationRule.d.ts +2 -2
  710. package/src/engine/graphics/ecs/animation/AnimationRule.d.ts.map +1 -1
  711. package/src/engine/graphics/ecs/animation/AnimationRule.js +3 -2
  712. package/src/engine/graphics/ecs/animation/animator/AnimationClip.d.ts.map +1 -1
  713. package/src/engine/graphics/ecs/animation/animator/AnimationClip.js +20 -22
  714. package/src/engine/graphics/ecs/animation/animator/AnimationClipDefinition.d.ts.map +1 -1
  715. package/src/engine/graphics/ecs/animation/animator/AnimationClipDefinition.js +23 -25
  716. package/src/engine/graphics/ecs/animation/animator/AnimationGraphSystem.d.ts +4 -4
  717. package/src/engine/graphics/ecs/animation/animator/AnimationGraphSystem.d.ts.map +1 -1
  718. package/src/engine/graphics/ecs/animation/animator/AnimationGraphSystem.js +32 -33
  719. package/src/engine/graphics/ecs/animation/animator/AnimationNotification.d.ts.map +1 -1
  720. package/src/engine/graphics/ecs/animation/animator/AnimationNotification.js +10 -12
  721. package/src/engine/graphics/ecs/animation/animator/AnimationNotificationDefinition.d.ts.map +1 -1
  722. package/src/engine/graphics/ecs/animation/animator/AnimationNotificationDefinition.js +10 -12
  723. package/src/engine/graphics/ecs/animation/animator/blending/BlendSpace.d.ts +1 -1
  724. package/src/engine/graphics/ecs/animation/animator/blending/BlendSpace.d.ts.map +1 -1
  725. package/src/engine/graphics/ecs/animation/animator/blending/BlendSpace.js +10 -12
  726. package/src/engine/graphics/ecs/animation/animator/blending/BlendSpaceDefinition.d.ts.map +1 -1
  727. package/src/engine/graphics/ecs/animation/animator/blending/BlendSpaceDefinition.js +5 -7
  728. package/src/engine/graphics/ecs/animation/animator/blending/BlendSpacePoint.d.ts.map +1 -1
  729. package/src/engine/graphics/ecs/animation/animator/blending/BlendSpacePoint.js +10 -12
  730. package/src/engine/graphics/ecs/animation/animator/blending/BlendState.d.ts.map +1 -1
  731. package/src/engine/graphics/ecs/animation/animator/blending/BlendState.js +5 -7
  732. package/src/engine/graphics/ecs/animation/animator/blending/BlendStateDatabase.d.ts.map +1 -1
  733. package/src/engine/graphics/ecs/animation/animator/blending/BlendStateDatabase.js +18 -21
  734. package/src/engine/graphics/ecs/animation/animator/graph/AnimationGraph.d.ts.map +1 -1
  735. package/src/engine/graphics/ecs/animation/animator/graph/AnimationGraph.js +75 -77
  736. package/src/engine/graphics/ecs/animation/animator/graph/AnimationState.d.ts.map +1 -1
  737. package/src/engine/graphics/ecs/animation/animator/graph/AnimationState.js +35 -38
  738. package/src/engine/graphics/ecs/animation/animator/graph/AnimationTransition.d.ts.map +1 -1
  739. package/src/engine/graphics/ecs/animation/animator/graph/AnimationTransition.js +30 -32
  740. package/src/engine/graphics/ecs/animation/animator/graph/definition/AnimationStateDefinition.d.ts.map +1 -1
  741. package/src/engine/graphics/ecs/animation/animator/graph/definition/AnimationStateDefinition.js +31 -33
  742. package/src/engine/graphics/ecs/animation/animator/graph/definition/AnimationTransitionDefinition.d.ts.map +1 -1
  743. package/src/engine/graphics/ecs/animation/animator/graph/definition/AnimationTransitionDefinition.js +20 -23
  744. package/src/engine/graphics/ecs/camera/Camera.d.ts.map +1 -1
  745. package/src/engine/graphics/ecs/camera/Camera.js +45 -47
  746. package/src/engine/graphics/ecs/camera/CameraSystem.d.ts +1 -1
  747. package/src/engine/graphics/ecs/camera/CameraSystem.d.ts.map +1 -1
  748. package/src/engine/graphics/ecs/camera/CameraSystem.js +8 -9
  749. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.d.ts.map +1 -1
  750. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationUpgrader_0_1.js +37 -41
  751. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts.map +1 -1
  752. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +8 -10
  753. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSerializationUpgrader_0_1.d.ts.map +1 -1
  754. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSerializationUpgrader_0_1.js +2 -6
  755. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
  756. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +5 -10
  757. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraLanderSystem.d.ts.map +1 -1
  758. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraLanderSystem.js +20 -25
  759. package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.d.ts.map +1 -1
  760. package/src/engine/graphics/ecs/decal/v2/FPDecalSystem.js +2 -2
  761. package/src/engine/graphics/ecs/highlight/HighlightSerializationUpgrader_0_1.d.ts.map +1 -1
  762. package/src/engine/graphics/ecs/highlight/HighlightSerializationUpgrader_0_1.js +2 -6
  763. package/src/engine/graphics/ecs/highlight/plugin/OutlineRenderPlugin.d.ts.map +1 -1
  764. package/src/engine/graphics/ecs/highlight/plugin/OutlineRenderPlugin.js +23 -25
  765. package/src/engine/graphics/ecs/highlight/renderer/HighlightRenderElement.d.ts.map +1 -1
  766. package/src/engine/graphics/ecs/highlight/renderer/HighlightRenderElement.js +13 -16
  767. package/src/engine/graphics/ecs/highlight/renderer/HighlightRenderGroup.d.ts.map +1 -1
  768. package/src/engine/graphics/ecs/highlight/renderer/HighlightRenderGroup.js +5 -7
  769. package/src/engine/graphics/ecs/highlight/renderer/OutlineRenderer.d.ts +12 -12
  770. package/src/engine/graphics/ecs/highlight/renderer/OutlineRenderer.d.ts.map +1 -1
  771. package/src/engine/graphics/ecs/highlight/renderer/OutlineRenderer.js +54 -57
  772. package/src/engine/graphics/ecs/highlight/system/HighlightSystemBase.js +205 -205
  773. package/src/engine/graphics/ecs/light/Light.d.ts.map +1 -1
  774. package/src/engine/graphics/ecs/light/Light.js +59 -65
  775. package/src/engine/graphics/ecs/light/LightSystem.d.ts +16 -16
  776. package/src/engine/graphics/ecs/light/LightSystem.d.ts.map +1 -1
  777. package/src/engine/graphics/ecs/light/LightSystem.js +23 -25
  778. package/src/engine/graphics/ecs/light/binding/LightBinding.d.ts.map +1 -1
  779. package/src/engine/graphics/ecs/light/binding/LightBinding.js +13 -14
  780. package/src/engine/graphics/ecs/light/binding/fp/FPLightBinding.d.ts.map +1 -1
  781. package/src/engine/graphics/ecs/light/binding/fp/FPLightBinding.js +11 -15
  782. package/src/engine/graphics/ecs/light/shadow/ShadowMap.d.ts.map +1 -1
  783. package/src/engine/graphics/ecs/light/shadow/ShadowMap.js +5 -9
  784. package/src/engine/graphics/ecs/light/three/ThreeLightCache.d.ts.map +1 -1
  785. package/src/engine/graphics/ecs/light/three/ThreeLightCache.js +20 -22
  786. package/src/engine/graphics/ecs/mesh/Mesh.d.ts.map +1 -1
  787. package/src/engine/graphics/ecs/mesh/Mesh.js +35 -37
  788. package/src/engine/graphics/ecs/mesh/MeshSystem.d.ts +15 -15
  789. package/src/engine/graphics/ecs/mesh/MeshSystem.d.ts.map +1 -1
  790. package/src/engine/graphics/ecs/mesh/MeshSystem.js +21 -22
  791. package/src/engine/graphics/ecs/mesh/skeleton/BoneMapping.d.ts.map +1 -1
  792. package/src/engine/graphics/ecs/mesh/skeleton/BoneMapping.js +1 -3
  793. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.d.ts.map +1 -1
  794. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +51 -52
  795. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.d.ts +6 -6
  796. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.d.ts.map +1 -1
  797. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +37 -37
  798. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshAnimationControllerSystem.d.ts +1 -1
  799. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshAnimationControllerSystem.d.ts.map +1 -1
  800. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshAnimationControllerSystem.js +9 -11
  801. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshHighlightSystem.d.ts.map +1 -1
  802. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshHighlightSystem.js +1 -5
  803. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.d.ts +10 -10
  804. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.d.ts.map +1 -1
  805. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.js +22 -22
  806. package/src/engine/graphics/ecs/mesh-v2/render/SGThreeObjectCache.d.ts.map +1 -1
  807. package/src/engine/graphics/ecs/mesh-v2/render/SGThreeObjectCache.js +6 -8
  808. package/src/engine/graphics/ecs/mesh-v2/render/ShadedGeometryRendererContext.d.ts.map +1 -1
  809. package/src/engine/graphics/ecs/mesh-v2/render/ShadedGeometryRendererContext.js +8 -10
  810. package/src/engine/graphics/ecs/mesh-v2/render/adapters/AbstractRenderAdapter.d.ts.map +1 -1
  811. package/src/engine/graphics/ecs/mesh-v2/render/adapters/AbstractRenderAdapter.js +12 -15
  812. package/src/engine/graphics/ecs/mesh-v2/render/adapters/GenericRendererAdapter.d.ts.map +1 -1
  813. package/src/engine/graphics/ecs/mesh-v2/render/adapters/GenericRendererAdapter.js +6 -10
  814. package/src/engine/graphics/ecs/mesh-v2/render/optimization/RuntimeDrawMethodOptimizer.d.ts.map +1 -1
  815. package/src/engine/graphics/ecs/mesh-v2/render/optimization/RuntimeDrawMethodOptimizer.js +12 -15
  816. package/src/engine/graphics/ecs/path/PathDisplay.d.ts.map +1 -1
  817. package/src/engine/graphics/ecs/path/PathDisplay.js +5 -7
  818. package/src/engine/graphics/ecs/path/PathDisplaySpec.d.ts.map +1 -1
  819. package/src/engine/graphics/ecs/path/PathDisplaySpec.js +10 -12
  820. package/src/engine/graphics/ecs/path/PathDisplaySystem.d.ts +6 -6
  821. package/src/engine/graphics/ecs/path/PathDisplaySystem.d.ts.map +1 -1
  822. package/src/engine/graphics/ecs/path/PathDisplaySystem.js +12 -17
  823. package/src/engine/graphics/ecs/path/entity/EntityPath.d.ts.map +1 -1
  824. package/src/engine/graphics/ecs/path/entity/EntityPath.js +15 -17
  825. package/src/engine/graphics/ecs/path/entity/EntityPathMarker.d.ts.map +1 -1
  826. package/src/engine/graphics/ecs/path/entity/EntityPathMarker.js +15 -17
  827. package/src/engine/graphics/ecs/path/entity/EntityPathMarkerDefinition.d.ts.map +1 -1
  828. package/src/engine/graphics/ecs/path/entity/EntityPathMarkerDefinition.js +15 -17
  829. package/src/engine/graphics/ecs/path/entity/EntityPathStyle.d.ts.map +1 -1
  830. package/src/engine/graphics/ecs/path/entity/EntityPathStyle.js +20 -23
  831. package/src/engine/graphics/ecs/path/highlight/PathDisplayHighlightSystem.d.ts.map +1 -1
  832. package/src/engine/graphics/ecs/path/highlight/PathDisplayHighlightSystem.js +1 -5
  833. package/src/engine/graphics/ecs/path/ribbon/RibbonPathBuilder.d.ts.map +1 -1
  834. package/src/engine/graphics/ecs/path/ribbon/RibbonPathBuilder.js +15 -17
  835. package/src/engine/graphics/ecs/path/ribbon/RibbonPathStyle.d.ts.map +1 -1
  836. package/src/engine/graphics/ecs/path/ribbon/RibbonPathStyle.js +15 -17
  837. package/src/engine/graphics/ecs/path/tube/MatcapMaterialDefinition.d.ts.map +1 -1
  838. package/src/engine/graphics/ecs/path/tube/MatcapMaterialDefinition.js +5 -7
  839. package/src/engine/graphics/ecs/path/tube/StandardMaterialDefinition.d.ts.map +1 -1
  840. package/src/engine/graphics/ecs/path/tube/StandardMaterialDefinition.js +2 -4
  841. package/src/engine/graphics/ecs/path/tube/TubePathStyle.d.ts.map +1 -1
  842. package/src/engine/graphics/ecs/path/tube/TubePathStyle.js +84 -86
  843. package/src/engine/graphics/ecs/path/tube/build/StreamGeometryBuilder.d.ts.map +1 -1
  844. package/src/engine/graphics/ecs/path/tube/build/StreamGeometryBuilder.js +37 -36
  845. package/src/engine/graphics/ecs/path/tube/build/TubePathBuilder.d.ts.map +1 -1
  846. package/src/engine/graphics/ecs/path/tube/build/TubePathBuilder.js +23 -25
  847. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.d.ts.map +1 -1
  848. package/src/engine/graphics/ecs/sprite/SpriteSystemPE.js +2 -3
  849. package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts +5 -5
  850. package/src/engine/graphics/ecs/trail2d/Trail2DSystem.d.ts.map +1 -1
  851. package/src/engine/graphics/ecs/trail2d/Trail2DSystem.js +6 -9
  852. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts +4 -4
  853. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts.map +1 -1
  854. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.js +5 -5
  855. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_0_1.d.ts.map +1 -1
  856. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_0_1.js +2 -6
  857. package/src/engine/graphics/ecs/water/WaterSystem.d.ts +1 -1
  858. package/src/engine/graphics/ecs/water/WaterSystem.d.ts.map +1 -1
  859. package/src/engine/graphics/ecs/water/WaterSystem.js +13 -15
  860. package/src/engine/graphics/geometry/MikkT/MikkTSpace.d.ts.map +1 -1
  861. package/src/engine/graphics/geometry/MikkT/MikkTSpace.js +97 -95
  862. package/src/engine/graphics/geometry/MikkT/SSubGroup.d.ts.map +1 -1
  863. package/src/engine/graphics/geometry/MikkT/SSubGroup.js +10 -12
  864. package/src/engine/graphics/geometry/instancing/InstancedMeshGroup.d.ts.map +1 -1
  865. package/src/engine/graphics/geometry/instancing/InstancedMeshGroup.js +103 -110
  866. package/src/engine/graphics/geometry/optimization/geometry_optimize_uv_tension.d.ts +18 -3
  867. package/src/engine/graphics/geometry/optimization/geometry_optimize_uv_tension.d.ts.map +1 -1
  868. package/src/engine/graphics/geometry/optimization/geometry_optimize_uv_tension.js +135 -85
  869. package/src/engine/graphics/geometry/optimization/merge/merge_geometry_hierarchy.d.ts.map +1 -1
  870. package/src/engine/graphics/geometry/optimization/merge/merge_geometry_hierarchy.js +3 -5
  871. package/src/engine/graphics/geometry/ribbon/Quad.d.ts +2 -2
  872. package/src/engine/graphics/geometry/ribbon/Quad.d.ts.map +1 -1
  873. package/src/engine/graphics/geometry/ribbon/Quad.js +17 -17
  874. package/src/engine/graphics/material/manager/ManagedMaterial.d.ts +6 -6
  875. package/src/engine/graphics/material/manager/ManagedMaterial.d.ts.map +1 -1
  876. package/src/engine/graphics/material/manager/ManagedMaterial.js +20 -20
  877. package/src/engine/graphics/material/optimization/MaterialDescriptor.d.ts.map +1 -1
  878. package/src/engine/graphics/material/optimization/MaterialDescriptor.js +11 -14
  879. package/src/engine/graphics/material/optimization/MaterialOptimizationContext.d.ts.map +1 -1
  880. package/src/engine/graphics/material/optimization/MaterialOptimizationContext.js +20 -23
  881. package/src/engine/graphics/material/optimization/UvContext.d.ts.map +1 -1
  882. package/src/engine/graphics/material/optimization/UvContext.js +10 -12
  883. package/src/engine/graphics/particles/node-based/editor/ParticleSpecificationEditorView.d.ts.map +1 -1
  884. package/src/engine/graphics/particles/node-based/editor/ParticleSpecificationEditorView.js +23 -23
  885. package/src/engine/graphics/particles/node-based/rendering/AbstractParticleRenderer.d.ts.map +1 -1
  886. package/src/engine/graphics/particles/node-based/rendering/AbstractParticleRenderer.js +5 -7
  887. package/src/engine/graphics/particles/node-based/rendering/billboard/BillboardParticleRenderer.d.ts +11 -11
  888. package/src/engine/graphics/particles/node-based/rendering/billboard/BillboardParticleRenderer.d.ts.map +1 -1
  889. package/src/engine/graphics/particles/node-based/rendering/billboard/BillboardParticleRenderer.js +21 -23
  890. package/src/engine/graphics/particles/node-based/simulation/EmitterAttributeData.d.ts.map +1 -1
  891. package/src/engine/graphics/particles/node-based/simulation/EmitterAttributeData.js +17 -19
  892. package/src/engine/graphics/particles/node-based/simulation/GLDataBuffer.d.ts.map +1 -1
  893. package/src/engine/graphics/particles/node-based/simulation/GLDataBuffer.js +25 -27
  894. package/src/engine/graphics/particles/node-based/simulation/GLSLSimulationShader.d.ts.map +1 -1
  895. package/src/engine/graphics/particles/node-based/simulation/GLSLSimulationShader.js +12 -16
  896. package/src/engine/graphics/particles/node-based/simulation/TransformFeedback.d.ts.map +1 -1
  897. package/src/engine/graphics/particles/node-based/simulation/TransformFeedback.js +64 -67
  898. package/src/engine/graphics/particles/particular/engine/MovingBoundingBox.d.ts +3 -0
  899. package/src/engine/graphics/particles/particular/engine/MovingBoundingBox.d.ts.map +1 -1
  900. package/src/engine/graphics/particles/particular/engine/MovingBoundingBox.js +36 -39
  901. package/src/engine/graphics/particles/particular/engine/emitter/serde/ParticleEmitterSerializationUpgrader_0_1.d.ts.map +1 -1
  902. package/src/engine/graphics/particles/particular/engine/emitter/serde/ParticleEmitterSerializationUpgrader_0_1.js +2 -6
  903. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.d.ts.map +1 -1
  904. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.js +5 -7
  905. package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.d.ts +10 -10
  906. package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.d.ts.map +1 -1
  907. package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.js +12 -12
  908. package/src/engine/graphics/particles/particular/engine/simulator/AbstractSimulationStep.d.ts.map +1 -1
  909. package/src/engine/graphics/particles/particular/engine/simulator/AbstractSimulationStep.js +35 -37
  910. package/src/engine/graphics/particles/particular/engine/simulator/SimulationStepCurlNoiseVelocity.d.ts.map +1 -1
  911. package/src/engine/graphics/particles/particular/engine/simulator/SimulationStepCurlNoiseVelocity.js +1 -5
  912. package/src/engine/graphics/particles/particular/engine/simulator/SimulationStepDefinition.d.ts.map +1 -1
  913. package/src/engine/graphics/particles/particular/engine/simulator/SimulationStepDefinition.js +11 -12
  914. package/src/engine/graphics/particles/particular/engine/utils/volume/AttributeValue.d.ts.map +1 -1
  915. package/src/engine/graphics/particles/particular/engine/utils/volume/AttributeValue.js +199 -201
  916. package/src/engine/graphics/particles/particular/engine/utils/volume/ParticleVolume.d.ts +11 -11
  917. package/src/engine/graphics/particles/particular/engine/utils/volume/ParticleVolume.d.ts.map +1 -1
  918. package/src/engine/graphics/particles/particular/engine/utils/volume/ParticleVolume.js +13 -13
  919. package/src/engine/graphics/particles/particular/group/ParticleGroup.d.ts +6 -6
  920. package/src/engine/graphics/particles/particular/group/ParticleGroup.d.ts.map +1 -1
  921. package/src/engine/graphics/particles/particular/group/ParticleGroup.js +33 -34
  922. package/src/engine/graphics/particles/particular/group/ParticleSpecification.d.ts.map +1 -1
  923. package/src/engine/graphics/particles/particular/group/ParticleSpecification.js +2 -8
  924. package/src/engine/graphics/render/Lines.d.ts.map +1 -1
  925. package/src/engine/graphics/render/Lines.js +2 -2
  926. package/src/engine/graphics/render/buffer/FrameBufferManager.d.ts.map +1 -1
  927. package/src/engine/graphics/render/buffer/FrameBufferManager.js +20 -22
  928. package/src/engine/graphics/render/buffer/simple-fx/DepthLimitedBlur.d.ts +2 -2
  929. package/src/engine/graphics/render/buffer/simple-fx/DepthLimitedBlur.d.ts.map +1 -1
  930. package/src/engine/graphics/render/buffer/simple-fx/DepthLimitedBlur.js +16 -19
  931. package/src/engine/graphics/render/buffer/simple-fx/SimplePostProcessEffect.d.ts.map +1 -1
  932. package/src/engine/graphics/render/buffer/simple-fx/SimplePostProcessEffect.js +5 -8
  933. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts +2 -2
  934. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
  935. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +37 -38
  936. package/src/engine/graphics/render/buffer/slot/ProgramSlotValue.d.ts +5 -5
  937. package/src/engine/graphics/render/buffer/slot/ProgramSlotValue.d.ts.map +1 -1
  938. package/src/engine/graphics/render/buffer/slot/ProgramSlotValue.js +8 -6
  939. package/src/engine/graphics/render/buffer/slot/ProgramValueSlotDefinition.d.ts +5 -5
  940. package/src/engine/graphics/render/buffer/slot/ProgramValueSlotDefinition.d.ts.map +1 -1
  941. package/src/engine/graphics/render/buffer/slot/ProgramValueSlotDefinition.js +6 -6
  942. package/src/engine/graphics/render/buffer/slot/parameter/ProgramValueSlotParameterSet.d.ts.map +1 -1
  943. package/src/engine/graphics/render/buffer/slot/parameter/ProgramValueSlotParameterSet.js +11 -13
  944. package/src/engine/graphics/render/forward_plus/LightManager.d.ts +6 -6
  945. package/src/engine/graphics/render/forward_plus/LightManager.d.ts.map +1 -1
  946. package/src/engine/graphics/render/forward_plus/LightManager.js +15 -18
  947. package/src/engine/graphics/render/forward_plus/LightRenderMetadata.d.ts +5 -5
  948. package/src/engine/graphics/render/forward_plus/LightRenderMetadata.d.ts.map +1 -1
  949. package/src/engine/graphics/render/forward_plus/LightRenderMetadata.js +7 -8
  950. package/src/engine/graphics/render/forward_plus/model/Decal.d.ts +1 -1
  951. package/src/engine/graphics/render/forward_plus/model/Decal.d.ts.map +1 -1
  952. package/src/engine/graphics/render/forward_plus/model/Decal.js +22 -22
  953. package/src/engine/graphics/render/gizmo/GizmoShapeRenderingInterface.d.ts.map +1 -1
  954. package/src/engine/graphics/render/gizmo/GizmoShapeRenderingInterface.js +8 -8
  955. package/src/engine/graphics/render/layers/RenderLayerManager.d.ts.map +1 -1
  956. package/src/engine/graphics/render/layers/RenderLayerManager.js +10 -12
  957. package/src/engine/graphics/render/layers/RenderLayerManagerState.d.ts.map +1 -1
  958. package/src/engine/graphics/render/layers/RenderLayerManagerState.js +5 -9
  959. package/src/engine/graphics/render/layers/RenderLayerState.d.ts.map +1 -1
  960. package/src/engine/graphics/render/layers/RenderLayerState.js +5 -7
  961. package/src/engine/graphics/render/utils/ThreeBypassRenderer.d.ts.map +1 -1
  962. package/src/engine/graphics/render/utils/ThreeBypassRenderer.js +6 -9
  963. package/src/engine/graphics/render/view/CameraView.d.ts.map +1 -1
  964. package/src/engine/graphics/render/view/CameraView.js +56 -59
  965. package/src/engine/graphics/render/view/CameraViewManager.d.ts.map +1 -1
  966. package/src/engine/graphics/render/view/CameraViewManager.js +18 -20
  967. package/src/engine/graphics/render/visibility/IncrementalDeltaSet.d.ts +6 -6
  968. package/src/engine/graphics/render/visibility/IncrementalDeltaSet.d.ts.map +1 -1
  969. package/src/engine/graphics/render/visibility/IncrementalDeltaSet.js +40 -39
  970. package/src/engine/graphics/render/visibility/VisibilityComputer.d.ts.map +1 -1
  971. package/src/engine/graphics/render/visibility/VisibilityComputer.js +19 -22
  972. package/src/engine/graphics/render/visibility/VisibilityFilter.d.ts +5 -5
  973. package/src/engine/graphics/render/visibility/VisibilityFilter.d.ts.map +1 -1
  974. package/src/engine/graphics/render/visibility/VisibilityFilter.js +6 -6
  975. package/src/engine/graphics/render/visibility/hiz/buffer/HierarchicalZBuffer.d.ts.map +1 -1
  976. package/src/engine/graphics/render/visibility/hiz/buffer/HierarchicalZBuffer.js +40 -43
  977. package/src/engine/graphics/render/visibility/hiz/query/BatchOcclusionQuery.d.ts +1 -1
  978. package/src/engine/graphics/render/visibility/hiz/query/BatchOcclusionQuery.d.ts.map +1 -1
  979. package/src/engine/graphics/render/visibility/hiz/query/BatchOcclusionQuery.js +2 -2
  980. package/src/engine/graphics/sh3/lpv/WebGLCubeProbeRenderer.d.ts.map +1 -1
  981. package/src/engine/graphics/sh3/lpv/WebGLCubeProbeRenderer.js +7 -9
  982. package/src/engine/graphics/sh3/path_tracer/BufferedGeometryBVH.d.ts.map +1 -1
  983. package/src/engine/graphics/sh3/path_tracer/BufferedGeometryBVH.js +24 -28
  984. package/src/engine/graphics/shaders/TerrainShader.js +5 -18
  985. package/src/engine/graphics/shadows/ShadowMapRenderer.d.ts.map +1 -1
  986. package/src/engine/graphics/shadows/ShadowMapRenderer.js +11 -15
  987. package/src/engine/graphics/texture/AttributeDataTexture.d.ts +11 -11
  988. package/src/engine/graphics/texture/AttributeDataTexture.d.ts.map +1 -1
  989. package/src/engine/graphics/texture/AttributeDataTexture.js +14 -14
  990. package/src/engine/graphics/texture/TextureBackedMemoryRegion.d.ts +6 -6
  991. package/src/engine/graphics/texture/TextureBackedMemoryRegion.d.ts.map +1 -1
  992. package/src/engine/graphics/texture/TextureBackedMemoryRegion.js +33 -32
  993. package/src/engine/graphics/texture/atlas/AtlasLookupTexture.d.ts.map +1 -1
  994. package/src/engine/graphics/texture/atlas/AtlasLookupTexture.js +12 -12
  995. package/src/engine/graphics/texture/atlas/CachingTextureAtlas.d.ts.map +1 -1
  996. package/src/engine/graphics/texture/atlas/CachingTextureAtlas.js +17 -20
  997. package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.d.ts +1 -1
  998. package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.d.ts.map +1 -1
  999. package/src/engine/graphics/texture/atlas/ReferencedTextureAtlas.js +14 -14
  1000. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts +10 -10
  1001. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
  1002. package/src/engine/graphics/texture/atlas/TextureAtlas.js +13 -14
  1003. package/src/engine/graphics/texture/atlas/gpu/WebGLTextureAtlas.d.ts.map +1 -1
  1004. package/src/engine/graphics/texture/atlas/gpu/WebGLTextureAtlas.js +49 -49
  1005. package/src/engine/graphics/texture/sampler/Sampler2D.d.ts +5 -5
  1006. package/src/engine/graphics/texture/sampler/Sampler2D.d.ts.map +1 -1
  1007. package/src/engine/graphics/texture/sampler/Sampler2D.js +6 -6
  1008. package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.d.ts +16 -0
  1009. package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.d.ts.map +1 -0
  1010. package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js +54 -0
  1011. package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.d.ts +22 -0
  1012. package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.d.ts.map +1 -0
  1013. package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js +68 -0
  1014. package/src/engine/graphics/trail/tube/TubeX.d.ts +6 -6
  1015. package/src/engine/graphics/trail/tube/TubeX.d.ts.map +1 -1
  1016. package/src/engine/graphics/trail/tube/TubeX.js +3 -3
  1017. package/src/engine/graphics/trail/x/RibbonX.d.ts.map +1 -1
  1018. package/src/engine/graphics/trail/x/RibbonX.js +36 -39
  1019. package/src/engine/grid/area/AreaTrigger.d.ts +1 -1
  1020. package/src/engine/grid/area/AreaTrigger.d.ts.map +1 -1
  1021. package/src/engine/grid/area/AreaTrigger.js +8 -7
  1022. package/src/engine/grid/area/TriggerAction.d.ts.map +1 -1
  1023. package/src/engine/grid/area/TriggerAction.js +2 -4
  1024. package/src/engine/grid/grid2transform/GridPosition2TransformSerializationUpgrader_0_1.d.ts.map +1 -1
  1025. package/src/engine/grid/grid2transform/GridPosition2TransformSerializationUpgrader_0_1.js +3 -6
  1026. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts +3 -3
  1027. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
  1028. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.js +6 -9
  1029. package/src/engine/grid/position/GridPositionSerializationUpdater_0_1.d.ts.map +1 -1
  1030. package/src/engine/grid/position/GridPositionSerializationUpdater_0_1.js +2 -6
  1031. package/src/engine/grid/position/GridPositionSystem.d.ts.map +1 -1
  1032. package/src/engine/grid/position/GridPositionSystem.js +7 -11
  1033. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.d.ts.map +1 -1
  1034. package/src/engine/grid/transform2grid/Transform2GridPositionSystem.js +32 -40
  1035. package/src/engine/input/devices/PointerDevice.d.ts +8 -0
  1036. package/src/engine/input/devices/PointerDevice.d.ts.map +1 -1
  1037. package/src/engine/input/devices/PointerDevice.js +10 -4
  1038. package/src/engine/input/ecs/systems/InputControllerSystem.d.ts.map +1 -1
  1039. package/src/engine/input/ecs/systems/InputControllerSystem.js +9 -13
  1040. package/src/engine/input/ecs/systems/InputSystem.d.ts +1 -1
  1041. package/src/engine/input/ecs/systems/InputSystem.d.ts.map +1 -1
  1042. package/src/engine/input/ecs/systems/InputSystem.js +8 -8
  1043. package/src/engine/intelligence/behavior/SelectorBehavior.d.ts.map +1 -1
  1044. package/src/engine/intelligence/behavior/SelectorBehavior.js +13 -17
  1045. package/src/engine/intelligence/behavior/composite/ParallelBehavior.d.ts +10 -10
  1046. package/src/engine/intelligence/behavior/composite/ParallelBehavior.d.ts.map +1 -1
  1047. package/src/engine/intelligence/behavior/composite/ParallelBehavior.js +12 -11
  1048. package/src/engine/intelligence/behavior/composite/ParallelBehaviorSerializationAdapter.d.ts.map +1 -1
  1049. package/src/engine/intelligence/behavior/composite/ParallelBehaviorSerializationAdapter.js +2 -6
  1050. package/src/engine/intelligence/behavior/composite/SequenceBehavior.d.ts.map +1 -1
  1051. package/src/engine/intelligence/behavior/composite/SequenceBehavior.js +19 -22
  1052. package/src/engine/intelligence/behavior/composite/SequenceBehaviorSerializationAdapter.d.ts.map +1 -1
  1053. package/src/engine/intelligence/behavior/composite/SequenceBehaviorSerializationAdapter.js +2 -6
  1054. package/src/engine/intelligence/behavior/decorator/AbstractDecoratorBehavior.d.ts.map +1 -1
  1055. package/src/engine/intelligence/behavior/decorator/AbstractDecoratorBehavior.js +6 -10
  1056. package/src/engine/intelligence/behavior/decorator/InvertStatusBehaviorSerializationAdapter.d.ts.map +1 -1
  1057. package/src/engine/intelligence/behavior/decorator/InvertStatusBehaviorSerializationAdapter.js +2 -6
  1058. package/src/engine/intelligence/behavior/decorator/RepeatBehavior.d.ts +5 -5
  1059. package/src/engine/intelligence/behavior/decorator/RepeatBehavior.d.ts.map +1 -1
  1060. package/src/engine/intelligence/behavior/decorator/RepeatBehavior.js +7 -7
  1061. package/src/engine/intelligence/behavior/decorator/RepeatBehaviorSerializationAdapter.d.ts.map +1 -1
  1062. package/src/engine/intelligence/behavior/decorator/RepeatBehaviorSerializationAdapter.js +2 -6
  1063. package/src/engine/intelligence/behavior/decorator/RepeatUntilSuccessBehavior.d.ts +2 -2
  1064. package/src/engine/intelligence/behavior/decorator/RepeatUntilSuccessBehavior.d.ts.map +1 -1
  1065. package/src/engine/intelligence/behavior/decorator/RepeatUntilSuccessBehavior.js +7 -7
  1066. package/src/engine/intelligence/behavior/ecs/BehaviorSystem.d.ts.map +1 -1
  1067. package/src/engine/intelligence/behavior/ecs/BehaviorSystem.js +3 -5
  1068. package/src/engine/intelligence/behavior/primitive/PromiseBehavior.d.ts +2 -2
  1069. package/src/engine/intelligence/behavior/primitive/PromiseBehavior.d.ts.map +1 -1
  1070. package/src/engine/intelligence/behavior/primitive/PromiseBehavior.js +3 -4
  1071. package/src/engine/intelligence/behavior/util/RotationBehavior.d.ts.map +1 -1
  1072. package/src/engine/intelligence/behavior/util/RotationBehavior.js +8 -13
  1073. package/src/engine/intelligence/blackboard/Blackboard.d.ts.map +1 -1
  1074. package/src/engine/intelligence/blackboard/Blackboard.js +14 -14
  1075. package/src/engine/intelligence/blackboard/BlackboardDynamicStorageAdapter.d.ts.map +1 -1
  1076. package/src/engine/intelligence/blackboard/BlackboardDynamicStorageAdapter.js +52 -55
  1077. package/src/engine/intelligence/blackboard/BlackboardStack.d.ts.map +1 -1
  1078. package/src/engine/intelligence/blackboard/BlackboardStack.js +12 -16
  1079. package/src/engine/intelligence/blackboard/BlackboardValue.d.ts.map +1 -1
  1080. package/src/engine/intelligence/blackboard/BlackboardValue.js +6 -5
  1081. package/src/engine/intelligence/controller/graph/StateGraph.d.ts +1 -1
  1082. package/src/engine/intelligence/controller/graph/StateGraph.d.ts.map +1 -1
  1083. package/src/engine/intelligence/controller/graph/StateGraph.js +2 -2
  1084. package/src/engine/intelligence/controller/graph/StateGraphDescription.js +6 -8
  1085. package/src/engine/intelligence/controller/graph/StateGraphSystem.d.ts.map +1 -1
  1086. package/src/engine/intelligence/controller/graph/StateGraphSystem.js +1 -5
  1087. package/src/engine/intelligence/mcts/MoveEdge.d.ts.map +1 -1
  1088. package/src/engine/intelligence/mcts/MoveEdge.js +5 -8
  1089. package/src/engine/intelligence/resource/ResourceAllocationBid.d.ts +5 -5
  1090. package/src/engine/intelligence/resource/ResourceAllocationBid.d.ts.map +1 -1
  1091. package/src/engine/intelligence/resource/ResourceAllocationBid.js +6 -6
  1092. package/src/engine/interpolation/InterpolationLog.d.ts.map +1 -1
  1093. package/src/engine/interpolation/InterpolationLog.js +564 -583
  1094. package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -1
  1095. package/src/engine/interpolation/InterpolationSystem.js +232 -233
  1096. package/src/engine/knowledge/database/StaticKnowledgeDataTableDescriptor.d.ts.map +1 -1
  1097. package/src/engine/knowledge/database/StaticKnowledgeDataTableDescriptor.js +17 -20
  1098. package/src/engine/logging/elastic/ElasticSearchLogger.d.ts +6 -6
  1099. package/src/engine/logging/elastic/ElasticSearchLogger.d.ts.map +1 -1
  1100. package/src/engine/logging/elastic/ElasticSearchLogger.js +31 -32
  1101. package/src/engine/metrics/ProxyMetricsGateway.d.ts.map +1 -1
  1102. package/src/engine/metrics/ProxyMetricsGateway.js +5 -9
  1103. package/src/engine/navigation/ecs/components/Path.d.ts.map +1 -1
  1104. package/src/engine/navigation/ecs/components/Path.js +30 -32
  1105. package/src/engine/navigation/ecs/components/PathSerializationUpgrader_0_1.d.ts.map +1 -1
  1106. package/src/engine/navigation/ecs/components/PathSerializationUpgrader_0_1.js +2 -6
  1107. package/src/engine/navigation/ecs/components/PathSerializationUpgrader_1_2.d.ts.map +1 -1
  1108. package/src/engine/navigation/ecs/components/PathSerializationUpgrader_1_2.js +2 -6
  1109. package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.d.ts.map +1 -1
  1110. package/src/engine/navigation/ecs/components/PathSerializationUpgrader_2_3.js +34 -38
  1111. package/src/engine/navigation/ecs/path_following/PathFollowerSerializationUpgrader_0_1.d.ts.map +1 -1
  1112. package/src/engine/navigation/ecs/path_following/PathFollowerSerializationUpgrader_0_1.js +2 -6
  1113. package/src/engine/navigation/ecs/path_following/PathFollowerSerializationUpgrader_1_2.d.ts.map +1 -1
  1114. package/src/engine/navigation/ecs/path_following/PathFollowerSerializationUpgrader_1_2.js +2 -6
  1115. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.d.ts.map +1 -1
  1116. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.js +12 -17
  1117. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  1118. package/src/engine/network/NetworkSession.js +2037 -2036
  1119. package/src/engine/network/PriorityFetch.d.ts +6 -6
  1120. package/src/engine/network/PriorityFetch.d.ts.map +1 -1
  1121. package/src/engine/network/PriorityFetch.js +14 -14
  1122. package/src/engine/network/diagnostics/BandwidthMeter.d.ts.map +1 -1
  1123. package/src/engine/network/diagnostics/BandwidthMeter.js +150 -155
  1124. package/src/engine/network/diagnostics/ReplayLog.d.ts +2 -2
  1125. package/src/engine/network/diagnostics/ReplayLog.d.ts.map +1 -1
  1126. package/src/engine/network/diagnostics/ReplayLog.js +132 -137
  1127. package/src/engine/network/diagnostics/SyncTest.d.ts.map +1 -1
  1128. package/src/engine/network/diagnostics/SyncTest.js +146 -151
  1129. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  1130. package/src/engine/network/orchestrator/NetworkPeer.js +1202 -1209
  1131. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +4 -4
  1132. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
  1133. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +8 -10
  1134. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +7 -7
  1135. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
  1136. package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +643 -654
  1137. package/src/engine/network/replication/Replicator.d.ts +24 -24
  1138. package/src/engine/network/replication/Replicator.d.ts.map +1 -1
  1139. package/src/engine/network/replication/Replicator.js +405 -410
  1140. package/src/engine/network/sim/ReplicatedComponentRegistry.d.ts.map +1 -1
  1141. package/src/engine/network/sim/ReplicatedComponentRegistry.js +133 -140
  1142. package/src/engine/network/sim/RewindEngine.d.ts.map +1 -1
  1143. package/src/engine/network/sim/RewindEngine.js +179 -182
  1144. package/src/engine/network/sim/SimActionExecutor.d.ts +34 -34
  1145. package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
  1146. package/src/engine/network/sim/SimActionExecutor.js +305 -306
  1147. package/src/engine/network/sim/SimActionRegistry.d.ts.map +1 -1
  1148. package/src/engine/network/sim/SimActionRegistry.js +121 -128
  1149. package/src/engine/network/sim/SmoothingState.d.ts.map +1 -1
  1150. package/src/engine/network/sim/SmoothingState.js +220 -222
  1151. package/src/engine/network/sim/SpeculationLog.d.ts.map +1 -1
  1152. package/src/engine/network/sim/SpeculationLog.js +79 -84
  1153. package/src/engine/network/state/Baseline.d.ts.map +1 -1
  1154. package/src/engine/network/state/Baseline.js +78 -83
  1155. package/src/engine/network/state/ChangedEntitySet.d.ts.map +1 -1
  1156. package/src/engine/network/state/ChangedEntitySet.js +253 -256
  1157. package/src/engine/network/state/MutationLedger.d.ts.map +1 -1
  1158. package/src/engine/network/state/MutationLedger.js +179 -182
  1159. package/src/engine/network/state/ReplicationSlotTable.d.ts.map +1 -1
  1160. package/src/engine/network/state/ReplicationSlotTable.js +206 -211
  1161. package/src/engine/network/time/AdaptiveRenderDelay.d.ts.map +1 -1
  1162. package/src/engine/network/time/AdaptiveRenderDelay.js +250 -258
  1163. package/src/engine/network/time/JitterBuffer.d.ts.map +1 -1
  1164. package/src/engine/network/time/JitterBuffer.js +113 -116
  1165. package/src/engine/network/time/TimeSync.d.ts.map +1 -1
  1166. package/src/engine/network/time/TimeSync.js +145 -152
  1167. package/src/engine/network/transport/LoopbackTransport.d.ts.map +1 -1
  1168. package/src/engine/network/transport/LoopbackTransport.js +165 -173
  1169. package/src/engine/network/transport/ReliableCommandPipeline.d.ts.map +1 -1
  1170. package/src/engine/network/transport/ReliableCommandPipeline.js +315 -320
  1171. package/src/engine/network/transport/adapters/SimulatedTransport.d.ts.map +1 -1
  1172. package/src/engine/network/transport/adapters/SimulatedTransport.js +235 -241
  1173. package/src/engine/network/transport/adapters/WebSocketTransport.d.ts.map +1 -1
  1174. package/src/engine/network/transport/adapters/WebSocketTransport.js +154 -154
  1175. package/src/engine/network/transport/adapters/WebTransportTransport.d.ts.map +1 -1
  1176. package/src/engine/network/transport/adapters/WebTransportTransport.js +195 -199
  1177. package/src/engine/network/transport/fragments/FragmentAssembler.d.ts.map +1 -1
  1178. package/src/engine/network/transport/fragments/FragmentAssembler.js +286 -291
  1179. package/src/engine/network/transport/fragments/FragmentRetention.d.ts.map +1 -1
  1180. package/src/engine/network/transport/fragments/FragmentRetention.js +191 -194
  1181. package/src/engine/physics/body/BodyStorage.d.ts +2 -2
  1182. package/src/engine/physics/body/BodyStorage.d.ts.map +1 -1
  1183. package/src/engine/physics/body/BodyStorage.js +406 -406
  1184. package/src/engine/physics/body/SolverBodyState.d.ts +8 -8
  1185. package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -1
  1186. package/src/engine/physics/body/SolverBodyState.js +10 -10
  1187. package/src/engine/physics/broadphase/PairList.d.ts +1 -1
  1188. package/src/engine/physics/broadphase/PairList.d.ts.map +1 -1
  1189. package/src/engine/physics/broadphase/PairList.js +98 -97
  1190. package/src/engine/physics/contact/ManifoldStore.d.ts +2 -2
  1191. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  1192. package/src/engine/physics/contact/ManifoldStore.js +3 -4
  1193. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +2 -2
  1194. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -1
  1195. package/src/engine/physics/ecs/ColliderObserverSystem.js +102 -103
  1196. package/src/engine/physics/ecs/PhysicsSystem.d.ts +2 -2
  1197. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  1198. package/src/engine/physics/ecs/PhysicsSystem.js +248 -249
  1199. package/src/engine/physics/events/ContactEventBuffer.d.ts +1 -1
  1200. package/src/engine/physics/events/ContactEventBuffer.d.ts.map +1 -1
  1201. package/src/engine/physics/events/ContactEventBuffer.js +2 -1
  1202. package/src/engine/physics/fluid/FluidField.d.ts +54 -0
  1203. package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
  1204. package/src/engine/physics/fluid/FluidField.js +198 -15
  1205. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  1206. package/src/engine/physics/fluid/FluidSimulator.js +14 -4
  1207. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  1208. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +0 -45
  1209. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts.map +1 -1
  1210. package/src/engine/physics/fluid/ecs/FluidSystem.js +8 -144
  1211. package/src/engine/physics/fluid/ecs/FluidWorkerCore.d.ts +43 -0
  1212. package/src/engine/physics/fluid/ecs/FluidWorkerCore.d.ts.map +1 -0
  1213. package/src/engine/physics/fluid/ecs/FluidWorkerCore.js +123 -0
  1214. package/src/engine/physics/fluid/ecs/WorkerFluidSystem.d.ts +119 -0
  1215. package/src/engine/physics/fluid/ecs/WorkerFluidSystem.d.ts.map +1 -0
  1216. package/src/engine/physics/fluid/ecs/WorkerFluidSystem.js +395 -0
  1217. package/src/engine/physics/fluid/ecs/fluid.worker.d.ts +2 -0
  1218. package/src/engine/physics/fluid/ecs/fluid.worker.d.ts.map +1 -0
  1219. package/src/engine/physics/fluid/ecs/fluid.worker.js +12 -0
  1220. package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.d.ts +20 -0
  1221. package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.d.ts.map +1 -0
  1222. package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.js +32 -0
  1223. package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.d.ts +22 -0
  1224. package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.d.ts.map +1 -0
  1225. package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.js +47 -0
  1226. package/src/engine/physics/fluid/ecs/fluid_reanchor_field.d.ts +28 -0
  1227. package/src/engine/physics/fluid/ecs/fluid_reanchor_field.d.ts.map +1 -0
  1228. package/src/engine/physics/fluid/ecs/fluid_reanchor_field.js +85 -0
  1229. package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.d.ts +11 -0
  1230. package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.d.ts.map +1 -0
  1231. package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.js +16 -0
  1232. package/src/engine/physics/fluid/ecs/fluid_worker_protocol.d.ts +51 -0
  1233. package/src/engine/physics/fluid/ecs/fluid_worker_protocol.d.ts.map +1 -0
  1234. package/src/engine/physics/fluid/ecs/fluid_worker_protocol.js +111 -0
  1235. package/src/engine/physics/fluid/ecs/makeFluidWorker.d.ts +17 -0
  1236. package/src/engine/physics/fluid/ecs/makeFluidWorker.d.ts.map +1 -0
  1237. package/src/engine/physics/fluid/ecs/makeFluidWorker.js +18 -0
  1238. package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -1
  1239. package/src/engine/physics/island/IslandBuilder.js +84 -86
  1240. package/src/engine/physics/mls-mpm/MLS_MPM.d.ts.map +1 -1
  1241. package/src/engine/physics/mls-mpm/MLS_MPM.js +24 -26
  1242. package/src/engine/physics/queries/PhysicsSurfacePoint.d.ts.map +1 -1
  1243. package/src/engine/physics/queries/PhysicsSurfacePoint.js +98 -100
  1244. package/src/engine/physics/vehicle/RaycastVehicle.d.ts +6 -6
  1245. package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -1
  1246. package/src/engine/physics/vehicle/RaycastVehicle.js +357 -344
  1247. package/src/engine/plugin/PluginReferenceContext.d.ts +12 -12
  1248. package/src/engine/plugin/PluginReferenceContext.d.ts.map +1 -1
  1249. package/src/engine/plugin/PluginReferenceContext.js +24 -25
  1250. package/src/engine/scene/Scene.d.ts +5 -5
  1251. package/src/engine/scene/Scene.d.ts.map +1 -1
  1252. package/src/engine/scene/Scene.js +24 -23
  1253. package/src/engine/scene/SceneManager.d.ts +5 -5
  1254. package/src/engine/scene/SceneManager.d.ts.map +1 -1
  1255. package/src/engine/scene/SceneManager.js +13 -14
  1256. package/src/engine/sound/SoundEngine.d.ts +2 -2
  1257. package/src/engine/sound/SoundEngine.d.ts.map +1 -1
  1258. package/src/engine/sound/SoundEngine.js +7 -8
  1259. package/src/engine/sound/asset/SoundAssetPlaybackSpec.d.ts.map +1 -1
  1260. package/src/engine/sound/asset/SoundAssetPlaybackSpec.js +16 -20
  1261. package/src/engine/sound/ecs/SoundController.d.ts.map +1 -1
  1262. package/src/engine/sound/ecs/SoundController.js +37 -33
  1263. package/src/engine/sound/ecs/SoundControllerSystem.d.ts.map +1 -1
  1264. package/src/engine/sound/ecs/SoundControllerSystem.js +5 -11
  1265. package/src/engine/sound/ecs/SoundListener.d.ts.map +1 -1
  1266. package/src/engine/sound/ecs/SoundListener.js +4 -6
  1267. package/src/engine/sound/ecs/SoundListenerSystem.d.ts +6 -6
  1268. package/src/engine/sound/ecs/SoundListenerSystem.d.ts.map +1 -1
  1269. package/src/engine/sound/ecs/SoundListenerSystem.js +22 -23
  1270. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts +10 -4
  1271. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts.map +1 -1
  1272. package/src/engine/sound/ecs/audio/AudioEmitter.js +131 -122
  1273. package/src/engine/sound/ecs/audio/AudioEmitterSerializationAdapter.d.ts +34 -0
  1274. package/src/engine/sound/ecs/audio/AudioEmitterSerializationAdapter.d.ts.map +1 -0
  1275. package/src/engine/sound/ecs/audio/AudioEmitterSerializationAdapter.js +68 -0
  1276. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts +6 -6
  1277. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts.map +1 -1
  1278. package/src/engine/sound/ecs/audio/AudioEmitterSystem.js +314 -316
  1279. package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.d.ts.map +1 -1
  1280. package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.js +31 -1
  1281. package/src/engine/sound/ecs/emitter/SoundEmitterSerializationUpgrader_0_1.d.ts.map +1 -1
  1282. package/src/engine/sound/ecs/emitter/SoundEmitterSerializationUpgrader_0_1.js +2 -6
  1283. package/src/engine/sound/ecs/emitter/SoundEmitterSerializationUpgrader_1_2.d.ts.map +1 -1
  1284. package/src/engine/sound/ecs/emitter/SoundEmitterSerializationUpgrader_1_2.js +2 -6
  1285. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts +5 -5
  1286. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts.map +1 -1
  1287. package/src/engine/sound/ecs/emitter/SoundEmitterSystem.js +159 -159
  1288. package/src/engine/sound/material/concrete/RandomSoundMaterial.d.ts.map +1 -1
  1289. package/src/engine/sound/material/concrete/RandomSoundMaterial.js +5 -9
  1290. package/src/engine/sound/material/concrete/SingleSoundMaterial.d.ts.map +1 -1
  1291. package/src/engine/sound/material/concrete/SingleSoundMaterial.js +1 -6
  1292. package/src/engine/sound/material/concrete/SoundMaterialComposition.d.ts.map +1 -1
  1293. package/src/engine/sound/material/concrete/SoundMaterialComposition.js +18 -21
  1294. package/src/engine/sound/material/detector/terrain/TerrainSoundMaterialSurfaceDetector.d.ts.map +1 -1
  1295. package/src/engine/sound/material/detector/terrain/TerrainSoundMaterialSurfaceDetector.js +13 -15
  1296. package/src/engine/sound/simulation/core/OccluderBody.d.ts +12 -12
  1297. package/src/engine/sound/simulation/core/OccluderBody.d.ts.map +1 -1
  1298. package/src/engine/sound/simulation/core/OccluderBody.js +74 -74
  1299. package/src/engine/sound/simulation/prototypeAcousticSolver.js +5 -4
  1300. package/src/engine/sound/sopra/definition/SopraPanningModel.d.ts +7 -3
  1301. package/src/engine/sound/sopra/definition/SopraPanningModel.d.ts.map +1 -1
  1302. package/src/engine/sound/sopra/definition/SopraPanningModel.js +34 -20
  1303. package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.d.ts +15 -0
  1304. package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.d.ts.map +1 -1
  1305. package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.js +122 -106
  1306. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
  1307. package/src/engine/sound/sopra/runtime/EventInstance.js +852 -815
  1308. package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts +20 -6
  1309. package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts.map +1 -1
  1310. package/src/engine/sound/sopra/util/buildAttenuationCurve.js +56 -21
  1311. package/src/engine/ui/bindings/DomElementBinding.d.ts.map +1 -1
  1312. package/src/engine/ui/bindings/DomElementBinding.js +15 -18
  1313. package/src/engine/ui/bindings/DomElementBindingDescription.d.ts.map +1 -1
  1314. package/src/engine/ui/bindings/DomElementBindingDescription.js +10 -14
  1315. package/src/engine/ui/bindings/DomElementProcessor.d.ts.map +1 -1
  1316. package/src/engine/ui/bindings/DomElementProcessor.js +5 -7
  1317. package/src/engine/ui/bindings/DomElementProcessorManager.d.ts.map +1 -1
  1318. package/src/engine/ui/bindings/DomElementProcessorManager.js +26 -26
  1319. package/src/engine/ui/bindings/processors/DomElementProcessorTilt3D.d.ts.map +1 -1
  1320. package/src/engine/ui/bindings/processors/DomElementProcessorTilt3D.js +15 -19
  1321. package/src/engine/ui/cursor/CursorCoalescence.d.ts.map +1 -1
  1322. package/src/engine/ui/cursor/CursorCoalescence.js +11 -14
  1323. package/src/engine/ui/modal/ModalStack.d.ts.map +1 -1
  1324. package/src/engine/ui/modal/ModalStack.js +19 -22
  1325. package/src/engine/ui/notification/AnimatedObjectEmitter.d.ts +4 -4
  1326. package/src/engine/ui/notification/AnimatedObjectEmitter.d.ts.map +1 -1
  1327. package/src/engine/ui/notification/AnimatedObjectEmitter.js +68 -70
  1328. package/src/engine/ui/notification/NotificationManager.d.ts +4 -4
  1329. package/src/engine/ui/notification/NotificationManager.d.ts.map +1 -1
  1330. package/src/engine/ui/notification/NotificationManager.js +27 -29
  1331. package/src/engine/ui/notification/ViewEmitter.d.ts.map +1 -1
  1332. package/src/engine/ui/notification/ViewEmitter.js +2 -2
  1333. package/src/engine/ui/scene/SceneGUIContext.d.ts.map +1 -1
  1334. package/src/engine/ui/scene/SceneGUIContext.js +20 -22
  1335. package/src/engine/ui/tiles2d/TileMoveProgram.d.ts.map +1 -1
  1336. package/src/engine/ui/tiles2d/TileMoveProgram.js +5 -7
  1337. package/src/generation/GraphGenerationRule.d.ts.map +1 -1
  1338. package/src/generation/GraphGenerationRule.js +10 -14
  1339. package/src/generation/GridGeneratorConfig.d.ts.map +1 -1
  1340. package/src/generation/GridGeneratorConfig.js +15 -19
  1341. package/src/generation/filtering/CellFilterCellMatcher.d.ts.map +1 -1
  1342. package/src/generation/filtering/CellFilterCellMatcher.js +5 -9
  1343. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.d.ts.map +1 -1
  1344. package/src/generation/filtering/numeric/complex/CellFilterAngleToNormal.js +11 -15
  1345. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainObstacle.d.ts.map +1 -1
  1346. package/src/generation/grid/actions/ContinuousGridCellActionSetTerrainObstacle.js +11 -15
  1347. package/src/generation/grid/actions/ContinuousGridCellActionWriteObstacle.d.ts +10 -10
  1348. package/src/generation/grid/actions/ContinuousGridCellActionWriteObstacle.d.ts.map +1 -1
  1349. package/src/generation/grid/actions/ContinuousGridCellActionWriteObstacle.js +20 -19
  1350. package/src/generation/markers/matcher/MarkerNodeMatcherByType.d.ts.map +1 -1
  1351. package/src/generation/markers/matcher/MarkerNodeMatcherByType.js +5 -9
  1352. package/src/generation/placement/action/random/CellActionSelectRandom.d.ts.map +1 -1
  1353. package/src/generation/placement/action/random/CellActionSelectRandom.js +11 -16
  1354. package/src/view/ViewGroup.d.ts.map +1 -1
  1355. package/src/view/ViewGroup.js +11 -14
  1356. package/src/view/common/HTMLElementCacheKey.d.ts.map +1 -1
  1357. package/src/view/common/HTMLElementCacheKey.js +21 -24
  1358. package/src/view/common/TabbedView.d.ts +56 -26
  1359. package/src/view/common/TabbedView.d.ts.map +1 -1
  1360. package/src/view/common/TabbedView.js +128 -30
  1361. package/src/view/common/dnd/DragAndDropContext.d.ts.map +1 -1
  1362. package/src/view/common/dnd/DragAndDropContext.js +11 -12
  1363. package/src/view/common/dnd/Draggable.d.ts +5 -5
  1364. package/src/view/common/dnd/Draggable.d.ts.map +1 -1
  1365. package/src/view/common/dnd/Draggable.js +6 -6
  1366. package/src/view/controller/GuiController.d.ts +1 -1
  1367. package/src/view/controller/GuiController.d.ts.map +1 -1
  1368. package/src/view/controller/GuiController.js +3 -3
  1369. package/src/view/controller/controls/BooleanVector3Control.d.ts.map +1 -1
  1370. package/src/view/controller/controls/BooleanVector3Control.js +2 -2
  1371. package/src/view/controller/controls/NumericIntervalControl.d.ts +1 -1
  1372. package/src/view/controller/controls/NumericIntervalControl.d.ts.map +1 -1
  1373. package/src/view/controller/controls/NumericIntervalControl.js +90 -90
  1374. package/src/view/controller/controls/Vector2Control.d.ts +1 -1
  1375. package/src/view/controller/controls/Vector2Control.d.ts.map +1 -1
  1376. package/src/view/controller/controls/Vector2Control.js +83 -83
  1377. package/src/view/controller/controls/Vector3Control.d.ts +1 -1
  1378. package/src/view/controller/controls/Vector3Control.d.ts.map +1 -1
  1379. package/src/view/controller/controls/Vector3Control.js +90 -90
  1380. package/{editor/view/ecs/components → src/view/controller/dat}/DatGuiController.d.ts +45 -45
  1381. package/src/view/controller/dat/DatGuiController.d.ts.map +1 -0
  1382. package/src/view/controller/dat/DatGuiController.js +216 -0
  1383. package/src/view/currency/CompositeCurrencyLabelView.d.ts.map +1 -1
  1384. package/src/view/currency/CompositeCurrencyLabelView.js +2 -2
  1385. package/src/view/currency/CurrencyDenominationLabelView.d.ts.map +1 -1
  1386. package/src/view/currency/CurrencyDenominationLabelView.js +2 -3
  1387. package/src/view/currency/CurrencyDenominationSpec.d.ts.map +1 -1
  1388. package/src/view/currency/CurrencyDenominationSpec.js +11 -12
  1389. package/src/view/currency/CurrencyDisplaySpec.d.ts.map +1 -1
  1390. package/src/view/currency/CurrencyDisplaySpec.js +5 -9
  1391. package/src/view/elements/ColorPickerView.js +1 -1
  1392. package/src/view/elements/EmptyView.js +1 -1
  1393. package/src/view/elements/MeshPreview.d.ts +1 -1
  1394. package/src/view/elements/MeshPreview.d.ts.map +1 -1
  1395. package/src/view/elements/MeshPreview.js +4 -10
  1396. package/{editor/view/ecs/components/particles/ColorParameterLUTController.d.ts → src/view/elements/drawCheckers.d.ts} +13 -14
  1397. package/src/view/elements/drawCheckers.d.ts.map +1 -0
  1398. package/src/view/elements/drawCheckers.js +33 -0
  1399. package/src/view/elements/progress/segmented/SegmentDefinition.d.ts.map +1 -1
  1400. package/src/view/elements/progress/segmented/SegmentDefinition.js +1 -3
  1401. package/src/view/elements/radial/RadialMenu.d.ts.map +1 -1
  1402. package/src/view/elements/radial/RadialMenu.js +18 -19
  1403. package/src/view/elements/radial/RadialMenuElementDefinition.d.ts.map +1 -1
  1404. package/src/view/elements/radial/RadialMenuElementDefinition.js +95 -87
  1405. package/src/view/elements/radial/RadialText.d.ts +3 -3
  1406. package/src/view/elements/radial/RadialText.d.ts.map +1 -1
  1407. package/src/view/elements/radial/RadialText.js +19 -22
  1408. package/src/view/elements/tiles2d/Tile.d.ts +1 -1
  1409. package/src/view/elements/tiles2d/Tile.d.ts.map +1 -1
  1410. package/src/view/elements/tiles2d/Tile.js +2 -2
  1411. package/src/view/elements/tiles2d/TileGrid.d.ts.map +1 -1
  1412. package/src/view/elements/tiles2d/TileGrid.js +1 -4
  1413. package/src/view/game/achievements/AchievementNotificationView.d.ts.map +1 -1
  1414. package/src/view/game/achievements/AchievementNotificationView.js +2 -3
  1415. package/src/view/interaction/InteractionCommandSet.d.ts.map +1 -1
  1416. package/src/view/interaction/InteractionCommandSet.js +5 -7
  1417. package/src/view/layout/DockDragOverlayView.d.ts +24 -0
  1418. package/src/view/layout/DockDragOverlayView.d.ts.map +1 -0
  1419. package/src/view/layout/DockDragOverlayView.js +63 -0
  1420. package/src/view/layout/DockDropPlan.d.ts +46 -0
  1421. package/src/view/layout/DockDropPlan.d.ts.map +1 -0
  1422. package/src/view/layout/DockDropPlan.js +53 -0
  1423. package/src/view/layout/DockGroupNode.d.ts +29 -0
  1424. package/src/view/layout/DockGroupNode.d.ts.map +1 -0
  1425. package/src/view/layout/DockGroupNode.js +38 -0
  1426. package/src/view/layout/DockLayoutView.d.ts +125 -0
  1427. package/src/view/layout/DockLayoutView.d.ts.map +1 -0
  1428. package/src/view/layout/DockLayoutView.js +1193 -0
  1429. package/src/view/layout/DockPanel.d.ts +38 -0
  1430. package/src/view/layout/DockPanel.d.ts.map +1 -0
  1431. package/src/view/layout/DockPanel.js +51 -0
  1432. package/src/view/layout/DockSide.d.ts +48 -0
  1433. package/src/view/layout/DockSide.d.ts.map +1 -0
  1434. package/src/view/layout/DockSide.js +88 -0
  1435. package/src/view/layout/DockSplitNode.d.ts +59 -0
  1436. package/src/view/layout/DockSplitNode.d.ts.map +1 -0
  1437. package/src/view/layout/DockSplitNode.js +78 -0
  1438. package/src/view/layout/DrawerView.d.ts +53 -0
  1439. package/src/view/layout/DrawerView.d.ts.map +1 -0
  1440. package/src/view/layout/DrawerView.js +163 -0
  1441. package/src/view/layout/FloatingPanelView.d.ts +38 -0
  1442. package/src/view/layout/FloatingPanelView.d.ts.map +1 -0
  1443. package/src/view/layout/FloatingPanelView.js +251 -0
  1444. package/src/view/layout/SplitView.d.ts +73 -0
  1445. package/src/view/layout/SplitView.d.ts.map +1 -0
  1446. package/src/view/layout/SplitView.js +365 -0
  1447. package/src/view/layout/layout.scss +236 -0
  1448. package/src/view/layout/show_popup_menu.d.ts +19 -0
  1449. package/src/view/layout/show_popup_menu.d.ts.map +1 -0
  1450. package/src/view/layout/show_popup_menu.js +81 -0
  1451. package/src/view/minimap/Minimap.d.ts.map +1 -1
  1452. package/src/view/minimap/Minimap.js +2 -6
  1453. package/src/view/minimap/dom/MinimapMarkerView.d.ts.map +1 -1
  1454. package/src/view/minimap/dom/MinimapMarkerView.js +2 -3
  1455. package/src/view/minimap/gl/MarkerGL.d.ts +6 -6
  1456. package/src/view/minimap/gl/MarkerGL.d.ts.map +1 -1
  1457. package/src/view/minimap/gl/MarkerGL.js +8 -9
  1458. package/src/view/minimap/gl/MinimapFogOfWar.d.ts +7 -7
  1459. package/src/view/minimap/gl/MinimapFogOfWar.d.ts.map +1 -1
  1460. package/src/view/minimap/gl/MinimapFogOfWar.js +10 -12
  1461. package/src/view/minimap/gl/MinimapWorldGL.d.ts +15 -15
  1462. package/src/view/minimap/gl/MinimapWorldGL.d.ts.map +1 -1
  1463. package/src/view/minimap/gl/MinimapWorldGL.js +18 -16
  1464. package/src/view/minimap/gl/MinimapWorldLayer.d.ts.map +1 -1
  1465. package/src/view/minimap/gl/MinimapWorldLayer.js +4 -8
  1466. package/src/view/tooltip/DomTooltipManager.d.ts +6 -6
  1467. package/src/view/tooltip/DomTooltipManager.d.ts.map +1 -1
  1468. package/src/view/tooltip/DomTooltipManager.js +21 -22
  1469. package/src/view/tooltip/TooltipManager.d.ts.map +1 -1
  1470. package/src/view/tooltip/TooltipManager.js +6 -8
  1471. package/src/view/tooltip/TooltipView.d.ts +1 -1
  1472. package/src/view/tooltip/TooltipView.d.ts.map +1 -1
  1473. package/src/view/tooltip/TooltipView.js +3 -4
  1474. package/src/view/tooltip/VisualTip.d.ts +5 -5
  1475. package/src/view/tooltip/VisualTip.d.ts.map +1 -1
  1476. package/src/view/tooltip/VisualTip.js +6 -7
  1477. package/src/view/tooltip/gml/GMLEngine.d.ts.map +1 -1
  1478. package/src/view/tooltip/gml/GMLEngine.js +57 -63
  1479. package/src/view/util/DomSizeObserver.d.ts +6 -6
  1480. package/src/view/util/DomSizeObserver.d.ts.map +1 -1
  1481. package/src/view/util/DomSizeObserver.js +27 -20
  1482. package/editor/actions/concrete/PatchTerrainTextureAction.js +0 -153
  1483. package/editor/ecs/component/editors/ecs/MinimapMarkerEditor.d.ts +0 -14
  1484. package/editor/ecs/component/editors/ecs/MinimapMarkerEditor.d.ts.map +0 -1
  1485. package/editor/process/symbolic/makeStoryTriggerSetAreaDisplay.js +0 -114
  1486. package/editor/tools/TopDownCameraControlTool.d.ts +0 -13
  1487. package/editor/tools/TopDownCameraControlTool.d.ts.map +0 -1
  1488. package/editor/tools/TopDownCameraControlTool.js +0 -56
  1489. package/editor/tools/TransformTool.js +0 -385
  1490. package/editor/tools/paint/prototypeTerrainEditor.js +0 -135
  1491. package/editor/view/EditorView.d.ts +0 -39
  1492. package/editor/view/EditorView.d.ts.map +0 -1
  1493. package/editor/view/EditorView.js +0 -304
  1494. package/editor/view/ecs/ComponentControlFactory.d.ts +0 -19
  1495. package/editor/view/ecs/ComponentControlFactory.d.ts.map +0 -1
  1496. package/editor/view/ecs/ComponentControlFactory.js +0 -45
  1497. package/editor/view/ecs/EntityList.d.ts +0 -22
  1498. package/editor/view/ecs/EntityList.d.ts.map +0 -1
  1499. package/editor/view/ecs/HierarchicalEntityListView.module.scss +0 -13
  1500. package/editor/view/ecs/components/BlackboardController.js +0 -50
  1501. package/editor/view/ecs/components/DatGuiController.d.ts.map +0 -1
  1502. package/editor/view/ecs/components/FogOfWarController.js +0 -78
  1503. package/editor/view/ecs/components/FogOfWarRevealerController.js +0 -44
  1504. package/editor/view/ecs/components/GeneratedArmyController.js +0 -42
  1505. package/editor/view/ecs/components/GridObstacleController.d.ts +0 -14
  1506. package/editor/view/ecs/components/GridObstacleController.d.ts.map +0 -1
  1507. package/editor/view/ecs/components/GridObstacleController.js +0 -72
  1508. package/editor/view/ecs/components/GridPositionController.js +0 -40
  1509. package/editor/view/ecs/components/HeadsUpDisplayController.js +0 -30
  1510. package/editor/view/ecs/components/HighlightController.js +0 -47
  1511. package/editor/view/ecs/components/MeshController.js +0 -122
  1512. package/editor/view/ecs/components/PathFollowerController.js +0 -87
  1513. package/editor/view/ecs/components/TagController.js +0 -80
  1514. package/editor/view/ecs/components/TerrainController.js +0 -215
  1515. package/editor/view/ecs/components/TransformController.js +0 -97
  1516. package/editor/view/ecs/components/UnitShopController.js +0 -112
  1517. package/editor/view/ecs/components/army/ArmyController.js +0 -130
  1518. package/editor/view/ecs/components/gui/GUIElementController.js +0 -117
  1519. package/editor/view/ecs/components/items/ItemContainerController.js +0 -132
  1520. package/editor/view/ecs/components/items/ItemContainerController.stories.js +0 -50
  1521. package/editor/view/ecs/components/items/ItemController.js +0 -52
  1522. package/editor/view/ecs/components/particles/ColorParameterLUTController.d.ts.map +0 -1
  1523. package/editor/view/ecs/components/particles/ColorParameterLUTController.js +0 -444
  1524. package/editor/view/ecs/components/particles/ParameterTrackController.js +0 -51
  1525. package/editor/view/ecs/components/particles/ParticleEmitterController.js +0 -213
  1526. package/editor/view/ecs/components/particles/ParticleLayerController.js +0 -213
  1527. package/editor/view/ecs/components/particles/ScalarParameterLUTController.js +0 -401
  1528. package/editor/view/ecs/components/sockets/AttachmentSocketsController.js +0 -51
  1529. package/editor/view/ecs/components/sound/SoundEmitterController.js +0 -137
  1530. package/editor/view/ecs/components/story/StoryTriggerSetController.js +0 -214
  1531. package/editor/view/ecs/components/unit/AuraController.js +0 -45
  1532. package/editor/view/makeEntityDecorators.d.ts +0 -6
  1533. package/editor/view/makeEntityDecorators.d.ts.map +0 -1
  1534. package/editor/view/v2/SplitView.d.ts +0 -17
  1535. package/editor/view/v2/SplitView.d.ts.map +0 -1
  1536. package/editor/view/v2/ViewManagementSystem.js +0 -0
  1537. package/editor/view/v2/prototypeEditor.js +0 -127
  1538. package/src/engine/graphics/ecs/sprite/prototypeSpriteSystem.d.ts +0 -2
  1539. package/src/engine/graphics/ecs/sprite/prototypeSpriteSystem.d.ts.map +0 -1
  1540. package/src/engine/graphics/ecs/sprite/prototypeSpriteSystem.js +0 -1570
  1541. package/src/engine/graphics/three/TransfromControls.js +0 -1495
  1542. package/src/view/controller/controls_v2/AbstractPropertyBinding.d.ts +0 -8
  1543. package/src/view/controller/controls_v2/AbstractPropertyBinding.d.ts.map +0 -1
  1544. package/src/view/controller/controls_v2/AbstractPropertyBinding.ts +0 -10
  1545. package/src/view/controller/controls_v2/Controller.d.ts +0 -21
  1546. package/src/view/controller/controls_v2/Controller.d.ts.map +0 -1
  1547. package/src/view/controller/controls_v2/Controller.ts +0 -32
  1548. package/src/view/controller/controls_v2/IPropertyBinding.d.ts +0 -5
  1549. package/src/view/controller/controls_v2/IPropertyBinding.d.ts.map +0 -1
  1550. package/src/view/controller/controls_v2/IPropertyBinding.ts +0 -6
  1551. package/src/view/controller/controls_v2/PrimitivePropertyBinding.d.ts +0 -6
  1552. package/src/view/controller/controls_v2/PrimitivePropertyBinding.d.ts.map +0 -1
  1553. package/src/view/controller/controls_v2/PrimitivePropertyBinding.ts +0 -12
  1554. package/src/view/controller/controls_v2/SliderController.d.ts +0 -6
  1555. package/src/view/controller/controls_v2/SliderController.d.ts.map +0 -1
  1556. package/src/view/controller/controls_v2/SliderController.ts +0 -10
  1557. package/src/view/elements/button/ButtonView.stories.d.ts +0 -2
  1558. package/src/view/elements/button/ButtonView.stories.d.ts.map +0 -1
  1559. package/src/view/elements/button/ButtonView.stories.js +0 -14
@@ -1,2028 +1,2052 @@
1
- import { assert } from "../../../core/assert.js";
2
- import { Ray3 } from "../../../core/geom/3d/ray/Ray3.js";
3
- import { CapsuleShape3D } from "../../../core/geom/3d/shape/CapsuleShape3D.js";
4
- import { TransformedShape3D } from "../../../core/geom/3d/shape/TransformedShape3D.js";
5
- import Quaternion from "../../../core/geom/Quaternion.js";
6
- import Vector3 from "../../../core/geom/Vector3.js";
7
- import { clamp } from "../../../core/math/clamp.js";
8
- import { DEG_TO_RAD } from "../../../core/math/DEG_TO_RAD.js";
9
- import { lerp } from "../../../core/math/lerp.js";
10
- import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js";
11
- import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
12
- import { SerializationMetadata } from "../../ecs/components/SerializationMetadata.js";
13
- import Entity from "../../ecs/Entity.js";
14
- import { System } from "../../ecs/System.js";
15
- import { Transform } from "../../ecs/transform/Transform.js";
16
- import { Camera } from "../../graphics/ecs/camera/Camera.js";
17
- import { BodyKind } from "../../physics/ecs/BodyKind.js";
18
- import { Collider } from "../../physics/ecs/Collider.js";
19
- import { PhysicsSystem } from "../../physics/ecs/PhysicsSystem.js";
20
- import { RigidBody } from "../../physics/ecs/RigidBody.js";
21
- import { PhysicsSurfacePoint } from "../../physics/queries/PhysicsSurfacePoint.js";
22
- import { KinematicMover } from "./collision/KinematicMover.js";
23
- import { EyeOffsetStack } from "./composer/EyeOffsetStack.js";
24
- import { FirstPersonPlayerController } from "./FirstPersonPlayerController.js";
25
- import { DecisionPoint } from "./mastery/DecisionPoint.js";
26
- import { computeJumpFromApex } from "./math/computeJumpFromApex.js";
27
- import { computeLRCBreathRate } from "./math/computeLRCBreathRate.js";
28
- import { computeMassRatios } from "./math/computeMassRatios.js";
29
- import { jumpForceForHeight, launchVelocityForForce } from "./math/jumpDynamics.js";
30
- import { Spring } from "./math/Spring.js";
31
- import { stepTowards } from "./math/stepTowards.js";
32
- import { FirstPersonActionState, FirstPersonLocomotionMode } from "./pose/FirstPersonPose.js";
33
- import { FirstPersonPosture } from "./pose/FirstPersonPosture.js";
34
- import { FirstPersonSensors } from "./sensors/FirstPersonSensors.js";
35
-
36
- // ---------------------------------------------------------------------------
37
- // Scratch allocations — reused per frame to avoid GC pressure
38
- // ---------------------------------------------------------------------------
39
- const SCRATCH_V3_A = new Vector3();
40
- const SCRATCH_V3_B = new Vector3();
41
- const SCRATCH_V3_C = new Vector3();
42
- const SCRATCH_Q_A = new Quaternion();
43
- const SCRATCH_Q_B = new Quaternion();
44
- const SCRATCH_Q_C = new Quaternion();
45
-
46
- const TWO_PI = Math.PI * 2;
47
- const LN2 = Math.log(2);
48
-
49
- /**
50
- * Build a posture-sized player capsule: a {@link CapsuleShape3D} of
51
- * `radius` and the appropriate cylinder height, wrapped in a
52
- * {@link TransformedShape3D} whose Y offset puts the capsule's bottom
53
- * exactly at the wrapped shape's local origin. The entity's
54
- * `transform.position` then represents the player's feet — and a
55
- * posture-driven shrink doesn't yank the feet up the way a centred
56
- * capsule would, nor dip them below the floor.
57
- *
58
- * The capsule's lowest point in its own local frame is at
59
- * `-(cylinderHeight/2 + radius) = -max(totalHeight/2, radius)`.
60
- * Offsetting the wrapper by the magnitude of that puts the bottom at
61
- * Y = 0:
62
- * - Stand (`H = 1.8`, `r = 0.34`): cylHeight = 1.12, offset = 0.9.
63
- * Bottom = -0.9 + 0.9 = 0. Top = +0.9 + 0.9 = 1.8.
64
- * - Crouch (`H = 0.8`, `r = 0.34`): cylHeight = 0.12, offset = 0.4.
65
- * Bottom = -0.4 + 0.4 = 0. Top = +0.4 + 0.4 = 0.8.
66
- * - Prone (`H = 0.4`, `r = 0.34`): cylHeight = 0 (capsule collapses
67
- * to a sphere of radius), offset = max(0.2, 0.34) = 0.34.
68
- * Bottom = -0.34 + 0.34 = 0. Top = +0.34 + 0.34 = 0.68. The
69
- * `totalHeight = 0.4` value is honoured for the offset budget
70
- * but the actual Y extent floors at `2·radius`.
71
- *
72
- * Picking `totalHeight/2` blindly (the obvious choice) would put the
73
- * Prone capsule's bottom at `0.2 - 0.34 = -0.14` — dipping below the
74
- * feet, and into any physics floor that's flush with feet level. On
75
- * a physics ground slab, every horizontal shape_cast from inside the
76
- * floor returns t = 0, `advance = max(0, t - SKIN) = 0`, and the
77
- * slide freezes in place — see SlideMotion.spec.js for the
78
- * regression test that pins this.
79
- *
80
- * @param {number} radius — capsule radius in metres
81
- * @param {number} totalHeight — desired full Y extent; ignored below
82
- * `2·radius` (the capsule's intrinsic minimum extent)
83
- * @returns {TransformedShape3D}
84
- */
85
- function makePostureCapsule(radius, totalHeight) {
86
- const cylinderHeight = Math.max(0, totalHeight - 2 * radius);
87
- const yOffset = Math.max(totalHeight / 2, radius);
88
- return TransformedShape3D.from_translation(
89
- CapsuleShape3D.from(radius, cylinderHeight),
90
- [0, yOffset, 0],
91
- );
92
- }
93
-
94
- /**
95
- * Per-entity runtime state the system maintains internally — too transient
96
- * even for {@link FirstPersonPlayerController}'s `state` member, because it
97
- * encodes input-edge bookkeeping and timer values the public surface should
98
- * never see directly.
99
- */
100
- class PerEntityRuntime {
101
- constructor() {
102
- /**
103
- * Co-attached kinematic body. Set by {@link FirstPersonPlayerControllerSystem.link}
104
- * after asserting it's present. The controller writes Transform.position
105
- * directly (existing motion logic); physics derives the body's velocity
106
- * from the per-step delta. Other physics systems (raycasts, contact
107
- * events) see the player through this body.
108
- * @type {RigidBody|null}
109
- */
110
- this.rigidBody = null;
111
-
112
- /**
113
- * Co-attached collider, cached at link. Same source the physics
114
- * narrowphase uses, so move-and-slide casts the player's
115
- * actual collision shape against the world.
116
- * @type {Collider|null}
117
- */
118
- this.collider = null;
119
-
120
- /**
121
- * Pre-built capsule colliders, one per posture. Cached at link
122
- * from `config.body.{height, crouchHeight, proneHeight, radius}`
123
- * so {@link _syncColliderShape} can swap the collider's shape on
124
- * a posture change with zero per-tick allocation. Hang reuses
125
- * Stand (the player's body is full-extent, just hanging below
126
- * the ledge the rig animates the arms-up pose). Sentinel
127
- * `lastPosture = -1` forces a sync on the first tick after
128
- * link, so the initial shape always matches Stand.
129
- * @private
130
- * @type {TransformedShape3D|null}
131
- */
132
- this.colliderShapeStand = null;
133
- /** @private @type {TransformedShape3D|null} */
134
- this.colliderShapeCrouch = null;
135
- /** @private @type {TransformedShape3D|null} */
136
- this.colliderShapeProne = null;
137
- /** @private */
138
- this.lastPosture = -1;
139
-
140
- /** Eye pitch in radians, clamped to config.look limits. */
141
- this.eyePitch = 0;
142
- /** Body yaw in radians (around world up). */
143
- this.bodyYaw = 0;
144
- /** Yaw rate (rad/s) computed in look consumption — for evaluators. */
145
- this.yawRateRadPerSec = 0;
146
-
147
- // -- Render-rate look + position interpolation ---------------------
148
- // The camera DIRECTION (yaw/pitch) is consumed at RENDER rate (see the
149
- // controller's _consumeLook), so it tracks the mouse smoothly instead of
150
- // being aliased by the 60Hz sim sampling — consuming the accumulated
151
- // mouse delta once per fixed step beats against an unaligned mouse
152
- // report rate and makes a slow turn lurch (the reported snap). The body
153
- // POSITION still advances only at the fixed step, so it's blended
154
- // between the last two steps by the sub-step alpha to stay smooth on a
155
- // high-refresh display.
156
- /** Body yaw at the end of the previous fixed step used to derive the
157
- * per-step turn RATE that drives look-lean, decoupled from per-frame
158
- * input jitter. */
159
- this.prevFixedYaw = 0;
160
- this.renderPrevPos = new Vector3();
161
- this.renderCurPos = new Vector3();
162
- /** When true, the next position snapshot collapses prev=cur (no blend)
163
- * — set on a body teleport (e.g. the ledge-grab hang snap) so the
164
- * camera doesn't glide across the jump. */
165
- this.renderSnap = false;
166
-
167
- /** Horizontal+vertical velocity. We integrate these inside the system
168
- * when no external physics layer is attached. */
169
- this.velocityX = 0;
170
- this.velocityY = 0;
171
- this.velocityZ = 0;
172
-
173
- /** Previous-tick jump intent for rising/falling edge detection. */
174
- this.prevJumpHeld = false;
175
- /** Previous-tick crouch intent — for toggle-mode edge detection. */
176
- this.prevCrouchHeld = false;
177
- /** True while crouch toggle is latched on (used only in toggle mode). */
178
- this.crouchLatched = false;
179
-
180
- /** Remaining time in jump anticipation, or <= 0 if not anticipating. */
181
- this.anticipationRemaining = 0;
182
- /** Cached derived gravity (m/s^2) from peakHeight + timeToApex. */
183
- this.gravity = 9.81;
184
- /**
185
- * Cached jump PUSH-OFF FORCE (newtons) — the physical leg force that
186
- * reaches `peakHeight` at the reference mass, the quantity jump power is
187
- * expressed in. Derived at link via {@link jumpForceForHeight}; abilities
188
- * (e.g. the scramble wall-kick) launch off this same force.
189
- */
190
- this.jumpForce = 0;
191
- /** Cached derived jump impulse (m/s upward), post-mass-scaling. */
192
- this.jumpInitialVy = 5.0;
193
- /**
194
- * Cached mass scaling factors — computed once at link. See
195
- * {@link computeMassRatios}. Heavier ⇒ lower jumpV0Scale, lower
196
- * groundAccelScale, higher landingDipScale + exertionRiseScale.
197
- */
198
- this.massRatios = null;
199
-
200
- /** Spring for landing dip (under-damped → rings after impact). */
201
- this.landSpring = new Spring();
202
- /** Spring for FOV (critically damped). */
203
- this.fovSpring = new Spring(70);
204
- /** Spring for eye height (crouch transition). */
205
- this.eyeHeightSpring = new Spring(1.80);
206
- /** Spring for lean roll (radians) — banks into lateral acceleration. */
207
- this.leanSpring = new Spring();
208
- /**
209
- * Lean target this tick (radians). Always set; L2.f spring-steps
210
- * toward this value. Whoever owned motion this tick wrote it:
211
- * base writes the lat-accel + look-lean derived value at the end
212
- * of {@link _runBaseLocomotion}; abilities that want to override
213
- * (WallRuntilt-into-wall, Slide/Mantle/LedgeGrab → zero) write
214
- * their own value in tick. Uniform channel — no null sentinel.
215
- */
216
- this.leanTargetRad = 0;
217
-
218
- /** Previous horizontal velocity — for lateral acceleration → lean. */
219
- this.prevVelocityX = 0;
220
- this.prevVelocityZ = 0;
221
-
222
- /** Previous-tick grounded for edge detection. */
223
- this.prevGrounded = true;
224
- /** Vertical speed at moment of last "leave ground". */
225
- this.takeoffVy = 0;
226
- /** Max vertical position since last takeoff for jump apex detection. */
227
- this.peakAltitude = 0;
228
- /** Set true once a jump has been launched; cleared on land. */
229
- this.midJump = false;
230
- /** Apex already fired for this airborne segment? */
231
- this.apexFired = false;
232
-
233
- /** Stride phase from previous fixed step — for footstep edge detection. */
234
- this.prevStridePhase = 0;
235
- /** Breath phase from previous fixed step — for inhale/exhale edge detection. */
236
- this.prevBreathPhase = 0;
237
- /** Which foot fires next — flipped on each footstep signal. */
238
- this.nextFootSide = "R";
239
- /**
240
- * Which foot is currently bearing the body's weight (the foot that
241
- * most recently landed). Drives the lateral-bob direction: at R
242
- * midstance the COM is over the right foot, so the head shifts
243
- * laterally toward screen-right; at L midstance the opposite.
244
- * Coupled to the same signal the footstep emits, so anything that
245
- * listens to onFootStep.side will see the bob agree.
246
- * Initialized "L" so the very first footstep fires "R" and the
247
- * standingFoot updates to "R" — putting the head laterally right
248
- * during the first half-stride, as expected.
249
- */
250
- this.standingFoot = "L";
251
-
252
- /**
253
- * [0..1] How "backward" the player is currently moving. Derived in
254
- * fixedUpdate from velocity · screen-forward, normalized to sprint
255
- * speed. Drives the gait wobble amplifier on the L3 camera-composition
256
- * pass. Stored on runtime (rather than state) because it's a render-
257
- * side input — downstream observers should look at velocity directly.
258
- */
259
- this.backwardness = 0;
260
-
261
- /**
262
- * Smoothed bob amplitude envelope. Target = max(speedNormalized,
263
- * backwardness) when grounded, 0 airborne. Spring decay prevents
264
- * the whiplash where stopping motion would snap the bob to neutral.
265
- */
266
- this.bobIntensitySpring = new Spring();
267
-
268
- /**
269
- * Vertical impact spring kicked downward at each footfall, decays
270
- * with a slight under-damped overshoot. Produces the impact-arrest +
271
- * leg-push curve. value units: meters (added directly to eyeLocal.y).
272
- */
273
- this.verticalImpactSpring = new Spring();
274
-
275
- /**
276
- * Sprint-posture spring — eye pitches forward as the player commits
277
- * to a sprint, returns to neutral when they slow. Value is in
278
- * radians; slower half-life than other springs so it feels like
279
- * a posture change rather than an input twitch. See cfg.posture.
280
- */
281
- this.sprintPostureSpring = new Spring();
282
-
283
- /**
284
- * Head-droop spring — additional forward pitch as exertion rises.
285
- * Sells fatigue subtly. Target tracks exertion-driven max droop
286
- * angle; spring lag keeps the transition slow and physical.
287
- */
288
- this.headDroopSpring = new Spring();
289
-
290
- /**
291
- * [0..1] sprintness — how much of the walk→sprint speed range the
292
- * body is currently in. Computed in fixedUpdate, read by L3 for FOV
293
- * and the sprint-posture pitch / forward-shift offset.
294
- */
295
- this.sprintness = 0;
296
-
297
- /**
298
- * Cached sin/cos of current body yaw — written once per fixedUpdate
299
- * after look intent is consumed, read by every downstream step
300
- * (locomotion, backwardness, lean look-rate, pose channels). Avoids
301
- * recomputing the trig 3+ times per tick.
302
- */
303
- this.sinYaw = 0;
304
- this.cosYaw = 1;
305
-
306
- /** Cached horizontal speed (m/s) for this tick — written in derived-state. */
307
- this.horizSpeed = 0;
308
-
309
- /** Cached stride frequency (Hz) for this tick written in breath block, read by stride. */
310
- this.strideFreqHz = 0;
311
-
312
- /**
313
- * Additive accumulator for body-local eye-position offsets. The
314
- * system pushes its own contributions (bob, breath, landing,
315
- * sprint posture) each render frame; external systems can push
316
- * recoil/shake/knockback contributions via the same interface.
317
- */
318
- this.eyeOffsetStack = new EyeOffsetStack();
319
-
320
- /**
321
- * Spatial-query results populated by {@link FirstPersonSensorsSystem}
322
- * (when present). Abilities and the locomotion FSM read this.
323
- * Lives on runtime so other systems can populate it without
324
- * touching the controller component's public surface.
325
- */
326
- this.sensors = new FirstPersonSensors();
327
-
328
- /** Cached eye entity ID. -1 until link assigns it. */
329
- this.eyeEntity = -1;
330
-
331
- /**
332
- * One-shot hand-off flag: LedgeGrab sets it on a pull-up release so
333
- * Mantle (next tick) knows this is a ledge pull-up — it then allows a
334
- * too-thin top (auto-mantle still requires a standable one) and vaults
335
- * OVER instead of onto. Mantle consumes it in onActivate.
336
- */
337
- this.ledgePullUpRequest = false;
338
-
339
- /**
340
- * Seconds remaining in the post-release ledge re-grab refractory
341
- * window. Set by {@link LedgeGrab} on deactivation, aged down by dt
342
- * each fixed step, and read by LedgeGrab.canActivate — so a deliberate
343
- * dismount can fall clear of the grab window without the auto-catch
344
- * snapping the body straight back onto the same lip.
345
- */
346
- this.ledgeRegrabCooldown = 0;
347
- }
348
- }
349
-
350
- /**
351
- * Drives a first-person camera + body from intent fields. See sibling
352
- * DESIGN.md for goals, architecture, and the five processing layers (L0..L4).
353
- *
354
- * - fixedUpdate runs L1 (locomotion), L2 (pose state), and L4 (events) so
355
- * the simulation remains deterministic.
356
- * - update runs L3 (camera composition) at render rate so the eye is never
357
- * smoother than the screen.
358
- *
359
- * The system itself integrates a simple flat-floor at y = `config.gravity.magnitude > 0
360
- * ? state.groundY : -Infinity` for the prototype. A real physics layer should
361
- * write `state.grounded`/`state.groundNormal` from outside instead; the
362
- * built-in resolver is just a convenience to keep the controller usable
363
- * without dependencies.
364
- *
365
- * @author Alex Goldring
366
- * @copyright Company Named Limited (c) 2026
367
- */
368
- export class FirstPersonPlayerControllerSystem extends System {
369
- constructor() {
370
- super();
371
-
372
- // Dependencies kept to (controller, transform) so we can ASSERT on
373
- // RigidBody at link time and emit a clear error if missing. If
374
- // RigidBody were a hard dep, entities lacking one would silently
375
- // never link — the controller would appear inert with no
376
- // diagnostic. The assert below catches the missing-body case
377
- // explicitly.
378
- this.dependencies = [FirstPersonPlayerController, Transform];
379
-
380
- this.components_used = [
381
- ResourceAccessSpecification.from(Transform, ResourceAccessKind.Write),
382
- ResourceAccessSpecification.from(Camera, ResourceAccessKind.Write),
383
- ResourceAccessSpecification.from(RigidBody, ResourceAccessKind.Write),
384
- ];
385
-
386
- /**
387
- * Per-entity runtime, keyed by entity id.
388
- * @type {Map<number, PerEntityRuntime>}
389
- */
390
- this.runtime = new Map();
391
-
392
- /**
393
- * If true, the system clamps body y >= groundY and writes
394
- * state.grounded itself. Turn off when wiring a real physics layer.
395
- * @type {boolean}
396
- */
397
- this.useBuiltInFlatGround = true;
398
-
399
- /**
400
- * The flat-ground y for the built-in resolver. Ignored when
401
- * useBuiltInFlatGround is false.
402
- * @type {number}
403
- */
404
- this.groundY = 0;
405
-
406
- /**
407
- * Optional callback that returns the surface Y under the player
408
- * for ground resolution. Called each tick with the player's
409
- * current (x, y, z); returns the world-Y of the ground below,
410
- * or null if no ground is below (gap / void).
411
- *
412
- * Combines with `useBuiltInFlatGround`: the effective ground for
413
- * the tick is `max(this.groundY when enabled, resolver(...))`.
414
- * Set both off (`useBuiltInFlatGround=false`, `groundResolver=null`)
415
- * to defer to external physics entirely.
416
- *
417
- * Designed for prototypes / gyms that need elevated platforms
418
- * without a full physics layer. Production should wire a real
419
- * physics system instead.
420
- *
421
- * @type {((x:number, y:number, z:number) => number|null) | null}
422
- */
423
- this.groundResolver = null;
424
-
425
- /**
426
- * PhysicsSystem reference. Auto-acquired at startup; can be
427
- * overridden by the caller. When present, collision is resolved
428
- * by the {@link KinematicMover}; when null, the controller uses
429
- * the flat-ground integrator ({@link _moveFlatGround}) — useful
430
- * for spec setups that don't wire physics.
431
- * @type {PhysicsSystem|null}
432
- */
433
- this.physicsSystem = null;
434
-
435
- /**
436
- * The collision solver the authoritative move resolver whenever
437
- * a {@link PhysicsSystem} is present (recover + unified
438
- * sweep-and-slide + ground-categorize; see DESIGN_COLLISION.md).
439
- * Lazily built on first use. With no physics the controller falls
440
- * back to a flat-ground integrator ({@link _moveFlatGround}).
441
- * @private
442
- * @type {KinematicMover|null}
443
- */
444
- this._mover = null;
445
- /** @private Scratch velocity handed to the mover each tick. */
446
- this._moverVelocity = new Vector3();
447
- /** @private Scratch for the posture-headroom up-cast. */
448
- this._postureRay = new Ray3();
449
- /** @private @type {PhysicsSurfacePoint} */
450
- this._postureHit = new PhysicsSurfacePoint();
451
- }
452
-
453
- async startup(entityManager) {
454
- this.entityManager = entityManager;
455
- if (this.physicsSystem === null) {
456
- const ps = entityManager.getSystem(PhysicsSystem);
457
- if (ps !== null) this.physicsSystem = ps;
458
- }
459
- }
460
-
461
- /**
462
- * @param {FirstPersonPlayerController} controller
463
- * @param {Transform} bodyTransform
464
- * @param {number} entity
465
- */
466
- link(controller, bodyTransform, entity) {
467
- const ecd = this.entityManager.dataset;
468
-
469
- // The controller assumes a kinematic-position RigidBody is co-
470
- // attached on this entity. The body is the spatial proxy used
471
- // for sensor raycasts and physics-side observers (other entities
472
- // raycasting against the player, dynamic bodies colliding with
473
- // the capsule, etc.). The controller writes Transform directly,
474
- // physics derives velocity from the per-step delta. If a body is
475
- // missing the controller could still drive the camera, but the
476
- // physics integration silently breaks — assert here so the
477
- // misconfiguration is caught at link time.
478
- const rigidBody = ecd.getComponent(entity, RigidBody);
479
- assert.ok(rigidBody !== undefined,
480
- "FirstPersonPlayerController entity must have a co-attached RigidBody "
481
- + "(kinematic capsule). See prototype_first_person_controller.js for setup.");
482
- assert.equal(rigidBody.kind, BodyKind.KinematicPosition,
483
- "FirstPersonPlayerController RigidBody must be BodyKind.KinematicPosition; "
484
- + "the controller owns the Transform and physics derives velocity.");
485
- // Collider is also required — the KinematicMover casts this shape
486
- // against the world (recover / sweep / ground probe). Asserted
487
- // here so a missing collider surfaces at link rather than
488
- // producing a null-deref at the first cast attempt.
489
- const collider = ecd.getComponent(entity, Collider);
490
- assert.ok(collider !== undefined,
491
- "FirstPersonPlayerController entity must have a co-attached Collider. "
492
- + "The controller's move-and-slide casts this shape to detect blockers.");
493
-
494
- const runtime = new PerEntityRuntime();
495
- runtime.rigidBody = rigidBody;
496
- runtime.collider = collider;
497
-
498
- // Pre-build one capsule per posture from cfg.body. Eye-height
499
- // doubles as collider-top by convention here the prototype's
500
- // `buildPlayerEntity` uses the same approximation (`totalHeight =
501
- // bodyCfg.height`). The +Y offset puts the capsule bottom at
502
- // transform.position so the player's "feet" stay anchored across
503
- // posture changes; only the head drops/rises.
504
- const radius = controller.config.body.radius;
505
- runtime.colliderShapeStand = makePostureCapsule(radius, controller.config.body.height);
506
- runtime.colliderShapeCrouch = makePostureCapsule(radius, controller.config.body.crouchHeight);
507
- runtime.colliderShapeProne = makePostureCapsule(radius, controller.config.body.proneHeight);
508
- // Cache each posture capsule's ACTUAL top (bbox maxY) — not the
509
- // nominal config height. A capsule's height floors at 2·radius,
510
- // so e.g. a 0.4 m "prone" height actually yields a 0.68 m
511
- // capsule. The headroom up-cast sweeps by the difference of real
512
- // tops; using config heights would over-/under-estimate the grow
513
- // and clamp to the wrong posture (Prone instead of Crouch).
514
- const bbox = new Float64Array(6);
515
- runtime.colliderShapeStand.compute_bounding_box(bbox);
516
- runtime.standTop = bbox[4];
517
- runtime.colliderShapeCrouch.compute_bounding_box(bbox);
518
- runtime.crouchTop = bbox[4];
519
- runtime.colliderShapeProne.compute_bounding_box(bbox);
520
- runtime.proneTop = bbox[4];
521
- // Force a shape sync on the first tick: even though the caller
522
- // built a Stand-sized collider, we rebuild it from cfg here so a
523
- // post-link config tweak (e.g. crouchHeight changed for a unit
524
- // test) is reflected on the live collider without a relink.
525
- runtime.lastPosture = -1;
526
-
527
- this.runtime.set(entity, runtime);
528
-
529
- // Derive gravity + jump impulse from designer-friendly params, then
530
- // mass-scale the initial velocity (heavier ⇒ lower jump).
531
- runtime.massRatios = computeMassRatios(
532
- controller.config.body.mass,
533
- controller.config.body.referenceMass,
534
- controller.config.body.massCouplingStrength,
535
- );
536
- const derived = { gravity: 0, initialVelocity: 0 };
537
- computeJumpFromApex(controller.config.jump.peakHeight, controller.config.jump.timeToApex, derived);
538
- runtime.gravity = derived.gravity;
539
- // Express jump power as a physical push-off FORCE (newtons): the force the
540
- // legs deliver to reach peakHeight at the REFERENCE mass. Stored so other
541
- // moves (the scramble wall-kick) can launch off the SAME force.
542
- const bodyCfg = controller.config.body;
543
- runtime.jumpForce = jumpForceForHeight(
544
- bodyCfg.referenceMass, runtime.gravity, bodyCfg.jumpPushoffDistance, controller.config.jump.peakHeight,
545
- );
546
- // Force launch velocity for the reference body (== √(2·g·peakHeight)),
547
- // then the mass-coupling dial (jumpV0Scale 1/√mass, modulated by
548
- // massCouplingStrength) scales it for the actual body. Identical result
549
- // to the prior `derived.initialVelocity · jumpV0Scale`, now routed
550
- // through the force so jump power has a single physical source.
551
- runtime.jumpInitialVy =
552
- launchVelocityForForce(runtime.jumpForce, bodyCfg.referenceMass, bodyCfg.jumpPushoffDistance)
553
- * runtime.massRatios.jumpV0Scale;
554
-
555
- // Seed yaw from the starting body rotation. `toEulerAnglesYXZ`
556
- // returns (pitch, yaw, roll) — we only care about y.
557
- bodyTransform.rotation.toEulerAnglesYXZ(SCRATCH_V3_A);
558
- runtime.bodyYaw = SCRATCH_V3_A.y;
559
- runtime.eyePitch = 0;
560
-
561
- // Seed render-rate-look + position-interpolation state to the spawn pose.
562
- runtime.prevFixedYaw = runtime.bodyYaw;
563
- runtime.renderPrevPos.copy(bodyTransform.position);
564
- runtime.renderCurPos.copy(bodyTransform.position);
565
-
566
- // Initialize springs to standing-eye-height baseline
567
- runtime.eyeHeightSpring.settle(controller.config.body.height);
568
- runtime.fovSpring.settle(controller.config.fov.base);
569
- controller.state.eyeHeight = controller.config.body.height;
570
-
571
- // Create eye entity if one wasn't supplied
572
- if (controller.eyeEntity === -1 || !ecd.entityExists(controller.eyeEntity)) {
573
- const eye = new Entity();
574
-
575
- const eyeTransform = new Transform();
576
- const baseEyePos = SCRATCH_V3_A.copy(bodyTransform.position);
577
- baseEyePos.y += controller.config.body.height;
578
- eyeTransform.position.copy(baseEyePos);
579
-
580
- const camera = new Camera();
581
- camera.active.set(true);
582
- camera.fov.set(controller.config.fov.base);
583
- camera.clip_near = 0.05;
584
- camera.clip_far = 1000;
585
- camera.autoClip = true;
586
-
587
- eye.add(eyeTransform);
588
- eye.add(camera);
589
- eye.add(SerializationMetadata.Transient);
590
-
591
- eye.build(ecd);
592
-
593
- controller.eyeEntity = eye.id;
594
- }
595
-
596
- runtime.eyeEntity = controller.eyeEntity;
597
- }
598
-
599
- /**
600
- * @param {FirstPersonPlayerController} controller
601
- * @param {Transform} bodyTransform
602
- * @param {number} entity
603
- */
604
- unlink(controller, bodyTransform, entity) {
605
- const ecd = this.entityManager.dataset;
606
-
607
- if (controller.eyeEntity !== -1 && ecd.entityExists(controller.eyeEntity)) {
608
- ecd.removeEntity(controller.eyeEntity);
609
- controller.eyeEntity = -1;
610
- }
611
-
612
- this.runtime.delete(entity);
613
- }
614
-
615
- /**
616
- * Look up the per-entity runtime for an entity that has this
617
- * controller. Used by cross-system code (sensors system, future
618
- * ability-driven systems) to reach internal state without leaking
619
- * it onto the controller component itself.
620
- *
621
- * @param {number} entity
622
- * @returns {PerEntityRuntime|undefined} undefined if entity is not linked
623
- */
624
- getRuntime(entity) {
625
- return this.runtime.get(entity);
626
- }
627
-
628
- /**
629
- * Deterministic simulation step — L1 + L2 + L4.
630
- * @param {number} dt
631
- */
632
- fixedUpdate(dt) {
633
- const ecd = this.entityManager.dataset;
634
- if (ecd === null) return;
635
-
636
- this._currentDt = dt;
637
- ecd.traverseComponents(FirstPersonPlayerController, this._tickEntity, this);
638
- }
639
-
640
- /**
641
- * Variable-rate camera composition L3.
642
- * @param {number} dt
643
- */
644
- update(dt) {
645
- const ecd = this.entityManager.dataset;
646
- if (ecd === null) return;
647
-
648
- this._currentRenderDt = dt;
649
- ecd.traverseComponents(FirstPersonPlayerController, this._composeEye, this);
650
- }
651
-
652
- /**
653
- * @private
654
- * @param {FirstPersonPlayerController} controller
655
- * @param {number} entity
656
- */
657
- _tickEntity(controller, entity) {
658
- const ecd = this.entityManager.dataset;
659
- const runtime = this.runtime.get(entity);
660
- if (runtime === undefined) return;
661
-
662
- const dt = this._currentDt;
663
- const cfg = controller.config;
664
- const intent = controller.intent;
665
- const state = controller.state;
666
- const sig = controller.signals;
667
-
668
- const bodyTransform = ecd.getComponent(entity, Transform);
669
- if (bodyTransform === undefined) return;
670
-
671
- // Decay the mastery score's EMA. Doing this once per tick keeps the
672
- // score's time-window characteristic stable regardless of how many
673
- // evaluators fire (they each *record* a sample, the decay
674
- // independently ages all samples).
675
- controller.mastery.tick(dt);
676
-
677
- // -- L1.a: Derive the per-step turn rate ------------------------
678
- // Look (yaw/pitch) is consumed at RENDER rate in {@link _consumeLook} so
679
- // the camera tracks the mouse without 60Hz aliasing — consuming the
680
- // accumulated mouse delta once per fixed step beats against an unaligned
681
- // mouse report rate and makes a slow turn lurch (the reported snap).
682
- // fixedUpdate reads the latest yaw and derives the per-fixed-step turn
683
- // RATE for look-lean + the turn evaluators, from the yaw change since the
684
- // previous step (shortest-arc across the ±π wrap) — smooth and decoupled
685
- // from per-frame input jitter.
686
- let yawStep = runtime.bodyYaw - runtime.prevFixedYaw;
687
- if (yawStep > Math.PI) yawStep -= TWO_PI;
688
- else if (yawStep < -Math.PI) yawStep += TWO_PI;
689
- runtime.yawRateRadPerSec = yawStep / Math.max(dt, 1e-4);
690
- runtime.prevFixedYaw = runtime.bodyYaw;
691
-
692
- // Write body yaw back to the transform (pure yaw, no pitch on body)
693
- // the collision sweep + sensors use it. The camera uses runtime.bodyYaw
694
- // directly, which _consumeLook keeps current at render rate.
695
- bodyTransform.rotation.fromAxisAngle(Vector3.up, runtime.bodyYaw);
696
-
697
- // -- Shared flags. Computed BEFORE the ability tick so abilities
698
- // can read them. `isCrouchActive` is deliberately computed
699
- // AFTER the ability tick because `_resolveCrouchHeld` mutates
700
- // `runtime.prevCrouchHeld` — abilities like Slide need to see
701
- // the previous-tick value to detect a rising edge on the
702
- // crouch press.
703
- const isSprintIntent = intent.sprint && intent.move.y > 0.5 && state.grounded;
704
- const isBackwardIntent = intent.move.y < 0;
705
- runtime.sinYaw = Math.sin(runtime.bodyYaw);
706
- runtime.cosYaw = Math.cos(runtime.bodyYaw);
707
- // L2 observers read sinYaw/cosYaw as locals destructure once.
708
- const { sinYaw, cosYaw } = runtime;
709
-
710
- // Age the ledge re-grab refractory window (dt-driven). Decremented
711
- // BEFORE the ability layer so LedgeGrab.canActivate sees the current
712
- // value; it gates the auto-catch for a beat after any dismount.
713
- if (runtime.ledgeRegrabCooldown > 0) {
714
- runtime.ledgeRegrabCooldown = Math.max(0, runtime.ledgeRegrabCooldown - dt);
715
- }
716
-
717
- // -- Ability layer: at most one active ability owns motion. The
718
- // set returns true when no ability owned the tick, in which
719
- // case base L1.b-h runs below; false means an ability fully
720
- // handled this tick (it called the system's helpers for any
721
- // standard work it wanted to keep, e.g. gravity).
722
- const runBaseLocomotion = controller.abilities.tick(
723
- controller, runtime, bodyTransform, runtime.sensors, dt, this,
724
- );
725
-
726
- // The ledge-pull-up request is set and consumed WITHIN abilities.tick
727
- // (LedgeGrab release Mantle activation, same call). Clear it here as
728
- // a safety net so a pull-up that didn't activate a mantle can't leak
729
- // the flag into a later auto-mantle (which would let it vault a thin
730
- // wall it should have refused).
731
- runtime.ledgePullUpRequest = false;
732
-
733
- // Now resolve crouch (updates prevCrouchHeld) — used by base and L2.
734
- // Headroom-aware: force-keeps the player crouched under an overhang.
735
- const isCrouchActive = this._resolveCrouchHeld(controller, runtime, bodyTransform);
736
-
737
- if (runBaseLocomotion) {
738
- this._runBaseLocomotion(
739
- controller, runtime, bodyTransform, dt,
740
- isCrouchActive, isSprintIntent, isBackwardIntent,
741
- );
742
- }
743
-
744
- // (everything below this line runs every tick L2 observers don't
745
- // care who owned motion)
746
-
747
- // -- L2.a: speed / moveMode ------------------------------------
748
- // -- L2.a: speed / moveMode ------------------------------------
749
- const horizSpeed = Math.hypot(runtime.velocityX, runtime.velocityZ);
750
- runtime.horizSpeed = horizSpeed;
751
- state.speed = horizSpeed;
752
- state.speedNormalized = clamp(horizSpeed / Math.max(cfg.motion.sprintSpeed, 1e-3), 0, 1);
753
-
754
- // Backwardness: 0 = moving forward (or sideways), 1 = moving directly
755
- // backward at the back-pedal speed ceiling. Derived from the actual
756
- // velocity (not the intent) so external knockback or stuck states
757
- // also register as "moving backward" and the gait wobble reflects it.
758
- //
759
- // Reference speed is the *achievable* backward max — walkSpeed ×
760
- // backwardSpeedFactor — NOT the sprint speed. Backward can never
761
- // reach sprint, so normalizing against sprint would cap backwardness
762
- // at ~0.3 and the wobble multipliers below would barely apply.
763
- const screenFwdVel = runtime.velocityX * sinYaw + runtime.velocityZ * cosYaw;
764
- const maxBackwardSpeed = Math.max(cfg.motion.walkSpeed * cfg.motion.backwardSpeedFactor, 1e-3);
765
- runtime.backwardness = clamp(-screenFwdVel / maxBackwardSpeed, 0, 1);
766
-
767
- // Locomotion mode is the *intent-driven* horizontal mode. Airborne
768
- // state is tracked separately on pose.actionStatethey're
769
- // orthogonal facets (you can be Sprint+Airborne after a jump).
770
- const prevLocomotionMode = state.locomotionMode;
771
- if (isCrouchActive) {
772
- state.locomotionMode = FirstPersonLocomotionMode.Crouch;
773
- } else if (isSprintIntent && horizSpeed > 0.1) {
774
- state.locomotionMode = FirstPersonLocomotionMode.Sprint;
775
- } else if (horizSpeed > 0.1) {
776
- state.locomotionMode = FirstPersonLocomotionMode.Walk;
777
- } else {
778
- state.locomotionMode = FirstPersonLocomotionMode.Idle;
779
- }
780
-
781
- if (state.locomotionMode === FirstPersonLocomotionMode.Sprint
782
- && prevLocomotionMode !== FirstPersonLocomotionMode.Sprint) {
783
- sig.onSprintStart.send0();
784
- } else if (prevLocomotionMode === FirstPersonLocomotionMode.Sprint
785
- && state.locomotionMode !== FirstPersonLocomotionMode.Sprint) {
786
- sig.onSprintStop.send0();
787
- }
788
-
789
- // -- L2.b: Exertion --------------------------------------------
790
- // Heavier bodies tire faster — sprint rise scales with massRatios.exertionRiseScale.
791
- const exertionRise = isSprintIntent
792
- ? cfg.exertion.sprintRiseRate * runtime.massRatios.exertionRiseScale
793
- : 0;
794
- const exertionFall = exertionRise > 0 ? 0 : cfg.exertion.idleDecayRate;
795
- state.exertion = clamp(state.exertion + (exertionRise - exertionFall) * dt, 0, 1);
796
-
797
- // -- L2.c: Breath ----------------------------------------------
798
- // breathRate and breathAmplitude lag exertion through separate
799
- // exponential decays. Rate hangs around longer than amplitude.
800
- const metabolicRate = lerp(cfg.breath.rateRestHz, cfg.breath.rateMaxHz, state.exertion);
801
- const targetAmp = lerp(cfg.breath.amplitudeRestM, cfg.breath.amplitudeMaxM, state.exertion);
802
-
803
- // Locomotor-respiratory coupling — see math/computeLRCBreathRate.
804
- // The pure function is unit-tested; this site just provides inputs.
805
- //
806
- // Gait is gated on a "feet strike the ground" posture (Stand /
807
- // Crouch). Prone (slide) and Hang (ledge-grab) have no stride —
808
- // the body's feet are not making contact in a walking pattern,
809
- // so stride frequency drops to zero and downstream gait
810
- // signals (footsteps, bob intensity) go quiet.
811
- const feetStriking = state.posture === FirstPersonPosture.Stand
812
- || state.posture === FirstPersonPosture.Crouch;
813
- const strideFreqHz = feetStriking && state.grounded && horizSpeed > cfg.bob.minStepSpeed
814
- ? cfg.bob.stepFreqAtWalk * Math.pow(
815
- Math.max(horizSpeed, 1e-3) / Math.max(cfg.motion.walkSpeed, 1e-3),
816
- cfg.bob.stepFreqExp,
817
- )
818
- : 0;
819
- const targetRate = computeLRCBreathRate(
820
- metabolicRate,
821
- strideFreqHz,
822
- state.exertion,
823
- cfg.breath.locomotorCouplingMax,
824
- cfg.breath.couplingMinStrideFreqHz,
825
- );
826
- state.breathRateHz = exponentialApproach(state.breathRateHz, targetRate, cfg.exertion.rateDecayHalfLife, dt);
827
- state.breathAmplitudeM = exponentialApproach(state.breathAmplitudeM, targetAmp, cfg.exertion.ampDecayHalfLife, dt);
828
-
829
- runtime.prevBreathPhase = state.breathPhase;
830
- state.breathPhase += state.breathRateHz * dt;
831
- state.breathPhase -= Math.floor(state.breathPhase); // wrap [0,1)
832
-
833
- // Breath edge detection inhale at 0.25, exhale at 0.75
834
- if (phaseCrossed(runtime.prevBreathPhase, state.breathPhase, 0.25)) {
835
- sig.onBreathIn.send1({ amplitude: state.breathAmplitudeM, rateHz: state.breathRateHz });
836
- }
837
- if (phaseCrossed(runtime.prevBreathPhase, state.breathPhase, 0.75)) {
838
- sig.onBreathOut.send1({ amplitude: state.breathAmplitudeM, rateHz: state.breathRateHz });
839
- }
840
-
841
- // -- L2.d: Stride ----------------------------------------------
842
- // strideFreqHz computed above in the breath block; reused here.
843
- runtime.prevStridePhase = state.stridePhase;
844
- if (strideFreqHz > 0) {
845
- // 1 full stride cycle = 2 footfalls; phase advances at freq/2 of cycle
846
- state.stridePhase += (strideFreqHz * 0.5) * dt;
847
- state.stridePhase -= Math.floor(state.stridePhase);
848
- }
849
- // Footstep on phase wraparound past 0 (R) or past 0.5 (L). Same
850
- // posture gate as stride advance — feet must be striking.
851
- if (feetStriking && state.grounded && horizSpeed > cfg.bob.minStepSpeed) {
852
- const fireFootstep = () => {
853
- state.stepCount++;
854
- const side = runtime.nextFootSide;
855
- runtime.nextFootSide = side === "R" ? "L" : "R";
856
- // The foot that just fired is now the one bearing weight
857
- // through the upcoming half-stride. Drives lateral-bob sign.
858
- runtime.standingFoot = side;
859
- sig.onFootStep.send1({ side, speed: horizSpeed, surfaceTag: state.surfaceTag });
860
- // Kick the vertical impact spring DOWNWARD. The kick magnitude
861
- // is the per-step desired peak dip × impactKickMultiplier; the
862
- // multiplier is empirical (depends on impact spring params) so
863
- // that "verticalAmpAtWalk" still corresponds approximately to
864
- // the visible peak dip depth. Scaled by bobIntensity so a
865
- // mid-deceleration footstep doesn't deliver a full-strength
866
- // impulse.
867
- const massBoost = (cfg.body.mass - 80) * cfg.bob.ampMassScale;
868
- const ampVMult = 1 + (cfg.bob.backwardVerticalAmpFactor - 1) * runtime.backwardness;
869
- const peakDip = (cfg.bob.verticalAmpAtWalk + massBoost) * runtime.bobIntensitySpring.value * ampVMult;
870
- runtime.verticalImpactSpring.kick(-peakDip * cfg.bob.impactKickMultiplier);
871
- };
872
- if (phaseCrossed(runtime.prevStridePhase, state.stridePhase, 0)) {
873
- fireFootstep();
874
- }
875
- if (phaseCrossed(runtime.prevStridePhase, state.stridePhase, 0.5)) {
876
- fireFootstep();
877
- }
878
- }
879
-
880
- // -- L2.d.bob-intensity & impact -------------------------------
881
- // Smoothed bob amplitude envelope: when the player starts/stops
882
- // moving the visible bob fades in/out rather than cutting on/off.
883
- // Target = the "natural" amp scale (max of speed and backwardness)
884
- // while grounded, zero while airborne so the bob disappears mid-jump.
885
- const naturalBobIntensity = Math.max(state.speedNormalized, runtime.backwardness);
886
- // Bob fades to zero whenever feet aren't striking (airborne, or
887
- // Prone/Hang posture). The verticalImpactSpring (separate
888
- // channel) still carries any entry/landing kicks through to the
889
- // camera, but no recurring step bob.
890
- const targetBobIntensity = (state.grounded && feetStriking) ? naturalBobIntensity : 0;
891
- runtime.bobIntensitySpring.stepTo(targetBobIntensity, cfg.bob.intensityHalfLife, 1.0, dt);
892
-
893
- // Vertical impact spring damped decay toward 0, with the under-
894
- // damped overshoot that produces the recovery + leg-push curve.
895
- runtime.verticalImpactSpring.stepTo(0, cfg.bob.impactSpringHalfLife, cfg.bob.impactSpringZeta, dt);
896
-
897
- // Sprint posture — head pitches forward as commitment to sprint
898
- // builds. Driven by "sprintness" — how much of the gap between
899
- // walk and sprint speed the player is *currently* in (0..1). The
900
- // pitch target is multiplied by sprintness, then critically damped.
901
- // Only applies while grounded — pitching into airborne motion looks weird.
902
- const sprintness = clamp(
903
- (state.speed - cfg.motion.walkSpeed)
904
- / Math.max(cfg.motion.sprintSpeed - cfg.motion.walkSpeed, 1e-3),
905
- 0, 1,
906
- );
907
- const targetSprintPitch = state.grounded
908
- ? cfg.posture.sprintForwardPitchDeg * DEG_TO_RAD * sprintness
909
- : 0;
910
- runtime.sprintPostureSpring.stepTo(
911
- targetSprintPitch,
912
- cfg.posture.sprintForwardPitchHalfLife,
913
- 1.0, dt,
914
- );
915
- runtime.sprintness = sprintness;
916
-
917
- // Head droopexertion drives a subtle additional forward pitch.
918
- // Combines with sprintPostureSpring (sprint = head down to commit)
919
- // so a fatigued sprinter has BOTH effects layered.
920
- const targetDroopRad = cfg.exertion.headDroopAtMaxDeg * DEG_TO_RAD * state.exertion;
921
- runtime.headDroopSpring.stepTo(targetDroopRad, cfg.exertion.headDroopHalfLife, 1.0, dt);
922
-
923
- // -- L2.e: Posture eye height --------------------------------
924
- // Posture is set by whichever layer owned motion this tick: base
925
- // writes Stand / Crouch from isCrouchActive (see end of
926
- // _runBaseLocomotion); active abilities write Prone (Slide) or
927
- // Hang (LedgeGrab) in their tick. Mapping is one switch — adding
928
- // a new posture is one enum value + one case.
929
- let targetEyeH;
930
- switch (state.posture) {
931
- case FirstPersonPosture.Prone: targetEyeH = cfg.body.proneHeight; break;
932
- case FirstPersonPosture.Crouch: targetEyeH = cfg.body.crouchHeight; break;
933
- case FirstPersonPosture.Hang: targetEyeH = cfg.body.height; break;
934
- case FirstPersonPosture.Stand:
935
- default: targetEyeH = cfg.body.height; break;
936
- }
937
- const crouchHalfLife = cfg.crouch.transitionTime / 4; // halfLife is ~quarter of full transition
938
- runtime.eyeHeightSpring.stepTo(targetEyeH, crouchHalfLife, 1.0, dt);
939
- state.eyeHeight = runtime.eyeHeightSpring.value;
940
-
941
- if (isCrouchActive !== state.crouchActive) {
942
- state.crouchActive = isCrouchActive;
943
- if (isCrouchActive) {
944
- sig.onCrouchEnter.send0();
945
- // Impulse: dropping into a crouch grips the knees. Small
946
- // bump — we don't want crouch-spamming to instantly tire.
947
- state.exertion = clamp(
948
- state.exertion + cfg.exertion.crouchEnterRise * runtime.massRatios.exertionRiseScale,
949
- 0, 1,
950
- );
951
- } else {
952
- sig.onCrouchExit.send0();
953
- }
954
- }
955
-
956
- // -- L2.f: Lean spring camera roll ---------------------------
957
- // The TARGET for this tick was written by whichever layer owned
958
- // motion: base writes the lat-accel + look-lean derived value at
959
- // the end of _runBaseLocomotion; abilities override (WallRun
960
- // tilts toward the wall; Slide / LedgeGrab / Mantle force zero).
961
- // L2.f is now a flat spring-step + commit no branching, no
962
- // null sentinel.
963
- runtime.prevVelocityX = runtime.velocityX;
964
- runtime.prevVelocityZ = runtime.velocityZ;
965
- runtime.leanSpring.stepTo(runtime.leanTargetRad, cfg.lean.spring.halfLife, cfg.lean.spring.zeta, dt);
966
- state.leanRollRad = runtime.leanSpring.value;
967
-
968
- // -- L2.g: Land spring decay (drives the landing recovery dip) -
969
- // Target is 0; under-damped (cfg zeta < 1) so it rings.
970
- runtime.landSpring.stepTo(0, cfg.landing.recovery.spring.halfLife, cfg.landing.recovery.spring.zeta, dt);
971
-
972
- // -- L2.h: Publish pose channels --------------------------------
973
- this._publishPose(controller, runtime, bodyTransform);
974
-
975
- // -- L2.i: Sync collider shape to posture -----------------------
976
- // All posture-writers (base locomotion + any active ability)
977
- // have run for this tick. Swap the collider's shape to the
978
- // pre-built capsule matching the final posture so downstream
979
- // physics queries (move-and-slide cast, sensors, overlap from
980
- // outside) see the right volume. No-op when posture is
981
- // unchanged.
982
- this._syncColliderShape(runtime, state.posture);
983
- }
984
-
985
- /**
986
- * @private
987
- * @param {FirstPersonPlayerController} controller
988
- * @param {PerEntityRuntime} runtime
989
- * @returns {boolean}
990
- */
991
- /**
992
- * Swap {@link Collider.shape} to the pre-built capsule that matches
993
- * the player's current posture. Cheap just a reference swap when
994
- * the posture changed, no-op otherwise. The pre-built shapes live
995
- * on the runtime (see {@link PerEntityRuntime.colliderShapeStand}
996
- * etc.) so this method allocates nothing per tick.
997
- *
998
- * Hang posture reuses Stand: the player's body is full-extent,
999
- * hanging below the ledge the rig handles the arms-up animation,
1000
- * but the collision volume is unchanged. If a game ever wants a
1001
- * narrower hang silhouette (e.g. wedging into a chimney) it can
1002
- * add a `colliderShapeHang` and route here.
1003
- *
1004
- * @private
1005
- */
1006
- _syncColliderShape(runtime, posture) {
1007
- if (posture === runtime.lastPosture) return;
1008
- let next;
1009
- if (posture === FirstPersonPosture.Crouch) {
1010
- next = runtime.colliderShapeCrouch;
1011
- } else if (posture === FirstPersonPosture.Prone) {
1012
- next = runtime.colliderShapeProne;
1013
- } else {
1014
- // Stand and Hang share the full-extent capsule.
1015
- next = runtime.colliderShapeStand;
1016
- }
1017
- runtime.collider.shape = next;
1018
- runtime.lastPosture = posture;
1019
- }
1020
-
1021
- _resolveCrouchHeld(controller, runtime, bodyTransform) {
1022
- const cfg = controller.config;
1023
- const intent = controller.intent;
1024
- const state = controller.state;
1025
-
1026
- let wantsCrouch;
1027
- if (cfg.crouch.mode === "toggle") {
1028
- // Edge: rising press flips the latch
1029
- if (intent.crouch && !runtime.prevCrouchHeld) {
1030
- runtime.crouchLatched = !runtime.crouchLatched;
1031
- }
1032
- runtime.prevCrouchHeld = intent.crouch;
1033
- wantsCrouch = runtime.crouchLatched;
1034
- } else {
1035
- // "hold" mode
1036
- runtime.prevCrouchHeld = intent.crouch;
1037
- wantsCrouch = intent.crouch;
1038
- }
1039
-
1040
- // Headroom override — block UN-crouching under an overhang. A player who
1041
- // is ALREADY low (crouched or prone) and wants to stand is force-kept
1042
- // crouched while the Stand collider won't fit: letting it grow into the
1043
- // overhang would have the next tick's depenetration shove the player out
1044
- // the bottom, through the floor. `crouchBlocked` records this for HUD /
1045
- // camera.
1046
- //
1047
- // Crucially this fires ONLY when already low. A STANDING player who walks
1048
- // under a low ceiling is NEVER auto-pushed down into a crouch — their
1049
- // full-height capsule simply can't fit, so the mover blocks their forward
1050
- // motion instead. "If I can't clear it, I don't move forward." Dropping
1051
- // posture is the player's choice (press crouch), not the ceiling's.
1052
- const alreadyLow = runtime.lastPosture === FirstPersonPosture.Crouch
1053
- || runtime.lastPosture === FirstPersonPosture.Prone;
1054
- if (!wantsCrouch && alreadyLow) {
1055
- const growDelta = runtime.standTop - runtime.crouchTop;
1056
- if (!this._hasHeadroomToGrow(runtime, bodyTransform, runtime.colliderShapeCrouch, growDelta)) {
1057
- state.crouchBlocked = true;
1058
- return true;
1059
- }
1060
- }
1061
- state.crouchBlocked = false;
1062
- return wantsCrouch;
1063
- }
1064
-
1065
- /**
1066
- * Headroom probe for a posture change that GROWS the collider.
1067
- * Sweeps `fromShape` (the shorter posture's capsule) straight up by
1068
- * `growDelta` (the height the collider would gain). A hit means an
1069
- * overhang occupies the space the taller posture needs, so the grow
1070
- * is blocked. Returns true (clear) when no physics is wired or the
1071
- * grow is non-positive.
1072
- *
1073
- * Why an up-cast rather than an overlap-test of the taller shape:
1074
- * overlap would false-positive on a wall *beside* the player. The
1075
- * up-cast sweeps parallel to (and, per the mover's skin clearance,
1076
- * clear of) lateral walls so they aren't hit; the floor is below
1077
- * the rising probe; the player's own body is filtered out. Only
1078
- * genuine overhead geometry blocks the grow. Relies on the player
1079
- * resting at `floor+skin` (the mover's stick-to-ground), so the
1080
- * probe's bottom starts clear of the floor.
1081
- *
1082
- * @private
1083
- */
1084
- _hasHeadroomToGrow(runtime, bodyTransform, fromShape, growDelta) {
1085
- if (this.physicsSystem === null || growDelta <= 1e-4) return true;
1086
- const ownCollider = runtime.collider;
1087
- const filter = (_e, c) => c !== ownCollider;
1088
- const ray = this._postureRay;
1089
- ray.setOrigin(bodyTransform.position.x, bodyTransform.position.y, bodyTransform.position.z);
1090
- ray.setDirection(0, 1, 0);
1091
- ray.tMax = growDelta;
1092
- return !this.physicsSystem.shapeCast(ray, fromShape, bodyTransform.rotation, this._postureHit, filter);
1093
- }
1094
-
1095
- /**
1096
- * Jump finite-state-machine: button-edge detection, buffer + coyote
1097
- * grace, anticipation timer, impulse on completion. Variable-height
1098
- * cut is captured here as a `state.isVariableJumpCut` flag that the
1099
- * gravity step in `_integrateVerticalAndResolveGround` consumes.
1100
- *
1101
- * @private
1102
- * @param {FirstPersonPlayerController} controller
1103
- * @param {PerEntityRuntime} runtime
1104
- * @param {Transform} bodyTransform
1105
- * @param {number} dt
1106
- */
1107
- _advanceJumpFsm(controller, runtime, bodyTransform, dt) {
1108
- const cfg = controller.config;
1109
- const intent = controller.intent;
1110
- const state = controller.state;
1111
- const sig = controller.signals;
1112
-
1113
- const jumpPressedEdge = intent.jump && !runtime.prevJumpHeld;
1114
- const jumpReleasedEdge = !intent.jump && runtime.prevJumpHeld;
1115
- runtime.prevJumpHeld = intent.jump;
1116
-
1117
- if (jumpPressedEdge) {
1118
- state.jumpBufferRemaining = cfg.jump.bufferTime;
1119
- }
1120
- state.jumpBufferRemaining = Math.max(0, state.jumpBufferRemaining - dt);
1121
-
1122
- const canJumpNow =
1123
- (state.grounded || state.timeSinceGrounded < cfg.jump.coyoteTime)
1124
- && state.jumpBufferRemaining > 0
1125
- && !state.inJumpAnticipation
1126
- && !runtime.midJump;
1127
-
1128
- if (canJumpNow) {
1129
- // Begin anticipation — squash; impulse fires after duration elapses
1130
- state.inJumpAnticipation = true;
1131
- runtime.anticipationRemaining = cfg.jump.anticipation.duration;
1132
- state.jumpBufferRemaining = 0; // claimed
1133
- }
1134
-
1135
- // Variable-height cut: only valid during ascent, post-launch.
1136
- if (jumpReleasedEdge && runtime.midJump && runtime.velocityY > 0) {
1137
- state.isVariableJumpCut = true;
1138
- }
1139
-
1140
- // Anticipation timer; impulse on completion.
1141
- //
1142
- // Anticipation completes regardless of grounded state. The reason
1143
- // we DON'T cancel on `!grounded`: the canonical coyote-jump path
1144
- // depends on it. The player walks off a ledge (grounded → false),
1145
- // presses jump within the coyote window, canJumpNow accepts on
1146
- // the coyote branch and starts anticipation. If we cancelled
1147
- // anticipation here on !grounded, the impulse would never fire
1148
- // and "coyote time" would be silently dead — the FSM's own next-
1149
- // statement contradicting the canJumpNow gate three lines up.
1150
- //
1151
- // The same logic handles the rug-pull case (player on a moving
1152
- // platform that slides out mid-anticipation): the player
1153
- // committed to the jump, they get the jump. A future
1154
- // knockback / stagger system can explicitly clear
1155
- // inJumpAnticipation if it wants to override that commitment.
1156
- if (state.inJumpAnticipation) {
1157
- runtime.anticipationRemaining -= dt;
1158
- if (runtime.anticipationRemaining <= 0) {
1159
- // Mastery: gather a multiplier from all evaluators
1160
- // registered for JumpImpulse. Default (no evaluators)
1161
- // returns 1.0 unchanged behaviour.
1162
- const masteryMul = controller.mastery.evaluate(
1163
- DecisionPoint.JumpImpulse, controller, runtime,
1164
- );
1165
- runtime.velocityY = runtime.jumpInitialVy * masteryMul;
1166
- runtime.midJump = true;
1167
- runtime.apexFired = false;
1168
- runtime.peakAltitude = bodyTransform.position.y;
1169
- state.inJumpAnticipation = false;
1170
- state.isVariableJumpCut = false;
1171
- state.isAscending = true;
1172
- state.exertion = clamp(
1173
- state.exertion + cfg.exertion.jumpRise * runtime.massRatios.exertionRiseScale,
1174
- 0, 1,
1175
- );
1176
-
1177
- sig.onJumpStart.send1({ peakHeight: cfg.jump.peakHeight });
1178
- // NOTE: onLeaveGround is fired by the motor (_onLeaveGround)
1179
- // when the move actually breaks ground contact this same tick —
1180
- // with reason "jump" since midJump is now true. Firing it here
1181
- // too double-sent the signal on every grounded jump (and a
1182
- // spurious second "jump" leave on a coyote jump, which already
1183
- // left ground on the walk-off). Let the motor own it.
1184
- }
1185
- }
1186
- }
1187
-
1188
- /**
1189
- * The vertical + collision phase of one fixed step: apply gravity
1190
- * (the motor), resolve the move against the world, then detect the
1191
- * jump apex. Collision resolution is the {@link KinematicMover} when
1192
- * a physics layer is present; otherwise a lightweight flat-ground
1193
- * integrator for headless / unit-test scenes with no collidable
1194
- * geometry. Land / leave-ground events flow identically from either.
1195
- *
1196
- * @private
1197
- * @param {FirstPersonPlayerController} controller
1198
- * @param {PerEntityRuntime} runtime
1199
- * @param {Transform} bodyTransform
1200
- * @param {number} dt
1201
- */
1202
- _integrateVerticalAndResolveGround(controller, runtime, bodyTransform, dt) {
1203
- this._applyGravity(controller, runtime, dt);
1204
- this._resolveMotion(controller, runtime, bodyTransform, dt);
1205
- this._detectJumpApex(controller, runtime, bodyTransform);
1206
- }
1207
-
1208
- /**
1209
- * Resolve the current `runtime.velocity*` against the world — the
1210
- * physics-backed {@link KinematicMover} (recover + sweep-and-slide +
1211
- * ground-categorize) when a PhysicsSystem is present, else the no-physics
1212
- * flat-ground fallback. Gravity is NOT applied here: the standard path
1213
- * applies it in {@link _applyGravity} just before, and abilities with a
1214
- * non-standard vertical model (WallRun's reduced gravity) apply their own
1215
- * and then call this directly so motion routes through one motor for
1216
- * everyone (sweep-and-slide, anti-tunnel, land/leave events) rather than
1217
- * a bespoke `position._add` + hand-rolled ground-catch.
1218
- * @private
1219
- */
1220
- _resolveMotion(controller, runtime, bodyTransform, dt) {
1221
- if (this.physicsSystem !== null) {
1222
- this._moveViaMover(controller, runtime, bodyTransform, dt);
1223
- } else {
1224
- this._moveFlatGround(controller, runtime, bodyTransform, dt);
1225
- }
1226
- }
1227
-
1228
- /**
1229
- * Motor: integrate gravity into `velocityY` with the fall / variable-
1230
- * cut multipliers. The mover never invents motion, so gravity lives
1231
- * here (callable by abilities that want standard gravity too).
1232
- * @private
1233
- */
1234
- _applyGravity(controller, runtime, dt) {
1235
- const cfg = controller.config;
1236
- const state = controller.state;
1237
- let gMag = runtime.gravity;
1238
- if (runtime.velocityY <= 0) {
1239
- gMag *= cfg.jump.fallGravityMult;
1240
- state.isAscending = false;
1241
- } else if (state.isVariableJumpCut) {
1242
- gMag *= cfg.jump.cutGravityMult;
1243
- }
1244
- runtime.velocityY -= gMag * dt;
1245
- }
1246
-
1247
- /**
1248
- * Resolve the move against the world via {@link KinematicMover}
1249
- * (recover + unified sweep-and-slide + ground-categorize), then map
1250
- * the result back to controller state and land / leave events.
1251
- * @private
1252
- */
1253
- _moveViaMover(controller, runtime, bodyTransform, dt) {
1254
- const state = controller.state;
1255
-
1256
- if (this._mover === null) {
1257
- this._mover = new KinematicMover(this.physicsSystem, this.entityManager.dataset);
1258
- }
1259
-
1260
- // Fall speed captured BEFORE the move — the mover clips velocityY
1261
- // to ~0 on contact, so the land event needs the pre-move value.
1262
- const impactVy = -runtime.velocityY;
1263
-
1264
- const v = this._moverVelocity;
1265
- v.set(runtime.velocityX, runtime.velocityY, runtime.velocityZ);
1266
-
1267
- const ownCollider = runtime.collider;
1268
- const filter = (_e, c) => c !== ownCollider;
1269
-
1270
- const result = this._mover.move(
1271
- bodyTransform.position,
1272
- bodyTransform.rotation,
1273
- runtime.collider.shape,
1274
- v, dt, filter,
1275
- );
1276
-
1277
- // Read back corrected horizontal velocity. Vertical follows the
1278
- // control policy: zero on ground (the slope-climb the mover
1279
- // produced is a position effect, not accumulated speed), keep the
1280
- // mover-clipped value in the air (captures ceiling bonks).
1281
- runtime.velocityX = v.x;
1282
- runtime.velocityZ = v.z;
1283
- runtime.velocityY = result.grounded ? 0 : v.y;
1284
-
1285
- const wasGrounded = state.grounded;
1286
- if (result.grounded) {
1287
- if (!wasGrounded) this._onLand(controller, runtime, impactVy);
1288
- state.grounded = true;
1289
- state.groundNormal.copy(result.groundNormal);
1290
- state.verticalSpeed = 0;
1291
- state.airborneTime = 0;
1292
- state.timeSinceGrounded = 0;
1293
- } else {
1294
- if (wasGrounded) this._onLeaveGround(controller, runtime, bodyTransform);
1295
- state.grounded = false;
1296
- state.verticalSpeed = runtime.velocityY;
1297
- state.airborneTime += dt;
1298
- state.timeSinceGrounded += dt;
1299
- state.fallDistance += Math.max(0, -runtime.velocityY * dt);
1300
- }
1301
- }
1302
-
1303
- /**
1304
- * No-physics fallback: integrate the velocity directly (there's no
1305
- * collidable geometry to sweep against) and resolve the floor from
1306
- * the built-in flat ground and/or the host `groundResolver`. For
1307
- * headless scenes and control-layer unit tests. With both ground
1308
- * sources off, an external physics layer is expected to own
1309
- * `state.grounded`; we only advance timers.
1310
- * @private
1311
- */
1312
- _moveFlatGround(controller, runtime, bodyTransform, dt) {
1313
- const state = controller.state;
1314
-
1315
- bodyTransform.position._add(
1316
- runtime.velocityX * dt, runtime.velocityY * dt, runtime.velocityZ * dt,
1317
- );
1318
-
1319
- if (this.useBuiltInFlatGround || this.groundResolver !== null) {
1320
- // Effective ground = max(built-in flat ground, resolver).
1321
- let testY = this.useBuiltInFlatGround ? this.groundY : Number.NEGATIVE_INFINITY;
1322
- if (this.groundResolver !== null) {
1323
- const resolved = this.groundResolver(
1324
- bodyTransform.position.x, bodyTransform.position.y, bodyTransform.position.z,
1325
- );
1326
- if (resolved !== null && resolved > testY) testY = resolved;
1327
- }
1328
- const haveGround = testY !== Number.NEGATIVE_INFINITY;
1329
- if (haveGround && bodyTransform.position.y <= testY) {
1330
- bodyTransform.position.setY(testY);
1331
- if (!state.grounded) this._onLand(controller, runtime, -runtime.velocityY);
1332
- state.grounded = true;
1333
- state.groundNormal.set(0, 1, 0);
1334
- state.verticalSpeed = 0;
1335
- runtime.velocityY = 0;
1336
- state.airborneTime = 0;
1337
- state.timeSinceGrounded = 0;
1338
- } else {
1339
- if (state.grounded) this._onLeaveGround(controller, runtime, bodyTransform);
1340
- state.grounded = false;
1341
- state.verticalSpeed = runtime.velocityY;
1342
- state.airborneTime += dt;
1343
- state.timeSinceGrounded += dt;
1344
- state.fallDistance += Math.max(0, -runtime.velocityY * dt);
1345
- }
1346
- } else {
1347
- // External physics maintains state.grounded; just track timers.
1348
- if (state.grounded) {
1349
- state.timeSinceGrounded = 0;
1350
- state.airborneTime = 0;
1351
- } else {
1352
- state.timeSinceGrounded += dt;
1353
- state.airborneTime += dt;
1354
- }
1355
- }
1356
- }
1357
-
1358
- /**
1359
- * Landing transition: dip / impact springs, exertion impulse, flag
1360
- * resets, and the `onLand` signal (fired LAST so handlers see fully-
1361
- * reacted state). Shared by both move paths. `impactVy` is the
1362
- * downward speed at touchdown (positive).
1363
- * @private
1364
- */
1365
- _onLand(controller, runtime, impactVy) {
1366
- const cfg = controller.config;
1367
- const state = controller.state;
1368
- const kind = impactVy >= cfg.landing.hardThreshold ? "hard" : "soft";
1369
-
1370
- const massScaledDip = impactVy * cfg.landing.recovery.dipPerVy
1371
- * runtime.massRatios.landingDipScale;
1372
- const dip = clamp(massScaledDip, 0, cfg.landing.recovery.dipMax);
1373
- runtime.landSpring.settle(-dip);
1374
-
1375
- const landImpulse = clamp(
1376
- impactVy * cfg.exertion.landImpulsePerVy * runtime.massRatios.exertionRiseScale,
1377
- 0, cfg.exertion.landImpulseMax,
1378
- );
1379
- state.exertion = clamp(state.exertion + landImpulse, 0, 1);
1380
-
1381
- runtime.midJump = false;
1382
- state.isAscending = false;
1383
- state.isVariableJumpCut = false;
1384
- state.fallDistance = 0;
1385
-
1386
- controller.signals.onLand.send1({ verticalSpeed: impactVy, kind });
1387
- }
1388
-
1389
- /**
1390
- * Leave-ground transition: `onLeaveGround` signal + takeoff bookkeeping.
1391
- * Shared by both move paths.
1392
- * @private
1393
- */
1394
- _onLeaveGround(controller, runtime, bodyTransform) {
1395
- controller.signals.onLeaveGround.send1({ reason: runtime.midJump ? "jump" : "fall" });
1396
- runtime.takeoffVy = runtime.velocityY;
1397
- runtime.peakAltitude = bodyTransform.position.y;
1398
- }
1399
-
1400
- /**
1401
- * Fire `onJumpApex` once, when an in-progress jump stops rising.
1402
- * @private
1403
- */
1404
- _detectJumpApex(controller, runtime, bodyTransform) {
1405
- if (runtime.midJump && !runtime.apexFired) {
1406
- if (bodyTransform.position.y > runtime.peakAltitude) {
1407
- runtime.peakAltitude = bodyTransform.position.y;
1408
- } else if (runtime.velocityY <= 0) {
1409
- controller.signals.onJumpApex.send0();
1410
- runtime.apexFired = true;
1411
- }
1412
- }
1413
- }
1414
-
1415
- /**
1416
- * Run the base (no-ability) L1 locomotion phases: speed selection,
1417
- * desired-velocity computation, accel/decel, jump FSM, gravity, body
1418
- * integration, ground resolution. Only invoked when no ability owns
1419
- * the tick (see {@link AbilitySet.tick}).
1420
- *
1421
- * @private
1422
- * @param {FirstPersonPlayerController} controller
1423
- * @param {PerEntityRuntime} runtime
1424
- * @param {Transform} bodyTransform
1425
- * @param {number} dt
1426
- * @param {boolean} isCrouchActive
1427
- * @param {boolean} isSprintIntent
1428
- * @param {boolean} isBackwardIntent
1429
- */
1430
- _runBaseLocomotion(controller, runtime, bodyTransform, dt,
1431
- isCrouchActive, isSprintIntent, isBackwardIntent) {
1432
- const cfg = controller.config;
1433
- const intent = controller.intent;
1434
- const state = controller.state;
1435
-
1436
- // -- L1.b: Speed selection ------------------------------------
1437
- let targetSpeed;
1438
- if (isCrouchActive) {
1439
- targetSpeed = cfg.motion.crouchSpeed;
1440
- } else if (isSprintIntent) {
1441
- targetSpeed = cfg.motion.sprintSpeed;
1442
- } else {
1443
- targetSpeed = cfg.motion.walkSpeed;
1444
- }
1445
- if (isBackwardIntent) {
1446
- targetSpeed *= cfg.motion.backwardSpeedFactor;
1447
- }
1448
-
1449
- // Airborne momentum floor — preserve whatever horizontal speed
1450
- // the player carried into the jump. Without this, a sprint
1451
- // jump (9 m/s) decays toward walkSpeed (4.5 m/s) at
1452
- // airAccel = 14 m/s², losing all sprint momentum in ~0.32 s —
1453
- // well before the apex of a `peakHeight = 1.8 m` jump arc. The
1454
- // air-control band (Mirror's Edge, Titanfall, modern CoD) and
1455
- // the long-jump biomechanics literature both say the same
1456
- // thing: there's no thrust source in flight, so horizontal
1457
- // velocity is conserved across the arc and air "control" is
1458
- // for steering (direction) — not for changing speed magnitude.
1459
- // Raising the target to the current speed makes `stepTowards`
1460
- // a no-op when the player keeps pressing forward, while
1461
- // releasing the stick still lets `airAccel` decelerate to
1462
- // `walkSpeed` (the user CAN bleed off speed, just not have it
1463
- // bled off for them).
1464
- if (!state.grounded) {
1465
- const horizSpeed = Math.hypot(runtime.velocityX, runtime.velocityZ);
1466
- if (horizSpeed > targetSpeed) targetSpeed = horizSpeed;
1467
- }
1468
-
1469
- // -- L1.c: Move intent → desired horizontal velocity ----------
1470
- // screen_forward(θ) = ( sin θ, 0, cos θ )
1471
- // screen_right (θ) = (-cos θ, 0, sin θ )
1472
- const { sinYaw, cosYaw } = runtime;
1473
- const mvX = intent.move.x;
1474
- const mvY = intent.move.y;
1475
- const mvMag = Math.hypot(mvX, mvY);
1476
- const nmvX = mvMag > 1 ? mvX / mvMag : mvX;
1477
- const nmvY = mvMag > 1 ? mvY / mvMag : mvY;
1478
- const desiredVx = sinYaw * nmvY + -cosYaw * nmvX;
1479
- const desiredVz = cosYaw * nmvY + sinYaw * nmvX;
1480
- const desiredHorizontalVx = desiredVx * targetSpeed;
1481
- const desiredHorizontalVz = desiredVz * targetSpeed;
1482
-
1483
- // -- L1.d: Accel/decel toward desired velocity ----------------
1484
- //
1485
- // Three regimes air control, grounded decel-to-stop, grounded
1486
- // accel-to-target each with its own model:
1487
- //
1488
- // • Air control: constant-rate `stepTowards`. No ground
1489
- // reaction force in flight; air control is a steering
1490
- // budget, not a thrust curve. Constant accel matches the
1491
- // player mental model of "fixed mid-air authority".
1492
- //
1493
- // Grounded decel (no intent): constant-rate `stepTowards`
1494
- // toward zero. Friction is approximately constant for a
1495
- // biped on level ground Coulomb friction. Faster than
1496
- // accel because the body's own resistance + active
1497
- // decel-foot-plants combine into a sharper deceleration.
1498
- //
1499
- // • Grounded accel (intent active): mono-exponential
1500
- // approach (Hill 1927; Furusawa-Hill 1928). dv/dt is
1501
- // proportional to (v_target v), so accel is highest at
1502
- // low speed and tapers as v approaches v_target. Matches
1503
- // human sprint biomechanics modern sprint-profiling
1504
- // work (Morin & Samozino 2016) fits this same mono-exp
1505
- // curve to empirical force-plate data.
1506
- //
1507
- // The mass + mastery + backward scalars compose multiplicatively
1508
- // on the EFFECTIVE half-life (heavier ⇒ longer half-life ⇒
1509
- // slower ramp; mastery accel-bonus shorter half-life
1510
- // faster ramp). See FirstPersonPlayerControllerConfig.js's
1511
- // `groundAccelHalfLife` doc for the literature and the
1512
- // SprintAcceleration.spec.js for the model assertions.
1513
- const intentLen = Math.hypot(nmvX, nmvY);
1514
- if (!state.grounded) {
1515
- const maxStep = cfg.motion.airAccel * dt;
1516
- runtime.velocityX = stepTowards(runtime.velocityX, desiredHorizontalVx, maxStep);
1517
- runtime.velocityZ = stepTowards(runtime.velocityZ, desiredHorizontalVz, maxStep);
1518
- } else if (intentLen < 1e-4) {
1519
- let decel = cfg.motion.groundDecel * runtime.massRatios.groundAccelScale;
1520
- decel *= controller.mastery.evaluate(DecisionPoint.GroundAccel, controller, runtime);
1521
- const maxStep = decel * dt;
1522
- runtime.velocityX = stepTowards(runtime.velocityX, 0, maxStep);
1523
- runtime.velocityZ = stepTowards(runtime.velocityZ, 0, maxStep);
1524
- } else {
1525
- // Mono-exponential approach. Scale half-life by the
1526
- // inverse of the accel scalars so that "more accel" (large
1527
- // groundAccelScale, mastery > 1.0) translates to a shorter
1528
- // half-life (faster ramp). Backward intent slows things
1529
- // down backwardAccelFactor < 1 ⇒ longer half-life.
1530
- let halfLife = cfg.motion.groundAccelHalfLife
1531
- / runtime.massRatios.groundAccelScale
1532
- / controller.mastery.evaluate(DecisionPoint.GroundAccel, controller, runtime);
1533
- if (isBackwardIntent) halfLife /= cfg.motion.backwardAccelFactor;
1534
- runtime.velocityX = exponentialApproach(runtime.velocityX, desiredHorizontalVx, halfLife, dt);
1535
- runtime.velocityZ = exponentialApproach(runtime.velocityZ, desiredHorizontalVz, halfLife, dt);
1536
- }
1537
-
1538
- // -- L1.e/f/g/h: jump FSM + vertical integration --------------
1539
- this._advanceJumpFsm(controller, runtime, bodyTransform, dt);
1540
- this._integrateVerticalAndResolveGround(controller, runtime, bodyTransform, dt);
1541
-
1542
- // -- Publish posture for L2 consumers (eye height, gait gating).
1543
- // Base owns posture when no ability is active: Crouch if the
1544
- // crouch intent is resolved active, otherwise Stand. Abilities
1545
- // that need a different posture (slide → Prone, ledge-grab →
1546
- // Hang) set state.posture themselves in their tick.
1547
- let posture = isCrouchActive
1548
- ? FirstPersonPosture.Crouch
1549
- : FirstPersonPosture.Stand;
1550
- // Headroom floor block UN-proning under a very low overhang. Only an
1551
- // ALREADY-prone body (e.g. one that slid into a crawl-height tunnel) is
1552
- // force-kept prone while the Crouch collider can't fit above it. A player
1553
- // merely crouching by intent is NOT auto-dropped to prone when they meet
1554
- // a lower overhang — same rule as above: the mover blocks their forward
1555
- // motion instead of the ceiling shoving them down. Uses the post-move
1556
- // position, so it reflects where the body actually ended up this tick.
1557
- if (posture === FirstPersonPosture.Crouch
1558
- && runtime.lastPosture === FirstPersonPosture.Prone) {
1559
- const proneGrow = runtime.crouchTop - runtime.proneTop;
1560
- if (!this._hasHeadroomToGrow(runtime, bodyTransform, runtime.colliderShapeProne, proneGrow)) {
1561
- posture = FirstPersonPosture.Prone;
1562
- }
1563
- }
1564
- controller.state.posture = posture;
1565
-
1566
- // -- Publish lean target for L2.f. Base writes the natural
1567
- // (lat-accel + look-lean) value; abilities override in their
1568
- // own tick. L2.f spring-steps toward whatever's here.
1569
- runtime.leanTargetRad = this._computeNaturalLeanTarget(controller, runtime, dt);
1570
- }
1571
-
1572
- /**
1573
- * Compute the natural camera lean for this tick: lat-accel-driven
1574
- * roll into a turn, plus a yaw-rate look-lean contribution, both
1575
- * clamped. The result is the target the lean spring chases each
1576
- * tick when no ability has opinions.
1577
- *
1578
- * Pure-ish helperreads `controller`, `runtime`, `dt`; returns a
1579
- * number. Extracted so both base and any future ability that wants
1580
- * to compose its lean on top of the natural value can call it.
1581
- *
1582
- * @private
1583
- * @param {FirstPersonPlayerController} controller
1584
- * @param {PerEntityRuntime} runtime
1585
- * @param {number} dt
1586
- * @returns {number} target roll in radians
1587
- */
1588
- _computeNaturalLeanTarget(controller, runtime, dt) {
1589
- const cfg = controller.config;
1590
- const state = controller.state;
1591
- if (!cfg.lean.enabled) return 0;
1592
-
1593
- const sinYaw = runtime.sinYaw;
1594
- const cosYaw = runtime.cosYaw;
1595
-
1596
- // Lateral acceleration projected onto screen-right.
1597
- // accel_world = (vel - prevVel) / dt; screen_right = (-cos θ, 0, sin θ).
1598
- const accWorldX = (runtime.velocityX - runtime.prevVelocityX) / Math.max(dt, 1e-4);
1599
- const accWorldZ = (runtime.velocityZ - runtime.prevVelocityZ) / Math.max(dt, 1e-4);
1600
- const latAccel = accWorldX * (-cosYaw) + accWorldZ * sinYaw;
1601
- const normalized = clamp(latAccel / 9.81, -2, 2);
1602
- //
1603
- // Sign convention for the roll (the eye composes the rotation
1604
- // as qYaw * qPitch * qRoll, where qRoll is around (0,0,1)).
1605
- // After the engine's camera-invert pipeline:
1606
- // φ > 0 → camera-up tilts toward screen-right (−X) → HEAD TILTS RIGHT
1607
- // φ < 0 → camera-up tilts toward screen-left (+X) → HEAD TILTS LEFT
1608
- //
1609
- // For the "bank into the turn" feel (Apex / Titanfall / Mirror's
1610
- // Edge): accelerating right (latAccel > 0) should tilt the head
1611
- // RIGHT, i.e. positive φ. So leanTargetRad has the SAME sign
1612
- // as latAccel.
1613
- let leanTargetRad = normalized * cfg.lean.maxRollDeg * DEG_TO_RAD;
1614
-
1615
- // Look-lean: yaw-rate-driven banking. runtime.yawRateRadPerSec
1616
- // was cached at L1.a — negative is the "turn right" convention.
1617
- // For "bank into the turn": turning right → head tilts right →
1618
- // positive engine roll. So lookLean = -yawRate * scale matches
1619
- // sign.
1620
- //
1621
- // Crouched players are in a low, stable, low-momentum stance
1622
- // banking the head from a mouse turn reads as unmotivated. We
1623
- // scale the contribution down (default to 0) while crouched.
1624
- // Lat-accel lean is left alone: its magnitude naturally tracks
1625
- // the (lower) crouch acceleration, so it stays motivated.
1626
- if (cfg.lean.lookLeanEnabled) {
1627
- const yawRate = clamp(
1628
- runtime.yawRateRadPerSec,
1629
- -cfg.lean.lookLeanYawRateClamp,
1630
- cfg.lean.lookLeanYawRateClamp,
1631
- );
1632
- const crouchFactor = state.crouchActive ? cfg.lean.crouchLookLeanFactor : 1.0;
1633
- leanTargetRad += -yawRate * cfg.lean.lookLeanDegPerRadPerSec * DEG_TO_RAD * crouchFactor;
1634
- }
1635
-
1636
- // Final clamp on the sum: cap the combined target to ±2 ×
1637
- // maxRollDeg (matches the latAccel normalized clamp range) so
1638
- // even simultaneous max-strafe-accel + max-yaw-flick produces a
1639
- // sane upper bound.
1640
- const maxTotal = cfg.lean.maxRollDeg * DEG_TO_RAD * 2;
1641
- return clamp(leanTargetRad, -maxTotal, maxTotal);
1642
- }
1643
-
1644
- /**
1645
- * Snapshot the per-tick "what is the body doing" information into the
1646
- * pose channels for downstream consumption (skeleton, sound, AI).
1647
- * Read-only with respect to controller state this is purely a publish
1648
- * step.
1649
- *
1650
- * @private
1651
- * @param {FirstPersonPlayerController} controller
1652
- * @param {PerEntityRuntime} runtime
1653
- * @param {Transform} bodyTransform
1654
- */
1655
- _publishPose(controller, runtime, bodyTransform) {
1656
- const cfg = controller.config;
1657
- const state = controller.state;
1658
- const pose = controller.pose;
1659
-
1660
- pose.rootPosition.copy(bodyTransform.position);
1661
- pose.rootYawRad = runtime.bodyYaw;
1662
- pose.headYawRad = runtime.bodyYaw;
1663
- pose.headPitchRad = runtime.eyePitch;
1664
- pose.headRollRad = state.leanRollRad;
1665
- pose.locomotionPhase = state.stridePhase;
1666
- pose.locomotionSpeed = runtime.horizSpeed;
1667
- // Strafe component: project velocity onto screen-right (-cos θ, 0, sin θ).
1668
- // Positive = moving to the player's right.
1669
- pose.locomotionStrafe = (runtime.velocityX * (-runtime.cosYaw) + runtime.velocityZ * runtime.sinYaw)
1670
- / Math.max(cfg.motion.sprintSpeed, 1e-3);
1671
- pose.actionState =
1672
- state.inJumpAnticipation ? FirstPersonActionState.Anticipating
1673
- : !state.grounded ? FirstPersonActionState.Airborne
1674
- : (Math.abs(runtime.landSpring.value) > 0.01 ? FirstPersonActionState.Landing
1675
- : FirstPersonActionState.Grounded);
1676
- pose.locomotionMode = state.locomotionMode;
1677
- const crouchSpan = Math.max(cfg.body.height - cfg.body.crouchHeight, 1e-3);
1678
- pose.crouchAmount = clamp((cfg.body.height - state.eyeHeight) / crouchSpan, 0, 1);
1679
-
1680
- // Posture channel for downstream animation: which body shape +
1681
- // how far the body is into it from the standing neutral.
1682
- //
1683
- // `posture` is the enum (Stand / Crouch / Prone / Hang) — picks
1684
- // the animation track. `postureAmount` is the [0..1] blend
1685
- // weight from standing toward that posture, derived from the
1686
- // eye-height spring so the value transitions smoothly across
1687
- // changes (matches the visible camera motion).
1688
- pose.posture = state.posture;
1689
- let postureTargetH;
1690
- switch (state.posture) {
1691
- case FirstPersonPosture.Prone: postureTargetH = cfg.body.proneHeight; break;
1692
- case FirstPersonPosture.Crouch: postureTargetH = cfg.body.crouchHeight; break;
1693
- case FirstPersonPosture.Hang: postureTargetH = cfg.body.height; break;
1694
- case FirstPersonPosture.Stand:
1695
- default: postureTargetH = cfg.body.height; break;
1696
- }
1697
- const postureSpan = Math.max(cfg.body.height - postureTargetH, 1e-3);
1698
- pose.postureAmount = clamp((cfg.body.height - state.eyeHeight) / postureSpan, 0, 1);
1699
-
1700
- pose.aimPitch = runtime.eyePitch;
1701
-
1702
- this._assertTickInvariants(controller, runtime, bodyTransform);
1703
- this._snapshotRenderPose(runtime, bodyTransform);
1704
- }
1705
-
1706
- /**
1707
- * Record this fixed step's authoritative body position as the latest
1708
- * render-interpolation endpoint, shifting the old latest to "previous".
1709
- * {@link _composeEye} blends prev→cur by the sub-step alpha so the camera
1710
- * position is smooth at any refresh rate. (Yaw/pitch aren't snapshotted —
1711
- * they're render-rate.) On a teleport (`renderSnap`), prev and cur collapse
1712
- * to the new position so the camera jumps rather than gliding across it.
1713
- * @private
1714
- */
1715
- _snapshotRenderPose(runtime, bodyTransform) {
1716
- const p = bodyTransform.position;
1717
- if (runtime.renderSnap) {
1718
- runtime.renderSnap = false;
1719
- runtime.renderPrevPos.set(p.x, p.y, p.z);
1720
- runtime.renderCurPos.set(p.x, p.y, p.z);
1721
- return;
1722
- }
1723
- runtime.renderPrevPos.copy(runtime.renderCurPos);
1724
- runtime.renderCurPos.set(p.x, p.y, p.z);
1725
- }
1726
-
1727
- /**
1728
- * Dev-only invariant guard (meep asserts compile out of prod, so this is
1729
- * free there). Catches the class of corruption that surfaces as an
1730
- * unexplained camera "snap": a NaN/Infinity leaking into the yaw/pitch the
1731
- * eye composes from, or into the body's velocity / position. Runs once at
1732
- * the END of every fixed step, so a bad value is caught the tick it appears
1733
- * naming the layer that produced it instead of manifesting as a camera
1734
- * jump frames (or seconds) later when something finally reads it.
1735
- * @private
1736
- */
1737
- _assertTickInvariants(controller, runtime, bodyTransform) {
1738
- const p = bodyTransform.position;
1739
- assert.ok(Number.isFinite(runtime.bodyYaw),
1740
- "FP invariant: runtime.bodyYaw is non-finite (camera yaw corrupted)");
1741
- assert.ok(Number.isFinite(runtime.eyePitch),
1742
- "FP invariant: runtime.eyePitch is non-finite (camera pitch corrupted)");
1743
- assert.ok(Number.isFinite(controller.state.leanRollRad),
1744
- "FP invariant: state.leanRollRad is non-finite (camera roll corrupted)");
1745
- assert.ok(
1746
- Number.isFinite(runtime.velocityX)
1747
- && Number.isFinite(runtime.velocityY)
1748
- && Number.isFinite(runtime.velocityZ),
1749
- "FP invariant: runtime.velocity is non-finite",
1750
- );
1751
- assert.ok(
1752
- Number.isFinite(p.x) && Number.isFinite(p.y) && Number.isFinite(p.z),
1753
- "FP invariant: body position is non-finite",
1754
- );
1755
- }
1756
-
1757
- /**
1758
- * Consume the accumulated mouse-look delta into body yaw + eye pitch at
1759
- * RENDER rate (once per render frame, from {@link _composeEye}). This is the
1760
- * fix for the slow-turn camera snap: sampling `intent.look` at the 60Hz
1761
- * fixed step aliases an unaligned mouse report rate into an uneven turn,
1762
- * whereas consuming it every render frame tracks the mouse as smoothly as it
1763
- * arrives. `intent.look` is zeroed after consuming so the same delta isn't
1764
- * re-applied next frame.
1765
- *
1766
- * Conventions (raw mouse delta source movementX/Y positive moving
1767
- * right/down): look.x > 0 ("mouse right") → turn right (the yaw sign is
1768
- * negated because the engine is left-handed with +Z forward, so a +Y
1769
- * rotation reads as a LEFT turn through the camera); look.y > 0 → look down,
1770
- * flipped by invertY.
1771
- * @private
1772
- */
1773
- _consumeLook(controller, runtime) {
1774
- const intent = controller.intent;
1775
- const cfg = controller.config;
1776
- let yawDelta = -intent.look.x;
1777
- const pitchSign = cfg.look.invertY ? -1 : 1;
1778
- let pitchDelta = intent.look.y * pitchSign;
1779
- intent.look.set(0, 0);
1780
-
1781
- // Reject input SPIKES. A single render frame's look delta beyond a sane
1782
- // bound is a corrupt browser/driver `movementX` (a high-res mouse under
1783
- // OS pointer acceleration, or a pointer-lock burst), not a real turn
1784
- // applying it snaps the camera 20–100°. Drop the spiking axis; legit
1785
- // turns (even fast flicks) stay well under the bound at render rate.
1786
- const maxDelta = cfg.look.maxFrameDeltaRad;
1787
- if (Math.abs(yawDelta) > maxDelta) yawDelta = 0;
1788
- if (Math.abs(pitchDelta) > maxDelta) pitchDelta = 0;
1789
-
1790
- runtime.bodyYaw += yawDelta;
1791
- if (runtime.bodyYaw > Math.PI) runtime.bodyYaw -= TWO_PI;
1792
- else if (runtime.bodyYaw < -Math.PI) runtime.bodyYaw += TWO_PI;
1793
-
1794
- runtime.eyePitch = clamp(
1795
- runtime.eyePitch + pitchDelta,
1796
- cfg.look.pitchMinDeg * DEG_TO_RAD,
1797
- cfg.look.pitchMaxDeg * DEG_TO_RAD,
1798
- );
1799
- }
1800
-
1801
- /**
1802
- * Compose the eye transform from body + state-driven offsets.
1803
- * @private
1804
- * @param {FirstPersonPlayerController} controller
1805
- * @param {number} entity
1806
- */
1807
- _composeEye(controller, entity) {
1808
- const ecd = this.entityManager.dataset;
1809
- const runtime = this.runtime.get(entity);
1810
- if (runtime === undefined) return;
1811
-
1812
- // Consume mouse-look at RENDER rate so the camera direction tracks the
1813
- // mouse smoothly, decoupled from the aliased 60Hz sim sampling.
1814
- this._consumeLook(controller, runtime);
1815
-
1816
- const dt = this._currentRenderDt;
1817
- const cfg = controller.config;
1818
- const state = controller.state;
1819
-
1820
- const bodyTransform = ecd.getComponent(entity, Transform);
1821
- if (bodyTransform === undefined) return;
1822
-
1823
- if (controller.eyeEntity === -1) return;
1824
- const eyeTransform = ecd.getComponent(controller.eyeEntity, Transform);
1825
- const camera = ecd.getComponent(controller.eyeEntity, Camera);
1826
- if (eyeTransform === undefined || camera === undefined) return;
1827
-
1828
- // -- Body-local eye offset, composed via the additive stack ----
1829
- // The base (0, eyeHeight, 0) is the standing/crouched neutral; each
1830
- // additional contribution (bob, breath, landing, anticipation,
1831
- // sprint posture) goes through the stack so external systems can
1832
- // push their own contributions on the same channel.
1833
- const stack = runtime.eyeOffsetStack;
1834
- stack.clear();
1835
- stack.push("eyeHeight", 0, state.eyeHeight, 0);
1836
-
1837
- // Bob gated on grounded only (the impact spring decays naturally
1838
- // even at rest, so the bob fade-out is smooth; lateral amp uses the
1839
- // bob-intensity envelope which spring-decays after stopping).
1840
- if (state.grounded) {
1841
- const phase = state.stridePhase * TWO_PI;
1842
- const massBoost = (cfg.body.mass - 80) * cfg.bob.ampMassScale;
1843
- const intensity = runtime.bobIntensitySpring.value;
1844
-
1845
- // Back-pedal amp boost — lateral grows more than vertical because
1846
- // backward gait has worse side-to-side balance than vertical compression.
1847
- // Exertion adds a smaller boost on top: tired = wobbly gait.
1848
- const ampLMult = 1 + (cfg.bob.backwardLateralAmpFactor - 1) * runtime.backwardness;
1849
- const exertionBoost = 1 + cfg.exertion.bobLateralBoostAtMax * state.exertion;
1850
- const ampL = (cfg.bob.lateralAmpAtWalk + massBoost) * intensity * ampLMult * exertionBoost;
1851
-
1852
- // Vertical: read directly from the impact spring (footfall kicks,
1853
- // under-damped recovery trough + leg-push overshoot).
1854
- stack.push("bob.impact", 0, runtime.verticalImpactSpring.value, 0);
1855
-
1856
- // Lateral: head shifts toward the foot bearing weight. Polarity
1857
- // sourced from runtime.standingFoot — the same signal the
1858
- // footstep emits — so bob direction and footstep side agree.
1859
- // |sin(phase)| is the non-negative "midstance envelope".
1860
- const lateralPolarity = runtime.standingFoot === "R" ? -1 : 1;
1861
- stack.push("bob.lateral", ampL * lateralPolarity * Math.abs(Math.sin(phase)), 0, 0);
1862
- }
1863
-
1864
- // Breath — sine + tiny noise riding the rate spring.
1865
- const breathOffset = -state.breathAmplitudeM
1866
- * Math.sin(state.breathPhase * TWO_PI)
1867
- * (1 + cfg.breath.noiseAmount * (Math.sin(state.breathPhase * 13.7) * 0.5));
1868
- stack.push("breath", 0, breathOffset, 0);
1869
-
1870
- // Landing spring dip (under-damped overshoots once on recovery).
1871
- stack.push("landing", 0, runtime.landSpring.value, 0);
1872
-
1873
- // Jump anticipation dip (eased ramp during the squash window).
1874
- if (state.inJumpAnticipation) {
1875
- const t = 1 - clamp(runtime.anticipationRemaining / Math.max(cfg.jump.anticipation.duration, 1e-3), 0, 1);
1876
- const eased = t * (2 - t); // ease-out quad
1877
- stack.push("anticipation", 0, -cfg.jump.anticipation.dipAmount * eased, 0);
1878
- }
1879
-
1880
- // Sprint posture: head leans slightly forward as commitment builds.
1881
- // Pitch part is in the rotation block below; the +Z position shift
1882
- // sells "head leading the hips" (Mirror's Edge), tied to the same
1883
- // spring envelope so they move together.
1884
- const sprintPitch = runtime.sprintPostureSpring.value;
1885
- const sprintShiftFraction =
1886
- cfg.posture.sprintForwardPitchDeg > 0
1887
- ? sprintPitch / (cfg.posture.sprintForwardPitchDeg * DEG_TO_RAD)
1888
- : 0;
1889
- stack.push("posture.sprintShift", 0, 0, cfg.posture.sprintForwardShiftM * sprintShiftFraction);
1890
-
1891
- // Body POSITION advances only at the fixed step → blend the last two
1892
- // steps by the engine's sub-step alpha (the same fraction the
1893
- // InterpolationSystem uses for physics bodies) so it's smooth on a high-
1894
- // refresh display. Yaw + pitch are already render-rate (consumed in
1895
- // _consumeLook), so they're used directly — no blend, no aliasing.
1896
- const alpha = this.entityManager.getFixedStepAlpha();
1897
- const iPosX = lerp(runtime.renderPrevPos.x, runtime.renderCurPos.x, alpha);
1898
- const iPosY = lerp(runtime.renderPrevPos.y, runtime.renderCurPos.y, alpha);
1899
- const iPosZ = lerp(runtime.renderPrevPos.z, runtime.renderCurPos.z, alpha);
1900
- const qYaw0 = SCRATCH_Q_A.fromAxisAngle(Vector3.up, runtime.bodyYaw);
1901
-
1902
- // Transform body-local accumulated offset into world space (about the
1903
- // render-rate yaw, to match the interpolated base position).
1904
- const worldOffset = SCRATCH_V3_B.copy(stack.offset);
1905
- worldOffset.applyQuaternion(qYaw0);
1906
-
1907
- eyeTransform.position.set(iPosX, iPosY, iPosZ);
1908
- eyeTransform.position._add(worldOffset.x, worldOffset.y, worldOffset.z);
1909
-
1910
- // -- Eye rotation: body yaw × eye pitch × roll -------------------
1911
- // Bob roll mixes in for a subtle head sway (in phase with lateral bob).
1912
- // Breath pitch is a small extra nod 90° out of phase with vertical
1913
- // breath; merged into the main pitch so we don't pay an extra quat
1914
- // multiply and the composition stays trivially correct.
1915
- let rollTotal = state.leanRollRad;
1916
- if (state.grounded) {
1917
- // Roll: head tilts toward the standing foot, in phase with the
1918
- // lateral sway. Polarity sourced from runtime.standingFoot for
1919
- // consistency with the lateral bob. Positive engine roll = head
1920
- // tilts RIGHT (camera-invert convention), so R-foot midstance =
1921
- // positive roll, L-foot midstance = negative roll.
1922
- const phase = state.stridePhase * TWO_PI;
1923
- const rollBackMult = 1 + (cfg.bob.backwardRollFactor - 1) * runtime.backwardness;
1924
- const ampRoll = cfg.bob.rollAtWalkDeg * DEG_TO_RAD * runtime.bobIntensitySpring.value * rollBackMult;
1925
- const rollPolarity = runtime.standingFoot === "R" ? 1 : -1;
1926
- const rollEnvelope = Math.abs(Math.sin(phase));
1927
- const bobRollSigned = ampRoll * rollPolarity * rollEnvelope;
1928
-
1929
- // Lean × bob coupling: excursions in the lean direction get
1930
- // amplified, opposite excursions attenuated. Lean is normalized
1931
- // against maxRollDeg so the coupling magnitude stays bounded
1932
- // regardless of how aggressively lean is configured.
1933
- const maxLeanRad = Math.max(cfg.lean.maxRollDeg * DEG_TO_RAD, 1e-6);
1934
- const leanFraction = clamp(state.leanRollRad / maxLeanRad, -1, 1);
1935
- // sign(bobRollSigned) matches lean? amplify; else attenuate.
1936
- const sameSign = (bobRollSigned * leanFraction) >= 0;
1937
- const couplingMag = cfg.bob.leanCouplingFactor * Math.abs(leanFraction);
1938
- const couplingScale = sameSign ? (1 + couplingMag) : (1 - couplingMag);
1939
- rollTotal += bobRollSigned * couplingScale;
1940
- }
1941
-
1942
- const breathPitch = lerp(cfg.breath.pitchAmpRestDeg, cfg.breath.pitchAmpMaxDeg, state.exertion)
1943
- * DEG_TO_RAD
1944
- * Math.cos(state.breathPhase * TWO_PI);
1945
- // Combined pitch contributions: player input + breath nod + sprint
1946
- // commitment + fatigue droop. All in the same "positive = look-down"
1947
- // convention so they sum cleanly.
1948
- const pitchTotal = runtime.eyePitch
1949
- + breathPitch
1950
- + runtime.sprintPostureSpring.value
1951
- + runtime.headDroopSpring.value;
1952
-
1953
- // composition: yaw * pitch * roll
1954
- // pitch around world X yaw applied after, so effective axis is camera-local right
1955
- // roll around world Z — yaw and pitch applied after, so effective axis is camera-local forward
1956
- const qYaw = qYaw0; // SCRATCH_Q_A, already the render-rate yaw
1957
- const qPitch = SCRATCH_Q_B.fromAxisAngle(Vector3.right, pitchTotal);
1958
- const qRoll = SCRATCH_Q_C.fromAxisAngle(Vector3.forward, rollTotal);
1959
-
1960
- eyeTransform.rotation.multiplyQuaternions(qYaw, qPitch);
1961
- eyeTransform.rotation.multiply(qRoll);
1962
-
1963
- // Dev-only camera-output guard (compiled out of prod). The composed eye
1964
- // direction is exactly what the player sees; if a spring (head-droop,
1965
- // breath nod) or lean ever drove pitch/roll to NaN, the camera would
1966
- // snap to garbage. Assert it here, at the source of the visible jump.
1967
- const er = eyeTransform.rotation;
1968
- assert.ok(
1969
- Number.isFinite(er.x) && Number.isFinite(er.y) && Number.isFinite(er.z) && Number.isFinite(er.w),
1970
- "FP invariant: composed eye rotation is non-finite (camera direction corrupted)",
1971
- );
1972
-
1973
- // -- FOV ---------------------------------------------------------
1974
- let fovTarget = cfg.fov.base;
1975
- if (cfg.fov.sprintAdd !== 0) {
1976
- fovTarget += cfg.fov.sprintAdd * runtime.sprintness;
1977
- }
1978
- if (state.crouchActive) fovTarget += cfg.fov.crouchAdd;
1979
-
1980
- runtime.fovSpring.stepTo(fovTarget, cfg.fov.smoothHalfLife, 1.0, dt);
1981
- // Write directly to the underlying Three.js camera. Going through
1982
- // camera.fov.set() fires onChanged which triggers a full camera
1983
- // rebuild in CameraSystem — far too expensive to do per frame.
1984
- // The CameraSystem's visibility-construction hook calls
1985
- // updateProjectionMatrix() each frame anyway.
1986
- if (camera.object !== null) {
1987
- camera.object.fov = runtime.fovSpring.value;
1988
- }
1989
- }
1990
- }
1991
-
1992
- // ---------------------------------------------------------------------------
1993
- // helpers
1994
- // ---------------------------------------------------------------------------
1995
-
1996
- /**
1997
- * Exponential approach with half-life parameterization.
1998
- * @param {number} current
1999
- * @param {number} target
2000
- * @param {number} halfLife
2001
- * @param {number} dt
2002
- * @returns {number}
2003
- */
2004
- function exponentialApproach(current, target, halfLife, dt) {
2005
- if (halfLife <= 0) return target;
2006
- const alpha = 1 - Math.exp(-LN2 * dt / halfLife);
2007
- return current + (target - current) * alpha;
2008
- }
2009
-
2010
- /**
2011
- * Detect that phase value crossed a boundary in [0,1) between two ticks.
2012
- * Handles the wraparound case where phase jumps from e.g. 0.95 to 0.05.
2013
- *
2014
- * @param {number} prev previous phase in [0,1)
2015
- * @param {number} next current phase in [0,1)
2016
- * @param {number} boundary in [0,1)
2017
- * @returns {boolean}
2018
- */
2019
- function phaseCrossed(prev, next, boundary) {
2020
- if (next >= prev) {
2021
- // no wrap
2022
- return prev < boundary && next >= boundary;
2023
- } else {
2024
- // wrapped past 1.0
2025
- return prev < boundary || next >= boundary;
2026
- }
2027
- }
2028
-
1
+ import { assert } from "../../../core/assert.js";
2
+ import { Ray3 } from "../../../core/geom/3d/ray/Ray3.js";
3
+ import { CapsuleShape3D } from "../../../core/geom/3d/shape/CapsuleShape3D.js";
4
+ import { TransformedShape3D } from "../../../core/geom/3d/shape/TransformedShape3D.js";
5
+ import Quaternion from "../../../core/geom/Quaternion.js";
6
+ import Vector3 from "../../../core/geom/Vector3.js";
7
+ import { clamp } from "../../../core/math/clamp.js";
8
+ import { DEG_TO_RAD } from "../../../core/math/DEG_TO_RAD.js";
9
+ import { lerp } from "../../../core/math/lerp.js";
10
+ import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js";
11
+ import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
12
+ import { SerializationMetadata } from "../../ecs/components/SerializationMetadata.js";
13
+ import Entity from "../../ecs/Entity.js";
14
+ import { System } from "../../ecs/System.js";
15
+ import { Transform } from "../../ecs/transform/Transform.js";
16
+ import { Camera } from "../../graphics/ecs/camera/Camera.js";
17
+ import { BodyKind } from "../../physics/ecs/BodyKind.js";
18
+ import { Collider } from "../../physics/ecs/Collider.js";
19
+ import { PhysicsSystem } from "../../physics/ecs/PhysicsSystem.js";
20
+ import { RigidBody } from "../../physics/ecs/RigidBody.js";
21
+ import { PhysicsSurfacePoint } from "../../physics/queries/PhysicsSurfacePoint.js";
22
+ import { KinematicMover } from "./collision/KinematicMover.js";
23
+ import { EyeOffsetStack } from "./composer/EyeOffsetStack.js";
24
+ import { FirstPersonPlayerController } from "./FirstPersonPlayerController.js";
25
+ import { DecisionPoint } from "./mastery/DecisionPoint.js";
26
+ import { computeJumpFromApex } from "./math/computeJumpFromApex.js";
27
+ import { computeLRCBreathRate } from "./math/computeLRCBreathRate.js";
28
+ import { computeMassRatios } from "./math/computeMassRatios.js";
29
+ import { jumpForceForHeight, launchVelocityForForce } from "./math/jumpDynamics.js";
30
+ import { Spring } from "./math/Spring.js";
31
+ import { stepTowards } from "./math/stepTowards.js";
32
+ import { FirstPersonActionState, FirstPersonLocomotionMode } from "./pose/FirstPersonPose.js";
33
+ import { FirstPersonPosture } from "./pose/FirstPersonPosture.js";
34
+ import { FirstPersonSensors } from "./sensors/FirstPersonSensors.js";
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Scratch allocations — reused per frame to avoid GC pressure
38
+ // ---------------------------------------------------------------------------
39
+ const SCRATCH_V3_A = new Vector3();
40
+ const SCRATCH_V3_B = new Vector3();
41
+ const SCRATCH_V3_C = new Vector3();
42
+ const SCRATCH_Q_A = new Quaternion();
43
+ const SCRATCH_Q_B = new Quaternion();
44
+ const SCRATCH_Q_C = new Quaternion();
45
+
46
+ const TWO_PI = Math.PI * 2;
47
+ const LN2 = Math.log(2);
48
+
49
+ /**
50
+ * Build a posture-sized player capsule: a {@link CapsuleShape3D} of
51
+ * `radius` and the appropriate cylinder height, wrapped in a
52
+ * {@link TransformedShape3D} whose Y offset puts the capsule's bottom
53
+ * exactly at the wrapped shape's local origin. The entity's
54
+ * `transform.position` then represents the player's feet — and a
55
+ * posture-driven shrink doesn't yank the feet up the way a centred
56
+ * capsule would, nor dip them below the floor.
57
+ *
58
+ * The capsule's lowest point in its own local frame is at
59
+ * `-(cylinderHeight/2 + radius) = -max(totalHeight/2, radius)`.
60
+ * Offsetting the wrapper by the magnitude of that puts the bottom at
61
+ * Y = 0:
62
+ * - Stand (`H = 1.8`, `r = 0.34`): cylHeight = 1.12, offset = 0.9.
63
+ * Bottom = -0.9 + 0.9 = 0. Top = +0.9 + 0.9 = 1.8.
64
+ * - Crouch (`H = 0.8`, `r = 0.34`): cylHeight = 0.12, offset = 0.4.
65
+ * Bottom = -0.4 + 0.4 = 0. Top = +0.4 + 0.4 = 0.8.
66
+ * - Prone (`H = 0.4`, `r = 0.34`): cylHeight = 0 (capsule collapses
67
+ * to a sphere of radius), offset = max(0.2, 0.34) = 0.34.
68
+ * Bottom = -0.34 + 0.34 = 0. Top = +0.34 + 0.34 = 0.68. The
69
+ * `totalHeight = 0.4` value is honoured for the offset budget
70
+ * but the actual Y extent floors at `2·radius`.
71
+ *
72
+ * Picking `totalHeight/2` blindly (the obvious choice) would put the
73
+ * Prone capsule's bottom at `0.2 - 0.34 = -0.14` — dipping below the
74
+ * feet, and into any physics floor that's flush with feet level. On
75
+ * a physics ground slab, every horizontal shape_cast from inside the
76
+ * floor returns t = 0, `advance = max(0, t - SKIN) = 0`, and the
77
+ * slide freezes in place — see SlideMotion.spec.js for the
78
+ * regression test that pins this.
79
+ *
80
+ * @param {number} radius — capsule radius in metres
81
+ * @param {number} totalHeight — desired full Y extent; ignored below
82
+ * `2·radius` (the capsule's intrinsic minimum extent)
83
+ * @returns {TransformedShape3D}
84
+ */
85
+ function makePostureCapsule(radius, totalHeight) {
86
+ const cylinderHeight = Math.max(0, totalHeight - 2 * radius);
87
+ const yOffset = Math.max(totalHeight / 2, radius);
88
+ return TransformedShape3D.from_translation(
89
+ CapsuleShape3D.from(radius, cylinderHeight),
90
+ [0, yOffset, 0],
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Per-entity runtime state the system maintains internally — too transient
96
+ * even for {@link FirstPersonPlayerController}'s `state` member, because it
97
+ * encodes input-edge bookkeeping and timer values the public surface should
98
+ * never see directly.
99
+ */
100
+ class PerEntityRuntime {
101
+ /**
102
+ * Co-attached kinematic body. Set by {@link FirstPersonPlayerControllerSystem.link}
103
+ * after asserting it's present. The controller writes Transform.position
104
+ * directly (existing motion logic); physics derives the body's velocity
105
+ * from the per-step delta. Other physics systems (raycasts, contact
106
+ * events) see the player through this body.
107
+ * @type {RigidBody|null}
108
+ */
109
+ rigidBody = null;
110
+
111
+ /**
112
+ * Co-attached collider, cached at link. Same source the physics
113
+ * narrowphase uses, so move-and-slide casts the player's
114
+ * actual collision shape against the world.
115
+ * @type {Collider|null}
116
+ */
117
+ collider = null;
118
+
119
+ /**
120
+ * Pre-built capsule colliders, one per posture. Cached at link
121
+ * from `config.body.{height, crouchHeight, proneHeight, radius}`
122
+ * so {@link _syncColliderShape} can swap the collider's shape on
123
+ * a posture change with zero per-tick allocation. Hang reuses
124
+ * Stand (the player's body is full-extent, just hanging below
125
+ * the ledge — the rig animates the arms-up pose). Sentinel
126
+ * `lastPosture = -1` forces a sync on the first tick after
127
+ * link, so the initial shape always matches Stand.
128
+ * @private
129
+ * @type {TransformedShape3D|null}
130
+ */
131
+ colliderShapeStand = null;
132
+
133
+ /** @private @type {TransformedShape3D|null} */
134
+ colliderShapeCrouch = null;
135
+
136
+ /** @private @type {TransformedShape3D|null} */
137
+ colliderShapeProne = null;
138
+
139
+ /** @private */
140
+ lastPosture = -1;
141
+
142
+ /** Eye pitch in radians, clamped to config.look limits. */
143
+ eyePitch = 0;
144
+
145
+ /** Body yaw in radians (around world up). */
146
+ bodyYaw = 0;
147
+
148
+ /** Yaw rate (rad/s) computed in look consumption for evaluators. */
149
+ yawRateRadPerSec = 0;
150
+
151
+ // -- Render-rate look + position interpolation ---------------------
152
+ // The camera DIRECTION (yaw/pitch) is consumed at RENDER rate (see the
153
+ // controller's _consumeLook), so it tracks the mouse smoothly instead of
154
+ // being aliased by the 60Hz sim sampling consuming the accumulated
155
+ // mouse delta once per fixed step beats against an unaligned mouse
156
+ // report rate and makes a slow turn lurch (the reported snap). The body
157
+ // POSITION still advances only at the fixed step, so it's blended
158
+ // between the last two steps by the sub-step alpha to stay smooth on a
159
+ // high-refresh display.
160
+ /** Body yaw at the end of the previous fixed step — used to derive the
161
+ * per-step turn RATE that drives look-lean, decoupled from per-frame
162
+ * input jitter. */
163
+ prevFixedYaw = 0;
164
+
165
+ renderPrevPos = new Vector3();
166
+ renderCurPos = new Vector3();
167
+
168
+ /** When true, the next position snapshot collapses prev=cur (no blend)
169
+ * — set on a body teleport (e.g. the ledge-grab hang snap) so the
170
+ * camera doesn't glide across the jump. */
171
+ renderSnap = false;
172
+
173
+ /** Horizontal+vertical velocity. We integrate these inside the system
174
+ * when no external physics layer is attached. */
175
+ velocityX = 0;
176
+
177
+ velocityY = 0;
178
+ velocityZ = 0;
179
+
180
+ /** Previous-tick jump intent for rising/falling edge detection. */
181
+ prevJumpHeld = false;
182
+
183
+ /** Previous-tick crouch intent — for toggle-mode edge detection. */
184
+ prevCrouchHeld = false;
185
+
186
+ /** True while crouch toggle is latched on (used only in toggle mode). */
187
+ crouchLatched = false;
188
+
189
+ /** Remaining time in jump anticipation, or <= 0 if not anticipating. */
190
+ anticipationRemaining = 0;
191
+
192
+ /** Cached derived gravity (m/s^2) from peakHeight + timeToApex. */
193
+ gravity = 9.81;
194
+
195
+ /**
196
+ * Cached jump PUSH-OFF FORCE (newtons) — the physical leg force that
197
+ * reaches `peakHeight` at the reference mass, the quantity jump power is
198
+ * expressed in. Derived at link via {@link jumpForceForHeight}; abilities
199
+ * (e.g. the scramble wall-kick) launch off this same force.
200
+ */
201
+ jumpForce = 0;
202
+
203
+ /** Cached derived jump impulse (m/s upward), post-mass-scaling. */
204
+ jumpInitialVy = 5.0;
205
+
206
+ /**
207
+ * Cached mass scaling factors — computed once at link. See
208
+ * {@link computeMassRatios}. Heavier ⇒ lower jumpV0Scale, lower
209
+ * groundAccelScale, higher landingDipScale + exertionRiseScale.
210
+ */
211
+ massRatios = null;
212
+
213
+ /** Spring for landing dip (under-dampedrings after impact). */
214
+ landSpring = new Spring();
215
+
216
+ /** Spring for FOV (critically damped). */
217
+ fovSpring = new Spring(70);
218
+
219
+ /** Spring for eye height (crouch transition). */
220
+ eyeHeightSpring = new Spring(1.80);
221
+
222
+ /** Spring for lean roll (radians) — banks into lateral acceleration. */
223
+ leanSpring = new Spring();
224
+
225
+ /**
226
+ * Lean target this tick (radians). Always set; L2.f spring-steps
227
+ * toward this value. Whoever owned motion this tick wrote it:
228
+ * base writes the lat-accel + look-lean derived value at the end
229
+ * of {@link _runBaseLocomotion}; abilities that want to override
230
+ * (WallRun tilt-into-wall, Slide/Mantle/LedgeGrab zero) write
231
+ * their own value in tick. Uniform channel — no null sentinel.
232
+ */
233
+ leanTargetRad = 0;
234
+
235
+ /** Previous horizontal velocity — for lateral acceleration → lean. */
236
+ prevVelocityX = 0;
237
+
238
+ prevVelocityZ = 0;
239
+
240
+ /** Previous-tick grounded for edge detection. */
241
+ prevGrounded = true;
242
+
243
+ /** Vertical speed at moment of last "leave ground". */
244
+ takeoffVy = 0;
245
+
246
+ /** Max vertical position since last takeoff for jump apex detection. */
247
+ peakAltitude = 0;
248
+
249
+ /** Set true once a jump has been launched; cleared on land. */
250
+ midJump = false;
251
+
252
+ /** Apex already fired for this airborne segment? */
253
+ apexFired = false;
254
+
255
+ /** Stride phase from previous fixed step for footstep edge detection. */
256
+ prevStridePhase = 0;
257
+
258
+ /** Breath phase from previous fixed step — for inhale/exhale edge detection. */
259
+ prevBreathPhase = 0;
260
+
261
+ /** Which foot fires next — flipped on each footstep signal. */
262
+ nextFootSide = "R";
263
+
264
+ /**
265
+ * Which foot is currently bearing the body's weight (the foot that
266
+ * most recently landed). Drives the lateral-bob direction: at R
267
+ * midstance the COM is over the right foot, so the head shifts
268
+ * laterally toward screen-right; at L midstance the opposite.
269
+ * Coupled to the same signal the footstep emits, so anything that
270
+ * listens to onFootStep.side will see the bob agree.
271
+ * Initialized "L" so the very first footstep fires "R" and the
272
+ * standingFoot updates to "R" — putting the head laterally right
273
+ * during the first half-stride, as expected.
274
+ */
275
+ standingFoot = "L";
276
+
277
+ /**
278
+ * [0..1] How "backward" the player is currently moving. Derived in
279
+ * fixedUpdate from velocity · screen-forward, normalized to sprint
280
+ * speed. Drives the gait wobble amplifier on the L3 camera-composition
281
+ * pass. Stored on runtime (rather than state) because it's a render-
282
+ * side input — downstream observers should look at velocity directly.
283
+ */
284
+ backwardness = 0;
285
+
286
+ /**
287
+ * Smoothed bob amplitude envelope. Target = max(speedNormalized,
288
+ * backwardness) when grounded, 0 airborne. Spring decay prevents
289
+ * the whiplash where stopping motion would snap the bob to neutral.
290
+ */
291
+ bobIntensitySpring = new Spring();
292
+
293
+ /**
294
+ * Vertical impact spring — kicked downward at each footfall, decays
295
+ * with a slight under-damped overshoot. Produces the impact-arrest +
296
+ * leg-push curve. value units: meters (added directly to eyeLocal.y).
297
+ */
298
+ verticalImpactSpring = new Spring();
299
+
300
+ /**
301
+ * Sprint-posture spring eye pitches forward as the player commits
302
+ * to a sprint, returns to neutral when they slow. Value is in
303
+ * radians; slower half-life than other springs so it feels like
304
+ * a posture change rather than an input twitch. See cfg.posture.
305
+ */
306
+ sprintPostureSpring = new Spring();
307
+
308
+ /**
309
+ * Head-droop springadditional forward pitch as exertion rises.
310
+ * Sells fatigue subtly. Target tracks exertion-driven max droop
311
+ * angle; spring lag keeps the transition slow and physical.
312
+ */
313
+ headDroopSpring = new Spring();
314
+
315
+ /**
316
+ * [0..1] sprintness how much of the walk→sprint speed range the
317
+ * body is currently in. Computed in fixedUpdate, read by L3 for FOV
318
+ * and the sprint-posture pitch / forward-shift offset.
319
+ */
320
+ sprintness = 0;
321
+
322
+ /**
323
+ * Cached sin/cos of current body yaw written once per fixedUpdate
324
+ * after look intent is consumed, read by every downstream step
325
+ * (locomotion, backwardness, lean look-rate, pose channels). Avoids
326
+ * recomputing the trig 3+ times per tick.
327
+ */
328
+ sinYaw = 0;
329
+
330
+ cosYaw = 1;
331
+
332
+ /** Cached horizontal speed (m/s) for this tick written in derived-state. */
333
+ horizSpeed = 0;
334
+
335
+ /** Cached stride frequency (Hz) for this tick — written in breath block, read by stride. */
336
+ strideFreqHz = 0;
337
+
338
+ /**
339
+ * Additive accumulator for body-local eye-position offsets. The
340
+ * system pushes its own contributions (bob, breath, landing,
341
+ * sprint posture) each render frame; external systems can push
342
+ * recoil/shake/knockback contributions via the same interface.
343
+ */
344
+ eyeOffsetStack = new EyeOffsetStack();
345
+
346
+ /**
347
+ * Spatial-query results populated by {@link FirstPersonSensorsSystem}
348
+ * (when present). Abilities and the locomotion FSM read this.
349
+ * Lives on runtime so other systems can populate it without
350
+ * touching the controller component's public surface.
351
+ */
352
+ sensors = new FirstPersonSensors();
353
+
354
+ /** Cached eye entity ID. -1 until link assigns it. */
355
+ eyeEntity = -1;
356
+
357
+ /**
358
+ * One-shot hand-off flag: LedgeGrab sets it on a pull-up release so
359
+ * Mantle (next tick) knows this is a ledge pull-up it then allows a
360
+ * too-thin top (auto-mantle still requires a standable one) and vaults
361
+ * OVER instead of onto. Mantle consumes it in onActivate.
362
+ */
363
+ ledgePullUpRequest = false;
364
+
365
+ /**
366
+ * Seconds remaining in the post-release ledge re-grab refractory
367
+ * window. Set by {@link LedgeGrab} on deactivation, aged down by dt
368
+ * each fixed step, and read by LedgeGrab.canActivate — so a deliberate
369
+ * dismount can fall clear of the grab window without the auto-catch
370
+ * snapping the body straight back onto the same lip.
371
+ */
372
+ ledgeRegrabCooldown = 0;
373
+ }
374
+
375
+ /**
376
+ * Drives a first-person camera + body from intent fields. See sibling
377
+ * DESIGN.md for goals, architecture, and the five processing layers (L0..L4).
378
+ *
379
+ * - fixedUpdate runs L1 (locomotion), L2 (pose state), and L4 (events) so
380
+ * the simulation remains deterministic.
381
+ * - update runs L3 (camera composition) at render rate so the eye is never
382
+ * smoother than the screen.
383
+ *
384
+ * The system itself integrates a simple flat-floor at y = `config.gravity.magnitude > 0
385
+ * ? state.groundY : -Infinity` for the prototype. A real physics layer should
386
+ * write `state.grounded`/`state.groundNormal` from outside instead; the
387
+ * built-in resolver is just a convenience to keep the controller usable
388
+ * without dependencies.
389
+ *
390
+ * @author Alex Goldring
391
+ * @copyright Company Named Limited (c) 2026
392
+ */
393
+ export class FirstPersonPlayerControllerSystem extends System {
394
+ // Dependencies kept to (controller, transform) so we can ASSERT on
395
+ // RigidBody at link time and emit a clear error if missing. If
396
+ // RigidBody were a hard dep, entities lacking one would silently
397
+ // never link — the controller would appear inert with no
398
+ // diagnostic. The assert below catches the missing-body case
399
+ // explicitly.
400
+ dependencies = [FirstPersonPlayerController, Transform];
401
+
402
+ components_used = [
403
+ ResourceAccessSpecification.from(Transform, ResourceAccessKind.Write),
404
+ ResourceAccessSpecification.from(Camera, ResourceAccessKind.Write),
405
+ ResourceAccessSpecification.from(RigidBody, ResourceAccessKind.Write),
406
+ ];
407
+
408
+ /**
409
+ * Per-entity runtime, keyed by entity id.
410
+ * @type {Map<number, PerEntityRuntime>}
411
+ */
412
+ runtime = new Map();
413
+
414
+ /**
415
+ * If true, the system clamps body y >= groundY and writes
416
+ * state.grounded itself. Turn off when wiring a real physics layer.
417
+ * @type {boolean}
418
+ */
419
+ useBuiltInFlatGround = true;
420
+
421
+ /**
422
+ * The flat-ground y for the built-in resolver. Ignored when
423
+ * useBuiltInFlatGround is false.
424
+ * @type {number}
425
+ */
426
+ groundY = 0;
427
+
428
+ /**
429
+ * Optional callback that returns the surface Y under the player
430
+ * for ground resolution. Called each tick with the player's
431
+ * current (x, y, z); returns the world-Y of the ground below,
432
+ * or null if no ground is below (gap / void).
433
+ *
434
+ * Combines with `useBuiltInFlatGround`: the effective ground for
435
+ * the tick is `max(this.groundY when enabled, resolver(...))`.
436
+ * Set both off (`useBuiltInFlatGround=false`, `groundResolver=null`)
437
+ * to defer to external physics entirely.
438
+ *
439
+ * Designed for prototypes / gyms that need elevated platforms
440
+ * without a full physics layer. Production should wire a real
441
+ * physics system instead.
442
+ *
443
+ * @type {((x:number, y:number, z:number) => number|null) | null}
444
+ */
445
+ groundResolver = null;
446
+
447
+ /**
448
+ * PhysicsSystem reference. Auto-acquired at startup; can be
449
+ * overridden by the caller. When present, collision is resolved
450
+ * by the {@link KinematicMover}; when null, the controller uses
451
+ * the flat-ground integrator ({@link _moveFlatGround}) — useful
452
+ * for spec setups that don't wire physics.
453
+ * @type {PhysicsSystem|null}
454
+ */
455
+ physicsSystem = null;
456
+
457
+ /**
458
+ * The collision solver — the authoritative move resolver whenever
459
+ * a {@link PhysicsSystem} is present (recover + unified
460
+ * sweep-and-slide + ground-categorize; see DESIGN_COLLISION.md).
461
+ * Lazily built on first use. With no physics the controller falls
462
+ * back to a flat-ground integrator ({@link _moveFlatGround}).
463
+ * @private
464
+ * @type {KinematicMover|null}
465
+ */
466
+ _mover = null;
467
+
468
+ /** @private Scratch velocity handed to the mover each tick. */
469
+ _moverVelocity = new Vector3();
470
+
471
+ /** @private Scratch for the posture-headroom up-cast. */
472
+ _postureRay = new Ray3();
473
+
474
+ /** @private @type {PhysicsSurfacePoint} */
475
+ _postureHit = new PhysicsSurfacePoint();
476
+
477
+ async startup(entityManager) {
478
+ this.entityManager = entityManager;
479
+ if (this.physicsSystem === null) {
480
+ const ps = entityManager.getSystem(PhysicsSystem);
481
+ if (ps !== null) this.physicsSystem = ps;
482
+ }
483
+ }
484
+
485
+ /**
486
+ * @param {FirstPersonPlayerController} controller
487
+ * @param {Transform} bodyTransform
488
+ * @param {number} entity
489
+ */
490
+ link(controller, bodyTransform, entity) {
491
+ const ecd = this.entityManager.dataset;
492
+
493
+ // The controller assumes a kinematic-position RigidBody is co-
494
+ // attached on this entity. The body is the spatial proxy used
495
+ // for sensor raycasts and physics-side observers (other entities
496
+ // raycasting against the player, dynamic bodies colliding with
497
+ // the capsule, etc.). The controller writes Transform directly,
498
+ // physics derives velocity from the per-step delta. If a body is
499
+ // missing the controller could still drive the camera, but the
500
+ // physics integration silently breaks assert here so the
501
+ // misconfiguration is caught at link time.
502
+ const rigidBody = ecd.getComponent(entity, RigidBody);
503
+ assert.ok(rigidBody !== undefined,
504
+ "FirstPersonPlayerController entity must have a co-attached RigidBody "
505
+ + "(kinematic capsule). See prototype_first_person_controller.js for setup.");
506
+ assert.equal(rigidBody.kind, BodyKind.KinematicPosition,
507
+ "FirstPersonPlayerController RigidBody must be BodyKind.KinematicPosition; "
508
+ + "the controller owns the Transform and physics derives velocity.");
509
+ // Collider is also required the KinematicMover casts this shape
510
+ // against the world (recover / sweep / ground probe). Asserted
511
+ // here so a missing collider surfaces at link rather than
512
+ // producing a null-deref at the first cast attempt.
513
+ const collider = ecd.getComponent(entity, Collider);
514
+ assert.ok(collider !== undefined,
515
+ "FirstPersonPlayerController entity must have a co-attached Collider. "
516
+ + "The controller's move-and-slide casts this shape to detect blockers.");
517
+
518
+ const runtime = new PerEntityRuntime();
519
+ runtime.rigidBody = rigidBody;
520
+ runtime.collider = collider;
521
+
522
+ // Pre-build one capsule per posture from cfg.body. Eye-height
523
+ // doubles as collider-top by convention here the prototype's
524
+ // `buildPlayerEntity` uses the same approximation (`totalHeight =
525
+ // bodyCfg.height`). The +Y offset puts the capsule bottom at
526
+ // transform.position so the player's "feet" stay anchored across
527
+ // posture changes; only the head drops/rises.
528
+ const radius = controller.config.body.radius;
529
+ runtime.colliderShapeStand = makePostureCapsule(radius, controller.config.body.height);
530
+ runtime.colliderShapeCrouch = makePostureCapsule(radius, controller.config.body.crouchHeight);
531
+ runtime.colliderShapeProne = makePostureCapsule(radius, controller.config.body.proneHeight);
532
+ // Cache each posture capsule's ACTUAL top (bbox maxY) — not the
533
+ // nominal config height. A capsule's height floors at 2·radius,
534
+ // so e.g. a 0.4 m "prone" height actually yields a 0.68 m
535
+ // capsule. The headroom up-cast sweeps by the difference of real
536
+ // tops; using config heights would over-/under-estimate the grow
537
+ // and clamp to the wrong posture (Prone instead of Crouch).
538
+ const bbox = new Float64Array(6);
539
+ runtime.colliderShapeStand.compute_bounding_box(bbox);
540
+ runtime.standTop = bbox[4];
541
+ runtime.colliderShapeCrouch.compute_bounding_box(bbox);
542
+ runtime.crouchTop = bbox[4];
543
+ runtime.colliderShapeProne.compute_bounding_box(bbox);
544
+ runtime.proneTop = bbox[4];
545
+ // Force a shape sync on the first tick: even though the caller
546
+ // built a Stand-sized collider, we rebuild it from cfg here so a
547
+ // post-link config tweak (e.g. crouchHeight changed for a unit
548
+ // test) is reflected on the live collider without a relink.
549
+ runtime.lastPosture = -1;
550
+
551
+ this.runtime.set(entity, runtime);
552
+
553
+ // Derive gravity + jump impulse from designer-friendly params, then
554
+ // mass-scale the initial velocity (heavier ⇒ lower jump).
555
+ runtime.massRatios = computeMassRatios(
556
+ controller.config.body.mass,
557
+ controller.config.body.referenceMass,
558
+ controller.config.body.massCouplingStrength,
559
+ );
560
+ const derived = { gravity: 0, initialVelocity: 0 };
561
+ computeJumpFromApex(controller.config.jump.peakHeight, controller.config.jump.timeToApex, derived);
562
+ runtime.gravity = derived.gravity;
563
+ // Express jump power as a physical push-off FORCE (newtons): the force the
564
+ // legs deliver to reach peakHeight at the REFERENCE mass. Stored so other
565
+ // moves (the scramble wall-kick) can launch off the SAME force.
566
+ const bodyCfg = controller.config.body;
567
+ runtime.jumpForce = jumpForceForHeight(
568
+ bodyCfg.referenceMass, runtime.gravity, bodyCfg.jumpPushoffDistance, controller.config.jump.peakHeight,
569
+ );
570
+ // Force → launch velocity for the reference body (== √(2·g·peakHeight)),
571
+ // then the mass-coupling dial (jumpV0Scale 1/√mass, modulated by
572
+ // massCouplingStrength) scales it for the actual body. Identical result
573
+ // to the prior `derived.initialVelocity · jumpV0Scale`, now routed
574
+ // through the force so jump power has a single physical source.
575
+ runtime.jumpInitialVy =
576
+ launchVelocityForForce(runtime.jumpForce, bodyCfg.referenceMass, bodyCfg.jumpPushoffDistance)
577
+ * runtime.massRatios.jumpV0Scale;
578
+
579
+ // Seed yaw from the starting body rotation. `toEulerAnglesYXZ`
580
+ // returns (pitch, yaw, roll) — we only care about y.
581
+ bodyTransform.rotation.toEulerAnglesYXZ(SCRATCH_V3_A);
582
+ runtime.bodyYaw = SCRATCH_V3_A.y;
583
+ runtime.eyePitch = 0;
584
+
585
+ // Seed render-rate-look + position-interpolation state to the spawn pose.
586
+ runtime.prevFixedYaw = runtime.bodyYaw;
587
+ runtime.renderPrevPos.copy(bodyTransform.position);
588
+ runtime.renderCurPos.copy(bodyTransform.position);
589
+
590
+ // Initialize springs to standing-eye-height baseline
591
+ runtime.eyeHeightSpring.settle(controller.config.body.height);
592
+ runtime.fovSpring.settle(controller.config.fov.base);
593
+ controller.state.eyeHeight = controller.config.body.height;
594
+
595
+ // Create eye entity if one wasn't supplied
596
+ if (controller.eyeEntity === -1 || !ecd.entityExists(controller.eyeEntity)) {
597
+ const eye = new Entity();
598
+
599
+ const eyeTransform = new Transform();
600
+ const baseEyePos = SCRATCH_V3_A.copy(bodyTransform.position);
601
+ baseEyePos.y += controller.config.body.height;
602
+ eyeTransform.position.copy(baseEyePos);
603
+
604
+ const camera = new Camera();
605
+ camera.active.set(true);
606
+ camera.fov.set(controller.config.fov.base);
607
+ camera.clip_near = 0.05;
608
+ camera.clip_far = 1000;
609
+ camera.autoClip = true;
610
+
611
+ eye.add(eyeTransform);
612
+ eye.add(camera);
613
+ eye.add(SerializationMetadata.Transient);
614
+
615
+ eye.build(ecd);
616
+
617
+ controller.eyeEntity = eye.id;
618
+ }
619
+
620
+ runtime.eyeEntity = controller.eyeEntity;
621
+ }
622
+
623
+ /**
624
+ * @param {FirstPersonPlayerController} controller
625
+ * @param {Transform} bodyTransform
626
+ * @param {number} entity
627
+ */
628
+ unlink(controller, bodyTransform, entity) {
629
+ const ecd = this.entityManager.dataset;
630
+
631
+ if (controller.eyeEntity !== -1 && ecd.entityExists(controller.eyeEntity)) {
632
+ ecd.removeEntity(controller.eyeEntity);
633
+ controller.eyeEntity = -1;
634
+ }
635
+
636
+ this.runtime.delete(entity);
637
+ }
638
+
639
+ /**
640
+ * Look up the per-entity runtime for an entity that has this
641
+ * controller. Used by cross-system code (sensors system, future
642
+ * ability-driven systems) to reach internal state without leaking
643
+ * it onto the controller component itself.
644
+ *
645
+ * @param {number} entity
646
+ * @returns {PerEntityRuntime|undefined} undefined if entity is not linked
647
+ */
648
+ getRuntime(entity) {
649
+ return this.runtime.get(entity);
650
+ }
651
+
652
+ /**
653
+ * Deterministic simulation step — L1 + L2 + L4.
654
+ * @param {number} dt
655
+ */
656
+ fixedUpdate(dt) {
657
+ const ecd = this.entityManager.dataset;
658
+ if (ecd === null) return;
659
+
660
+ this._currentDt = dt;
661
+ ecd.traverseComponents(FirstPersonPlayerController, this._tickEntity, this);
662
+ }
663
+
664
+ /**
665
+ * Variable-rate camera composition — L3.
666
+ * @param {number} dt
667
+ */
668
+ update(dt) {
669
+ const ecd = this.entityManager.dataset;
670
+ if (ecd === null) return;
671
+
672
+ this._currentRenderDt = dt;
673
+ ecd.traverseComponents(FirstPersonPlayerController, this._composeEye, this);
674
+ }
675
+
676
+ /**
677
+ * @private
678
+ * @param {FirstPersonPlayerController} controller
679
+ * @param {number} entity
680
+ */
681
+ _tickEntity(controller, entity) {
682
+ const ecd = this.entityManager.dataset;
683
+ const runtime = this.runtime.get(entity);
684
+ if (runtime === undefined) return;
685
+
686
+ const dt = this._currentDt;
687
+ const cfg = controller.config;
688
+ const intent = controller.intent;
689
+ const state = controller.state;
690
+ const sig = controller.signals;
691
+
692
+ const bodyTransform = ecd.getComponent(entity, Transform);
693
+ if (bodyTransform === undefined) return;
694
+
695
+ // Decay the mastery score's EMA. Doing this once per tick keeps the
696
+ // score's time-window characteristic stable regardless of how many
697
+ // evaluators fire (they each *record* a sample, the decay
698
+ // independently ages all samples).
699
+ controller.mastery.tick(dt);
700
+
701
+ // -- L1.a: Derive the per-step turn rate ------------------------
702
+ // Look (yaw/pitch) is consumed at RENDER rate in {@link _consumeLook} so
703
+ // the camera tracks the mouse without 60Hz aliasing — consuming the
704
+ // accumulated mouse delta once per fixed step beats against an unaligned
705
+ // mouse report rate and makes a slow turn lurch (the reported snap).
706
+ // fixedUpdate reads the latest yaw and derives the per-fixed-step turn
707
+ // RATE for look-lean + the turn evaluators, from the yaw change since the
708
+ // previous step (shortest-arc across the ±π wrap) — smooth and decoupled
709
+ // from per-frame input jitter.
710
+ let yawStep = runtime.bodyYaw - runtime.prevFixedYaw;
711
+ if (yawStep > Math.PI) yawStep -= TWO_PI;
712
+ else if (yawStep < -Math.PI) yawStep += TWO_PI;
713
+ runtime.yawRateRadPerSec = yawStep / Math.max(dt, 1e-4);
714
+ runtime.prevFixedYaw = runtime.bodyYaw;
715
+
716
+ // Write body yaw back to the transform (pure yaw, no pitch on body) —
717
+ // the collision sweep + sensors use it. The camera uses runtime.bodyYaw
718
+ // directly, which _consumeLook keeps current at render rate.
719
+ bodyTransform.rotation.fromAxisAngle(Vector3.up, runtime.bodyYaw);
720
+
721
+ // -- Shared flags. Computed BEFORE the ability tick so abilities
722
+ // can read them. `isCrouchActive` is deliberately computed
723
+ // AFTER the ability tick because `_resolveCrouchHeld` mutates
724
+ // `runtime.prevCrouchHeld` — abilities like Slide need to see
725
+ // the previous-tick value to detect a rising edge on the
726
+ // crouch press.
727
+ const isSprintIntent = intent.sprint && intent.move.y > 0.5 && state.grounded;
728
+ const isBackwardIntent = intent.move.y < 0;
729
+ runtime.sinYaw = Math.sin(runtime.bodyYaw);
730
+ runtime.cosYaw = Math.cos(runtime.bodyYaw);
731
+ // L2 observers read sinYaw/cosYaw as locals — destructure once.
732
+ const { sinYaw, cosYaw } = runtime;
733
+
734
+ // Age the ledge re-grab refractory window (dt-driven). Decremented
735
+ // BEFORE the ability layer so LedgeGrab.canActivate sees the current
736
+ // value; it gates the auto-catch for a beat after any dismount.
737
+ if (runtime.ledgeRegrabCooldown > 0) {
738
+ runtime.ledgeRegrabCooldown = Math.max(0, runtime.ledgeRegrabCooldown - dt);
739
+ }
740
+
741
+ // -- Ability layer: at most one active ability owns motion. The
742
+ // set returns true when no ability owned the tick, in which
743
+ // case base L1.b-h runs below; false means an ability fully
744
+ // handled this tick (it called the system's helpers for any
745
+ // standard work it wanted to keep, e.g. gravity).
746
+ const runBaseLocomotion = controller.abilities.tick(
747
+ controller, runtime, bodyTransform, runtime.sensors, dt, this,
748
+ );
749
+
750
+ // The ledge-pull-up request is set and consumed WITHIN abilities.tick
751
+ // (LedgeGrab release → Mantle activation, same call). Clear it here as
752
+ // a safety net so a pull-up that didn't activate a mantle can't leak
753
+ // the flag into a later auto-mantle (which would let it vault a thin
754
+ // wall it should have refused).
755
+ runtime.ledgePullUpRequest = false;
756
+
757
+ // Now resolve crouch (updates prevCrouchHeld) used by base and L2.
758
+ // Headroom-aware: force-keeps the player crouched under an overhang.
759
+ const isCrouchActive = this._resolveCrouchHeld(controller, runtime, bodyTransform);
760
+
761
+ if (runBaseLocomotion) {
762
+ this._runBaseLocomotion(
763
+ controller, runtime, bodyTransform, dt,
764
+ isCrouchActive, isSprintIntent, isBackwardIntent,
765
+ );
766
+ }
767
+
768
+ // (everything below this line runs every tick L2 observers don't
769
+ // care who owned motion)
770
+
771
+ // -- L2.a: speed / moveMode ------------------------------------
772
+ // -- L2.a: speed / moveMode ------------------------------------
773
+ const horizSpeed = Math.hypot(runtime.velocityX, runtime.velocityZ);
774
+ runtime.horizSpeed = horizSpeed;
775
+ state.speed = horizSpeed;
776
+ state.speedNormalized = clamp(horizSpeed / Math.max(cfg.motion.sprintSpeed, 1e-3), 0, 1);
777
+
778
+ // Backwardness: 0 = moving forward (or sideways), 1 = moving directly
779
+ // backward at the back-pedal speed ceiling. Derived from the actual
780
+ // velocity (not the intent) so external knockback or stuck states
781
+ // also register as "moving backward" and the gait wobble reflects it.
782
+ //
783
+ // Reference speed is the *achievable* backward max — walkSpeed ×
784
+ // backwardSpeedFactor NOT the sprint speed. Backward can never
785
+ // reach sprint, so normalizing against sprint would cap backwardness
786
+ // at ~0.3 and the wobble multipliers below would barely apply.
787
+ const screenFwdVel = runtime.velocityX * sinYaw + runtime.velocityZ * cosYaw;
788
+ const maxBackwardSpeed = Math.max(cfg.motion.walkSpeed * cfg.motion.backwardSpeedFactor, 1e-3);
789
+ runtime.backwardness = clamp(-screenFwdVel / maxBackwardSpeed, 0, 1);
790
+
791
+ // Locomotion mode is the *intent-driven* horizontal mode. Airborne
792
+ // state is tracked separately on pose.actionState they're
793
+ // orthogonal facets (you can be Sprint+Airborne after a jump).
794
+ const prevLocomotionMode = state.locomotionMode;
795
+ if (isCrouchActive) {
796
+ state.locomotionMode = FirstPersonLocomotionMode.Crouch;
797
+ } else if (isSprintIntent && horizSpeed > 0.1) {
798
+ state.locomotionMode = FirstPersonLocomotionMode.Sprint;
799
+ } else if (horizSpeed > 0.1) {
800
+ state.locomotionMode = FirstPersonLocomotionMode.Walk;
801
+ } else {
802
+ state.locomotionMode = FirstPersonLocomotionMode.Idle;
803
+ }
804
+
805
+ if (state.locomotionMode === FirstPersonLocomotionMode.Sprint
806
+ && prevLocomotionMode !== FirstPersonLocomotionMode.Sprint) {
807
+ sig.onSprintStart.send0();
808
+ } else if (prevLocomotionMode === FirstPersonLocomotionMode.Sprint
809
+ && state.locomotionMode !== FirstPersonLocomotionMode.Sprint) {
810
+ sig.onSprintStop.send0();
811
+ }
812
+
813
+ // -- L2.b: Exertion --------------------------------------------
814
+ // Heavier bodies tire faster — sprint rise scales with massRatios.exertionRiseScale.
815
+ const exertionRise = isSprintIntent
816
+ ? cfg.exertion.sprintRiseRate * runtime.massRatios.exertionRiseScale
817
+ : 0;
818
+ const exertionFall = exertionRise > 0 ? 0 : cfg.exertion.idleDecayRate;
819
+ state.exertion = clamp(state.exertion + (exertionRise - exertionFall) * dt, 0, 1);
820
+
821
+ // -- L2.c: Breath ----------------------------------------------
822
+ // breathRate and breathAmplitude lag exertion through separate
823
+ // exponential decays. Rate hangs around longer than amplitude.
824
+ const metabolicRate = lerp(cfg.breath.rateRestHz, cfg.breath.rateMaxHz, state.exertion);
825
+ const targetAmp = lerp(cfg.breath.amplitudeRestM, cfg.breath.amplitudeMaxM, state.exertion);
826
+
827
+ // Locomotor-respiratory coupling see math/computeLRCBreathRate.
828
+ // The pure function is unit-tested; this site just provides inputs.
829
+ //
830
+ // Gait is gated on a "feet strike the ground" posture (Stand /
831
+ // Crouch). Prone (slide) and Hang (ledge-grab) have no stride —
832
+ // the body's feet are not making contact in a walking pattern,
833
+ // so stride frequency drops to zero and downstream gait
834
+ // signals (footsteps, bob intensity) go quiet.
835
+ const feetStriking = state.posture === FirstPersonPosture.Stand
836
+ || state.posture === FirstPersonPosture.Crouch;
837
+ const strideFreqHz = feetStriking && state.grounded && horizSpeed > cfg.bob.minStepSpeed
838
+ ? cfg.bob.stepFreqAtWalk * Math.pow(
839
+ Math.max(horizSpeed, 1e-3) / Math.max(cfg.motion.walkSpeed, 1e-3),
840
+ cfg.bob.stepFreqExp,
841
+ )
842
+ : 0;
843
+ const targetRate = computeLRCBreathRate(
844
+ metabolicRate,
845
+ strideFreqHz,
846
+ state.exertion,
847
+ cfg.breath.locomotorCouplingMax,
848
+ cfg.breath.couplingMinStrideFreqHz,
849
+ );
850
+ state.breathRateHz = exponentialApproach(state.breathRateHz, targetRate, cfg.exertion.rateDecayHalfLife, dt);
851
+ state.breathAmplitudeM = exponentialApproach(state.breathAmplitudeM, targetAmp, cfg.exertion.ampDecayHalfLife, dt);
852
+
853
+ runtime.prevBreathPhase = state.breathPhase;
854
+ state.breathPhase += state.breathRateHz * dt;
855
+ state.breathPhase -= Math.floor(state.breathPhase); // wrap [0,1)
856
+
857
+ // Breath edge detection — inhale at 0.25, exhale at 0.75
858
+ if (phaseCrossed(runtime.prevBreathPhase, state.breathPhase, 0.25)) {
859
+ sig.onBreathIn.send1({ amplitude: state.breathAmplitudeM, rateHz: state.breathRateHz });
860
+ }
861
+ if (phaseCrossed(runtime.prevBreathPhase, state.breathPhase, 0.75)) {
862
+ sig.onBreathOut.send1({ amplitude: state.breathAmplitudeM, rateHz: state.breathRateHz });
863
+ }
864
+
865
+ // -- L2.d: Stride ----------------------------------------------
866
+ // strideFreqHz computed above in the breath block; reused here.
867
+ runtime.prevStridePhase = state.stridePhase;
868
+ if (strideFreqHz > 0) {
869
+ // 1 full stride cycle = 2 footfalls; phase advances at freq/2 of cycle
870
+ state.stridePhase += (strideFreqHz * 0.5) * dt;
871
+ state.stridePhase -= Math.floor(state.stridePhase);
872
+ }
873
+ // Footstep on phase wraparound past 0 (R) or past 0.5 (L). Same
874
+ // posture gate as stride advance — feet must be striking.
875
+ if (feetStriking && state.grounded && horizSpeed > cfg.bob.minStepSpeed) {
876
+ const fireFootstep = () => {
877
+ state.stepCount++;
878
+ const side = runtime.nextFootSide;
879
+ runtime.nextFootSide = side === "R" ? "L" : "R";
880
+ // The foot that just fired is now the one bearing weight
881
+ // through the upcoming half-stride. Drives lateral-bob sign.
882
+ runtime.standingFoot = side;
883
+ sig.onFootStep.send1({ side, speed: horizSpeed, surfaceTag: state.surfaceTag });
884
+ // Kick the vertical impact spring DOWNWARD. The kick magnitude
885
+ // is the per-step desired peak dip × impactKickMultiplier; the
886
+ // multiplier is empirical (depends on impact spring params) so
887
+ // that "verticalAmpAtWalk" still corresponds approximately to
888
+ // the visible peak dip depth. Scaled by bobIntensity so a
889
+ // mid-deceleration footstep doesn't deliver a full-strength
890
+ // impulse.
891
+ const massBoost = (cfg.body.mass - 80) * cfg.bob.ampMassScale;
892
+ const ampVMult = 1 + (cfg.bob.backwardVerticalAmpFactor - 1) * runtime.backwardness;
893
+ const peakDip = (cfg.bob.verticalAmpAtWalk + massBoost) * runtime.bobIntensitySpring.value * ampVMult;
894
+ runtime.verticalImpactSpring.kick(-peakDip * cfg.bob.impactKickMultiplier);
895
+ };
896
+ if (phaseCrossed(runtime.prevStridePhase, state.stridePhase, 0)) {
897
+ fireFootstep();
898
+ }
899
+ if (phaseCrossed(runtime.prevStridePhase, state.stridePhase, 0.5)) {
900
+ fireFootstep();
901
+ }
902
+ }
903
+
904
+ // -- L2.d.bob-intensity & impact -------------------------------
905
+ // Smoothed bob amplitude envelope: when the player starts/stops
906
+ // moving the visible bob fades in/out rather than cutting on/off.
907
+ // Target = the "natural" amp scale (max of speed and backwardness)
908
+ // while grounded, zero while airborne so the bob disappears mid-jump.
909
+ const naturalBobIntensity = Math.max(state.speedNormalized, runtime.backwardness);
910
+ // Bob fades to zero whenever feet aren't striking (airborne, or
911
+ // Prone/Hang posture). The verticalImpactSpring (separate
912
+ // channel) still carries any entry/landing kicks through to the
913
+ // camera, but no recurring step bob.
914
+ const targetBobIntensity = (state.grounded && feetStriking) ? naturalBobIntensity : 0;
915
+ runtime.bobIntensitySpring.stepTo(targetBobIntensity, cfg.bob.intensityHalfLife, 1.0, dt);
916
+
917
+ // Vertical impact spring damped decay toward 0, with the under-
918
+ // damped overshoot that produces the recovery + leg-push curve.
919
+ runtime.verticalImpactSpring.stepTo(0, cfg.bob.impactSpringHalfLife, cfg.bob.impactSpringZeta, dt);
920
+
921
+ // Sprint posture — head pitches forward as commitment to sprint
922
+ // builds. Driven by "sprintness" — how much of the gap between
923
+ // walk and sprint speed the player is *currently* in (0..1). The
924
+ // pitch target is multiplied by sprintness, then critically damped.
925
+ // Only applies while grounded pitching into airborne motion looks weird.
926
+ const sprintness = clamp(
927
+ (state.speed - cfg.motion.walkSpeed)
928
+ / Math.max(cfg.motion.sprintSpeed - cfg.motion.walkSpeed, 1e-3),
929
+ 0, 1,
930
+ );
931
+ const targetSprintPitch = state.grounded
932
+ ? cfg.posture.sprintForwardPitchDeg * DEG_TO_RAD * sprintness
933
+ : 0;
934
+ runtime.sprintPostureSpring.stepTo(
935
+ targetSprintPitch,
936
+ cfg.posture.sprintForwardPitchHalfLife,
937
+ 1.0, dt,
938
+ );
939
+ runtime.sprintness = sprintness;
940
+
941
+ // Head droop — exertion drives a subtle additional forward pitch.
942
+ // Combines with sprintPostureSpring (sprint = head down to commit)
943
+ // so a fatigued sprinter has BOTH effects layered.
944
+ const targetDroopRad = cfg.exertion.headDroopAtMaxDeg * DEG_TO_RAD * state.exertion;
945
+ runtime.headDroopSpring.stepTo(targetDroopRad, cfg.exertion.headDroopHalfLife, 1.0, dt);
946
+
947
+ // -- L2.e: Posture → eye height --------------------------------
948
+ // Posture is set by whichever layer owned motion this tick: base
949
+ // writes Stand / Crouch from isCrouchActive (see end of
950
+ // _runBaseLocomotion); active abilities write Prone (Slide) or
951
+ // Hang (LedgeGrab) in their tick. Mapping is one switch — adding
952
+ // a new posture is one enum value + one case.
953
+ let targetEyeH;
954
+ switch (state.posture) {
955
+ case FirstPersonPosture.Prone: targetEyeH = cfg.body.proneHeight; break;
956
+ case FirstPersonPosture.Crouch: targetEyeH = cfg.body.crouchHeight; break;
957
+ case FirstPersonPosture.Hang: targetEyeH = cfg.body.height; break;
958
+ case FirstPersonPosture.Stand:
959
+ default: targetEyeH = cfg.body.height; break;
960
+ }
961
+ const crouchHalfLife = cfg.crouch.transitionTime / 4; // halfLife is ~quarter of full transition
962
+ runtime.eyeHeightSpring.stepTo(targetEyeH, crouchHalfLife, 1.0, dt);
963
+ state.eyeHeight = runtime.eyeHeightSpring.value;
964
+
965
+ if (isCrouchActive !== state.crouchActive) {
966
+ state.crouchActive = isCrouchActive;
967
+ if (isCrouchActive) {
968
+ sig.onCrouchEnter.send0();
969
+ // Impulse: dropping into a crouch grips the knees. Small
970
+ // bump — we don't want crouch-spamming to instantly tire.
971
+ state.exertion = clamp(
972
+ state.exertion + cfg.exertion.crouchEnterRise * runtime.massRatios.exertionRiseScale,
973
+ 0, 1,
974
+ );
975
+ } else {
976
+ sig.onCrouchExit.send0();
977
+ }
978
+ }
979
+
980
+ // -- L2.f: Lean spring camera roll ---------------------------
981
+ // The TARGET for this tick was written by whichever layer owned
982
+ // motion: base writes the lat-accel + look-lean derived value at
983
+ // the end of _runBaseLocomotion; abilities override (WallRun
984
+ // tilts toward the wall; Slide / LedgeGrab / Mantle force zero).
985
+ // L2.f is now a flat spring-step + commit — no branching, no
986
+ // null sentinel.
987
+ runtime.prevVelocityX = runtime.velocityX;
988
+ runtime.prevVelocityZ = runtime.velocityZ;
989
+ runtime.leanSpring.stepTo(runtime.leanTargetRad, cfg.lean.spring.halfLife, cfg.lean.spring.zeta, dt);
990
+ state.leanRollRad = runtime.leanSpring.value;
991
+
992
+ // -- L2.g: Land spring decay (drives the landing recovery dip) -
993
+ // Target is 0; under-damped (cfg zeta < 1) so it rings.
994
+ runtime.landSpring.stepTo(0, cfg.landing.recovery.spring.halfLife, cfg.landing.recovery.spring.zeta, dt);
995
+
996
+ // -- L2.h: Publish pose channels --------------------------------
997
+ this._publishPose(controller, runtime, bodyTransform);
998
+
999
+ // -- L2.i: Sync collider shape to posture -----------------------
1000
+ // All posture-writers (base locomotion + any active ability)
1001
+ // have run for this tick. Swap the collider's shape to the
1002
+ // pre-built capsule matching the final posture so downstream
1003
+ // physics queries (move-and-slide cast, sensors, overlap from
1004
+ // outside) see the right volume. No-op when posture is
1005
+ // unchanged.
1006
+ this._syncColliderShape(runtime, state.posture);
1007
+ }
1008
+
1009
+ /**
1010
+ * @private
1011
+ * @param {FirstPersonPlayerController} controller
1012
+ * @param {PerEntityRuntime} runtime
1013
+ * @returns {boolean}
1014
+ */
1015
+ /**
1016
+ * Swap {@link Collider.shape} to the pre-built capsule that matches
1017
+ * the player's current posture. Cheap — just a reference swap when
1018
+ * the posture changed, no-op otherwise. The pre-built shapes live
1019
+ * on the runtime (see {@link PerEntityRuntime.colliderShapeStand}
1020
+ * etc.) so this method allocates nothing per tick.
1021
+ *
1022
+ * Hang posture reuses Stand: the player's body is full-extent,
1023
+ * hanging below the ledge — the rig handles the arms-up animation,
1024
+ * but the collision volume is unchanged. If a game ever wants a
1025
+ * narrower hang silhouette (e.g. wedging into a chimney) it can
1026
+ * add a `colliderShapeHang` and route here.
1027
+ *
1028
+ * @private
1029
+ */
1030
+ _syncColliderShape(runtime, posture) {
1031
+ if (posture === runtime.lastPosture) return;
1032
+ let next;
1033
+ if (posture === FirstPersonPosture.Crouch) {
1034
+ next = runtime.colliderShapeCrouch;
1035
+ } else if (posture === FirstPersonPosture.Prone) {
1036
+ next = runtime.colliderShapeProne;
1037
+ } else {
1038
+ // Stand and Hang share the full-extent capsule.
1039
+ next = runtime.colliderShapeStand;
1040
+ }
1041
+ runtime.collider.shape = next;
1042
+ runtime.lastPosture = posture;
1043
+ }
1044
+
1045
+ _resolveCrouchHeld(controller, runtime, bodyTransform) {
1046
+ const cfg = controller.config;
1047
+ const intent = controller.intent;
1048
+ const state = controller.state;
1049
+
1050
+ let wantsCrouch;
1051
+ if (cfg.crouch.mode === "toggle") {
1052
+ // Edge: rising press flips the latch
1053
+ if (intent.crouch && !runtime.prevCrouchHeld) {
1054
+ runtime.crouchLatched = !runtime.crouchLatched;
1055
+ }
1056
+ runtime.prevCrouchHeld = intent.crouch;
1057
+ wantsCrouch = runtime.crouchLatched;
1058
+ } else {
1059
+ // "hold" mode
1060
+ runtime.prevCrouchHeld = intent.crouch;
1061
+ wantsCrouch = intent.crouch;
1062
+ }
1063
+
1064
+ // Headroom override — block UN-crouching under an overhang. A player who
1065
+ // is ALREADY low (crouched or prone) and wants to stand is force-kept
1066
+ // crouched while the Stand collider won't fit: letting it grow into the
1067
+ // overhang would have the next tick's depenetration shove the player out
1068
+ // the bottom, through the floor. `crouchBlocked` records this for HUD /
1069
+ // camera.
1070
+ //
1071
+ // Crucially this fires ONLY when already low. A STANDING player who walks
1072
+ // under a low ceiling is NEVER auto-pushed down into a crouch — their
1073
+ // full-height capsule simply can't fit, so the mover blocks their forward
1074
+ // motion instead. "If I can't clear it, I don't move forward." Dropping
1075
+ // posture is the player's choice (press crouch), not the ceiling's.
1076
+ const alreadyLow = runtime.lastPosture === FirstPersonPosture.Crouch
1077
+ || runtime.lastPosture === FirstPersonPosture.Prone;
1078
+ if (!wantsCrouch && alreadyLow) {
1079
+ const growDelta = runtime.standTop - runtime.crouchTop;
1080
+ if (!this._hasHeadroomToGrow(runtime, bodyTransform, runtime.colliderShapeCrouch, growDelta)) {
1081
+ state.crouchBlocked = true;
1082
+ return true;
1083
+ }
1084
+ }
1085
+ state.crouchBlocked = false;
1086
+ return wantsCrouch;
1087
+ }
1088
+
1089
+ /**
1090
+ * Headroom probe for a posture change that GROWS the collider.
1091
+ * Sweeps `fromShape` (the shorter posture's capsule) straight up by
1092
+ * `growDelta` (the height the collider would gain). A hit means an
1093
+ * overhang occupies the space the taller posture needs, so the grow
1094
+ * is blocked. Returns true (clear) when no physics is wired or the
1095
+ * grow is non-positive.
1096
+ *
1097
+ * Why an up-cast rather than an overlap-test of the taller shape:
1098
+ * overlap would false-positive on a wall *beside* the player. The
1099
+ * up-cast sweeps parallel to (and, per the mover's skin clearance,
1100
+ * clear of) lateral walls so they aren't hit; the floor is below
1101
+ * the rising probe; the player's own body is filtered out. Only
1102
+ * genuine overhead geometry blocks the grow. Relies on the player
1103
+ * resting at `floor+skin` (the mover's stick-to-ground), so the
1104
+ * probe's bottom starts clear of the floor.
1105
+ *
1106
+ * @private
1107
+ */
1108
+ _hasHeadroomToGrow(runtime, bodyTransform, fromShape, growDelta) {
1109
+ if (this.physicsSystem === null || growDelta <= 1e-4) return true;
1110
+ const ownCollider = runtime.collider;
1111
+ const filter = (_e, c) => c !== ownCollider;
1112
+ const ray = this._postureRay;
1113
+ ray.setOrigin(bodyTransform.position.x, bodyTransform.position.y, bodyTransform.position.z);
1114
+ ray.setDirection(0, 1, 0);
1115
+ ray.tMax = growDelta;
1116
+ return !this.physicsSystem.shapeCast(ray, fromShape, bodyTransform.rotation, this._postureHit, filter);
1117
+ }
1118
+
1119
+ /**
1120
+ * Jump finite-state-machine: button-edge detection, buffer + coyote
1121
+ * grace, anticipation timer, impulse on completion. Variable-height
1122
+ * cut is captured here as a `state.isVariableJumpCut` flag that the
1123
+ * gravity step in `_integrateVerticalAndResolveGround` consumes.
1124
+ *
1125
+ * @private
1126
+ * @param {FirstPersonPlayerController} controller
1127
+ * @param {PerEntityRuntime} runtime
1128
+ * @param {Transform} bodyTransform
1129
+ * @param {number} dt
1130
+ */
1131
+ _advanceJumpFsm(controller, runtime, bodyTransform, dt) {
1132
+ const cfg = controller.config;
1133
+ const intent = controller.intent;
1134
+ const state = controller.state;
1135
+ const sig = controller.signals;
1136
+
1137
+ const jumpPressedEdge = intent.jump && !runtime.prevJumpHeld;
1138
+ const jumpReleasedEdge = !intent.jump && runtime.prevJumpHeld;
1139
+ runtime.prevJumpHeld = intent.jump;
1140
+
1141
+ if (jumpPressedEdge) {
1142
+ state.jumpBufferRemaining = cfg.jump.bufferTime;
1143
+ }
1144
+ state.jumpBufferRemaining = Math.max(0, state.jumpBufferRemaining - dt);
1145
+
1146
+ const canJumpNow =
1147
+ (state.grounded || state.timeSinceGrounded < cfg.jump.coyoteTime)
1148
+ && state.jumpBufferRemaining > 0
1149
+ && !state.inJumpAnticipation
1150
+ && !runtime.midJump;
1151
+
1152
+ if (canJumpNow) {
1153
+ // Begin anticipation squash; impulse fires after duration elapses
1154
+ state.inJumpAnticipation = true;
1155
+ runtime.anticipationRemaining = cfg.jump.anticipation.duration;
1156
+ state.jumpBufferRemaining = 0; // claimed
1157
+ }
1158
+
1159
+ // Variable-height cut: only valid during ascent, post-launch.
1160
+ if (jumpReleasedEdge && runtime.midJump && runtime.velocityY > 0) {
1161
+ state.isVariableJumpCut = true;
1162
+ }
1163
+
1164
+ // Anticipation timer; impulse on completion.
1165
+ //
1166
+ // Anticipation completes regardless of grounded state. The reason
1167
+ // we DON'T cancel on `!grounded`: the canonical coyote-jump path
1168
+ // depends on it. The player walks off a ledge (grounded → false),
1169
+ // presses jump within the coyote window, canJumpNow accepts on
1170
+ // the coyote branch and starts anticipation. If we cancelled
1171
+ // anticipation here on !grounded, the impulse would never fire
1172
+ // and "coyote time" would be silently dead — the FSM's own next-
1173
+ // statement contradicting the canJumpNow gate three lines up.
1174
+ //
1175
+ // The same logic handles the rug-pull case (player on a moving
1176
+ // platform that slides out mid-anticipation): the player
1177
+ // committed to the jump, they get the jump. A future
1178
+ // knockback / stagger system can explicitly clear
1179
+ // inJumpAnticipation if it wants to override that commitment.
1180
+ if (state.inJumpAnticipation) {
1181
+ runtime.anticipationRemaining -= dt;
1182
+ if (runtime.anticipationRemaining <= 0) {
1183
+ // Mastery: gather a multiplier from all evaluators
1184
+ // registered for JumpImpulse. Default (no evaluators)
1185
+ // returns 1.0 → unchanged behaviour.
1186
+ const masteryMul = controller.mastery.evaluate(
1187
+ DecisionPoint.JumpImpulse, controller, runtime,
1188
+ );
1189
+ runtime.velocityY = runtime.jumpInitialVy * masteryMul;
1190
+ runtime.midJump = true;
1191
+ runtime.apexFired = false;
1192
+ runtime.peakAltitude = bodyTransform.position.y;
1193
+ state.inJumpAnticipation = false;
1194
+ state.isVariableJumpCut = false;
1195
+ state.isAscending = true;
1196
+ state.exertion = clamp(
1197
+ state.exertion + cfg.exertion.jumpRise * runtime.massRatios.exertionRiseScale,
1198
+ 0, 1,
1199
+ );
1200
+
1201
+ sig.onJumpStart.send1({ peakHeight: cfg.jump.peakHeight });
1202
+ // NOTE: onLeaveGround is fired by the motor (_onLeaveGround)
1203
+ // when the move actually breaks ground contact this same tick —
1204
+ // with reason "jump" since midJump is now true. Firing it here
1205
+ // too double-sent the signal on every grounded jump (and a
1206
+ // spurious second "jump" leave on a coyote jump, which already
1207
+ // left ground on the walk-off). Let the motor own it.
1208
+ }
1209
+ }
1210
+ }
1211
+
1212
+ /**
1213
+ * The vertical + collision phase of one fixed step: apply gravity
1214
+ * (the motor), resolve the move against the world, then detect the
1215
+ * jump apex. Collision resolution is the {@link KinematicMover} when
1216
+ * a physics layer is present; otherwise a lightweight flat-ground
1217
+ * integrator for headless / unit-test scenes with no collidable
1218
+ * geometry. Land / leave-ground events flow identically from either.
1219
+ *
1220
+ * @private
1221
+ * @param {FirstPersonPlayerController} controller
1222
+ * @param {PerEntityRuntime} runtime
1223
+ * @param {Transform} bodyTransform
1224
+ * @param {number} dt
1225
+ */
1226
+ _integrateVerticalAndResolveGround(controller, runtime, bodyTransform, dt) {
1227
+ this._applyGravity(controller, runtime, dt);
1228
+ this._resolveMotion(controller, runtime, bodyTransform, dt);
1229
+ this._detectJumpApex(controller, runtime, bodyTransform);
1230
+ }
1231
+
1232
+ /**
1233
+ * Resolve the current `runtime.velocity*` against the world — the
1234
+ * physics-backed {@link KinematicMover} (recover + sweep-and-slide +
1235
+ * ground-categorize) when a PhysicsSystem is present, else the no-physics
1236
+ * flat-ground fallback. Gravity is NOT applied here: the standard path
1237
+ * applies it in {@link _applyGravity} just before, and abilities with a
1238
+ * non-standard vertical model (WallRun's reduced gravity) apply their own
1239
+ * and then call this directly — so motion routes through one motor for
1240
+ * everyone (sweep-and-slide, anti-tunnel, land/leave events) rather than
1241
+ * a bespoke `position._add` + hand-rolled ground-catch.
1242
+ * @private
1243
+ */
1244
+ _resolveMotion(controller, runtime, bodyTransform, dt) {
1245
+ if (this.physicsSystem !== null) {
1246
+ this._moveViaMover(controller, runtime, bodyTransform, dt);
1247
+ } else {
1248
+ this._moveFlatGround(controller, runtime, bodyTransform, dt);
1249
+ }
1250
+ }
1251
+
1252
+ /**
1253
+ * Motor: integrate gravity into `velocityY` with the fall / variable-
1254
+ * cut multipliers. The mover never invents motion, so gravity lives
1255
+ * here (callable by abilities that want standard gravity too).
1256
+ * @private
1257
+ */
1258
+ _applyGravity(controller, runtime, dt) {
1259
+ const cfg = controller.config;
1260
+ const state = controller.state;
1261
+ let gMag = runtime.gravity;
1262
+ if (runtime.velocityY <= 0) {
1263
+ gMag *= cfg.jump.fallGravityMult;
1264
+ state.isAscending = false;
1265
+ } else if (state.isVariableJumpCut) {
1266
+ gMag *= cfg.jump.cutGravityMult;
1267
+ }
1268
+ runtime.velocityY -= gMag * dt;
1269
+ }
1270
+
1271
+ /**
1272
+ * Resolve the move against the world via {@link KinematicMover}
1273
+ * (recover + unified sweep-and-slide + ground-categorize), then map
1274
+ * the result back to controller state and land / leave events.
1275
+ * @private
1276
+ */
1277
+ _moveViaMover(controller, runtime, bodyTransform, dt) {
1278
+ const state = controller.state;
1279
+
1280
+ if (this._mover === null) {
1281
+ this._mover = new KinematicMover(this.physicsSystem, this.entityManager.dataset);
1282
+ }
1283
+
1284
+ // Fall speed captured BEFORE the move — the mover clips velocityY
1285
+ // to ~0 on contact, so the land event needs the pre-move value.
1286
+ const impactVy = -runtime.velocityY;
1287
+
1288
+ const v = this._moverVelocity;
1289
+ v.set(runtime.velocityX, runtime.velocityY, runtime.velocityZ);
1290
+
1291
+ const ownCollider = runtime.collider;
1292
+ const filter = (_e, c) => c !== ownCollider;
1293
+
1294
+ const result = this._mover.move(
1295
+ bodyTransform.position,
1296
+ bodyTransform.rotation,
1297
+ runtime.collider.shape,
1298
+ v, dt, filter,
1299
+ );
1300
+
1301
+ // Read back corrected horizontal velocity. Vertical follows the
1302
+ // control policy: zero on ground (the slope-climb the mover
1303
+ // produced is a position effect, not accumulated speed), keep the
1304
+ // mover-clipped value in the air (captures ceiling bonks).
1305
+ runtime.velocityX = v.x;
1306
+ runtime.velocityZ = v.z;
1307
+ runtime.velocityY = result.grounded ? 0 : v.y;
1308
+
1309
+ const wasGrounded = state.grounded;
1310
+ if (result.grounded) {
1311
+ if (!wasGrounded) this._onLand(controller, runtime, impactVy);
1312
+ state.grounded = true;
1313
+ state.groundNormal.copy(result.groundNormal);
1314
+ state.verticalSpeed = 0;
1315
+ state.airborneTime = 0;
1316
+ state.timeSinceGrounded = 0;
1317
+ } else {
1318
+ if (wasGrounded) this._onLeaveGround(controller, runtime, bodyTransform);
1319
+ state.grounded = false;
1320
+ state.verticalSpeed = runtime.velocityY;
1321
+ state.airborneTime += dt;
1322
+ state.timeSinceGrounded += dt;
1323
+ state.fallDistance += Math.max(0, -runtime.velocityY * dt);
1324
+ }
1325
+ }
1326
+
1327
+ /**
1328
+ * No-physics fallback: integrate the velocity directly (there's no
1329
+ * collidable geometry to sweep against) and resolve the floor from
1330
+ * the built-in flat ground and/or the host `groundResolver`. For
1331
+ * headless scenes and control-layer unit tests. With both ground
1332
+ * sources off, an external physics layer is expected to own
1333
+ * `state.grounded`; we only advance timers.
1334
+ * @private
1335
+ */
1336
+ _moveFlatGround(controller, runtime, bodyTransform, dt) {
1337
+ const state = controller.state;
1338
+
1339
+ bodyTransform.position._add(
1340
+ runtime.velocityX * dt, runtime.velocityY * dt, runtime.velocityZ * dt,
1341
+ );
1342
+
1343
+ if (this.useBuiltInFlatGround || this.groundResolver !== null) {
1344
+ // Effective ground = max(built-in flat ground, resolver).
1345
+ let testY = this.useBuiltInFlatGround ? this.groundY : Number.NEGATIVE_INFINITY;
1346
+ if (this.groundResolver !== null) {
1347
+ const resolved = this.groundResolver(
1348
+ bodyTransform.position.x, bodyTransform.position.y, bodyTransform.position.z,
1349
+ );
1350
+ if (resolved !== null && resolved > testY) testY = resolved;
1351
+ }
1352
+ const haveGround = testY !== Number.NEGATIVE_INFINITY;
1353
+ if (haveGround && bodyTransform.position.y <= testY) {
1354
+ bodyTransform.position.setY(testY);
1355
+ if (!state.grounded) this._onLand(controller, runtime, -runtime.velocityY);
1356
+ state.grounded = true;
1357
+ state.groundNormal.set(0, 1, 0);
1358
+ state.verticalSpeed = 0;
1359
+ runtime.velocityY = 0;
1360
+ state.airborneTime = 0;
1361
+ state.timeSinceGrounded = 0;
1362
+ } else {
1363
+ if (state.grounded) this._onLeaveGround(controller, runtime, bodyTransform);
1364
+ state.grounded = false;
1365
+ state.verticalSpeed = runtime.velocityY;
1366
+ state.airborneTime += dt;
1367
+ state.timeSinceGrounded += dt;
1368
+ state.fallDistance += Math.max(0, -runtime.velocityY * dt);
1369
+ }
1370
+ } else {
1371
+ // External physics maintains state.grounded; just track timers.
1372
+ if (state.grounded) {
1373
+ state.timeSinceGrounded = 0;
1374
+ state.airborneTime = 0;
1375
+ } else {
1376
+ state.timeSinceGrounded += dt;
1377
+ state.airborneTime += dt;
1378
+ }
1379
+ }
1380
+ }
1381
+
1382
+ /**
1383
+ * Landing transition: dip / impact springs, exertion impulse, flag
1384
+ * resets, and the `onLand` signal (fired LAST so handlers see fully-
1385
+ * reacted state). Shared by both move paths. `impactVy` is the
1386
+ * downward speed at touchdown (positive).
1387
+ * @private
1388
+ */
1389
+ _onLand(controller, runtime, impactVy) {
1390
+ const cfg = controller.config;
1391
+ const state = controller.state;
1392
+ const kind = impactVy >= cfg.landing.hardThreshold ? "hard" : "soft";
1393
+
1394
+ const massScaledDip = impactVy * cfg.landing.recovery.dipPerVy
1395
+ * runtime.massRatios.landingDipScale;
1396
+ const dip = clamp(massScaledDip, 0, cfg.landing.recovery.dipMax);
1397
+ runtime.landSpring.settle(-dip);
1398
+
1399
+ const landImpulse = clamp(
1400
+ impactVy * cfg.exertion.landImpulsePerVy * runtime.massRatios.exertionRiseScale,
1401
+ 0, cfg.exertion.landImpulseMax,
1402
+ );
1403
+ state.exertion = clamp(state.exertion + landImpulse, 0, 1);
1404
+
1405
+ runtime.midJump = false;
1406
+ state.isAscending = false;
1407
+ state.isVariableJumpCut = false;
1408
+ state.fallDistance = 0;
1409
+
1410
+ controller.signals.onLand.send1({ verticalSpeed: impactVy, kind });
1411
+ }
1412
+
1413
+ /**
1414
+ * Leave-ground transition: `onLeaveGround` signal + takeoff bookkeeping.
1415
+ * Shared by both move paths.
1416
+ * @private
1417
+ */
1418
+ _onLeaveGround(controller, runtime, bodyTransform) {
1419
+ controller.signals.onLeaveGround.send1({ reason: runtime.midJump ? "jump" : "fall" });
1420
+ runtime.takeoffVy = runtime.velocityY;
1421
+ runtime.peakAltitude = bodyTransform.position.y;
1422
+ }
1423
+
1424
+ /**
1425
+ * Fire `onJumpApex` once, when an in-progress jump stops rising.
1426
+ * @private
1427
+ */
1428
+ _detectJumpApex(controller, runtime, bodyTransform) {
1429
+ if (runtime.midJump && !runtime.apexFired) {
1430
+ if (bodyTransform.position.y > runtime.peakAltitude) {
1431
+ runtime.peakAltitude = bodyTransform.position.y;
1432
+ } else if (runtime.velocityY <= 0) {
1433
+ controller.signals.onJumpApex.send0();
1434
+ runtime.apexFired = true;
1435
+ }
1436
+ }
1437
+ }
1438
+
1439
+ /**
1440
+ * Run the base (no-ability) L1 locomotion phases: speed selection,
1441
+ * desired-velocity computation, accel/decel, jump FSM, gravity, body
1442
+ * integration, ground resolution. Only invoked when no ability owns
1443
+ * the tick (see {@link AbilitySet.tick}).
1444
+ *
1445
+ * @private
1446
+ * @param {FirstPersonPlayerController} controller
1447
+ * @param {PerEntityRuntime} runtime
1448
+ * @param {Transform} bodyTransform
1449
+ * @param {number} dt
1450
+ * @param {boolean} isCrouchActive
1451
+ * @param {boolean} isSprintIntent
1452
+ * @param {boolean} isBackwardIntent
1453
+ */
1454
+ _runBaseLocomotion(controller, runtime, bodyTransform, dt,
1455
+ isCrouchActive, isSprintIntent, isBackwardIntent) {
1456
+ const cfg = controller.config;
1457
+ const intent = controller.intent;
1458
+ const state = controller.state;
1459
+
1460
+ // -- L1.b: Speed selection ------------------------------------
1461
+ let targetSpeed;
1462
+ if (isCrouchActive) {
1463
+ targetSpeed = cfg.motion.crouchSpeed;
1464
+ } else if (isSprintIntent) {
1465
+ targetSpeed = cfg.motion.sprintSpeed;
1466
+ } else {
1467
+ targetSpeed = cfg.motion.walkSpeed;
1468
+ }
1469
+ if (isBackwardIntent) {
1470
+ targetSpeed *= cfg.motion.backwardSpeedFactor;
1471
+ }
1472
+
1473
+ // Airborne momentum floor — preserve whatever horizontal speed
1474
+ // the player carried into the jump. Without this, a sprint
1475
+ // jump (9 m/s) decays toward walkSpeed (4.5 m/s) at
1476
+ // airAccel = 14 m/s², losing all sprint momentum in ~0.32 s —
1477
+ // well before the apex of a `peakHeight = 1.8 m` jump arc. The
1478
+ // air-control band (Mirror's Edge, Titanfall, modern CoD) and
1479
+ // the long-jump biomechanics literature both say the same
1480
+ // thing: there's no thrust source in flight, so horizontal
1481
+ // velocity is conserved across the arc and air "control" is
1482
+ // for steering (direction) — not for changing speed magnitude.
1483
+ // Raising the target to the current speed makes `stepTowards`
1484
+ // a no-op when the player keeps pressing forward, while
1485
+ // releasing the stick still lets `airAccel` decelerate to
1486
+ // `walkSpeed` (the user CAN bleed off speed, just not have it
1487
+ // bled off for them).
1488
+ if (!state.grounded) {
1489
+ const horizSpeed = Math.hypot(runtime.velocityX, runtime.velocityZ);
1490
+ if (horizSpeed > targetSpeed) targetSpeed = horizSpeed;
1491
+ }
1492
+
1493
+ // -- L1.c: Move intent desired horizontal velocity ----------
1494
+ // screen_forward(θ) = ( sin θ, 0, cos θ )
1495
+ // screen_right (θ) = (-cos θ, 0, sin θ )
1496
+ const { sinYaw, cosYaw } = runtime;
1497
+ const mvX = intent.move.x;
1498
+ const mvY = intent.move.y;
1499
+ const mvMag = Math.hypot(mvX, mvY);
1500
+ const nmvX = mvMag > 1 ? mvX / mvMag : mvX;
1501
+ const nmvY = mvMag > 1 ? mvY / mvMag : mvY;
1502
+ const desiredVx = sinYaw * nmvY + -cosYaw * nmvX;
1503
+ const desiredVz = cosYaw * nmvY + sinYaw * nmvX;
1504
+ const desiredHorizontalVx = desiredVx * targetSpeed;
1505
+ const desiredHorizontalVz = desiredVz * targetSpeed;
1506
+
1507
+ // -- L1.d: Accel/decel toward desired velocity ----------------
1508
+ //
1509
+ // Three regimes air control, grounded decel-to-stop, grounded
1510
+ // accel-to-target each with its own model:
1511
+ //
1512
+ // Air control: constant-rate `stepTowards`. No ground
1513
+ // reaction force in flight; air control is a steering
1514
+ // budget, not a thrust curve. Constant accel matches the
1515
+ // player mental model of "fixed mid-air authority".
1516
+ //
1517
+ // • Grounded decel (no intent): constant-rate `stepTowards`
1518
+ // toward zero. Friction is approximately constant for a
1519
+ // biped on level ground — Coulomb friction. Faster than
1520
+ // accel because the body's own resistance + active
1521
+ // decel-foot-plants combine into a sharper deceleration.
1522
+ //
1523
+ // • Grounded accel (intent active): mono-exponential
1524
+ // approach (Hill 1927; Furusawa-Hill 1928). dv/dt is
1525
+ // proportional to (v_target v), so accel is highest at
1526
+ // low speed and tapers as v approaches v_target. Matches
1527
+ // human sprint biomechanics modern sprint-profiling
1528
+ // work (Morin & Samozino 2016) fits this same mono-exp
1529
+ // curve to empirical force-plate data.
1530
+ //
1531
+ // The mass + mastery + backward scalars compose multiplicatively
1532
+ // on the EFFECTIVE half-life (heavier longer half-life ⇒
1533
+ // slower ramp; mastery accel-bonus ⇒ shorter half-life ⇒
1534
+ // faster ramp). See FirstPersonPlayerControllerConfig.js's
1535
+ // `groundAccelHalfLife` doc for the literature and the
1536
+ // SprintAcceleration.spec.js for the model assertions.
1537
+ const intentLen = Math.hypot(nmvX, nmvY);
1538
+ if (!state.grounded) {
1539
+ const maxStep = cfg.motion.airAccel * dt;
1540
+ runtime.velocityX = stepTowards(runtime.velocityX, desiredHorizontalVx, maxStep);
1541
+ runtime.velocityZ = stepTowards(runtime.velocityZ, desiredHorizontalVz, maxStep);
1542
+ } else if (intentLen < 1e-4) {
1543
+ let decel = cfg.motion.groundDecel * runtime.massRatios.groundAccelScale;
1544
+ decel *= controller.mastery.evaluate(DecisionPoint.GroundAccel, controller, runtime);
1545
+ const maxStep = decel * dt;
1546
+ runtime.velocityX = stepTowards(runtime.velocityX, 0, maxStep);
1547
+ runtime.velocityZ = stepTowards(runtime.velocityZ, 0, maxStep);
1548
+ } else {
1549
+ // Mono-exponential approach. Scale half-life by the
1550
+ // inverse of the accel scalars so that "more accel" (large
1551
+ // groundAccelScale, mastery > 1.0) translates to a shorter
1552
+ // half-life (faster ramp). Backward intent slows things
1553
+ // down backwardAccelFactor < 1 longer half-life.
1554
+ let halfLife = cfg.motion.groundAccelHalfLife
1555
+ / runtime.massRatios.groundAccelScale
1556
+ / controller.mastery.evaluate(DecisionPoint.GroundAccel, controller, runtime);
1557
+ if (isBackwardIntent) halfLife /= cfg.motion.backwardAccelFactor;
1558
+ runtime.velocityX = exponentialApproach(runtime.velocityX, desiredHorizontalVx, halfLife, dt);
1559
+ runtime.velocityZ = exponentialApproach(runtime.velocityZ, desiredHorizontalVz, halfLife, dt);
1560
+ }
1561
+
1562
+ // -- L1.e/f/g/h: jump FSM + vertical integration --------------
1563
+ this._advanceJumpFsm(controller, runtime, bodyTransform, dt);
1564
+ this._integrateVerticalAndResolveGround(controller, runtime, bodyTransform, dt);
1565
+
1566
+ // -- Publish posture for L2 consumers (eye height, gait gating).
1567
+ // Base owns posture when no ability is active: Crouch if the
1568
+ // crouch intent is resolved active, otherwise Stand. Abilities
1569
+ // that need a different posture (slide → Prone, ledge-grab →
1570
+ // Hang) set state.posture themselves in their tick.
1571
+ let posture = isCrouchActive
1572
+ ? FirstPersonPosture.Crouch
1573
+ : FirstPersonPosture.Stand;
1574
+ // Headroom floor block UN-proning under a very low overhang. Only an
1575
+ // ALREADY-prone body (e.g. one that slid into a crawl-height tunnel) is
1576
+ // force-kept prone while the Crouch collider can't fit above it. A player
1577
+ // merely crouching by intent is NOT auto-dropped to prone when they meet
1578
+ // a lower overhang same rule as above: the mover blocks their forward
1579
+ // motion instead of the ceiling shoving them down. Uses the post-move
1580
+ // position, so it reflects where the body actually ended up this tick.
1581
+ if (posture === FirstPersonPosture.Crouch
1582
+ && runtime.lastPosture === FirstPersonPosture.Prone) {
1583
+ const proneGrow = runtime.crouchTop - runtime.proneTop;
1584
+ if (!this._hasHeadroomToGrow(runtime, bodyTransform, runtime.colliderShapeProne, proneGrow)) {
1585
+ posture = FirstPersonPosture.Prone;
1586
+ }
1587
+ }
1588
+ controller.state.posture = posture;
1589
+
1590
+ // -- Publish lean target for L2.f. Base writes the natural
1591
+ // (lat-accel + look-lean) value; abilities override in their
1592
+ // own tick. L2.f spring-steps toward whatever's here.
1593
+ runtime.leanTargetRad = this._computeNaturalLeanTarget(controller, runtime, dt);
1594
+ }
1595
+
1596
+ /**
1597
+ * Compute the natural camera lean for this tick: lat-accel-driven
1598
+ * roll into a turn, plus a yaw-rate look-lean contribution, both
1599
+ * clamped. The result is the target the lean spring chases each
1600
+ * tick when no ability has opinions.
1601
+ *
1602
+ * Pure-ish helper — reads `controller`, `runtime`, `dt`; returns a
1603
+ * number. Extracted so both base and any future ability that wants
1604
+ * to compose its lean on top of the natural value can call it.
1605
+ *
1606
+ * @private
1607
+ * @param {FirstPersonPlayerController} controller
1608
+ * @param {PerEntityRuntime} runtime
1609
+ * @param {number} dt
1610
+ * @returns {number} target roll in radians
1611
+ */
1612
+ _computeNaturalLeanTarget(controller, runtime, dt) {
1613
+ const cfg = controller.config;
1614
+ const state = controller.state;
1615
+ if (!cfg.lean.enabled) return 0;
1616
+
1617
+ const sinYaw = runtime.sinYaw;
1618
+ const cosYaw = runtime.cosYaw;
1619
+
1620
+ // Lateral acceleration projected onto screen-right.
1621
+ // accel_world = (vel - prevVel) / dt; screen_right = (-cos θ, 0, sin θ).
1622
+ const accWorldX = (runtime.velocityX - runtime.prevVelocityX) / Math.max(dt, 1e-4);
1623
+ const accWorldZ = (runtime.velocityZ - runtime.prevVelocityZ) / Math.max(dt, 1e-4);
1624
+ const latAccel = accWorldX * (-cosYaw) + accWorldZ * sinYaw;
1625
+ const normalized = clamp(latAccel / 9.81, -2, 2);
1626
+ //
1627
+ // Sign convention for the roll (the eye composes the rotation
1628
+ // as qYaw * qPitch * qRoll, where qRoll is around (0,0,1)).
1629
+ // After the engine's camera-invert pipeline:
1630
+ // φ > 0 → camera-up tilts toward screen-right (−X) → HEAD TILTS RIGHT
1631
+ // φ < 0 → camera-up tilts toward screen-left (+X) → HEAD TILTS LEFT
1632
+ //
1633
+ // For the "bank into the turn" feel (Apex / Titanfall / Mirror's
1634
+ // Edge): accelerating right (latAccel > 0) should tilt the head
1635
+ // RIGHT, i.e. positive φ. So leanTargetRad has the SAME sign
1636
+ // as latAccel.
1637
+ let leanTargetRad = normalized * cfg.lean.maxRollDeg * DEG_TO_RAD;
1638
+
1639
+ // Look-lean: yaw-rate-driven banking. runtime.yawRateRadPerSec
1640
+ // was cached at L1.a negative is the "turn right" convention.
1641
+ // For "bank into the turn": turning right → head tilts right →
1642
+ // positive engine roll. So lookLean = -yawRate * scale matches
1643
+ // sign.
1644
+ //
1645
+ // Crouched players are in a low, stable, low-momentum stance
1646
+ // banking the head from a mouse turn reads as unmotivated. We
1647
+ // scale the contribution down (default to 0) while crouched.
1648
+ // Lat-accel lean is left alone: its magnitude naturally tracks
1649
+ // the (lower) crouch acceleration, so it stays motivated.
1650
+ if (cfg.lean.lookLeanEnabled) {
1651
+ const yawRate = clamp(
1652
+ runtime.yawRateRadPerSec,
1653
+ -cfg.lean.lookLeanYawRateClamp,
1654
+ cfg.lean.lookLeanYawRateClamp,
1655
+ );
1656
+ const crouchFactor = state.crouchActive ? cfg.lean.crouchLookLeanFactor : 1.0;
1657
+ leanTargetRad += -yawRate * cfg.lean.lookLeanDegPerRadPerSec * DEG_TO_RAD * crouchFactor;
1658
+ }
1659
+
1660
+ // Final clamp on the sum: cap the combined target to ±2 ×
1661
+ // maxRollDeg (matches the latAccel normalized clamp range) so
1662
+ // even simultaneous max-strafe-accel + max-yaw-flick produces a
1663
+ // sane upper bound.
1664
+ const maxTotal = cfg.lean.maxRollDeg * DEG_TO_RAD * 2;
1665
+ return clamp(leanTargetRad, -maxTotal, maxTotal);
1666
+ }
1667
+
1668
+ /**
1669
+ * Snapshot the per-tick "what is the body doing" information into the
1670
+ * pose channels for downstream consumption (skeleton, sound, AI).
1671
+ * Read-only with respect to controller state — this is purely a publish
1672
+ * step.
1673
+ *
1674
+ * @private
1675
+ * @param {FirstPersonPlayerController} controller
1676
+ * @param {PerEntityRuntime} runtime
1677
+ * @param {Transform} bodyTransform
1678
+ */
1679
+ _publishPose(controller, runtime, bodyTransform) {
1680
+ const cfg = controller.config;
1681
+ const state = controller.state;
1682
+ const pose = controller.pose;
1683
+
1684
+ pose.rootPosition.copy(bodyTransform.position);
1685
+ pose.rootYawRad = runtime.bodyYaw;
1686
+ pose.headYawRad = runtime.bodyYaw;
1687
+ pose.headPitchRad = runtime.eyePitch;
1688
+ pose.headRollRad = state.leanRollRad;
1689
+ pose.locomotionPhase = state.stridePhase;
1690
+ pose.locomotionSpeed = runtime.horizSpeed;
1691
+ // Strafe component: project velocity onto screen-right (-cos θ, 0, sin θ).
1692
+ // Positive = moving to the player's right.
1693
+ pose.locomotionStrafe = (runtime.velocityX * (-runtime.cosYaw) + runtime.velocityZ * runtime.sinYaw)
1694
+ / Math.max(cfg.motion.sprintSpeed, 1e-3);
1695
+ pose.actionState =
1696
+ state.inJumpAnticipation ? FirstPersonActionState.Anticipating
1697
+ : !state.grounded ? FirstPersonActionState.Airborne
1698
+ : (Math.abs(runtime.landSpring.value) > 0.01 ? FirstPersonActionState.Landing
1699
+ : FirstPersonActionState.Grounded);
1700
+ pose.locomotionMode = state.locomotionMode;
1701
+ const crouchSpan = Math.max(cfg.body.height - cfg.body.crouchHeight, 1e-3);
1702
+ pose.crouchAmount = clamp((cfg.body.height - state.eyeHeight) / crouchSpan, 0, 1);
1703
+
1704
+ // Posture channel for downstream animation: which body shape +
1705
+ // how far the body is into it from the standing neutral.
1706
+ //
1707
+ // `posture` is the enum (Stand / Crouch / Prone / Hang) — picks
1708
+ // the animation track. `postureAmount` is the [0..1] blend
1709
+ // weight from standing toward that posture, derived from the
1710
+ // eye-height spring so the value transitions smoothly across
1711
+ // changes (matches the visible camera motion).
1712
+ pose.posture = state.posture;
1713
+ let postureTargetH;
1714
+ switch (state.posture) {
1715
+ case FirstPersonPosture.Prone: postureTargetH = cfg.body.proneHeight; break;
1716
+ case FirstPersonPosture.Crouch: postureTargetH = cfg.body.crouchHeight; break;
1717
+ case FirstPersonPosture.Hang: postureTargetH = cfg.body.height; break;
1718
+ case FirstPersonPosture.Stand:
1719
+ default: postureTargetH = cfg.body.height; break;
1720
+ }
1721
+ const postureSpan = Math.max(cfg.body.height - postureTargetH, 1e-3);
1722
+ pose.postureAmount = clamp((cfg.body.height - state.eyeHeight) / postureSpan, 0, 1);
1723
+
1724
+ pose.aimPitch = runtime.eyePitch;
1725
+
1726
+ this._assertTickInvariants(controller, runtime, bodyTransform);
1727
+ this._snapshotRenderPose(runtime, bodyTransform);
1728
+ }
1729
+
1730
+ /**
1731
+ * Record this fixed step's authoritative body position as the latest
1732
+ * render-interpolation endpoint, shifting the old latest to "previous".
1733
+ * {@link _composeEye} blends prev→cur by the sub-step alpha so the camera
1734
+ * position is smooth at any refresh rate. (Yaw/pitch aren't snapshotted —
1735
+ * they're render-rate.) On a teleport (`renderSnap`), prev and cur collapse
1736
+ * to the new position so the camera jumps rather than gliding across it.
1737
+ * @private
1738
+ */
1739
+ _snapshotRenderPose(runtime, bodyTransform) {
1740
+ const p = bodyTransform.position;
1741
+ if (runtime.renderSnap) {
1742
+ runtime.renderSnap = false;
1743
+ runtime.renderPrevPos.set(p.x, p.y, p.z);
1744
+ runtime.renderCurPos.set(p.x, p.y, p.z);
1745
+ return;
1746
+ }
1747
+ runtime.renderPrevPos.copy(runtime.renderCurPos);
1748
+ runtime.renderCurPos.set(p.x, p.y, p.z);
1749
+ }
1750
+
1751
+ /**
1752
+ * Dev-only invariant guard (meep asserts compile out of prod, so this is
1753
+ * free there). Catches the class of corruption that surfaces as an
1754
+ * unexplained camera "snap": a NaN/Infinity leaking into the yaw/pitch the
1755
+ * eye composes from, or into the body's velocity / position. Runs once at
1756
+ * the END of every fixed step, so a bad value is caught the tick it appears
1757
+ * — naming the layer that produced it — instead of manifesting as a camera
1758
+ * jump frames (or seconds) later when something finally reads it.
1759
+ * @private
1760
+ */
1761
+ _assertTickInvariants(controller, runtime, bodyTransform) {
1762
+ const p = bodyTransform.position;
1763
+ assert.ok(Number.isFinite(runtime.bodyYaw),
1764
+ "FP invariant: runtime.bodyYaw is non-finite (camera yaw corrupted)");
1765
+ assert.ok(Number.isFinite(runtime.eyePitch),
1766
+ "FP invariant: runtime.eyePitch is non-finite (camera pitch corrupted)");
1767
+ assert.ok(Number.isFinite(controller.state.leanRollRad),
1768
+ "FP invariant: state.leanRollRad is non-finite (camera roll corrupted)");
1769
+ assert.ok(
1770
+ Number.isFinite(runtime.velocityX)
1771
+ && Number.isFinite(runtime.velocityY)
1772
+ && Number.isFinite(runtime.velocityZ),
1773
+ "FP invariant: runtime.velocity is non-finite",
1774
+ );
1775
+ assert.ok(
1776
+ Number.isFinite(p.x) && Number.isFinite(p.y) && Number.isFinite(p.z),
1777
+ "FP invariant: body position is non-finite",
1778
+ );
1779
+ }
1780
+
1781
+ /**
1782
+ * Consume the accumulated mouse-look delta into body yaw + eye pitch at
1783
+ * RENDER rate (once per render frame, from {@link _composeEye}). This is the
1784
+ * fix for the slow-turn camera snap: sampling `intent.look` at the 60Hz
1785
+ * fixed step aliases an unaligned mouse report rate into an uneven turn,
1786
+ * whereas consuming it every render frame tracks the mouse as smoothly as it
1787
+ * arrives. `intent.look` is zeroed after consuming so the same delta isn't
1788
+ * re-applied next frame.
1789
+ *
1790
+ * Conventions (raw mouse delta source — movementX/Y positive moving
1791
+ * right/down): look.x > 0 ("mouse right") turn right (the yaw sign is
1792
+ * negated because the engine is left-handed with +Z forward, so a +Y
1793
+ * rotation reads as a LEFT turn through the camera); look.y > 0 → look down,
1794
+ * flipped by invertY.
1795
+ * @private
1796
+ */
1797
+ _consumeLook(controller, runtime) {
1798
+ const intent = controller.intent;
1799
+ const cfg = controller.config;
1800
+ let yawDelta = -intent.look.x;
1801
+ const pitchSign = cfg.look.invertY ? -1 : 1;
1802
+ let pitchDelta = intent.look.y * pitchSign;
1803
+ intent.look.set(0, 0);
1804
+
1805
+ // Reject input SPIKES. A single render frame's look delta beyond a sane
1806
+ // bound is a corrupt browser/driver `movementX` (a high-res mouse under
1807
+ // OS pointer acceleration, or a pointer-lock burst), not a real turn —
1808
+ // applying it snaps the camera 20–100°. Drop the spiking axis; legit
1809
+ // turns (even fast flicks) stay well under the bound at render rate.
1810
+ const maxDelta = cfg.look.maxFrameDeltaRad;
1811
+ if (Math.abs(yawDelta) > maxDelta) yawDelta = 0;
1812
+ if (Math.abs(pitchDelta) > maxDelta) pitchDelta = 0;
1813
+
1814
+ runtime.bodyYaw += yawDelta;
1815
+ if (runtime.bodyYaw > Math.PI) runtime.bodyYaw -= TWO_PI;
1816
+ else if (runtime.bodyYaw < -Math.PI) runtime.bodyYaw += TWO_PI;
1817
+
1818
+ runtime.eyePitch = clamp(
1819
+ runtime.eyePitch + pitchDelta,
1820
+ cfg.look.pitchMinDeg * DEG_TO_RAD,
1821
+ cfg.look.pitchMaxDeg * DEG_TO_RAD,
1822
+ );
1823
+ }
1824
+
1825
+ /**
1826
+ * Compose the eye transform from body + state-driven offsets.
1827
+ * @private
1828
+ * @param {FirstPersonPlayerController} controller
1829
+ * @param {number} entity
1830
+ */
1831
+ _composeEye(controller, entity) {
1832
+ const ecd = this.entityManager.dataset;
1833
+ const runtime = this.runtime.get(entity);
1834
+ if (runtime === undefined) return;
1835
+
1836
+ // Consume mouse-look at RENDER rate so the camera direction tracks the
1837
+ // mouse smoothly, decoupled from the aliased 60Hz sim sampling.
1838
+ this._consumeLook(controller, runtime);
1839
+
1840
+ const dt = this._currentRenderDt;
1841
+ const cfg = controller.config;
1842
+ const state = controller.state;
1843
+
1844
+ const bodyTransform = ecd.getComponent(entity, Transform);
1845
+ if (bodyTransform === undefined) return;
1846
+
1847
+ if (controller.eyeEntity === -1) return;
1848
+ const eyeTransform = ecd.getComponent(controller.eyeEntity, Transform);
1849
+ const camera = ecd.getComponent(controller.eyeEntity, Camera);
1850
+ if (eyeTransform === undefined || camera === undefined) return;
1851
+
1852
+ // -- Body-local eye offset, composed via the additive stack ----
1853
+ // The base (0, eyeHeight, 0) is the standing/crouched neutral; each
1854
+ // additional contribution (bob, breath, landing, anticipation,
1855
+ // sprint posture) goes through the stack so external systems can
1856
+ // push their own contributions on the same channel.
1857
+ const stack = runtime.eyeOffsetStack;
1858
+ stack.clear();
1859
+ stack.push("eyeHeight", 0, state.eyeHeight, 0);
1860
+
1861
+ // Bob gated on grounded only (the impact spring decays naturally
1862
+ // even at rest, so the bob fade-out is smooth; lateral amp uses the
1863
+ // bob-intensity envelope which spring-decays after stopping).
1864
+ if (state.grounded) {
1865
+ const phase = state.stridePhase * TWO_PI;
1866
+ const massBoost = (cfg.body.mass - 80) * cfg.bob.ampMassScale;
1867
+ const intensity = runtime.bobIntensitySpring.value;
1868
+
1869
+ // Back-pedal amp boost — lateral grows more than vertical because
1870
+ // backward gait has worse side-to-side balance than vertical compression.
1871
+ // Exertion adds a smaller boost on top: tired = wobbly gait.
1872
+ const ampLMult = 1 + (cfg.bob.backwardLateralAmpFactor - 1) * runtime.backwardness;
1873
+ const exertionBoost = 1 + cfg.exertion.bobLateralBoostAtMax * state.exertion;
1874
+ const ampL = (cfg.bob.lateralAmpAtWalk + massBoost) * intensity * ampLMult * exertionBoost;
1875
+
1876
+ // Vertical: read directly from the impact spring (footfall kicks,
1877
+ // under-damped recovery trough + leg-push overshoot).
1878
+ stack.push("bob.impact", 0, runtime.verticalImpactSpring.value, 0);
1879
+
1880
+ // Lateral: head shifts toward the foot bearing weight. Polarity
1881
+ // sourced from runtime.standingFoot the same signal the
1882
+ // footstep emits so bob direction and footstep side agree.
1883
+ // |sin(phase)| is the non-negative "midstance envelope".
1884
+ const lateralPolarity = runtime.standingFoot === "R" ? -1 : 1;
1885
+ stack.push("bob.lateral", ampL * lateralPolarity * Math.abs(Math.sin(phase)), 0, 0);
1886
+ }
1887
+
1888
+ // Breath — sine + tiny noise riding the rate spring.
1889
+ const breathOffset = -state.breathAmplitudeM
1890
+ * Math.sin(state.breathPhase * TWO_PI)
1891
+ * (1 + cfg.breath.noiseAmount * (Math.sin(state.breathPhase * 13.7) * 0.5));
1892
+ stack.push("breath", 0, breathOffset, 0);
1893
+
1894
+ // Landing spring dip (under-damped overshoots once on recovery).
1895
+ stack.push("landing", 0, runtime.landSpring.value, 0);
1896
+
1897
+ // Jump anticipation dip (eased ramp during the squash window).
1898
+ if (state.inJumpAnticipation) {
1899
+ const t = 1 - clamp(runtime.anticipationRemaining / Math.max(cfg.jump.anticipation.duration, 1e-3), 0, 1);
1900
+ const eased = t * (2 - t); // ease-out quad
1901
+ stack.push("anticipation", 0, -cfg.jump.anticipation.dipAmount * eased, 0);
1902
+ }
1903
+
1904
+ // Sprint posture: head leans slightly forward as commitment builds.
1905
+ // Pitch part is in the rotation block below; the +Z position shift
1906
+ // sells "head leading the hips" (Mirror's Edge), tied to the same
1907
+ // spring envelope so they move together.
1908
+ const sprintPitch = runtime.sprintPostureSpring.value;
1909
+ const sprintShiftFraction =
1910
+ cfg.posture.sprintForwardPitchDeg > 0
1911
+ ? sprintPitch / (cfg.posture.sprintForwardPitchDeg * DEG_TO_RAD)
1912
+ : 0;
1913
+ stack.push("posture.sprintShift", 0, 0, cfg.posture.sprintForwardShiftM * sprintShiftFraction);
1914
+
1915
+ // Body POSITION advances only at the fixed step → blend the last two
1916
+ // steps by the engine's sub-step alpha (the same fraction the
1917
+ // InterpolationSystem uses for physics bodies) so it's smooth on a high-
1918
+ // refresh display. Yaw + pitch are already render-rate (consumed in
1919
+ // _consumeLook), so they're used directly no blend, no aliasing.
1920
+ const alpha = this.entityManager.getFixedStepAlpha();
1921
+ const iPosX = lerp(runtime.renderPrevPos.x, runtime.renderCurPos.x, alpha);
1922
+ const iPosY = lerp(runtime.renderPrevPos.y, runtime.renderCurPos.y, alpha);
1923
+ const iPosZ = lerp(runtime.renderPrevPos.z, runtime.renderCurPos.z, alpha);
1924
+ const qYaw0 = SCRATCH_Q_A.fromAxisAngle(Vector3.up, runtime.bodyYaw);
1925
+
1926
+ // Transform body-local accumulated offset into world space (about the
1927
+ // render-rate yaw, to match the interpolated base position).
1928
+ const worldOffset = SCRATCH_V3_B.copy(stack.offset);
1929
+ worldOffset.applyQuaternion(qYaw0);
1930
+
1931
+ eyeTransform.position.set(iPosX, iPosY, iPosZ);
1932
+ eyeTransform.position._add(worldOffset.x, worldOffset.y, worldOffset.z);
1933
+
1934
+ // -- Eye rotation: body yaw × eye pitch × roll -------------------
1935
+ // Bob roll mixes in for a subtle head sway (in phase with lateral bob).
1936
+ // Breath pitch is a small extra nod 90° out of phase with vertical
1937
+ // breath; merged into the main pitch so we don't pay an extra quat
1938
+ // multiply and the composition stays trivially correct.
1939
+ let rollTotal = state.leanRollRad;
1940
+ if (state.grounded) {
1941
+ // Roll: head tilts toward the standing foot, in phase with the
1942
+ // lateral sway. Polarity sourced from runtime.standingFoot for
1943
+ // consistency with the lateral bob. Positive engine roll = head
1944
+ // tilts RIGHT (camera-invert convention), so R-foot midstance =
1945
+ // positive roll, L-foot midstance = negative roll.
1946
+ const phase = state.stridePhase * TWO_PI;
1947
+ const rollBackMult = 1 + (cfg.bob.backwardRollFactor - 1) * runtime.backwardness;
1948
+ const ampRoll = cfg.bob.rollAtWalkDeg * DEG_TO_RAD * runtime.bobIntensitySpring.value * rollBackMult;
1949
+ const rollPolarity = runtime.standingFoot === "R" ? 1 : -1;
1950
+ const rollEnvelope = Math.abs(Math.sin(phase));
1951
+ const bobRollSigned = ampRoll * rollPolarity * rollEnvelope;
1952
+
1953
+ // Lean × bob coupling: excursions in the lean direction get
1954
+ // amplified, opposite excursions attenuated. Lean is normalized
1955
+ // against maxRollDeg so the coupling magnitude stays bounded
1956
+ // regardless of how aggressively lean is configured.
1957
+ const maxLeanRad = Math.max(cfg.lean.maxRollDeg * DEG_TO_RAD, 1e-6);
1958
+ const leanFraction = clamp(state.leanRollRad / maxLeanRad, -1, 1);
1959
+ // sign(bobRollSigned) matches lean? amplify; else attenuate.
1960
+ const sameSign = (bobRollSigned * leanFraction) >= 0;
1961
+ const couplingMag = cfg.bob.leanCouplingFactor * Math.abs(leanFraction);
1962
+ const couplingScale = sameSign ? (1 + couplingMag) : (1 - couplingMag);
1963
+ rollTotal += bobRollSigned * couplingScale;
1964
+ }
1965
+
1966
+ const breathPitch = lerp(cfg.breath.pitchAmpRestDeg, cfg.breath.pitchAmpMaxDeg, state.exertion)
1967
+ * DEG_TO_RAD
1968
+ * Math.cos(state.breathPhase * TWO_PI);
1969
+ // Combined pitch contributions: player input + breath nod + sprint
1970
+ // commitment + fatigue droop. All in the same "positive = look-down"
1971
+ // convention so they sum cleanly.
1972
+ const pitchTotal = runtime.eyePitch
1973
+ + breathPitch
1974
+ + runtime.sprintPostureSpring.value
1975
+ + runtime.headDroopSpring.value;
1976
+
1977
+ // composition: yaw * pitch * roll
1978
+ // pitch around world X — yaw applied after, so effective axis is camera-local right
1979
+ // roll around world Z — yaw and pitch applied after, so effective axis is camera-local forward
1980
+ const qYaw = qYaw0; // SCRATCH_Q_A, already the render-rate yaw
1981
+ const qPitch = SCRATCH_Q_B.fromAxisAngle(Vector3.right, pitchTotal);
1982
+ const qRoll = SCRATCH_Q_C.fromAxisAngle(Vector3.forward, rollTotal);
1983
+
1984
+ eyeTransform.rotation.multiplyQuaternions(qYaw, qPitch);
1985
+ eyeTransform.rotation.multiply(qRoll);
1986
+
1987
+ // Dev-only camera-output guard (compiled out of prod). The composed eye
1988
+ // direction is exactly what the player sees; if a spring (head-droop,
1989
+ // breath nod) or lean ever drove pitch/roll to NaN, the camera would
1990
+ // snap to garbage. Assert it here, at the source of the visible jump.
1991
+ const er = eyeTransform.rotation;
1992
+ assert.ok(
1993
+ Number.isFinite(er.x) && Number.isFinite(er.y) && Number.isFinite(er.z) && Number.isFinite(er.w),
1994
+ "FP invariant: composed eye rotation is non-finite (camera direction corrupted)",
1995
+ );
1996
+
1997
+ // -- FOV ---------------------------------------------------------
1998
+ let fovTarget = cfg.fov.base;
1999
+ if (cfg.fov.sprintAdd !== 0) {
2000
+ fovTarget += cfg.fov.sprintAdd * runtime.sprintness;
2001
+ }
2002
+ if (state.crouchActive) fovTarget += cfg.fov.crouchAdd;
2003
+
2004
+ runtime.fovSpring.stepTo(fovTarget, cfg.fov.smoothHalfLife, 1.0, dt);
2005
+ // Write directly to the underlying Three.js camera. Going through
2006
+ // camera.fov.set() fires onChanged which triggers a full camera
2007
+ // rebuild in CameraSystem far too expensive to do per frame.
2008
+ // The CameraSystem's visibility-construction hook calls
2009
+ // updateProjectionMatrix() each frame anyway.
2010
+ if (camera.object !== null) {
2011
+ camera.object.fov = runtime.fovSpring.value;
2012
+ }
2013
+ }
2014
+ }
2015
+
2016
+ // ---------------------------------------------------------------------------
2017
+ // helpers
2018
+ // ---------------------------------------------------------------------------
2019
+
2020
+ /**
2021
+ * Exponential approach with half-life parameterization.
2022
+ * @param {number} current
2023
+ * @param {number} target
2024
+ * @param {number} halfLife
2025
+ * @param {number} dt
2026
+ * @returns {number}
2027
+ */
2028
+ function exponentialApproach(current, target, halfLife, dt) {
2029
+ if (halfLife <= 0) return target;
2030
+ const alpha = 1 - Math.exp(-LN2 * dt / halfLife);
2031
+ return current + (target - current) * alpha;
2032
+ }
2033
+
2034
+ /**
2035
+ * Detect that phase value crossed a boundary in [0,1) between two ticks.
2036
+ * Handles the wraparound case where phase jumps from e.g. 0.95 to 0.05.
2037
+ *
2038
+ * @param {number} prev previous phase in [0,1)
2039
+ * @param {number} next current phase in [0,1)
2040
+ * @param {number} boundary in [0,1)
2041
+ * @returns {boolean}
2042
+ */
2043
+ function phaseCrossed(prev, next, boundary) {
2044
+ if (next >= prev) {
2045
+ // no wrap
2046
+ return prev < boundary && next >= boundary;
2047
+ } else {
2048
+ // wrapped past 1.0
2049
+ return prev < boundary || next >= boundary;
2050
+ }
2051
+ }
2052
+