@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,2036 +1,2037 @@
1
- import { assert } from "../../core/assert.js";
2
- import { BinaryBuffer } from "../../core/binary/BinaryBuffer.js";
3
- import Signal from "../../core/events/signal/Signal.js";
4
- import { EntityObserver } from "../ecs/EntityObserver.js";
5
- import { UUID } from "../ecs/guid/UUID.js";
6
- import { BinarySerializationRegistry, } from "../ecs/storage/binary/BinarySerializationRegistry.js";
7
-
8
- import { NetworkIdentity } from "./ecs/components/NetworkIdentity.js";
9
- import { make_owner_authorization } from "./ecs/owner_authorization.js";
10
- import { NetworkSystem } from "./ecs/NetworkSystem.js";
11
- import { NetworkIdentitySerializationAdapter, } from "./ecs/serialization/NetworkIdentitySerializationAdapter.js";
12
- import { NetworkPeer, ResumeRejectReason, } from "./orchestrator/NetworkPeer.js";
13
- import { ServerAuthoritativeClient } from "./orchestrator/ServerAuthoritativeClient.js";
14
- import { ServerAuthoritativeServer } from "./orchestrator/ServerAuthoritativeServer.js";
15
- import { OwnerAwareScope } from "./replication/ScopeFilter.js";
16
- import { BinaryInterpolationAdapter, InterpolationKind, } from "../interpolation/BinaryInterpolationAdapter.js";
17
- import { InterpolationLog } from "../interpolation/InterpolationLog.js";
18
- import { SimAction } from "./sim/SimAction.js";
19
- import { snapshotter_emit } from "./sim/Snapshotter.js";
20
- import { AdaptiveRenderDelay } from "./time/AdaptiveRenderDelay.js";
21
- import { TimeDilation } from "./time/TimeDilation.js";
22
-
23
- /**
24
- * Roles a session can occupy. The orchestrator it wires up underneath is
25
- * uniquely determined by the role.
26
- *
27
- * - `'client'` → {@link ServerAuthoritativeClient}: predicts locally
28
- * when an input sampler is registered; reconciles to
29
- * server-authoritative state arriving via AUTH_STATE.
30
- * With no input sampler, a client is a pure spectator
31
- * — it receives the action stream, smooths via
32
- * interpolation, and never predicts or rewinds. This
33
- * is the default role and covers both "playing" and
34
- * "spectating" use cases.
35
- * - `'host'` → {@link ServerAuthoritativeServer}: simulation
36
- * authority for one or more clients; runs the optional
37
- * server-side input buffer (`simulation_delay_ticks`).
38
- *
39
- * @readonly @enum {string}
40
- */
41
- export const NetworkSessionRole = Object.freeze({
42
- Client: 'client',
43
- Host: 'host',
44
- });
45
-
46
- const DEFAULT_TICK_RATE_HZ = 60;
47
- const DEFAULT_SIMULATION_DELAY_TICKS = 4;
48
- const DEFAULT_INITIAL_RENDER_DELAY_FRAMES = 6;
49
-
50
- /**
51
- * Default depth of the action-log ring (in frames). This single value bounds
52
- * THREE distinct horizons at once:
53
- * 1. Rollback depth — how far back the server can rewind+replay to fold in
54
- * late client input.
55
- * 2. Fresh-connect back-fill range — how many past frames a newly-connected
56
- * peer's action stream covers before it has acked anything.
57
- * 3. Action-stream retransmit window — how long an unacked frame stays
58
- * resendable before it ages out of the ring.
59
- *
60
- * At 60 Hz, 32 frames ≈ 0.53 s. That is comfortable for rollback but tight for
61
- * loss recovery on high-latency links: if a peer's ack RTT + jitter exceeds the
62
- * window, unacked frames age out and the action stream develops gaps that fall
63
- * back to the heavier RECOVERY_REQUEST → STATE_BURST path. Raise `frame_capacity`
64
- * for high-RTT / lossy deployments (memory cost is ~one buffer per frame per
65
- * orchestrator, so it is cheap to grow).
66
- */
67
- const DEFAULT_ACTION_LOG_FRAME_CAPACITY = 32;
68
- const PULL_PER_MS = 0.0005; // wall-clock low-pass on render frame state machine
69
- const MAX_CLIENT_DILATED_TICKS_PER_STEP = 3;
70
- const MAX_FIXED_STEPS_PER_TICK = 8;
71
-
72
- const DEFAULT_SERVER_RESUME_GRACE_MS = 30_000;
73
-
74
- // Inbound-silence budget before a peer is reaped (host → grace, client →
75
- // reconnect ladder). Matches GNS's TimeoutConnected default. The facade pumps
76
- // at least one packet per tick in each direction (host: AUTH_STATE +
77
- // TIME_DILATION; client: action-stream / empty-ack), so a live, mutually-
78
- // ticking pair never approaches this — only a genuinely-gone peer does. Set 0
79
- // to disable (e.g. turn-based games, or transports that signal disconnect
80
- // themselves). Primarily for connectionless transports (NodeUDP, WebRTC /
81
- // WebTransport datagram), where no close event ever fires.
82
- const DEFAULT_CONNECTION_TIMEOUT_MS = 10_000;
83
-
84
- const DEFAULT_RECONNECT_POLICY = Object.freeze({
85
- enabled: true,
86
- max_attempts: 8,
87
- base_delay_ms: 200,
88
- max_delay_ms: 5_000,
89
- exponential_factor: 2.0,
90
- total_timeout_ms: 60_000,
91
- accept_state_resync: true,
92
- });
93
-
94
- /**
95
- * Client-side reconnect state machine values.
96
- * @readonly @enum {string}
97
- */
98
- const ReconnectState = Object.freeze({
99
- Connected: 'connected',
100
- Reconnecting: 'reconnecting',
101
- Resyncing: 'resyncing',
102
- Lost: 'lost',
103
- });
104
-
105
- /**
106
- * Scratch buffer size used for AUTH_STATE assembly and net_mutate_component
107
- * event payloads. Sized for the common cases (Transform ≈ 36 B); components
108
- * with richer payloads need this raised or a different scratch lifecycle.
109
- */
110
- const SCRATCH_BUFFER_BYTES = 1024;
111
-
112
- /**
113
- * Shared zero-length payload for empty ack packets. Hoisted to module scope so
114
- * the per-peer, per-tick ack send doesn't allocate a fresh array each time.
115
- * @type {Uint8Array}
116
- */
117
- const EMPTY_PAYLOAD = new Uint8Array(0);
118
-
119
- /**
120
- * High-level networking facade. Wraps the orchestrator + state machinery
121
- * into one config + lifecycle surface. The lower-level orchestrators
122
- * remain reachable through `session.server` / `session.client` /
123
- * `session.peer` for callers that need to override defaults post-hoc.
124
- *
125
- * Replication is component-driven: attaching a {@link NetworkIdentity}
126
- * to an entity registers it with the session via an `EntityObserver`.
127
- * Mutation is event-driven: callers fire
128
- * `dataset.sendEvent(entity, "net_mutate_component", payload)`.
129
- *
130
- * Per-tick driving is a single `session.tick(dt_seconds)`. The session
131
- * runs fixed-timestep stepping, client time dilation, server-side input
132
- * buffer, AUTH_STATE dispatch, time-dilation feedback, and interpolated
133
- * rendering for remote-owned entities.
134
- *
135
- * @author Alex Goldring
136
- * @copyright Company Named Limited (c) 2026
137
- */
138
- export class NetworkSession {
139
-
140
- // ---- Pre-start registration (set via replicate / defineAction / defineInputSampler) ----
141
-
142
- /**
143
- * User-registered replicated component classes, in `replicate()` call
144
- * order. Finalized at `start()` into {@link #replicated} with
145
- * `NetworkIdentity` prepended.
146
- * @private @type {Array<{klass: Function, interp: BinaryInterpolationAdapter|null}>}
147
- */
148
- #user_replicated = [];
149
-
150
- /**
151
- * Finalized at `start()`. Insertion order = wire-format order;
152
- * `NetworkIdentity` is the first entry (its slot allocation must
153
- * fire before subsequent components attach during initial-sync apply).
154
- * Each value: `{ interp, interp_type_id }`.
155
- * @private @type {Map<Function, {interp: BinaryInterpolationAdapter|null, interp_type_id: number}>}
156
- */
157
- #replicated = new Map();
158
-
159
- /**
160
- * Type_id → ComponentClass for the synthetic ReplaceComponentAction.
161
- * @private @type {Function[]}
162
- */
163
- #interp_type_id_to_class = [];
164
-
165
- /** @private @type {Array<Function>} */
166
- #user_action_classes = [];
167
-
168
- /** @private @type {((frame: number) => Array<SimAction>|null) | null} */
169
- #input_sampler = null;
170
-
171
- // ---- Set in start() ----
172
-
173
- /** @private @type {ServerAuthoritativeServer|null} */
174
- #server = null;
175
- /** @private @type {ServerAuthoritativeClient|null} */
176
- #client = null;
177
- /** @private @type {NetworkPeer|null} */
178
- #peer = null;
179
- /** @private @type {EntityObserver|null} */
180
- #identity_observer = null;
181
- /** @private @type {InterpolationLog|null} */
182
- #interp_log = null;
183
- /** @private @type {Function|null} */
184
- #ReplaceComponentAction = null;
185
-
186
- // ---- Per-entity tracking ----
187
-
188
- /**
189
- * `entity_id → "net_mutate_component" listener` so detach can clean up.
190
- * Also doubles as the iterable of all networked entities.
191
- * @private @type {Map<number, Function>}
192
- */
193
- #event_listeners = new Map();
194
-
195
- /**
196
- * Entities the local peer does NOT own — driven by the render
197
- * interpolation pass.
198
- * @private @type {Set<number>}
199
- */
200
- #remote_entities = new Set();
201
-
202
- // ---- Render-frame interpolation state ----
203
-
204
- /** @private @type {number} */ #latest_received_frame = -1;
205
- /** @private @type {number} */ #smooth_render_frame = -1;
206
- /** @private @type {number} */ #smooth_last_wall_ms = -1;
207
-
208
- /**
209
- * Set after `#render_interpolated_entities` writes interp values into
210
- * live components; cleared by `normalize_if_dirty`. Drives the
211
- * canonical-form invariant: live is canonical when this is false,
212
- * polluted (smooth) when true.
213
- * @private @type {boolean}
214
- */
215
- render_dirty = false;
216
-
217
- // ---- Per-tick driver state ----
218
-
219
- /** @private @type {number} */ #local_frame = 0;
220
- /** @private @type {number} */ #local_frame_accum = 0;
221
- /** @private @type {number} */ #sim_accum_ms = 0;
222
-
223
- /** @private @type {Set<number>} */
224
- #connected_peers = new Set();
225
-
226
- /**
227
- * Host-only: peer_id → remaining-resends. Drained in
228
- * `#on_host_tick_complete` once `current_sim_frame >= 0`. Sent twice
229
- * for loss tolerance; production code should re-send on receiver
230
- * RECOVERY request.
231
- * @private @type {Map<number, number>}
232
- */
233
- #pending_initial_sync = new Map();
234
-
235
- /**
236
- * Sampled-action ledger for predict-reconcile replay. The sampler
237
- * reads live input, so re-calling it during reconcile would give
238
- * wrong results; replay reads from here instead.
239
- * @private @type {Map<number, Array<{type_id: number, bytes: Uint8Array}>>}
240
- */
241
- #sampled_actions_per_frame = new Map();
242
-
243
- /** @private @type {boolean} */
244
- #started = false;
245
-
246
- // ---- Host-side: per-peer session token + grace state ----
247
-
248
- /**
249
- * Tokens issued at first INITIAL_SYNC, kept while the peer is
250
- * connected or in the grace window. Used to authenticate
251
- * RESUME_HELLO.
252
- * @private @type {Map<number, UUID>}
253
- */
254
- #peer_session_tokens = new Map();
255
-
256
- /**
257
- * Peers currently in the post-disconnect grace window. Value carries
258
- * the deadline (wall-clock ms) and the cached `last_acked_frame` so
259
- * a successful resume restores the action-stream baseline.
260
- * @private @type {Map<number, { deadline_ms: number, last_acked: number }>}
261
- */
262
- #peer_grace_state = new Map();
263
-
264
- /**
265
- * Per-peer transport onDisconnect listener bookkeeping so we can
266
- * unsubscribe when the peer is fully dropped.
267
- * @private @type {Map<number, { transport: object, listener: Function }>}
268
- */
269
- #peer_transport_listeners = new Map();
270
-
271
- // ---- Client-side: session token + reconnect state machine ----
272
-
273
- /**
274
- * Server-issued session token (raw UUID v1 bytes). Set on first
275
- * INITIAL_SYNC arrival; refreshed on every Tier-3 resync. Sent in
276
- * `RESUME_HELLO` to claim continuity with a prior session record.
277
- * Observable to game code for diagnostics; not auth-grade.
278
- * @type {UUID|null}
279
- */
280
- session_token = null;
281
-
282
- /** @private @type {string} */
283
- #reconnect_state = ReconnectState.Connected;
284
- /** @private @type {number} */
285
- #reconnect_attempt = 0;
286
- /** @private @type {number} */
287
- #reconnect_elapsed_ms = 0;
288
- /** @private @type {number} */
289
- #reconnect_next_delay_ms = 0;
290
- /** @private @type {number} */
291
- #reconnect_timer_remaining_ms = 0;
292
- /** @private @type {object|null} */
293
- #current_transport = null;
294
- /** @private @type {Function|null} */
295
- #current_transport_listener = null;
296
- /** @private @type {number} */
297
- #remote_peer_id = -1;
298
-
299
- // ---- Signals (initialized inline because they take no input) ----
300
-
301
- /**
302
- * Host: a connected peer's transport reported a disconnect (the peer
303
- * has entered the grace window). Args: `(peer_id, reason)`.
304
- * @type {Signal}
305
- */
306
- onPeerLost = new Signal();
307
-
308
- /**
309
- * Host: a peer has been permanently freed — either via
310
- * `drop_peer()`, a grace-window timeout, or a peer-initiated
311
- * DISCONNECT. Args: `(peer_id, reason)`.
312
- * @type {Signal}
313
- */
314
- onPeerPermanentlyDropped = new Signal();
315
-
316
- /**
317
- * Client: the transport has reported a disconnect, and the
318
- * reconnect ladder has started. Args: `(reason)`.
319
- * @type {Signal}
320
- */
321
- onConnectionLost = new Signal();
322
-
323
- /**
324
- * Client: about to rebuild the transport and dial the host. Args:
325
- * `(attempt_number)`.
326
- * @type {Signal}
327
- */
328
- onReconnectAttempt = new Signal();
329
-
330
- /**
331
- * Client: reconnect succeeded. Args: `({ state_resynced: boolean })` —
332
- * `state_resynced` is `true` if we fell through to Tier-3 (server
333
- * had purged our state and re-sent INITIAL_SYNC), `false` for the
334
- * happy Tier-2 path where the action stream just resumed.
335
- * @type {Signal}
336
- */
337
- onReconnected = new Signal();
338
-
339
- /**
340
- * Client: the reconnect ladder is exhausted, or the host explicitly
341
- * kicked us with a DISCONNECT packet, or `reconnect.enabled` is
342
- * false and the link dropped. Args: `(reason)`.
343
- * @type {Signal}
344
- */
345
- onConnectionPermanentlyLost = new Signal();
346
-
347
- // ---- Set in constructor (depend on input or imperative setup) ----
348
-
349
- /** @private */ #transport = null;
350
- /** @private */ #scope_filter = null;
351
- /** @private @type {TimeDilation} */ #time_dilation;
352
- /** @private @type {AdaptiveRenderDelay} */ #adaptive_render_delay;
353
- /** @private @type {BinaryBuffer} */ #scratch_send_buf;
354
- /** @private @type {BinaryBuffer} */ #scratch_interp_buf;
355
- /** @private @type {() => void} */ #bound_normalize_if_dirty;
356
-
357
- /** @private @type {number} */ #server_resume_grace_ms;
358
- /** @private @type {object} */ #reconnect_policy;
359
- /** @private @type {(() => object) | null} */ #transport_factory;
360
-
361
- /** @private @type {number} ms of inbound silence before reaping; 0 = off */ #connection_timeout_ms;
362
- /** @private @type {number} monotonic ms clock advanced by tick(dt) */ #liveness_clock_ms = 0;
363
- /**
364
- * Per-peer liveness mark: the inbound-packet count last seen and the clock
365
- * time it was last seen advancing. Drives idle reaping.
366
- * @private @type {Map<number, { count: number, since_ms: number }>}
367
- */
368
- #peer_inbound_mark = new Map();
369
-
370
- /**
371
- * @param {{
372
- * entity_manager: EntityManager,
373
- * transport?: object,
374
- * transport_factory?: (() => object),
375
- * role?: 'client'|'host',
376
- * local_peer_id?: number,
377
- * simulation_delay_ticks?: number,
378
- * tick_rate_hz?: number,
379
- * binary_registry?: BinarySerializationRegistry,
380
- * scope_filter?: object,
381
- * time_dilation?: TimeDilation,
382
- * adaptive_render_delay?: AdaptiveRenderDelay,
383
- * server_resume_grace_ms?: number,
384
- * connection_timeout_ms?: number,
385
- * reconnect?: {
386
- * enabled?: boolean,
387
- * max_attempts?: number,
388
- * base_delay_ms?: number,
389
- * max_delay_ms?: number,
390
- * exponential_factor?: number,
391
- * total_timeout_ms?: number,
392
- * accept_state_resync?: boolean,
393
- * },
394
- * }} options
395
- *
396
- * - `transport` is not connected at construction — call {@link connect}
397
- * with an explicit remote peer id after {@link start}.
398
- * - `transport_factory` (client role): zero-arg function that returns
399
- * a fresh `Transport` on each reconnect attempt. Required for
400
- * automatic reconnect; without it, transport-level disconnects fall
401
- * straight through to `onConnectionPermanentlyLost`.
402
- * - `role` defaults to `'client'`. See {@link NetworkSessionRole}.
403
- * - `local_peer_id` defaults to 0 for host, 1 for client. Must be
404
- * unique across peers.
405
- * - `simulation_delay_ticks` is honored only under `role: 'host'`.
406
- * Default 4. See {@link ServerAuthoritativeServer}.
407
- * - `tick_rate_hz` defaults to 60.
408
- * - `frame_capacity` sizes the action-log ring (rollback depth + back-fill
409
- * range + retransmit window — see {@link DEFAULT_ACTION_LOG_FRAME_CAPACITY}).
410
- * Default 32. Raise it for high-RTT / lossy links.
411
- * - `scope_filter` defaults to {@link OwnerAwareScope} on the host.
412
- * - `server_resume_grace_ms` (host role): how long peer state is
413
- * retained after a transport drop before being freed. Default 30s.
414
- * - `connection_timeout_ms`: inbound-silence budget before a peer is
415
- * reaped (host → grace, client → reconnect ladder). Default 10s; set 0
416
- * to disable. Mainly for connectionless transports, which never emit a
417
- * disconnect event — see {@link DEFAULT_CONNECTION_TIMEOUT_MS}.
418
- * - `reconnect` (client role): policy for the reconnect ladder. See
419
- * {@link DEFAULT_RECONNECT_POLICY}. Set `enabled: false` to opt
420
- * out entirely.
421
- */
422
- constructor({
423
- entity_manager,
424
- transport = null,
425
- transport_factory = null,
426
- role = NetworkSessionRole.Client,
427
- local_peer_id = -1,
428
- simulation_delay_ticks = DEFAULT_SIMULATION_DELAY_TICKS,
429
- tick_rate_hz = DEFAULT_TICK_RATE_HZ,
430
- frame_capacity = DEFAULT_ACTION_LOG_FRAME_CAPACITY,
431
- binary_registry = null,
432
- scope_filter = null,
433
- time_dilation = null,
434
- adaptive_render_delay = null,
435
- server_resume_grace_ms = DEFAULT_SERVER_RESUME_GRACE_MS,
436
- connection_timeout_ms = DEFAULT_CONNECTION_TIMEOUT_MS,
437
- reconnect = null,
438
- } = {}) {
439
- assert.ok(entity_manager !== undefined && entity_manager !== null,
440
- 'NetworkSession: entity_manager is required');
441
- if (role !== NetworkSessionRole.Client && role !== NetworkSessionRole.Host) {
442
- throw new Error(`NetworkSession: invalid role '${role}'`);
443
- }
444
- assert.isNumber(tick_rate_hz, 'tick_rate_hz');
445
- assert.ok(tick_rate_hz > 0, 'tick_rate_hz must be positive');
446
- assert.isNonNegativeInteger(simulation_delay_ticks, 'simulation_delay_ticks');
447
- assert.isPositiveInteger(frame_capacity, 'frame_capacity');
448
-
449
- /**
450
- * @readonly
451
- * @type {EntityManager}
452
- */
453
- this.entity_manager = entity_manager;
454
-
455
- /**
456
- * @readonly
457
- * @type {EntityComponentDataset}
458
- */
459
- this.world = entity_manager.dataset;
460
-
461
- /**
462
- * @readonly
463
- * @type {NetworkSessionRole|string}
464
- */
465
- this.role = role;
466
-
467
- /**
468
- * @readonly
469
- * @type {number}
470
- */
471
- this.tick_period_ms = 1000 / tick_rate_hz;
472
-
473
- /**
474
- * @readonly
475
- * @type {number}
476
- */
477
- this.simulation_delay_ticks = role === NetworkSessionRole.Host
478
- ? simulation_delay_ticks
479
- : 0;
480
-
481
- /**
482
- * Depth of the action-log ring passed to the orchestrator. Bounds
483
- * rollback depth, back-fill range, and retransmit window — see
484
- * {@link DEFAULT_ACTION_LOG_FRAME_CAPACITY}.
485
- * @readonly
486
- * @type {number}
487
- */
488
- this.frame_capacity = frame_capacity;
489
-
490
- /**
491
- * @readonly
492
- * @type {number}
493
- */
494
- this.local_peer_id = local_peer_id >= 0
495
- ? local_peer_id
496
- : (role === NetworkSessionRole.Host ? 0 : 1);
497
-
498
- /**
499
- * @readonly
500
- * @type {BinarySerializationRegistry}
501
- */
502
- this.binary_registry = binary_registry || new BinarySerializationRegistry();
503
-
504
- this.#transport = transport;
505
- this.#scope_filter = scope_filter;
506
-
507
- this.#time_dilation = time_dilation || new TimeDilation({
508
- target_buffer_depth: this.simulation_delay_ticks > 0 ? this.simulation_delay_ticks : 2,
509
- max_dilation: 0.05,
510
- gain: 0.05,
511
- });
512
- this.#adaptive_render_delay = adaptive_render_delay || new AdaptiveRenderDelay({
513
- tick_period_ms: this.tick_period_ms,
514
- min_delay_frames: 2,
515
- max_delay_frames: 30,
516
- initial_delay_frames: DEFAULT_INITIAL_RENDER_DELAY_FRAMES,
517
- history_size: 60,
518
- safety_multiplier: 2.0,
519
- decay_per_sample_ms: 1.0,
520
- });
521
-
522
- this.#scratch_send_buf = new BinaryBuffer();
523
- this.#scratch_send_buf.setCapacity(SCRATCH_BUFFER_BYTES);
524
-
525
- this.#scratch_interp_buf = new BinaryBuffer();
526
- this.#scratch_interp_buf.setCapacity(SCRATCH_BUFFER_BYTES);
527
-
528
- /**
529
- * @type {() => void}
530
- */
531
- this.#bound_normalize_if_dirty = () => this.normalize_if_dirty();
532
-
533
- this.#server_resume_grace_ms = server_resume_grace_ms;
534
- assert.isNonNegativeInteger(connection_timeout_ms, 'connection_timeout_ms');
535
- this.#connection_timeout_ms = connection_timeout_ms;
536
- this.#reconnect_policy = Object.freeze({ ...DEFAULT_RECONNECT_POLICY, ...(reconnect || {}) });
537
- this.#transport_factory = transport_factory;
538
- }
539
-
540
- // ----------------------------------------------------------------
541
- // Configuration (must run before start())
542
- // ----------------------------------------------------------------
543
-
544
- /**
545
- * Register a component class for network replication.
546
- *
547
- * The component must have a {@link BinaryClassSerializationAdapter}
548
- * registered for its `typeName` in the session's `binary_registry`
549
- * before {@link start} is called — that adapter handles the wire
550
- * format and rewind capture.
551
- *
552
- * `interpolator` is optional. When omitted, the component's state
553
- * snaps on each received update (no sub-tick smoothing). When
554
- * provided, it must extend {@link BinaryInterpolationAdapter} with
555
- * `kind = InterpolationKind.Linear` — Discrete and Cubic are
556
- * reserved for future support and currently throw.
557
- *
558
- * The order of `replicate()` calls must match across all peers in
559
- * the session: AUTH_STATE payload layout iterates components in
560
- * insertion order.
561
- *
562
- * @param {Function} ComponentClass
563
- * @param {BinaryInterpolationAdapter} [interpolator]
564
- */
565
- replicate(ComponentClass, interpolator = null) {
566
- this.#assert_not_started('replicate');
567
- assert.isFunction(ComponentClass, 'ComponentClass');
568
-
569
- if (ComponentClass === NetworkIdentity) {
570
- throw new Error("NetworkSession.replicate: NetworkIdentity is auto-replicated; do not call replicate() for it");
571
- }
572
-
573
- for (const entry of this.#user_replicated) {
574
- if (entry.klass === ComponentClass) {
575
- throw new Error(`NetworkSession.replicate: ${ComponentClass.typeName ?? ComponentClass.name} already registered`);
576
- }
577
- }
578
- if (interpolator !== null) {
579
- if (!(interpolator instanceof BinaryInterpolationAdapter)) {
580
- throw new Error("NetworkSession.replicate: interpolator must extend BinaryInterpolationAdapter");
581
- }
582
- if (interpolator.kind !== InterpolationKind.Linear) {
583
- throw new Error(
584
- `NetworkSession.replicate: only Linear interpolation is currently supported; ` +
585
- `got kind=${interpolator.kind} for ${ComponentClass.typeName ?? ComponentClass.name}. ` +
586
- `Omit the interpolator argument for snap (Discrete) behavior; Cubic is reserved.`,
587
- );
588
- }
589
- }
590
- this.#user_replicated.push({ klass: ComponentClass, interp: interpolator });
591
- }
592
-
593
- /**
594
- * Register a user-defined {@link SimAction} subclass with the
595
- * session. The action's `type_id` is assigned by the underlying
596
- * registry when {@link start} runs. Calling code constructs
597
- * instances directly (`new MyAction(...)`) and dispatches via
598
- * {@link send} or returns them from the input sampler.
599
- *
600
- * @param {Function} SimActionClass class extending SimAction
601
- */
602
- defineAction(SimActionClass) {
603
- this.#assert_not_started('defineAction');
604
- assert.isFunction(SimActionClass, 'SimActionClass');
605
- this.#user_action_classes.push(SimActionClass);
606
- }
607
-
608
- /**
609
- * Install a per-tick input sampler (client role only). The sampler
610
- * is called once per local sim tick at predict time and must return
611
- * an array of {@link SimAction} instances representing the local
612
- * peer's inputs for that frame. The session executes them locally
613
- * (predict), records their serialized bytes for replay, and forwards
614
- * them over the action stream.
615
- *
616
- * The sampler is NOT called during reconciliation replay — the
617
- * recorded bytes are deserialized and re-executed instead, so the
618
- * action set faithfully reproduces what the user originally
619
- * sampled at that frame.
620
- *
621
- * Return `[]` (or `null`) for ticks with no inputs.
622
- *
623
- * @param {(frame: number) => Array<SimAction>|null} sampler_fn
624
- */
625
- defineInputSampler(sampler_fn) {
626
- this.#assert_not_started('defineInputSampler');
627
- assert.isFunction(sampler_fn, 'sampler_fn');
628
-
629
- if (this.role !== NetworkSessionRole.Client) {
630
- throw new Error(`NetworkSession.defineInputSampler: only valid for role='${NetworkSessionRole.Client}', got '${this.role}'`);
631
- }
632
-
633
- this.#input_sampler = sampler_fn;
634
- }
635
-
636
- // ----------------------------------------------------------------
637
- // Lifecycle
638
- // ----------------------------------------------------------------
639
-
640
- /**
641
- * Wire the session to the engine. After this, the EntityObserver
642
- * is active, the right orchestrator is constructed, and a
643
- * {@link NetworkSystem} is attached. Subsequent {@link replicate}
644
- * / {@link defineAction} / {@link defineInputSampler} calls throw.
645
- *
646
- * Pre-condition: every component class passed to {@link replicate}
647
- * must already have a `BinaryClassSerializationAdapter` registered
648
- * in the session's binary_registry.
649
- */
650
- async start() {
651
- if (this.#started) throw new Error("NetworkSession.start: already started");
652
-
653
- // Auto-register the NetworkIdentity adapter so initial-sync can
654
- // ship it across the wire. Allow caller-supplied registrations
655
- // to win (game might have a custom one).
656
- if (this.binary_registry.getAdapter(NetworkIdentity.typeName) === undefined) {
657
- this.binary_registry.registerAdapter(new NetworkIdentitySerializationAdapter(), NetworkIdentity.typeName);
658
- }
659
-
660
- // Finalize the replicated-components ordering: NetworkIdentity
661
- // FIRST (so it gets type_id=0 and arrives as the first per-entity
662
- // record in snapshots — receiver attaches NetworkIdentity first,
663
- // which triggers slot-table allocation before any other component
664
- // payload needs to look up `entity_for(network_id)`).
665
- let next_id = 0;
666
- const network_identity_scratch = new BinaryBuffer();
667
- network_identity_scratch.setCapacity(SCRATCH_BUFFER_BYTES);
668
- this.#replicated.set(NetworkIdentity, {
669
- interp: null,
670
- interp_type_id: next_id++,
671
- });
672
- this.#interp_type_id_to_class[0] = NetworkIdentity;
673
- for (const { klass, interp } of this.#user_replicated) {
674
- const interp_type_id = next_id++;
675
- this.#replicated.set(klass, { interp, interp_type_id });
676
- this.#interp_type_id_to_class[interp_type_id] = klass;
677
- }
678
-
679
- // Verify each replicated component has a serialization adapter.
680
- for (const ComponentClass of this.#replicated.keys()) {
681
- const name = ComponentClass.typeName;
682
- if (typeof name !== 'string') {
683
- throw new Error(`NetworkSession.start: ${ComponentClass.name} has no static .typeName; cannot look up its adapter`);
684
- }
685
- if (this.binary_registry.getAdapter(name) === undefined) {
686
- throw new Error(`NetworkSession.start: no BinaryClassSerializationAdapter registered for ${name}; register one in the binary_registry before calling start()`);
687
- }
688
- }
689
-
690
- // Build the synthetic ReplaceComponentAction now so it's in the
691
- // action_classes list when the orchestrator is constructed.
692
- this.#ReplaceComponentAction = this.#make_replace_component_action_class();
693
-
694
- const replicated_components = Array.from(this.#replicated.keys());
695
- const action_classes = [...this.#user_action_classes, this.#ReplaceComponentAction];
696
-
697
- const orchestrator_opts = {
698
- world: this.world,
699
- binary_registry: this.binary_registry,
700
- replicated_components,
701
- action_classes,
702
- frame_capacity: this.frame_capacity,
703
- };
704
-
705
- if (this.role === NetworkSessionRole.Host) {
706
- this.#server = new ServerAuthoritativeServer({
707
- ...orchestrator_opts,
708
- simulation_delay_ticks: this.simulation_delay_ticks,
709
- });
710
- this.#peer = this.#server.peer;
711
- this.#peer.replicator.scope_filter = this.#scope_filter || new OwnerAwareScope({
712
- world: this.world,
713
- slot_table: this.#server.slot_table,
714
- identity_class: NetworkIdentity,
715
- });
716
- this.#server.onTickComplete.add((sim_frame) => this.#on_host_tick_complete(sim_frame));
717
- } else {
718
- // Client role: covers both "playing" (input sampler registered)
719
- // and "spectator" (no sampler — sampler-driven onPredict
720
- // returns no actions, so the predict-reconcile machinery
721
- // exists but stays idle).
722
- this.#client = new ServerAuthoritativeClient({
723
- ...orchestrator_opts,
724
- time_dilation: this.#time_dilation,
725
- });
726
- this.#peer = this.#client.peer;
727
- if (this.#scope_filter) {
728
- this.#peer.replicator.scope_filter = this.#scope_filter;
729
- }
730
- this.#wire_client_predict_reconcile();
731
- }
732
-
733
- // Install the canonical-form normalize hook on the executor.
734
- // Anything that calls executor.execute — predict apply, action-
735
- // stream apply, reconcile replay — first normalizes the world.
736
- // Idempotent: cheap when `render_dirty` is false (one bool
737
- // check), expensive (one adapter.deserialize per remote-owned
738
- // component) only on the first execute after a render pass.
739
- this.#peer.executor.before_execute = this.#bound_normalize_if_dirty;
740
-
741
- // Secure-by-default inbound authorization on the host: a remote client
742
- // may only mutate entities it owns (NetworkIdentity.owner_peer_id ===
743
- // sender); the authoritative server (sender === local_peer_id) may
744
- // mutate anything. Without this a connected peer can craft an action
745
- // targeting another peer's (or a server-owned) entity. Games needing a
746
- // different policy can overwrite executor.authorize after start().
747
- // Client sessions trust the server's stream and so are not gated.
748
- if (this.role === NetworkSessionRole.Host) {
749
- this.#peer.executor.authorize = make_owner_authorization({
750
- world: this.world,
751
- identity_class: NetworkIdentity,
752
- server_peer_id: this.local_peer_id,
753
- });
754
- }
755
-
756
- // NetworkSystem handles slot_table allocation on NetworkIdentity attach.
757
- // addSystem returns a promise that resolves once the system's own
758
- // startup completes (it auto-starts when added to an already-Running
759
- // EM). Await it so the session's identity observer below doesn't
760
- // race with the NetworkSystem's observer over slot_table allocation.
761
- await this.entity_manager.addSystem(new NetworkSystem(this.#peer));
762
-
763
- // Sub-tick interpolation log: records per-frame replicated component
764
- // state, sampled at render time for remotely-owned entities.
765
- this.#interp_log = new InterpolationLog({
766
- buffer_capacity_bytes: 65536,
767
- records_capacity: 4096,
768
- });
769
-
770
- // Per-frame applied: snapshot all replicated components for each
771
- // remote-owned entity into the interpolation log.
772
- this.#peer.replicator.onFrameApplied.add((peer_id, frame_number) => {
773
- this.#on_frame_applied(peer_id, frame_number);
774
- });
775
-
776
- // Initial-sync arrival (client only): store the session token,
777
- // populate the world from the host's snapshot, resume normal
778
- // action-stream processing, and finalize any in-flight Tier-3
779
- // resync state.
780
- if (this.role === NetworkSessionRole.Client) {
781
- this.#peer.onInitialSync.add((_peer_id, session_token, _frame_number, buf, payload_end) => {
782
- const token = new UUID();
783
- token.data = session_token;
784
- this.session_token = token;
785
- this.#apply_initial_sync(buf, payload_end);
786
- if (this.#reconnect_state === ReconnectState.Resyncing) {
787
- this.#enter_connected();
788
- this.onReconnected.send1({ state_resynced: true });
789
- }
790
- });
791
- // Host's response to our RESUME_HELLO during reconnect.
792
- this.#peer.onResumeAccept.add(() => {
793
- if (this.#reconnect_state === ReconnectState.Reconnecting
794
- || this.#reconnect_state === ReconnectState.Resyncing) {
795
- this.#enter_connected();
796
- this.onReconnected.send1({ state_resynced: false });
797
- }
798
- });
799
- this.#peer.onResumeReject.add((_peer_id, reason_code) => {
800
- if (this.#reconnect_policy.accept_state_resync) {
801
- // Tier-3: drop unconfirmed predictions and wait for
802
- // the host to deliver a fresh INITIAL_SYNC.
803
- this.#sampled_actions_per_frame.clear();
804
- this.session_token = null;
805
- this.#reconnect_state = ReconnectState.Resyncing;
806
- // Restart the elapsed-ms budget so the Resyncing
807
- // watchdog (see #tick_reconnect) has a full
808
- // total_timeout_ms before falling to Lost.
809
- this.#reconnect_elapsed_ms = 0;
810
- } else {
811
- this.#permanently_lose_connection(`resume_rejected:${reason_code}`);
812
- }
813
- });
814
- // Host kicked us — final, no reconnect.
815
- this.#peer.onDisconnectPacket.add((_peer_id, reason_label) => {
816
- this.#permanently_lose_connection(reason_label || 'disconnected_by_host');
817
- });
818
- } else {
819
- // Host: RESUME_HELLO validation + peer-initiated disconnects.
820
- this.#peer.onResumeHello.add((peer_id, local_peer_id, last_acked_frame, session_token) => {
821
- this.#on_resume_hello(peer_id, local_peer_id, last_acked_frame, session_token);
822
- });
823
- this.#peer.onDisconnectPacket.add((peer_id, reason_label) => {
824
- // Client-initiated disconnect — skip the grace window
825
- // and free state immediately.
826
- this.drop_peer(peer_id, reason_label || 'client_disconnect');
827
- });
828
- }
829
-
830
- // EntityObserver for NetworkIdentity: patches owner/network_id on
831
- // attach, wires the per-entity mutation listener.
832
- this.#identity_observer = new EntityObserver(
833
- [NetworkIdentity],
834
- (identity, entity_id) => this.#on_identity_attached(identity, entity_id),
835
- (identity, entity_id) => this.#on_identity_detached(identity, entity_id),
836
- this,
837
- );
838
- this.#identity_observer.connect(this.world);
839
-
840
- this.#started = true;
841
- }
842
-
843
- /**
844
- * Connect to a remote peer. Host can connect many peers (one per
845
- * client); client typically connects one (the server). Called both
846
- * for first-time connects and reconnect attempts — the session
847
- * detects which based on whether a `session_token` is cached
848
- * (client) or whether the peer is in the grace window (host).
849
- *
850
- * @param {number} remote_peer_id
851
- * @param {object} transport
852
- */
853
- connect(remote_peer_id, transport) {
854
- if (!this.#started) throw new Error("NetworkSession.connect: call start() first");
855
-
856
- if (this.role === NetworkSessionRole.Host) {
857
- const in_grace = this.#peer_grace_state.has(remote_peer_id);
858
- this.#server.connect_peer(remote_peer_id, transport);
859
- if (in_grace) {
860
- // Resume candidate. Wait for RESUME_HELLO; restore
861
- // baseline + add to connected_peers on validation.
862
- // Don't queue INITIAL_SYNC — it'll only fire if we
863
- // RESUME_REJECT.
864
- } else {
865
- // Fresh peer. Queue initial-sync for the next host tick.
866
- this.#pending_initial_sync.set(remote_peer_id, 2);
867
- this.#connected_peers.add(remote_peer_id);
868
- }
869
- this.#wire_peer_transport_disconnect(remote_peer_id, transport);
870
- } else {
871
- // Client
872
- this.#client.connect_to_server(remote_peer_id, transport);
873
- this.#remote_peer_id = remote_peer_id;
874
- this.#current_transport = transport;
875
- this.#wire_client_transport_disconnect(transport);
876
- this.#connected_peers.add(remote_peer_id);
877
-
878
- // If we have a session token cached, treat this as a
879
- // reconnect attempt and send RESUME_HELLO immediately. A
880
- // fresh session has no token yet (it arrives in the first
881
- // INITIAL_SYNC), so first-time connects don't trigger this.
882
- if (this.session_token !== null) {
883
- const last_acked = Math.max(0, this.#latest_received_frame);
884
- this.#peer.send_resume_hello(
885
- remote_peer_id, this.local_peer_id, last_acked,
886
- this.session_token.data,
887
- );
888
- }
889
- }
890
- }
891
-
892
- /**
893
- * Host-only: forcibly free a peer's state, regardless of whether
894
- * they are actively connected, in the grace window, or already
895
- * unknown. Fires `onPeerPermanentlyDropped(peer_id, reason)`.
896
- * Sends a DISCONNECT packet if the peer is still actively
897
- * connected so the remote can short-circuit its reconnect attempts.
898
- *
899
- * Policy for *when* to drop (high server load, anti-cheat, admin
900
- * kick, etc.) is the caller's; the session provides the mechanism.
901
- *
902
- * @param {number} peer_id
903
- * @param {string} [reason]
904
- */
905
- drop_peer(peer_id, reason = '') {
906
- if (this.role !== NetworkSessionRole.Host) {
907
- throw new Error("NetworkSession.drop_peer: host-only");
908
- }
909
- const was_connected = this.#connected_peers.has(peer_id);
910
- const was_in_grace = this.#peer_grace_state.has(peer_id);
911
- if (!was_connected && !was_in_grace) return;
912
-
913
- if (was_connected) {
914
- // Best-effort notification — peer may have already dropped.
915
- try {
916
- this.#peer.send_disconnect(peer_id, reason);
917
- } catch (_) { /* swallow */
918
- }
919
- this.#server.disconnect_peer(peer_id);
920
- }
921
- this.#unwire_peer_transport_disconnect(peer_id);
922
- this.#connected_peers.delete(peer_id);
923
- this.#peer_inbound_mark.delete(peer_id);
924
- this.#peer_grace_state.delete(peer_id);
925
- this.#peer_session_tokens.delete(peer_id);
926
- this.#pending_initial_sync.delete(peer_id);
927
- this.onPeerPermanentlyDropped.send2(peer_id, reason);
928
- }
929
-
930
- /**
931
- * Client-only: voluntarily disconnect from the server. Sends a
932
- * DISCONNECT packet so the host can free state immediately and
933
- * skip the grace window. Disables reconnect for this session.
934
- *
935
- * @param {string} [reason]
936
- */
937
- disconnect(reason = '') {
938
- if (this.role !== NetworkSessionRole.Client) {
939
- throw new Error("NetworkSession.disconnect: client-only — host uses drop_peer instead");
940
- }
941
- if (this.#remote_peer_id < 0) return;
942
- const peer_id = this.#remote_peer_id;
943
- try {
944
- this.#peer.send_disconnect(peer_id, reason);
945
- } catch (_) { /* swallow */
946
- }
947
- // Tear down the channel binding so a subsequent connect() with a
948
- // fresh transport doesn't trip the underlying NetworkPeer's
949
- // "peer already exists" guard. Mirrors the cleanup that
950
- // #on_client_transport_disconnected does on an involuntary drop.
951
- try {
952
- this.#peer.disconnect_peer(peer_id);
953
- } catch (_) { /* swallow */
954
- }
955
- // Drop the peer from the live send set so the (still-running) tick loop
956
- // doesn't try to push empty-ack packets through the disposed channel.
957
- this.#connected_peers.delete(peer_id);
958
- this.#peer_inbound_mark.delete(peer_id);
959
- if (this.#current_transport_listener !== null && this.#current_transport !== null) {
960
- try {
961
- this.#current_transport.onDisconnect.remove(this.#current_transport_listener);
962
- } catch (_) { /* swallow */
963
- }
964
- }
965
- this.#current_transport = null;
966
- this.#current_transport_listener = null;
967
- this.#remote_peer_id = -1;
968
- this.#permanently_lose_connection(reason || 'client_disconnect');
969
- }
970
-
971
- /**
972
- * Tear down. Idempotent — safe to call from cleanup paths.
973
- */
974
- stop() {
975
- if (!this.#started) return;
976
- if (this.#identity_observer !== null) {
977
- this.#identity_observer.disconnect();
978
- this.#identity_observer = null;
979
- }
980
- for (const [entity_id, listener] of this.#event_listeners) {
981
- this.world.removeEntityEventListener(entity_id, "net_mutate_component", listener);
982
- }
983
- this.#event_listeners.clear();
984
- this.#remote_entities.clear();
985
- this.#sampled_actions_per_frame.clear();
986
- this.#peer_inbound_mark.clear();
987
- this.#started = false;
988
- }
989
-
990
- // ----------------------------------------------------------------
991
- // Per-tick driver
992
- // ----------------------------------------------------------------
993
-
994
- /**
995
- * Fixed-timestep accumulator-driven step. Catch-up bounded by
996
- * `MAX_FIXED_STEPS_PER_TICK`. Under `role: 'client'` the local frame
997
- * advances at `1 / dilation_factor` per fixed step.
998
- *
999
- * @param {number} dt_seconds elapsed wall seconds since the previous call
1000
- */
1001
- tick(dt_seconds) {
1002
- if (!this.#started) return;
1003
-
1004
- // Restore canonical form before sim work. The executor's
1005
- // `before_execute` hook also covers per-action paths (e.g.
1006
- // packets delivered between session.tick calls).
1007
- this.normalize_if_dirty();
1008
-
1009
- const dt_ms = dt_seconds * 1000;
1010
-
1011
- // Host: expire grace-window peers whose deadline has passed.
1012
- if (this.role === NetworkSessionRole.Host) {
1013
- this.#expire_grace_peers(performance.now());
1014
- } else {
1015
- // Client: drive the reconnect timer.
1016
- this.#tick_reconnect(dt_ms);
1017
- }
1018
-
1019
- // Idle reaping: a connected peer that has gone silent past the timeout
1020
- // is reaped (host → grace, client → reconnect ladder). The clock is the
1021
- // deterministic tick-dt accumulator (not wall-clock), so it's testable.
1022
- if (this.#connection_timeout_ms > 0) {
1023
- this.#liveness_clock_ms += dt_ms;
1024
- this.#reap_silent_peers();
1025
- }
1026
-
1027
- this.#sim_accum_ms += dt_ms;
1028
- let steps = 0;
1029
- while (this.#sim_accum_ms >= this.tick_period_ms && steps < MAX_FIXED_STEPS_PER_TICK) {
1030
- this.#sim_accum_ms -= this.tick_period_ms;
1031
- this.#simulate_one_step();
1032
- steps++;
1033
- }
1034
- if (steps === MAX_FIXED_STEPS_PER_TICK) this.#sim_accum_ms = 0;
1035
-
1036
- // Per-render-frame: smooth interpolated rendering of remote
1037
- // entities. Marks the live ECD as polluted so the next sim-tick
1038
- // or reconcile starts with a normalize.
1039
- this.#render_interpolated_entities();
1040
- }
1041
-
1042
- /**
1043
- * Undo render-time interpolation on remote-owned components by
1044
- * restoring each from its latest InterpolationLog entry. Idempotent
1045
- * via the `render_dirty` flag — cheap when false.
1046
- *
1047
- * Called from `tick()` (top), `executor.before_execute` (per-action
1048
- * applies, including transport-delivered action stream), and
1049
- * `client.onBeforeReconcile` (before the rewind walk).
1050
- *
1051
- * @private
1052
- */
1053
- normalize_if_dirty() {
1054
- if (!this.render_dirty) return;
1055
- this.render_dirty = false;
1056
-
1057
- if (this.#latest_received_frame < 0) return;
1058
- if (this.#remote_entities.size === 0) return;
1059
-
1060
- const scratch = this.#scratch_interp_buf;
1061
- const tick = this.#latest_received_frame;
1062
-
1063
- for (const entity_id of this.#remote_entities) {
1064
- const identity = this.world.getComponent(entity_id, NetworkIdentity);
1065
- if (identity === undefined || identity.network_id < 0) continue;
1066
- for (const [ComponentClass, desc] of this.#replicated) {
1067
- if (desc.interp === null) continue;
1068
- const live = this.world.getComponent(entity_id, ComponentClass);
1069
- if (live === undefined) continue;
1070
- scratch.position = 0;
1071
- // tick_a === tick_b, t = 0 — degenerate "snap to single
1072
- // snapshot" lerp. Output equals the stored snapshot.
1073
- const ok = this.#interp_log.interpolate(
1074
- scratch, identity.network_id, desc.interp_type_id,
1075
- tick, tick, 0, desc.interp,
1076
- );
1077
- if (!ok) continue;
1078
- scratch.position = 0;
1079
- const adapter = this.binary_registry.getAdapter(ComponentClass.typeName);
1080
- adapter.deserialize(scratch, live);
1081
- }
1082
- }
1083
- }
1084
-
1085
- /**
1086
- * Dispatch one user-defined action. Equivalent to
1087
- * `peer.executor.execute(action, this.local_peer_id)` but exposed
1088
- * at the session level so callers don't need a reference to the
1089
- * executor or to know the local peer id.
1090
- *
1091
- * @param {SimAction} action_instance
1092
- */
1093
- send(action_instance) {
1094
- if (!this.#started) throw new Error("NetworkSession.send: call start() first");
1095
- if (!action_instance || action_instance.isSimAction !== true) {
1096
- throw new Error("NetworkSession.send: argument must be a SimAction instance");
1097
- }
1098
- this.#peer.executor.execute(action_instance, this.local_peer_id);
1099
- }
1100
-
1101
- // ----------------------------------------------------------------
1102
- // Accessors
1103
- // ----------------------------------------------------------------
1104
-
1105
- /**
1106
- * @returns {ServerAuthoritativeServer|null}
1107
- */
1108
- get server() {
1109
- return this.#server;
1110
- }
1111
-
1112
- /**
1113
- * @returns {ServerAuthoritativeClient|null}
1114
- */
1115
- get client() {
1116
- return this.#client;
1117
- }
1118
-
1119
- /**
1120
- * @returns {NetworkPeer|null}
1121
- */
1122
- get peer() {
1123
- return this.#peer;
1124
- }
1125
-
1126
- /**
1127
- * @returns {InterpolationLog|null}
1128
- */
1129
- get interpolation_log() {
1130
- return this.#interp_log;
1131
- }
1132
-
1133
- /**
1134
- * @returns {AdaptiveRenderDelay}
1135
- */
1136
- get adaptive_render_delay() {
1137
- return this.#adaptive_render_delay;
1138
- }
1139
-
1140
- /**
1141
- * @returns {TimeDilation}
1142
- */
1143
- get time_dilation() {
1144
- return this.#time_dilation;
1145
- }
1146
-
1147
- /**
1148
- * Most recent locally-completed sim frame.
1149
- */
1150
- get current_frame() {
1151
- if (this.role === NetworkSessionRole.Host) return this.#server.current_sim_frame;
1152
- return this.#local_frame - 1;
1153
- }
1154
-
1155
- // ----------------------------------------------------------------
1156
- // Internal: identity observer (component-driven spawn/despawn)
1157
- // ----------------------------------------------------------------
1158
-
1159
- /**
1160
- * Patch `NetworkIdentity` on attach: `owner_peer_id` defaults to
1161
- * this session's `local_peer_id` if unset; `network_id` is assigned
1162
- * by {@link NetworkSystem.link}, which fires on the same attach.
1163
- *
1164
- * Callers must not mutate either field afterwards — the slot table
1165
- * and replicator cache them.
1166
- *
1167
- */
1168
- #on_identity_attached(identity, entity_id) {
1169
- if (identity.owner_peer_id < 0) {
1170
- identity.owner_peer_id = this.local_peer_id;
1171
- }
1172
- if (identity.owner_peer_id !== this.local_peer_id) {
1173
- this.#remote_entities.add(entity_id);
1174
- }
1175
- // Per-entity event listener for net_mutate_component.
1176
- const listener = (payload) => this.#on_net_mutate_component(entity_id, payload);
1177
- this.world.addEntityEventListener(entity_id, "net_mutate_component", listener);
1178
- this.#event_listeners.set(entity_id, listener);
1179
- }
1180
-
1181
- #on_identity_detached(_identity, entity_id) {
1182
- const listener = this.#event_listeners.get(entity_id);
1183
- if (listener !== undefined) {
1184
- this.world.removeEntityEventListener(entity_id, "net_mutate_component", listener);
1185
- this.#event_listeners.delete(entity_id);
1186
- }
1187
- this.#remote_entities.delete(entity_id);
1188
- }
1189
-
1190
- /**
1191
- * Handler for `"net_mutate_component"` events. Translates the payload
1192
- * into a `ReplaceComponentAction` and dispatches via the executor.
1193
- *
1194
- * Payload:
1195
- * - `{ component_type, new_state }` — recommended; engine captures
1196
- * current bytes as prior, then applies `new_state` via the
1197
- * replication adapter.
1198
- * - `{ component_type }` — caller has already mutated live; prior
1199
- * bytes equal post bytes (rewind for this mutation is a no-op).
1200
- *
1201
- */
1202
- #on_net_mutate_component(entity_id, payload) {
1203
- const { component_type, new_state } = payload || {};
1204
- if (!component_type) {
1205
- throw new Error("net_mutate_component: payload must include `component_type`");
1206
- }
1207
- const desc = this.#replicated.get(component_type);
1208
- if (desc === undefined) {
1209
- throw new Error(`net_mutate_component: ${component_type.typeName ?? component_type.name} is not replicated; call session.replicate() for it before mutating`);
1210
- }
1211
-
1212
- const network_id = this.#peer.slot_table.network_for(entity_id);
1213
- if (network_id < 0) return; // not networked yet
1214
-
1215
- const live = this.world.getComponent(entity_id, component_type);
1216
- if (live === undefined) return;
1217
-
1218
- const adapter = this.binary_registry.getAdapter(component_type.typeName);
1219
-
1220
- // Build the action with the new-state bytes pre-baked. The
1221
- // executor will then capture the current `live` as prior bytes
1222
- // before action.apply deserializes new bytes into `live`.
1223
- const action = this.#peer.action_registry.acquire(this.#ReplaceComponentAction);
1224
- action.network_id = network_id;
1225
- action.component_type_id = desc.interp_type_id;
1226
- action.payload_buf.position = 0;
1227
- if (new_state !== undefined && new_state !== null) {
1228
- adapter.serialize(action.payload_buf, new_state);
1229
- } else {
1230
- // No new_state: live IS the new state.
1231
- adapter.serialize(action.payload_buf, live);
1232
- }
1233
- action.payload_length = action.payload_buf.position;
1234
-
1235
- try {
1236
- this.#peer.executor.execute(action, this.local_peer_id);
1237
- } finally {
1238
- this.#peer.action_registry.release(action);
1239
- }
1240
- }
1241
-
1242
- /**
1243
- * Build the synthetic ReplaceComponentAction class.
1244
- * The factory captures `interp_type_id_to_class` and `binary_registry` by
1245
- * reference so the inner class methods can resolve type ids and
1246
- * adapters without reaching into the outer class's private fields
1247
- * (which `#`-private syntax would forbid from a different class).
1248
- */
1249
- #make_replace_component_action_class() {
1250
- const interp_type_id_to_class = this.#interp_type_id_to_class;
1251
- const binary_registry = this.binary_registry;
1252
- return class ReplaceComponentAction extends SimAction {
1253
- constructor(network_id = 0, component_type_id = 0) {
1254
- super();
1255
- this.network_id = network_id;
1256
- this.component_type_id = component_type_id;
1257
- this.payload_buf = new BinaryBuffer();
1258
- this.payload_buf.setCapacity(SCRATCH_BUFFER_BYTES);
1259
- this.payload_length = 0;
1260
- }
1261
-
1262
- apply(world, executor) {
1263
- const local = executor.slot_table.entity_for(this.network_id);
1264
- if (local < 0) return;
1265
- const ComponentClass = interp_type_id_to_class[this.component_type_id];
1266
- if (ComponentClass === undefined) return;
1267
- const live = world.getComponent(local, ComponentClass);
1268
- if (live === undefined) return;
1269
- const adapter = binary_registry.getAdapter(ComponentClass.typeName);
1270
- if (adapter === undefined) return;
1271
- this.payload_buf.position = 0;
1272
- adapter.deserialize(this.payload_buf, live);
1273
- }
1274
-
1275
- affected_components(callback, executor) {
1276
- const local = executor.slot_table.entity_for(this.network_id);
1277
- if (local < 0) return;
1278
- const ComponentClass = interp_type_id_to_class[this.component_type_id];
1279
- if (ComponentClass === undefined) return;
1280
- callback(local, ComponentClass);
1281
- }
1282
-
1283
- serialize(buffer) {
1284
- buffer.writeUintVar(this.network_id);
1285
- buffer.writeUint8(this.component_type_id);
1286
- buffer.writeUint32(this.payload_length);
1287
- if (this.payload_length > 0) {
1288
- buffer.writeBytes(this.payload_buf.raw_bytes, 0, this.payload_length);
1289
- }
1290
- }
1291
-
1292
- deserialize(buffer) {
1293
- this.network_id = buffer.readUintVar();
1294
- this.component_type_id = buffer.readUint8();
1295
- this.payload_length = buffer.readUint32();
1296
- if (this.payload_buf.raw_bytes.length < this.payload_length) {
1297
- this.payload_buf.setCapacity(this.payload_length);
1298
- }
1299
- if (this.payload_length > 0) {
1300
- buffer.readBytes(this.payload_buf.raw_bytes, 0, this.payload_length);
1301
- }
1302
- this.payload_buf.position = 0;
1303
- }
1304
-
1305
- reset() {
1306
- this.network_id = 0;
1307
- this.component_type_id = 0;
1308
- this.payload_length = 0;
1309
- this.payload_buf.position = 0;
1310
- }
1311
- };
1312
- }
1313
-
1314
- // ----------------------------------------------------------------
1315
- // Internal: per-step simulation
1316
- // ----------------------------------------------------------------
1317
-
1318
- #simulate_one_step() {
1319
- if (this.role === NetworkSessionRole.Host) {
1320
- this.#server.tick(this.#local_frame);
1321
- this.#local_frame++;
1322
- return;
1323
- }
1324
- // Client. Dilated tick rate: each fixed step advances by
1325
- // `1 / dilation_factor` (capped at MAX_CLIENT_DILATED_TICKS_PER_STEP).
1326
- this.#local_frame_accum += 1.0 / this.#client.dilation_factor;
1327
- let inner = 0;
1328
- while (this.#local_frame_accum >= 1.0 && inner < MAX_CLIENT_DILATED_TICKS_PER_STEP) {
1329
- this.#local_frame_accum -= 1.0;
1330
- this.#client.tick(this.#local_frame);
1331
- this.#send_empty_ack_packet();
1332
- this.#local_frame++;
1333
- inner++;
1334
- }
1335
- }
1336
-
1337
- /**
1338
- * Empty action-stream packet to advance the channel's seq/ack
1339
- * baseline on ticks with no outbound actions of our own.
1340
- */
1341
- #send_empty_ack_packet() {
1342
- for (const peer_id of this.#connected_peers) {
1343
- this.#peer.channel_for(peer_id).send(EMPTY_PAYLOAD, 0);
1344
- }
1345
- }
1346
-
1347
- // ----------------------------------------------------------------
1348
- // Internal: host-side tick completion (AUTH_STATE + dilation feedback)
1349
- // ----------------------------------------------------------------
1350
-
1351
- /**
1352
- *
1353
- * @param {number} sim_frame
1354
- */
1355
- #on_host_tick_complete(sim_frame) {
1356
- // Initial-sync first, so receivers have a populated world before
1357
- // AUTH_STATE / action-stream packets land.
1358
- if (this.#pending_initial_sync.size > 0) {
1359
-
1360
- for (const [peer_id, sends_remaining] of this.#pending_initial_sync) {
1361
-
1362
- this.#send_initial_sync_to(peer_id, sim_frame);
1363
-
1364
- if (sends_remaining <= 1) {
1365
- this.#pending_initial_sync.delete(peer_id);
1366
- } else {
1367
- this.#pending_initial_sync.set(peer_id, sends_remaining - 1);
1368
- }
1369
-
1370
- }
1371
-
1372
- }
1373
-
1374
- // AUTH_STATE per owned entity, then time-dilation feedback, to
1375
- // every connected peer.
1376
- for (const peer_id of this.#connected_peers) {
1377
-
1378
- for (const entity_id of this.#event_listeners.keys()) {
1379
-
1380
- const identity = this.world.getComponent(entity_id, NetworkIdentity);
1381
-
1382
- if (identity === undefined) {
1383
- continue;
1384
- }
1385
- if (identity.owner_peer_id !== peer_id) {
1386
- continue;
1387
- }
1388
-
1389
- this.#send_auth_state_for_entity(peer_id, sim_frame, entity_id, identity);
1390
-
1391
- }
1392
-
1393
- this.#server.send_time_dilation_feedback(peer_id);
1394
- }
1395
- }
1396
-
1397
-
1398
- /**
1399
- * Emit a Snapshotter-format payload to a freshly-connected peer.
1400
- * Generates and stashes a UUID v1 session token on first send,
1401
- * which the receiver echoes back via RESUME_HELLO to authenticate
1402
- * later reconnects.
1403
- * @param {number} peer_id
1404
- * @param {number} sim_frame
1405
- */
1406
- #send_initial_sync_to(peer_id, sim_frame) {
1407
- let token = this.#peer_session_tokens.get(peer_id);
1408
-
1409
- if (token === undefined) {
1410
- token = UUID.v1();
1411
- this.#peer_session_tokens.set(peer_id, token);
1412
- }
1413
-
1414
- this.#peer.send_initial_sync(peer_id, token.data, sim_frame, (buf) => {
1415
- snapshotter_emit({
1416
- buffer: buf,
1417
- world: this.world,
1418
- slot_table: this.#peer.slot_table,
1419
- component_registry: this.#peer.component_registry,
1420
- entity_iter: (cb) => {
1421
- for (const entity_id of this.#event_listeners.keys()) {
1422
- cb(entity_id);
1423
- }
1424
- },
1425
- });
1426
- });
1427
- }
1428
-
1429
- /**
1430
- * Apply an inbound INITIAL_SYNC payload. Walks the wire format
1431
- * directly instead of using `snapshotter_apply` so we can:
1432
- * - Handle re-sync (slot already allocated) without
1433
- * `slot_table.allocate_at` throwing.
1434
- * - Let `NetworkSystem.link` handle slot allocation on the
1435
- * NetworkIdentity-attach side-effect (NetworkIdentity is always
1436
- * first in the stream).
1437
- *
1438
- */
1439
- #apply_initial_sync(buffer, payload_end) {
1440
- const entity_count = buffer.readUintVar();
1441
- for (let i = 0; i < entity_count; i++) {
1442
- const network_id = buffer.readUintVar();
1443
- const component_count = buffer.readUint8();
1444
- let local_entity_id = this.#peer.slot_table.entity_for(network_id);
1445
- if (local_entity_id < 0) {
1446
- local_entity_id = this.world.createEntity();
1447
- }
1448
-
1449
- for (let j = 0; j < component_count; j++) {
1450
- const type_id = buffer.readUint8();
1451
- const payload_len = buffer.readUint32();
1452
- const ComponentClass = this.#peer.component_registry.class_of(type_id);
1453
- const adapter = this.#peer.component_registry.adapter_for_id(type_id);
1454
- if (ComponentClass === undefined || adapter === undefined) {
1455
- buffer.position += payload_len;
1456
- continue;
1457
- }
1458
- const existing = this.world.getComponent(local_entity_id, ComponentClass);
1459
- if (existing === undefined) {
1460
- const component = new ComponentClass();
1461
- adapter.deserialize(buffer, component);
1462
- this.world.addComponentToEntity(local_entity_id, component);
1463
- } else {
1464
- adapter.deserialize(buffer, existing);
1465
- }
1466
- }
1467
-
1468
- if (buffer.position > payload_end) {
1469
- throw new Error(`NetworkSession.#apply_initial_sync: read past payload_end at network_id ${network_id}`);
1470
- }
1471
- }
1472
- }
1473
-
1474
- /**
1475
- *
1476
- * @param {number} peer_id
1477
- * @param {number} sim_frame
1478
- * @param {number} entity_id
1479
- * @param {NetworkIdentity} identity
1480
- */
1481
- #send_auth_state_for_entity(peer_id, sim_frame, entity_id, identity) {
1482
- const network_id = identity.network_id;
1483
-
1484
- if (network_id < 0) {
1485
- // not initialized
1486
- return;
1487
- }
1488
-
1489
- this.#peer.send_auth_state(peer_id, sim_frame, network_id, (buf) => {
1490
-
1491
- // Concatenate all replicated components in #replicated order.
1492
- for (const [ComponentClass, desc] of this.#replicated) {
1493
-
1494
- const live = this.world.getComponent(entity_id, ComponentClass);
1495
-
1496
- if (live === undefined) {
1497
- continue;
1498
- }
1499
-
1500
- const adapter = this.binary_registry.getAdapter(ComponentClass.typeName);
1501
-
1502
- adapter.serialize(buf, live);
1503
-
1504
- }
1505
-
1506
- });
1507
- }
1508
-
1509
- // ----------------------------------------------------------------
1510
- // Internal: host-side reconnect / grace / drop machinery
1511
- // ----------------------------------------------------------------
1512
-
1513
- #wire_peer_transport_disconnect(peer_id, transport) {
1514
- if (!transport || !transport.onDisconnect) return;
1515
- const listener = (reason) => this.#on_peer_transport_disconnected(peer_id, reason);
1516
- transport.onDisconnect.add(listener);
1517
- this.#peer_transport_listeners.set(peer_id, { transport, listener });
1518
- }
1519
-
1520
- #unwire_peer_transport_disconnect(peer_id) {
1521
- const entry = this.#peer_transport_listeners.get(peer_id);
1522
- if (entry === undefined) return;
1523
- try {
1524
- entry.transport.onDisconnect.remove(entry.listener);
1525
- } catch (_) { /* swallow */
1526
- }
1527
- this.#peer_transport_listeners.delete(peer_id);
1528
- }
1529
-
1530
- /**
1531
- * Move a peer into the grace window after its transport reports a
1532
- * disconnect. The peer's channel + baseline are torn down (so a
1533
- * subsequent `connect()` on a fresh transport doesn't collide), but
1534
- * the session token, slot-table entries, and `last_acked` watermark
1535
- * are preserved for the grace window. On expiry, `#expire_grace_peers`
1536
- * promotes the peer to fully-dropped.
1537
- */
1538
- #on_peer_transport_disconnected(peer_id, reason) {
1539
- if (!this.#connected_peers.has(peer_id)) return;
1540
- const last_acked = this.#peer.baseline.last_acked(peer_id);
1541
- this.#server.disconnect_peer(peer_id);
1542
- this.#connected_peers.delete(peer_id);
1543
- this.#peer_inbound_mark.delete(peer_id);
1544
- this.#unwire_peer_transport_disconnect(peer_id);
1545
- this.#peer_grace_state.set(peer_id, {
1546
- deadline_ms: performance.now() + this.#server_resume_grace_ms,
1547
- last_acked,
1548
- });
1549
- this.onPeerLost.send2(peer_id, reason);
1550
- }
1551
-
1552
- /**
1553
- * Validate a RESUME_HELLO and either welcome the peer back (restore
1554
- * baseline + send RESUME_ACCEPT) or reject with a reason code.
1555
- * Rejection schedules a fresh INITIAL_SYNC so the client falls
1556
- * through to Tier-3.
1557
- */
1558
- #on_resume_hello(transport_peer_id, claimed_peer_id, last_acked_frame, claimed_token_bytes) {
1559
- // Engine convention: the matchmaking layer routes the reconnecting
1560
- // transport to the same `peer_id` it had before. If `claimed_peer_id`
1561
- // disagrees with `transport_peer_id`, the matchmaker mis-routed
1562
- // treat as collision and kick the connection cleanly. Symmetric
1563
- // with the TokenMismatch path below: we MUST NOT leave the rogue
1564
- // transport wired to the dispatcher (any subsequent packet would
1565
- // still be routed), and we MUST NOT add them to the connected
1566
- // set or queue an INITIAL_SYNC.
1567
- if (transport_peer_id !== claimed_peer_id) {
1568
- this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.PeerIdCollision);
1569
- this.#peer.send_disconnect(transport_peer_id, 'peer_id_collision');
1570
- this.#server.disconnect_peer(transport_peer_id);
1571
- this.#unwire_peer_transport_disconnect(transport_peer_id);
1572
- // The transport may have been added to the live set in connect()
1573
- // (a fresh peer connects, then sends a mis-routed RESUME_HELLO).
1574
- // Drop it from BOTH iterated maps otherwise the next host tick
1575
- // serializes AUTH_STATE / time-dilation to a now-disposed conn and
1576
- // throws "peer not connected", taking down the tick for all peers.
1577
- this.#connected_peers.delete(transport_peer_id);
1578
- this.#pending_initial_sync.delete(transport_peer_id);
1579
- this.#peer_inbound_mark.delete(transport_peer_id);
1580
- return;
1581
- }
1582
- const grace = this.#peer_grace_state.get(claimed_peer_id);
1583
- if (grace === undefined) {
1584
- // Either the peer is fully unknown OR they're already
1585
- // connected (their previous transport never dropped).
1586
- // Either way, no resume record to validate against.
1587
- this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.UnknownPeer);
1588
- // Treat as a fresh peer; schedule INITIAL_SYNC.
1589
- this.#pending_initial_sync.set(transport_peer_id, 2);
1590
- this.#connected_peers.add(transport_peer_id);
1591
- return;
1592
- }
1593
- const expected_token = this.#peer_session_tokens.get(claimed_peer_id);
1594
- if (expected_token === undefined) {
1595
- this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.UnknownPeer);
1596
- this.#peer_grace_state.delete(claimed_peer_id);
1597
- this.#pending_initial_sync.set(transport_peer_id, 2);
1598
- this.#connected_peers.add(transport_peer_id);
1599
- return;
1600
- }
1601
- const claimed = new UUID();
1602
- claimed.data = claimed_token_bytes;
1603
- if (!claimed.equals(expected_token)) {
1604
- // Hard reject: a wrong-token attempt MUST NOT take over the
1605
- // legitimate peer's grace state (would let an attacker
1606
- // pre-empt the real owner's reconnect window) and MUST NOT
1607
- // be fresh-treated either (would expose the world via
1608
- // INITIAL_SYNC). Kick the connection; legitimate-but-
1609
- // corrupted-token clients fall to permanent-lost and need
1610
- // a new peer_id from the matchmaking layer.
1611
- this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.TokenMismatch);
1612
- this.#peer.send_disconnect(transport_peer_id, 'token_mismatch');
1613
- this.#server.disconnect_peer(transport_peer_id);
1614
- this.#unwire_peer_transport_disconnect(transport_peer_id);
1615
- // Same cleanup as the collision branch above: if this transport was
1616
- // a fresh peer added to the live set, drop it from both iterated
1617
- // maps so the next host tick doesn't address a disposed conn.
1618
- this.#connected_peers.delete(transport_peer_id);
1619
- this.#pending_initial_sync.delete(transport_peer_id);
1620
- this.#peer_inbound_mark.delete(transport_peer_id);
1621
- return;
1622
- }
1623
- // Ring-floor gate. The client asks to resume from `last_acked_frame + 1`,
1624
- // but the action-log ring only still holds frames back to
1625
- // `current_sim_frame - frame_capacity + 1`. If the requested frame
1626
- // predates that floor, the frames needed to back-fill the gap have aged
1627
- // out `pack_for_peer`'s has_frame guard would silently skip them and
1628
- // the client would enter `connected` missing in-scope mutations (a
1629
- // "successful" resume that runs visibly desynced). Reject so the client
1630
- // falls through to a full INITIAL_SYNC re-sync instead. Grace is
1631
- // typically far longer than the ring, so this is the routine case for
1632
- // any reconnect that took more than ~`frame_capacity` ticks.
1633
- const ring_floor = this.#server.current_sim_frame - this.frame_capacity + 1;
1634
- if (last_acked_frame + 1 < ring_floor) {
1635
- this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.StaleFrame);
1636
- this.#peer_grace_state.delete(claimed_peer_id);
1637
- this.#pending_initial_sync.set(transport_peer_id, 2);
1638
- this.#connected_peers.add(transport_peer_id);
1639
- return;
1640
- }
1641
-
1642
- // Valid resume. Restore the action-stream baseline so the
1643
- // replicator packs from `last_acked + 1`, then accept.
1644
- this.#peer.baseline.set_acked(claimed_peer_id, last_acked_frame);
1645
- this.#peer_grace_state.delete(claimed_peer_id);
1646
- this.#connected_peers.add(transport_peer_id);
1647
- this.#peer.send_resume_accept(transport_peer_id);
1648
- }
1649
-
1650
- /**
1651
- * Walk the grace map and promote any peer whose deadline has
1652
- * passed to fully-dropped (fires `onPeerPermanentlyDropped`).
1653
- * Called from `tick()`.
1654
- */
1655
- #expire_grace_peers(now_ms) {
1656
- if (this.#peer_grace_state.size === 0) return;
1657
- for (const [peer_id, state] of this.#peer_grace_state) {
1658
- if (state.deadline_ms <= now_ms) {
1659
- this.#peer_grace_state.delete(peer_id);
1660
- this.#peer_session_tokens.delete(peer_id);
1661
- this.onPeerPermanentlyDropped.send2(peer_id, 'grace_expired');
1662
- }
1663
- }
1664
- }
1665
-
1666
- /**
1667
- * Reap any connected peer whose inbound-packet count has not advanced for
1668
- * longer than `connection_timeout_ms`. Host-side peers go into the grace
1669
- * window (the same path a transport disconnect takes); the client routes a
1670
- * silent server into the reconnect ladder. Connectionless transports never
1671
- * emit a disconnect of their own, so without this a vanished peer would be
1672
- * pinned forever (host) or never noticed (client). The per-tick keepalive
1673
- * traffic the facade sends both directions keeps a live connection well
1674
- * clear of the budget. Called from `tick()` only when the budget is > 0.
1675
- * @private
1676
- */
1677
- #reap_silent_peers() {
1678
- if (this.role === NetworkSessionRole.Host) {
1679
- // Collect first reaping mutates #connected_peers mid-iteration.
1680
- let to_reap = null;
1681
- for (const peer_id of this.#connected_peers) {
1682
- if (this.#peer_is_silent(peer_id)) (to_reap ??= []).push(peer_id);
1683
- }
1684
- if (to_reap !== null) {
1685
- for (let i = 0; i < to_reap.length; i++) {
1686
- this.#on_peer_transport_disconnected(to_reap[i], 'timeout');
1687
- }
1688
- }
1689
- } else if (this.#reconnect_state === ReconnectState.Connected && this.#remote_peer_id >= 0) {
1690
- // Only when actively connected Reconnecting/Resyncing have their
1691
- // own timers in #tick_reconnect.
1692
- if (this.#peer_is_silent(this.#remote_peer_id)) {
1693
- this.#on_client_transport_disconnected('timeout');
1694
- }
1695
- }
1696
- }
1697
-
1698
- /**
1699
- * Whether `peer_id` has produced no inbound packet within the timeout
1700
- * budget. Maintains the per-peer mark as a side effect: resets the silence
1701
- * window whenever the peer's inbound count advances. A freshly-seen peer is
1702
- * stamped at the current clock and reported live, so it always gets a full
1703
- * budget before it can be reaped.
1704
- * @private
1705
- */
1706
- #peer_is_silent(peer_id) {
1707
- const count = this.#peer.inbound_count(peer_id);
1708
- const mark = this.#peer_inbound_mark.get(peer_id);
1709
- if (mark === undefined) {
1710
- this.#peer_inbound_mark.set(peer_id, { count, since_ms: this.#liveness_clock_ms });
1711
- return false;
1712
- }
1713
- if (count !== mark.count) {
1714
- mark.count = count;
1715
- mark.since_ms = this.#liveness_clock_ms;
1716
- return false;
1717
- }
1718
- return this.#liveness_clock_ms - mark.since_ms >= this.#connection_timeout_ms;
1719
- }
1720
-
1721
- // ----------------------------------------------------------------
1722
- // Internal: client-side reconnect state machine
1723
- // ----------------------------------------------------------------
1724
-
1725
- #wire_client_transport_disconnect(transport) {
1726
- if (!transport || !transport.onDisconnect) return;
1727
- if (this.#current_transport_listener !== null && this.#current_transport !== null) {
1728
- try {
1729
- this.#current_transport.onDisconnect.remove(this.#current_transport_listener);
1730
- } catch (_) { /* swallow */
1731
- }
1732
- }
1733
- const listener = (reason) => this.#on_client_transport_disconnected(reason);
1734
- transport.onDisconnect.add(listener);
1735
- this.#current_transport_listener = listener;
1736
- }
1737
-
1738
- #on_client_transport_disconnected(reason) {
1739
- if (this.#reconnect_state === ReconnectState.Lost) return;
1740
- // Tear down the dead peer/channel binding so a fresh transport
1741
- // can be wired via `connect()`. The InterpolationLog,
1742
- // session_token, predicted-action ledger, and live world all
1743
- // survive that's what we need to claim continuity on resume.
1744
- if (this.#remote_peer_id >= 0) {
1745
- try {
1746
- this.#peer.disconnect_peer(this.#remote_peer_id);
1747
- } catch (_) { /* swallow */
1748
- }
1749
- // Stop treating this peer as a live send target. The channel is
1750
- // gone, but `#remote_peer_id` is deliberately kept so the reconnect
1751
- // ladder can re-`connect()` to the same id. Without this delete,
1752
- // `#send_empty_ack_packet` (driven every tick) dereferences the
1753
- // now-undefined channel and throws which would crash the very
1754
- // tick loop that drives the reconnect ladder.
1755
- this.#connected_peers.delete(this.#remote_peer_id);
1756
- this.#peer_inbound_mark.delete(this.#remote_peer_id);
1757
- }
1758
- this.#current_transport = null;
1759
- this.#current_transport_listener = null;
1760
- if (!this.#reconnect_policy.enabled) {
1761
- this.#permanently_lose_connection(reason || 'transport_disconnect');
1762
- return;
1763
- }
1764
- this.#reconnect_state = ReconnectState.Reconnecting;
1765
- this.#reconnect_attempt = 0;
1766
- this.#reconnect_elapsed_ms = 0;
1767
- this.#reconnect_next_delay_ms = this.#reconnect_policy.base_delay_ms;
1768
- this.#reconnect_timer_remaining_ms = this.#reconnect_next_delay_ms;
1769
- this.onConnectionLost.send1(reason || 'transport_disconnect');
1770
- }
1771
-
1772
- /**
1773
- * Per-tick advance for the client reconnect timer. When the timer
1774
- * elapses we rebuild the transport via `transport_factory` and
1775
- * call `connect()` on it; with a cached `session_token`, `connect`
1776
- * sends RESUME_HELLO. The server replies with RESUME_ACCEPT
1777
- * (→ Connected) or RESUME_REJECT (→ Resyncing, INITIAL_SYNC
1778
- * pending) or nothing within the next back-off interval (→ retry).
1779
- */
1780
- #tick_reconnect(dt_ms) {
1781
- // Resyncing has its own deadline: after RESUME_REJECT we expect
1782
- // the host to ship a fresh INITIAL_SYNC. If it never arrives
1783
- // (host crashed, network silently broke), fall to Lost so the
1784
- // application sees the failure instead of hanging forever.
1785
- if (this.#reconnect_state === ReconnectState.Resyncing) {
1786
- this.#reconnect_elapsed_ms += dt_ms;
1787
- if (this.#reconnect_elapsed_ms >= this.#reconnect_policy.total_timeout_ms) {
1788
- this.#permanently_lose_connection('resync_timeout');
1789
- }
1790
- return;
1791
- }
1792
- if (this.#reconnect_state !== ReconnectState.Reconnecting) return;
1793
- this.#reconnect_elapsed_ms += dt_ms;
1794
- if (this.#reconnect_elapsed_ms >= this.#reconnect_policy.total_timeout_ms) {
1795
- this.#permanently_lose_connection('reconnect_timeout');
1796
- return;
1797
- }
1798
- this.#reconnect_timer_remaining_ms -= dt_ms;
1799
- if (this.#reconnect_timer_remaining_ms > 0) return;
1800
-
1801
- // Time to attempt.
1802
- this.#reconnect_attempt++;
1803
- if (this.#reconnect_attempt > this.#reconnect_policy.max_attempts) {
1804
- this.#permanently_lose_connection('reconnect_attempts_exhausted');
1805
- return;
1806
- }
1807
- this.onReconnectAttempt.send1(this.#reconnect_attempt);
1808
-
1809
- if (this.#transport_factory === null) {
1810
- // No factory: caller must drive reconnects themselves. We
1811
- // remain in Reconnecting until they call connect() with a
1812
- // new transport, or until total_timeout_ms expires.
1813
- this.#reconnect_timer_remaining_ms = this.#next_backoff_delay();
1814
- return;
1815
- }
1816
- let new_transport;
1817
- try {
1818
- new_transport = this.#transport_factory();
1819
- } catch (e) {
1820
- console.warn('NetworkSession: transport_factory threw', e);
1821
- this.#reconnect_timer_remaining_ms = this.#next_backoff_delay();
1822
- return;
1823
- }
1824
- try {
1825
- this.connect(this.#remote_peer_id, new_transport);
1826
- } catch (e) {
1827
- console.warn('NetworkSession: connect() threw during reconnect', e);
1828
- this.#reconnect_timer_remaining_ms = this.#next_backoff_delay();
1829
- return;
1830
- }
1831
- // Wait the back-off interval before retrying again (if RESUME_ACCEPT
1832
- // never arrives in time).
1833
- this.#reconnect_timer_remaining_ms = this.#next_backoff_delay();
1834
- }
1835
-
1836
- #next_backoff_delay() {
1837
- const next = this.#reconnect_next_delay_ms * this.#reconnect_policy.exponential_factor;
1838
- this.#reconnect_next_delay_ms = Math.min(next, this.#reconnect_policy.max_delay_ms);
1839
- return this.#reconnect_next_delay_ms;
1840
- }
1841
-
1842
- #enter_connected() {
1843
- this.#reconnect_state = ReconnectState.Connected;
1844
- this.#reconnect_attempt = 0;
1845
- this.#reconnect_elapsed_ms = 0;
1846
- // Reset the exponential back-off so a subsequent disconnect starts
1847
- // its retry ladder from base_delay_ms without this, the next drop
1848
- // would inherit the last cycle's (potentially max_delay_ms) delay.
1849
- this.#reconnect_next_delay_ms = this.#reconnect_policy.base_delay_ms;
1850
- }
1851
-
1852
- #permanently_lose_connection(reason) {
1853
- if (this.#reconnect_state === ReconnectState.Lost) return;
1854
- this.#reconnect_state = ReconnectState.Lost;
1855
- this.onConnectionPermanentlyLost.send1(reason);
1856
- }
1857
-
1858
- // ----------------------------------------------------------------
1859
- // Internal: client predict-reconcile wiring
1860
- // ----------------------------------------------------------------
1861
-
1862
- #wire_client_predict_reconcile() {
1863
- // Rewind doesn't go through executor.execute, so the
1864
- // before_execute normalize hook misses it. Catch it here.
1865
- this.#client.onBeforeReconcile.add(() => this.normalize_if_dirty());
1866
-
1867
- // Predict: run the sampler, execute each action, record its
1868
- // bytes so the replay path can re-execute without re-sampling.
1869
- this.#client.onPredict.add((frame, _input_writer) => {
1870
- if (this.#input_sampler === null) return;
1871
- const actions = this.#input_sampler(frame);
1872
- if (!actions) return;
1873
- const recorded = [];
1874
- for (const action of actions) {
1875
- if (!action || action.isSimAction !== true) continue;
1876
- const type_id = action.constructor.type_id;
1877
- const scratch = this.#scratch_send_buf;
1878
- scratch.position = 0;
1879
- action.serialize(scratch);
1880
- const bytes = scratch.raw_bytes.slice(0, scratch.position);
1881
- recorded.push({ type_id, bytes });
1882
- this.#client.executor.execute(action, this.local_peer_id);
1883
- }
1884
- this.#sampled_actions_per_frame.set(frame, recorded);
1885
- this.#trim_sampled_action_ledger();
1886
- });
1887
-
1888
- // Replay: re-execute recorded actions from the ledger. Sampler is
1889
- // NOT called again live input has moved on.
1890
- this.#client.onReplay.add((frame, _input_reader) => {
1891
- const recorded = this.#sampled_actions_per_frame.get(frame);
1892
- if (!recorded) return;
1893
- for (const { type_id, bytes } of recorded) {
1894
- const klass = this.#peer.action_registry.klass_for(type_id);
1895
- if (klass === undefined) continue;
1896
- const action = this.#peer.action_registry.acquire(klass);
1897
- const scratch = this.#scratch_interp_buf;
1898
- scratch.position = 0;
1899
- scratch.writeBytes(bytes, 0, bytes.length);
1900
- scratch.position = 0;
1901
- action.deserialize(scratch);
1902
- try {
1903
- this.#client.executor.execute(action, this.local_peer_id);
1904
- } finally {
1905
- this.#peer.action_registry.release(action);
1906
- }
1907
- }
1908
- });
1909
-
1910
- // Apply auth state: deserialize concatenated replicated-component
1911
- // bytes into the live entity. Mirrors the host's serialize order.
1912
- this.#client.onApplyAuthState.add((_server_frame, network_id, buffer) => {
1913
- const local = this.#client.slot_table.entity_for(network_id);
1914
- if (local < 0) return;
1915
- for (const [ComponentClass] of this.#replicated) {
1916
- const live = this.world.getComponent(local, ComponentClass);
1917
- if (live === undefined) continue;
1918
- const adapter = this.binary_registry.getAdapter(ComponentClass.typeName);
1919
- adapter.deserialize(buffer, live);
1920
- }
1921
- });
1922
- }
1923
-
1924
- #trim_sampled_action_ledger() {
1925
- const action_log = this.#peer.action_log;
1926
- if (action_log === undefined) return;
1927
- const oldest_keep = Math.max(0, this.#local_frame - action_log.frame_capacity);
1928
- for (const f of this.#sampled_actions_per_frame.keys()) {
1929
- if (f < oldest_keep) this.#sampled_actions_per_frame.delete(f);
1930
- }
1931
- }
1932
-
1933
- // ----------------------------------------------------------------
1934
- // Internal: interpolation log + render-time sampling
1935
- // ----------------------------------------------------------------
1936
-
1937
- /**
1938
- * Record post-apply state for every remote-owned entity into the
1939
- * interpolation log. Locally-owned entities are predicted, not
1940
- * interpolated, so they're skipped.
1941
- *
1942
- * Doesn't fire on the host — the replicator's deferral path doesn't
1943
- * emit `onFrameApplied`, so the host gets no recording naturally.
1944
- */
1945
- #on_frame_applied(_peer_id, frame_number) {
1946
- this.#interp_log.begin_tick(frame_number);
1947
- for (const entity_id of this.#remote_entities) {
1948
- const identity = this.world.getComponent(entity_id, NetworkIdentity);
1949
- if (identity === undefined || identity.network_id < 0) continue;
1950
- for (const [ComponentClass, desc] of this.#replicated) {
1951
- if (desc.interp === null) continue;
1952
- const live = this.world.getComponent(entity_id, ComponentClass);
1953
- if (live === undefined) continue;
1954
- const adapter = this.binary_registry.getAdapter(ComponentClass.typeName);
1955
- const rec_buf = this.#interp_log.begin_record(identity.network_id, desc.interp_type_id);
1956
- adapter.serialize(rec_buf, live);
1957
- this.#interp_log.end_record();
1958
- }
1959
- }
1960
- this.#interp_log.end_tick();
1961
-
1962
- if (frame_number > this.#latest_received_frame) {
1963
- this.#adaptive_render_delay.record_arrival(performance.now(), frame_number);
1964
- this.#latest_received_frame = frame_number;
1965
- }
1966
- }
1967
-
1968
- /**
1969
- * Advance the smooth_render_frame state machine and write
1970
- * interpolated bytes into each remote-owned entity's live
1971
- * components. Sets `render_dirty` if anything was written.
1972
- *
1973
- * Host early-returns naturally (no `#latest_received_frame`
1974
- * recorded — deferred-apply path doesn't fire `onFrameApplied`).
1975
- */
1976
- #render_interpolated_entities() {
1977
- if (this.#latest_received_frame < 0) return;
1978
- if (this.#remote_entities.size === 0) return;
1979
-
1980
- const now_ms = performance.now();
1981
- const render_delay_frames = this.#adaptive_render_delay.delay_ms() / this.tick_period_ms;
1982
- const target = this.#latest_received_frame - render_delay_frames;
1983
-
1984
- if (this.#smooth_render_frame < 0) {
1985
- this.#smooth_render_frame = target < 0 ? 0 : target;
1986
- this.#smooth_last_wall_ms = now_ms;
1987
- } else {
1988
- const dt_ms = now_ms - this.#smooth_last_wall_ms;
1989
- this.#smooth_last_wall_ms = now_ms;
1990
- this.#smooth_render_frame += dt_ms / this.tick_period_ms;
1991
- const alpha = 1 - Math.exp(-PULL_PER_MS * dt_ms);
1992
- this.#smooth_render_frame += (target - this.#smooth_render_frame) * alpha;
1993
- }
1994
- if (this.#smooth_render_frame > this.#latest_received_frame) {
1995
- this.#smooth_render_frame = this.#latest_received_frame;
1996
- }
1997
- if (this.#smooth_render_frame < 0) this.#smooth_render_frame = 0;
1998
-
1999
- const tick_a = Math.floor(this.#smooth_render_frame);
2000
- const tick_b = tick_a + 1;
2001
- const t = this.#smooth_render_frame - tick_a;
2002
-
2003
- const scratch = this.#scratch_interp_buf;
2004
- let wrote_anything = false;
2005
- for (const entity_id of this.#remote_entities) {
2006
- const identity = this.world.getComponent(entity_id, NetworkIdentity);
2007
- if (identity === undefined || identity.network_id < 0) continue;
2008
- for (const [ComponentClass, desc] of this.#replicated) {
2009
- if (desc.interp === null) continue; // snap mode
2010
- const live = this.world.getComponent(entity_id, ComponentClass);
2011
- if (live === undefined) continue;
2012
- scratch.position = 0;
2013
- const ok = this.#interp_log.interpolate(
2014
- scratch, identity.network_id, desc.interp_type_id,
2015
- tick_a, tick_b, t, desc.interp,
2016
- );
2017
- if (!ok) continue;
2018
- scratch.position = 0;
2019
- const adapter = this.binary_registry.getAdapter(ComponentClass.typeName);
2020
- adapter.deserialize(scratch, live);
2021
- wrote_anything = true;
2022
- }
2023
- }
2024
- if (wrote_anything) this.render_dirty = true;
2025
- }
2026
-
2027
- // ----------------------------------------------------------------
2028
- // Utility
2029
- // ----------------------------------------------------------------
2030
-
2031
- #assert_not_started(method_name) {
2032
- if (this.#started) {
2033
- throw new Error(`NetworkSession.${method_name}: must be called before start()`);
2034
- }
2035
- }
2036
- }
1
+ import { assert } from "../../core/assert.js";
2
+ import { BinaryBuffer } from "../../core/binary/BinaryBuffer.js";
3
+ import Signal from "../../core/events/signal/Signal.js";
4
+ import { EntityObserver } from "../ecs/EntityObserver.js";
5
+ import { UUID } from "../ecs/guid/UUID.js";
6
+ import { BinarySerializationRegistry, } from "../ecs/storage/binary/BinarySerializationRegistry.js";
7
+
8
+ import { NetworkIdentity } from "./ecs/components/NetworkIdentity.js";
9
+ import { make_owner_authorization } from "./ecs/owner_authorization.js";
10
+ import { NetworkSystem } from "./ecs/NetworkSystem.js";
11
+ import { NetworkIdentitySerializationAdapter, } from "./ecs/serialization/NetworkIdentitySerializationAdapter.js";
12
+ import { NetworkPeer, ResumeRejectReason, } from "./orchestrator/NetworkPeer.js";
13
+ import { ServerAuthoritativeClient } from "./orchestrator/ServerAuthoritativeClient.js";
14
+ import { ServerAuthoritativeServer } from "./orchestrator/ServerAuthoritativeServer.js";
15
+ import { OwnerAwareScope } from "./replication/ScopeFilter.js";
16
+ import { BinaryInterpolationAdapter, InterpolationKind, } from "../interpolation/BinaryInterpolationAdapter.js";
17
+ import { InterpolationLog } from "../interpolation/InterpolationLog.js";
18
+ import { SimAction } from "./sim/SimAction.js";
19
+ import { snapshotter_emit } from "./sim/Snapshotter.js";
20
+ import { AdaptiveRenderDelay } from "./time/AdaptiveRenderDelay.js";
21
+ import { TimeDilation } from "./time/TimeDilation.js";
22
+
23
+ /**
24
+ * Roles a session can occupy. The orchestrator it wires up underneath is
25
+ * uniquely determined by the role.
26
+ *
27
+ * - `'client'` → {@link ServerAuthoritativeClient}: predicts locally
28
+ * when an input sampler is registered; reconciles to
29
+ * server-authoritative state arriving via AUTH_STATE.
30
+ * With no input sampler, a client is a pure spectator
31
+ * — it receives the action stream, smooths via
32
+ * interpolation, and never predicts or rewinds. This
33
+ * is the default role and covers both "playing" and
34
+ * "spectating" use cases.
35
+ * - `'host'` → {@link ServerAuthoritativeServer}: simulation
36
+ * authority for one or more clients; runs the optional
37
+ * server-side input buffer (`simulation_delay_ticks`).
38
+ *
39
+ * @readonly @enum {string}
40
+ */
41
+ export const NetworkSessionRole = Object.freeze({
42
+ Client: 'client',
43
+ Host: 'host',
44
+ });
45
+
46
+ const DEFAULT_TICK_RATE_HZ = 60;
47
+ const DEFAULT_SIMULATION_DELAY_TICKS = 4;
48
+ const DEFAULT_INITIAL_RENDER_DELAY_FRAMES = 6;
49
+
50
+ /**
51
+ * Default depth of the action-log ring (in frames). This single value bounds
52
+ * THREE distinct horizons at once:
53
+ * 1. Rollback depth — how far back the server can rewind+replay to fold in
54
+ * late client input.
55
+ * 2. Fresh-connect back-fill range — how many past frames a newly-connected
56
+ * peer's action stream covers before it has acked anything.
57
+ * 3. Action-stream retransmit window — how long an unacked frame stays
58
+ * resendable before it ages out of the ring.
59
+ *
60
+ * At 60 Hz, 32 frames ≈ 0.53 s. That is comfortable for rollback but tight for
61
+ * loss recovery on high-latency links: if a peer's ack RTT + jitter exceeds the
62
+ * window, unacked frames age out and the action stream develops gaps that fall
63
+ * back to the heavier RECOVERY_REQUEST → STATE_BURST path. Raise `frame_capacity`
64
+ * for high-RTT / lossy deployments (memory cost is ~one buffer per frame per
65
+ * orchestrator, so it is cheap to grow).
66
+ */
67
+ const DEFAULT_ACTION_LOG_FRAME_CAPACITY = 32;
68
+ const PULL_PER_MS = 0.0005; // wall-clock low-pass on render frame state machine
69
+ const MAX_CLIENT_DILATED_TICKS_PER_STEP = 3;
70
+ const MAX_FIXED_STEPS_PER_TICK = 8;
71
+
72
+ const DEFAULT_SERVER_RESUME_GRACE_MS = 30_000;
73
+
74
+ // Inbound-silence budget before a peer is reaped (host → grace, client →
75
+ // reconnect ladder). Matches GNS's TimeoutConnected default. The facade pumps
76
+ // at least one packet per tick in each direction (host: AUTH_STATE +
77
+ // TIME_DILATION; client: action-stream / empty-ack), so a live, mutually-
78
+ // ticking pair never approaches this — only a genuinely-gone peer does. Set 0
79
+ // to disable (e.g. turn-based games, or transports that signal disconnect
80
+ // themselves). Primarily for connectionless transports (NodeUDP, WebRTC /
81
+ // WebTransport datagram), where no close event ever fires.
82
+ const DEFAULT_CONNECTION_TIMEOUT_MS = 10_000;
83
+
84
+ const DEFAULT_RECONNECT_POLICY = Object.freeze({
85
+ enabled: true,
86
+ max_attempts: 8,
87
+ base_delay_ms: 200,
88
+ max_delay_ms: 5_000,
89
+ exponential_factor: 2.0,
90
+ total_timeout_ms: 60_000,
91
+ accept_state_resync: true,
92
+ });
93
+
94
+ /**
95
+ * Client-side reconnect state machine values.
96
+ * @readonly @enum {string}
97
+ */
98
+ const ReconnectState = Object.freeze({
99
+ Connected: 'connected',
100
+ Reconnecting: 'reconnecting',
101
+ Resyncing: 'resyncing',
102
+ Lost: 'lost',
103
+ });
104
+
105
+ /**
106
+ * Scratch buffer size used for AUTH_STATE assembly and net_mutate_component
107
+ * event payloads. Sized for the common cases (Transform ≈ 36 B); components
108
+ * with richer payloads need this raised or a different scratch lifecycle.
109
+ */
110
+ const SCRATCH_BUFFER_BYTES = 1024;
111
+
112
+ /**
113
+ * Shared zero-length payload for empty ack packets. Hoisted to module scope so
114
+ * the per-peer, per-tick ack send doesn't allocate a fresh array each time.
115
+ * @type {Uint8Array}
116
+ */
117
+ const EMPTY_PAYLOAD = new Uint8Array(0);
118
+
119
+ /**
120
+ * High-level networking facade. Wraps the orchestrator + state machinery
121
+ * into one config + lifecycle surface. The lower-level orchestrators
122
+ * remain reachable through `session.server` / `session.client` /
123
+ * `session.peer` for callers that need to override defaults post-hoc.
124
+ *
125
+ * Replication is component-driven: attaching a {@link NetworkIdentity}
126
+ * to an entity registers it with the session via an `EntityObserver`.
127
+ * Mutation is event-driven: callers fire
128
+ * `dataset.sendEvent(entity, "net_mutate_component", payload)`.
129
+ *
130
+ * Per-tick driving is a single `session.tick(dt_seconds)`. The session
131
+ * runs fixed-timestep stepping, client time dilation, server-side input
132
+ * buffer, AUTH_STATE dispatch, time-dilation feedback, and interpolated
133
+ * rendering for remote-owned entities.
134
+ *
135
+ * @author Alex Goldring
136
+ * @copyright Company Named Limited (c) 2026
137
+ */
138
+ export class NetworkSession {
139
+
140
+ // ---- Pre-start registration (set via replicate / defineAction / defineInputSampler) ----
141
+
142
+ /**
143
+ * User-registered replicated component classes, in `replicate()` call
144
+ * order. Finalized at `start()` into {@link #replicated} with
145
+ * `NetworkIdentity` prepended.
146
+ * @private @type {Array<{klass: Function, interp: BinaryInterpolationAdapter|null}>}
147
+ */
148
+ #user_replicated = [];
149
+
150
+ /**
151
+ * Finalized at `start()`. Insertion order = wire-format order;
152
+ * `NetworkIdentity` is the first entry (its slot allocation must
153
+ * fire before subsequent components attach during initial-sync apply).
154
+ * Each value: `{ interp, interp_type_id }`.
155
+ * @private @type {Map<Function, {interp: BinaryInterpolationAdapter|null, interp_type_id: number}>}
156
+ */
157
+ #replicated = new Map();
158
+
159
+ /**
160
+ * Type_id → ComponentClass for the synthetic ReplaceComponentAction.
161
+ * @private @type {Function[]}
162
+ */
163
+ #interp_type_id_to_class = [];
164
+
165
+ /** @private @type {Array<Function>} */
166
+ #user_action_classes = [];
167
+
168
+ /** @private @type {((frame: number) => Array<SimAction>|null) | null} */
169
+ #input_sampler = null;
170
+
171
+ // ---- Set in start() ----
172
+
173
+ /** @private @type {ServerAuthoritativeServer|null} */
174
+ #server = null;
175
+ /** @private @type {ServerAuthoritativeClient|null} */
176
+ #client = null;
177
+ /** @private @type {NetworkPeer|null} */
178
+ #peer = null;
179
+ /** @private @type {EntityObserver|null} */
180
+ #identity_observer = null;
181
+ /** @private @type {InterpolationLog|null} */
182
+ #interp_log = null;
183
+ /** @private @type {Function|null} */
184
+ #ReplaceComponentAction = null;
185
+
186
+ // ---- Per-entity tracking ----
187
+
188
+ /**
189
+ * `entity_id → "net_mutate_component" listener` so detach can clean up.
190
+ * Also doubles as the iterable of all networked entities.
191
+ * @private @type {Map<number, Function>}
192
+ */
193
+ #event_listeners = new Map();
194
+
195
+ /**
196
+ * Entities the local peer does NOT own — driven by the render
197
+ * interpolation pass.
198
+ * @private @type {Set<number>}
199
+ */
200
+ #remote_entities = new Set();
201
+
202
+ // ---- Render-frame interpolation state ----
203
+
204
+ /** @private @type {number} */ #latest_received_frame = -1;
205
+ /** @private @type {number} */ #smooth_render_frame = -1;
206
+ /** @private @type {number} */ #smooth_last_wall_ms = -1;
207
+
208
+ /**
209
+ * Set after `#render_interpolated_entities` writes interp values into
210
+ * live components; cleared by `normalize_if_dirty`. Drives the
211
+ * canonical-form invariant: live is canonical when this is false,
212
+ * polluted (smooth) when true.
213
+ * @private @type {boolean}
214
+ */
215
+ render_dirty = false;
216
+
217
+ // ---- Per-tick driver state ----
218
+
219
+ /** @private @type {number} */ #local_frame = 0;
220
+ /** @private @type {number} */ #local_frame_accum = 0;
221
+ /** @private @type {number} */ #sim_accum_ms = 0;
222
+
223
+ /** @private @type {Set<number>} */
224
+ #connected_peers = new Set();
225
+
226
+ /**
227
+ * Host-only: peer_id → remaining-resends. Drained in
228
+ * `#on_host_tick_complete` once `current_sim_frame >= 0`. Sent twice
229
+ * for loss tolerance; production code should re-send on receiver
230
+ * RECOVERY request.
231
+ * @private @type {Map<number, number>}
232
+ */
233
+ #pending_initial_sync = new Map();
234
+
235
+ /**
236
+ * Sampled-action ledger for predict-reconcile replay. The sampler
237
+ * reads live input, so re-calling it during reconcile would give
238
+ * wrong results; replay reads from here instead.
239
+ * @private @type {Map<number, Array<{type_id: number, bytes: Uint8Array}>>}
240
+ */
241
+ #sampled_actions_per_frame = new Map();
242
+
243
+ /** @private @type {boolean} */
244
+ #started = false;
245
+
246
+ // ---- Host-side: per-peer session token + grace state ----
247
+
248
+ /**
249
+ * Tokens issued at first INITIAL_SYNC, kept while the peer is
250
+ * connected or in the grace window. Used to authenticate
251
+ * RESUME_HELLO.
252
+ * @private @type {Map<number, UUID>}
253
+ */
254
+ #peer_session_tokens = new Map();
255
+
256
+ /**
257
+ * Peers currently in the post-disconnect grace window. Value carries
258
+ * the deadline (wall-clock ms) and the cached `last_acked_frame` so
259
+ * a successful resume restores the action-stream baseline.
260
+ * @private @type {Map<number, { deadline_ms: number, last_acked: number }>}
261
+ */
262
+ #peer_grace_state = new Map();
263
+
264
+ /**
265
+ * Per-peer transport onDisconnect listener bookkeeping so we can
266
+ * unsubscribe when the peer is fully dropped.
267
+ * @private @type {Map<number, { transport: object, listener: Function }>}
268
+ */
269
+ #peer_transport_listeners = new Map();
270
+
271
+ // ---- Client-side: session token + reconnect state machine ----
272
+
273
+ /**
274
+ * Server-issued session token (raw UUID v1 bytes). Set on first
275
+ * INITIAL_SYNC arrival; refreshed on every Tier-3 resync. Sent in
276
+ * `RESUME_HELLO` to claim continuity with a prior session record.
277
+ * Observable to game code for diagnostics; not auth-grade.
278
+ * @type {UUID|null}
279
+ */
280
+ session_token = null;
281
+
282
+ /** @private @type {string} */
283
+ #reconnect_state = ReconnectState.Connected;
284
+ /** @private @type {number} */
285
+ #reconnect_attempt = 0;
286
+ /** @private @type {number} */
287
+ #reconnect_elapsed_ms = 0;
288
+ /** @private @type {number} */
289
+ #reconnect_next_delay_ms = 0;
290
+ /** @private @type {number} */
291
+ #reconnect_timer_remaining_ms = 0;
292
+ /** @private @type {object|null} */
293
+ #current_transport = null;
294
+ /** @private @type {Function|null} */
295
+ #current_transport_listener = null;
296
+ /** @private @type {number} */
297
+ #remote_peer_id = -1;
298
+
299
+ // ---- Signals (initialized inline because they take no input) ----
300
+
301
+ /**
302
+ * Host: a connected peer's transport reported a disconnect (the peer
303
+ * has entered the grace window). Args: `(peer_id, reason)`.
304
+ * @type {Signal}
305
+ */
306
+ onPeerLost = new Signal();
307
+
308
+ /**
309
+ * Host: a peer has been permanently freed — either via
310
+ * `drop_peer()`, a grace-window timeout, or a peer-initiated
311
+ * DISCONNECT. Args: `(peer_id, reason)`.
312
+ * @type {Signal}
313
+ */
314
+ onPeerPermanentlyDropped = new Signal();
315
+
316
+ /**
317
+ * Client: the transport has reported a disconnect, and the
318
+ * reconnect ladder has started. Args: `(reason)`.
319
+ * @type {Signal}
320
+ */
321
+ onConnectionLost = new Signal();
322
+
323
+ /**
324
+ * Client: about to rebuild the transport and dial the host. Args:
325
+ * `(attempt_number)`.
326
+ * @type {Signal}
327
+ */
328
+ onReconnectAttempt = new Signal();
329
+
330
+ /**
331
+ * Client: reconnect succeeded. Args: `({ state_resynced: boolean })` —
332
+ * `state_resynced` is `true` if we fell through to Tier-3 (server
333
+ * had purged our state and re-sent INITIAL_SYNC), `false` for the
334
+ * happy Tier-2 path where the action stream just resumed.
335
+ * @type {Signal}
336
+ */
337
+ onReconnected = new Signal();
338
+
339
+ /**
340
+ * Client: the reconnect ladder is exhausted, or the host explicitly
341
+ * kicked us with a DISCONNECT packet, or `reconnect.enabled` is
342
+ * false and the link dropped. Args: `(reason)`.
343
+ * @type {Signal}
344
+ */
345
+ onConnectionPermanentlyLost = new Signal();
346
+
347
+ // ---- Set in constructor (depend on input or imperative setup) ----
348
+
349
+ /** @private */ #transport = null;
350
+ /** @private */ #scope_filter = null;
351
+ /** @private @type {TimeDilation} */ #time_dilation;
352
+ /** @private @type {AdaptiveRenderDelay} */ #adaptive_render_delay;
353
+ /** @private @type {BinaryBuffer} */ #scratch_send_buf;
354
+ /** @private @type {BinaryBuffer} */ #scratch_interp_buf;
355
+ /** @private @type {() => void} */ #bound_normalize_if_dirty;
356
+
357
+ /** @private @type {number} */ #server_resume_grace_ms;
358
+ /** @private @type {object} */ #reconnect_policy;
359
+ /** @private @type {(() => object) | null} */ #transport_factory;
360
+
361
+ /** @private @type {number} ms of inbound silence before reaping; 0 = off */ #connection_timeout_ms;
362
+ /** @private @type {number} monotonic ms clock advanced by tick(dt) */ #liveness_clock_ms = 0;
363
+ /**
364
+ * Per-peer liveness mark: the inbound-packet count last seen and the clock
365
+ * time it was last seen advancing. Drives idle reaping.
366
+ * @private @type {Map<number, { count: number, since_ms: number }>}
367
+ */
368
+ #peer_inbound_mark = new Map();
369
+
370
+ /**
371
+ * @param {{
372
+ * entity_manager: EntityManager,
373
+ * transport?: object,
374
+ * transport_factory?: (() => object),
375
+ * role?: 'client'|'host',
376
+ * local_peer_id?: number,
377
+ * simulation_delay_ticks?: number,
378
+ * tick_rate_hz?: number,
379
+ * binary_registry?: BinarySerializationRegistry,
380
+ * scope_filter?: object,
381
+ * time_dilation?: TimeDilation,
382
+ * adaptive_render_delay?: AdaptiveRenderDelay,
383
+ * server_resume_grace_ms?: number,
384
+ * connection_timeout_ms?: number,
385
+ * reconnect?: {
386
+ * enabled?: boolean,
387
+ * max_attempts?: number,
388
+ * base_delay_ms?: number,
389
+ * max_delay_ms?: number,
390
+ * exponential_factor?: number,
391
+ * total_timeout_ms?: number,
392
+ * accept_state_resync?: boolean,
393
+ * },
394
+ * }} options
395
+ *
396
+ * - `transport` is not connected at construction — call {@link connect}
397
+ * with an explicit remote peer id after {@link start}.
398
+ * - `transport_factory` (client role): zero-arg function that returns
399
+ * a fresh `Transport` on each reconnect attempt. Required for
400
+ * automatic reconnect; without it, transport-level disconnects fall
401
+ * straight through to `onConnectionPermanentlyLost`.
402
+ * - `role` defaults to `'client'`. See {@link NetworkSessionRole}.
403
+ * - `local_peer_id` defaults to 0 for host, 1 for client. Must be
404
+ * unique across peers.
405
+ * - `simulation_delay_ticks` is honored only under `role: 'host'`.
406
+ * Default 4. See {@link ServerAuthoritativeServer}.
407
+ * - `tick_rate_hz` defaults to 60.
408
+ * - `frame_capacity` sizes the action-log ring (rollback depth + back-fill
409
+ * range + retransmit window — see {@link DEFAULT_ACTION_LOG_FRAME_CAPACITY}).
410
+ * Default 32. Raise it for high-RTT / lossy links.
411
+ * - `scope_filter` defaults to {@link OwnerAwareScope} on the host.
412
+ * - `server_resume_grace_ms` (host role): how long peer state is
413
+ * retained after a transport drop before being freed. Default 30s.
414
+ * - `connection_timeout_ms`: inbound-silence budget before a peer is
415
+ * reaped (host → grace, client → reconnect ladder). Default 10s; set 0
416
+ * to disable. Mainly for connectionless transports, which never emit a
417
+ * disconnect event — see {@link DEFAULT_CONNECTION_TIMEOUT_MS}.
418
+ * - `reconnect` (client role): policy for the reconnect ladder. See
419
+ * {@link DEFAULT_RECONNECT_POLICY}. Set `enabled: false` to opt
420
+ * out entirely.
421
+ */
422
+ constructor({
423
+ entity_manager,
424
+ transport = null,
425
+ transport_factory = null,
426
+ role = NetworkSessionRole.Client,
427
+ local_peer_id = -1,
428
+ simulation_delay_ticks = DEFAULT_SIMULATION_DELAY_TICKS,
429
+ tick_rate_hz = DEFAULT_TICK_RATE_HZ,
430
+ frame_capacity = DEFAULT_ACTION_LOG_FRAME_CAPACITY,
431
+ binary_registry = null,
432
+ scope_filter = null,
433
+ time_dilation = null,
434
+ adaptive_render_delay = null,
435
+ server_resume_grace_ms = DEFAULT_SERVER_RESUME_GRACE_MS,
436
+ connection_timeout_ms = DEFAULT_CONNECTION_TIMEOUT_MS,
437
+ reconnect = null,
438
+ } = {}) {
439
+ assert.ok(entity_manager !== undefined && entity_manager !== null,
440
+ 'NetworkSession: entity_manager is required');
441
+ if (role !== NetworkSessionRole.Client && role !== NetworkSessionRole.Host) {
442
+ throw new Error(`NetworkSession: invalid role '${role}'`);
443
+ }
444
+ assert.isNumber(tick_rate_hz, 'tick_rate_hz');
445
+ assert.ok(tick_rate_hz > 0, 'tick_rate_hz must be positive');
446
+ assert.isNonNegativeInteger(simulation_delay_ticks, 'simulation_delay_ticks');
447
+ assert.isPositiveInteger(frame_capacity, 'frame_capacity');
448
+
449
+ /**
450
+ * @readonly
451
+ * @type {EntityManager}
452
+ */
453
+ this.entity_manager = entity_manager;
454
+
455
+ /**
456
+ * @readonly
457
+ * @type {EntityComponentDataset}
458
+ */
459
+ this.world = entity_manager.dataset;
460
+
461
+ /**
462
+ * @readonly
463
+ * @type {NetworkSessionRole|string}
464
+ */
465
+ this.role = role;
466
+
467
+ /**
468
+ * @readonly
469
+ * @type {number}
470
+ */
471
+ this.tick_period_ms = 1000 / tick_rate_hz;
472
+
473
+ /**
474
+ * @readonly
475
+ * @type {number}
476
+ */
477
+ this.simulation_delay_ticks = role === NetworkSessionRole.Host
478
+ ? simulation_delay_ticks
479
+ : 0;
480
+
481
+ /**
482
+ * Depth of the action-log ring passed to the orchestrator. Bounds
483
+ * rollback depth, back-fill range, and retransmit window — see
484
+ * {@link DEFAULT_ACTION_LOG_FRAME_CAPACITY}.
485
+ * @readonly
486
+ * @type {number}
487
+ */
488
+ this.frame_capacity = frame_capacity;
489
+
490
+ /**
491
+ * @readonly
492
+ * @type {number}
493
+ */
494
+ this.local_peer_id = local_peer_id >= 0
495
+ ? local_peer_id
496
+ : (role === NetworkSessionRole.Host ? 0 : 1);
497
+
498
+ /**
499
+ * @readonly
500
+ * @type {BinarySerializationRegistry}
501
+ */
502
+ this.binary_registry = binary_registry || new BinarySerializationRegistry();
503
+
504
+ this.#transport = transport;
505
+ this.#scope_filter = scope_filter;
506
+
507
+ this.#time_dilation = time_dilation || new TimeDilation({
508
+ target_buffer_depth: this.simulation_delay_ticks > 0 ? this.simulation_delay_ticks : 2,
509
+ max_dilation: 0.05,
510
+ gain: 0.05,
511
+ });
512
+ this.#adaptive_render_delay = adaptive_render_delay || new AdaptiveRenderDelay({
513
+ tick_period_ms: this.tick_period_ms,
514
+ min_delay_frames: 2,
515
+ max_delay_frames: 30,
516
+ initial_delay_frames: DEFAULT_INITIAL_RENDER_DELAY_FRAMES,
517
+ history_size: 60,
518
+ safety_multiplier: 2.0,
519
+ decay_per_sample_ms: 1.0,
520
+ });
521
+
522
+ this.#scratch_send_buf = new BinaryBuffer();
523
+ this.#scratch_send_buf.setCapacity(SCRATCH_BUFFER_BYTES);
524
+
525
+ this.#scratch_interp_buf = new BinaryBuffer();
526
+ this.#scratch_interp_buf.setCapacity(SCRATCH_BUFFER_BYTES);
527
+
528
+ /**
529
+ * @type {() => void}
530
+ */
531
+ this.#bound_normalize_if_dirty = () => this.normalize_if_dirty();
532
+
533
+ this.#server_resume_grace_ms = server_resume_grace_ms;
534
+ assert.isNonNegativeInteger(connection_timeout_ms, 'connection_timeout_ms');
535
+ this.#connection_timeout_ms = connection_timeout_ms;
536
+ this.#reconnect_policy = Object.freeze({ ...DEFAULT_RECONNECT_POLICY, ...(reconnect || {}) });
537
+ this.#transport_factory = transport_factory;
538
+ }
539
+
540
+ // ----------------------------------------------------------------
541
+ // Configuration (must run before start())
542
+ // ----------------------------------------------------------------
543
+
544
+ /**
545
+ * Register a component class for network replication.
546
+ *
547
+ * The component must have a {@link BinaryClassSerializationAdapter}
548
+ * registered for its `typeName` in the session's `binary_registry`
549
+ * before {@link start} is called — that adapter handles the wire
550
+ * format and rewind capture.
551
+ *
552
+ * `interpolator` is optional. When omitted, the component's state
553
+ * snaps on each received update (no sub-tick smoothing). When
554
+ * provided, it must extend {@link BinaryInterpolationAdapter} with
555
+ * `kind = InterpolationKind.Linear` — Discrete and Cubic are
556
+ * reserved for future support and currently throw.
557
+ *
558
+ * The order of `replicate()` calls must match across all peers in
559
+ * the session: AUTH_STATE payload layout iterates components in
560
+ * insertion order.
561
+ *
562
+ * @param {Function} ComponentClass
563
+ * @param {BinaryInterpolationAdapter} [interpolator]
564
+ */
565
+ replicate(ComponentClass, interpolator = null) {
566
+ this.#assert_not_started('replicate');
567
+ assert.isFunction(ComponentClass, 'ComponentClass');
568
+
569
+ if (ComponentClass === NetworkIdentity) {
570
+ throw new Error("NetworkSession.replicate: NetworkIdentity is auto-replicated; do not call replicate() for it");
571
+ }
572
+
573
+ for (const entry of this.#user_replicated) {
574
+ if (entry.klass === ComponentClass) {
575
+ throw new Error(`NetworkSession.replicate: ${ComponentClass.typeName ?? ComponentClass.name} already registered`);
576
+ }
577
+ }
578
+ if (interpolator !== null) {
579
+ if (!(interpolator instanceof BinaryInterpolationAdapter)) {
580
+ throw new Error("NetworkSession.replicate: interpolator must extend BinaryInterpolationAdapter");
581
+ }
582
+ if (interpolator.kind !== InterpolationKind.Linear) {
583
+ throw new Error(
584
+ `NetworkSession.replicate: only Linear interpolation is currently supported; ` +
585
+ `got kind=${interpolator.kind} for ${ComponentClass.typeName ?? ComponentClass.name}. ` +
586
+ `Omit the interpolator argument for snap (Discrete) behavior; Cubic is reserved.`,
587
+ );
588
+ }
589
+ }
590
+ this.#user_replicated.push({ klass: ComponentClass, interp: interpolator });
591
+ }
592
+
593
+ /**
594
+ * Register a user-defined {@link SimAction} subclass with the
595
+ * session. The action's `type_id` is assigned by the underlying
596
+ * registry when {@link start} runs. Calling code constructs
597
+ * instances directly (`new MyAction(...)`) and dispatches via
598
+ * {@link send} or returns them from the input sampler.
599
+ *
600
+ * @param {Function} SimActionClass class extending SimAction
601
+ */
602
+ defineAction(SimActionClass) {
603
+ this.#assert_not_started('defineAction');
604
+ assert.isFunction(SimActionClass, 'SimActionClass');
605
+ this.#user_action_classes.push(SimActionClass);
606
+ }
607
+
608
+ /**
609
+ * Install a per-tick input sampler (client role only). The sampler
610
+ * is called once per local sim tick at predict time and must return
611
+ * an array of {@link SimAction} instances representing the local
612
+ * peer's inputs for that frame. The session executes them locally
613
+ * (predict), records their serialized bytes for replay, and forwards
614
+ * them over the action stream.
615
+ *
616
+ * The sampler is NOT called during reconciliation replay — the
617
+ * recorded bytes are deserialized and re-executed instead, so the
618
+ * action set faithfully reproduces what the user originally
619
+ * sampled at that frame.
620
+ *
621
+ * Return `[]` (or `null`) for ticks with no inputs.
622
+ *
623
+ * @param {(frame: number) => Array<SimAction>|null} sampler_fn
624
+ */
625
+ defineInputSampler(sampler_fn) {
626
+ this.#assert_not_started('defineInputSampler');
627
+ assert.isFunction(sampler_fn, 'sampler_fn');
628
+
629
+ if (this.role !== NetworkSessionRole.Client) {
630
+ throw new Error(`NetworkSession.defineInputSampler: only valid for role='${NetworkSessionRole.Client}', got '${this.role}'`);
631
+ }
632
+
633
+ this.#input_sampler = sampler_fn;
634
+ }
635
+
636
+ // ----------------------------------------------------------------
637
+ // Lifecycle
638
+ // ----------------------------------------------------------------
639
+
640
+ /**
641
+ * Wire the session to the engine. After this, the EntityObserver
642
+ * is active, the right orchestrator is constructed, and a
643
+ * {@link NetworkSystem} is attached. Subsequent {@link replicate}
644
+ * / {@link defineAction} / {@link defineInputSampler} calls throw.
645
+ *
646
+ * Pre-condition: every component class passed to {@link replicate}
647
+ * must already have a `BinaryClassSerializationAdapter` registered
648
+ * in the session's binary_registry.
649
+ */
650
+ async start() {
651
+ if (this.#started) throw new Error("NetworkSession.start: already started");
652
+
653
+ // Auto-register the NetworkIdentity adapter so initial-sync can
654
+ // ship it across the wire. Allow caller-supplied registrations
655
+ // to win (game might have a custom one).
656
+ if (this.binary_registry.getAdapter(NetworkIdentity.typeName) === undefined) {
657
+ this.binary_registry.registerAdapter(new NetworkIdentitySerializationAdapter(), NetworkIdentity.typeName);
658
+ }
659
+
660
+ // Finalize the replicated-components ordering: NetworkIdentity
661
+ // FIRST (so it gets type_id=0 and arrives as the first per-entity
662
+ // record in snapshots — receiver attaches NetworkIdentity first,
663
+ // which triggers slot-table allocation before any other component
664
+ // payload needs to look up `entity_for(network_id)`).
665
+ let next_id = 0;
666
+ const network_identity_scratch = new BinaryBuffer();
667
+ network_identity_scratch.setCapacity(SCRATCH_BUFFER_BYTES);
668
+ this.#replicated.set(NetworkIdentity, {
669
+ interp: null,
670
+ interp_type_id: next_id++,
671
+ });
672
+ this.#interp_type_id_to_class[0] = NetworkIdentity;
673
+ for (const { klass, interp } of this.#user_replicated) {
674
+ const interp_type_id = next_id++;
675
+ this.#replicated.set(klass, { interp, interp_type_id });
676
+ this.#interp_type_id_to_class[interp_type_id] = klass;
677
+ }
678
+
679
+ // Verify each replicated component has a serialization adapter.
680
+ for (const ComponentClass of this.#replicated.keys()) {
681
+ const name = ComponentClass.typeName;
682
+ if (typeof name !== 'string') {
683
+ throw new Error(`NetworkSession.start: ${ComponentClass.name} has no static .typeName; cannot look up its adapter`);
684
+ }
685
+ if (this.binary_registry.getAdapter(name) === undefined) {
686
+ throw new Error(`NetworkSession.start: no BinaryClassSerializationAdapter registered for ${name}; register one in the binary_registry before calling start()`);
687
+ }
688
+ }
689
+
690
+ // Build the synthetic ReplaceComponentAction now so it's in the
691
+ // action_classes list when the orchestrator is constructed.
692
+ this.#ReplaceComponentAction = this.#make_replace_component_action_class();
693
+
694
+ const replicated_components = Array.from(this.#replicated.keys());
695
+ const action_classes = [...this.#user_action_classes, this.#ReplaceComponentAction];
696
+
697
+ const orchestrator_opts = {
698
+ world: this.world,
699
+ binary_registry: this.binary_registry,
700
+ replicated_components,
701
+ action_classes,
702
+ frame_capacity: this.frame_capacity,
703
+ };
704
+
705
+ if (this.role === NetworkSessionRole.Host) {
706
+ this.#server = new ServerAuthoritativeServer({
707
+ ...orchestrator_opts,
708
+ simulation_delay_ticks: this.simulation_delay_ticks,
709
+ });
710
+ this.#peer = this.#server.peer;
711
+ this.#peer.replicator.scope_filter = this.#scope_filter || new OwnerAwareScope({
712
+ world: this.world,
713
+ slot_table: this.#server.slot_table,
714
+ identity_class: NetworkIdentity,
715
+ });
716
+ this.#server.onTickComplete.add((sim_frame) => this.#on_host_tick_complete(sim_frame));
717
+ } else {
718
+ // Client role: covers both "playing" (input sampler registered)
719
+ // and "spectator" (no sampler — sampler-driven onPredict
720
+ // returns no actions, so the predict-reconcile machinery
721
+ // exists but stays idle).
722
+ this.#client = new ServerAuthoritativeClient({
723
+ ...orchestrator_opts,
724
+ time_dilation: this.#time_dilation,
725
+ });
726
+ this.#peer = this.#client.peer;
727
+ if (this.#scope_filter) {
728
+ this.#peer.replicator.scope_filter = this.#scope_filter;
729
+ }
730
+ this.#wire_client_predict_reconcile();
731
+ }
732
+
733
+ // Install the canonical-form normalize hook on the executor.
734
+ // Anything that calls executor.execute — predict apply, action-
735
+ // stream apply, reconcile replay — first normalizes the world.
736
+ // Idempotent: cheap when `render_dirty` is false (one bool
737
+ // check), expensive (one adapter.deserialize per remote-owned
738
+ // component) only on the first execute after a render pass.
739
+ this.#peer.executor.before_execute = this.#bound_normalize_if_dirty;
740
+
741
+ // Secure-by-default inbound authorization on the host: a remote client
742
+ // may only mutate entities it owns (NetworkIdentity.owner_peer_id ===
743
+ // sender); the authoritative server (sender === local_peer_id) may
744
+ // mutate anything. Without this a connected peer can craft an action
745
+ // targeting another peer's (or a server-owned) entity. Games needing a
746
+ // different policy can overwrite executor.authorize after start().
747
+ // Client sessions trust the server's stream and so are not gated.
748
+ if (this.role === NetworkSessionRole.Host) {
749
+ this.#peer.executor.authorize = make_owner_authorization({
750
+ world: this.world,
751
+ identity_class: NetworkIdentity,
752
+ server_peer_id: this.local_peer_id,
753
+ });
754
+ }
755
+
756
+ // NetworkSystem handles slot_table allocation on NetworkIdentity attach.
757
+ // addSystem returns a promise that resolves once the system's own
758
+ // startup completes (it auto-starts when added to an already-Running
759
+ // EM). Await it so the session's identity observer below doesn't
760
+ // race with the NetworkSystem's observer over slot_table allocation.
761
+ await this.entity_manager.addSystem(new NetworkSystem(this.#peer));
762
+
763
+ // Sub-tick interpolation log: records per-frame replicated component
764
+ // state, sampled at render time for remotely-owned entities.
765
+ this.#interp_log = new InterpolationLog({
766
+ buffer_capacity_bytes: 65536,
767
+ records_capacity: 4096,
768
+ });
769
+
770
+ // Per-frame applied: snapshot all replicated components for each
771
+ // remote-owned entity into the interpolation log.
772
+ this.#peer.replicator.onFrameApplied.add((peer_id, frame_number) => {
773
+ this.#on_frame_applied(peer_id, frame_number);
774
+ });
775
+
776
+ // Initial-sync arrival (client only): store the session token,
777
+ // populate the world from the host's snapshot, resume normal
778
+ // action-stream processing, and finalize any in-flight Tier-3
779
+ // resync state.
780
+ if (this.role === NetworkSessionRole.Client) {
781
+ this.#peer.onInitialSync.add((_peer_id, session_token, _frame_number, buf, payload_end) => {
782
+ const token = new UUID();
783
+ token.data = session_token;
784
+ this.session_token = token;
785
+ this.#apply_initial_sync(buf, payload_end);
786
+ if (this.#reconnect_state === ReconnectState.Resyncing) {
787
+ this.#enter_connected();
788
+ this.onReconnected.send1({ state_resynced: true });
789
+ }
790
+ });
791
+ // Host's response to our RESUME_HELLO during reconnect.
792
+ this.#peer.onResumeAccept.add(() => {
793
+ if (this.#reconnect_state === ReconnectState.Reconnecting
794
+ || this.#reconnect_state === ReconnectState.Resyncing) {
795
+ this.#enter_connected();
796
+ this.onReconnected.send1({ state_resynced: false });
797
+ }
798
+ });
799
+ this.#peer.onResumeReject.add((_peer_id, reason_code) => {
800
+ if (this.#reconnect_policy.accept_state_resync) {
801
+ // Tier-3: drop unconfirmed predictions and wait for
802
+ // the host to deliver a fresh INITIAL_SYNC.
803
+ this.#sampled_actions_per_frame.clear();
804
+ this.session_token = null;
805
+ this.#reconnect_state = ReconnectState.Resyncing;
806
+ // Restart the elapsed-ms budget so the Resyncing
807
+ // watchdog (see #tick_reconnect) has a full
808
+ // total_timeout_ms before falling to Lost.
809
+ this.#reconnect_elapsed_ms = 0;
810
+ } else {
811
+ this.#permanently_lose_connection(`resume_rejected:${reason_code}`);
812
+ }
813
+ });
814
+ // Host kicked us — final, no reconnect.
815
+ this.#peer.onDisconnectPacket.add((_peer_id, reason_label) => {
816
+ this.#permanently_lose_connection(reason_label || 'disconnected_by_host');
817
+ });
818
+ } else {
819
+ // Host: RESUME_HELLO validation + peer-initiated disconnects.
820
+ this.#peer.onResumeHello.add((peer_id, local_peer_id, last_acked_frame, session_token) => {
821
+ this.#on_resume_hello(peer_id, local_peer_id, last_acked_frame, session_token);
822
+ });
823
+ this.#peer.onDisconnectPacket.add((peer_id, reason_label) => {
824
+ // Client-initiated disconnect — skip the grace window
825
+ // and free state immediately.
826
+ this.drop_peer(peer_id, reason_label || 'client_disconnect');
827
+ });
828
+ }
829
+
830
+ // EntityObserver for NetworkIdentity: patches owner/network_id on
831
+ // attach, wires the per-entity mutation listener.
832
+ this.#identity_observer = new EntityObserver(
833
+ [NetworkIdentity],
834
+ (identity, entity_id) => this.#on_identity_attached(identity, entity_id),
835
+ (identity, entity_id) => this.#on_identity_detached(identity, entity_id),
836
+ this,
837
+ );
838
+ this.#identity_observer.connect(this.world);
839
+
840
+ this.#started = true;
841
+ }
842
+
843
+ /**
844
+ * Connect to a remote peer. Host can connect many peers (one per
845
+ * client); client typically connects one (the server). Called both
846
+ * for first-time connects and reconnect attempts — the session
847
+ * detects which based on whether a `session_token` is cached
848
+ * (client) or whether the peer is in the grace window (host).
849
+ *
850
+ * @param {number} remote_peer_id
851
+ * @param {object} transport
852
+ */
853
+ connect(remote_peer_id, transport) {
854
+ if (!this.#started) throw new Error("NetworkSession.connect: call start() first");
855
+
856
+ if (this.role === NetworkSessionRole.Host) {
857
+ const in_grace = this.#peer_grace_state.has(remote_peer_id);
858
+ this.#server.connect_peer(remote_peer_id, transport);
859
+ if (in_grace) {
860
+ // Resume candidate. Wait for RESUME_HELLO; restore
861
+ // baseline + add to connected_peers on validation.
862
+ // Don't queue INITIAL_SYNC — it'll only fire if we
863
+ // RESUME_REJECT.
864
+ } else {
865
+ // Fresh peer. Queue initial-sync for the next host tick.
866
+ this.#pending_initial_sync.set(remote_peer_id, 2);
867
+ this.#connected_peers.add(remote_peer_id);
868
+ }
869
+ this.#wire_peer_transport_disconnect(remote_peer_id, transport);
870
+ } else {
871
+ // Client
872
+ this.#client.connect_to_server(remote_peer_id, transport);
873
+ this.#remote_peer_id = remote_peer_id;
874
+ this.#current_transport = transport;
875
+ this.#wire_client_transport_disconnect(transport);
876
+ this.#connected_peers.add(remote_peer_id);
877
+
878
+ // If we have a session token cached, treat this as a
879
+ // reconnect attempt and send RESUME_HELLO immediately. A
880
+ // fresh session has no token yet (it arrives in the first
881
+ // INITIAL_SYNC), so first-time connects don't trigger this.
882
+ if (this.session_token !== null) {
883
+ const last_acked = Math.max(0, this.#latest_received_frame);
884
+ this.#peer.send_resume_hello(
885
+ remote_peer_id, this.local_peer_id, last_acked,
886
+ this.session_token.data,
887
+ );
888
+ }
889
+ }
890
+ }
891
+
892
+ /**
893
+ * Host-only: forcibly free a peer's state, regardless of whether
894
+ * they are actively connected, in the grace window, or already
895
+ * unknown. Fires `onPeerPermanentlyDropped(peer_id, reason)`.
896
+ * Sends a DISCONNECT packet if the peer is still actively
897
+ * connected so the remote can short-circuit its reconnect attempts.
898
+ *
899
+ * Policy for *when* to drop (high server load, anti-cheat, admin
900
+ * kick, etc.) is the caller's; the session provides the mechanism.
901
+ *
902
+ * @param {number} peer_id
903
+ * @param {string} [reason]
904
+ */
905
+ drop_peer(peer_id, reason = '') {
906
+ if (this.role !== NetworkSessionRole.Host) {
907
+ throw new Error("NetworkSession.drop_peer: host-only");
908
+ }
909
+ const was_connected = this.#connected_peers.has(peer_id);
910
+ const was_in_grace = this.#peer_grace_state.has(peer_id);
911
+ if (!was_connected && !was_in_grace) return;
912
+
913
+ if (was_connected) {
914
+ // Best-effort notification — peer may have already dropped.
915
+ try {
916
+ this.#peer.send_disconnect(peer_id, reason);
917
+ } catch (_) { /* swallow */
918
+ }
919
+ this.#server.disconnect_peer(peer_id);
920
+ }
921
+ this.#unwire_peer_transport_disconnect(peer_id);
922
+ this.#connected_peers.delete(peer_id);
923
+ this.#peer_inbound_mark.delete(peer_id);
924
+ this.#peer_grace_state.delete(peer_id);
925
+ this.#peer_session_tokens.delete(peer_id);
926
+ this.#pending_initial_sync.delete(peer_id);
927
+ this.onPeerPermanentlyDropped.send2(peer_id, reason);
928
+ }
929
+
930
+ /**
931
+ * Client-only: voluntarily disconnect from the server. Sends a
932
+ * DISCONNECT packet so the host can free state immediately and
933
+ * skip the grace window. Disables reconnect for this session.
934
+ *
935
+ * @param {string} [reason]
936
+ */
937
+ disconnect(reason = '') {
938
+ if (this.role !== NetworkSessionRole.Client) {
939
+ throw new Error("NetworkSession.disconnect: client-only — host uses drop_peer instead");
940
+ }
941
+ if (this.#remote_peer_id < 0) return;
942
+ const peer_id = this.#remote_peer_id;
943
+ try {
944
+ this.#peer.send_disconnect(peer_id, reason);
945
+ } catch (_) { /* swallow */
946
+ }
947
+ // Tear down the channel binding so a subsequent connect() with a
948
+ // fresh transport doesn't trip the underlying NetworkPeer's
949
+ // "peer already exists" guard. Mirrors the cleanup that
950
+ // #on_client_transport_disconnected does on an involuntary drop.
951
+ try {
952
+ this.#peer.disconnect_peer(peer_id);
953
+ } catch (_) { /* swallow */
954
+ }
955
+ // Drop the peer from the live send set so the (still-running) tick loop
956
+ // doesn't try to push empty-ack packets through the disposed channel.
957
+ this.#connected_peers.delete(peer_id);
958
+ this.#peer_inbound_mark.delete(peer_id);
959
+ if (this.#current_transport_listener !== null && this.#current_transport !== null) {
960
+ try {
961
+ this.#current_transport.onDisconnect.remove(this.#current_transport_listener);
962
+ } catch (_) { /* swallow */
963
+ }
964
+ }
965
+ this.#current_transport = null;
966
+ this.#current_transport_listener = null;
967
+ this.#remote_peer_id = -1;
968
+ this.#permanently_lose_connection(reason || 'client_disconnect');
969
+ }
970
+
971
+ /**
972
+ * Tear down. Idempotent — safe to call from cleanup paths.
973
+ */
974
+ stop() {
975
+ if (!this.#started) return;
976
+ if (this.#identity_observer !== null) {
977
+ this.#identity_observer.disconnect();
978
+ this.#identity_observer = null;
979
+ }
980
+ for (const [entity_id, listener] of this.#event_listeners) {
981
+ this.world.removeEntityEventListener(entity_id, "net_mutate_component", listener);
982
+ }
983
+ this.#event_listeners.clear();
984
+ this.#remote_entities.clear();
985
+ this.#sampled_actions_per_frame.clear();
986
+ this.#peer_inbound_mark.clear();
987
+ this.#started = false;
988
+ }
989
+
990
+ // ----------------------------------------------------------------
991
+ // Per-tick driver
992
+ // ----------------------------------------------------------------
993
+
994
+ /**
995
+ * Fixed-timestep accumulator-driven step. Catch-up bounded by
996
+ * `MAX_FIXED_STEPS_PER_TICK`. Under `role: 'client'` the local frame
997
+ * advances at `1 / dilation_factor` per fixed step.
998
+ *
999
+ * @param {number} dt_seconds elapsed wall seconds since the previous call
1000
+ */
1001
+ tick(dt_seconds) {
1002
+ if (!this.#started) return;
1003
+
1004
+ // Restore canonical form before sim work. The executor's
1005
+ // `before_execute` hook also covers per-action paths (e.g.
1006
+ // packets delivered between session.tick calls).
1007
+ this.normalize_if_dirty();
1008
+
1009
+ const dt_ms = dt_seconds * 1000;
1010
+
1011
+ // Host: expire grace-window peers whose deadline has passed.
1012
+ if (this.role === NetworkSessionRole.Host) {
1013
+ this.#expire_grace_peers(performance.now());
1014
+ } else {
1015
+ // Client: drive the reconnect timer.
1016
+ this.#tick_reconnect(dt_ms);
1017
+ }
1018
+
1019
+ // Idle reaping: a connected peer that has gone silent past the timeout
1020
+ // is reaped (host → grace, client → reconnect ladder). The clock is the
1021
+ // deterministic tick-dt accumulator (not wall-clock), so it's testable.
1022
+ if (this.#connection_timeout_ms > 0) {
1023
+ this.#liveness_clock_ms += dt_ms;
1024
+ this.#reap_silent_peers();
1025
+ }
1026
+
1027
+ this.#sim_accum_ms += dt_ms;
1028
+ let steps = 0;
1029
+ while (this.#sim_accum_ms >= this.tick_period_ms && steps < MAX_FIXED_STEPS_PER_TICK) {
1030
+ this.#sim_accum_ms -= this.tick_period_ms;
1031
+ this.#simulate_one_step();
1032
+ steps++;
1033
+ }
1034
+ if (steps === MAX_FIXED_STEPS_PER_TICK) this.#sim_accum_ms = 0;
1035
+
1036
+ // Per-render-frame: smooth interpolated rendering of remote
1037
+ // entities. Marks the live ECD as polluted so the next sim-tick
1038
+ // or reconcile starts with a normalize.
1039
+ this.#render_interpolated_entities();
1040
+ }
1041
+
1042
+ /**
1043
+ * Undo render-time interpolation on remote-owned components by
1044
+ * restoring each from its latest InterpolationLog entry. Idempotent
1045
+ * via the `render_dirty` flag — cheap when false.
1046
+ *
1047
+ * Called from `tick()` (top), `executor.before_execute` (per-action
1048
+ * applies, including transport-delivered action stream), and
1049
+ * `client.onBeforeReconcile` (before the rewind walk).
1050
+ *
1051
+ * @private
1052
+ */
1053
+ normalize_if_dirty() {
1054
+ if (!this.render_dirty) return;
1055
+ this.render_dirty = false;
1056
+
1057
+ if (this.#latest_received_frame < 0) return;
1058
+ if (this.#remote_entities.size === 0) return;
1059
+
1060
+ const scratch = this.#scratch_interp_buf;
1061
+ const tick = this.#latest_received_frame;
1062
+
1063
+ for (const entity_id of this.#remote_entities) {
1064
+ const identity = this.world.getComponent(entity_id, NetworkIdentity);
1065
+ if (identity === undefined || identity.network_id < 0) continue;
1066
+ for (const [ComponentClass, desc] of this.#replicated) {
1067
+ if (desc.interp === null) continue;
1068
+ const live = this.world.getComponent(entity_id, ComponentClass);
1069
+ if (live === undefined) continue;
1070
+ scratch.position = 0;
1071
+ // tick_a === tick_b, t = 0 — degenerate "snap to single
1072
+ // snapshot" lerp. Output equals the stored snapshot.
1073
+ const ok = this.#interp_log.interpolate(
1074
+ scratch, identity.network_id, desc.interp_type_id,
1075
+ tick, tick, 0, desc.interp,
1076
+ );
1077
+ if (!ok) continue;
1078
+ scratch.position = 0;
1079
+ const adapter = this.binary_registry.getAdapter(ComponentClass.typeName);
1080
+ adapter.deserialize(scratch, live);
1081
+ }
1082
+ }
1083
+ }
1084
+
1085
+ /**
1086
+ * Dispatch one user-defined action. Equivalent to
1087
+ * `peer.executor.execute(action, this.local_peer_id)` but exposed
1088
+ * at the session level so callers don't need a reference to the
1089
+ * executor or to know the local peer id.
1090
+ *
1091
+ * @param {SimAction} action_instance
1092
+ */
1093
+ send(action_instance) {
1094
+ if (!this.#started) throw new Error("NetworkSession.send: call start() first");
1095
+ if (!action_instance || action_instance.isSimAction !== true) {
1096
+ throw new Error("NetworkSession.send: argument must be a SimAction instance");
1097
+ }
1098
+ this.#peer.executor.execute(action_instance, this.local_peer_id);
1099
+ }
1100
+
1101
+ // ----------------------------------------------------------------
1102
+ // Accessors
1103
+ // ----------------------------------------------------------------
1104
+
1105
+ /**
1106
+ * @returns {ServerAuthoritativeServer|null}
1107
+ */
1108
+ get server() {
1109
+ return this.#server;
1110
+ }
1111
+
1112
+ /**
1113
+ * @returns {ServerAuthoritativeClient|null}
1114
+ */
1115
+ get client() {
1116
+ return this.#client;
1117
+ }
1118
+
1119
+ /**
1120
+ * @returns {NetworkPeer|null}
1121
+ */
1122
+ get peer() {
1123
+ return this.#peer;
1124
+ }
1125
+
1126
+ /**
1127
+ * @returns {InterpolationLog|null}
1128
+ */
1129
+ get interpolation_log() {
1130
+ return this.#interp_log;
1131
+ }
1132
+
1133
+ /**
1134
+ * @returns {AdaptiveRenderDelay}
1135
+ */
1136
+ get adaptive_render_delay() {
1137
+ return this.#adaptive_render_delay;
1138
+ }
1139
+
1140
+ /**
1141
+ * @returns {TimeDilation}
1142
+ */
1143
+ get time_dilation() {
1144
+ return this.#time_dilation;
1145
+ }
1146
+
1147
+ /**
1148
+ * Most recent locally-completed sim frame.
1149
+ */
1150
+ get current_frame() {
1151
+ if (this.role === NetworkSessionRole.Host) return this.#server.current_sim_frame;
1152
+ return this.#local_frame - 1;
1153
+ }
1154
+
1155
+ // ----------------------------------------------------------------
1156
+ // Internal: identity observer (component-driven spawn/despawn)
1157
+ // ----------------------------------------------------------------
1158
+
1159
+ /**
1160
+ * Patch `NetworkIdentity` on attach: `owner_peer_id` defaults to
1161
+ * this session's `local_peer_id` if unset; `network_id` is assigned
1162
+ * by {@link NetworkSystem.link}, which fires on the same attach.
1163
+ *
1164
+ * Callers must not mutate either field afterwards — the slot table
1165
+ * and replicator cache them.
1166
+ *
1167
+ */
1168
+ #on_identity_attached(identity, entity_id) {
1169
+ if (identity.owner_peer_id < 0) {
1170
+ identity.owner_peer_id = this.local_peer_id;
1171
+ }
1172
+ if (identity.owner_peer_id !== this.local_peer_id) {
1173
+ this.#remote_entities.add(entity_id);
1174
+ }
1175
+ // Per-entity event listener for net_mutate_component.
1176
+ const listener = (payload) => this.#on_net_mutate_component(entity_id, payload);
1177
+ this.world.addEntityEventListener(entity_id, "net_mutate_component", listener);
1178
+ this.#event_listeners.set(entity_id, listener);
1179
+ }
1180
+
1181
+ #on_identity_detached(_identity, entity_id) {
1182
+ const listener = this.#event_listeners.get(entity_id);
1183
+ if (listener !== undefined) {
1184
+ this.world.removeEntityEventListener(entity_id, "net_mutate_component", listener);
1185
+ this.#event_listeners.delete(entity_id);
1186
+ }
1187
+ this.#remote_entities.delete(entity_id);
1188
+ }
1189
+
1190
+ /**
1191
+ * Handler for `"net_mutate_component"` events. Translates the payload
1192
+ * into a `ReplaceComponentAction` and dispatches via the executor.
1193
+ *
1194
+ * Payload:
1195
+ * - `{ component_type, new_state }` — recommended; engine captures
1196
+ * current bytes as prior, then applies `new_state` via the
1197
+ * replication adapter.
1198
+ * - `{ component_type }` — caller has already mutated live; prior
1199
+ * bytes equal post bytes (rewind for this mutation is a no-op).
1200
+ *
1201
+ */
1202
+ #on_net_mutate_component(entity_id, payload) {
1203
+ const { component_type, new_state } = payload || {};
1204
+ if (!component_type) {
1205
+ throw new Error("net_mutate_component: payload must include `component_type`");
1206
+ }
1207
+ const desc = this.#replicated.get(component_type);
1208
+ if (desc === undefined) {
1209
+ throw new Error(`net_mutate_component: ${component_type.typeName ?? component_type.name} is not replicated; call session.replicate() for it before mutating`);
1210
+ }
1211
+
1212
+ const network_id = this.#peer.slot_table.network_for(entity_id);
1213
+ if (network_id < 0) return; // not networked yet
1214
+
1215
+ const live = this.world.getComponent(entity_id, component_type);
1216
+ if (live === undefined) return;
1217
+
1218
+ const adapter = this.binary_registry.getAdapter(component_type.typeName);
1219
+
1220
+ // Build the action with the new-state bytes pre-baked. The
1221
+ // executor will then capture the current `live` as prior bytes
1222
+ // before action.apply deserializes new bytes into `live`.
1223
+ const action = this.#peer.action_registry.acquire(this.#ReplaceComponentAction);
1224
+ action.network_id = network_id;
1225
+ action.component_type_id = desc.interp_type_id;
1226
+ action.payload_buf.position = 0;
1227
+ if (new_state !== undefined && new_state !== null) {
1228
+ adapter.serialize(action.payload_buf, new_state);
1229
+ } else {
1230
+ // No new_state: live IS the new state.
1231
+ adapter.serialize(action.payload_buf, live);
1232
+ }
1233
+ action.payload_length = action.payload_buf.position;
1234
+
1235
+ try {
1236
+ this.#peer.executor.execute(action, this.local_peer_id);
1237
+ } finally {
1238
+ this.#peer.action_registry.release(action);
1239
+ }
1240
+ }
1241
+
1242
+ /**
1243
+ * Build the synthetic ReplaceComponentAction class.
1244
+ * The factory captures `interp_type_id_to_class` and `binary_registry` by
1245
+ * reference so the inner class methods can resolve type ids and
1246
+ * adapters without reaching into the outer class's private fields
1247
+ * (which `#`-private syntax would forbid from a different class).
1248
+ */
1249
+ #make_replace_component_action_class() {
1250
+ const interp_type_id_to_class = this.#interp_type_id_to_class;
1251
+ const binary_registry = this.binary_registry;
1252
+ return class ReplaceComponentAction extends SimAction {
1253
+ payload_length = 0;
1254
+
1255
+ constructor(network_id = 0, component_type_id = 0) {
1256
+ super();
1257
+ this.network_id = network_id;
1258
+ this.component_type_id = component_type_id;
1259
+ this.payload_buf = new BinaryBuffer();
1260
+ this.payload_buf.setCapacity(SCRATCH_BUFFER_BYTES);
1261
+ }
1262
+
1263
+ apply(world, executor) {
1264
+ const local = executor.slot_table.entity_for(this.network_id);
1265
+ if (local < 0) return;
1266
+ const ComponentClass = interp_type_id_to_class[this.component_type_id];
1267
+ if (ComponentClass === undefined) return;
1268
+ const live = world.getComponent(local, ComponentClass);
1269
+ if (live === undefined) return;
1270
+ const adapter = binary_registry.getAdapter(ComponentClass.typeName);
1271
+ if (adapter === undefined) return;
1272
+ this.payload_buf.position = 0;
1273
+ adapter.deserialize(this.payload_buf, live);
1274
+ }
1275
+
1276
+ affected_components(callback, executor) {
1277
+ const local = executor.slot_table.entity_for(this.network_id);
1278
+ if (local < 0) return;
1279
+ const ComponentClass = interp_type_id_to_class[this.component_type_id];
1280
+ if (ComponentClass === undefined) return;
1281
+ callback(local, ComponentClass);
1282
+ }
1283
+
1284
+ serialize(buffer) {
1285
+ buffer.writeUintVar(this.network_id);
1286
+ buffer.writeUint8(this.component_type_id);
1287
+ buffer.writeUint32(this.payload_length);
1288
+ if (this.payload_length > 0) {
1289
+ buffer.writeBytes(this.payload_buf.raw_bytes, 0, this.payload_length);
1290
+ }
1291
+ }
1292
+
1293
+ deserialize(buffer) {
1294
+ this.network_id = buffer.readUintVar();
1295
+ this.component_type_id = buffer.readUint8();
1296
+ this.payload_length = buffer.readUint32();
1297
+ if (this.payload_buf.raw_bytes.length < this.payload_length) {
1298
+ this.payload_buf.setCapacity(this.payload_length);
1299
+ }
1300
+ if (this.payload_length > 0) {
1301
+ buffer.readBytes(this.payload_buf.raw_bytes, 0, this.payload_length);
1302
+ }
1303
+ this.payload_buf.position = 0;
1304
+ }
1305
+
1306
+ reset() {
1307
+ this.network_id = 0;
1308
+ this.component_type_id = 0;
1309
+ this.payload_length = 0;
1310
+ this.payload_buf.position = 0;
1311
+ }
1312
+ };
1313
+ }
1314
+
1315
+ // ----------------------------------------------------------------
1316
+ // Internal: per-step simulation
1317
+ // ----------------------------------------------------------------
1318
+
1319
+ #simulate_one_step() {
1320
+ if (this.role === NetworkSessionRole.Host) {
1321
+ this.#server.tick(this.#local_frame);
1322
+ this.#local_frame++;
1323
+ return;
1324
+ }
1325
+ // Client. Dilated tick rate: each fixed step advances by
1326
+ // `1 / dilation_factor` (capped at MAX_CLIENT_DILATED_TICKS_PER_STEP).
1327
+ this.#local_frame_accum += 1.0 / this.#client.dilation_factor;
1328
+ let inner = 0;
1329
+ while (this.#local_frame_accum >= 1.0 && inner < MAX_CLIENT_DILATED_TICKS_PER_STEP) {
1330
+ this.#local_frame_accum -= 1.0;
1331
+ this.#client.tick(this.#local_frame);
1332
+ this.#send_empty_ack_packet();
1333
+ this.#local_frame++;
1334
+ inner++;
1335
+ }
1336
+ }
1337
+
1338
+ /**
1339
+ * Empty action-stream packet to advance the channel's seq/ack
1340
+ * baseline on ticks with no outbound actions of our own.
1341
+ */
1342
+ #send_empty_ack_packet() {
1343
+ for (const peer_id of this.#connected_peers) {
1344
+ this.#peer.channel_for(peer_id).send(EMPTY_PAYLOAD, 0);
1345
+ }
1346
+ }
1347
+
1348
+ // ----------------------------------------------------------------
1349
+ // Internal: host-side tick completion (AUTH_STATE + dilation feedback)
1350
+ // ----------------------------------------------------------------
1351
+
1352
+ /**
1353
+ *
1354
+ * @param {number} sim_frame
1355
+ */
1356
+ #on_host_tick_complete(sim_frame) {
1357
+ // Initial-sync first, so receivers have a populated world before
1358
+ // AUTH_STATE / action-stream packets land.
1359
+ if (this.#pending_initial_sync.size > 0) {
1360
+
1361
+ for (const [peer_id, sends_remaining] of this.#pending_initial_sync) {
1362
+
1363
+ this.#send_initial_sync_to(peer_id, sim_frame);
1364
+
1365
+ if (sends_remaining <= 1) {
1366
+ this.#pending_initial_sync.delete(peer_id);
1367
+ } else {
1368
+ this.#pending_initial_sync.set(peer_id, sends_remaining - 1);
1369
+ }
1370
+
1371
+ }
1372
+
1373
+ }
1374
+
1375
+ // AUTH_STATE per owned entity, then time-dilation feedback, to
1376
+ // every connected peer.
1377
+ for (const peer_id of this.#connected_peers) {
1378
+
1379
+ for (const entity_id of this.#event_listeners.keys()) {
1380
+
1381
+ const identity = this.world.getComponent(entity_id, NetworkIdentity);
1382
+
1383
+ if (identity === undefined) {
1384
+ continue;
1385
+ }
1386
+ if (identity.owner_peer_id !== peer_id) {
1387
+ continue;
1388
+ }
1389
+
1390
+ this.#send_auth_state_for_entity(peer_id, sim_frame, entity_id, identity);
1391
+
1392
+ }
1393
+
1394
+ this.#server.send_time_dilation_feedback(peer_id);
1395
+ }
1396
+ }
1397
+
1398
+
1399
+ /**
1400
+ * Emit a Snapshotter-format payload to a freshly-connected peer.
1401
+ * Generates and stashes a UUID v1 session token on first send,
1402
+ * which the receiver echoes back via RESUME_HELLO to authenticate
1403
+ * later reconnects.
1404
+ * @param {number} peer_id
1405
+ * @param {number} sim_frame
1406
+ */
1407
+ #send_initial_sync_to(peer_id, sim_frame) {
1408
+ let token = this.#peer_session_tokens.get(peer_id);
1409
+
1410
+ if (token === undefined) {
1411
+ token = UUID.v1();
1412
+ this.#peer_session_tokens.set(peer_id, token);
1413
+ }
1414
+
1415
+ this.#peer.send_initial_sync(peer_id, token.data, sim_frame, (buf) => {
1416
+ snapshotter_emit({
1417
+ buffer: buf,
1418
+ world: this.world,
1419
+ slot_table: this.#peer.slot_table,
1420
+ component_registry: this.#peer.component_registry,
1421
+ entity_iter: (cb) => {
1422
+ for (const entity_id of this.#event_listeners.keys()) {
1423
+ cb(entity_id);
1424
+ }
1425
+ },
1426
+ });
1427
+ });
1428
+ }
1429
+
1430
+ /**
1431
+ * Apply an inbound INITIAL_SYNC payload. Walks the wire format
1432
+ * directly instead of using `snapshotter_apply` so we can:
1433
+ * - Handle re-sync (slot already allocated) without
1434
+ * `slot_table.allocate_at` throwing.
1435
+ * - Let `NetworkSystem.link` handle slot allocation on the
1436
+ * NetworkIdentity-attach side-effect (NetworkIdentity is always
1437
+ * first in the stream).
1438
+ *
1439
+ */
1440
+ #apply_initial_sync(buffer, payload_end) {
1441
+ const entity_count = buffer.readUintVar();
1442
+ for (let i = 0; i < entity_count; i++) {
1443
+ const network_id = buffer.readUintVar();
1444
+ const component_count = buffer.readUint8();
1445
+ let local_entity_id = this.#peer.slot_table.entity_for(network_id);
1446
+ if (local_entity_id < 0) {
1447
+ local_entity_id = this.world.createEntity();
1448
+ }
1449
+
1450
+ for (let j = 0; j < component_count; j++) {
1451
+ const type_id = buffer.readUint8();
1452
+ const payload_len = buffer.readUint32();
1453
+ const ComponentClass = this.#peer.component_registry.class_of(type_id);
1454
+ const adapter = this.#peer.component_registry.adapter_for_id(type_id);
1455
+ if (ComponentClass === undefined || adapter === undefined) {
1456
+ buffer.position += payload_len;
1457
+ continue;
1458
+ }
1459
+ const existing = this.world.getComponent(local_entity_id, ComponentClass);
1460
+ if (existing === undefined) {
1461
+ const component = new ComponentClass();
1462
+ adapter.deserialize(buffer, component);
1463
+ this.world.addComponentToEntity(local_entity_id, component);
1464
+ } else {
1465
+ adapter.deserialize(buffer, existing);
1466
+ }
1467
+ }
1468
+
1469
+ if (buffer.position > payload_end) {
1470
+ throw new Error(`NetworkSession.#apply_initial_sync: read past payload_end at network_id ${network_id}`);
1471
+ }
1472
+ }
1473
+ }
1474
+
1475
+ /**
1476
+ *
1477
+ * @param {number} peer_id
1478
+ * @param {number} sim_frame
1479
+ * @param {number} entity_id
1480
+ * @param {NetworkIdentity} identity
1481
+ */
1482
+ #send_auth_state_for_entity(peer_id, sim_frame, entity_id, identity) {
1483
+ const network_id = identity.network_id;
1484
+
1485
+ if (network_id < 0) {
1486
+ // not initialized
1487
+ return;
1488
+ }
1489
+
1490
+ this.#peer.send_auth_state(peer_id, sim_frame, network_id, (buf) => {
1491
+
1492
+ // Concatenate all replicated components in #replicated order.
1493
+ for (const [ComponentClass, desc] of this.#replicated) {
1494
+
1495
+ const live = this.world.getComponent(entity_id, ComponentClass);
1496
+
1497
+ if (live === undefined) {
1498
+ continue;
1499
+ }
1500
+
1501
+ const adapter = this.binary_registry.getAdapter(ComponentClass.typeName);
1502
+
1503
+ adapter.serialize(buf, live);
1504
+
1505
+ }
1506
+
1507
+ });
1508
+ }
1509
+
1510
+ // ----------------------------------------------------------------
1511
+ // Internal: host-side reconnect / grace / drop machinery
1512
+ // ----------------------------------------------------------------
1513
+
1514
+ #wire_peer_transport_disconnect(peer_id, transport) {
1515
+ if (!transport || !transport.onDisconnect) return;
1516
+ const listener = (reason) => this.#on_peer_transport_disconnected(peer_id, reason);
1517
+ transport.onDisconnect.add(listener);
1518
+ this.#peer_transport_listeners.set(peer_id, { transport, listener });
1519
+ }
1520
+
1521
+ #unwire_peer_transport_disconnect(peer_id) {
1522
+ const entry = this.#peer_transport_listeners.get(peer_id);
1523
+ if (entry === undefined) return;
1524
+ try {
1525
+ entry.transport.onDisconnect.remove(entry.listener);
1526
+ } catch (_) { /* swallow */
1527
+ }
1528
+ this.#peer_transport_listeners.delete(peer_id);
1529
+ }
1530
+
1531
+ /**
1532
+ * Move a peer into the grace window after its transport reports a
1533
+ * disconnect. The peer's channel + baseline are torn down (so a
1534
+ * subsequent `connect()` on a fresh transport doesn't collide), but
1535
+ * the session token, slot-table entries, and `last_acked` watermark
1536
+ * are preserved for the grace window. On expiry, `#expire_grace_peers`
1537
+ * promotes the peer to fully-dropped.
1538
+ */
1539
+ #on_peer_transport_disconnected(peer_id, reason) {
1540
+ if (!this.#connected_peers.has(peer_id)) return;
1541
+ const last_acked = this.#peer.baseline.last_acked(peer_id);
1542
+ this.#server.disconnect_peer(peer_id);
1543
+ this.#connected_peers.delete(peer_id);
1544
+ this.#peer_inbound_mark.delete(peer_id);
1545
+ this.#unwire_peer_transport_disconnect(peer_id);
1546
+ this.#peer_grace_state.set(peer_id, {
1547
+ deadline_ms: performance.now() + this.#server_resume_grace_ms,
1548
+ last_acked,
1549
+ });
1550
+ this.onPeerLost.send2(peer_id, reason);
1551
+ }
1552
+
1553
+ /**
1554
+ * Validate a RESUME_HELLO and either welcome the peer back (restore
1555
+ * baseline + send RESUME_ACCEPT) or reject with a reason code.
1556
+ * Rejection schedules a fresh INITIAL_SYNC so the client falls
1557
+ * through to Tier-3.
1558
+ */
1559
+ #on_resume_hello(transport_peer_id, claimed_peer_id, last_acked_frame, claimed_token_bytes) {
1560
+ // Engine convention: the matchmaking layer routes the reconnecting
1561
+ // transport to the same `peer_id` it had before. If `claimed_peer_id`
1562
+ // disagrees with `transport_peer_id`, the matchmaker mis-routed
1563
+ // treat as collision and kick the connection cleanly. Symmetric
1564
+ // with the TokenMismatch path below: we MUST NOT leave the rogue
1565
+ // transport wired to the dispatcher (any subsequent packet would
1566
+ // still be routed), and we MUST NOT add them to the connected
1567
+ // set or queue an INITIAL_SYNC.
1568
+ if (transport_peer_id !== claimed_peer_id) {
1569
+ this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.PeerIdCollision);
1570
+ this.#peer.send_disconnect(transport_peer_id, 'peer_id_collision');
1571
+ this.#server.disconnect_peer(transport_peer_id);
1572
+ this.#unwire_peer_transport_disconnect(transport_peer_id);
1573
+ // The transport may have been added to the live set in connect()
1574
+ // (a fresh peer connects, then sends a mis-routed RESUME_HELLO).
1575
+ // Drop it from BOTH iterated maps otherwise the next host tick
1576
+ // serializes AUTH_STATE / time-dilation to a now-disposed conn and
1577
+ // throws "peer not connected", taking down the tick for all peers.
1578
+ this.#connected_peers.delete(transport_peer_id);
1579
+ this.#pending_initial_sync.delete(transport_peer_id);
1580
+ this.#peer_inbound_mark.delete(transport_peer_id);
1581
+ return;
1582
+ }
1583
+ const grace = this.#peer_grace_state.get(claimed_peer_id);
1584
+ if (grace === undefined) {
1585
+ // Either the peer is fully unknown OR they're already
1586
+ // connected (their previous transport never dropped).
1587
+ // Either way, no resume record to validate against.
1588
+ this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.UnknownPeer);
1589
+ // Treat as a fresh peer; schedule INITIAL_SYNC.
1590
+ this.#pending_initial_sync.set(transport_peer_id, 2);
1591
+ this.#connected_peers.add(transport_peer_id);
1592
+ return;
1593
+ }
1594
+ const expected_token = this.#peer_session_tokens.get(claimed_peer_id);
1595
+ if (expected_token === undefined) {
1596
+ this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.UnknownPeer);
1597
+ this.#peer_grace_state.delete(claimed_peer_id);
1598
+ this.#pending_initial_sync.set(transport_peer_id, 2);
1599
+ this.#connected_peers.add(transport_peer_id);
1600
+ return;
1601
+ }
1602
+ const claimed = new UUID();
1603
+ claimed.data = claimed_token_bytes;
1604
+ if (!claimed.equals(expected_token)) {
1605
+ // Hard reject: a wrong-token attempt MUST NOT take over the
1606
+ // legitimate peer's grace state (would let an attacker
1607
+ // pre-empt the real owner's reconnect window) and MUST NOT
1608
+ // be fresh-treated either (would expose the world via
1609
+ // INITIAL_SYNC). Kick the connection; legitimate-but-
1610
+ // corrupted-token clients fall to permanent-lost and need
1611
+ // a new peer_id from the matchmaking layer.
1612
+ this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.TokenMismatch);
1613
+ this.#peer.send_disconnect(transport_peer_id, 'token_mismatch');
1614
+ this.#server.disconnect_peer(transport_peer_id);
1615
+ this.#unwire_peer_transport_disconnect(transport_peer_id);
1616
+ // Same cleanup as the collision branch above: if this transport was
1617
+ // a fresh peer added to the live set, drop it from both iterated
1618
+ // maps so the next host tick doesn't address a disposed conn.
1619
+ this.#connected_peers.delete(transport_peer_id);
1620
+ this.#pending_initial_sync.delete(transport_peer_id);
1621
+ this.#peer_inbound_mark.delete(transport_peer_id);
1622
+ return;
1623
+ }
1624
+ // Ring-floor gate. The client asks to resume from `last_acked_frame + 1`,
1625
+ // but the action-log ring only still holds frames back to
1626
+ // `current_sim_frame - frame_capacity + 1`. If the requested frame
1627
+ // predates that floor, the frames needed to back-fill the gap have aged
1628
+ // out `pack_for_peer`'s has_frame guard would silently skip them and
1629
+ // the client would enter `connected` missing in-scope mutations (a
1630
+ // "successful" resume that runs visibly desynced). Reject so the client
1631
+ // falls through to a full INITIAL_SYNC re-sync instead. Grace is
1632
+ // typically far longer than the ring, so this is the routine case for
1633
+ // any reconnect that took more than ~`frame_capacity` ticks.
1634
+ const ring_floor = this.#server.current_sim_frame - this.frame_capacity + 1;
1635
+ if (last_acked_frame + 1 < ring_floor) {
1636
+ this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.StaleFrame);
1637
+ this.#peer_grace_state.delete(claimed_peer_id);
1638
+ this.#pending_initial_sync.set(transport_peer_id, 2);
1639
+ this.#connected_peers.add(transport_peer_id);
1640
+ return;
1641
+ }
1642
+
1643
+ // Valid resume. Restore the action-stream baseline so the
1644
+ // replicator packs from `last_acked + 1`, then accept.
1645
+ this.#peer.baseline.set_acked(claimed_peer_id, last_acked_frame);
1646
+ this.#peer_grace_state.delete(claimed_peer_id);
1647
+ this.#connected_peers.add(transport_peer_id);
1648
+ this.#peer.send_resume_accept(transport_peer_id);
1649
+ }
1650
+
1651
+ /**
1652
+ * Walk the grace map and promote any peer whose deadline has
1653
+ * passed to fully-dropped (fires `onPeerPermanentlyDropped`).
1654
+ * Called from `tick()`.
1655
+ */
1656
+ #expire_grace_peers(now_ms) {
1657
+ if (this.#peer_grace_state.size === 0) return;
1658
+ for (const [peer_id, state] of this.#peer_grace_state) {
1659
+ if (state.deadline_ms <= now_ms) {
1660
+ this.#peer_grace_state.delete(peer_id);
1661
+ this.#peer_session_tokens.delete(peer_id);
1662
+ this.onPeerPermanentlyDropped.send2(peer_id, 'grace_expired');
1663
+ }
1664
+ }
1665
+ }
1666
+
1667
+ /**
1668
+ * Reap any connected peer whose inbound-packet count has not advanced for
1669
+ * longer than `connection_timeout_ms`. Host-side peers go into the grace
1670
+ * window (the same path a transport disconnect takes); the client routes a
1671
+ * silent server into the reconnect ladder. Connectionless transports never
1672
+ * emit a disconnect of their own, so without this a vanished peer would be
1673
+ * pinned forever (host) or never noticed (client). The per-tick keepalive
1674
+ * traffic the facade sends both directions keeps a live connection well
1675
+ * clear of the budget. Called from `tick()` only when the budget is > 0.
1676
+ * @private
1677
+ */
1678
+ #reap_silent_peers() {
1679
+ if (this.role === NetworkSessionRole.Host) {
1680
+ // Collect first — reaping mutates #connected_peers mid-iteration.
1681
+ let to_reap = null;
1682
+ for (const peer_id of this.#connected_peers) {
1683
+ if (this.#peer_is_silent(peer_id)) (to_reap ??= []).push(peer_id);
1684
+ }
1685
+ if (to_reap !== null) {
1686
+ for (let i = 0; i < to_reap.length; i++) {
1687
+ this.#on_peer_transport_disconnected(to_reap[i], 'timeout');
1688
+ }
1689
+ }
1690
+ } else if (this.#reconnect_state === ReconnectState.Connected && this.#remote_peer_id >= 0) {
1691
+ // Only when actively connected — Reconnecting/Resyncing have their
1692
+ // own timers in #tick_reconnect.
1693
+ if (this.#peer_is_silent(this.#remote_peer_id)) {
1694
+ this.#on_client_transport_disconnected('timeout');
1695
+ }
1696
+ }
1697
+ }
1698
+
1699
+ /**
1700
+ * Whether `peer_id` has produced no inbound packet within the timeout
1701
+ * budget. Maintains the per-peer mark as a side effect: resets the silence
1702
+ * window whenever the peer's inbound count advances. A freshly-seen peer is
1703
+ * stamped at the current clock and reported live, so it always gets a full
1704
+ * budget before it can be reaped.
1705
+ * @private
1706
+ */
1707
+ #peer_is_silent(peer_id) {
1708
+ const count = this.#peer.inbound_count(peer_id);
1709
+ const mark = this.#peer_inbound_mark.get(peer_id);
1710
+ if (mark === undefined) {
1711
+ this.#peer_inbound_mark.set(peer_id, { count, since_ms: this.#liveness_clock_ms });
1712
+ return false;
1713
+ }
1714
+ if (count !== mark.count) {
1715
+ mark.count = count;
1716
+ mark.since_ms = this.#liveness_clock_ms;
1717
+ return false;
1718
+ }
1719
+ return this.#liveness_clock_ms - mark.since_ms >= this.#connection_timeout_ms;
1720
+ }
1721
+
1722
+ // ----------------------------------------------------------------
1723
+ // Internal: client-side reconnect state machine
1724
+ // ----------------------------------------------------------------
1725
+
1726
+ #wire_client_transport_disconnect(transport) {
1727
+ if (!transport || !transport.onDisconnect) return;
1728
+ if (this.#current_transport_listener !== null && this.#current_transport !== null) {
1729
+ try {
1730
+ this.#current_transport.onDisconnect.remove(this.#current_transport_listener);
1731
+ } catch (_) { /* swallow */
1732
+ }
1733
+ }
1734
+ const listener = (reason) => this.#on_client_transport_disconnected(reason);
1735
+ transport.onDisconnect.add(listener);
1736
+ this.#current_transport_listener = listener;
1737
+ }
1738
+
1739
+ #on_client_transport_disconnected(reason) {
1740
+ if (this.#reconnect_state === ReconnectState.Lost) return;
1741
+ // Tear down the dead peer/channel binding so a fresh transport
1742
+ // can be wired via `connect()`. The InterpolationLog,
1743
+ // session_token, predicted-action ledger, and live world all
1744
+ // survive that's what we need to claim continuity on resume.
1745
+ if (this.#remote_peer_id >= 0) {
1746
+ try {
1747
+ this.#peer.disconnect_peer(this.#remote_peer_id);
1748
+ } catch (_) { /* swallow */
1749
+ }
1750
+ // Stop treating this peer as a live send target. The channel is
1751
+ // gone, but `#remote_peer_id` is deliberately kept so the reconnect
1752
+ // ladder can re-`connect()` to the same id. Without this delete,
1753
+ // `#send_empty_ack_packet` (driven every tick) dereferences the
1754
+ // now-undefined channel and throws — which would crash the very
1755
+ // tick loop that drives the reconnect ladder.
1756
+ this.#connected_peers.delete(this.#remote_peer_id);
1757
+ this.#peer_inbound_mark.delete(this.#remote_peer_id);
1758
+ }
1759
+ this.#current_transport = null;
1760
+ this.#current_transport_listener = null;
1761
+ if (!this.#reconnect_policy.enabled) {
1762
+ this.#permanently_lose_connection(reason || 'transport_disconnect');
1763
+ return;
1764
+ }
1765
+ this.#reconnect_state = ReconnectState.Reconnecting;
1766
+ this.#reconnect_attempt = 0;
1767
+ this.#reconnect_elapsed_ms = 0;
1768
+ this.#reconnect_next_delay_ms = this.#reconnect_policy.base_delay_ms;
1769
+ this.#reconnect_timer_remaining_ms = this.#reconnect_next_delay_ms;
1770
+ this.onConnectionLost.send1(reason || 'transport_disconnect');
1771
+ }
1772
+
1773
+ /**
1774
+ * Per-tick advance for the client reconnect timer. When the timer
1775
+ * elapses we rebuild the transport via `transport_factory` and
1776
+ * call `connect()` on it; with a cached `session_token`, `connect`
1777
+ * sends RESUME_HELLO. The server replies with RESUME_ACCEPT
1778
+ * (→ Connected) or RESUME_REJECT (→ Resyncing, INITIAL_SYNC
1779
+ * pending) or nothing within the next back-off interval (→ retry).
1780
+ */
1781
+ #tick_reconnect(dt_ms) {
1782
+ // Resyncing has its own deadline: after RESUME_REJECT we expect
1783
+ // the host to ship a fresh INITIAL_SYNC. If it never arrives
1784
+ // (host crashed, network silently broke), fall to Lost so the
1785
+ // application sees the failure instead of hanging forever.
1786
+ if (this.#reconnect_state === ReconnectState.Resyncing) {
1787
+ this.#reconnect_elapsed_ms += dt_ms;
1788
+ if (this.#reconnect_elapsed_ms >= this.#reconnect_policy.total_timeout_ms) {
1789
+ this.#permanently_lose_connection('resync_timeout');
1790
+ }
1791
+ return;
1792
+ }
1793
+ if (this.#reconnect_state !== ReconnectState.Reconnecting) return;
1794
+ this.#reconnect_elapsed_ms += dt_ms;
1795
+ if (this.#reconnect_elapsed_ms >= this.#reconnect_policy.total_timeout_ms) {
1796
+ this.#permanently_lose_connection('reconnect_timeout');
1797
+ return;
1798
+ }
1799
+ this.#reconnect_timer_remaining_ms -= dt_ms;
1800
+ if (this.#reconnect_timer_remaining_ms > 0) return;
1801
+
1802
+ // Time to attempt.
1803
+ this.#reconnect_attempt++;
1804
+ if (this.#reconnect_attempt > this.#reconnect_policy.max_attempts) {
1805
+ this.#permanently_lose_connection('reconnect_attempts_exhausted');
1806
+ return;
1807
+ }
1808
+ this.onReconnectAttempt.send1(this.#reconnect_attempt);
1809
+
1810
+ if (this.#transport_factory === null) {
1811
+ // No factory: caller must drive reconnects themselves. We
1812
+ // remain in Reconnecting until they call connect() with a
1813
+ // new transport, or until total_timeout_ms expires.
1814
+ this.#reconnect_timer_remaining_ms = this.#next_backoff_delay();
1815
+ return;
1816
+ }
1817
+ let new_transport;
1818
+ try {
1819
+ new_transport = this.#transport_factory();
1820
+ } catch (e) {
1821
+ console.warn('NetworkSession: transport_factory threw', e);
1822
+ this.#reconnect_timer_remaining_ms = this.#next_backoff_delay();
1823
+ return;
1824
+ }
1825
+ try {
1826
+ this.connect(this.#remote_peer_id, new_transport);
1827
+ } catch (e) {
1828
+ console.warn('NetworkSession: connect() threw during reconnect', e);
1829
+ this.#reconnect_timer_remaining_ms = this.#next_backoff_delay();
1830
+ return;
1831
+ }
1832
+ // Wait the back-off interval before retrying again (if RESUME_ACCEPT
1833
+ // never arrives in time).
1834
+ this.#reconnect_timer_remaining_ms = this.#next_backoff_delay();
1835
+ }
1836
+
1837
+ #next_backoff_delay() {
1838
+ const next = this.#reconnect_next_delay_ms * this.#reconnect_policy.exponential_factor;
1839
+ this.#reconnect_next_delay_ms = Math.min(next, this.#reconnect_policy.max_delay_ms);
1840
+ return this.#reconnect_next_delay_ms;
1841
+ }
1842
+
1843
+ #enter_connected() {
1844
+ this.#reconnect_state = ReconnectState.Connected;
1845
+ this.#reconnect_attempt = 0;
1846
+ this.#reconnect_elapsed_ms = 0;
1847
+ // Reset the exponential back-off so a subsequent disconnect starts
1848
+ // its retry ladder from base_delay_ms without this, the next drop
1849
+ // would inherit the last cycle's (potentially max_delay_ms) delay.
1850
+ this.#reconnect_next_delay_ms = this.#reconnect_policy.base_delay_ms;
1851
+ }
1852
+
1853
+ #permanently_lose_connection(reason) {
1854
+ if (this.#reconnect_state === ReconnectState.Lost) return;
1855
+ this.#reconnect_state = ReconnectState.Lost;
1856
+ this.onConnectionPermanentlyLost.send1(reason);
1857
+ }
1858
+
1859
+ // ----------------------------------------------------------------
1860
+ // Internal: client predict-reconcile wiring
1861
+ // ----------------------------------------------------------------
1862
+
1863
+ #wire_client_predict_reconcile() {
1864
+ // Rewind doesn't go through executor.execute, so the
1865
+ // before_execute normalize hook misses it. Catch it here.
1866
+ this.#client.onBeforeReconcile.add(() => this.normalize_if_dirty());
1867
+
1868
+ // Predict: run the sampler, execute each action, record its
1869
+ // bytes so the replay path can re-execute without re-sampling.
1870
+ this.#client.onPredict.add((frame, _input_writer) => {
1871
+ if (this.#input_sampler === null) return;
1872
+ const actions = this.#input_sampler(frame);
1873
+ if (!actions) return;
1874
+ const recorded = [];
1875
+ for (const action of actions) {
1876
+ if (!action || action.isSimAction !== true) continue;
1877
+ const type_id = action.constructor.type_id;
1878
+ const scratch = this.#scratch_send_buf;
1879
+ scratch.position = 0;
1880
+ action.serialize(scratch);
1881
+ const bytes = scratch.raw_bytes.slice(0, scratch.position);
1882
+ recorded.push({ type_id, bytes });
1883
+ this.#client.executor.execute(action, this.local_peer_id);
1884
+ }
1885
+ this.#sampled_actions_per_frame.set(frame, recorded);
1886
+ this.#trim_sampled_action_ledger();
1887
+ });
1888
+
1889
+ // Replay: re-execute recorded actions from the ledger. Sampler is
1890
+ // NOT called again — live input has moved on.
1891
+ this.#client.onReplay.add((frame, _input_reader) => {
1892
+ const recorded = this.#sampled_actions_per_frame.get(frame);
1893
+ if (!recorded) return;
1894
+ for (const { type_id, bytes } of recorded) {
1895
+ const klass = this.#peer.action_registry.klass_for(type_id);
1896
+ if (klass === undefined) continue;
1897
+ const action = this.#peer.action_registry.acquire(klass);
1898
+ const scratch = this.#scratch_interp_buf;
1899
+ scratch.position = 0;
1900
+ scratch.writeBytes(bytes, 0, bytes.length);
1901
+ scratch.position = 0;
1902
+ action.deserialize(scratch);
1903
+ try {
1904
+ this.#client.executor.execute(action, this.local_peer_id);
1905
+ } finally {
1906
+ this.#peer.action_registry.release(action);
1907
+ }
1908
+ }
1909
+ });
1910
+
1911
+ // Apply auth state: deserialize concatenated replicated-component
1912
+ // bytes into the live entity. Mirrors the host's serialize order.
1913
+ this.#client.onApplyAuthState.add((_server_frame, network_id, buffer) => {
1914
+ const local = this.#client.slot_table.entity_for(network_id);
1915
+ if (local < 0) return;
1916
+ for (const [ComponentClass] of this.#replicated) {
1917
+ const live = this.world.getComponent(local, ComponentClass);
1918
+ if (live === undefined) continue;
1919
+ const adapter = this.binary_registry.getAdapter(ComponentClass.typeName);
1920
+ adapter.deserialize(buffer, live);
1921
+ }
1922
+ });
1923
+ }
1924
+
1925
+ #trim_sampled_action_ledger() {
1926
+ const action_log = this.#peer.action_log;
1927
+ if (action_log === undefined) return;
1928
+ const oldest_keep = Math.max(0, this.#local_frame - action_log.frame_capacity);
1929
+ for (const f of this.#sampled_actions_per_frame.keys()) {
1930
+ if (f < oldest_keep) this.#sampled_actions_per_frame.delete(f);
1931
+ }
1932
+ }
1933
+
1934
+ // ----------------------------------------------------------------
1935
+ // Internal: interpolation log + render-time sampling
1936
+ // ----------------------------------------------------------------
1937
+
1938
+ /**
1939
+ * Record post-apply state for every remote-owned entity into the
1940
+ * interpolation log. Locally-owned entities are predicted, not
1941
+ * interpolated, so they're skipped.
1942
+ *
1943
+ * Doesn't fire on the host the replicator's deferral path doesn't
1944
+ * emit `onFrameApplied`, so the host gets no recording naturally.
1945
+ */
1946
+ #on_frame_applied(_peer_id, frame_number) {
1947
+ this.#interp_log.begin_tick(frame_number);
1948
+ for (const entity_id of this.#remote_entities) {
1949
+ const identity = this.world.getComponent(entity_id, NetworkIdentity);
1950
+ if (identity === undefined || identity.network_id < 0) continue;
1951
+ for (const [ComponentClass, desc] of this.#replicated) {
1952
+ if (desc.interp === null) continue;
1953
+ const live = this.world.getComponent(entity_id, ComponentClass);
1954
+ if (live === undefined) continue;
1955
+ const adapter = this.binary_registry.getAdapter(ComponentClass.typeName);
1956
+ const rec_buf = this.#interp_log.begin_record(identity.network_id, desc.interp_type_id);
1957
+ adapter.serialize(rec_buf, live);
1958
+ this.#interp_log.end_record();
1959
+ }
1960
+ }
1961
+ this.#interp_log.end_tick();
1962
+
1963
+ if (frame_number > this.#latest_received_frame) {
1964
+ this.#adaptive_render_delay.record_arrival(performance.now(), frame_number);
1965
+ this.#latest_received_frame = frame_number;
1966
+ }
1967
+ }
1968
+
1969
+ /**
1970
+ * Advance the smooth_render_frame state machine and write
1971
+ * interpolated bytes into each remote-owned entity's live
1972
+ * components. Sets `render_dirty` if anything was written.
1973
+ *
1974
+ * Host early-returns naturally (no `#latest_received_frame`
1975
+ * recorded — deferred-apply path doesn't fire `onFrameApplied`).
1976
+ */
1977
+ #render_interpolated_entities() {
1978
+ if (this.#latest_received_frame < 0) return;
1979
+ if (this.#remote_entities.size === 0) return;
1980
+
1981
+ const now_ms = performance.now();
1982
+ const render_delay_frames = this.#adaptive_render_delay.delay_ms() / this.tick_period_ms;
1983
+ const target = this.#latest_received_frame - render_delay_frames;
1984
+
1985
+ if (this.#smooth_render_frame < 0) {
1986
+ this.#smooth_render_frame = target < 0 ? 0 : target;
1987
+ this.#smooth_last_wall_ms = now_ms;
1988
+ } else {
1989
+ const dt_ms = now_ms - this.#smooth_last_wall_ms;
1990
+ this.#smooth_last_wall_ms = now_ms;
1991
+ this.#smooth_render_frame += dt_ms / this.tick_period_ms;
1992
+ const alpha = 1 - Math.exp(-PULL_PER_MS * dt_ms);
1993
+ this.#smooth_render_frame += (target - this.#smooth_render_frame) * alpha;
1994
+ }
1995
+ if (this.#smooth_render_frame > this.#latest_received_frame) {
1996
+ this.#smooth_render_frame = this.#latest_received_frame;
1997
+ }
1998
+ if (this.#smooth_render_frame < 0) this.#smooth_render_frame = 0;
1999
+
2000
+ const tick_a = Math.floor(this.#smooth_render_frame);
2001
+ const tick_b = tick_a + 1;
2002
+ const t = this.#smooth_render_frame - tick_a;
2003
+
2004
+ const scratch = this.#scratch_interp_buf;
2005
+ let wrote_anything = false;
2006
+ for (const entity_id of this.#remote_entities) {
2007
+ const identity = this.world.getComponent(entity_id, NetworkIdentity);
2008
+ if (identity === undefined || identity.network_id < 0) continue;
2009
+ for (const [ComponentClass, desc] of this.#replicated) {
2010
+ if (desc.interp === null) continue; // snap mode
2011
+ const live = this.world.getComponent(entity_id, ComponentClass);
2012
+ if (live === undefined) continue;
2013
+ scratch.position = 0;
2014
+ const ok = this.#interp_log.interpolate(
2015
+ scratch, identity.network_id, desc.interp_type_id,
2016
+ tick_a, tick_b, t, desc.interp,
2017
+ );
2018
+ if (!ok) continue;
2019
+ scratch.position = 0;
2020
+ const adapter = this.binary_registry.getAdapter(ComponentClass.typeName);
2021
+ adapter.deserialize(scratch, live);
2022
+ wrote_anything = true;
2023
+ }
2024
+ }
2025
+ if (wrote_anything) this.render_dirty = true;
2026
+ }
2027
+
2028
+ // ----------------------------------------------------------------
2029
+ // Utility
2030
+ // ----------------------------------------------------------------
2031
+
2032
+ #assert_not_started(method_name) {
2033
+ if (this.#started) {
2034
+ throw new Error(`NetworkSession.${method_name}: must be called before start()`);
2035
+ }
2036
+ }
2037
+ }