@woosh/meep-engine 3.46.0 → 3.47.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 (230) hide show
  1. package/editor/actions/concrete/TransformModifyAction.d.ts.map +1 -1
  2. package/editor/actions/concrete/TransformModifyAction.js +10 -2
  3. package/editor/ecs/component/FieldDescriptor.js +15 -0
  4. package/editor/ecs/component/createObjectEditor.js +41 -12
  5. package/editor/ecs/component/editors/ecs/Transform64Editor.d.ts.map +1 -1
  6. package/editor/ecs/component/editors/ecs/Transform64Editor.js +77 -70
  7. package/editor/tools/TransformToolV2.d.ts.map +1 -1
  8. package/editor/tools/TransformToolV2.js +4 -0
  9. package/editor/tools/v2/TransformControls.d.ts.map +1 -1
  10. package/editor/tools/v2/TransformControls.js +7 -0
  11. package/editor/view/ecs/EntityEditor.d.ts.map +1 -1
  12. package/editor/view/ecs/EntityEditor.js +4 -2
  13. package/package.json +1 -1
  14. package/src/core/collection/array/array_insert_at.d.ts +16 -0
  15. package/src/core/collection/array/array_insert_at.d.ts.map +1 -0
  16. package/src/core/collection/array/array_insert_at.js +32 -0
  17. package/src/core/collection/array/array_remove_at.d.ts +15 -0
  18. package/src/core/collection/array/array_remove_at.d.ts.map +1 -0
  19. package/src/core/collection/array/array_remove_at.js +30 -0
  20. package/src/core/geom/3d/quaternion/quat3_look_rotation.d.ts +37 -0
  21. package/src/core/geom/3d/quaternion/quat3_look_rotation.d.ts.map +1 -0
  22. package/src/core/geom/3d/quaternion/quat3_look_rotation.js +172 -0
  23. package/src/core/geom/Quaternion.d.ts +1 -0
  24. package/src/core/geom/Quaternion.d.ts.map +1 -1
  25. package/src/core/geom/Quaternion.js +12 -48
  26. package/src/engine/Engine.d.ts.map +1 -1
  27. package/src/engine/Engine.js +9 -4
  28. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  29. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +10 -0
  30. package/src/engine/ecs/transform/t64_look_rotation.d.ts +4 -4
  31. package/src/engine/ecs/transform/t64_look_rotation.d.ts.map +1 -1
  32. package/src/engine/ecs/transform/t64_look_rotation.js +7 -18
  33. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
  34. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +30 -1
  35. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +6 -0
  36. package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
  37. package/src/engine/graphics3/GPUParticleEmitterSystem.js +72 -29
  38. package/src/engine/input/devices/KeyboardDevice.d.ts +1 -0
  39. package/src/engine/input/devices/KeyboardDevice.d.ts.map +1 -1
  40. package/src/engine/input/devices/KeyboardDevice.js +7 -6
  41. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.d.ts.map +1 -1
  42. package/src/engine/navigation/ecs/path_following/PathFollowingSystem.js +15 -2
  43. package/src/engine/network/CONGESTION_CONTROL.md +9 -9
  44. package/src/engine/network/MESSAGE_TRANSPORT.md +364 -0
  45. package/src/engine/network/NetworkSession.d.ts +31 -11
  46. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  47. package/src/engine/network/NetworkSession.js +453 -286
  48. package/src/engine/network/README.md +77 -27
  49. package/src/engine/network/SMOOTHNESS_DESIGN.md +13 -5
  50. package/src/engine/network/diagnostics/BandwidthMeter.d.ts +1 -1
  51. package/src/engine/network/diagnostics/BandwidthMeter.js +1 -1
  52. package/src/engine/network/orchestrator/HostJoin.d.ts +111 -0
  53. package/src/engine/network/orchestrator/HostJoin.d.ts.map +1 -0
  54. package/src/engine/network/orchestrator/HostJoin.js +271 -0
  55. package/src/engine/network/orchestrator/NetworkPeer.d.ts +151 -112
  56. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  57. package/src/engine/network/orchestrator/NetworkPeer.js +450 -499
  58. package/src/engine/network/orchestrator/PendingSlices.d.ts +30 -0
  59. package/src/engine/network/orchestrator/PendingSlices.d.ts.map +1 -0
  60. package/src/engine/network/orchestrator/PendingSlices.js +83 -0
  61. package/src/engine/network/orchestrator/ReliableFrames.d.ts +34 -0
  62. package/src/engine/network/orchestrator/ReliableFrames.d.ts.map +1 -0
  63. package/src/engine/network/orchestrator/ReliableFrames.js +96 -0
  64. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +77 -10
  65. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
  66. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +236 -61
  67. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +20 -6
  68. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
  69. package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +33 -8
  70. package/src/engine/network/replication/Replicator.d.ts.map +1 -1
  71. package/src/engine/network/replication/Replicator.js +7 -1
  72. package/src/engine/network/sim/ActionLog.d.ts +32 -5
  73. package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
  74. package/src/engine/network/sim/ActionLog.js +47 -5
  75. package/src/engine/network/sim/SimActionExecutor.d.ts +42 -2
  76. package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
  77. package/src/engine/network/sim/SimActionExecutor.js +100 -4
  78. package/src/engine/network/sim/Snapshotter.d.ts +12 -0
  79. package/src/engine/network/sim/Snapshotter.d.ts.map +1 -1
  80. package/src/engine/network/sim/Snapshotter.js +43 -0
  81. package/src/engine/network/state/Baseline.d.ts +1 -1
  82. package/src/engine/network/state/Baseline.js +1 -1
  83. package/src/engine/network/state/FrameBufferRing.d.ts +25 -6
  84. package/src/engine/network/state/FrameBufferRing.d.ts.map +1 -1
  85. package/src/engine/network/state/FrameBufferRing.js +51 -6
  86. package/src/engine/network/state/InputRing.d.ts +4 -0
  87. package/src/engine/network/state/InputRing.d.ts.map +1 -1
  88. package/src/engine/network/state/InputRing.js +7 -0
  89. package/src/engine/network/transport/DeferredTransport.d.ts.map +1 -1
  90. package/src/engine/network/transport/DeferredTransport.js +1 -0
  91. package/src/engine/network/transport/LoopbackTransport.d.ts.map +1 -1
  92. package/src/engine/network/transport/LoopbackTransport.js +4 -2
  93. package/src/engine/network/transport/Transport.d.ts +7 -0
  94. package/src/engine/network/transport/Transport.d.ts.map +1 -1
  95. package/src/engine/network/transport/Transport.js +8 -0
  96. package/src/engine/network/transport/adapters/NodeUDPTransport.d.ts.map +1 -1
  97. package/src/engine/network/transport/adapters/NodeUDPTransport.js +20 -5
  98. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.d.ts.map +1 -1
  99. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +8 -0
  100. package/src/engine/network/transport/adapters/WebTransportTransport.d.ts.map +1 -1
  101. package/src/engine/network/transport/adapters/WebTransportTransport.js +6 -0
  102. package/src/engine/network/transport/connection/Connection.d.ts +191 -0
  103. package/src/engine/network/transport/connection/Connection.d.ts.map +1 -0
  104. package/src/engine/network/transport/connection/Connection.js +1262 -0
  105. package/src/engine/network/transport/connection/RangeSet.d.ts +94 -0
  106. package/src/engine/network/transport/connection/RangeSet.d.ts.map +1 -0
  107. package/src/engine/network/transport/connection/RangeSet.js +260 -0
  108. package/src/engine/network/transport/connection/RxStream.d.ts +48 -0
  109. package/src/engine/network/transport/connection/RxStream.d.ts.map +1 -0
  110. package/src/engine/network/transport/connection/RxStream.js +211 -0
  111. package/src/engine/network/transport/connection/TxStream.d.ts +90 -0
  112. package/src/engine/network/transport/connection/TxStream.d.ts.map +1 -0
  113. package/src/engine/network/transport/connection/TxStream.js +243 -0
  114. package/src/engine/network/transport/connection/bytes_copy.d.ts +17 -0
  115. package/src/engine/network/transport/connection/bytes_copy.d.ts.map +1 -0
  116. package/src/engine/network/transport/connection/bytes_copy.js +34 -0
  117. package/src/engine/network/transport/connection/varint.d.ts +46 -0
  118. package/src/engine/network/transport/connection/varint.d.ts.map +1 -0
  119. package/src/engine/network/transport/connection/varint.js +107 -0
  120. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  121. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts +38 -7
  122. package/src/engine/sound/ecs/audio/AudioEmitter.d.ts.map +1 -1
  123. package/src/engine/sound/ecs/audio/AudioEmitter.js +35 -7
  124. package/src/engine/sound/ecs/audio/AudioEmitterSerializationAdapter.d.ts.map +1 -1
  125. package/src/engine/sound/ecs/audio/AudioEmitterSerializationAdapter.js +5 -1
  126. package/src/engine/sound/ecs/audio/AudioEmitterSerializationUpgrader_0_1.d.ts +18 -0
  127. package/src/engine/sound/ecs/audio/AudioEmitterSerializationUpgrader_0_1.d.ts.map +1 -0
  128. package/src/engine/sound/ecs/audio/AudioEmitterSerializationUpgrader_0_1.js +48 -0
  129. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts +12 -2
  130. package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts.map +1 -1
  131. package/src/engine/sound/ecs/audio/AudioEmitterSystem.js +173 -9
  132. package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts +9 -3
  133. package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts.map +1 -1
  134. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +28 -8
  135. package/src/engine/sound/ecs/trigger/AudioEventTrigger.d.ts +8 -0
  136. package/src/engine/sound/ecs/trigger/AudioEventTrigger.d.ts.map +1 -1
  137. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationAdapter.d.ts.map +1 -1
  138. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationAdapter.js +5 -1
  139. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationUpgrader_0_1.d.ts +18 -0
  140. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationUpgrader_0_1.d.ts.map +1 -0
  141. package/src/engine/sound/ecs/trigger/AudioEventTriggerSerializationUpgrader_0_1.js +51 -0
  142. package/src/engine/sound/ecs/trigger/AudioEventTriggerSystem.d.ts.map +1 -1
  143. package/src/engine/sound/ecs/trigger/AudioEventTriggerSystem.js +15 -1
  144. package/src/engine/sound/sopra/README.md +768 -644
  145. package/src/engine/sound/sopra/SopraEngine.d.ts +104 -8
  146. package/src/engine/sound/sopra/SopraEngine.d.ts.map +1 -1
  147. package/src/engine/sound/sopra/SopraEngine.js +327 -12
  148. package/src/engine/sound/sopra/definition/EventDescription.d.ts +28 -2
  149. package/src/engine/sound/sopra/definition/EventDescription.d.ts.map +1 -1
  150. package/src/engine/sound/sopra/definition/EventDescription.js +55 -5
  151. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts +10 -0
  152. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts.map +1 -1
  153. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +77 -55
  154. package/src/engine/sound/sopra/definition/EventDescriptionSerializationUpgrader_0_1.d.ts +31 -0
  155. package/src/engine/sound/sopra/definition/EventDescriptionSerializationUpgrader_0_1.d.ts.map +1 -0
  156. package/src/engine/sound/sopra/definition/EventDescriptionSerializationUpgrader_0_1.js +75 -0
  157. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts +38 -9
  158. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts.map +1 -1
  159. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +34 -5
  160. package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts +7 -2
  161. package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts.map +1 -1
  162. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +137 -122
  163. package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts +3 -1
  164. package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts.map +1 -1
  165. package/src/engine/sound/sopra/definition/effect/EqEffect.js +146 -131
  166. package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts +1 -0
  167. package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts.map +1 -1
  168. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +6 -1
  169. package/src/engine/sound/sopra/runtime/BusGraph.d.ts +44 -2
  170. package/src/engine/sound/sopra/runtime/BusGraph.d.ts.map +1 -1
  171. package/src/engine/sound/sopra/runtime/BusGraph.js +83 -43
  172. package/src/engine/sound/sopra/runtime/EffectChain.d.ts +96 -0
  173. package/src/engine/sound/sopra/runtime/EffectChain.d.ts.map +1 -0
  174. package/src/engine/sound/sopra/runtime/EffectChain.js +286 -0
  175. package/src/engine/sound/sopra/runtime/EffectParamOverrides.d.ts +123 -0
  176. package/src/engine/sound/sopra/runtime/EffectParamOverrides.d.ts.map +1 -0
  177. package/src/engine/sound/sopra/runtime/EffectParamOverrides.js +320 -0
  178. package/src/engine/sound/sopra/runtime/EventInstance.d.ts +55 -5
  179. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
  180. package/src/engine/sound/sopra/runtime/EventInstance.js +201 -26
  181. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts +2 -1
  182. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts.map +1 -1
  183. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +6 -1
  184. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts +15 -0
  185. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts.map +1 -1
  186. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +32 -0
  187. package/src/engine/sound/sopra/util/MockAudioContext.d.ts.map +1 -1
  188. package/src/engine/sound/sopra/util/MockAudioContext.js +3 -0
  189. package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts +13 -3
  190. package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts.map +1 -1
  191. package/src/engine/sound/sopra/util/fadeOutAndStop.js +25 -13
  192. package/src/shade/RENDERER_CONTRACT.md +6 -3
  193. package/src/shade/device/SHADE_DEVICE_FLOOR.d.ts +24 -0
  194. package/src/shade/device/SHADE_DEVICE_FLOOR.d.ts.map +1 -0
  195. package/src/shade/device/SHADE_DEVICE_FLOOR.js +34 -0
  196. package/src/shade/device/ShadeDeviceFailure.d.ts +12 -5
  197. package/src/shade/device/ShadeDeviceFailure.d.ts.map +1 -1
  198. package/src/shade/device/ShadeDeviceFailure.js +17 -8
  199. package/src/shade/device/ShadeDeviceFailureReason.d.ts +1 -1
  200. package/src/shade/device/ShadeDeviceFailureReason.js +2 -2
  201. package/src/shade/device/shade_device_floor_unmet.d.ts +13 -0
  202. package/src/shade/device/shade_device_floor_unmet.d.ts.map +1 -0
  203. package/src/shade/device/shade_device_floor_unmet.js +32 -0
  204. package/src/shade/renderer/Renderer.d.ts +2 -0
  205. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  206. package/src/shade/renderer/Renderer.js +29 -55
  207. package/src/shade/renderer/buffer/GPUBufferAllocator.d.ts.map +1 -1
  208. package/src/shade/renderer/buffer/GPUBufferAllocator.js +9 -0
  209. package/src/shade/renderer/buffer/buffer_pool_ops.d.ts +21 -12
  210. package/src/shade/renderer/buffer/buffer_pool_ops.d.ts.map +1 -1
  211. package/src/shade/renderer/buffer/buffer_pool_ops.js +18 -19
  212. package/src/shade/renderer/gpu_primitive/test/gpu_test.d.ts.map +1 -1
  213. package/src/shade/renderer/gpu_primitive/test/gpu_test.js +8 -11
  214. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  215. package/src/shade/renderer/pool/TransientResourcePool.d.ts +24 -0
  216. package/src/shade/renderer/pool/TransientResourcePool.d.ts.map +1 -1
  217. package/src/shade/renderer/pool/TransientResourcePool.js +41 -10
  218. package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
  219. package/src/shade/renderer/shader/ComputeShader.js +18 -2
  220. package/src/shade/renderer/shader/ImageShader.d.ts.map +1 -1
  221. package/src/shade/renderer/shader/ImageShader.js +230 -218
  222. package/src/view/graphics/GraphicsContextFailureView.d.ts +1 -2
  223. package/src/view/graphics/GraphicsContextFailureView.d.ts.map +1 -1
  224. package/src/view/graphics/GraphicsContextFailureView.js +1 -2
  225. package/src/engine/network/transport/Channel.js +0 -336
  226. package/src/engine/network/transport/ReliableCommandPipeline.js +0 -335
  227. package/src/engine/network/transport/fragments/FragmentAssembler.js +0 -350
  228. package/src/engine/network/transport/fragments/FragmentRetention.js +0 -191
  229. package/src/engine/network/transport/fragments/fragment_send.js +0 -157
  230. package/src/engine/network/transport/fragments/packet_size.js +0 -107
@@ -3,6 +3,7 @@ import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
3
3
  import Signal from "../../../core/events/signal/Signal.js";
4
4
  import { MalformedPacketError } from "../core/MalformedPacketError.js";
5
5
  import { ActionRecordCursor } from "../sim/ActionRecordCursor.js";
6
+ import { SENDER_CORRECTED } from "../sim/SimActionExecutor.js";
6
7
 
7
8
  // Bytes of fixed per-action framing that always precede the variable payload:
8
9
  // uint8 action_type_id + uint32 action_payload_len.
@@ -321,6 +322,9 @@ export class Replicator {
321
322
  // back to it — harmless where ownership rejects it, and the host overwriting its own
322
323
  // simulation with a stale copy of itself where the entity belongs to the client.
323
324
  if (cursor.sender_id === peer_id) continue;
325
+ // Nor anyone a correction: it is the server's word about this peer's own world,
326
+ // and sent back, a host would take a client-owned entity's old state from it.
327
+ if (cursor.sender_id === SENDER_CORRECTED) continue;
324
328
  if (!this.#record_in_scope(cursor, peer_id)) continue;
325
329
  if ((in_scope_count + 1) * 3 > this.#in_scope_actions.length) {
326
330
  this.#grow_in_scope_actions();
@@ -543,7 +547,9 @@ export class Replicator {
543
547
  continue;
544
548
  }
545
549
 
546
- this.action_log.begin_frame(frame_number);
550
+ // After what the frame already holds: on a client this frame is usually one it has
551
+ // already predicted, and its own records are what a rewind undoes.
552
+ this.action_log.resume_frame(frame_number);
547
553
  try {
548
554
  for (let i = 0; i < action_count; i++) {
549
555
  if (in_buffer.position + ACTION_HEADER_BYTES > in_buffer_end) {
@@ -27,7 +27,7 @@
27
27
  * `sender_id` says where the record came from, which is what a rollback replay
28
28
  * needs in order to decide what to do with it:
29
29
  *
30
- * - a peer id `[0, 252]` — arrived from that peer;
30
+ * - a peer id `[0, 251]` — arrived from that peer;
31
31
  * - `SENDER_LOCAL` (255) — authored on this peer;
32
32
  * - `SENDER_DERIVED` (254) — computed by this peer's simulation from the
33
33
  * frame's own state. A replay of the frame recomputes these rather than
@@ -35,7 +35,11 @@
35
35
  * the sim's output does not accumulate a copy per replay;
36
36
  * - `SENDER_RECORDED` (253) — recorded, not executed: what this peer's world
37
37
  * became, for the peers (see `SimActionExecutor.record`). A replay carries
38
- * these over unchanged.
38
+ * these over unchanged;
39
+ * - `SENDER_CORRECTED` (252) — a server's correction of one entity on a
40
+ * predicting client (see `SimActionExecutor.begin_correction`): prior state
41
+ * and the server's bytes, no action. A replay applies it again, last in its
42
+ * frame, and it is never packed for any peer.
39
43
  *
40
44
  * It also gives the replay a stable sort key, so multi-client action order is
41
45
  * deterministic across peers. It never crosses the wire —
@@ -70,6 +74,29 @@ export class ActionLog {
70
74
  * @param {number} frame absolute frame number
71
75
  */
72
76
  begin_frame(frame: number): void;
77
+ /**
78
+ * Open a frame for writing after the records it already holds, rather than over them. A frame
79
+ * the ring does not hold is opened empty, as {@link begin_frame} opens it.
80
+ *
81
+ * For a writer adding to a frame someone else wrote: records arriving from a peer for a frame
82
+ * this peer has already simulated. Opened with {@link begin_frame} instead, they would erase
83
+ * this peer's own records for the frame — and with them the prior state a rewind needs to undo
84
+ * that input, so a replay would apply it a second time, and the outbound stream, which packs
85
+ * from this log, would lose it before the peer it was for had confirmed it.
86
+ *
87
+ * @param {number} frame absolute frame number
88
+ */
89
+ resume_frame(frame: number): void;
90
+ /**
91
+ * Drop every record the log holds, keeping each frame it holds present and empty.
92
+ *
93
+ * For when the world the records were written against is replaced wholesale, and the frame
94
+ * numbers they were written under with it: their prior state belongs to that world, so a rewind
95
+ * that walked them would put it back into this one. Present rather than absent, because a frame
96
+ * the new world never ran — skipped by the seek that came with it — is one in which nothing was
97
+ * done, and a rewind crossing it has nothing to undo; an absent frame would make it abandon.
98
+ */
99
+ empty_frames(): void;
73
100
  /**
74
101
  * Close the currently-open frame.
75
102
  */
@@ -100,9 +127,9 @@ export class ActionLog {
100
127
  * The buffer's `position` will be set to 0 and its valid byte length is `write_end_for(frame)`.
101
128
  *
102
129
  * **Only valid for frames that have been closed via {@link end_frame}.** While a
103
- * frame is open, its slot's recorded length still holds the previous
104
- * occupant's — `buffer_for`/`write_end_for` would return mismatched
105
- * buffer-contents-vs-length and any reader would walk garbage.
130
+ * frame is open, its slot's recorded length is what was last committed — 0 for a
131
+ * frame opened by {@link begin_frame}, the old end for one opened by
132
+ * {@link resume_frame} — so a reader sees none of what is being written.
106
133
  *
107
134
  * @param {number} frame
108
135
  * @returns {BinaryBuffer}
@@ -1 +1 @@
1
- {"version":3,"file":"ActionLog.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/sim/ActionLog.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH;IAoBI;;OAEG;IACH,qDAFW;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAE,EAIlE;IAED;;;OAGG;IACH,6BAEC;IAED;;;;OAIG;IACH,mBAFW,MAAM,QAShB;IAED;;OAEG;IACH,kBASC;IAED;;;;;;OAMG;IACH,kBAFa,OAAO,CAInB;IAED;;;;OAIG;IACH,+BAMC;IAED;;;;;OAKG;IACH,iBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;;;;;;;OAWG;IACH,kBAHW,MAAM,gBAKhB;IAED;;;;OAIG;IACH,qBAHW,MAAM,GACJ,MAAM,CAIlB;;CACJ"}
1
+ {"version":3,"file":"ActionLog.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/sim/ActionLog.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH;IAoBI;;OAEG;IACH,qDAFW;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAE,EAIlE;IAED;;;OAGG;IACH,6BAEC;IAED;;;;OAIG;IACH,mBAFW,MAAM,QAShB;IAED;;;;;;;;;;;OAWG;IACH,oBAFW,MAAM,QAShB;IAED;;;;;;;;OAQG;IACH,qBAMC;IAED;;OAEG;IACH,kBASC;IAED;;;;;;OAMG;IACH,kBAFa,OAAO,CAInB;IAED;;;;OAIG;IACH,+BAMC;IAED;;;;;OAKG;IACH,iBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;;;;;;;OAWG;IACH,kBAHW,MAAM,gBAKhB;IAED;;;;OAIG;IACH,qBAHW,MAAM,GACJ,MAAM,CAIlB;;CACJ"}
@@ -29,7 +29,7 @@ import { FrameBufferRing } from "../state/FrameBufferRing.js";
29
29
  * `sender_id` says where the record came from, which is what a rollback replay
30
30
  * needs in order to decide what to do with it:
31
31
  *
32
- * - a peer id `[0, 252]` — arrived from that peer;
32
+ * - a peer id `[0, 251]` — arrived from that peer;
33
33
  * - `SENDER_LOCAL` (255) — authored on this peer;
34
34
  * - `SENDER_DERIVED` (254) — computed by this peer's simulation from the
35
35
  * frame's own state. A replay of the frame recomputes these rather than
@@ -37,7 +37,11 @@ import { FrameBufferRing } from "../state/FrameBufferRing.js";
37
37
  * the sim's output does not accumulate a copy per replay;
38
38
  * - `SENDER_RECORDED` (253) — recorded, not executed: what this peer's world
39
39
  * became, for the peers (see `SimActionExecutor.record`). A replay carries
40
- * these over unchanged.
40
+ * these over unchanged;
41
+ * - `SENDER_CORRECTED` (252) — a server's correction of one entity on a
42
+ * predicting client (see `SimActionExecutor.begin_correction`): prior state
43
+ * and the server's bytes, no action. A replay applies it again, last in its
44
+ * frame, and it is never packed for any peer.
41
45
  *
42
46
  * It also gives the replay a stable sort key, so multi-client action order is
43
47
  * deterministic across peers. It never crosses the wire —
@@ -102,6 +106,44 @@ export class ActionLog {
102
106
  this.#current_frame = frame;
103
107
  }
104
108
 
109
+ /**
110
+ * Open a frame for writing after the records it already holds, rather than over them. A frame
111
+ * the ring does not hold is opened empty, as {@link begin_frame} opens it.
112
+ *
113
+ * For a writer adding to a frame someone else wrote: records arriving from a peer for a frame
114
+ * this peer has already simulated. Opened with {@link begin_frame} instead, they would erase
115
+ * this peer's own records for the frame — and with them the prior state a rewind needs to undo
116
+ * that input, so a replay would apply it a second time, and the outbound stream, which packs
117
+ * from this log, would lose it before the peer it was for had confirmed it.
118
+ *
119
+ * @param {number} frame absolute frame number
120
+ */
121
+ resume_frame(frame) {
122
+ if (this.#current_frame !== -1) {
123
+ throw new Error(`ActionLog.resume_frame: previous frame ${this.#current_frame} still open; call end_frame first`);
124
+ }
125
+
126
+ this.#current_buffer = this.#ring.resume(frame);
127
+ this.#current_frame = frame;
128
+ }
129
+
130
+ /**
131
+ * Drop every record the log holds, keeping each frame it holds present and empty.
132
+ *
133
+ * For when the world the records were written against is replaced wholesale, and the frame
134
+ * numbers they were written under with it: their prior state belongs to that world, so a rewind
135
+ * that walked them would put it back into this one. Present rather than absent, because a frame
136
+ * the new world never ran — skipped by the seek that came with it — is one in which nothing was
137
+ * done, and a rewind crossing it has nothing to undo; an absent frame would make it abandon.
138
+ */
139
+ empty_frames() {
140
+ if (this.#current_frame !== -1) {
141
+ throw new Error(`ActionLog.empty_frames: frame ${this.#current_frame} is open; call end_frame first`);
142
+ }
143
+
144
+ this.#ring.empty();
145
+ }
146
+
105
147
  /**
106
148
  * Close the currently-open frame.
107
149
  */
@@ -155,9 +197,9 @@ export class ActionLog {
155
197
  * The buffer's `position` will be set to 0 and its valid byte length is `write_end_for(frame)`.
156
198
  *
157
199
  * **Only valid for frames that have been closed via {@link end_frame}.** While a
158
- * frame is open, its slot's recorded length still holds the previous
159
- * occupant's — `buffer_for`/`write_end_for` would return mismatched
160
- * buffer-contents-vs-length and any reader would walk garbage.
200
+ * frame is open, its slot's recorded length is what was last committed — 0 for a
201
+ * frame opened by {@link begin_frame}, the old end for one opened by
202
+ * {@link resume_frame} — so a reader sees none of what is being written.
161
203
  *
162
204
  * @param {number} frame
163
205
  * @returns {BinaryBuffer}
@@ -23,7 +23,7 @@ export const SENDER_LOCAL: number;
23
23
  * Local-only, like `SENDER_LOCAL` — {@link Replicator#pack_for_peer} strips
24
24
  * the byte before send, so this never reaches the wire.
25
25
  *
26
- * The sentinels are reserved: valid peer ids are `[0, 252]`, enforced by
26
+ * The sentinels are reserved: valid peer ids are `[0, 251]`, enforced by
27
27
  * {@link SimActionExecutor#execute} and at {@link NetworkPeer#connect_peer}.
28
28
  * A peer allowed to take 254 would have its records read back as the local
29
29
  * sim's own output and dropped from every replay — its input would apply once
@@ -44,9 +44,24 @@ export const SENDER_DERIVED: number;
44
44
  * @type {number}
45
45
  */
46
46
  export const SENDER_RECORDED: number;
47
+ /**
48
+ * Sentinel sender id for a server's correction written into a predicting client's log (see
49
+ * {@link SimActionExecutor#begin_correction}): the server's state for one entity at the end of a
50
+ * frame, put over this client's prediction of it.
51
+ *
52
+ * Its prior state is what makes the correction undoable, so a rewind across its frame takes the
53
+ * entity back to the prediction underneath. It has no action: a rollback replay applies it again,
54
+ * last in its frame, from the bytes it carries. It is never sent — the client's log is its outbound
55
+ * stream, and a host would take a client-owned entity's old state from it. Local-only, like the
56
+ * other sentinels.
57
+ *
58
+ * @type {number}
59
+ */
60
+ export const SENDER_CORRECTED: number;
47
61
  /**
48
62
  * Largest id a remote peer may take. Above this are the local provenance sentinels,
49
- * {@link SENDER_RECORDED}, {@link SENDER_DERIVED} and {@link SENDER_LOCAL}.
63
+ * {@link SENDER_CORRECTED}, {@link SENDER_RECORDED}, {@link SENDER_DERIVED} and
64
+ * {@link SENDER_LOCAL}.
50
65
  *
51
66
  * @type {number}
52
67
  */
@@ -242,6 +257,31 @@ export class SimActionExecutor {
242
257
  * @param {SimAction} action
243
258
  */
244
259
  record(action: SimAction): void;
260
+ /**
261
+ * Open a {@link SENDER_CORRECTED} record in the open frame: capture the prior state of every
262
+ * replicated component `entity_id` has, as {@link execute} captures an action's, so a rewind
263
+ * across the frame puts it back. The caller then overwrites the entity with the server's state
264
+ * and closes the record with {@link end_correction} — also when that overwrite throws, or the
265
+ * frame is left holding half a record.
266
+ *
267
+ * Every replicated component, because a correction is the server's whole state for the entity
268
+ * and the executor cannot know which of them the caller's deserialize writes.
269
+ *
270
+ * No authorization, no {@link before_execute}, no announcement: the caller writes, not this.
271
+ *
272
+ * @param {number} entity_id
273
+ */
274
+ begin_correction(entity_id: number): void;
275
+ /**
276
+ * Close the record {@link begin_correction} opened, carrying `length` bytes of `bytes` from
277
+ * `offset` — what a replay needs to apply the correction again. The record has no action: its
278
+ * action type byte is 0 and means nothing, and readers go by its sender.
279
+ *
280
+ * @param {Uint8Array} bytes
281
+ * @param {number} offset
282
+ * @param {number} length
283
+ */
284
+ end_correction(bytes: Uint8Array, offset: number, length: number): void;
245
285
  #private;
246
286
  }
247
287
  import Signal from "../../../core/events/signal/Signal.js";
@@ -1 +1 @@
1
- {"version":3,"file":"SimActionExecutor.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/sim/SimActionExecutor.js"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,2BAFU,MAAM,CAEiB;AAEjC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,6BAFU,MAAM,CAEmB;AAEnC;;;;;;;;;;GAUG;AACH,8BAFU,MAAM,CAEoB;AAEpC;;;;;GAKG;AACH,0BAFU,MAAM,CAEgB;AAEhC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;IAqEI;;;;;;;;;OASG;IACH,sGATW;QACV,KAAS,yBAAyB;QAClC,UAAc,YAAY;QAC1B,eAAmB,oBAAoB;QACvC,kBAAsB,8BAA8B;QACpD,UAAc,uBAAuB;QACrC,gBAAoB,CAAC,mBAAmB;KACrC,EAyFH;IAjKD;;;;;;;;;OASG;IACH,gBAFU,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAEP;IAEtB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,WAFU,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,WAAW,KAAK,OAAO,CAAC,GAAG,IAAI,CAEtE;IAEjB;;;;;;;;;;;;;;;OAeG;IACH,UAFU,OAAO,CAEA;IAEjB;;;;;;;OAOG;IACH,sBAFU,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,UAAU,KAAK,IAAI,CAAC,GAAG,IAAI,CAE7C;IAaxB;;OAEG;IACH,8BAAkB;IAElB;;OAEG;IACH,sBAA4B;IAE5B;;OAEG;IACH,mCAAsC;IAEtC;;OAEG;IACH,gDAA4C;IAE5C;;;;OAIG;IACH,iCAA4B;IAE5B;;;;;;;;;;;OAWG;IACH,kBAFU,mBAAiB,IAAI,CAES;IA4BxC;;;;;;;OAOG;IACH,mBAFU,MAAM,CAEqB;IAErC;;;;;;;OAOG;IACH,kBAFU,MAAM,CAEoB;IAGxC;;;;;;;;;;OAUG;IACH,uCAPW,MAAM,GAGJ,OAAO,CA0KnB;IAkCD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,gCAwCC;;CACJ;mBA7gBkB,uCAAuC"}
1
+ {"version":3,"file":"SimActionExecutor.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/sim/SimActionExecutor.js"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,2BAFU,MAAM,CAEiB;AAEjC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,6BAFU,MAAM,CAEmB;AAEnC;;;;;;;;;;GAUG;AACH,8BAFU,MAAM,CAEoB;AAEpC;;;;;;;;;;;;GAYG;AACH,+BAFU,MAAM,CAEqB;AAErC;;;;;;GAMG;AACH,0BAFU,MAAM,CAEgB;AAEhC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;IA2EI;;;;;;;;;OASG;IACH,sGATW;QACV,KAAS,yBAAyB;QAClC,UAAc,YAAY;QAC1B,eAAmB,oBAAoB;QACvC,kBAAsB,8BAA8B;QACpD,UAAc,uBAAuB;QACrC,gBAAoB,CAAC,mBAAmB;KACrC,EAyFH;IAjKD;;;;;;;;;OASG;IACH,gBAFU,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAEP;IAEtB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,WAFU,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,WAAW,KAAK,OAAO,CAAC,GAAG,IAAI,CAEtE;IAEjB;;;;;;;;;;;;;;;OAeG;IACH,UAFU,OAAO,CAEA;IAEjB;;;;;;;OAOG;IACH,sBAFU,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,UAAU,KAAK,IAAI,CAAC,GAAG,IAAI,CAE7C;IAaxB;;OAEG;IACH,8BAAkB;IAElB;;OAEG;IACH,sBAA4B;IAE5B;;OAEG;IACH,mCAAsC;IAEtC;;OAEG;IACH,gDAA4C;IAE5C;;;;OAIG;IACH,iCAA4B;IAE5B;;;;;;;;;;;OAWG;IACH,kBAFU,mBAAiB,IAAI,CAES;IA4BxC;;;;;;;OAOG;IACH,mBAFU,MAAM,CAEqB;IAErC;;;;;;;OAOG;IACH,kBAFU,MAAM,CAEoB;IAGxC;;;;;;;;;;OAUG;IACH,uCAPW,MAAM,GAGJ,OAAO,CA0KnB;IAkCD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,gCAwCC;IAED;;;;;;;;;;;;;OAaG;IACH,4BAFW,MAAM,QAqChB;IAED;;;;;;;;OAQG;IACH,sBAJW,UAAU,UACV,MAAM,UACN,MAAM,QAchB;;CACJ;mBA7mBkB,uCAAuC"}
@@ -27,7 +27,7 @@ export const SENDER_LOCAL = 0xFF;
27
27
  * Local-only, like `SENDER_LOCAL` — {@link Replicator#pack_for_peer} strips
28
28
  * the byte before send, so this never reaches the wire.
29
29
  *
30
- * The sentinels are reserved: valid peer ids are `[0, 252]`, enforced by
30
+ * The sentinels are reserved: valid peer ids are `[0, 251]`, enforced by
31
31
  * {@link SimActionExecutor#execute} and at {@link NetworkPeer#connect_peer}.
32
32
  * A peer allowed to take 254 would have its records read back as the local
33
33
  * sim's own output and dropped from every replay — its input would apply once
@@ -50,13 +50,29 @@ export const SENDER_DERIVED = 0xFE;
50
50
  */
51
51
  export const SENDER_RECORDED = 0xFD;
52
52
 
53
+ /**
54
+ * Sentinel sender id for a server's correction written into a predicting client's log (see
55
+ * {@link SimActionExecutor#begin_correction}): the server's state for one entity at the end of a
56
+ * frame, put over this client's prediction of it.
57
+ *
58
+ * Its prior state is what makes the correction undoable, so a rewind across its frame takes the
59
+ * entity back to the prediction underneath. It has no action: a rollback replay applies it again,
60
+ * last in its frame, from the bytes it carries. It is never sent — the client's log is its outbound
61
+ * stream, and a host would take a client-owned entity's old state from it. Local-only, like the
62
+ * other sentinels.
63
+ *
64
+ * @type {number}
65
+ */
66
+ export const SENDER_CORRECTED = 0xFC;
67
+
53
68
  /**
54
69
  * Largest id a remote peer may take. Above this are the local provenance sentinels,
55
- * {@link SENDER_RECORDED}, {@link SENDER_DERIVED} and {@link SENDER_LOCAL}.
70
+ * {@link SENDER_CORRECTED}, {@link SENDER_RECORDED}, {@link SENDER_DERIVED} and
71
+ * {@link SENDER_LOCAL}.
56
72
  *
57
73
  * @type {number}
58
74
  */
59
- export const MAX_PEER_ID = 0xFC;
75
+ export const MAX_PEER_ID = 0xFB;
60
76
 
61
77
  /**
62
78
  * The single legitimate gateway for replicated state mutations.
@@ -86,6 +102,12 @@ export class SimActionExecutor {
86
102
  #scratch_classes;
87
103
  #collect_cb;
88
104
 
105
+ /**
106
+ * Between {@link begin_correction} and {@link end_correction}.
107
+ * @type {boolean}
108
+ */
109
+ #correction_open = false;
110
+
89
111
  /**
90
112
  * Optional hook fired at the top of {@link execute}, before
91
113
  * prior-bytes capture. Higher-level orchestrators install this
@@ -281,7 +303,7 @@ export class SimActionExecutor {
281
303
  // prod, and silent input loss is not something to ship behind one.
282
304
  if (sender_id > MAX_PEER_ID && sender_id !== SENDER_LOCAL) {
283
305
  throw new Error(
284
- `SimActionExecutor.execute: sender_id ${sender_id} is not a peer id; remote peer ids must be in [0, ${MAX_PEER_ID}] — 253, 254 and 255 are reserved provenance markers`,
306
+ `SimActionExecutor.execute: sender_id ${sender_id} is not a peer id; remote peer ids must be in [0, ${MAX_PEER_ID}] — 252 to 255 are reserved provenance markers`,
285
307
  );
286
308
  }
287
309
 
@@ -524,4 +546,78 @@ export class SimActionExecutor {
524
546
  buffer.writeUint32(action_payload_end - action_payload_start);
525
547
  buffer.position = action_payload_end;
526
548
  }
549
+
550
+ /**
551
+ * Open a {@link SENDER_CORRECTED} record in the open frame: capture the prior state of every
552
+ * replicated component `entity_id` has, as {@link execute} captures an action's, so a rewind
553
+ * across the frame puts it back. The caller then overwrites the entity with the server's state
554
+ * and closes the record with {@link end_correction} — also when that overwrite throws, or the
555
+ * frame is left holding half a record.
556
+ *
557
+ * Every replicated component, because a correction is the server's whole state for the entity
558
+ * and the executor cannot know which of them the caller's deserialize writes.
559
+ *
560
+ * No authorization, no {@link before_execute}, no announcement: the caller writes, not this.
561
+ *
562
+ * @param {number} entity_id
563
+ */
564
+ begin_correction(entity_id) {
565
+ assert.isNonNegativeInteger(entity_id, 'entity_id');
566
+ assert.equal(this.#correction_open, false, 'begin_correction: a correction is already open');
567
+
568
+ const buffer = this.action_log.current_buffer();
569
+ const registry = this.component_registry;
570
+ const world = this.world;
571
+ const type_count = registry.type_count();
572
+
573
+ let count = 0;
574
+ for (let type_id = 0; type_id < type_count; type_id++) {
575
+ if (world.getComponent(entity_id, registry.class_of(type_id)) !== undefined) count++;
576
+ }
577
+
578
+ buffer.writeUintVar(count);
579
+
580
+ for (let type_id = 0; type_id < type_count; type_id++) {
581
+ const component = world.getComponent(entity_id, registry.class_of(type_id));
582
+ if (component === undefined) continue;
583
+
584
+ buffer.writeUintVar(entity_id);
585
+ buffer.writeUint8(type_id);
586
+
587
+ const len_position = buffer.position;
588
+ buffer.writeUint32(0); // placeholder
589
+ const payload_start = buffer.position;
590
+ registry.adapter_for_id(type_id).serialize(buffer, component);
591
+ const payload_end = buffer.position;
592
+
593
+ buffer.position = len_position;
594
+ buffer.writeUint32(payload_end - payload_start);
595
+ buffer.position = payload_end;
596
+ }
597
+
598
+ this.#correction_open = true;
599
+ }
600
+
601
+ /**
602
+ * Close the record {@link begin_correction} opened, carrying `length` bytes of `bytes` from
603
+ * `offset` — what a replay needs to apply the correction again. The record has no action: its
604
+ * action type byte is 0 and means nothing, and readers go by its sender.
605
+ *
606
+ * @param {Uint8Array} bytes
607
+ * @param {number} offset
608
+ * @param {number} length
609
+ */
610
+ end_correction(bytes, offset, length) {
611
+ assert.equal(this.#correction_open, true, 'end_correction: no correction is open');
612
+ assert.isNonNegativeInteger(length, 'length');
613
+
614
+ const buffer = this.action_log.current_buffer();
615
+
616
+ buffer.writeUint8(0);
617
+ buffer.writeUint8(SENDER_CORRECTED);
618
+ buffer.writeUint32(length);
619
+ buffer.writeBytes(bytes, offset, length);
620
+
621
+ this.#correction_open = false;
622
+ }
527
623
  }
@@ -46,6 +46,18 @@ export function snapshotter_emit({ buffer, world, slot_table, component_registry
46
46
  component_registry: ReplicatedComponentRegistry;
47
47
  entity_iter: (arg0: (arg0: number) => void) => void;
48
48
  }): void;
49
+ /**
50
+ * Write one entity's record — network id, then each replicated component it has — in the format
51
+ * {@link snapshotter_emit} writes per entity. Allocates nothing: a page builder calls it once per
52
+ * entity it fits.
53
+ *
54
+ * @param {BinaryBuffer} buffer
55
+ * @param {EntityComponentDataset} world
56
+ * @param {ReplicationSlotTable} slot_table
57
+ * @param {ReplicatedComponentRegistry} component_registry
58
+ * @param {number} entity_id must be registered in the slot table
59
+ */
60
+ export function snapshotter_write_entity(buffer: BinaryBuffer, world: EntityComponentDataset, slot_table: ReplicationSlotTable, component_registry: ReplicatedComponentRegistry, entity_id: number): void;
49
61
  /**
50
62
  * Apply a snapshot stream to a (typically empty) world, creating each entity
51
63
  * and attaching components.
@@ -1 +1 @@
1
- {"version":3,"file":"Snapshotter.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/sim/Snapshotter.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;;;;;;;GAUG;AACH,iGARW;IACV,MAAU,eAAe;IACzB,KAAS,yBAAyB;IAClC,UAAc,uBAAuB;IACrC,kBAAsB,8BAA8B;IACpD,WAAe,gBAAoB,MAAM,KAAG,IAAI,KAAG,IAAI,CAAC;CACrD,QAkDH;AAED;;;;;;;;;;;GAWG;AACH,iGARW;IACV,MAAU,eAAe;IACzB,UAAc,EAAE,MAAM,CAAC;IACvB,KAAS,yBAAyB;IAClC,UAAc,uBAAuB;IACrC,kBAAsB,8BAA8B;CACjD,QA6CH;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,6GARW;IACV,MAAU,eAAe;IACzB,UAAc,EAAE,MAAM,CAAC;IACvB,KAAS,yBAAyB;IAClC,UAAc,uBAAuB;IACrC,kBAAsB,8BAA8B;CACjD,QAgDH"}
1
+ {"version":3,"file":"Snapshotter.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/sim/Snapshotter.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;;;;;;;GAUG;AACH,iGARW;IACV,MAAU,eAAe;IACzB,KAAS,yBAAyB;IAClC,UAAc,uBAAuB;IACrC,kBAAsB,8BAA8B;IACpD,WAAe,gBAAoB,MAAM,KAAG,IAAI,KAAG,IAAI,CAAC;CACrD,QAkDH;AAED;;;;;;;;;;GAUG;AACH,4LAFW,MAAM,QAgChB;AAED;;;;;;;;;;;GAWG;AACH,iGARW;IACV,MAAU,eAAe;IACzB,UAAc,EAAE,MAAM,CAAC;IACvB,KAAS,yBAAyB;IAClC,UAAc,uBAAuB;IACrC,kBAAsB,8BAA8B;CACjD,QA6CH;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,6GARW;IACV,MAAU,eAAe;IACzB,UAAc,EAAE,MAAM,CAAC;IACvB,KAAS,yBAAyB;IAClC,UAAc,uBAAuB;IACrC,kBAAsB,8BAA8B;CACjD,QAgDH"}
@@ -93,6 +93,49 @@ export function snapshotter_emit({ buffer, world, slot_table, component_registry
93
93
  }
94
94
  }
95
95
 
96
+ /**
97
+ * Write one entity's record — network id, then each replicated component it has — in the format
98
+ * {@link snapshotter_emit} writes per entity. Allocates nothing: a page builder calls it once per
99
+ * entity it fits.
100
+ *
101
+ * @param {BinaryBuffer} buffer
102
+ * @param {EntityComponentDataset} world
103
+ * @param {ReplicationSlotTable} slot_table
104
+ * @param {ReplicatedComponentRegistry} component_registry
105
+ * @param {number} entity_id must be registered in the slot table
106
+ */
107
+ export function snapshotter_write_entity(buffer, world, slot_table, component_registry, entity_id) {
108
+ const network_id = slot_table.network_for(entity_id);
109
+ if (network_id < 0) {
110
+ throw new Error(`snapshotter_write_entity: entity ${entity_id} is not registered in the slot table`);
111
+ }
112
+
113
+ const type_count = component_registry.type_count();
114
+ let present = 0;
115
+ for (let type_id = 0; type_id < type_count; type_id++) {
116
+ if (world.hasComponent(entity_id, component_registry.class_of(type_id))) present++;
117
+ }
118
+
119
+ buffer.writeUintVar(network_id);
120
+ buffer.writeUint8(present);
121
+
122
+ for (let type_id = 0; type_id < type_count; type_id++) {
123
+ const klass = component_registry.class_of(type_id);
124
+ const component = world.getComponent(entity_id, klass);
125
+ if (component === undefined) continue;
126
+
127
+ buffer.writeUint8(type_id);
128
+ const len_position = buffer.position;
129
+ buffer.writeUint32(0);
130
+ const payload_start = buffer.position;
131
+ component_registry.adapter_for_id(type_id).serialize(buffer, component);
132
+ const payload_end = buffer.position;
133
+ buffer.position = len_position;
134
+ buffer.writeUint32(payload_end - payload_start);
135
+ buffer.position = payload_end;
136
+ }
137
+ }
138
+
96
139
  /**
97
140
  * Apply a snapshot stream to a (typically empty) world, creating each entity
98
141
  * and attaching components.
@@ -2,7 +2,7 @@
2
2
  * Per-peer "last acknowledged frame" tracker.
3
3
  *
4
4
  * The replicator sends actions for frames `[Baseline.last_acked(peer) + 1, current]`
5
- * to each peer. When the peer's ack arrives (via the {@link Channel}), it
5
+ * to each peer. When the peer's ack arrives (via its {@link Connection}), it
6
6
  * advances the baseline.
7
7
  *
8
8
  * Frame numbers are absolute uint32. At 60 Hz, 2^32 frames = 2.27 years —
@@ -4,7 +4,7 @@ import { assert } from "../../../core/assert.js";
4
4
  * Per-peer "last acknowledged frame" tracker.
5
5
  *
6
6
  * The replicator sends actions for frames `[Baseline.last_acked(peer) + 1, current]`
7
- * to each peer. When the peer's ack arrives (via the {@link Channel}), it
7
+ * to each peer. When the peer's ack arrives (via its {@link Connection}), it
8
8
  * advances the baseline.
9
9
  *
10
10
  * Frame numbers are absolute uint32. At 60 Hz, 2^32 frames = 2.27 years —
@@ -15,12 +15,13 @@
15
15
  * owns storage and addressing and nothing else — it has no opinion about when a
16
16
  * frame may be opened or whether two may be open at once.
17
17
  *
18
- * Writers go through {@link open} then {@link commit}. Readers go through
19
- * {@link has}, {@link buffer_for} and {@link write_end_for}. A slot is claimed
20
- * by `open`, so it reads as present from that moment; its recorded length only
21
- * becomes meaningful at `commit`, which is why reading a frame that is still
22
- * being written yields the previous occupant's length. Owners that allow a
23
- * frame to stay open across calls are responsible for saying so.
18
+ * Writers go through {@link open} (or {@link resume}, to add to a frame) then
19
+ * {@link commit}. Readers go through {@link has}, {@link buffer_for} and
20
+ * {@link write_end_for}. A slot is claimed by `open`, so it reads as present
21
+ * from that moment; its recorded length is what was last committed — 0 for a
22
+ * slot `open` claimed, the old end for a frame `resume` reopened — so a reader
23
+ * sees none of what is still being written. Owners that allow a frame to stay
24
+ * open across calls are responsible for saying so.
24
25
  *
25
26
  * @author Alex Goldring
26
27
  * @copyright Company Named Limited (c) 2026
@@ -49,6 +50,24 @@ export class FrameBufferRing {
49
50
  * @returns {BinaryBuffer} positioned at 0, ready to be written
50
51
  */
51
52
  open(frame: number): BinaryBuffer;
53
+ /**
54
+ * Claim the slot for `frame` to write AFTER what it already holds: a slot holding `frame`
55
+ * hands back its buffer positioned at its committed end, and any other slot is claimed as
56
+ * {@link open} claims it. {@link commit} then fixes the longer length.
57
+ *
58
+ * @param {number} frame absolute frame number
59
+ * @returns {BinaryBuffer} positioned at the frame's committed end, ready to be written
60
+ */
61
+ resume(frame: number): BinaryBuffer;
62
+ /**
63
+ * Drop every slot's bytes and keep its frame: each frame the ring holds still reads as present,
64
+ * and as holding nothing.
65
+ */
66
+ empty(): void;
67
+ /**
68
+ * Release every slot: no frame reads as present until it is written again.
69
+ */
70
+ clear(): void;
52
71
  /**
53
72
  * Record how far the writer got, fixing the frame's length for readers.
54
73
  *
@@ -1 +1 @@
1
- {"version":3,"file":"FrameBufferRing.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/state/FrameBufferRing.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH;IAoCI;;OAEG;IACH,4DAFW;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAC,EAmB9E;IA5BD;;;;;OAKG;IACH,yBAFU,MAAM,CAED;IAwBf;;;;;;OAMG;IACH,YAHW,MAAM,GACJ,YAAY,CAcxB;IAED;;;;OAIG;IACH,cAFW,MAAM,QAQhB;IAED;;;;;OAKG;IACH,WAHW,MAAM,GACJ,OAAO,CAMnB;IAED;;;;;;OAMG;IACH,kBAHW,MAAM,GACJ,YAAY,CAYxB;IAED;;;;;;OAMG;IACH,qBAHW,MAAM,GACJ,MAAM,CAQlB;;CACJ;6BAtK4B,sCAAsC"}
1
+ {"version":3,"file":"FrameBufferRing.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/state/FrameBufferRing.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH;IAoCI;;OAEG;IACH,4DAFW;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAC,EAmB9E;IA5BD;;;;;OAKG;IACH,yBAFU,MAAM,CAED;IAwBf;;;;;;OAMG;IACH,YAHW,MAAM,GACJ,YAAY,CAkBxB;IAED;;;;;;;OAOG;IACH,cAHW,MAAM,GACJ,YAAY,CAgBxB;IAED;;;OAGG;IACH,cAEC;IAED;;OAEG;IACH,cAGC;IAED;;;;OAIG;IACH,cAFW,MAAM,QAQhB;IAED;;;;;OAKG;IACH,WAHW,MAAM,GACJ,OAAO,CAMnB;IAED;;;;;;OAMG;IACH,kBAHW,MAAM,GACJ,YAAY,CAYxB;IAED;;;;;;OAMG;IACH,qBAHW,MAAM,GACJ,MAAM,CAQlB;;CACJ;6BAnN4B,sCAAsC"}
@@ -18,12 +18,13 @@ import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
18
18
  * owns storage and addressing and nothing else — it has no opinion about when a
19
19
  * frame may be opened or whether two may be open at once.
20
20
  *
21
- * Writers go through {@link open} then {@link commit}. Readers go through
22
- * {@link has}, {@link buffer_for} and {@link write_end_for}. A slot is claimed
23
- * by `open`, so it reads as present from that moment; its recorded length only
24
- * becomes meaningful at `commit`, which is why reading a frame that is still
25
- * being written yields the previous occupant's length. Owners that allow a
26
- * frame to stay open across calls are responsible for saying so.
21
+ * Writers go through {@link open} (or {@link resume}, to add to a frame) then
22
+ * {@link commit}. Readers go through {@link has}, {@link buffer_for} and
23
+ * {@link write_end_for}. A slot is claimed by `open`, so it reads as present
24
+ * from that moment; its recorded length is what was last committed — 0 for a
25
+ * slot `open` claimed, the old end for a frame `resume` reopened — so a reader
26
+ * sees none of what is still being written. Owners that allow a frame to stay
27
+ * open across calls are responsible for saying so.
27
28
  *
28
29
  * @author Alex Goldring
29
30
  * @copyright Company Named Limited (c) 2026
@@ -104,9 +105,53 @@ export class FrameBufferRing {
104
105
 
105
106
  this.#frame_numbers[slot] = frame;
106
107
 
108
+ // Nothing of the new occupant is committed yet. Left alone, a slot claimed and never
109
+ // committed would report the previous occupant's length, and `resume` would write after it.
110
+ this.#write_ends[slot] = 0;
111
+
107
112
  return buffer;
108
113
  }
109
114
 
115
+ /**
116
+ * Claim the slot for `frame` to write AFTER what it already holds: a slot holding `frame`
117
+ * hands back its buffer positioned at its committed end, and any other slot is claimed as
118
+ * {@link open} claims it. {@link commit} then fixes the longer length.
119
+ *
120
+ * @param {number} frame absolute frame number
121
+ * @returns {BinaryBuffer} positioned at the frame's committed end, ready to be written
122
+ */
123
+ resume(frame) {
124
+ assert.isNonNegativeInteger(frame, 'frame');
125
+
126
+ const slot = frame % this.frame_capacity;
127
+
128
+ if (this.#frame_numbers[slot] !== frame) {
129
+ return this.open(frame);
130
+ }
131
+
132
+ const buffer = this.#buffers[slot];
133
+
134
+ buffer.position = this.#write_ends[slot];
135
+
136
+ return buffer;
137
+ }
138
+
139
+ /**
140
+ * Drop every slot's bytes and keep its frame: each frame the ring holds still reads as present,
141
+ * and as holding nothing.
142
+ */
143
+ empty() {
144
+ this.#write_ends.fill(0);
145
+ }
146
+
147
+ /**
148
+ * Release every slot: no frame reads as present until it is written again.
149
+ */
150
+ clear() {
151
+ this.#frame_numbers.fill(-1);
152
+ this.#write_ends.fill(0);
153
+ }
154
+
110
155
  /**
111
156
  * Record how far the writer got, fixing the frame's length for readers.
112
157
  *
@@ -59,6 +59,10 @@ export class InputRing {
59
59
  * @param {function(BinaryBuffer): void} writer
60
60
  */
61
61
  write(frame: number, writer: (arg0: BinaryBuffer) => void): void;
62
+ /**
63
+ * Forget every frame's input: none reads as present until it is written again.
64
+ */
65
+ clear(): void;
62
66
  /**
63
67
  * @param {number} frame
64
68
  * @returns {boolean}
@@ -1 +1 @@
1
- {"version":3,"file":"InputRing.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/state/InputRing.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH;IAMI;;OAEG;IACH,qDAFW;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAE,EAIlE;IAED;;;OAGG;IACH,6BAEC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,aAHW,MAAM,kCACkB,IAAI,QAQtC;IAED;;;OAGG;IACH,WAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;;OAMG;IACH,kBAHW,MAAM,gBAKhB;IAED;;;OAGG;IACH,qBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;OAOG;IACH,+BAJW,MAAM,aACN,MAAM,aACG,MAAM,4BAAgB,MAAM,KAAG,IAAI,QAmBtD;;CACJ"}
1
+ {"version":3,"file":"InputRing.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/state/InputRing.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH;IAMI;;OAEG;IACH,qDAFW;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAE,EAIlE;IAED;;;OAGG;IACH,6BAEC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,aAHW,MAAM,kCACkB,IAAI,QAQtC;IAED;;OAEG;IACH,cAEC;IAED;;;OAGG;IACH,WAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;;OAMG;IACH,kBAHW,MAAM,gBAKhB;IAED;;;OAGG;IACH,qBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;OAOG;IACH,+BAJW,MAAM,aACN,MAAM,aACG,MAAM,4BAAgB,MAAM,KAAG,IAAI,QAmBtD;;CACJ"}