@woosh/meep-engine 2.138.0 → 2.138.1

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 (199) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/build/bundle-worker-terrain.js +1 -1
  3. package/package.json +1 -1
  4. package/src/core/assert.d.ts +6 -0
  5. package/src/core/assert.d.ts.map +1 -1
  6. package/src/core/assert.js +16 -3
  7. package/src/core/binary/half_to_float_uint16.js +1 -1
  8. package/src/core/binary/to_half_float_uint16.d.ts.map +1 -1
  9. package/src/core/binary/to_half_float_uint16.js +9 -4
  10. package/src/core/collection/table/RowFirstTableSpec.js +1 -1
  11. package/src/core/events/signal/Signal.d.ts.map +1 -1
  12. package/src/core/events/signal/Signal.js +53 -0
  13. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts +3 -3
  14. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.js +3 -3
  15. package/src/engine/Clock.d.ts +2 -2
  16. package/src/engine/Clock.js +2 -2
  17. package/src/engine/graphics/ecs/highlight/system/RenderableHighlightSystem.d.ts.map +1 -1
  18. package/src/engine/graphics/ecs/highlight/system/RenderableHighlightSystem.js +17 -29
  19. package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.d.ts.map +1 -1
  20. package/src/engine/graphics/ecs/highlight/system/ShadedGeometryHighlightSystem.js +18 -31
  21. package/src/engine/network/NetworkSession.d.ts +386 -0
  22. package/src/engine/network/NetworkSession.d.ts.map +1 -0
  23. package/src/engine/network/NetworkSession.js +1841 -0
  24. package/src/engine/network/PriorityFetch.d.ts.map +1 -1
  25. package/src/engine/network/PriorityFetch.js +3 -2
  26. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts +14 -0
  27. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts.map +1 -0
  28. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +44 -0
  29. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts +18 -0
  30. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts.map +1 -0
  31. package/src/engine/network/adapters/TransformInterpolationAdapter.js +79 -0
  32. package/src/engine/network/adapters/TransformReplicationAdapter.d.ts +37 -0
  33. package/src/engine/network/adapters/TransformReplicationAdapter.d.ts.map +1 -0
  34. package/src/engine/network/adapters/TransformReplicationAdapter.js +87 -0
  35. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts +18 -0
  36. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts.map +1 -0
  37. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +46 -0
  38. package/src/engine/network/convertPathToURL.js +107 -107
  39. package/src/engine/network/core/quantize/quantize_float.d.ts +54 -0
  40. package/src/engine/network/core/quantize/quantize_float.d.ts.map +1 -0
  41. package/src/engine/network/core/quantize/quantize_float.js +66 -0
  42. package/src/engine/network/core/quantize/quantize_position.d.ts +44 -0
  43. package/src/engine/network/core/quantize/quantize_position.d.ts.map +1 -0
  44. package/src/engine/network/core/quantize/quantize_position.js +54 -0
  45. package/src/engine/network/core/sequence/ack_bitfield.d.ts +47 -0
  46. package/src/engine/network/core/sequence/ack_bitfield.d.ts.map +1 -0
  47. package/src/engine/network/core/sequence/ack_bitfield.js +77 -0
  48. package/src/engine/network/core/sequence/seq16.d.ts +53 -0
  49. package/src/engine/network/core/sequence/seq16.d.ts.map +1 -0
  50. package/src/engine/network/core/sequence/seq16.js +69 -0
  51. package/src/engine/network/core/sequence/seq32.d.ts +55 -0
  52. package/src/engine/network/core/sequence/seq32.d.ts.map +1 -0
  53. package/src/engine/network/core/sequence/seq32.js +73 -0
  54. package/src/engine/network/diagnostics/BandwidthMeter.d.ts +76 -0
  55. package/src/engine/network/diagnostics/BandwidthMeter.d.ts.map +1 -0
  56. package/src/engine/network/diagnostics/BandwidthMeter.js +155 -0
  57. package/src/engine/network/diagnostics/ReplayLog.d.ts +74 -0
  58. package/src/engine/network/diagnostics/ReplayLog.d.ts.map +1 -0
  59. package/src/engine/network/diagnostics/ReplayLog.js +137 -0
  60. package/src/engine/network/diagnostics/SyncTest.d.ts +74 -0
  61. package/src/engine/network/diagnostics/SyncTest.d.ts.map +1 -0
  62. package/src/engine/network/diagnostics/SyncTest.js +151 -0
  63. package/src/engine/network/ecs/NetworkSystem.d.ts +57 -0
  64. package/src/engine/network/ecs/NetworkSystem.d.ts.map +1 -0
  65. package/src/engine/network/ecs/NetworkSystem.js +84 -0
  66. package/src/engine/network/ecs/components/NetworkIdentity.d.ts +58 -0
  67. package/src/engine/network/ecs/components/NetworkIdentity.d.ts.map +1 -0
  68. package/src/engine/network/ecs/components/NetworkIdentity.js +73 -0
  69. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.d.ts +40 -0
  70. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.d.ts.map +1 -0
  71. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js +64 -0
  72. package/src/engine/network/orchestrator/NetworkPeer.d.ts +389 -0
  73. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -0
  74. package/src/engine/network/orchestrator/NetworkPeer.js +1107 -0
  75. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +260 -0
  76. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -0
  77. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +425 -0
  78. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +217 -0
  79. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -0
  80. package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +562 -0
  81. package/src/engine/network/replication/Replicator.d.ts +134 -0
  82. package/src/engine/network/replication/Replicator.d.ts.map +1 -0
  83. package/src/engine/network/replication/Replicator.js +334 -0
  84. package/src/engine/network/replication/ScopeFilter.d.ts +64 -0
  85. package/src/engine/network/replication/ScopeFilter.d.ts.map +1 -0
  86. package/src/engine/network/replication/ScopeFilter.js +71 -0
  87. package/src/engine/network/sim/ActionLog.d.ts +94 -0
  88. package/src/engine/network/sim/ActionLog.d.ts.map +1 -0
  89. package/src/engine/network/sim/ActionLog.js +189 -0
  90. package/src/engine/network/sim/BinaryInterpolationAdapter.d.ts +58 -0
  91. package/src/engine/network/sim/BinaryInterpolationAdapter.d.ts.map +1 -0
  92. package/src/engine/network/sim/BinaryInterpolationAdapter.js +56 -0
  93. package/src/engine/network/sim/InterpolationLog.d.ts +165 -0
  94. package/src/engine/network/sim/InterpolationLog.d.ts.map +1 -0
  95. package/src/engine/network/sim/InterpolationLog.js +583 -0
  96. package/src/engine/network/sim/ReplicatedComponentRegistry.d.ts +59 -0
  97. package/src/engine/network/sim/ReplicatedComponentRegistry.d.ts.map +1 -0
  98. package/src/engine/network/sim/ReplicatedComponentRegistry.js +140 -0
  99. package/src/engine/network/sim/RewindEngine.d.ts +66 -0
  100. package/src/engine/network/sim/RewindEngine.d.ts.map +1 -0
  101. package/src/engine/network/sim/RewindEngine.js +182 -0
  102. package/src/engine/network/sim/SimAction.d.ts +133 -0
  103. package/src/engine/network/sim/SimAction.d.ts.map +1 -0
  104. package/src/engine/network/sim/SimAction.js +273 -0
  105. package/src/engine/network/sim/SimActionExecutor.d.ts +109 -0
  106. package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -0
  107. package/src/engine/network/sim/SimActionExecutor.js +238 -0
  108. package/src/engine/network/sim/SimActionRegistry.d.ts +60 -0
  109. package/src/engine/network/sim/SimActionRegistry.d.ts.map +1 -0
  110. package/src/engine/network/sim/SimActionRegistry.js +128 -0
  111. package/src/engine/network/sim/SmoothingState.d.ts +87 -0
  112. package/src/engine/network/sim/SmoothingState.d.ts.map +1 -0
  113. package/src/engine/network/sim/SmoothingState.js +223 -0
  114. package/src/engine/network/sim/Snapshotter.d.ts +98 -0
  115. package/src/engine/network/sim/Snapshotter.d.ts.map +1 -0
  116. package/src/engine/network/sim/Snapshotter.js +206 -0
  117. package/src/engine/network/sim/SpeculationLog.d.ts +53 -0
  118. package/src/engine/network/sim/SpeculationLog.d.ts.map +1 -0
  119. package/src/engine/network/sim/SpeculationLog.js +84 -0
  120. package/src/engine/network/state/Baseline.d.ts +48 -0
  121. package/src/engine/network/state/Baseline.d.ts.map +1 -0
  122. package/src/engine/network/state/Baseline.js +83 -0
  123. package/src/engine/network/state/ChangedEntitySet.d.ts +94 -0
  124. package/src/engine/network/state/ChangedEntitySet.d.ts.map +1 -0
  125. package/src/engine/network/state/ChangedEntitySet.js +256 -0
  126. package/src/engine/network/state/InputRing.d.ts +90 -0
  127. package/src/engine/network/state/InputRing.d.ts.map +1 -0
  128. package/src/engine/network/state/InputRing.js +173 -0
  129. package/src/engine/network/state/MutationLedger.d.ts +82 -0
  130. package/src/engine/network/state/MutationLedger.d.ts.map +1 -0
  131. package/src/engine/network/state/MutationLedger.js +182 -0
  132. package/src/engine/network/state/PriorityAccumulator.d.ts +104 -0
  133. package/src/engine/network/state/PriorityAccumulator.d.ts.map +1 -0
  134. package/src/engine/network/state/PriorityAccumulator.js +180 -0
  135. package/src/engine/network/state/ReplicationSlotTable.d.ts +78 -0
  136. package/src/engine/network/state/ReplicationSlotTable.d.ts.map +1 -0
  137. package/src/engine/network/state/ReplicationSlotTable.js +211 -0
  138. package/src/engine/network/time/AdaptiveRenderDelay.d.ts +128 -0
  139. package/src/engine/network/time/AdaptiveRenderDelay.d.ts.map +1 -0
  140. package/src/engine/network/time/AdaptiveRenderDelay.js +258 -0
  141. package/src/engine/network/time/JitterBuffer.d.ts +58 -0
  142. package/src/engine/network/time/JitterBuffer.d.ts.map +1 -0
  143. package/src/engine/network/time/JitterBuffer.js +116 -0
  144. package/src/engine/network/time/TimeDilation.d.ts +49 -0
  145. package/src/engine/network/time/TimeDilation.d.ts.map +1 -0
  146. package/src/engine/network/time/TimeDilation.js +62 -0
  147. package/src/engine/network/time/TimeSync.d.ts +68 -0
  148. package/src/engine/network/time/TimeSync.d.ts.map +1 -0
  149. package/src/engine/network/time/TimeSync.js +153 -0
  150. package/src/engine/network/transport/Channel.d.ts +74 -0
  151. package/src/engine/network/transport/Channel.d.ts.map +1 -0
  152. package/src/engine/network/transport/Channel.js +272 -0
  153. package/src/engine/network/transport/LoopbackTransport.d.ts +59 -0
  154. package/src/engine/network/transport/LoopbackTransport.d.ts.map +1 -0
  155. package/src/engine/network/transport/LoopbackTransport.js +194 -0
  156. package/src/engine/network/transport/ReliableCommandPipeline.d.ts +139 -0
  157. package/src/engine/network/transport/ReliableCommandPipeline.d.ts.map +1 -0
  158. package/src/engine/network/transport/ReliableCommandPipeline.js +291 -0
  159. package/src/engine/network/transport/Transport.d.ts +109 -0
  160. package/src/engine/network/transport/Transport.d.ts.map +1 -0
  161. package/src/engine/network/transport/Transport.js +119 -0
  162. package/src/engine/network/transport/adapters/NodeUDPTransport.d.ts +60 -0
  163. package/src/engine/network/transport/adapters/NodeUDPTransport.d.ts.map +1 -0
  164. package/src/engine/network/transport/adapters/NodeUDPTransport.js +206 -0
  165. package/src/engine/network/transport/adapters/SimulatedTransport.d.ts +110 -0
  166. package/src/engine/network/transport/adapters/SimulatedTransport.d.ts.map +1 -0
  167. package/src/engine/network/transport/adapters/SimulatedTransport.js +252 -0
  168. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.d.ts +33 -0
  169. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.d.ts.map +1 -0
  170. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +131 -0
  171. package/src/engine/network/transport/adapters/WebSocketTransport.d.ts +49 -0
  172. package/src/engine/network/transport/adapters/WebSocketTransport.d.ts.map +1 -0
  173. package/src/engine/network/transport/adapters/WebSocketTransport.js +180 -0
  174. package/src/engine/network/transport/adapters/WebTransportTransport.d.ts +73 -0
  175. package/src/engine/network/transport/adapters/WebTransportTransport.d.ts.map +1 -0
  176. package/src/engine/network/transport/adapters/WebTransportTransport.js +210 -0
  177. package/src/engine/network/transport/fragments/FragmentAssembler.d.ts +104 -0
  178. package/src/engine/network/transport/fragments/FragmentAssembler.d.ts.map +1 -0
  179. package/src/engine/network/transport/fragments/FragmentAssembler.js +291 -0
  180. package/src/engine/network/transport/fragments/FragmentRetention.d.ts +103 -0
  181. package/src/engine/network/transport/fragments/FragmentRetention.d.ts.map +1 -0
  182. package/src/engine/network/transport/fragments/FragmentRetention.js +194 -0
  183. package/src/engine/network/transport/fragments/fragment_send.d.ts +53 -0
  184. package/src/engine/network/transport/fragments/fragment_send.d.ts.map +1 -0
  185. package/src/engine/network/transport/fragments/fragment_send.js +147 -0
  186. package/src/engine/network/transport/fragments/packet_size.d.ts +93 -0
  187. package/src/engine/network/transport/fragments/packet_size.d.ts.map +1 -0
  188. package/src/engine/network/transport/fragments/packet_size.js +101 -0
  189. package/src/engine/network/xhr.js +23 -23
  190. package/src/engine/simulation/Ticker.d.ts +7 -0
  191. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  192. package/src/engine/simulation/Ticker.js +15 -4
  193. package/src/engine/network/DataChannel.js +0 -1210
  194. package/src/engine/network/RemoteController.d.ts +0 -23
  195. package/src/engine/network/RemoteController.d.ts.map +0 -1
  196. package/src/engine/network/RemoteController.js +0 -114
  197. package/src/engine/network/remoteEditor.d.ts +0 -2
  198. package/src/engine/network/remoteEditor.d.ts.map +0 -1
  199. package/src/engine/network/remoteEditor.js +0 -142
@@ -0,0 +1,155 @@
1
+ import { assert } from "../../../core/assert.js";
2
+
3
+ /**
4
+ * Aggregates `getStats()` snapshots from one or more named sources (typically
5
+ * a `Transport`, `Channel`, or `NetworkPeer.channel_for(peer)`) and reports
6
+ * recent throughput rates.
7
+ *
8
+ * Per-source stats are queried via the source's `getStats()` method. A "sample"
9
+ * captures the current totals at a wall-clock moment; the meter retains a
10
+ * sliding window of samples and computes bytes-per-second / packets-per-second
11
+ * over the window.
12
+ *
13
+ * No timer of its own — caller invokes {@link sample} per tick (or whenever).
14
+ *
15
+ * @author Alex Goldring
16
+ * @copyright Company Named Limited (c) 2025
17
+ */
18
+ export class BandwidthMeter {
19
+ #sources;
20
+ #samples;
21
+
22
+
23
+ /**
24
+ * @param {{ window_seconds?: number }} [options]
25
+ */
26
+ constructor({ window_seconds = 5 } = {}) {
27
+ assert.isNumber(window_seconds, 'window_seconds');
28
+ if (window_seconds <= 0) throw new Error('BandwidthMeter: window_seconds must be > 0');
29
+
30
+ /** @readonly */
31
+ this.window_seconds = window_seconds;
32
+
33
+ /**
34
+ * Registered sources. Each is { name: string, source: { getStats() } }.
35
+ * @type {{name: string, source: any}[]}
36
+ * @private
37
+ */
38
+ this.#sources = [];
39
+
40
+ /**
41
+ * Per-window samples (oldest first). Each entry: { time_ms, totals }.
42
+ * @type {{time_ms: number, totals: {bytes_in: number, bytes_out: number, packets_in: number, packets_out: number}}[]}
43
+ * @private
44
+ */
45
+ this.#samples = [];
46
+ }
47
+
48
+ /**
49
+ * @param {string} name human-readable label
50
+ * @param {{ getStats(): {bytes_in: number, bytes_out: number, packets_in: number, packets_out: number} }} source
51
+ */
52
+ add_source(name, source) {
53
+ assert.isString(name, 'name');
54
+ assert.ok(source && typeof source.getStats === 'function', 'source must implement getStats()');
55
+ this.#sources.push({ name, source });
56
+ }
57
+
58
+ /**
59
+ * Capture a fresh sample at the given wall-clock time (caller-supplied so
60
+ * tests can use a deterministic clock; in production typically `Date.now()`).
61
+ *
62
+ * @param {number} now_ms
63
+ */
64
+ sample(now_ms) {
65
+ assert.isNumber(now_ms, 'now_ms');
66
+ const totals = this.#sum_totals();
67
+ this.#samples.push({ time_ms: now_ms, totals });
68
+
69
+ // Drop samples older than the window, but keep one straddling expired
70
+ // sample if it's the only thing standing between us and "only the
71
+ // just-pushed sample is left" (which would silently make __rate return
72
+ // 0 after a long quiet stretch). Once we have 2+ in-window samples the
73
+ // straddle is no longer needed and would bias the rate, so drop it.
74
+ const cutoff = now_ms - this.window_seconds * 1000;
75
+ while (this.#samples.length >= 2 && this.#samples[1].time_ms < cutoff) {
76
+ this.#samples.shift();
77
+ }
78
+ if (this.#samples.length >= 3 && this.#samples[0].time_ms < cutoff) {
79
+ this.#samples.shift();
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Cumulative totals across all sources, right now (re-queries getStats()).
85
+ * Independent of the sample window.
86
+ */
87
+ cumulative() {
88
+ return this.#sum_totals();
89
+ }
90
+
91
+ /**
92
+ * Bytes-per-second received, averaged over the sample window.
93
+ * Returns 0 if fewer than 2 samples have been taken.
94
+ */
95
+ rate_bytes_in() {
96
+ return this.#rate('bytes_in');
97
+ }
98
+ rate_bytes_out() {
99
+ return this.#rate('bytes_out');
100
+ }
101
+ rate_packets_in() {
102
+ return this.#rate('packets_in');
103
+ }
104
+ rate_packets_out() {
105
+ return this.#rate('packets_out');
106
+ }
107
+
108
+ /**
109
+ * Per-source breakdown of cumulative totals (re-queries getStats()).
110
+ * @returns {Array<{name: string, totals: object}>}
111
+ */
112
+ per_source() {
113
+ const result = [];
114
+ for (let i = 0; i < this.#sources.length; i++) {
115
+ const s = this.#sources[i];
116
+ result.push({ name: s.name, totals: s.source.getStats() });
117
+ }
118
+ return result;
119
+ }
120
+
121
+ /**
122
+ * Drop all retained samples. Source registrations are preserved.
123
+ */
124
+ reset_samples() {
125
+ this.#samples.length = 0;
126
+ }
127
+
128
+ /**
129
+ * @private
130
+ */
131
+ #sum_totals() {
132
+ let bytes_in = 0, bytes_out = 0, packets_in = 0, packets_out = 0;
133
+ for (let i = 0; i < this.#sources.length; i++) {
134
+ const s = this.#sources[i].source.getStats();
135
+ bytes_in += s.bytes_in;
136
+ bytes_out += s.bytes_out;
137
+ packets_in += s.packets_in;
138
+ packets_out += s.packets_out;
139
+ }
140
+ return { bytes_in, bytes_out, packets_in, packets_out };
141
+ }
142
+
143
+ /**
144
+ * @private
145
+ */
146
+ #rate(key) {
147
+ const n = this.#samples.length;
148
+ if (n < 2) return 0;
149
+ const oldest = this.#samples[0];
150
+ const newest = this.#samples[n - 1];
151
+ const dt_seconds = (newest.time_ms - oldest.time_ms) / 1000;
152
+ if (dt_seconds <= 0) return 0;
153
+ return (newest.totals[key] - oldest.totals[key]) / dt_seconds;
154
+ }
155
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Append-only record of action bytes per frame. Useful for offline replay,
3
+ * bug reproduction, and demos.
4
+ *
5
+ * Each entry stores a copy of the action bytes for one frame. The bytes
6
+ * typically come from the per-frame `ActionLog` buffer (the action portion
7
+ * after `Replicator.pack_for_peer` strips prior-state). The format on the wire
8
+ * is the same one `Replicator.unpack_from_peer` consumes, so a recorded
9
+ * session can be replayed by feeding the bytes back through a peer.
10
+ *
11
+ * Persistence to disk / IndexedDB / network is left to the application —
12
+ * `serialize_to_buffer` produces a single contiguous `BinaryBuffer` snapshot.
13
+ *
14
+ * @author Alex Goldring
15
+ * @copyright Company Named Limited (c) 2025
16
+ */
17
+ export class ReplayLog {
18
+ /**
19
+ * Build a ReplayLog from a previously-serialized buffer. Reads from the
20
+ * buffer's current position; advances the position past the consumed bytes.
21
+ *
22
+ * @param {BinaryBuffer} buffer
23
+ * @returns {ReplayLog}
24
+ */
25
+ static deserialize_from_buffer(buffer: BinaryBuffer): ReplayLog;
26
+ /**
27
+ * Append a frame's bytes. The source array is copied; caller may reuse it.
28
+ *
29
+ * @param {number} frame
30
+ * @param {Uint8Array} bytes
31
+ * @param {number} length
32
+ */
33
+ record(frame: number, bytes: Uint8Array, length: number): void;
34
+ /**
35
+ * @returns {number}
36
+ */
37
+ size(): number;
38
+ /**
39
+ * Iterate frames in `[start_frame, end_frame]` (inclusive).
40
+ * @param {number} start_frame
41
+ * @param {number} end_frame
42
+ * @param {function(number, Uint8Array): void} callback receives (frame, bytes)
43
+ */
44
+ for_each_in_range(start_frame: number, end_frame: number, callback: (arg0: number, arg1: Uint8Array) => void): void;
45
+ /**
46
+ * Drop all entries.
47
+ */
48
+ clear(): void;
49
+ /**
50
+ * Frame number of the first entry, or -1 if empty.
51
+ */
52
+ earliest_frame(): any;
53
+ /**
54
+ * Frame number of the last entry, or -1 if empty.
55
+ */
56
+ latest_frame(): any;
57
+ /**
58
+ * Serialize the entire log to a fresh buffer. Format:
59
+ * ```
60
+ * varint: entry_count
61
+ * loop:
62
+ * varint: frame
63
+ * varint: bytes_length
64
+ * bytes: payload
65
+ * ```
66
+ *
67
+ * @param {BinaryBuffer} [buffer] if provided, written into; otherwise a fresh one is created
68
+ * @returns {BinaryBuffer}
69
+ */
70
+ serialize_to_buffer(buffer?: BinaryBuffer): BinaryBuffer;
71
+ #private;
72
+ }
73
+ import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
74
+ //# sourceMappingURL=ReplayLog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReplayLog.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/diagnostics/ReplayLog.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AACH;IAkGI;;;;;;OAMG;IACH,uCAHW,YAAY,GACV,SAAS,CAarB;IAxGD;;;;;;OAMG;IACH,cAJW,MAAM,SACN,UAAU,UACV,MAAM,QAQhB;IAED;;OAEG;IACH,QAFa,MAAM,CAIlB;IAED;;;;;OAKG;IACH,+BAJW,MAAM,aACN,MAAM,mBACG,MAAM,QAAE,UAAU,KAAG,IAAI,QAY5C;IAED;;OAEG;IACH,cAEC;IAED;;OAEG;IACH,sBAEC;IAED;;OAEG;IACH,oBAEC;IAED;;;;;;;;;;;;OAYG;IACH,6BAHW,YAAY,GACV,YAAY,CAYxB;;CAqBJ;6BAvI4B,sCAAsC"}
@@ -0,0 +1,137 @@
1
+ import { assert } from "../../../core/assert.js";
2
+ import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
3
+
4
+ /**
5
+ * Append-only record of action bytes per frame. Useful for offline replay,
6
+ * bug reproduction, and demos.
7
+ *
8
+ * Each entry stores a copy of the action bytes for one frame. The bytes
9
+ * typically come from the per-frame `ActionLog` buffer (the action portion
10
+ * after `Replicator.pack_for_peer` strips prior-state). The format on the wire
11
+ * is the same one `Replicator.unpack_from_peer` consumes, so a recorded
12
+ * session can be replayed by feeding the bytes back through a peer.
13
+ *
14
+ * Persistence to disk / IndexedDB / network is left to the application —
15
+ * `serialize_to_buffer` produces a single contiguous `BinaryBuffer` snapshot.
16
+ *
17
+ * @author Alex Goldring
18
+ * @copyright Company Named Limited (c) 2025
19
+ */
20
+ export class ReplayLog {
21
+ #entries;
22
+
23
+
24
+ constructor() {
25
+ /**
26
+ * @type {{frame: number, bytes: Uint8Array}[]}
27
+ * @private
28
+ */
29
+ this.#entries = [];
30
+ }
31
+
32
+ /**
33
+ * Append a frame's bytes. The source array is copied; caller may reuse it.
34
+ *
35
+ * @param {number} frame
36
+ * @param {Uint8Array} bytes
37
+ * @param {number} length
38
+ */
39
+ record(frame, bytes, length) {
40
+ assert.isNonNegativeInteger(frame, 'frame');
41
+ assert.isNonNegativeInteger(length, 'length');
42
+ const copy = new Uint8Array(length);
43
+ copy.set(bytes.subarray(0, length));
44
+ this.#entries.push({ frame, bytes: copy });
45
+ }
46
+
47
+ /**
48
+ * @returns {number}
49
+ */
50
+ size() {
51
+ return this.#entries.length;
52
+ }
53
+
54
+ /**
55
+ * Iterate frames in `[start_frame, end_frame]` (inclusive).
56
+ * @param {number} start_frame
57
+ * @param {number} end_frame
58
+ * @param {function(number, Uint8Array): void} callback receives (frame, bytes)
59
+ */
60
+ for_each_in_range(start_frame, end_frame, callback) {
61
+ assert.isNonNegativeInteger(start_frame, 'start_frame');
62
+ assert.isNonNegativeInteger(end_frame, 'end_frame');
63
+ assert.isFunction(callback, 'callback');
64
+ for (let i = 0; i < this.#entries.length; i++) {
65
+ const e = this.#entries[i];
66
+ if (e.frame >= start_frame && e.frame <= end_frame) {
67
+ callback(e.frame, e.bytes);
68
+ }
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Drop all entries.
74
+ */
75
+ clear() {
76
+ this.#entries.length = 0;
77
+ }
78
+
79
+ /**
80
+ * Frame number of the first entry, or -1 if empty.
81
+ */
82
+ earliest_frame() {
83
+ return this.#entries.length === 0 ? -1 : this.#entries[0].frame;
84
+ }
85
+
86
+ /**
87
+ * Frame number of the last entry, or -1 if empty.
88
+ */
89
+ latest_frame() {
90
+ return this.#entries.length === 0 ? -1 : this.#entries[this.#entries.length - 1].frame;
91
+ }
92
+
93
+ /**
94
+ * Serialize the entire log to a fresh buffer. Format:
95
+ * ```
96
+ * varint: entry_count
97
+ * loop:
98
+ * varint: frame
99
+ * varint: bytes_length
100
+ * bytes: payload
101
+ * ```
102
+ *
103
+ * @param {BinaryBuffer} [buffer] if provided, written into; otherwise a fresh one is created
104
+ * @returns {BinaryBuffer}
105
+ */
106
+ serialize_to_buffer(buffer) {
107
+ const buf = buffer === undefined ? new BinaryBuffer() : buffer;
108
+ buf.writeUintVar(this.#entries.length);
109
+ for (let i = 0; i < this.#entries.length; i++) {
110
+ const e = this.#entries[i];
111
+ buf.writeUintVar(e.frame);
112
+ buf.writeUintVar(e.bytes.length);
113
+ buf.writeBytes(e.bytes, 0, e.bytes.length);
114
+ }
115
+ return buf;
116
+ }
117
+
118
+ /**
119
+ * Build a ReplayLog from a previously-serialized buffer. Reads from the
120
+ * buffer's current position; advances the position past the consumed bytes.
121
+ *
122
+ * @param {BinaryBuffer} buffer
123
+ * @returns {ReplayLog}
124
+ */
125
+ static deserialize_from_buffer(buffer) {
126
+ const log = new ReplayLog();
127
+ const count = buffer.readUintVar();
128
+ for (let i = 0; i < count; i++) {
129
+ const frame = buffer.readUintVar();
130
+ const length = buffer.readUintVar();
131
+ const bytes = new Uint8Array(length);
132
+ buffer.readUint8Array(bytes, 0, length);
133
+ log.#entries.push({ frame, bytes });
134
+ }
135
+ return log;
136
+ }
137
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Compute a 32-bit fingerprint of every replicated component on every entity
3
+ * in the world. Used by `SyncTest` and as a general "did the world state change?"
4
+ * primitive.
5
+ *
6
+ * The hash is FNV-1a — fast, stable, no dependencies. Not cryptographic.
7
+ *
8
+ * Iteration order: entities in the order yielded by `world` (the
9
+ * EntityComponentDataset's iterator), then components in `type_id` order.
10
+ * As long as both sides iterate identically, the fingerprint is comparable.
11
+ *
12
+ * @param {EntityComponentDataset} world
13
+ * @param {ReplicatedComponentRegistry} component_registry
14
+ * @param {BinaryBuffer} [scratch] optional reusable scratch; resets to position 0 each call
15
+ * @returns {number} 32-bit unsigned hash
16
+ */
17
+ export function fingerprint_world(world: EntityComponentDataset, component_registry: ReplicatedComponentRegistry, scratch?: BinaryBuffer): number;
18
+ /**
19
+ * Diagnostic harness for catching rewind / replay bugs.
20
+ *
21
+ * Usage:
22
+ * - `harness.checkpoint()` — records the current world fingerprint.
23
+ * - … run tick logic …
24
+ * - `harness.assert_recoverable_to_checkpoint(rewind_engine, current_frame, target_frame)`
25
+ * rewinds and asserts the world's fingerprint matches the checkpoint.
26
+ *
27
+ * For full nondeterminism detection (GGPO-style "save → advance → load →
28
+ * advance → diff"), the application's tick logic must be re-runnable; that
29
+ * coordination is left to the caller. This harness provides the fingerprint +
30
+ * compare primitives and the assertion shape.
31
+ *
32
+ * @author Alex Goldring
33
+ * @copyright Company Named Limited (c) 2025
34
+ */
35
+ export class SyncTest {
36
+ /**
37
+ * @param {{
38
+ * world: EntityComponentDataset,
39
+ * component_registry: ReplicatedComponentRegistry,
40
+ * }} options
41
+ */
42
+ constructor({ world, component_registry }: {
43
+ world: EntityComponentDataset;
44
+ component_registry: ReplicatedComponentRegistry;
45
+ });
46
+ /** @type {EntityComponentDataset} */
47
+ world: EntityComponentDataset;
48
+ /** @type {ReplicatedComponentRegistry} */
49
+ component_registry: ReplicatedComponentRegistry;
50
+ /**
51
+ * Capture the current world state as the reference point for the next
52
+ * `assert_recoverable_to_checkpoint` call.
53
+ */
54
+ checkpoint(): void;
55
+ /**
56
+ * @returns {number}
57
+ */
58
+ current_fingerprint(): number;
59
+ /**
60
+ * Rewind from `current_frame` back to `target_frame` (typically the frame
61
+ * at which `checkpoint()` was called) and assert the resulting world state
62
+ * matches the checkpoint.
63
+ *
64
+ * Throws on mismatch with a diagnostic message including both fingerprints.
65
+ *
66
+ * @param {RewindEngine} rewind_engine
67
+ * @param {number} current_frame
68
+ * @param {number} target_frame
69
+ */
70
+ assert_recoverable_to_checkpoint(rewind_engine: RewindEngine, current_frame: number, target_frame: number): void;
71
+ #private;
72
+ }
73
+ import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
74
+ //# sourceMappingURL=SyncTest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SyncTest.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/diagnostics/SyncTest.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AACH,4HAHW,YAAY,GACV,MAAM,CAmClB;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH;IAMI;;;;;OAKG;IACH,2CALW;QACV,KAAS,yBAAyB;QAClC,kBAAsB,8BAA8B;KACjD,EAoBH;IAdG,qCAAqC;IACrC,8BAAkB;IAElB,0CAA0C;IAC1C,gDAA4C;IAYhD;;;OAGG;IACH,mBAGC;IAED;;OAEG;IACH,uBAFa,MAAM,CAIlB;IAED;;;;;;;;;;OAUG;IACH,6EAHW,MAAM,gBACN,MAAM,QAahB;;CACJ;6BArJ4B,sCAAsC"}
@@ -0,0 +1,151 @@
1
+ import { assert } from "../../../core/assert.js";
2
+ import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
3
+
4
+ /**
5
+ * Compute a 32-bit fingerprint of every replicated component on every entity
6
+ * in the world. Used by `SyncTest` and as a general "did the world state change?"
7
+ * primitive.
8
+ *
9
+ * The hash is FNV-1a — fast, stable, no dependencies. Not cryptographic.
10
+ *
11
+ * Iteration order: entities in the order yielded by `world` (the
12
+ * EntityComponentDataset's iterator), then components in `type_id` order.
13
+ * As long as both sides iterate identically, the fingerprint is comparable.
14
+ *
15
+ * @param {EntityComponentDataset} world
16
+ * @param {ReplicatedComponentRegistry} component_registry
17
+ * @param {BinaryBuffer} [scratch] optional reusable scratch; resets to position 0 each call
18
+ * @returns {number} 32-bit unsigned hash
19
+ */
20
+ export function fingerprint_world(world, component_registry, scratch) {
21
+ assert.ok(world, 'world required');
22
+ assert.ok(component_registry, 'component_registry required');
23
+
24
+ const buffer = scratch === undefined ? new BinaryBuffer() : scratch;
25
+ let h = 0x811c9dc5; // FNV-1a 32-bit offset basis
26
+
27
+ const type_count = component_registry.type_count();
28
+ for (const entity of world) {
29
+ for (let type_id = 0; type_id < type_count; type_id++) {
30
+ const klass = component_registry.class_of(type_id);
31
+ if (!world.hasComponent(entity, klass)) continue;
32
+
33
+ const adapter = component_registry.adapter_for_id(type_id);
34
+ buffer.position = 0;
35
+ adapter.serialize(buffer, world.getComponent(entity, klass));
36
+
37
+ // Mix entity_id and type_id into the hash so structurally-identical
38
+ // components on different entities/types produce different prints.
39
+ h = __fnv_byte(h, entity & 0xFF);
40
+ h = __fnv_byte(h, (entity >> 8) & 0xFF);
41
+ h = __fnv_byte(h, (entity >> 16) & 0xFF);
42
+ h = __fnv_byte(h, (entity >> 24) & 0xFF);
43
+ h = __fnv_byte(h, type_id);
44
+
45
+ const bytes = buffer.raw_bytes;
46
+ const len = buffer.position;
47
+ for (let i = 0; i < len; i++) {
48
+ h = __fnv_byte(h, bytes[i]);
49
+ }
50
+ }
51
+ }
52
+ return h >>> 0;
53
+ }
54
+
55
+ /**
56
+ * @private
57
+ */
58
+ function __fnv_byte(h, byte) {
59
+ h ^= byte;
60
+ return Math.imul(h, 0x01000193) >>> 0;
61
+ }
62
+
63
+ /**
64
+ * Diagnostic harness for catching rewind / replay bugs.
65
+ *
66
+ * Usage:
67
+ * - `harness.checkpoint()` — records the current world fingerprint.
68
+ * - … run tick logic …
69
+ * - `harness.assert_recoverable_to_checkpoint(rewind_engine, current_frame, target_frame)`
70
+ * rewinds and asserts the world's fingerprint matches the checkpoint.
71
+ *
72
+ * For full nondeterminism detection (GGPO-style "save → advance → load →
73
+ * advance → diff"), the application's tick logic must be re-runnable; that
74
+ * coordination is left to the caller. This harness provides the fingerprint +
75
+ * compare primitives and the assertion shape.
76
+ *
77
+ * @author Alex Goldring
78
+ * @copyright Company Named Limited (c) 2025
79
+ */
80
+ export class SyncTest {
81
+ #scratch;
82
+ #checkpoint_hash;
83
+ #has_checkpoint;
84
+
85
+
86
+ /**
87
+ * @param {{
88
+ * world: EntityComponentDataset,
89
+ * component_registry: ReplicatedComponentRegistry,
90
+ * }} options
91
+ */
92
+ constructor({ world, component_registry }) {
93
+ assert.ok(world, 'world required');
94
+ assert.ok(component_registry, 'component_registry required');
95
+
96
+ /** @type {EntityComponentDataset} */
97
+ this.world = world;
98
+
99
+ /** @type {ReplicatedComponentRegistry} */
100
+ this.component_registry = component_registry;
101
+
102
+ /** Reused scratch for fingerprint serialization. */
103
+ this.#scratch = new BinaryBuffer();
104
+
105
+ /** @type {number} @private */
106
+ this.#checkpoint_hash = 0;
107
+
108
+ /** @type {boolean} @private */
109
+ this.#has_checkpoint = false;
110
+ }
111
+
112
+ /**
113
+ * Capture the current world state as the reference point for the next
114
+ * `assert_recoverable_to_checkpoint` call.
115
+ */
116
+ checkpoint() {
117
+ this.#checkpoint_hash = fingerprint_world(this.world, this.component_registry, this.#scratch);
118
+ this.#has_checkpoint = true;
119
+ }
120
+
121
+ /**
122
+ * @returns {number}
123
+ */
124
+ current_fingerprint() {
125
+ return fingerprint_world(this.world, this.component_registry, this.#scratch);
126
+ }
127
+
128
+ /**
129
+ * Rewind from `current_frame` back to `target_frame` (typically the frame
130
+ * at which `checkpoint()` was called) and assert the resulting world state
131
+ * matches the checkpoint.
132
+ *
133
+ * Throws on mismatch with a diagnostic message including both fingerprints.
134
+ *
135
+ * @param {RewindEngine} rewind_engine
136
+ * @param {number} current_frame
137
+ * @param {number} target_frame
138
+ */
139
+ assert_recoverable_to_checkpoint(rewind_engine, current_frame, target_frame) {
140
+ assert.ok(this.#has_checkpoint, 'SyncTest: no checkpoint() taken before assertion');
141
+ rewind_engine.rewind_to(current_frame, target_frame);
142
+ const after = fingerprint_world(this.world, this.component_registry, this.#scratch);
143
+ if (after !== this.#checkpoint_hash) {
144
+ throw new Error(
145
+ `SyncTest: world fingerprint after rewind (${after.toString(16)}) ` +
146
+ `does not match checkpoint (${this.#checkpoint_hash.toString(16)}) ` +
147
+ `— rewind is non-deterministic or did not fully restore state`
148
+ );
149
+ }
150
+ }
151
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * ECS system that owns the per-entity replication lifecycle.
3
+ *
4
+ * Responsibilities:
5
+ * - When an entity gains a {@link NetworkIdentity}, allocate a `network_id`
6
+ * in the peer's slot table (or honour an explicit pre-set one — used by
7
+ * `Snapshotter` and incoming-packet flows).
8
+ * - When an entity loses its `NetworkIdentity` (or is destroyed), free the
9
+ * slot.
10
+ *
11
+ * **Not** responsible for the per-tick `begin_tick` / `end_tick` cadence —
12
+ * the engine's `EntityManager` doesn't expose pre/post-simulate hooks, so the
13
+ * application code wraps its `engine.simulate(dt)` call:
14
+ *
15
+ * ```js
16
+ * peer.begin_tick(frame_number);
17
+ * engine.simulate(dt);
18
+ * peer.end_tick();
19
+ * ```
20
+ *
21
+ * Add this system to the engine once per `NetworkPeer` instance:
22
+ *
23
+ * ```js
24
+ * const peer = new NetworkPeer({ ... });
25
+ * const network_system = new NetworkSystem(peer);
26
+ * engine.entityManager.addSystem(network_system);
27
+ * ```
28
+ *
29
+ * @author Alex Goldring
30
+ * @copyright Company Named Limited (c) 2025
31
+ */
32
+ export class NetworkSystem extends System<any, any, any, any, any> {
33
+ /**
34
+ * @param {NetworkPeer} peer
35
+ */
36
+ constructor(peer: NetworkPeer);
37
+ dependencies: (typeof NetworkIdentity)[];
38
+ components_used: ResourceAccessSpecification<typeof NetworkIdentity>[];
39
+ /**
40
+ * @type {NetworkPeer}
41
+ */
42
+ peer: NetworkPeer;
43
+ /**
44
+ * @param {NetworkIdentity} identity
45
+ * @param {number} entity
46
+ */
47
+ link(identity: NetworkIdentity, entity: number): void;
48
+ /**
49
+ * @param {NetworkIdentity} identity
50
+ * @param {number} entity
51
+ */
52
+ unlink(identity: NetworkIdentity, entity: number): void;
53
+ }
54
+ import { System } from "../../ecs/System.js";
55
+ import { NetworkIdentity } from "./components/NetworkIdentity.js";
56
+ import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
57
+ //# sourceMappingURL=NetworkSystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NetworkSystem.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/ecs/NetworkSystem.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH;IAQI;;OAEG;IACH,+BAQC;IAjBD,yCAAiC;IAEjC,uEAEE;IASE;;OAEG;IACH,kBAAgB;IAGpB;;;OAGG;IACH,eAHW,eAAe,UACf,MAAM,QAWhB;IAED;;;OAGG;IACH,iBAHW,eAAe,UACf,MAAM,QAOhB;CACJ;uBAhFsB,qBAAqB;gCACZ,iCAAiC;4CAFrB,oDAAoD"}