rollback-netcode 0.0.4

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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +140 -0
  3. package/dist/debug.d.ts +29 -0
  4. package/dist/debug.d.ts.map +1 -0
  5. package/dist/debug.js +56 -0
  6. package/dist/debug.js.map +1 -0
  7. package/dist/index.d.ts +62 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +57 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/protocol/encoding.d.ts +80 -0
  12. package/dist/protocol/encoding.d.ts.map +1 -0
  13. package/dist/protocol/encoding.js +992 -0
  14. package/dist/protocol/encoding.js.map +1 -0
  15. package/dist/protocol/messages.d.ts +271 -0
  16. package/dist/protocol/messages.d.ts.map +1 -0
  17. package/dist/protocol/messages.js +114 -0
  18. package/dist/protocol/messages.js.map +1 -0
  19. package/dist/rollback/engine.d.ts +261 -0
  20. package/dist/rollback/engine.d.ts.map +1 -0
  21. package/dist/rollback/engine.js +543 -0
  22. package/dist/rollback/engine.js.map +1 -0
  23. package/dist/rollback/input-buffer.d.ts +225 -0
  24. package/dist/rollback/input-buffer.d.ts.map +1 -0
  25. package/dist/rollback/input-buffer.js +483 -0
  26. package/dist/rollback/input-buffer.js.map +1 -0
  27. package/dist/rollback/snapshot-buffer.d.ts +119 -0
  28. package/dist/rollback/snapshot-buffer.d.ts.map +1 -0
  29. package/dist/rollback/snapshot-buffer.js +256 -0
  30. package/dist/rollback/snapshot-buffer.js.map +1 -0
  31. package/dist/session/desync-manager.d.ts +106 -0
  32. package/dist/session/desync-manager.d.ts.map +1 -0
  33. package/dist/session/desync-manager.js +136 -0
  34. package/dist/session/desync-manager.js.map +1 -0
  35. package/dist/session/lag-monitor.d.ts +69 -0
  36. package/dist/session/lag-monitor.d.ts.map +1 -0
  37. package/dist/session/lag-monitor.js +74 -0
  38. package/dist/session/lag-monitor.js.map +1 -0
  39. package/dist/session/message-builders.d.ts +86 -0
  40. package/dist/session/message-builders.d.ts.map +1 -0
  41. package/dist/session/message-builders.js +199 -0
  42. package/dist/session/message-builders.js.map +1 -0
  43. package/dist/session/message-router.d.ts +61 -0
  44. package/dist/session/message-router.d.ts.map +1 -0
  45. package/dist/session/message-router.js +105 -0
  46. package/dist/session/message-router.js.map +1 -0
  47. package/dist/session/player-manager.d.ts +100 -0
  48. package/dist/session/player-manager.d.ts.map +1 -0
  49. package/dist/session/player-manager.js +160 -0
  50. package/dist/session/player-manager.js.map +1 -0
  51. package/dist/session/session.d.ts +379 -0
  52. package/dist/session/session.d.ts.map +1 -0
  53. package/dist/session/session.js +1294 -0
  54. package/dist/session/session.js.map +1 -0
  55. package/dist/session/topology.d.ts +66 -0
  56. package/dist/session/topology.d.ts.map +1 -0
  57. package/dist/session/topology.js +72 -0
  58. package/dist/session/topology.js.map +1 -0
  59. package/dist/transport/adapter.d.ts +99 -0
  60. package/dist/transport/adapter.d.ts.map +1 -0
  61. package/dist/transport/adapter.js +8 -0
  62. package/dist/transport/adapter.js.map +1 -0
  63. package/dist/transport/local.d.ts +192 -0
  64. package/dist/transport/local.d.ts.map +1 -0
  65. package/dist/transport/local.js +435 -0
  66. package/dist/transport/local.js.map +1 -0
  67. package/dist/transport/transforming.d.ts +177 -0
  68. package/dist/transport/transforming.d.ts.map +1 -0
  69. package/dist/transport/transforming.js +407 -0
  70. package/dist/transport/transforming.js.map +1 -0
  71. package/dist/transport/webrtc.d.ts +285 -0
  72. package/dist/transport/webrtc.d.ts.map +1 -0
  73. package/dist/transport/webrtc.js +734 -0
  74. package/dist/transport/webrtc.js.map +1 -0
  75. package/dist/types.d.ts +394 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +256 -0
  78. package/dist/types.js.map +1 -0
  79. package/dist/utils/rate-limiter.d.ts +59 -0
  80. package/dist/utils/rate-limiter.d.ts.map +1 -0
  81. package/dist/utils/rate-limiter.js +93 -0
  82. package/dist/utils/rate-limiter.js.map +1 -0
  83. package/package.json +61 -0
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Ring buffer for storing game state snapshots.
3
+ *
4
+ * Used for rollback: when a misprediction is detected, the game state
5
+ * is restored from a snapshot and resimulated forward.
6
+ */
7
+ /**
8
+ * A ring buffer that stores game state snapshots for rollback.
9
+ *
10
+ * Snapshots are stored at specific ticks and can be retrieved by tick.
11
+ * When the buffer is full, the oldest snapshot is evicted.
12
+ * Uses a tick-to-index map for O(1) lookup performance.
13
+ *
14
+ * INVARIANT: Snapshots must be saved in ascending tick order. This is required
15
+ * for O(log n) binary search in getAtOrBefore(). The invariant is maintained by:
16
+ * - Normal simulation: ticks advance sequentially (0, 1, 2, ...)
17
+ * - Resimulation: existing ticks are updated in-place, preserving order
18
+ *
19
+ * Saving a non-existing tick out of order will corrupt the sort order and
20
+ * cause getAtOrBefore() to return incorrect results.
21
+ */
22
+ export class SnapshotBuffer {
23
+ capacity;
24
+ buffer;
25
+ tickToIndex = new Map();
26
+ head = 0; // Next write position
27
+ count = 0;
28
+ _oldestTick;
29
+ _newestTick;
30
+ /**
31
+ * Create a new snapshot buffer.
32
+ *
33
+ * @param capacity - Maximum number of snapshots to store
34
+ */
35
+ constructor(capacity) {
36
+ this.capacity = capacity;
37
+ if (capacity <= 0) {
38
+ throw new Error("Capacity must be positive");
39
+ }
40
+ this.buffer = new Array(capacity);
41
+ }
42
+ /**
43
+ * Number of snapshots currently stored.
44
+ */
45
+ get size() {
46
+ return this.count;
47
+ }
48
+ /**
49
+ * The oldest tick in the buffer, or undefined if empty.
50
+ */
51
+ get oldestTick() {
52
+ return this._oldestTick;
53
+ }
54
+ /**
55
+ * The newest tick in the buffer, or undefined if empty.
56
+ */
57
+ get newestTick() {
58
+ return this._newestTick;
59
+ }
60
+ /**
61
+ * Save a snapshot at a specific tick.
62
+ *
63
+ * If the tick already exists, updates in-place to maintain sort order.
64
+ * If the buffer is full, the oldest snapshot is evicted.
65
+ * The state is copied to prevent external mutation.
66
+ *
67
+ * PRECONDITION: New ticks (not already in buffer) must be >= all existing ticks.
68
+ * Saving an out-of-order new tick corrupts sort order. See class invariant.
69
+ *
70
+ * @param tick - The tick this snapshot was taken at
71
+ * @param state - The serialized game state
72
+ * @param hash - The hash of the game state
73
+ */
74
+ save(tick, state, hash) {
75
+ // Copy the state to prevent external mutation
76
+ const stateCopy = new Uint8Array(state.length);
77
+ stateCopy.set(state);
78
+ const snapshot = {
79
+ tick,
80
+ state: stateCopy,
81
+ hash,
82
+ };
83
+ // Check if this tick already exists - update in-place to maintain order
84
+ const existingIdx = this.tickToIndex.get(tick);
85
+ if (existingIdx !== undefined) {
86
+ this.buffer[existingIdx] = snapshot;
87
+ return;
88
+ }
89
+ // Warn on invariant violation: new ticks must be >= newest for binary search
90
+ if (this._newestTick !== undefined && tick < this._newestTick) {
91
+ console.error(`SnapshotBuffer: tick ${tick} is out of order (newest: ${this._newestTick}). This will corrupt binary search in getAtOrBefore().`);
92
+ }
93
+ // New tick - append or evict oldest
94
+ if (this.count === this.capacity) {
95
+ // Remove old tick from index map
96
+ const oldSnapshot = this.buffer[this.head];
97
+ if (oldSnapshot) {
98
+ this.tickToIndex.delete(oldSnapshot.tick);
99
+ }
100
+ // The slot at head contains the oldest snapshot
101
+ this.buffer[this.head] = snapshot;
102
+ this.tickToIndex.set(tick, this.head);
103
+ this.head = (this.head + 1) % this.capacity;
104
+ // Update oldest tick to the new oldest
105
+ const oldestSnapshot = this.buffer[this.head];
106
+ this._oldestTick = oldestSnapshot?.tick;
107
+ }
108
+ else {
109
+ // Buffer not full, just append
110
+ const writePos = (this.head + this.count) % this.capacity;
111
+ this.buffer[writePos] = snapshot;
112
+ this.tickToIndex.set(tick, writePos);
113
+ this.count++;
114
+ if (this.count === 1) {
115
+ this._oldestTick = tick;
116
+ }
117
+ }
118
+ this._newestTick = tick;
119
+ }
120
+ /**
121
+ * Get a snapshot at a specific tick.
122
+ * Uses O(1) map lookup for performance.
123
+ *
124
+ * @param tick - The tick to retrieve
125
+ * @returns The snapshot at that tick, or undefined if not found
126
+ */
127
+ get(tick) {
128
+ const idx = this.tickToIndex.get(tick);
129
+ if (idx === undefined) {
130
+ return undefined;
131
+ }
132
+ return this.buffer[idx];
133
+ }
134
+ /**
135
+ * Get the oldest snapshot in the buffer.
136
+ *
137
+ * @returns The oldest snapshot, or undefined if empty
138
+ */
139
+ getOldest() {
140
+ if (this.count === 0) {
141
+ return undefined;
142
+ }
143
+ return this.buffer[this.head];
144
+ }
145
+ /**
146
+ * Get the newest snapshot in the buffer.
147
+ *
148
+ * @returns The newest snapshot, or undefined if empty
149
+ */
150
+ getNewest() {
151
+ if (this.count === 0) {
152
+ return undefined;
153
+ }
154
+ const newestIdx = (this.head + this.count - 1) % this.capacity;
155
+ return this.buffer[newestIdx];
156
+ }
157
+ /**
158
+ * Clear all snapshots from the buffer.
159
+ */
160
+ clear() {
161
+ this.buffer.fill(undefined);
162
+ this.tickToIndex.clear();
163
+ this.head = 0;
164
+ this.count = 0;
165
+ this._oldestTick = undefined;
166
+ this._newestTick = undefined;
167
+ }
168
+ /**
169
+ * Get the snapshot at or before a given tick.
170
+ * Useful for finding the closest snapshot for rollback.
171
+ *
172
+ * O(log n) binary search. Snapshots are stored in ascending tick order.
173
+ *
174
+ * @param tick - The target tick
175
+ * @returns The snapshot at or before that tick, or undefined if none exists
176
+ */
177
+ getAtOrBefore(tick) {
178
+ if (this.count === 0) {
179
+ return undefined;
180
+ }
181
+ // Binary search to find the rightmost snapshot with tick <= target
182
+ // Logical indices: 0 to count-1, physical: (head + logical) % capacity
183
+ let lo = 0;
184
+ let hi = this.count - 1;
185
+ let result;
186
+ while (lo <= hi) {
187
+ const mid = (lo + hi) >>> 1;
188
+ const idx = (this.head + mid) % this.capacity;
189
+ const snapshot = this.buffer[idx];
190
+ if (snapshot && snapshot.tick <= tick) {
191
+ // This snapshot is valid, but there might be a better one to the right
192
+ result = snapshot;
193
+ lo = mid + 1;
194
+ }
195
+ else {
196
+ // Snapshot tick is too high, search left
197
+ hi = mid - 1;
198
+ }
199
+ }
200
+ return result;
201
+ }
202
+ /**
203
+ * Remove all snapshots before a given tick.
204
+ * Used to clean up old snapshots that are no longer needed.
205
+ *
206
+ * @param tick - Remove all snapshots with tick < this value
207
+ */
208
+ pruneBeforeTick(tick) {
209
+ while (this.count > 0) {
210
+ const oldest = this.buffer[this.head];
211
+ if (oldest && oldest.tick < tick) {
212
+ // Remove from index map
213
+ this.tickToIndex.delete(oldest.tick);
214
+ this.buffer[this.head] = undefined;
215
+ this.head = (this.head + 1) % this.capacity;
216
+ this.count--;
217
+ if (this.count === 0) {
218
+ this._oldestTick = undefined;
219
+ this._newestTick = undefined;
220
+ }
221
+ else {
222
+ this._oldestTick = this.buffer[this.head]?.tick;
223
+ }
224
+ }
225
+ else {
226
+ break;
227
+ }
228
+ }
229
+ }
230
+ /**
231
+ * Check if a snapshot exists at a given tick.
232
+ *
233
+ * @param tick - The tick to check
234
+ * @returns true if a snapshot exists at that tick
235
+ */
236
+ has(tick) {
237
+ return this.get(tick) !== undefined;
238
+ }
239
+ /**
240
+ * Get all ticks that have snapshots, in order from oldest to newest.
241
+ *
242
+ * @returns Array of ticks with snapshots
243
+ */
244
+ getTicks() {
245
+ const ticks = [];
246
+ for (let i = 0; i < this.count; i++) {
247
+ const idx = (this.head + i) % this.capacity;
248
+ const snapshot = this.buffer[idx];
249
+ if (snapshot) {
250
+ ticks.push(snapshot.tick);
251
+ }
252
+ }
253
+ return ticks;
254
+ }
255
+ }
256
+ //# sourceMappingURL=snapshot-buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-buffer.js","sourceRoot":"","sources":["../../src/rollback/snapshot-buffer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Desync detection and management.
3
+ *
4
+ * Supports both host-authority mode (host collects and compares all hashes)
5
+ * and peer mode (each peer compares independently).
6
+ */
7
+ import { DesyncAuthority, type PlayerId, type Tick, Topology } from "../types.js";
8
+ /**
9
+ * Configuration for the desync manager.
10
+ */
11
+ export interface DesyncManagerConfig {
12
+ /** Network topology */
13
+ topology: Topology;
14
+ /** Desync authority mode */
15
+ desyncAuthority: DesyncAuthority;
16
+ /** Hash interval for pruning calculations */
17
+ hashInterval: number;
18
+ }
19
+ /**
20
+ * Result when a desync is detected.
21
+ */
22
+ export interface DesyncResult {
23
+ /** The tick where desync was detected */
24
+ tick: Tick;
25
+ /** The player who desynced (in host-authority mode) */
26
+ desyncedPlayerId: PlayerId;
27
+ /** The host/reference hash */
28
+ referenceHash: number;
29
+ /** The player's actual hash */
30
+ playerHash: number;
31
+ }
32
+ /**
33
+ * Result of checking a hash in peer mode.
34
+ * Returned only when a desync is detected (non-null means desync).
35
+ */
36
+ export interface PeerDesyncResult {
37
+ /** The tick where desync was detected */
38
+ tick: Tick;
39
+ /** Local hash */
40
+ localHash: number;
41
+ /** Remote hash */
42
+ remoteHash: number;
43
+ /** Remote player ID */
44
+ remotePlayerId: PlayerId;
45
+ }
46
+ /**
47
+ * Manages desync detection for both host-authority and peer modes.
48
+ */
49
+ export declare class DesyncManager {
50
+ private readonly topology;
51
+ private readonly desyncAuthority;
52
+ private readonly hashInterval;
53
+ /** Host-authority mode: hashes received from players, keyed by tick then playerId */
54
+ private readonly receivedHashes;
55
+ constructor(config: DesyncManagerConfig);
56
+ /**
57
+ * Whether this is host-authority mode.
58
+ */
59
+ get isHostAuthority(): boolean;
60
+ /**
61
+ * Record a hash from a player (host-authority mode).
62
+ *
63
+ * @param tick - The tick the hash is for
64
+ * @param playerId - The player who sent the hash
65
+ * @param hash - The hash value
66
+ */
67
+ recordHash(tick: Tick, playerId: PlayerId, hash: number): void;
68
+ /**
69
+ * Check if we have all hashes for a tick (host-authority mode).
70
+ *
71
+ * @param tick - The tick to check
72
+ * @param expectedPlayerCount - Number of players expected to submit hashes
73
+ * @returns true if all hashes have been received
74
+ */
75
+ hasAllHashes(tick: Tick, expectedPlayerCount: number): boolean;
76
+ /**
77
+ * Check for desyncs at a tick (host-authority mode).
78
+ * Compares all player hashes against the reference (host) hash.
79
+ *
80
+ * @param tick - The tick to check
81
+ * @param hostPlayerId - The host's player ID
82
+ * @returns Array of desync results for any desynced players
83
+ */
84
+ checkDesyncs(tick: Tick, hostPlayerId: PlayerId): DesyncResult[];
85
+ /**
86
+ * Check for desync in peer mode (comparing local vs remote hash).
87
+ *
88
+ * @param tick - The tick to check
89
+ * @param localHash - Local hash value (or undefined if not available)
90
+ * @param remoteHash - Remote hash value
91
+ * @param remotePlayerId - Remote player's ID
92
+ * @returns Desync result if a desync is detected, null otherwise
93
+ */
94
+ checkPeerDesync(tick: Tick, localHash: number | undefined, remoteHash: number, remotePlayerId: PlayerId): PeerDesyncResult | null;
95
+ /**
96
+ * Remove old hash entries to prevent memory growth.
97
+ *
98
+ * @param currentTick - Current simulation tick
99
+ */
100
+ pruneOldHashes(currentTick: Tick): void;
101
+ /**
102
+ * Clear all stored hashes.
103
+ */
104
+ clear(): void;
105
+ }
106
+ //# sourceMappingURL=desync-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desync-manager.d.ts","sourceRoot":"","sources":["../../src/session/desync-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACN,eAAe,EACf,KAAK,QAAQ,EACb,KAAK,IAAI,EACT,QAAQ,EAER,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,uBAAuB;IACvB,QAAQ,EAAE,QAAQ,CAAC;IACnB,4BAA4B;IAC5B,eAAe,EAAE,eAAe,CAAC;IACjC,6CAA6C;IAC7C,YAAY,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,yCAAyC;IACzC,IAAI,EAAE,IAAI,CAAC;IACX,uDAAuD;IACvD,gBAAgB,EAAE,QAAQ,CAAC;IAC3B,8BAA8B;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAChC,yCAAyC;IACzC,IAAI,EAAE,IAAI,CAAC;IACX,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,cAAc,EAAE,QAAQ,CAAC;CACzB;AAED;;GAEG;AACH,qBAAa,aAAa;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IAEtC,qFAAqF;IACrF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA+C;gBAElE,MAAM,EAAE,mBAAmB;IAMvC;;OAEG;IACH,IAAI,eAAe,IAAI,OAAO,CAO7B;IAED;;;;;;OAMG;IACH,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAS9D;;;;;;OAMG;IACH,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,GAAG,OAAO;IAQ9D;;;;;;;OAOG;IACH,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,GAAG,YAAY,EAAE;IA6BhE;;;;;;;;OAQG;IACH,eAAe,CACd,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,QAAQ,GACtB,gBAAgB,GAAG,IAAI;IAiB1B;;;;OAIG;IACH,cAAc,CAAC,WAAW,EAAE,IAAI,GAAG,IAAI;IASvC;;OAEG;IACH,KAAK,IAAI,IAAI;CAGb"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Desync detection and management.
3
+ *
4
+ * Supports both host-authority mode (host collects and compares all hashes)
5
+ * and peer mode (each peer compares independently).
6
+ */
7
+ import { DesyncAuthority, Topology, asTick, } from "../types.js";
8
+ /**
9
+ * Manages desync detection for both host-authority and peer modes.
10
+ */
11
+ export class DesyncManager {
12
+ topology;
13
+ desyncAuthority;
14
+ hashInterval;
15
+ /** Host-authority mode: hashes received from players, keyed by tick then playerId */
16
+ receivedHashes = new Map();
17
+ constructor(config) {
18
+ this.topology = config.topology;
19
+ this.desyncAuthority = config.desyncAuthority;
20
+ this.hashInterval = config.hashInterval;
21
+ }
22
+ /**
23
+ * Whether this is host-authority mode.
24
+ */
25
+ get isHostAuthority() {
26
+ // Only Mesh + Host authority uses host-authority mode
27
+ // Star topology and Mesh + Peer use peer-based detection
28
+ return (this.topology === Topology.Mesh &&
29
+ this.desyncAuthority === DesyncAuthority.Host);
30
+ }
31
+ /**
32
+ * Record a hash from a player (host-authority mode).
33
+ *
34
+ * @param tick - The tick the hash is for
35
+ * @param playerId - The player who sent the hash
36
+ * @param hash - The hash value
37
+ */
38
+ recordHash(tick, playerId, hash) {
39
+ let tickHashes = this.receivedHashes.get(tick);
40
+ if (!tickHashes) {
41
+ tickHashes = new Map();
42
+ this.receivedHashes.set(tick, tickHashes);
43
+ }
44
+ tickHashes.set(playerId, hash);
45
+ }
46
+ /**
47
+ * Check if we have all hashes for a tick (host-authority mode).
48
+ *
49
+ * @param tick - The tick to check
50
+ * @param expectedPlayerCount - Number of players expected to submit hashes
51
+ * @returns true if all hashes have been received
52
+ */
53
+ hasAllHashes(tick, expectedPlayerCount) {
54
+ const tickHashes = this.receivedHashes.get(tick);
55
+ if (!tickHashes) {
56
+ return false;
57
+ }
58
+ return tickHashes.size >= expectedPlayerCount;
59
+ }
60
+ /**
61
+ * Check for desyncs at a tick (host-authority mode).
62
+ * Compares all player hashes against the reference (host) hash.
63
+ *
64
+ * @param tick - The tick to check
65
+ * @param hostPlayerId - The host's player ID
66
+ * @returns Array of desync results for any desynced players
67
+ */
68
+ checkDesyncs(tick, hostPlayerId) {
69
+ const tickHashes = this.receivedHashes.get(tick);
70
+ if (!tickHashes) {
71
+ return [];
72
+ }
73
+ const hostHash = tickHashes.get(hostPlayerId);
74
+ if (hostHash === undefined) {
75
+ return [];
76
+ }
77
+ const desyncs = [];
78
+ for (const [playerId, playerHash] of tickHashes) {
79
+ if (playerId === hostPlayerId) {
80
+ continue;
81
+ }
82
+ if (playerHash !== hostHash) {
83
+ desyncs.push({
84
+ tick,
85
+ desyncedPlayerId: playerId,
86
+ referenceHash: hostHash,
87
+ playerHash,
88
+ });
89
+ }
90
+ }
91
+ return desyncs;
92
+ }
93
+ /**
94
+ * Check for desync in peer mode (comparing local vs remote hash).
95
+ *
96
+ * @param tick - The tick to check
97
+ * @param localHash - Local hash value (or undefined if not available)
98
+ * @param remoteHash - Remote hash value
99
+ * @param remotePlayerId - Remote player's ID
100
+ * @returns Desync result if a desync is detected, null otherwise
101
+ */
102
+ checkPeerDesync(tick, localHash, remoteHash, remotePlayerId) {
103
+ if (localHash === undefined) {
104
+ return null;
105
+ }
106
+ if (localHash !== remoteHash) {
107
+ return {
108
+ tick,
109
+ localHash,
110
+ remoteHash,
111
+ remotePlayerId,
112
+ };
113
+ }
114
+ return null;
115
+ }
116
+ /**
117
+ * Remove old hash entries to prevent memory growth.
118
+ *
119
+ * @param currentTick - Current simulation tick
120
+ */
121
+ pruneOldHashes(currentTick) {
122
+ const pruneBelow = asTick(currentTick - this.hashInterval * 2);
123
+ for (const tick of this.receivedHashes.keys()) {
124
+ if (tick < pruneBelow) {
125
+ this.receivedHashes.delete(tick);
126
+ }
127
+ }
128
+ }
129
+ /**
130
+ * Clear all stored hashes.
131
+ */
132
+ clear() {
133
+ this.receivedHashes.clear();
134
+ }
135
+ }
136
+ //# sourceMappingURL=desync-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desync-manager.js","sourceRoot":"","sources":["../../src/session/desync-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Lag monitoring for detecting and reporting players that fall behind.
3
+ */
4
+ import type { PlayerId, Tick } from "../types.js";
5
+ /**
6
+ * Configuration for the lag monitor.
7
+ */
8
+ export interface LagMonitorConfig {
9
+ /**
10
+ * Number of ticks behind before a player is considered lagging.
11
+ * Set to 0 to disable lag monitoring.
12
+ */
13
+ threshold: number;
14
+ /**
15
+ * Minimum ticks between lag reports for the same player.
16
+ * Prevents spam when a player is consistently lagging.
17
+ */
18
+ cooldownTicks: number;
19
+ }
20
+ /**
21
+ * Result when a player is detected as lagging.
22
+ */
23
+ export interface LagReport {
24
+ /** The player who is lagging */
25
+ laggyPlayerId: PlayerId;
26
+ /** How many ticks behind they are */
27
+ ticksBehind: number;
28
+ }
29
+ /**
30
+ * Default cooldown between lag reports for the same player.
31
+ */
32
+ export declare const DEFAULT_LAG_REPORT_COOLDOWN_TICKS = 60;
33
+ /**
34
+ * Monitors player lag and generates reports when players fall too far behind.
35
+ *
36
+ * Uses a cooldown system to prevent flooding the host with lag reports
37
+ * for persistently lagging players.
38
+ */
39
+ export declare class LagMonitor {
40
+ private readonly threshold;
41
+ private readonly cooldownTicks;
42
+ private readonly lastReportTick;
43
+ constructor(config: LagMonitorConfig);
44
+ /**
45
+ * Check if a player is lagging and should be reported.
46
+ *
47
+ * @param playerId - The player to check
48
+ * @param ticksBehind - How many ticks behind the player is
49
+ * @param currentTick - The current simulation tick
50
+ * @returns A LagReport if the player is lagging and cooldown has passed, null otherwise
51
+ */
52
+ checkPlayer(playerId: PlayerId, ticksBehind: number, currentTick: Tick): LagReport | null;
53
+ /**
54
+ * Reset the cooldown for a player.
55
+ * Use this when a player recovers from lag.
56
+ *
57
+ * @param playerId - The player to reset
58
+ */
59
+ reset(playerId: PlayerId): void;
60
+ /**
61
+ * Clear all lag tracking state.
62
+ */
63
+ clear(): void;
64
+ /**
65
+ * Whether lag monitoring is enabled.
66
+ */
67
+ get isEnabled(): boolean;
68
+ }
69
+ //# sourceMappingURL=lag-monitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lag-monitor.d.ts","sourceRoot":"","sources":["../../src/session/lag-monitor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,gCAAgC;IAChC,aAAa,EAAE,QAAQ,CAAC;IACxB,qCAAqC;IACrC,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAEpD;;;;;GAKG;AACH,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAkC;gBAErD,MAAM,EAAE,gBAAgB;IAKpC;;;;;;;OAOG;IACH,WAAW,CACV,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,IAAI,GACf,SAAS,GAAG,IAAI;IA4BnB;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAI/B;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;CACD"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Lag monitoring for detecting and reporting players that fall behind.
3
+ */
4
+ /**
5
+ * Default cooldown between lag reports for the same player.
6
+ */
7
+ export const DEFAULT_LAG_REPORT_COOLDOWN_TICKS = 60;
8
+ /**
9
+ * Monitors player lag and generates reports when players fall too far behind.
10
+ *
11
+ * Uses a cooldown system to prevent flooding the host with lag reports
12
+ * for persistently lagging players.
13
+ */
14
+ export class LagMonitor {
15
+ threshold;
16
+ cooldownTicks;
17
+ lastReportTick = new Map();
18
+ constructor(config) {
19
+ this.threshold = config.threshold;
20
+ this.cooldownTicks = config.cooldownTicks;
21
+ }
22
+ /**
23
+ * Check if a player is lagging and should be reported.
24
+ *
25
+ * @param playerId - The player to check
26
+ * @param ticksBehind - How many ticks behind the player is
27
+ * @param currentTick - The current simulation tick
28
+ * @returns A LagReport if the player is lagging and cooldown has passed, null otherwise
29
+ */
30
+ checkPlayer(playerId, ticksBehind, currentTick) {
31
+ // Disabled if threshold is 0 or negative
32
+ if (this.threshold <= 0) {
33
+ return null;
34
+ }
35
+ // Not lagging enough
36
+ if (ticksBehind < this.threshold) {
37
+ return null;
38
+ }
39
+ // Check cooldown
40
+ const lastReport = this.lastReportTick.get(playerId);
41
+ if (lastReport !== undefined &&
42
+ currentTick - lastReport < this.cooldownTicks) {
43
+ return null;
44
+ }
45
+ // Record this report and return
46
+ this.lastReportTick.set(playerId, currentTick);
47
+ return {
48
+ laggyPlayerId: playerId,
49
+ ticksBehind,
50
+ };
51
+ }
52
+ /**
53
+ * Reset the cooldown for a player.
54
+ * Use this when a player recovers from lag.
55
+ *
56
+ * @param playerId - The player to reset
57
+ */
58
+ reset(playerId) {
59
+ this.lastReportTick.delete(playerId);
60
+ }
61
+ /**
62
+ * Clear all lag tracking state.
63
+ */
64
+ clear() {
65
+ this.lastReportTick.clear();
66
+ }
67
+ /**
68
+ * Whether lag monitoring is enabled.
69
+ */
70
+ get isEnabled() {
71
+ return this.threshold > 0;
72
+ }
73
+ }
74
+ //# sourceMappingURL=lag-monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lag-monitor.js","sourceRoot":"","sources":["../../src/session/lag-monitor.ts"],"names":[],"mappings":"AAAA;;GAEG;AA+BH;;GAEG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,EAAE,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,OAAO,UAAU;IACL,SAAS,CAAS;IAClB,aAAa,CAAS;IACtB,cAAc,GAAwB,IAAI,GAAG,EAAE,CAAC;IAEjE,YAAY,MAAwB;QACnC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;QAClC,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;IAC3C,CAAC;IAED;;;;;;;OAOG;IACH,WAAW,CACV,QAAkB,EAClB,WAAmB,EACnB,WAAiB;QAEjB,yCAAyC;QACzC,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC;QACb,CAAC;QAED,qBAAqB;QACrB,IAAI,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC;QACb,CAAC;QAED,iBAAiB;QACjB,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrD,IACC,UAAU,KAAK,SAAS;YACxB,WAAW,GAAG,UAAU,GAAG,IAAI,CAAC,aAAa,EAC5C,CAAC;YACF,OAAO,IAAI,CAAC;QACb,CAAC;QAED,gCAAgC;QAChC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC/C,OAAO;YACN,aAAa,EAAE,QAAQ;YACvB,WAAW;SACX,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,QAAkB;QACvB,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IAED;;OAEG;IACH,KAAK;QACJ,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,IAAI,SAAS;QACZ,OAAO,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;IAC3B,CAAC;CACD"}