@woosh/meep-engine 3.46.0 → 3.49.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 (827) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/editor/actions/concrete/TransformModifyAction.d.ts.map +1 -1
  3. package/editor/actions/concrete/TransformModifyAction.js +10 -2
  4. package/editor/ecs/component/FieldDescriptor.js +15 -0
  5. package/editor/ecs/component/createObjectEditor.js +41 -12
  6. package/editor/ecs/component/editors/ecs/Transform64Editor.d.ts.map +1 -1
  7. package/editor/ecs/component/editors/ecs/Transform64Editor.js +77 -70
  8. package/editor/particles/effect/ParticleReferenceSimulation.d.ts +1 -2
  9. package/editor/particles/effect/ParticleReferenceSimulation.d.ts.map +1 -1
  10. package/editor/particles/effect/ParticleReferenceSimulation.js +2 -4
  11. package/editor/tools/TransformToolV2.d.ts.map +1 -1
  12. package/editor/tools/TransformToolV2.js +4 -0
  13. package/editor/tools/v2/TransformControls.d.ts.map +1 -1
  14. package/editor/tools/v2/TransformControls.js +7 -0
  15. package/editor/view/ecs/EntityEditor.d.ts.map +1 -1
  16. package/editor/view/ecs/EntityEditor.js +4 -2
  17. package/editor/view/particles/effect/ParticleGraphEditorView.js +1 -1
  18. package/package.json +1 -1
  19. package/src/core/collection/array/array_insert_at.d.ts +16 -0
  20. package/src/core/collection/array/array_insert_at.d.ts.map +1 -0
  21. package/src/core/collection/array/array_insert_at.js +32 -0
  22. package/src/core/collection/array/array_remove_at.d.ts +15 -0
  23. package/src/core/collection/array/array_remove_at.d.ts.map +1 -0
  24. package/src/core/collection/array/array_remove_at.js +30 -0
  25. package/src/core/geom/3d/quaternion/quat3_look_rotation.d.ts +37 -0
  26. package/src/core/geom/3d/quaternion/quat3_look_rotation.d.ts.map +1 -0
  27. package/src/core/geom/3d/quaternion/quat3_look_rotation.js +172 -0
  28. package/src/core/geom/Quaternion.d.ts +1 -0
  29. package/src/core/geom/Quaternion.d.ts.map +1 -1
  30. package/src/core/geom/Quaternion.js +12 -48
  31. package/src/engine/Engine.d.ts.map +1 -1
  32. package/src/engine/Engine.js +9 -4
  33. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +27 -11
  34. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  35. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +21 -0
  36. package/src/engine/ecs/transform/t64_look_rotation.d.ts +4 -4
  37. package/src/engine/ecs/transform/t64_look_rotation.d.ts.map +1 -1
  38. package/src/engine/ecs/transform/t64_look_rotation.js +7 -18
  39. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
  40. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +30 -1
  41. package/src/engine/graphics/ecs/particles/ParticleEffect.d.ts +2 -2
  42. package/src/engine/graphics/ecs/particles/ParticleEffect.d.ts.map +1 -1
  43. package/src/engine/graphics/ecs/particles/ParticleEffect.js +7 -7
  44. package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.d.ts +8 -0
  45. package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.d.ts.map +1 -1
  46. package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.js +11 -3
  47. package/src/engine/graphics/ecs/particles/ParticleEffectSerializationUpgrader_1_2.d.ts +26 -0
  48. package/src/engine/graphics/ecs/particles/ParticleEffectSerializationUpgrader_1_2.d.ts.map +1 -0
  49. package/src/engine/graphics/ecs/particles/ParticleEffectSerializationUpgrader_1_2.js +91 -0
  50. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +6 -0
  51. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
  52. package/src/engine/graphics3/GPUParticleEmitterSystem.js +72 -29
  53. package/src/engine/graphics3/GraphicsEngine.d.ts.map +1 -1
  54. package/src/engine/graphics3/GraphicsEngine.js +8 -1
  55. package/src/engine/graphics3/ParticipatingMedia.d.ts +84 -1
  56. package/src/engine/graphics3/ParticipatingMedia.d.ts.map +1 -1
  57. package/src/engine/graphics3/ParticipatingMedia.js +139 -5
  58. package/src/engine/graphics3/ParticipatingMediaSerializationAdapter.d.ts +6 -2
  59. package/src/engine/graphics3/ParticipatingMediaSerializationAdapter.d.ts.map +1 -1
  60. package/src/engine/graphics3/ParticipatingMediaSerializationAdapter.js +29 -4
  61. package/src/engine/graphics3/ParticipatingMediaSerializationUpgrader_0_1.d.ts +10 -0
  62. package/src/engine/graphics3/ParticipatingMediaSerializationUpgrader_0_1.d.ts.map +1 -0
  63. package/src/engine/graphics3/ParticipatingMediaSerializationUpgrader_0_1.js +38 -0
  64. package/src/engine/graphics3/ParticipatingMediaSystem.d.ts.map +1 -1
  65. package/src/engine/graphics3/ParticipatingMediaSystem.js +46 -3
  66. package/src/engine/input/devices/KeyboardDevice.d.ts +1 -0
  67. package/src/engine/input/devices/KeyboardDevice.d.ts.map +1 -1
  68. package/src/engine/input/devices/KeyboardDevice.js +7 -6
  69. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.d.ts.map +1 -1
  70. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.js +15 -2
  71. package/src/engine/network/CONGESTION_CONTROL.md +9 -9
  72. package/src/engine/network/MESSAGE_TRANSPORT.md +364 -0
  73. package/src/engine/network/NetworkSession.d.ts +47 -16
  74. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  75. package/src/engine/network/NetworkSession.js +556 -292
  76. package/src/engine/network/README.md +104 -28
  77. package/src/engine/network/SMOOTHNESS_DESIGN.md +13 -5
  78. package/src/engine/network/diagnostics/BandwidthMeter.d.ts +1 -1
  79. package/src/engine/network/diagnostics/BandwidthMeter.js +1 -1
  80. package/src/engine/network/orchestrator/HostJoin.d.ts +111 -0
  81. package/src/engine/network/orchestrator/HostJoin.d.ts.map +1 -0
  82. package/src/engine/network/orchestrator/HostJoin.js +271 -0
  83. package/src/engine/network/orchestrator/NetworkPeer.d.ts +151 -112
  84. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  85. package/src/engine/network/orchestrator/NetworkPeer.js +450 -499
  86. package/src/engine/network/orchestrator/PendingSlices.d.ts +30 -0
  87. package/src/engine/network/orchestrator/PendingSlices.d.ts.map +1 -0
  88. package/src/engine/network/orchestrator/PendingSlices.js +83 -0
  89. package/src/engine/network/orchestrator/ReliableFrames.d.ts +34 -0
  90. package/src/engine/network/orchestrator/ReliableFrames.d.ts.map +1 -0
  91. package/src/engine/network/orchestrator/ReliableFrames.js +96 -0
  92. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +77 -10
  93. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
  94. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +236 -61
  95. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +59 -23
  96. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
  97. package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +213 -198
  98. package/src/engine/network/replication/Replicator.d.ts +15 -4
  99. package/src/engine/network/replication/Replicator.d.ts.map +1 -1
  100. package/src/engine/network/replication/Replicator.js +57 -17
  101. package/src/engine/network/sim/ActionLog.d.ts +32 -5
  102. package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
  103. package/src/engine/network/sim/ActionLog.js +47 -5
  104. package/src/engine/network/sim/SimActionExecutor.d.ts +42 -2
  105. package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
  106. package/src/engine/network/sim/SimActionExecutor.js +100 -4
  107. package/src/engine/network/sim/Snapshotter.d.ts +12 -0
  108. package/src/engine/network/sim/Snapshotter.d.ts.map +1 -1
  109. package/src/engine/network/sim/Snapshotter.js +43 -0
  110. package/src/engine/network/state/Baseline.d.ts +1 -1
  111. package/src/engine/network/state/Baseline.js +1 -1
  112. package/src/engine/network/state/FrameBufferRing.d.ts +25 -6
  113. package/src/engine/network/state/FrameBufferRing.d.ts.map +1 -1
  114. package/src/engine/network/state/FrameBufferRing.js +51 -6
  115. package/src/engine/network/state/InputRing.d.ts +4 -0
  116. package/src/engine/network/state/InputRing.d.ts.map +1 -1
  117. package/src/engine/network/state/InputRing.js +7 -0
  118. package/src/engine/network/state/RecordArrivalRing.d.ts +57 -0
  119. package/src/engine/network/state/RecordArrivalRing.d.ts.map +1 -0
  120. package/src/engine/network/state/RecordArrivalRing.js +144 -0
  121. package/src/engine/network/transport/DeferredTransport.d.ts.map +1 -1
  122. package/src/engine/network/transport/DeferredTransport.js +1 -0
  123. package/src/engine/network/transport/LoopbackTransport.d.ts.map +1 -1
  124. package/src/engine/network/transport/LoopbackTransport.js +4 -2
  125. package/src/engine/network/transport/Transport.d.ts +7 -0
  126. package/src/engine/network/transport/Transport.d.ts.map +1 -1
  127. package/src/engine/network/transport/Transport.js +8 -0
  128. package/src/engine/network/transport/adapters/NodeUDPTransport.d.ts.map +1 -1
  129. package/src/engine/network/transport/adapters/NodeUDPTransport.js +20 -5
  130. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.d.ts.map +1 -1
  131. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +8 -0
  132. package/src/engine/network/transport/adapters/WebTransportTransport.d.ts.map +1 -1
  133. package/src/engine/network/transport/adapters/WebTransportTransport.js +6 -0
  134. package/src/engine/network/transport/connection/Connection.d.ts +191 -0
  135. package/src/engine/network/transport/connection/Connection.d.ts.map +1 -0
  136. package/src/engine/network/transport/connection/Connection.js +1262 -0
  137. package/src/engine/network/transport/connection/RangeSet.d.ts +94 -0
  138. package/src/engine/network/transport/connection/RangeSet.d.ts.map +1 -0
  139. package/src/engine/network/transport/connection/RangeSet.js +260 -0
  140. package/src/engine/network/transport/connection/RxStream.d.ts +48 -0
  141. package/src/engine/network/transport/connection/RxStream.d.ts.map +1 -0
  142. package/src/engine/network/transport/connection/RxStream.js +211 -0
  143. package/src/engine/network/transport/connection/TxStream.d.ts +90 -0
  144. package/src/engine/network/transport/connection/TxStream.d.ts.map +1 -0
  145. package/src/engine/network/transport/connection/TxStream.js +243 -0
  146. package/src/engine/network/transport/connection/bytes_copy.d.ts +17 -0
  147. package/src/engine/network/transport/connection/bytes_copy.d.ts.map +1 -0
  148. package/src/engine/network/transport/connection/bytes_copy.js +34 -0
  149. package/src/engine/network/transport/connection/varint.d.ts +46 -0
  150. package/src/engine/network/transport/connection/varint.d.ts.map +1 -0
  151. package/src/engine/network/transport/connection/varint.js +107 -0
  152. package/src/engine/physics/cloth/MEASUREMENTS.md +354 -0
  153. package/src/engine/physics/cloth/PLAN.md +85 -5
  154. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +0 -12
  155. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  156. package/src/engine/physics/cloth/ecs/ClothInstance.js +0 -13
  157. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +2 -1
  158. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
  159. package/src/engine/physics/cloth/ecs/ClothSystem.js +7 -1
  160. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  161. package/src/engine/physics/cloth/ecs/ClothWorld.js +4 -15
  162. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts +2 -1
  163. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts.map +1 -1
  164. package/src/engine/physics/cloth/ecs/WorkerClothSystem.js +6 -1
  165. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -1
  166. package/src/engine/physics/cloth/ecs/cloth_build_rows.js +20 -10
  167. package/src/engine/physics/cloth/playground/collide.html +5 -3
  168. package/src/engine/physics/cloth/playground/collide_main.js +14 -13
  169. package/src/engine/physics/cloth/solver/ClothState.d.ts +30 -2
  170. package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
  171. package/src/engine/physics/cloth/solver/ClothState.js +62 -4
  172. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts +11 -3
  173. package/src/engine/physics/cloth/solver/cloth_self_find.d.ts.map +1 -1
  174. package/src/engine/physics/cloth/solver/cloth_self_find.js +52 -11
  175. package/src/engine/physics/cloth/solver/cloth_self_radius.d.ts +42 -0
  176. package/src/engine/physics/cloth/solver/cloth_self_radius.d.ts.map +1 -0
  177. package/src/engine/physics/cloth/solver/cloth_self_radius.js +64 -0
  178. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +99 -24
  179. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
  180. package/src/engine/physics/cloth/solver/cloth_solver_constants.js +102 -25
  181. package/src/engine/physics/cloth/solver/cloth_step.d.ts +5 -1
  182. package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
  183. package/src/engine/physics/cloth/solver/cloth_step.js +13 -3
  184. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts +0 -1
  185. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts.map +1 -1
  186. package/src/engine/physics/cloth/solver/cloth_step_instance.js +12 -14
  187. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts +23 -4
  188. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts.map +1 -1
  189. package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.js +36 -8
  190. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts.map +1 -1
  191. package/src/engine/physics/cloth/solver/constraint/cloth_self_row.js +4 -2
  192. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  193. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts +38 -7
  194. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts.map +1 -1
  195. package/src/engine/sound/ecs/audio/AudioEmitter.js +35 -7
  196. package/src/engine/sound/ecs/audio/AudioEmitterSerializationAdapter.d.ts.map +1 -1
  197. package/src/engine/sound/ecs/audio/AudioEmitterSerializationAdapter.js +5 -1
  198. package/src/engine/sound/ecs/audio/AudioEmitterSerializationUpgrader_0_1.d.ts +18 -0
  199. package/src/engine/sound/ecs/audio/AudioEmitterSerializationUpgrader_0_1.d.ts.map +1 -0
  200. package/src/engine/sound/ecs/audio/AudioEmitterSerializationUpgrader_0_1.js +48 -0
  201. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts +12 -2
  202. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts.map +1 -1
  203. package/src/engine/sound/ecs/audio/AudioEmitterSystem.js +173 -9
  204. package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts +9 -3
  205. package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts.map +1 -1
  206. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +28 -8
  207. package/src/engine/sound/ecs/trigger/AudioEventTrigger.d.ts +8 -0
  208. package/src/engine/sound/ecs/trigger/AudioEventTrigger.d.ts.map +1 -1
  209. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationAdapter.d.ts.map +1 -1
  210. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationAdapter.js +5 -1
  211. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationUpgrader_0_1.d.ts +18 -0
  212. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationUpgrader_0_1.d.ts.map +1 -0
  213. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationUpgrader_0_1.js +51 -0
  214. package/src/engine/sound/ecs/trigger/AudioEventTriggerSystem.d.ts.map +1 -1
  215. package/src/engine/sound/ecs/trigger/AudioEventTriggerSystem.js +15 -1
  216. package/src/engine/sound/sopra/README.md +768 -644
  217. package/src/engine/sound/sopra/SopraEngine.d.ts +104 -8
  218. package/src/engine/sound/sopra/SopraEngine.d.ts.map +1 -1
  219. package/src/engine/sound/sopra/SopraEngine.js +327 -12
  220. package/src/engine/sound/sopra/definition/EventDescription.d.ts +28 -2
  221. package/src/engine/sound/sopra/definition/EventDescription.d.ts.map +1 -1
  222. package/src/engine/sound/sopra/definition/EventDescription.js +55 -5
  223. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts +10 -0
  224. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts.map +1 -1
  225. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +77 -55
  226. package/src/engine/sound/sopra/definition/EventDescriptionSerializationUpgrader_0_1.d.ts +31 -0
  227. package/src/engine/sound/sopra/definition/EventDescriptionSerializationUpgrader_0_1.d.ts.map +1 -0
  228. package/src/engine/sound/sopra/definition/EventDescriptionSerializationUpgrader_0_1.js +75 -0
  229. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts +38 -9
  230. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts.map +1 -1
  231. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +34 -5
  232. package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts +7 -2
  233. package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts.map +1 -1
  234. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +137 -122
  235. package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts +3 -1
  236. package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts.map +1 -1
  237. package/src/engine/sound/sopra/definition/effect/EqEffect.js +146 -131
  238. package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts +1 -0
  239. package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts.map +1 -1
  240. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +6 -1
  241. package/src/engine/sound/sopra/runtime/BusGraph.d.ts +44 -2
  242. package/src/engine/sound/sopra/runtime/BusGraph.d.ts.map +1 -1
  243. package/src/engine/sound/sopra/runtime/BusGraph.js +83 -43
  244. package/src/engine/sound/sopra/runtime/EffectChain.d.ts +96 -0
  245. package/src/engine/sound/sopra/runtime/EffectChain.d.ts.map +1 -0
  246. package/src/engine/sound/sopra/runtime/EffectChain.js +286 -0
  247. package/src/engine/sound/sopra/runtime/EffectParamOverrides.d.ts +123 -0
  248. package/src/engine/sound/sopra/runtime/EffectParamOverrides.d.ts.map +1 -0
  249. package/src/engine/sound/sopra/runtime/EffectParamOverrides.js +320 -0
  250. package/src/engine/sound/sopra/runtime/EventInstance.d.ts +55 -5
  251. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
  252. package/src/engine/sound/sopra/runtime/EventInstance.js +201 -26
  253. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts +2 -1
  254. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts.map +1 -1
  255. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +6 -1
  256. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts +15 -0
  257. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts.map +1 -1
  258. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +32 -0
  259. package/src/engine/sound/sopra/util/MockAudioContext.d.ts.map +1 -1
  260. package/src/engine/sound/sopra/util/MockAudioContext.js +3 -0
  261. package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts +13 -3
  262. package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts.map +1 -1
  263. package/src/engine/sound/sopra/util/fadeOutAndStop.js +25 -13
  264. package/src/shade/RENDERER_CONTRACT.md +6 -3
  265. package/src/shade/device/SHADE_DEVICE_FLOOR.d.ts +24 -0
  266. package/src/shade/device/SHADE_DEVICE_FLOOR.d.ts.map +1 -0
  267. package/src/shade/device/SHADE_DEVICE_FLOOR.js +34 -0
  268. package/src/shade/device/ShadeDeviceFailure.d.ts +12 -5
  269. package/src/shade/device/ShadeDeviceFailure.d.ts.map +1 -1
  270. package/src/shade/device/ShadeDeviceFailure.js +17 -8
  271. package/src/shade/device/ShadeDeviceFailureReason.d.ts +1 -1
  272. package/src/shade/device/ShadeDeviceFailureReason.js +2 -2
  273. package/src/shade/device/mock/SoftwareGPUDevice.d.ts +15 -0
  274. package/src/shade/device/mock/SoftwareGPUDevice.d.ts.map +1 -1
  275. package/src/shade/device/mock/SoftwareGPUDevice.js +21 -0
  276. package/src/shade/device/pipeline/ComputePipelineManager.d.ts +20 -0
  277. package/src/shade/device/pipeline/ComputePipelineManager.d.ts.map +1 -1
  278. package/src/shade/device/pipeline/ComputePipelineManager.js +80 -6
  279. package/src/shade/device/pipeline/PipelineEnquiry.d.ts +52 -0
  280. package/src/shade/device/pipeline/PipelineEnquiry.d.ts.map +1 -0
  281. package/src/shade/device/pipeline/PipelineEnquiry.js +63 -0
  282. package/src/shade/device/pipeline/RenderPipelineManager.d.ts +21 -0
  283. package/src/shade/device/pipeline/RenderPipelineManager.d.ts.map +1 -1
  284. package/src/shade/device/pipeline/RenderPipelineManager.js +84 -4
  285. package/src/shade/device/shade_device_floor_unmet.d.ts +13 -0
  286. package/src/shade/device/shade_device_floor_unmet.d.ts.map +1 -0
  287. package/src/shade/device/shade_device_floor_unmet.js +32 -0
  288. package/src/shade/playground/contact_shadows/ContactShadowLab.d.ts +137 -0
  289. package/src/shade/playground/contact_shadows/ContactShadowLab.d.ts.map +1 -0
  290. package/src/shade/playground/contact_shadows/ContactShadowLab.js +654 -0
  291. package/src/shade/playground/contact_shadows/README.md +126 -0
  292. package/src/shade/playground/contact_shadows/index.html +191 -0
  293. package/src/shade/playground/contact_shadows/main.d.ts +2 -0
  294. package/src/shade/playground/contact_shadows/main.d.ts.map +1 -0
  295. package/src/shade/playground/contact_shadows/main.js +888 -0
  296. package/src/shade/playground/contact_shadows/scenes.d.ts +48 -0
  297. package/src/shade/playground/contact_shadows/scenes.d.ts.map +1 -0
  298. package/src/shade/playground/contact_shadows/scenes.js +620 -0
  299. package/src/shade/playground/contact_shadows/shader_contact_lab_composite.d.ts +36 -0
  300. package/src/shade/playground/contact_shadows/shader_contact_lab_composite.d.ts.map +1 -0
  301. package/src/shade/playground/contact_shadows/shader_contact_lab_composite.js +245 -0
  302. package/src/shade/playground/contact_shadows/shader_contact_lab_reference.d.ts +15 -0
  303. package/src/shade/playground/contact_shadows/shader_contact_lab_reference.d.ts.map +1 -0
  304. package/src/shade/playground/contact_shadows/shader_contact_lab_reference.js +120 -0
  305. package/src/shade/playground/contact_shadows/shader_contact_lab_stats.d.ts +55 -0
  306. package/src/shade/playground/contact_shadows/shader_contact_lab_stats.d.ts.map +1 -0
  307. package/src/shade/playground/contact_shadows/shader_contact_lab_stats.js +216 -0
  308. package/src/shade/playground/contact_shadows/shader_contact_lab_view.d.ts +20 -0
  309. package/src/shade/playground/contact_shadows/shader_contact_lab_view.d.ts.map +1 -0
  310. package/src/shade/playground/contact_shadows/shader_contact_lab_view.js +114 -0
  311. package/src/shade/playground/contact_shadows/shader_contact_shadow_march.d.ts +11 -0
  312. package/src/shade/playground/contact_shadows/shader_contact_shadow_march.d.ts.map +1 -0
  313. package/src/shade/playground/contact_shadows/shader_contact_shadow_march.js +90 -0
  314. package/src/shade/playground/contact_shadows/textures.d.ts +16 -0
  315. package/src/shade/playground/contact_shadows/textures.d.ts.map +1 -0
  316. package/src/shade/playground/contact_shadows/textures.js +123 -0
  317. package/src/shade/playground/particle_ecs/README.md +1 -1
  318. package/src/shade/playground/particle_ecs/effects/flame.json +1932 -1881
  319. package/src/shade/playground/particle_ecs/effects/smoke.json +99 -1
  320. package/src/shade/playground/particle_ecs/effects/soot.json +152 -145
  321. package/src/shade/playground/tonemap_sdr/README.md +90 -0
  322. package/src/shade/playground/tonemap_sdr/ToneMapCompare.d.ts +105 -0
  323. package/src/shade/playground/tonemap_sdr/ToneMapCompare.d.ts.map +1 -0
  324. package/src/shade/playground/tonemap_sdr/ToneMapCompare.js +380 -0
  325. package/src/shade/playground/tonemap_sdr/chunk_tonemap_gt7_sdr_reference.d.ts +32 -0
  326. package/src/shade/playground/tonemap_sdr/chunk_tonemap_gt7_sdr_reference.d.ts.map +1 -0
  327. package/src/shade/playground/tonemap_sdr/chunk_tonemap_gt7_sdr_reference.js +104 -0
  328. package/src/shade/playground/tonemap_sdr/index.html +90 -0
  329. package/src/shade/playground/tonemap_sdr/main.d.ts +2 -0
  330. package/src/shade/playground/tonemap_sdr/main.d.ts.map +1 -0
  331. package/src/shade/playground/tonemap_sdr/main.js +306 -0
  332. package/src/shade/playground/tonemap_sdr/scenes.d.ts +26 -0
  333. package/src/shade/playground/tonemap_sdr/scenes.d.ts.map +1 -0
  334. package/src/shade/playground/tonemap_sdr/scenes.js +404 -0
  335. package/src/shade/playground/volumetrics_froxel/main.d.ts.map +1 -1
  336. package/src/shade/playground/volumetrics_froxel/main.js +85 -4
  337. package/src/shade/playground/water_refraction/README.md +26 -18
  338. package/src/shade/playground/water_refraction/index.html +1 -1
  339. package/src/shade/renderer/Renderer.d.ts +37 -3
  340. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  341. package/src/shade/renderer/Renderer.js +237 -67
  342. package/src/shade/renderer/ShadeToneMappingMode.d.ts +14 -0
  343. package/src/shade/renderer/ShadeToneMappingMode.d.ts.map +1 -0
  344. package/src/shade/renderer/ShadeToneMappingMode.js +30 -0
  345. package/src/shade/renderer/animation/GPUAnimationManager.d.ts +9 -0
  346. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  347. package/src/shade/renderer/animation/GPUAnimationManager.js +14 -0
  348. package/src/shade/renderer/animation/skinning/shader_meshlet_apply_skinning.d.ts.map +1 -1
  349. package/src/shade/renderer/animation/skinning/shader_meshlet_apply_skinning.js +5 -3
  350. package/src/shade/renderer/buffer/GPUBufferAllocator.d.ts.map +1 -1
  351. package/src/shade/renderer/buffer/GPUBufferAllocator.js +9 -0
  352. package/src/shade/renderer/buffer/buffer_pool_ops.d.ts +21 -12
  353. package/src/shade/renderer/buffer/buffer_pool_ops.d.ts.map +1 -1
  354. package/src/shade/renderer/buffer/buffer_pool_ops.js +18 -19
  355. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts +7 -0
  356. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
  357. package/src/shade/renderer/buffer/table/GPUDatabase.js +12 -0
  358. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.d.ts +8 -0
  359. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.d.ts.map +1 -1
  360. package/src/shade/renderer/buffer/table/single/GPUSingleTypeTable.js +12 -0
  361. package/src/shade/renderer/camera/GPUCameraContext.d.ts.map +1 -1
  362. package/src/shade/renderer/camera/GPUCameraContext.js +19 -8
  363. package/src/shade/renderer/deferred/chunk_read_gBuffer_material.d.ts.map +1 -1
  364. package/src/shade/renderer/deferred/chunk_read_gBuffer_material.js +4 -0
  365. package/src/shade/renderer/deferred/chunk_shade_standard_material_direct.d.ts.map +1 -1
  366. package/src/shade/renderer/deferred/chunk_shade_standard_material_direct.js +4 -0
  367. package/src/shade/renderer/deferred/chunk_shade_standard_material_direct_restir.d.ts.map +1 -1
  368. package/src/shade/renderer/deferred/chunk_shade_standard_material_direct_restir.js +4 -0
  369. package/src/shade/renderer/deferred/shader_deferred_main.d.ts.map +1 -1
  370. package/src/shade/renderer/deferred/shader_deferred_main.js +3 -0
  371. package/src/shade/renderer/deferred/shader_tonemap_HDR.d.ts +6 -0
  372. package/src/shade/renderer/deferred/shader_tonemap_HDR.d.ts.map +1 -1
  373. package/src/shade/renderer/deferred/shader_tonemap_HDR.js +13 -3
  374. package/src/shade/renderer/deferred/shader_tonemap_HDR_none.d.ts +9 -0
  375. package/src/shade/renderer/deferred/shader_tonemap_HDR_none.d.ts.map +1 -0
  376. package/src/shade/renderer/deferred/shader_tonemap_HDR_none.js +63 -0
  377. package/src/shade/renderer/deferred/shader_tonemap_HDR_pbr_neutral.d.ts +10 -0
  378. package/src/shade/renderer/deferred/shader_tonemap_HDR_pbr_neutral.d.ts.map +1 -0
  379. package/src/shade/renderer/deferred/shader_tonemap_HDR_pbr_neutral.js +87 -0
  380. package/src/shade/renderer/deferred/shader_tonemap_LDR.d.ts +6 -0
  381. package/src/shade/renderer/deferred/shader_tonemap_LDR.d.ts.map +1 -1
  382. package/src/shade/renderer/deferred/shader_tonemap_LDR.js +23 -5
  383. package/src/shade/renderer/deferred/shader_tonemap_LDR_none.d.ts +9 -0
  384. package/src/shade/renderer/deferred/shader_tonemap_LDR_none.d.ts.map +1 -0
  385. package/src/shade/renderer/deferred/shader_tonemap_LDR_none.js +68 -0
  386. package/src/shade/renderer/deferred/shader_tonemap_LDR_pbr_neutral.d.ts +10 -0
  387. package/src/shade/renderer/deferred/shader_tonemap_LDR_pbr_neutral.d.ts.map +1 -0
  388. package/src/shade/renderer/deferred/shader_tonemap_LDR_pbr_neutral.js +73 -0
  389. package/src/shade/renderer/geometry/meshlet/MeshletBatch.d.ts.map +1 -1
  390. package/src/shade/renderer/geometry/meshlet/MeshletBatch.js +58 -9
  391. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts +35 -25
  392. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts.map +1 -1
  393. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +97 -196
  394. package/src/shade/renderer/geometry/sdf/shader_build_sdf.d.ts.map +1 -1
  395. package/src/shade/renderer/geometry/sdf/shader_build_sdf.js +121 -129
  396. package/src/shade/renderer/global_illumination/brick4/gpu/bake/brick4_bake_basic.d.ts.map +1 -1
  397. package/src/shade/renderer/global_illumination/brick4/gpu/bake/brick4_bake_basic.js +3 -0
  398. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_probes.d.ts.map +1 -1
  399. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_probes.js +4 -0
  400. package/src/shade/renderer/global_illumination/brick4/gpu/draw/shader_brick4_indirect_lighting.d.ts.map +1 -1
  401. package/src/shade/renderer/global_illumination/brick4/gpu/draw/shader_brick4_indirect_lighting.js +4 -0
  402. package/src/shade/renderer/global_illumination/lpv/bake/GPULightProbeVolumeRenderer.d.ts.map +1 -1
  403. package/src/shade/renderer/global_illumination/lpv/bake/GPULightProbeVolumeRenderer.js +3 -0
  404. package/src/shade/renderer/global_illumination/lpv/deprecated/probe_trace_compute_shader_code.d.ts.map +1 -1
  405. package/src/shade/renderer/global_illumination/lpv/deprecated/probe_trace_compute_shader_code.js +4 -0
  406. package/src/shade/renderer/global_illumination/probe/octahedral/graph_sample_light_probes.d.ts.map +1 -1
  407. package/src/shade/renderer/global_illumination/probe/octahedral/graph_sample_light_probes.js +4 -0
  408. package/src/shade/renderer/global_illumination/shader_resolve_indirect_lighting.d.ts.map +1 -1
  409. package/src/shade/renderer/global_illumination/shader_resolve_indirect_lighting.js +4 -0
  410. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +131 -1
  411. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_delta.d.ts +7 -0
  412. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_delta.d.ts.map +1 -1
  413. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_delta.js +45 -38
  414. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_determine_range.d.ts +2 -0
  415. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_determine_range.d.ts.map +1 -1
  416. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_determine_range.js +81 -79
  417. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_find_split.d.ts +2 -0
  418. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_find_split.d.ts.map +1 -1
  419. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_find_split.js +5 -3
  420. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_internal_children.d.ts +21 -0
  421. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_internal_children.d.ts.map +1 -0
  422. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_internal_children.js +42 -0
  423. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_morton_code.d.ts +19 -0
  424. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_morton_code.d.ts.map +1 -0
  425. package/src/shade/renderer/gpu_primitive/bvh/chunk_bvh_morton_code.js +44 -0
  426. package/src/shade/renderer/gpu_primitive/bvh/graph_bvh_build_lbvh.d.ts +23 -5
  427. package/src/shade/renderer/gpu_primitive/bvh/graph_bvh_build_lbvh.d.ts.map +1 -1
  428. package/src/shade/renderer/gpu_primitive/bvh/graph_bvh_build_lbvh.js +139 -23
  429. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_build_single_workgroup.d.ts +25 -0
  430. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_build_single_workgroup.d.ts.map +1 -0
  431. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_build_single_workgroup.js +179 -0
  432. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_emit_hierarchy.d.ts +2 -2
  433. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_emit_hierarchy.d.ts.map +1 -1
  434. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_emit_hierarchy.js +7 -16
  435. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_morton.d.ts +3 -4
  436. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_morton.d.ts.map +1 -1
  437. package/src/shade/renderer/gpu_primitive/bvh/shader_bvh_morton.js +6 -23
  438. package/src/shade/renderer/gpu_primitive/indirect/IndirectComputeCommandBuildPass.d.ts +11 -0
  439. package/src/shade/renderer/gpu_primitive/indirect/IndirectComputeCommandBuildPass.d.ts.map +1 -1
  440. package/src/shade/renderer/gpu_primitive/indirect/IndirectComputeCommandBuildPass.js +14 -3
  441. package/src/shade/renderer/gpu_primitive/indirect/build_indirect_draw_command.d.ts +10 -0
  442. package/src/shade/renderer/gpu_primitive/indirect/build_indirect_draw_command.d.ts.map +1 -1
  443. package/src/shade/renderer/gpu_primitive/indirect/build_indirect_draw_command.js +13 -3
  444. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/graph_prefix_scan_csdldf.d.ts +10 -0
  445. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/graph_prefix_scan_csdldf.d.ts.map +1 -1
  446. package/src/shade/renderer/gpu_primitive/prefix_sum/v1/graph_prefix_scan_csdldf.js +14 -0
  447. package/src/shade/renderer/gpu_primitive/sort/chunk_sort_bitonic_comparator.d.ts +26 -0
  448. package/src/shade/renderer/gpu_primitive/sort/chunk_sort_bitonic_comparator.d.ts.map +1 -0
  449. package/src/shade/renderer/gpu_primitive/sort/chunk_sort_bitonic_comparator.js +52 -0
  450. package/src/shade/renderer/gpu_primitive/sort/chunk_sort_bitonic_workgroup.d.ts +32 -0
  451. package/src/shade/renderer/gpu_primitive/sort/chunk_sort_bitonic_workgroup.d.ts.map +1 -0
  452. package/src/shade/renderer/gpu_primitive/sort/chunk_sort_bitonic_workgroup.js +89 -0
  453. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.d.ts +10 -0
  454. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.d.ts.map +1 -1
  455. package/src/shade/renderer/gpu_primitive/sort/graph_radix_sort.js +20 -1
  456. package/src/shade/renderer/gpu_primitive/test/gpu_test.d.ts.map +1 -1
  457. package/src/shade/renderer/gpu_primitive/test/gpu_test.js +8 -11
  458. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +10 -0
  459. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  460. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +294 -281
  461. package/src/shade/renderer/light/GPULightCollection.d.ts +8 -0
  462. package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
  463. package/src/shade/renderer/light/GPULightCollection.js +18 -1
  464. package/src/shade/renderer/light/cluster/cull/graph_cull_lights.d.ts +8 -0
  465. package/src/shade/renderer/light/cluster/cull/graph_cull_lights.d.ts.map +1 -1
  466. package/src/shade/renderer/light/cluster/cull/graph_cull_lights.js +234 -218
  467. package/src/shade/renderer/light/cluster/graph_assign_clsuters.d.ts +7 -0
  468. package/src/shade/renderer/light/cluster/graph_assign_clsuters.d.ts.map +1 -1
  469. package/src/shade/renderer/light/cluster/graph_assign_clsuters.js +13 -0
  470. package/src/shade/renderer/light/cluster/graph_build_light_clusters.d.ts +7 -0
  471. package/src/shade/renderer/light/cluster/graph_build_light_clusters.d.ts.map +1 -1
  472. package/src/shade/renderer/light/cluster/graph_build_light_clusters.js +16 -2
  473. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts +10 -0
  474. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts.map +1 -1
  475. package/src/shade/renderer/light/environment/execute_filter_environment_map.js +13 -0
  476. package/src/shade/renderer/lightmap/GPULightMap.d.ts.map +1 -1
  477. package/src/shade/renderer/lightmap/GPULightMap.js +3 -0
  478. package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_irradiance_flat.d.ts.map +1 -1
  479. package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_irradiance_flat.js +4 -0
  480. package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_sh.d.ts.map +1 -1
  481. package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_sh.js +4 -0
  482. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  483. package/src/shade/renderer/material/GPUMaterialManager.d.ts +16 -4
  484. package/src/shade/renderer/material/GPUMaterialManager.d.ts.map +1 -1
  485. package/src/shade/renderer/material/GPUMaterialManager.js +38 -8
  486. package/src/shade/renderer/particles/DESIGN.md +49 -7
  487. package/src/shade/renderer/particles/ORIENTATION_PLAN_2026_10_15.md +453 -0
  488. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts +5 -1
  489. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.d.ts.map +1 -1
  490. package/src/shade/renderer/particles/data/PARTICLE_EMITTER_STRUCT.js +13 -5
  491. package/src/shade/renderer/particles/data/particle_emitter_record.js +2 -2
  492. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.d.ts.map +1 -1
  493. package/src/shade/renderer/particles/graph/groups/particle_builtin_groups.js +12 -0
  494. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.d.ts +60 -0
  495. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.d.ts.map +1 -1
  496. package/src/shade/renderer/particles/graph/groups/particle_groups_geometry.js +156 -0
  497. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +2 -2
  498. package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
  499. package/src/shade/renderer/particles/runtime/ParticleEmitter.js +8 -8
  500. package/src/shade/renderer/particles/shaders/chunk_particle_billboard_vertex.d.ts.map +1 -1
  501. package/src/shade/renderer/particles/shaders/chunk_particle_billboard_vertex.js +6 -4
  502. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.d.ts +7 -6
  503. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.d.ts.map +1 -1
  504. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.js +91 -58
  505. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.d.ts.map +1 -1
  506. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.js +10 -0
  507. package/src/shade/renderer/path_tracer/accumulating/shader_accumulating_path_tracer.d.ts.map +1 -1
  508. package/src/shade/renderer/path_tracer/accumulating/shader_accumulating_path_tracer.js +5 -1
  509. package/src/shade/renderer/path_tracer/graph_path_trace_scene.d.ts.map +1 -1
  510. package/src/shade/renderer/path_tracer/graph_path_trace_scene.js +6 -0
  511. package/src/shade/renderer/pool/TransientResourcePool.d.ts +24 -0
  512. package/src/shade/renderer/pool/TransientResourcePool.d.ts.map +1 -1
  513. package/src/shade/renderer/pool/TransientResourcePool.js +41 -10
  514. package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.d.ts +7 -0
  515. package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.d.ts.map +1 -1
  516. package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.js +18 -0
  517. package/src/shade/renderer/postprocess/denoise/LigmaDenoiser.d.ts.map +1 -1
  518. package/src/shade/renderer/postprocess/denoise/LigmaDenoiser.js +1 -0
  519. package/src/shade/renderer/postprocess/denoise/shader_temporal_reprojection_luma.d.ts.map +1 -1
  520. package/src/shade/renderer/postprocess/denoise/shader_temporal_reprojection_luma.js +6 -4
  521. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.d.ts +11 -0
  522. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.d.ts.map +1 -1
  523. package/src/shade/renderer/postprocess/eye/GPUCameraExposureManager.js +16 -0
  524. package/src/shade/renderer/postprocess/gtao/GTAO.d.ts +10 -0
  525. package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
  526. package/src/shade/renderer/postprocess/gtao/GTAO.js +16 -0
  527. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts.map +1 -1
  528. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.js +32 -10
  529. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  530. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.d.ts.map +1 -1
  531. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.js +3 -0
  532. package/src/shade/renderer/postprocess/ssr/resolve/build_ssr_resolve_shader.d.ts.map +1 -1
  533. package/src/shade/renderer/postprocess/ssr/resolve/build_ssr_resolve_shader.js +6 -3
  534. package/src/shade/renderer/postprocess/taa/TAA.d.ts +12 -0
  535. package/src/shade/renderer/postprocess/taa/TAA.d.ts.map +1 -1
  536. package/src/shade/renderer/postprocess/taa/TAA.js +15 -0
  537. package/src/shade/renderer/postprocess/velocity/graph_add_per_object_velocity.d.ts +7 -0
  538. package/src/shade/renderer/postprocess/velocity/graph_add_per_object_velocity.d.ts.map +1 -1
  539. package/src/shade/renderer/postprocess/velocity/graph_add_per_object_velocity.js +13 -0
  540. package/src/shade/renderer/rasterize/TemplatePassDescriptor.d.ts +31 -3
  541. package/src/shade/renderer/rasterize/TemplatePassDescriptor.d.ts.map +1 -1
  542. package/src/shade/renderer/rasterize/TemplatePassDescriptor.js +79 -4
  543. package/src/shade/renderer/rasterize/bucket/graph_meshlets_rasterize_by_bucket.d.ts +17 -0
  544. package/src/shade/renderer/rasterize/bucket/graph_meshlets_rasterize_by_bucket.d.ts.map +1 -1
  545. package/src/shade/renderer/rasterize/bucket/graph_meshlets_rasterize_by_bucket.js +35 -4
  546. package/src/shade/renderer/rasterize/compute/graph_mesh_frustum_cull.d.ts +7 -0
  547. package/src/shade/renderer/rasterize/compute/graph_mesh_frustum_cull.d.ts.map +1 -1
  548. package/src/shade/renderer/rasterize/compute/graph_mesh_frustum_cull.js +182 -169
  549. package/src/shade/renderer/rasterize/expand/bucket/mesh/graph_meshes_classify_buckets.d.ts +10 -0
  550. package/src/shade/renderer/rasterize/expand/bucket/mesh/graph_meshes_classify_buckets.d.ts.map +1 -1
  551. package/src/shade/renderer/rasterize/expand/bucket/mesh/graph_meshes_classify_buckets.js +20 -1
  552. package/src/shade/renderer/rasterize/expand/bucket/meshlet/graph_extract_meshlets_from_bucket.d.ts +10 -0
  553. package/src/shade/renderer/rasterize/expand/bucket/meshlet/graph_extract_meshlets_from_bucket.d.ts.map +1 -1
  554. package/src/shade/renderer/rasterize/expand/bucket/meshlet/graph_extract_meshlets_from_bucket.js +18 -1
  555. package/src/shade/renderer/rasterize/expand/bucket/meshlet/graph_sort_meshlets_by_bucket_id.d.ts +10 -0
  556. package/src/shade/renderer/rasterize/expand/bucket/meshlet/graph_sort_meshlets_by_bucket_id.d.ts.map +1 -1
  557. package/src/shade/renderer/rasterize/expand/bucket/meshlet/graph_sort_meshlets_by_bucket_id.js +20 -1
  558. package/src/shade/renderer/rasterize/expand/graph_mesh_filter_one_way.d.ts +7 -0
  559. package/src/shade/renderer/rasterize/expand/graph_mesh_filter_one_way.d.ts.map +1 -1
  560. package/src/shade/renderer/rasterize/expand/graph_mesh_filter_one_way.js +187 -170
  561. package/src/shade/renderer/rasterize/expand/graph_meshlets_filter_one_way.d.ts +10 -0
  562. package/src/shade/renderer/rasterize/expand/graph_meshlets_filter_one_way.d.ts.map +1 -1
  563. package/src/shade/renderer/rasterize/expand/graph_meshlets_filter_one_way.js +19 -1
  564. package/src/shade/renderer/rasterize/expand/graph_meshlets_rasterize.d.ts +13 -0
  565. package/src/shade/renderer/rasterize/expand/graph_meshlets_rasterize.d.ts.map +1 -1
  566. package/src/shade/renderer/rasterize/expand/graph_meshlets_rasterize.js +25 -1
  567. package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.d.ts +10 -0
  568. package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.d.ts.map +1 -1
  569. package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.js +20 -0
  570. package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_brick4.d.ts.map +1 -1
  571. package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_brick4.js +6 -0
  572. package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_ibl.d.ts.map +1 -1
  573. package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_ibl.js +6 -0
  574. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts +3 -2
  575. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts.map +1 -1
  576. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.js +3 -2
  577. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts +7 -2
  578. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -1
  579. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +12 -4
  580. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_brick4.d.ts.map +1 -1
  581. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_brick4.js +4 -3
  582. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_ibl.d.ts.map +1 -1
  583. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_ibl.js +4 -3
  584. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.d.ts +2 -1
  585. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.d.ts.map +1 -1
  586. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.js +11 -3
  587. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments.d.ts.map +1 -1
  588. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments.js +4 -3
  589. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.d.ts.map +1 -1
  590. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +4 -3
  591. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts +15 -0
  592. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  593. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +35 -2
  594. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts +14 -0
  595. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
  596. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +32 -4
  597. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts +17 -0
  598. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
  599. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +34 -4
  600. package/src/shade/renderer/restir/di/chunk_restir_resolve.d.ts +5 -3
  601. package/src/shade/renderer/restir/di/chunk_restir_resolve.d.ts.map +1 -1
  602. package/src/shade/renderer/restir/di/chunk_restir_resolve.js +8 -3
  603. package/src/shade/renderer/restir/di/graph_restir_di.d.ts.map +1 -1
  604. package/src/shade/renderer/restir/di/graph_restir_di.js +1 -0
  605. package/src/shade/renderer/restir/di/shader_restir_di_resolve.d.ts.map +1 -1
  606. package/src/shade/renderer/restir/di/shader_restir_di_resolve.js +5 -1
  607. package/src/shade/renderer/restir/reservoir/shader.d.ts.map +1 -1
  608. package/src/shade/renderer/restir/reservoir/shader.js +7 -4
  609. package/src/shade/renderer/scene/GPUInstancesAccelerationStructure.d.ts +7 -0
  610. package/src/shade/renderer/scene/GPUInstancesAccelerationStructure.d.ts.map +1 -1
  611. package/src/shade/renderer/scene/GPUInstancesAccelerationStructure.js +13 -1
  612. package/src/shade/renderer/scene/GPUSceneContext.d.ts +10 -0
  613. package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
  614. package/src/shade/renderer/scene/GPUSceneContext.js +19 -0
  615. package/src/shade/renderer/scene/SceneManager.d.ts +9 -0
  616. package/src/shade/renderer/scene/SceneManager.d.ts.map +1 -1
  617. package/src/shade/renderer/scene/SceneManager.js +11 -0
  618. package/src/shade/renderer/scene/hierarchy/graph_nodes_update_transform_hierarchy.d.ts +11 -0
  619. package/src/shade/renderer/scene/hierarchy/graph_nodes_update_transform_hierarchy.d.ts.map +1 -1
  620. package/src/shade/renderer/scene/hierarchy/graph_nodes_update_transform_hierarchy.js +17 -0
  621. package/src/shade/renderer/scene/hierarchy/graph_scene_update_transform_hierarchy.d.ts +11 -0
  622. package/src/shade/renderer/scene/hierarchy/graph_scene_update_transform_hierarchy.d.ts.map +1 -1
  623. package/src/shade/renderer/scene/hierarchy/graph_scene_update_transform_hierarchy.js +18 -1
  624. package/src/shade/renderer/shader/ComputeShader.d.ts +12 -0
  625. package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
  626. package/src/shade/renderer/shader/ComputeShader.js +71 -2
  627. package/src/shade/renderer/shader/ImageShader.d.ts +27 -8
  628. package/src/shade/renderer/shader/ImageShader.d.ts.map +1 -1
  629. package/src/shade/renderer/shader/ImageShader.js +299 -218
  630. package/src/shade/renderer/shader/ShaderDescriptor.d.ts +21 -0
  631. package/src/shade/renderer/shader/ShaderDescriptor.d.ts.map +1 -1
  632. package/src/shade/renderer/shader/ShaderDescriptor.js +71 -1
  633. package/src/shade/renderer/shader/chunk/camera/chunk_project_view_position_from_depth.d.ts +17 -0
  634. package/src/shade/renderer/shader/chunk/camera/chunk_project_view_position_from_depth.d.ts.map +1 -0
  635. package/src/shade/renderer/shader/chunk/camera/chunk_project_view_position_from_depth.js +45 -0
  636. package/src/shade/renderer/shader/chunk/camera/chunk_screen_coordinate_to_view.js +3 -3
  637. package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_eotf_st2084.d.ts +1 -1
  638. package/src/shade/renderer/shader/chunk/color/iCtCp/chunk_eotf_st2084.js +62 -62
  639. package/src/shade/renderer/shader/chunk/color/tonemap/HDR_TONE_MAPPING_FORMS_BRIEF_2026_10_16.md +318 -0
  640. package/src/shade/renderer/shader/chunk/color/tonemap/SDR_TONE_MAPPING_BRIEF_2026_10_16.md +237 -0
  641. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_PAPER_WHITE_NITS.d.ts +17 -0
  642. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_PAPER_WHITE_NITS.d.ts.map +1 -0
  643. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_PAPER_WHITE_NITS.js +22 -0
  644. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_khronos_pbr.d.ts +14 -1
  645. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_khronos_pbr.d.ts.map +1 -1
  646. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_khronos_pbr.js +28 -15
  647. package/src/shade/renderer/shader/chunk/color/tonemap/gt7/chunk_tonemap_gt7.d.ts +12 -0
  648. package/src/shade/renderer/shader/chunk/color/tonemap/gt7/chunk_tonemap_gt7.d.ts.map +1 -1
  649. package/src/shade/renderer/shader/chunk/color/tonemap/gt7/chunk_tonemap_gt7.js +128 -110
  650. package/src/shade/renderer/shader/chunk/geometry/ray/chunk_offset_ray_from_depth.d.ts +20 -0
  651. package/src/shade/renderer/shader/chunk/geometry/ray/chunk_offset_ray_from_depth.d.ts.map +1 -0
  652. package/src/shade/renderer/shader/chunk/geometry/ray/chunk_offset_ray_from_depth.js +33 -0
  653. package/src/shade/renderer/shader/chunk/light/chunk_compute_multiscattering_specular.d.ts +4 -0
  654. package/src/shade/renderer/shader/chunk/light/chunk_compute_multiscattering_specular.d.ts.map +1 -1
  655. package/src/shade/renderer/shader/chunk/light/chunk_compute_multiscattering_specular.js +12 -1
  656. package/src/shade/renderer/shader/chunk/material/MATERIAL_SHADING_INFO_STRUCT.d.ts.map +1 -1
  657. package/src/shade/renderer/shader/chunk/material/MATERIAL_SHADING_INFO_STRUCT.js +7 -0
  658. package/src/shade/renderer/shader/chunk/material/MULTISCATTER.md +108 -0
  659. package/src/shade/renderer/shader/chunk/material/chunk_compute_indirect_specular.d.ts.map +1 -1
  660. package/src/shade/renderer/shader/chunk/material/chunk_compute_indirect_specular.js +23 -4
  661. package/src/shade/renderer/shader/chunk/material/chunk_re_direct_multiscatter.d.ts +18 -0
  662. package/src/shade/renderer/shader/chunk/material/chunk_re_direct_multiscatter.d.ts.map +1 -0
  663. package/src/shade/renderer/shader/chunk/material/chunk_re_direct_multiscatter.js +56 -0
  664. package/src/shade/renderer/shader/chunk/material/chunk_re_indirect_specular_physical.d.ts.map +1 -1
  665. package/src/shade/renderer/shader/chunk/material/chunk_re_indirect_specular_physical.js +2 -0
  666. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_space_linear_ray.d.ts +43 -0
  667. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_space_linear_ray.d.ts.map +1 -0
  668. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_space_linear_ray.js +319 -0
  669. package/src/shade/renderer/shader/compiler/CodeChunk.d.ts +33 -0
  670. package/src/shade/renderer/shader/compiler/CodeChunk.d.ts.map +1 -1
  671. package/src/shade/renderer/shader/compiler/CodeChunk.js +140 -0
  672. package/src/shade/renderer/shader/compiler/CodeChunkChoice.d.ts +74 -0
  673. package/src/shade/renderer/shader/compiler/CodeChunkChoice.d.ts.map +1 -0
  674. package/src/shade/renderer/shader/compiler/CodeChunkChoice.js +166 -0
  675. package/src/shade/renderer/shader/compiler/SHADER_SWITCH_PROPOSAL_2026_10_16.md +680 -0
  676. package/src/shade/renderer/shader/compiler/ShaderSwitch.d.ts +86 -0
  677. package/src/shade/renderer/shader/compiler/ShaderSwitch.d.ts.map +1 -0
  678. package/src/shade/renderer/shader/compiler/ShaderSwitch.js +178 -0
  679. package/src/shade/renderer/shader/compiler/ShaderSwitchSet.d.ts +67 -0
  680. package/src/shade/renderer/shader/compiler/ShaderSwitchSet.d.ts.map +1 -0
  681. package/src/shade/renderer/shader/compiler/ShaderSwitchSet.js +205 -0
  682. package/src/shade/renderer/shadow/contact/CONTACT_SHADOW_WAVE_SIZE.d.ts +14 -0
  683. package/src/shade/renderer/shadow/contact/CONTACT_SHADOW_WAVE_SIZE.d.ts.map +1 -0
  684. package/src/shade/renderer/shadow/contact/CONTACT_SHADOW_WAVE_SIZE.js +13 -0
  685. package/src/shade/renderer/shadow/contact/ContactShadowDispatchList.d.ts +65 -0
  686. package/src/shade/renderer/shadow/contact/ContactShadowDispatchList.d.ts.map +1 -0
  687. package/src/shade/renderer/shadow/contact/ContactShadowDispatchList.js +252 -0
  688. package/src/shade/renderer/shadow/contact/FINDINGS.md +413 -0
  689. package/src/shade/renderer/shadow/contact/chunk_contact_shadow_wavefront_extents.d.ts +29 -0
  690. package/src/shade/renderer/shadow/contact/chunk_contact_shadow_wavefront_extents.d.ts.map +1 -0
  691. package/src/shade/renderer/shadow/contact/chunk_contact_shadow_wavefront_extents.js +105 -0
  692. package/src/shade/renderer/shadow/contact/graph_contact_shadow_linear.d.ts +29 -0
  693. package/src/shade/renderer/shadow/contact/graph_contact_shadow_linear.d.ts.map +1 -0
  694. package/src/shade/renderer/shadow/contact/graph_contact_shadow_linear.js +39 -0
  695. package/src/shade/renderer/shadow/contact/graph_contact_shadow_wavefront.d.ts +29 -0
  696. package/src/shade/renderer/shadow/contact/graph_contact_shadow_wavefront.d.ts.map +1 -0
  697. package/src/shade/renderer/shadow/contact/graph_contact_shadow_wavefront.js +92 -0
  698. package/src/shade/renderer/shadow/contact/shader_contact_shadow_linear.d.ts +29 -0
  699. package/src/shade/renderer/shadow/contact/shader_contact_shadow_linear.d.ts.map +1 -0
  700. package/src/shade/renderer/shadow/contact/shader_contact_shadow_linear.js +128 -0
  701. package/src/shade/renderer/shadow/contact/shader_contact_shadow_wavefront.d.ts +52 -0
  702. package/src/shade/renderer/shadow/contact/shader_contact_shadow_wavefront.d.ts.map +1 -0
  703. package/src/shade/renderer/shadow/contact/shader_contact_shadow_wavefront.js +358 -0
  704. package/src/shade/renderer/shadow/map/ShadowDirectionalTechnique.d.ts +10 -0
  705. package/src/shade/renderer/shadow/map/ShadowDirectionalTechnique.d.ts.map +1 -0
  706. package/src/shade/renderer/shadow/map/ShadowDirectionalTechnique.js +12 -0
  707. package/src/shade/renderer/shadow/map/shader/SHADOW_DIRECTIONAL_TECHNIQUE.d.ts +13 -0
  708. package/src/shade/renderer/shadow/map/shader/SHADOW_DIRECTIONAL_TECHNIQUE.d.ts.map +1 -0
  709. package/src/shade/renderer/shadow/map/shader/SHADOW_DIRECTIONAL_TECHNIQUE.js +14 -0
  710. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_directional.d.ts +14 -8
  711. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_directional.d.ts.map +1 -1
  712. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_directional.js +20 -86
  713. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_directional_cascades.d.ts +14 -0
  714. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_directional_cascades.d.ts.map +1 -0
  715. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_directional_cascades.js +79 -0
  716. package/src/shade/renderer/shadow/ray/shader_ray_shadow_code.d.ts.map +1 -1
  717. package/src/shade/renderer/shadow/ray/shader_ray_shadow_code.js +12 -12
  718. package/src/shade/renderer/shadow/ray/staged/gen/shader_generate_rays.d.ts.map +1 -1
  719. package/src/shade/renderer/shadow/ray/staged/gen/shader_generate_rays.js +153 -151
  720. package/src/shade/renderer/shadow/vsm/shader/chunk_shadowmap_sample_directional_virtual.d.ts +13 -0
  721. package/src/shade/renderer/shadow/vsm/shader/chunk_shadowmap_sample_directional_virtual.d.ts.map +1 -0
  722. package/src/shade/renderer/shadow/vsm/shader/chunk_shadowmap_sample_directional_virtual.js +48 -0
  723. package/src/shade/renderer/texture/GPUTextureContext.d.ts +10 -0
  724. package/src/shade/renderer/texture/GPUTextureContext.d.ts.map +1 -1
  725. package/src/shade/renderer/texture/GPUTextureContext.js +24 -0
  726. package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.d.ts +219 -0
  727. package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.d.ts.map +1 -0
  728. package/src/shade/renderer/texture/atlas/GPUTileAtlas3D.js +671 -0
  729. package/src/shade/renderer/volumetrics/GPUSceneVolumetrics.d.ts +26 -2
  730. package/src/shade/renderer/volumetrics/GPUSceneVolumetrics.d.ts.map +1 -1
  731. package/src/shade/renderer/volumetrics/GPUSceneVolumetrics.js +108 -4
  732. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts +5 -1
  733. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
  734. package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +6 -0
  735. package/src/shade/renderer/volumetrics/NOTES.md +50 -0
  736. package/src/shade/renderer/volumetrics/PARTICIPATING_MEDIA_VOLUME_STRUCT.d.ts.map +1 -1
  737. package/src/shade/renderer/volumetrics/PARTICIPATING_MEDIA_VOLUME_STRUCT.js +30 -1
  738. package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.d.ts +51 -13
  739. package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.d.ts.map +1 -1
  740. package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.js +98 -13
  741. package/src/shade/renderer/volumetrics/SceneVolumetrics.d.ts +9 -0
  742. package/src/shade/renderer/volumetrics/SceneVolumetrics.d.ts.map +1 -1
  743. package/src/shade/renderer/volumetrics/SceneVolumetrics.js +78 -68
  744. package/src/shade/renderer/volumetrics/VOLUMETRIC_NOISE_PLAN_2026_10_15.md +326 -0
  745. package/src/shade/renderer/volumetrics/chunk_participating_media_volume_sample.d.ts +7 -4
  746. package/src/shade/renderer/volumetrics/chunk_participating_media_volume_sample.d.ts.map +1 -1
  747. package/src/shade/renderer/volumetrics/chunk_participating_media_volume_sample.js +21 -9
  748. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_uvw_from_clip.d.ts +19 -0
  749. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_uvw_from_clip.d.ts.map +1 -0
  750. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_uvw_from_clip.js +53 -0
  751. package/src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.d.ts +12 -3
  752. package/src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.d.ts.map +1 -1
  753. package/src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.js +85 -48
  754. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts +4 -1
  755. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts.map +1 -1
  756. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.js +4 -1
  757. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +4 -1
  758. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  759. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +21 -1
  760. package/src/shade/renderer/volumetrics/noise/PARTICIPATING_MEDIA_NOISE.d.ts +98 -0
  761. package/src/shade/renderer/volumetrics/noise/PARTICIPATING_MEDIA_NOISE.d.ts.map +1 -0
  762. package/src/shade/renderer/volumetrics/noise/PARTICIPATING_MEDIA_NOISE.js +115 -0
  763. package/src/shade/renderer/volumetrics/noise/VOLUMETRICS_NOISE_TILE_BAKE_SETTINGS_STRUCT.d.ts +9 -0
  764. package/src/shade/renderer/volumetrics/noise/VOLUMETRICS_NOISE_TILE_BAKE_SETTINGS_STRUCT.d.ts.map +1 -0
  765. package/src/shade/renderer/volumetrics/noise/VOLUMETRICS_NOISE_TILE_BAKE_SETTINGS_STRUCT.js +24 -0
  766. package/src/shade/renderer/volumetrics/noise/bake_volumetrics_noise_tile.d.ts +27 -0
  767. package/src/shade/renderer/volumetrics/noise/bake_volumetrics_noise_tile.d.ts.map +1 -0
  768. package/src/shade/renderer/volumetrics/noise/bake_volumetrics_noise_tile.js +91 -0
  769. package/src/shade/renderer/volumetrics/noise/chunk_participating_media_noise.d.ts +35 -0
  770. package/src/shade/renderer/volumetrics/noise/chunk_participating_media_noise.d.ts.map +1 -0
  771. package/src/shade/renderer/volumetrics/noise/chunk_participating_media_noise.js +92 -0
  772. package/src/shade/renderer/volumetrics/noise/shader_volumetrics_noise_tile_bake.d.ts +10 -0
  773. package/src/shade/renderer/volumetrics/noise/shader_volumetrics_noise_tile_bake.d.ts.map +1 -0
  774. package/src/shade/renderer/volumetrics/noise/shader_volumetrics_noise_tile_bake.js +121 -0
  775. package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.d.ts.map +1 -1
  776. package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.js +38 -3
  777. package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.d.ts.map +1 -1
  778. package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.js +311 -290
  779. package/src/shade/renderer/volumetrics/volumetrics_position_world_to_froxel_uvw.d.ts.map +1 -1
  780. package/src/shade/renderer/volumetrics/volumetrics_position_world_to_froxel_uvw.js +28 -49
  781. package/src/shade/renderer/water/GPUWaterRenderer.d.ts +3 -1
  782. package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
  783. package/src/shade/renderer/water/GPUWaterRenderer.js +24 -18
  784. package/src/shade/renderer/water/chunk_water_transport.d.ts +0 -1
  785. package/src/shade/renderer/water/chunk_water_transport.d.ts.map +1 -1
  786. package/src/shade/renderer/water/chunk_water_transport.js +0 -17
  787. package/src/shade/renderer/water/shader_water.d.ts +4 -2
  788. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  789. package/src/shade/renderer/water/shader_water.js +27 -16
  790. package/src/shade/renderer/water/shader_water_avboit.d.ts +0 -4
  791. package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -1
  792. package/src/shade/renderer/water/shader_water_avboit.js +0 -63
  793. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts.map +1 -1
  794. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +43 -9
  795. package/src/view/graphics/GraphicsContextFailureView.d.ts +1 -2
  796. package/src/view/graphics/GraphicsContextFailureView.d.ts.map +1 -1
  797. package/src/view/graphics/GraphicsContextFailureView.js +1 -2
  798. package/src/engine/network/transport/Channel.d.ts +0 -85
  799. package/src/engine/network/transport/Channel.d.ts.map +0 -1
  800. package/src/engine/network/transport/Channel.js +0 -336
  801. package/src/engine/network/transport/ReliableCommandPipeline.d.ts +0 -155
  802. package/src/engine/network/transport/ReliableCommandPipeline.d.ts.map +0 -1
  803. package/src/engine/network/transport/ReliableCommandPipeline.js +0 -335
  804. package/src/engine/network/transport/fragments/FragmentAssembler.d.ts +0 -105
  805. package/src/engine/network/transport/fragments/FragmentAssembler.d.ts.map +0 -1
  806. package/src/engine/network/transport/fragments/FragmentAssembler.js +0 -350
  807. package/src/engine/network/transport/fragments/FragmentRetention.d.ts +0 -103
  808. package/src/engine/network/transport/fragments/FragmentRetention.d.ts.map +0 -1
  809. package/src/engine/network/transport/fragments/FragmentRetention.js +0 -191
  810. package/src/engine/network/transport/fragments/fragment_send.d.ts +0 -61
  811. package/src/engine/network/transport/fragments/fragment_send.d.ts.map +0 -1
  812. package/src/engine/network/transport/fragments/fragment_send.js +0 -157
  813. package/src/engine/network/transport/fragments/packet_size.d.ts +0 -99
  814. package/src/engine/network/transport/fragments/packet_size.d.ts.map +0 -1
  815. package/src/engine/network/transport/fragments/packet_size.js +0 -107
  816. package/src/shade/renderer/shader/chunk/color/sRGB/chunk_RRTAndODTFit.d.ts +0 -6
  817. package/src/shade/renderer/shader/chunk/color/sRGB/chunk_RRTAndODTFit.d.ts.map +0 -1
  818. package/src/shade/renderer/shader/chunk/color/sRGB/chunk_RRTAndODTFit.js +0 -16
  819. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_aces.d.ts +0 -7
  820. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_aces.d.ts.map +0 -1
  821. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_aces.js +0 -46
  822. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_agx.d.ts +0 -10
  823. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_agx.d.ts.map +0 -1
  824. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_agx.js +0 -127
  825. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_pbr_neutral.d.ts +0 -7
  826. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_pbr_neutral.d.ts.map +0 -1
  827. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_pbr_neutral.js +0 -39
@@ -9,7 +9,8 @@ import { NetworkIdentity } from "./ecs/components/NetworkIdentity.js";
9
9
  import { make_owner_authorization } from "./ecs/owner_authorization.js";
10
10
  import { NetworkSystem } from "./ecs/NetworkSystem.js";
11
11
  import { NetworkIdentitySerializationAdapter, } from "./ecs/serialization/NetworkIdentitySerializationAdapter.js";
12
- import { NetworkPeer, ResumeRejectReason, } from "./orchestrator/NetworkPeer.js";
12
+ import { InitialSyncPage, NetworkPeer, ResumeRejectReason, } from "./orchestrator/NetworkPeer.js";
13
+ import { HostJoin, JoinProgress } from "./orchestrator/HostJoin.js";
13
14
  import { ServerAuthoritativeClient } from "./orchestrator/ServerAuthoritativeClient.js";
14
15
  import { LateInput, ServerAuthoritativeServer } from "./orchestrator/ServerAuthoritativeServer.js";
15
16
  import { OwnerAwareScope } from "./replication/ScopeFilter.js";
@@ -19,7 +20,6 @@ import { Interpolated, INTERPOLATION_SOURCE_LOCAL } from "../interpolation/Inter
19
20
  import { InterpolationLog } from "../interpolation/InterpolationLog.js";
20
21
  import { InterpolationSystem } from "../interpolation/InterpolationSystem.js";
21
22
  import { SimAction } from "./sim/SimAction.js";
22
- import { snapshotter_emit } from "./sim/Snapshotter.js";
23
23
  import { PredictionHistory } from "./state/PredictionHistory.js";
24
24
  import { AdaptiveRenderDelay } from "./time/AdaptiveRenderDelay.js";
25
25
  import { RenderPlayout } from "./time/RenderPlayout.js";
@@ -143,13 +143,15 @@ const ReconnectState = Object.freeze({
143
143
  /**
144
144
  * Client-side: how the host has answered the transport binding in use. A new binding does not know
145
145
  * which world the client is in until the host says: the one it has been predicting on, which the
146
- * host resumes (RESUME_ACCEPT), or the host's own, sent as a snapshot (INITIAL_SYNC). The first
147
- * answer is the answer; the host's handshake arriving after it is stale.
146
+ * host resumes (RESUME_ACCEPT), or the host's own, sent as pages of a snapshot (INITIAL_SYNC). The
147
+ * first answer is the answer; the host's handshake arriving after it is stale. The first page is
148
+ * the answer (Syncing); the FINAL page completes it (Synced).
148
149
  * @readonly @enum {string}
149
150
  */
150
151
  const BindingAnswer = Object.freeze({
151
152
  Pending: 'pending',
152
153
  Resumed: 'resumed',
154
+ Syncing: 'syncing',
153
155
  Synced: 'synced',
154
156
  });
155
157
 
@@ -229,6 +231,18 @@ function assert_copies_itself(method, name, ComponentClass) {
229
231
  * @param {number} length
230
232
  * @returns {boolean}
231
233
  */
234
+ /**
235
+ * Close a transport the session is giving up on. Best effort: one already closed, or failing to
236
+ * close, changes nothing about what follows.
237
+ * @param {{disconnect?: function(): void}} transport
238
+ */
239
+ function close_transport(transport) {
240
+ try {
241
+ transport.disconnect();
242
+ } catch (_) { /* swallow */
243
+ }
244
+ }
245
+
232
246
  function bytes_equal(a, b, length) {
233
247
  for (let i = 0; i < length; i++) {
234
248
  if (a[i] !== b[i]) return false;
@@ -255,8 +269,17 @@ function bytes_equal_lengths(a, a_length, b, b_length) {
255
269
  *
256
270
  * Replication is component-driven: attaching a {@link NetworkIdentity}
257
271
  * to an entity registers it with the session via an `EntityObserver`.
258
- * Mutation is event-driven: callers fire
259
- * `dataset.sendEvent(entity, "net_mutate_component", payload)`.
272
+ *
273
+ * **What travels is what goes through the action executor**, and nothing
274
+ * else: actions (`send`, a client's input), and direct writes their writer
275
+ * reports — `dataset.sendEvent(entity, "net_mutate_component", payload)`.
276
+ * The session never looks for changes by itself. That is what keeps a tick's
277
+ * cost in what changed and what is owned, never in the size of the world;
278
+ * the stack is built for worlds of millions of networked objects. A
279
+ * replicated component written directly and not reported stays where it was
280
+ * written. Ownership is set when an identity attaches, or by the join, and
281
+ * `owner_peer_id` / `network_id` are not written afterwards. The README's
282
+ * "What the session assumes" has the whole contract and its measured costs.
260
283
  *
261
284
  * **The session runs on the engine's clock.** The `EntityManager`'s fixed
262
285
  * step is the network tick: each step opens a network frame before any
@@ -329,6 +352,11 @@ export class NetworkSession {
329
352
  #client = null;
330
353
  /** @private @type {NetworkPeer|null} */
331
354
  #peer = null;
355
+ /**
356
+ * The session's place in the engine loop, added by `start()` and removed by `stop()`.
357
+ * @private @type {NetworkSystem|null}
358
+ */
359
+ #network_system = null;
332
360
  /** @private @type {EntityObserver|null} */
333
361
  #identity_observer = null;
334
362
  /** @private @type {InterpolationLog|null} */
@@ -352,6 +380,22 @@ export class NetworkSession {
352
380
  */
353
381
  #remote_entities = new Set();
354
382
 
383
+ /**
384
+ * Networked entities by owner: peer id → its entities. What a tick does per owned entity —
385
+ * the host's AUTH_STATE to each peer, the client's record of what it predicted — walks this,
386
+ * never every networked entity: the cost of a tick is in what is owned and what changed, not
387
+ * in the size of the world. Kept exact by attach, detach and {@link #apply_ownership}, the
388
+ * only places ownership is set.
389
+ * @private @type {Map<number, Set<number>>}
390
+ */
391
+ #owned_by = new Map();
392
+
393
+ /**
394
+ * Owner each networked entity is filed under in {@link #owned_by}.
395
+ * @private @type {Map<number, number>}
396
+ */
397
+ #owner_of = new Map();
398
+
355
399
  // ---- Render-frame interpolation state ----
356
400
 
357
401
  /**
@@ -416,26 +460,11 @@ export class NetworkSession {
416
460
  */
417
461
  #inbound = new Map();
418
462
 
419
- // ---- Host: publishing what the simulation changed ----
420
-
421
- /** @private @type {boolean} */
422
- #publish_changes;
463
+ // ---- Host ----
423
464
 
424
465
  /** @private @type {string} */
425
466
  #late_input;
426
467
 
427
- /**
428
- * What the peers hold for each networked entity, per replicated component type id, as the
429
- * replication adapter's bytes — so "changed" means "changed on the wire". Updated by every
430
- * record the host publishes and by every action it executes (the peers apply the action and
431
- * arrive at the same value), and compared with the live state at the end of each step.
432
- * @private @type {Map<number, Array<{bytes: Uint8Array, length: number}|null>>}
433
- */
434
- #published = new Map();
435
-
436
- /** @private @type {BinaryBuffer} */
437
- #scratch_publish_buf = new BinaryBuffer();
438
-
439
468
  // ---- Client: reconciliation by comparison ----
440
469
 
441
470
  /** @private @type {string} */
@@ -491,6 +520,16 @@ export class NetworkSession {
491
520
  */
492
521
  correction_skipped_count = 0;
493
522
 
523
+ /**
524
+ * Client: how many times it found itself more than the host's input window behind the frame the
525
+ * host last named, or was told by the host's depth feedback that its input lands below the
526
+ * host's window, and re-took the host's time. A tab in the background, a debugger, a laptop
527
+ * asleep — see {@link #retake_host_time_if_stalled}. Diagnostic: a game that sees these on a
528
+ * machine that never left the foreground has a client that cannot keep the step.
529
+ * @type {number}
530
+ */
531
+ stall_count = 0;
532
+
494
533
  /**
495
534
  * Client, {@link NetworkSessionReconcile.Correct}: a component of an authoritative state
496
535
  * differed from the prediction, and the present was corrected. Once per component corrected.
@@ -506,19 +545,21 @@ export class NetworkSession {
506
545
  #connected_peers = new Set();
507
546
 
508
547
  /**
509
- * Host-only: peer_id → remaining-resends. Drained in
510
- * `#on_host_tick_complete` once `current_sim_frame >= 0`. Sent twice
511
- * for loss tolerance, and never queued again on the peer's say-so: each
512
- * send serializes the whole world, and a sync the peer could ask for is one
513
- * it could keep asking for. Filled only by {@link #queue_initial_sync}.
514
- * (A fragment of one that goes missing is re-sent on the receiver's
515
- * NACK_FRAGMENT. That repair is `NetworkPeer`'s: a NACK re-sends each
516
- * chunk of the sync at most once, for at most
517
- * `FRAGMENT_RETENTION_MAX_RETRIES` NACKs, so it adds at most that many
518
- * copies of each sync, however the peer asks.)
519
- * @private @type {Map<number, number>}
548
+ * Host-only: peer_id → its join, while one is going on. Advanced in
549
+ * `#on_host_tick_complete`; created only by {@link #queue_initial_sync}, so never on the
550
+ * peer's say-so: a join the peer could ask for is one it could keep asking for. The pages ride
551
+ * the connection's BULK lane, which re-sends what is lost; what a join costs beyond the world
552
+ * once is the entities that changed while it was going out, for at most
553
+ * {@link JOIN_MAX_ROUNDS} rounds.
554
+ * @private @type {Map<number, HostJoin>}
520
555
  */
521
- #pending_initial_sync = new Map();
556
+ #joins = new Map();
557
+
558
+ /**
559
+ * Host-only: scratch every join builds its pages in.
560
+ * @private @type {BinaryBuffer}
561
+ */
562
+ #page_buffer = new BinaryBuffer();
522
563
 
523
564
  /**
524
565
  * Host-only: peers whose current connection has been given its
@@ -541,6 +582,13 @@ export class NetworkSession {
541
582
  /** @private @type {boolean} */
542
583
  #started = false;
543
584
 
585
+ /**
586
+ * {@link stop} has run. A session is one match: what it knew of its peers outlives the stop, so
587
+ * it does not start again.
588
+ * @private @type {boolean}
589
+ */
590
+ #stopped = false;
591
+
544
592
  // ---- Host-side: per-peer session token + grace state ----
545
593
 
546
594
  /**
@@ -613,14 +661,47 @@ export class NetworkSession {
613
661
  /**
614
662
  * Client-side: how the host has answered the binding the last `connect()`
615
663
  * made. Once it has, what the binding carries about the handshake is news
616
- * no longer: the host sends each sync more than once against loss, and a
617
- * host that has finished with our old session takes the new transport for
618
- * a fresh peer and sends its sync before reading the RESUME_HELLO the
619
- * transport carried, so the RESUME_REJECT it answers that with arrives
620
- * after the resync it asks for.
664
+ * no longer: a host that has finished with our old session takes the new
665
+ * transport for a fresh peer and starts its join before reading the
666
+ * RESUME_HELLO the transport carried, so the RESUME_REJECT it answers that
667
+ * with arrives after the resync it asks for.
621
668
  * @private @type {BindingAnswer|string}
622
669
  */
623
670
  #binding_answer = BindingAnswer.Pending;
671
+
672
+ /**
673
+ * Client: the newest sim frame the host has named on this binding — every TIME_DILATION
674
+ * carries it — or -1. A join's pages can take a while to arrive, and the frame a page names
675
+ * is as old as its trip; this is the freshest word of where the host is.
676
+ * @private @type {number}
677
+ */
678
+ #host_frame_seen = -1;
679
+
680
+ /**
681
+ * Client: the host's newest depth feedback — its latest sight of this client's frame minus its
682
+ * own sim frame, measured where this client's input lands — and that latest sight itself (the
683
+ * frame the host named plus the depth). The dilation steers by the depth; the stall rule reads
684
+ * it for what the host's window makes of our input, latency included. 0 and -1 on a new binding.
685
+ * @private @type {number}
686
+ */
687
+ #host_depth_seen = 0;
688
+ /** @private @type {number} */
689
+ #host_marker_seen = -1;
690
+
691
+ /**
692
+ * Client: the peer whose held inbound — action stream and AUTH_STATE, held from `connect()`
693
+ * until the binding's answer is whole — is to be released as the step's inbound is done, or -1.
694
+ * Released there rather than inside the answer's own dispatch: the release applies what was
695
+ * held, through the same receive buffer the answer is still being read from.
696
+ * @private @type {number}
697
+ */
698
+ #inbound_release_peer = -1;
699
+
700
+ /**
701
+ * Client: whether that release completes a join, so {@link onSynced} follows it.
702
+ * @private @type {boolean}
703
+ */
704
+ #inbound_release_syncs = false;
624
705
  /**
625
706
  * Client-side: the first frame predicted on the world this client holds —
626
707
  * where the last INITIAL_SYNC it applied set the frame counter, 0 before
@@ -671,6 +752,14 @@ export class NetworkSession {
671
752
  */
672
753
  onReconnected = new Signal();
673
754
 
755
+ /**
756
+ * Client: a join has completed — the host's FINAL page has landed, every networked entity's
757
+ * state is here, and from now on this client's input is sent. No arguments. A world is sent
758
+ * as pages over as many ticks as its size takes; until this fires it is still arriving.
759
+ * @type {Signal}
760
+ */
761
+ onSynced = new Signal();
762
+
674
763
  /**
675
764
  * Client: the reconnect ladder is exhausted, or the host explicitly
676
765
  * kicked us with a DISCONNECT packet, or `reconnect.enabled` is
@@ -762,12 +851,6 @@ export class NetworkSession {
762
851
  * - `reconnect` (client role): policy for the reconnect ladder. See
763
852
  * {@link DEFAULT_RECONNECT_POLICY}. Set `enabled: false` to opt
764
853
  * out entirely.
765
- * - `publish_changes` (host role, default true): at the end of every
766
- * step, send each replicated component the simulation changed — physics,
767
- * controllers, anything that wrote it directly. Components an action
768
- * wrote are already on the wire and are not sent twice. Publishing never
769
- * writes back into the host's own state. Set false to publish by hand
770
- * (`net_mutate_component`).
771
854
  * - `reconcile` (client role): see {@link NetworkSessionReconcile}. Default
772
855
  * `'correct'`.
773
856
  * - `late_input` (host role): what to do with client input that arrives
@@ -791,7 +874,6 @@ export class NetworkSession {
791
874
  server_resume_grace_ms = DEFAULT_SERVER_RESUME_GRACE_MS,
792
875
  connection_timeout_ms = DEFAULT_CONNECTION_TIMEOUT_MS,
793
876
  reconnect = null,
794
- publish_changes = true,
795
877
  late_input = LateInput.Apply,
796
878
  reconcile = NetworkSessionReconcile.Correct,
797
879
  } = {}) {
@@ -904,7 +986,6 @@ export class NetworkSession {
904
986
  this.#connection_timeout_ms = connection_timeout_ms;
905
987
  this.#reconnect_policy = Object.freeze({ ...DEFAULT_RECONNECT_POLICY, ...(reconnect || {}) });
906
988
  this.#transport_factory = transport_factory;
907
- this.#publish_changes = publish_changes;
908
989
  this.#late_input = late_input;
909
990
  if (reconcile !== NetworkSessionReconcile.Correct && reconcile !== NetworkSessionReconcile.Replay) {
910
991
  throw new Error(`NetworkSession: reconcile must be one of ${Object.values(NetworkSessionReconcile).join(', ')}, got '${reconcile}'`);
@@ -1092,7 +1173,7 @@ export class NetworkSession {
1092
1173
  /**
1093
1174
  * Wire the session to the engine. After this, the EntityObserver
1094
1175
  * is active, the right orchestrator is constructed, and a
1095
- * {@link NetworkSystem} is attached. Subsequent `replicate_*`
1176
+ * {@link NetworkSystem} is attached until {@link stop}. Subsequent `replicate_*`
1096
1177
  * / {@link defineAction} / {@link defineInputSampler} calls throw.
1097
1178
  *
1098
1179
  * Pre-condition: every replicated component class must already have a
@@ -1101,6 +1182,7 @@ export class NetworkSession {
1101
1182
  */
1102
1183
  async start() {
1103
1184
  if (this.#started) throw new Error("NetworkSession.start: already started");
1185
+ if (this.#stopped) throw new Error("NetworkSession.start: a stopped session does not start again; the next match is a new NetworkSession");
1104
1186
 
1105
1187
  // Auto-register the NetworkIdentity adapter so initial-sync can
1106
1188
  // ship it across the wire. Allow caller-supplied registrations
@@ -1156,6 +1238,11 @@ export class NetworkSession {
1156
1238
  replicated_components,
1157
1239
  action_classes,
1158
1240
  frame_capacity: this.frame_capacity,
1241
+ // The session's one clock: render-paced, so the fixed steps one update runs all see
1242
+ // the same time, and a burst of catch-up steps cannot age packets whose
1243
+ // acknowledgements could not have arrived yet.
1244
+ clock: () => this.#clock_ms,
1245
+ tick_period_ms: this.tick_period_ms,
1159
1246
  };
1160
1247
 
1161
1248
  if (this.role === NetworkSessionRole.Host) {
@@ -1171,9 +1258,6 @@ export class NetworkSession {
1171
1258
  identity_class: NetworkIdentity,
1172
1259
  });
1173
1260
  this.#server.onTickComplete.add((sim_frame) => this.#on_host_tick_complete(sim_frame));
1174
- if (this.#publish_changes) {
1175
- this.#server.onBeforeTickClose.add(() => this.#publish_simulated_changes());
1176
- }
1177
1261
  } else {
1178
1262
  // Client role: covers both "playing" (input sampler registered)
1179
1263
  // and "spectator" (no sampler — sampler-driven onPredict
@@ -1204,13 +1288,6 @@ export class NetworkSession {
1204
1288
  // blended component on the first execute after a render pass.
1205
1289
  this.#peer.executor.before_execute = this.#bound_normalize_if_dirty;
1206
1290
 
1207
- if (this.role === NetworkSessionRole.Host && this.#publish_changes) {
1208
- // an action carries its own change to the peers: what it wrote is what they will hold
1209
- this.#peer.executor.on_component_written = (entity_id, component_class) => {
1210
- this.#note_published(entity_id, component_class);
1211
- };
1212
- }
1213
-
1214
1291
  // Secure-by-default inbound authorization on the host: a remote client
1215
1292
  // may only mutate entities it owns (NetworkIdentity.owner_peer_id ===
1216
1293
  // sender); the authoritative server (sender === local_peer_id) may
@@ -1234,11 +1311,12 @@ export class NetworkSession {
1234
1311
  //
1235
1312
  // It is also the session's place in the engine loop: each fixed step opens a network frame
1236
1313
  // and closes it, and every rendered frame runs the session's wall-paced work.
1237
- await this.entity_manager.addSystem(new NetworkSystem(this.#peer, {
1314
+ this.#network_system = new NetworkSystem(this.#peer, {
1238
1315
  open_step: () => this.#open_step(),
1239
1316
  close_step: () => this.#close_step(),
1240
1317
  frame: (dt) => this.#on_frame(dt),
1241
- }));
1318
+ });
1319
+ await this.entity_manager.addSystem(this.#network_system);
1242
1320
 
1243
1321
  // The network timeline: per-frame state of every remote entity, blended at render time on
1244
1322
  // the render-delayed playout by the engine's InterpolationSystem.
@@ -1267,65 +1345,34 @@ export class NetworkSession {
1267
1345
  // action-stream processing, and finalize any in-flight Tier-3
1268
1346
  // resync state.
1269
1347
  if (this.role === NetworkSessionRole.Client) {
1270
- this.#peer.onInitialSync.add((peer_id, session_token, frame_number, buf, payload_end) => {
1271
- if (this.#binding_answer === BindingAnswer.Synced && this.#reconnect_state !== ReconnectState.Resyncing) {
1272
- // A copy: the host sends every sync more than once, against loss, and this
1273
- // binding already has its world. The client has been predicting on it since;
1274
- // applied again, it would put what the client owns back behind input already
1275
- // sent, and seek the frame counter over frames already predicted.
1348
+ this.#peer.onInitialSync.add((peer_id, session_token, frame_number, flags, buf, payload_end) => {
1349
+ const first = (flags & InitialSyncPage.FIRST) !== 0;
1350
+ const final = (flags & InitialSyncPage.FINAL) !== 0;
1351
+
1352
+ if (first) {
1353
+ if (this.#binding_answer !== BindingAnswer.Pending) {
1354
+ // This binding has had its answer — a resume, or a join already under
1355
+ // way or done. The host starts one join per connection, so this is not
1356
+ // ours to act on: the client has been predicting on the world it holds.
1357
+ return;
1358
+ }
1359
+ this.#begin_sync(peer_id, session_token, frame_number);
1360
+ } else if (this.#binding_answer !== BindingAnswer.Syncing) {
1361
+ // a page of a join this binding is not in
1276
1362
  return;
1277
1363
  }
1278
1364
 
1279
- const token = new UUID();
1280
- token.data = session_token;
1281
- this.session_token = token;
1282
-
1283
- /*
1284
- Align to the host's clock. This argument has always been on the wire and in this
1285
- signature -- it was named `_frame_number` and dropped -- and without it a client
1286
- joining a match in progress tags its inputs from 0 against a host at frame 6,000,
1287
- which trims every one of them silently. GAP-042.
1288
-
1289
- The lead is the buffer depth the time dilation is already steering toward: an
1290
- input authored now arrives in the host's future, and this is how far ahead it has
1291
- to be tagged to land in the window rather than behind it. It only has to be in the
1292
- right neighbourhood -- `TimeDilation` converges on the real depth from the host's
1293
- feedback within a second or two -- but starting six thousand frames out is not a
1294
- neighbourhood, which is what made this total rather than approximate.
1295
- */
1296
- this.seek_to_frame(frame_number + 1 + this.#time_dilation.target_buffer_depth);
1297
-
1298
- // The world is about to be the host's. What this client predicted, sampled or was
1299
- // told about the one it had is about a world, and a frame numbering, that is gone.
1300
- this.#prediction_history.clear();
1301
- this.#pending_auth.clear();
1302
- this.#reconciled_frame.clear();
1303
- this.#sampled_actions_per_frame.clear();
1304
-
1305
1365
  this.#apply_initial_sync(buf, payload_end);
1306
- this.#binding_answer = BindingAnswer.Synced;
1307
-
1308
- // Input from here on is predicted on the host's world. What was predicted before —
1309
- // held since this binding was made, or still in the log from before it — was
1310
- // predicted on the world just replaced, and is never sent.
1311
- this.#world_start_frame = this.#local_frame;
1312
- this.#peer.release_outbound(peer_id, this.#world_start_frame);
1313
1366
 
1314
- // Reconnecting counts as well as Resyncing: a host that has
1315
- // finished with our old session readmits us as a fresh peer,
1316
- // so the snapshot arrives with no RESUME_ACCEPT ahead of it.
1317
- // That is the reconnection — staying in the ladder would burn
1318
- // attempts while holding the world the host just sent, and
1319
- // recovering later via RESUME_ACCEPT would report
1320
- // `state_resynced: false` over a world that was wholly
1321
- // replaced. `Lost` is excluded on purpose: the application has
1322
- // already been told that session is over.
1323
- if (this.#reconnect_state === ReconnectState.Resyncing
1324
- || this.#reconnect_state === ReconnectState.Reconnecting) {
1325
- this.#enter_connected();
1326
- this.onReconnected.send1({ state_resynced: true });
1367
+ if (final) {
1368
+ this.#finish_sync(peer_id, frame_number);
1327
1369
  }
1328
1370
  });
1371
+ this.#peer.onTimeDilationFeedback.add((_peer_id, depth, frame_number) => {
1372
+ if (frame_number > this.#host_frame_seen) this.#host_frame_seen = frame_number;
1373
+ this.#host_depth_seen = depth;
1374
+ this.#host_marker_seen = frame_number + depth;
1375
+ });
1329
1376
  // Host's response to our RESUME_HELLO during reconnect.
1330
1377
  this.#peer.onResumeAccept.add((peer_id) => {
1331
1378
  if (this.#binding_answer !== BindingAnswer.Pending) {
@@ -1336,6 +1383,9 @@ export class NetworkSession {
1336
1383
  // The host kept the world this client holds, so what the client predicted on it —
1337
1384
  // while cut off, and since — is owed to the host like any input, late as it is.
1338
1385
  this.#peer.release_outbound(peer_id, this.#world_start_frame);
1386
+ // and what the host sent on it meanwhile is about that world
1387
+ this.#inbound_release_peer = peer_id;
1388
+ this.#inbound_release_syncs = false;
1339
1389
  if (this.#reconnect_state === ReconnectState.Reconnecting
1340
1390
  || this.#reconnect_state === ReconnectState.Resyncing) {
1341
1391
  this.#enter_connected();
@@ -1370,6 +1420,7 @@ export class NetworkSession {
1370
1420
  this.#peer.onDisconnectPacket.add((_peer_id, reason_label) => {
1371
1421
  this.#permanently_lose_connection(reason_label || 'disconnected_by_host');
1372
1422
  });
1423
+ this.#peer.onProtocolError.add((_peer_id, reason) => this.#on_client_protocol_error(reason));
1373
1424
  } else {
1374
1425
  // Host: RESUME_HELLO validation + peer-initiated disconnects.
1375
1426
  this.#peer.onResumeHello.add((peer_id, local_peer_id, last_acked_frame, has_token, session_token) => {
@@ -1380,6 +1431,7 @@ export class NetworkSession {
1380
1431
  // and free state immediately.
1381
1432
  this.drop_peer(peer_id, reason_label || 'client_disconnect');
1382
1433
  });
1434
+ this.#peer.onProtocolError.add((peer_id, reason) => this.#on_peer_protocol_error(peer_id, reason));
1383
1435
  }
1384
1436
 
1385
1437
  // EntityObserver for NetworkIdentity: patches owner/network_id on
@@ -1478,7 +1530,15 @@ export class NetworkSession {
1478
1530
  // snapshot replaces must never reach the host. So what the client predicts meanwhile is
1479
1531
  // held: sent on an accept, dropped on a sync.
1480
1532
  this.#binding_answer = BindingAnswer.Pending;
1533
+ this.#host_frame_seen = -1;
1534
+ this.#host_depth_seen = 0;
1535
+ this.#host_marker_seen = -1;
1481
1536
  this.#peer.hold_outbound(remote_peer_id);
1537
+ // Inbound too, until the answer is whole: an action stream that arrives ahead of it —
1538
+ // a join's stream can, when the pages are lost on the way — is about the world the
1539
+ // answer settles, a resumed one or a new one, and is applied once it is settled.
1540
+ this.#peer.hold_inbound(remote_peer_id);
1541
+ this.#inbound_release_peer = -1;
1482
1542
 
1483
1543
  // Announce ourselves whenever this connect could be a claim on an
1484
1544
  // id the host still holds a record for: we have a token to prove
@@ -1499,6 +1559,9 @@ export class NetworkSession {
1499
1559
  remote_peer_id, this.local_peer_id, last_acked,
1500
1560
  token !== null, token !== null ? token.data : NO_SESSION_TOKEN,
1501
1561
  );
1562
+ // Out at once rather than with the next tick: a connect is an event, not part of
1563
+ // a frame, and the host can answer nothing until it has heard the claim.
1564
+ this.#peer.flush_peer(remote_peer_id);
1502
1565
  }
1503
1566
  this.#has_ever_connected = true;
1504
1567
  }
@@ -1542,6 +1605,7 @@ export class NetworkSession {
1542
1605
  // Best-effort notification — peer may have already dropped.
1543
1606
  try {
1544
1607
  this.#peer.send_disconnect(peer_id, reason);
1608
+ this.#peer.flush_peer(peer_id);
1545
1609
  } catch (_) { /* swallow */
1546
1610
  }
1547
1611
  this.#server.disconnect_peer(peer_id);
@@ -1551,10 +1615,23 @@ export class NetworkSession {
1551
1615
  this.#peer_inbound_mark.delete(peer_id);
1552
1616
  this.#peer_grace_state.delete(peer_id);
1553
1617
  this.#peer_session_tokens.delete(peer_id);
1554
- this.#pending_initial_sync.delete(peer_id);
1618
+ this.#joins.delete(peer_id);
1555
1619
  this.onPeerPermanentlyDropped.send2(peer_id, reason);
1556
1620
  }
1557
1621
 
1622
+ /**
1623
+ * Send what is queued for `peer_id` — a kick's RESUME_REJECT and DISCONNECT — before its
1624
+ * connection is torn down. One best-effort flush: a transport that refuses it (closed, say)
1625
+ * changes nothing about the teardown that follows.
1626
+ * @param {number} peer_id
1627
+ */
1628
+ #flush_last_word(peer_id) {
1629
+ try {
1630
+ this.#peer.flush_peer(peer_id);
1631
+ } catch (_) { /* swallow */
1632
+ }
1633
+ }
1634
+
1558
1635
  /**
1559
1636
  * Client-only: voluntarily disconnect from the server. Sends a
1560
1637
  * DISCONNECT packet so the host can free state immediately and
@@ -1570,6 +1647,7 @@ export class NetworkSession {
1570
1647
  const peer_id = this.#remote_peer_id;
1571
1648
  try {
1572
1649
  this.#peer.send_disconnect(peer_id, reason);
1650
+ this.#peer.flush_peer(peer_id);
1573
1651
  } catch (_) { /* swallow */
1574
1652
  }
1575
1653
  // Tear down the channel binding so a subsequent connect() with a
@@ -1598,9 +1676,19 @@ export class NetworkSession {
1598
1676
 
1599
1677
  /**
1600
1678
  * Tear down. Idempotent — safe to call from cleanup paths.
1679
+ *
1680
+ * Takes back from the engine what {@link start} put in it: the {@link NetworkSystem} is out of
1681
+ * the engine's loop when this returns, so another session can start on the same engine; this one
1682
+ * does not start again. The
1683
+ * networked entities stay in the world, and their `network_id`s are released — an id means
1684
+ * nothing without the session that gave it. The {@link InterpolationSystem} stays too, whoever
1685
+ * added it: it is the engine's, and blends whatever else is interpolated; the session only
1686
+ * withdraws its own source from it.
1687
+ *
1688
+ * @returns {Promise} resolves once the engine has shut the {@link NetworkSystem} down
1601
1689
  */
1602
1690
  stop() {
1603
- if (!this.#started) return;
1691
+ if (!this.#started) return Promise.resolve();
1604
1692
 
1605
1693
  this.entity_manager.fixedStepTimeScale = 1;
1606
1694
 
@@ -1634,6 +1722,8 @@ export class NetworkSession {
1634
1722
  }
1635
1723
  this.#event_listeners.clear();
1636
1724
  this.#remote_entities.clear();
1725
+ this.#owned_by.clear();
1726
+ this.#owner_of.clear();
1637
1727
  this.#sampled_actions_per_frame.clear();
1638
1728
  this.#peer_inbound_mark.clear();
1639
1729
  for (const transport of this.#inbound.values()) {
@@ -1641,6 +1731,13 @@ export class NetworkSession {
1641
1731
  }
1642
1732
  this.#inbound.clear();
1643
1733
  this.#started = false;
1734
+ this.#stopped = true;
1735
+
1736
+ // last: removing the system unlinks every networked entity, and nothing of the session's
1737
+ // may still be listening to that
1738
+ const network_system = this.#network_system;
1739
+ this.#network_system = null;
1740
+ return this.entity_manager.removeSystem(network_system);
1644
1741
  }
1645
1742
 
1646
1743
  // ----------------------------------------------------------------
@@ -1679,6 +1776,11 @@ export class NetworkSession {
1679
1776
  this.#interpolation.settle();
1680
1777
 
1681
1778
  this.#flush_inbound();
1779
+ this.#release_held_inbound();
1780
+
1781
+ if (this.#client !== null) {
1782
+ this.#retake_host_time_if_stalled();
1783
+ }
1682
1784
 
1683
1785
  this.#apply_pending_interpolation();
1684
1786
 
@@ -1710,11 +1812,12 @@ export class NetworkSession {
1710
1812
 
1711
1813
  try {
1712
1814
  if (this.#server !== null) {
1713
- // runs the frame's local sim, then publishes (onBeforeTickClose), then closes it
1815
+ // runs the frame's local sim, then closes it
1714
1816
  this.#server.end_tick();
1715
1817
  } else {
1716
- this.#client.end_tick();
1818
+ // queued before end_tick, which sends: the marker leaves with the tick's stream
1717
1819
  this.#send_frame_marker(this.#local_frame);
1820
+ this.#client.end_tick();
1718
1821
  if (this.#reconcile === NetworkSessionReconcile.Correct) {
1719
1822
  this.#record_prediction(this.#local_frame);
1720
1823
  }
@@ -1769,102 +1872,6 @@ export class NetworkSession {
1769
1872
  return deferred;
1770
1873
  }
1771
1874
 
1772
- /**
1773
- * Host, end of step: record every replicated component whose wire image differs from what the
1774
- * peers hold, into the still-open frame, for the action stream to carry. Recorded, not
1775
- * executed — see {@link SimActionExecutor#record}. A component an action wrote this step and
1776
- * nothing changed since is not sent again: the peers apply the action.
1777
- */
1778
- #publish_simulated_changes() {
1779
- const world = this.world;
1780
- const executor = this.#peer.executor;
1781
- const scratch = this.#scratch_publish_buf;
1782
- const changed_entities = executor.changed_entities;
1783
-
1784
- for (const entity_id of this.#event_listeners.keys()) {
1785
- const identity = world.getComponent(entity_id, NetworkIdentity);
1786
- if (identity === undefined || identity.network_id < 0) continue;
1787
-
1788
- for (const [ComponentClass, desc] of this.#replicated) {
1789
- if (ComponentClass === NetworkIdentity) continue;
1790
-
1791
- const live = world.getComponent(entity_id, ComponentClass);
1792
- if (live === undefined) continue;
1793
-
1794
- const type_id = desc.interp_type_id;
1795
-
1796
- scratch.position = 0;
1797
- this.binary_registry.getAdapter(ComponentClass.typeName).serialize(scratch, live);
1798
- const length = scratch.position;
1799
-
1800
- if (!this.#remember_published(entity_id, type_id, scratch.raw_bytes, length)) continue;
1801
-
1802
- const action = this.#peer.action_registry.acquire(this.#ReplaceComponentAction);
1803
- try {
1804
- action.network_id = identity.network_id;
1805
- action.component_type_id = type_id;
1806
- action.payload_buf.position = 0;
1807
- action.payload_buf.writeBytes(scratch.raw_bytes, 0, length);
1808
- action.payload_length = length;
1809
- executor.record(action);
1810
- } finally {
1811
- this.#peer.action_registry.release(action);
1812
- }
1813
-
1814
- if (changed_entities !== null) changed_entities.add(identity.network_id);
1815
- }
1816
- }
1817
- }
1818
-
1819
- /**
1820
- * Host: an action wrote `ComponentClass` of `entity_id`; the peers will hold what it wrote.
1821
- * @param {number} entity_id
1822
- * @param {Function} ComponentClass
1823
- */
1824
- #note_published(entity_id, ComponentClass) {
1825
- const desc = this.#replicated.get(ComponentClass);
1826
- if (desc === undefined || ComponentClass === NetworkIdentity) return;
1827
-
1828
- const live = this.world.getComponent(entity_id, ComponentClass);
1829
- if (live === undefined) return;
1830
-
1831
- const scratch = this.#scratch_publish_buf;
1832
- scratch.position = 0;
1833
- this.binary_registry.getAdapter(ComponentClass.typeName).serialize(scratch, live);
1834
- this.#remember_published(entity_id, desc.interp_type_id, scratch.raw_bytes, scratch.position);
1835
- }
1836
-
1837
- /**
1838
- * Set what the peers hold for `(entity_id, type_id)`.
1839
- * @param {number} entity_id
1840
- * @param {number} type_id
1841
- * @param {Uint8Array} bytes
1842
- * @param {number} length
1843
- * @returns {boolean} whether it differs from what was held before
1844
- */
1845
- #remember_published(entity_id, type_id, bytes, length) {
1846
- let published = this.#published.get(entity_id);
1847
- if (published === undefined) {
1848
- published = [];
1849
- this.#published.set(entity_id, published);
1850
- }
1851
- while (published.length <= type_id) published.push(null);
1852
-
1853
- let held = published[type_id];
1854
- if (held !== null && held.length === length && bytes_equal(bytes, held.bytes, length)) {
1855
- return false;
1856
- }
1857
-
1858
- if (held === null || held.bytes.length < length) {
1859
- held = { bytes: new Uint8Array(Math.max(length, 32)), length: 0 };
1860
- published[type_id] = held;
1861
- }
1862
- held.bytes.set(bytes.subarray(0, length), 0);
1863
- held.length = length;
1864
-
1865
- return true;
1866
- }
1867
-
1868
1875
  /**
1869
1876
  * Once per rendered frame: the work paced by wall time rather than by the simulation.
1870
1877
  * @param {number} dt_seconds
@@ -2056,9 +2063,9 @@ export class NetworkSession {
2056
2063
  * frame 6,000; a client has to start tagging its inputs near 6,000 or the host discards every
2057
2064
  * one of them — `ServerAuthoritativeServer.tick` trims pending actions older than
2058
2065
  * `sim_frame - frame_capacity + 1`, and a client tagging frame 0 against a host at 6,000 has
2059
- * every input dropped **silently**. It moves on its own screen, never moves on the host's, and
2060
- * no signal fires: `onPendingActionDropped` covers the future-lead and capacity bounds, not
2061
- * this one.
2066
+ * every input dropped. It moves on its own screen and never moves on the host's — and until
2067
+ * 2026-10-15 no signal fired; the host's `onPendingActionDropped` now reports each such drop
2068
+ * as `'window'`.
2062
2069
  *
2063
2070
  * Everything needed was already there except the setter. INITIAL_SYNC carries the host's frame,
2064
2071
  * `NetworkPeer` writes it, and `onInitialSync` dispatches it — and the handler named it
@@ -2067,8 +2074,10 @@ export class NetworkSession {
2067
2074
  * with the input sampler silenced until its counter caught up: 28,801 calls and 62 ms of wall
2068
2075
  * clock for an hour-old host. Reported as GAP-042.
2069
2076
  *
2070
- * A client now seeks automatically on INITIAL_SYNC. This is the manual door, for a caller that
2071
- * computes its own lead.
2077
+ * A client now seeks automatically on INITIAL_SYNC, and again whenever it finds itself more than
2078
+ * the host's input window behind the frame the host last named — a stall, see
2079
+ * {@link #retake_host_time_if_stalled}. This is the manual door, for a caller that computes its
2080
+ * own lead.
2072
2081
  *
2073
2082
  * A client sends the host nothing it predicted before its last sync, and knows it by frame: its
2074
2083
  * input starts at the frame the sync sought to. A caller whose own lead is shorter moves that
@@ -2109,6 +2118,7 @@ export class NetworkSession {
2109
2118
  if (identity.owner_peer_id !== this.local_peer_id) {
2110
2119
  this.#remote_entities.add(entity_id);
2111
2120
  }
2121
+ this.#file_owner(entity_id, identity.owner_peer_id);
2112
2122
  this.#interpolation_pending.add(entity_id);
2113
2123
  // Per-entity event listener for net_mutate_component.
2114
2124
  const listener = (payload) => this.#on_net_mutate_component(entity_id, payload);
@@ -2123,23 +2133,26 @@ export class NetworkSession {
2123
2133
  this.#event_listeners.delete(entity_id);
2124
2134
  }
2125
2135
  this.#remote_entities.delete(entity_id);
2126
- this.#published.delete(entity_id);
2136
+ this.#file_owner(entity_id, -1);
2127
2137
  this.#prediction_history.forget(entity_id);
2128
2138
  // take back our Interpolated at the next step or frame, if the entity outlives its identity
2129
2139
  this.#interpolation_pending.add(entity_id);
2130
2140
  }
2131
2141
 
2132
2142
  /**
2133
- * Handler for `"net_mutate_component"` events. Translates the payload
2134
- * into a `ReplaceComponentAction` and dispatches via the executor.
2143
+ * Handler for `"net_mutate_component"` events: a replicated component changed outside an
2144
+ * action, and goes through the executor as a `ReplaceComponentAction`. The session finds no
2145
+ * change by itself (see the README, "What travels"); this is the door for a system that writes
2146
+ * replicated state directly — physics publishing its moving bodies, say — at a cost per
2147
+ * change, never per entity.
2135
2148
  *
2136
2149
  * Payload:
2137
- * - `{ component_type, new_state }` — recommended; engine captures
2138
- * current bytes as prior, then applies `new_state` via the
2139
- * replication adapter.
2140
- * - `{ component_type }` — caller has already mutated live; prior
2141
- * bytes equal post bytes (rewind for this mutation is a no-op).
2142
- *
2150
+ * - `{ component_type, new_state }` — the engine captures the current bytes as prior, then
2151
+ * applies `new_state` through the replication adapter.
2152
+ * - `{ component_type }` — the caller has already written live. On the host it is recorded,
2153
+ * not executed ({@link SimActionExecutor#record}): live is the truth, at full precision,
2154
+ * and executing would write the wire's quantised image back over it. On a client it is
2155
+ * executed, so prediction and rewind keep their prior state.
2143
2156
  */
2144
2157
  #on_net_mutate_component(entity_id, payload) {
2145
2158
  const { component_type, new_state } = payload || {};
@@ -2174,8 +2187,15 @@ export class NetworkSession {
2174
2187
  }
2175
2188
  action.payload_length = action.payload_buf.position;
2176
2189
 
2190
+ const executor = this.#peer.executor;
2177
2191
  try {
2178
- this.#peer.executor.execute(action, this.local_peer_id);
2192
+ if (this.#server !== null && (new_state === undefined || new_state === null)) {
2193
+ executor.record(action);
2194
+ // recorded writes are changes too: a join re-sends an entity changed after its page
2195
+ if (executor.changed_entities !== null) executor.changed_entities.add(network_id);
2196
+ } else {
2197
+ executor.execute(action, this.local_peer_id);
2198
+ }
2179
2199
  } finally {
2180
2200
  this.#peer.action_registry.release(action);
2181
2201
  }
@@ -2280,41 +2300,31 @@ export class NetworkSession {
2280
2300
  * @param {number} sim_frame
2281
2301
  */
2282
2302
  #on_host_tick_complete(sim_frame) {
2283
- // Initial-sync first, so receivers have a populated world before
2284
- // AUTH_STATE / action-stream packets land.
2285
- if (this.#pending_initial_sync.size > 0) {
2286
-
2287
- for (const [peer_id, sends_remaining] of this.#pending_initial_sync) {
2288
-
2289
- this.#send_initial_sync_to(peer_id, sim_frame);
2290
-
2291
- if (sends_remaining <= 1) {
2292
- this.#pending_initial_sync.delete(peer_id);
2293
- } else {
2294
- this.#pending_initial_sync.set(peer_id, sends_remaining - 1);
2303
+ // Joins first: a page is the state at this frame, which has just closed.
2304
+ if (this.#joins.size > 0) {
2305
+ for (const [peer_id, join] of this.#joins) {
2306
+ const progress = join.advance(sim_frame);
2307
+ if (progress === JoinProgress.Done) {
2308
+ this.#joins.delete(peer_id);
2309
+ } else if (progress === JoinProgress.DidNotConverge) {
2310
+ this.drop_peer(peer_id, 'join_did_not_converge');
2295
2311
  }
2296
-
2297
2312
  }
2298
-
2299
2313
  }
2300
2314
 
2301
2315
  // AUTH_STATE per owned entity, then time-dilation feedback, to
2302
- // every connected peer.
2316
+ // every connected peer. A peer still joining gets no AUTH_STATE: it
2317
+ // does not hold the world the state would correct yet.
2303
2318
  for (const peer_id of this.#connected_peers) {
2304
2319
 
2305
- for (const entity_id of this.#event_listeners.keys()) {
2320
+ const owned = this.#joins.has(peer_id) ? undefined : this.#owned_by.get(peer_id);
2306
2321
 
2307
- const identity = this.world.getComponent(entity_id, NetworkIdentity);
2308
-
2309
- if (identity === undefined) {
2310
- continue;
2322
+ if (owned !== undefined) {
2323
+ for (const entity_id of owned) {
2324
+ const identity = this.world.getComponent(entity_id, NetworkIdentity);
2325
+ if (identity === undefined) continue;
2326
+ this.#send_auth_state_for_entity(peer_id, sim_frame, entity_id, identity);
2311
2327
  }
2312
- if (identity.owner_peer_id !== peer_id) {
2313
- continue;
2314
- }
2315
-
2316
- this.#send_auth_state_for_entity(peer_id, sim_frame, entity_id, identity);
2317
-
2318
2328
  }
2319
2329
 
2320
2330
  this.#server.send_time_dilation_feedback(peer_id);
@@ -2323,49 +2333,215 @@ export class NetworkSession {
2323
2333
 
2324
2334
 
2325
2335
  /**
2326
- * Queue `peer_id`'s INITIAL_SYNC, unless its connection has been given
2327
- * one already — queued, or queued and sent. These are the host's only
2328
- * full-world sends, so how many a connection gets is the host's decision:
2329
- * one, when it takes the connection for a fresh peer — as `connect()`
2330
- * binds it, or after refusing its claim to resume. Nothing the peer sends
2331
- * afterwards earns it another.
2336
+ * Start `peer_id`'s join, unless its connection has been given one already — started, or
2337
+ * finished. These are the host's only whole-world sends, so how many a connection gets is the
2338
+ * host's decision: one, when it takes the connection for a fresh peer — as `connect()` binds
2339
+ * it, or after refusing its claim to resume. Nothing the peer sends afterwards earns it another.
2340
+ *
2341
+ * The peer's action stream is held from here until the join's FINAL page: what it would carry
2342
+ * is about a world the peer does not hold yet. The session token is issued here, and goes out
2343
+ * with every page; the receiver echoes it back via RESUME_HELLO to authenticate a later
2344
+ * reconnect.
2332
2345
  * @param {number} peer_id
2333
2346
  */
2334
2347
  #queue_initial_sync(peer_id) {
2335
2348
  if (this.#initial_sync_given.has(peer_id)) return;
2336
2349
  this.#initial_sync_given.add(peer_id);
2337
- this.#pending_initial_sync.set(peer_id, 2);
2350
+
2351
+ let token = this.#peer_session_tokens.get(peer_id);
2352
+ if (token === undefined) {
2353
+ token = UUID.v1();
2354
+ this.#peer_session_tokens.set(peer_id, token);
2355
+ }
2356
+
2357
+ this.#peer.hold_outbound(peer_id);
2358
+ this.#joins.set(peer_id, new HostJoin({
2359
+ peer: this.#peer,
2360
+ peer_id,
2361
+ session_token: token.data,
2362
+ world: this.world,
2363
+ for_each_entity: this.#for_each_networked_entity,
2364
+ page_buffer: this.#page_buffer,
2365
+ window_bytes: 128 * 1024,
2366
+ }));
2338
2367
  }
2339
2368
 
2340
2369
  /**
2341
- * Emit a Snapshotter-format payload to a freshly-connected peer.
2342
- * Generates and stashes a UUID v1 session token on first send,
2343
- * which the receiver echoes back via RESUME_HELLO to authenticate
2344
- * later reconnects.
2370
+ * Visits every networked entity: those whose NetworkIdentity the session is listening to.
2371
+ * @type {function(function(number): void): void}
2372
+ */
2373
+ #for_each_networked_entity = (visit) => {
2374
+ for (const entity_id of this.#event_listeners.keys()) {
2375
+ visit(entity_id);
2376
+ }
2377
+ };
2378
+
2379
+ /**
2380
+ * Client: the first page of a join. It is this binding's answer: the world is about to be the
2381
+ * host's, so what was predicted, sampled or told about the one the client had goes, and the
2382
+ * reconnect ladder stops — a join of a large world takes a while, and an attempt that tore the
2383
+ * binding down would restart it from the first page.
2384
+ *
2345
2385
  * @param {number} peer_id
2346
- * @param {number} sim_frame
2386
+ * @param {Uint8Array} session_token
2387
+ * @param {number} frame_number the frame the page's state is from
2388
+ */
2389
+ #begin_sync(peer_id, session_token, frame_number) {
2390
+ const token = new UUID();
2391
+ token.data = session_token;
2392
+ this.session_token = token;
2393
+
2394
+ this.#align_to_host(frame_number);
2395
+ this.#forget_predicted_world();
2396
+ this.#binding_answer = BindingAnswer.Syncing;
2397
+ // The host releases its action stream, and its AUTH_STATE, from the frame after the last
2398
+ // page; their datagrams can overtake the pages. The binding's inbound hold, taken at
2399
+ // `connect()`, keeps them until the world is whole.
2400
+
2401
+ // Reconnecting counts as well as Resyncing: a host that has
2402
+ // finished with our old session readmits us as a fresh peer,
2403
+ // so the snapshot arrives with no RESUME_ACCEPT ahead of it.
2404
+ // That is the reconnection — staying in the ladder would burn
2405
+ // attempts while holding the world the host is sending, and
2406
+ // recovering later via RESUME_ACCEPT would report
2407
+ // `state_resynced: false` over a world that was wholly
2408
+ // replaced. `Lost` is excluded on purpose: the application has
2409
+ // already been told that session is over.
2410
+ if (this.#reconnect_state === ReconnectState.Resyncing
2411
+ || this.#reconnect_state === ReconnectState.Reconnecting) {
2412
+ this.#enter_connected();
2413
+ this.onReconnected.send1({ state_resynced: true });
2414
+ }
2415
+ }
2416
+
2417
+ /**
2418
+ * Client: the FINAL page. Every entity's state at `frame_number` is here, and the host's action
2419
+ * stream resumes after it. The client predicted on a partial world while the pages came in, and
2420
+ * against the frame the first page named, which is as old as the join is long: both are put
2421
+ * right here, and only then does the client's input go out.
2422
+ *
2423
+ * @param {number} peer_id
2424
+ * @param {number} frame_number
2347
2425
  */
2348
- #send_initial_sync_to(peer_id, sim_frame) {
2349
- let token = this.#peer_session_tokens.get(peer_id);
2426
+ #finish_sync(peer_id, frame_number) {
2427
+ this.#align_to_host(frame_number);
2428
+ this.#forget_predicted_world();
2429
+ this.#binding_answer = BindingAnswer.Synced;
2350
2430
 
2351
- if (token === undefined) {
2352
- token = UUID.v1();
2353
- this.#peer_session_tokens.set(peer_id, token);
2431
+ // Input from here on is predicted on the host's world. What was predicted before —
2432
+ // held since this binding was made, or still in the log from before it — was
2433
+ // predicted on a world that is gone, or half-built, and is never sent.
2434
+ this.#world_start_frame = this.#local_frame;
2435
+ this.#peer.release_outbound(peer_id, this.#world_start_frame);
2436
+
2437
+ // what the host sent after the last page, applied as the step's inbound is done, now that
2438
+ // the world it is about is here; then onSynced
2439
+ this.#inbound_release_peer = peer_id;
2440
+ this.#inbound_release_syncs = true;
2441
+ }
2442
+
2443
+ /**
2444
+ * Client: apply what the binding held, once its answer is whole (see {@link #inbound_release_peer}).
2445
+ */
2446
+ #release_held_inbound() {
2447
+ const peer_id = this.#inbound_release_peer;
2448
+ if (peer_id < 0) return;
2449
+ this.#inbound_release_peer = -1;
2450
+ if (!this.#peer.is_connected(peer_id)) return;
2451
+ this.#peer.release_inbound(peer_id);
2452
+ if (this.#inbound_release_syncs) {
2453
+ this.#inbound_release_syncs = false;
2454
+ this.onSynced.send0();
2354
2455
  }
2456
+ }
2355
2457
 
2356
- this.#peer.send_initial_sync(peer_id, token.data, sim_frame, (buf) => {
2357
- snapshotter_emit({
2358
- buffer: buf,
2359
- world: this.world,
2360
- slot_table: this.#peer.slot_table,
2361
- component_registry: this.#peer.component_registry,
2362
- entity_iter: (cb) => {
2363
- for (const entity_id of this.#event_listeners.keys()) {
2364
- cb(entity_id);
2365
- }
2366
- },
2367
- });
2368
- });
2458
+ /**
2459
+ * Seek the frame counter to the host's `frame_number` — or the newer frame a TIME_DILATION
2460
+ * has named since — plus the lead input needs.
2461
+ *
2462
+ * The frame has always been on the wire — it was named `_frame_number` and dropped — and
2463
+ * without it a client joining a match in progress tags its inputs from 0 against a host at
2464
+ * frame 6,000, which trims every one of them silently. GAP-042.
2465
+ *
2466
+ * The lead is the buffer depth the time dilation is already steering toward: an input
2467
+ * authored now arrives in the host's future, and this is how far ahead it has to be tagged to
2468
+ * land in the window rather than behind it. It only has to be in the right neighbourhood —
2469
+ * `TimeDilation` converges on the real depth from the host's feedback within a second or two —
2470
+ * but starting six thousand frames out is not a neighbourhood, which is what made this total
2471
+ * rather than approximate.
2472
+ *
2473
+ * @param {number} frame_number
2474
+ */
2475
+ #align_to_host(frame_number) {
2476
+ const host_frame = Math.max(frame_number, this.#host_frame_seen);
2477
+ this.seek_to_frame(host_frame + 1 + this.#time_dilation.target_buffer_depth);
2478
+ }
2479
+
2480
+ /**
2481
+ * Drop what this client predicted, sampled or was told about the world it held: it is about a
2482
+ * world, and a frame numbering, that is being replaced.
2483
+ */
2484
+ #forget_predicted_world() {
2485
+ this.#prediction_history.clear();
2486
+ this.#pending_auth.clear();
2487
+ this.#reconciled_frame.clear();
2488
+ this.#sampled_actions_per_frame.clear();
2489
+ this.#client.forget_timeline();
2490
+ }
2491
+
2492
+ /**
2493
+ * Client, as a step opens: more than the host's input window behind, the client re-takes the
2494
+ * host's time.
2495
+ *
2496
+ * A client stops stepping when its tab goes to the background (`Ticker` suspends, and discards
2497
+ * the gap when the tab returns), under a debugger, or with the lid shut; the host runs on, and
2498
+ * what it sent meanwhile is waiting in the held transport. Nothing told the session. Behind by N
2499
+ * frames, the time dilation closed the gap at 5% — 20 × N frames, 190 s for a 10 s stall — and
2500
+ * until the gap was inside the window `ServerAuthoritativeServer.begin_tick` trimmed every input
2501
+ * the client sent, silently: the client's own entities ran off on its screen, and were yanked
2502
+ * back by the corrections once its input landed again. Measured 2026-10-15; the spec is
2503
+ * `NetworkSession.stall.spec.js`.
2504
+ *
2505
+ * The host names its frame in every TIME_DILATION packet (`#host_frame_seen`), so the gap is
2506
+ * known the moment the held packets are applied. Past the window — where the host drops input
2507
+ * rather than applying it late — it is a discontinuity, not something to steer: the counter is
2508
+ * re-taken as INITIAL_SYNC takes it, and what was predicted on the old numbering is dropped.
2509
+ * Inside the window the dilation is left to it, with the input applied late meanwhile (a 31-frame
2510
+ * stall costs 9.8 s of that). The window is the host's; the client's own `frame_capacity` stands
2511
+ * for it here, the host never names it, and the two default alike.
2512
+ *
2513
+ * The gap is exact only at zero latency: the frame the host named is a one-way trip old, and
2514
+ * input tagged now lands a one-way trip later, so a client behind by a little less than the
2515
+ * window as it sees it is behind by more than the window as the host does — every input
2516
+ * dropped, and the gap never says so, for the 20 × 2 × latency frames the dilation takes to
2517
+ * close the band (2 s at 50 ms one way). The host's depth feedback says so exactly: it is its
2518
+ * latest sight of our frame minus its sim frame, measured where our input lands, latency
2519
+ * included. At or past the window's depth the host is dropping our input, and the client
2520
+ * re-takes its time — once the host has seen input from the time last taken, since the reports
2521
+ * in flight after a re-align still carry the old depth.
2522
+ *
2523
+ * Keyed on the gap and on the host's word, never on the page's visibility: a debugger pause, a
2524
+ * GC stall and a sleeping laptop look the same from here, and the session never sees the ticker.
2525
+ */
2526
+ #retake_host_time_if_stalled() {
2527
+ if (this.#binding_answer !== BindingAnswer.Synced && this.#binding_answer !== BindingAnswer.Resumed) return;
2528
+
2529
+ const behind = this.#host_frame_seen - this.#local_frame;
2530
+ const trimmed = this.#host_depth_seen <= -this.frame_capacity
2531
+ && this.#host_marker_seen >= this.#world_start_frame;
2532
+ if (behind < this.frame_capacity && !trimmed) return;
2533
+
2534
+ this.stall_count++;
2535
+
2536
+ this.#align_to_host(this.#host_frame_seen);
2537
+ this.#forget_predicted_world();
2538
+
2539
+ // Input from here on is predicted on the host's time. What was predicted before it is below
2540
+ // the host's window and would be trimmed on arrival, so it is not sent.
2541
+ this.#world_start_frame = this.#local_frame;
2542
+ if (this.#peer.is_connected(this.#remote_peer_id)) {
2543
+ this.#peer.release_outbound(this.#remote_peer_id, this.#world_start_frame);
2544
+ }
2369
2545
  }
2370
2546
 
2371
2547
  /**
@@ -2382,7 +2558,7 @@ export class NetworkSession {
2382
2558
  // written over the truth, not over a render blend that a restore would then put back
2383
2559
  this.normalize_if_dirty();
2384
2560
 
2385
- const entity_count = buffer.readUintVar();
2561
+ const entity_count = buffer.readUint32();
2386
2562
  for (let i = 0; i < entity_count; i++) {
2387
2563
  const network_id = buffer.readUintVar();
2388
2564
  const component_count = buffer.readUint8();
@@ -2471,9 +2647,35 @@ export class NetworkSession {
2471
2647
  } else {
2472
2648
  this.#remote_entities.add(entity_id);
2473
2649
  }
2650
+ this.#file_owner(entity_id, owner_peer_id);
2474
2651
  this.#interpolation_pending.add(entity_id);
2475
2652
  }
2476
2653
 
2654
+ /**
2655
+ * File `entity_id` under `owner_peer_id` in {@link #owned_by}, out of wherever it was; -1 files
2656
+ * it nowhere.
2657
+ * @param {number} entity_id
2658
+ * @param {number} owner_peer_id
2659
+ */
2660
+ #file_owner(entity_id, owner_peer_id) {
2661
+ const previous = this.#owner_of.get(entity_id);
2662
+ if (previous === owner_peer_id) return;
2663
+ if (previous !== undefined) {
2664
+ const set = this.#owned_by.get(previous);
2665
+ set.delete(entity_id);
2666
+ if (set.size === 0) this.#owned_by.delete(previous);
2667
+ this.#owner_of.delete(entity_id);
2668
+ }
2669
+ if (owner_peer_id < 0) return;
2670
+ let set = this.#owned_by.get(owner_peer_id);
2671
+ if (set === undefined) {
2672
+ set = new Set();
2673
+ this.#owned_by.set(owner_peer_id, set);
2674
+ }
2675
+ set.add(entity_id);
2676
+ this.#owner_of.set(entity_id, owner_peer_id);
2677
+ }
2678
+
2477
2679
  /**
2478
2680
  *
2479
2681
  * @param {number} peer_id
@@ -2551,7 +2753,7 @@ export class NetworkSession {
2551
2753
  // session. Both calls are no-ops when there is nothing to release.
2552
2754
  this.#server.disconnect_peer(peer_id);
2553
2755
  this.#unwire_peer_transport_disconnect(peer_id);
2554
- this.#pending_initial_sync.delete(peer_id);
2756
+ this.#joins.delete(peer_id);
2555
2757
  return;
2556
2758
  }
2557
2759
  const last_acked = this.#peer.baseline.last_acked(peer_id);
@@ -2563,15 +2765,32 @@ export class NetworkSession {
2563
2765
  // from `#on_host_tick_complete` and throws — before AUTH_STATE and
2564
2766
  // time-dilation feedback go out to *any* peer, and without ever
2565
2767
  // draining the entry, so every later tick throws the same way.
2566
- this.#pending_initial_sync.delete(peer_id);
2768
+ // a peer that drops before its join finished holds half a world: it may not resume it
2769
+ const join_complete = !this.#joins.has(peer_id);
2770
+ this.#joins.delete(peer_id);
2567
2771
  this.#unwire_peer_transport_disconnect(peer_id);
2568
2772
  this.#peer_grace_state.set(peer_id, {
2569
2773
  deadline_ms: this.#clock_ms + this.#server_resume_grace_ms,
2570
2774
  last_acked,
2775
+ join_complete,
2571
2776
  });
2572
2777
  this.onPeerLost.send2(peer_id, reason);
2573
2778
  }
2574
2779
 
2780
+ /**
2781
+ * Host: `peer_id`'s connection is no longer whole (see Connection.onProtocolError) and can
2782
+ * carry nothing more. Handled as its transport dropping — the peer goes into the grace window
2783
+ * and may resume on a new connection — and the transport is closed, so the client hears of it
2784
+ * now rather than when silence reaps it.
2785
+ * @param {number} peer_id
2786
+ * @param {string} reason
2787
+ */
2788
+ #on_peer_protocol_error(peer_id, reason) {
2789
+ const entry = this.#peer_transport_listeners.get(peer_id);
2790
+ this.#on_peer_transport_disconnected(peer_id, `protocol_error: ${reason}`);
2791
+ if (entry !== undefined) close_transport(entry.transport);
2792
+ }
2793
+
2575
2794
  /**
2576
2795
  * Validate a RESUME_HELLO and either welcome the peer back (restore
2577
2796
  * baseline + send RESUME_ACCEPT) or reject with a reason code. A claim
@@ -2603,6 +2822,7 @@ export class NetworkSession {
2603
2822
  if (transport_peer_id !== claimed_peer_id) {
2604
2823
  this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.PeerIdCollision);
2605
2824
  this.#peer.send_disconnect(transport_peer_id, 'peer_id_collision');
2825
+ this.#flush_last_word(transport_peer_id);
2606
2826
  this.#server.disconnect_peer(transport_peer_id);
2607
2827
  this.#unwire_peer_transport_disconnect(transport_peer_id);
2608
2828
  // The transport may have been added to the live set in connect()
@@ -2611,7 +2831,7 @@ export class NetworkSession {
2611
2831
  // serializes AUTH_STATE / time-dilation to a now-disposed conn and
2612
2832
  // throws "peer not connected", taking down the tick for all peers.
2613
2833
  this.#connected_peers.delete(transport_peer_id);
2614
- this.#pending_initial_sync.delete(transport_peer_id);
2834
+ this.#joins.delete(transport_peer_id);
2615
2835
  this.#peer_inbound_mark.delete(transport_peer_id);
2616
2836
  return;
2617
2837
  }
@@ -2639,6 +2859,7 @@ export class NetworkSession {
2639
2859
  // INITIAL_SYNC queue.
2640
2860
  this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.NoSessionToken);
2641
2861
  this.#peer.send_disconnect(transport_peer_id, 'no_session_token');
2862
+ this.#flush_last_word(transport_peer_id);
2642
2863
  this.#server.disconnect_peer(transport_peer_id);
2643
2864
  this.#unwire_peer_transport_disconnect(transport_peer_id);
2644
2865
  return;
@@ -2679,16 +2900,28 @@ export class NetworkSession {
2679
2900
  // a new peer_id from the matchmaking layer.
2680
2901
  this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.TokenMismatch);
2681
2902
  this.#peer.send_disconnect(transport_peer_id, 'token_mismatch');
2903
+ this.#flush_last_word(transport_peer_id);
2682
2904
  this.#server.disconnect_peer(transport_peer_id);
2683
2905
  this.#unwire_peer_transport_disconnect(transport_peer_id);
2684
2906
  // Same cleanup as the collision branch above: if this transport was
2685
2907
  // a fresh peer added to the live set, drop it from both iterated
2686
2908
  // maps so the next host tick doesn't address a disposed conn.
2687
2909
  this.#connected_peers.delete(transport_peer_id);
2688
- this.#pending_initial_sync.delete(transport_peer_id);
2910
+ this.#joins.delete(transport_peer_id);
2689
2911
  this.#peer_inbound_mark.delete(transport_peer_id);
2690
2912
  return;
2691
2913
  }
2914
+ // A join that never finished. The client holds whatever pages reached it — part of a
2915
+ // world — and resuming would keep it. Refused like a stale frame: the client falls through
2916
+ // to a full resync.
2917
+ if (grace.join_complete === false) {
2918
+ this.#peer.send_resume_reject(transport_peer_id, ResumeRejectReason.StaleFrame);
2919
+ this.#peer_grace_state.delete(claimed_peer_id);
2920
+ this.#queue_initial_sync(transport_peer_id);
2921
+ this.#connected_peers.add(transport_peer_id);
2922
+ return;
2923
+ }
2924
+
2692
2925
  // Ring-floor gate. The client asks to resume from `last_acked_frame + 1`,
2693
2926
  // but the action-log ring only still holds frames back to
2694
2927
  // `current_sim_frame - frame_capacity + 1`. If the requested frame
@@ -2733,7 +2966,7 @@ export class NetworkSession {
2733
2966
  // so release it here or the id stays unconnectable forever.
2734
2967
  this.#server.disconnect_peer(peer_id);
2735
2968
  this.#unwire_peer_transport_disconnect(peer_id);
2736
- this.#pending_initial_sync.delete(peer_id);
2969
+ this.#joins.delete(peer_id);
2737
2970
  this.onPeerPermanentlyDropped.send2(peer_id, 'grace_expired');
2738
2971
  }
2739
2972
  }
@@ -2748,6 +2981,13 @@ export class NetworkSession {
2748
2981
  * pinned forever (host) or never noticed (client). The per-tick keepalive
2749
2982
  * traffic the facade sends both directions keeps a live connection well
2750
2983
  * clear of the budget. Called from `tick()` only when the budget is > 0.
2984
+ *
2985
+ * The host closes a reaped peer's transport as well. A silent peer is not always a gone one: a
2986
+ * client whose tab went to the background stops stepping and keeps its socket, and when it steps
2987
+ * again the close is waiting behind the packets it was sent, so it learns of the reap then — not
2988
+ * a whole silence budget later, on a clock that stood still with it (its budget runs on frame
2989
+ * time). A transport with no close of its own (`NodeUDP`) leaves it to that budget.
2990
+ * `NetworkSession.stall.spec.js`.
2751
2991
  * @private
2752
2992
  */
2753
2993
  #reap_silent_peers() {
@@ -2759,7 +2999,11 @@ export class NetworkSession {
2759
2999
  }
2760
3000
  if (to_reap !== null) {
2761
3001
  for (let i = 0; i < to_reap.length; i++) {
2762
- this.#on_peer_transport_disconnected(to_reap[i], 'timeout');
3002
+ const peer_id = to_reap[i];
3003
+ // unwired by the reap before it is closed, so the close is not taken for a second loss
3004
+ const entry = this.#peer_transport_listeners.get(peer_id);
3005
+ this.#on_peer_transport_disconnected(peer_id, 'timeout');
3006
+ if (entry !== undefined) close_transport(entry.transport);
2763
3007
  }
2764
3008
  }
2765
3009
  } else if (this.#reconnect_state === ReconnectState.Connected && this.#remote_peer_id >= 0) {
@@ -2813,6 +3057,25 @@ export class NetworkSession {
2813
3057
  this.#current_transport_listener = listener;
2814
3058
  }
2815
3059
 
3060
+ /**
3061
+ * Client: the connection to the host is no longer whole (see Connection.onProtocolError) and
3062
+ * can carry nothing more. Given up as a dropped transport is — the reconnect ladder resumes on
3063
+ * a new one — and closed, so the host hears of it now rather than when silence reaps it.
3064
+ * @param {string} reason
3065
+ */
3066
+ #on_client_protocol_error(reason) {
3067
+ const transport = this.#current_transport;
3068
+ // unhooked first: the close must not be taken for a second loss
3069
+ if (transport !== null && this.#current_transport_listener !== null) {
3070
+ try {
3071
+ transport.onDisconnect.remove(this.#current_transport_listener);
3072
+ } catch (_) { /* swallow */
3073
+ }
3074
+ }
3075
+ this.#on_client_transport_disconnected(`protocol_error: ${reason}`);
3076
+ if (transport !== null) close_transport(transport);
3077
+ }
3078
+
2816
3079
  #on_client_transport_disconnected(reason) {
2817
3080
  if (this.#reconnect_state === ReconnectState.Lost) return;
2818
3081
  // Tear down the dead peer/channel binding so a fresh transport
@@ -3001,9 +3264,10 @@ export class NetworkSession {
3001
3264
  const scratch = this.#scratch_send_buf;
3002
3265
  const world = this.world;
3003
3266
 
3004
- for (const entity_id of this.#event_listeners.keys()) {
3005
- if (this.#remote_entities.has(entity_id)) continue;
3267
+ const owned = this.#owned_by.get(this.local_peer_id);
3268
+ if (owned === undefined) return;
3006
3269
 
3270
+ for (const entity_id of owned) {
3007
3271
  const identity = world.getComponent(entity_id, NetworkIdentity);
3008
3272
  if (identity === undefined || identity.network_id < 0) continue;
3009
3273