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.
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/debug.d.ts +29 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +56 -0
- package/dist/debug.js.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/encoding.d.ts +80 -0
- package/dist/protocol/encoding.d.ts.map +1 -0
- package/dist/protocol/encoding.js +992 -0
- package/dist/protocol/encoding.js.map +1 -0
- package/dist/protocol/messages.d.ts +271 -0
- package/dist/protocol/messages.d.ts.map +1 -0
- package/dist/protocol/messages.js +114 -0
- package/dist/protocol/messages.js.map +1 -0
- package/dist/rollback/engine.d.ts +261 -0
- package/dist/rollback/engine.d.ts.map +1 -0
- package/dist/rollback/engine.js +543 -0
- package/dist/rollback/engine.js.map +1 -0
- package/dist/rollback/input-buffer.d.ts +225 -0
- package/dist/rollback/input-buffer.d.ts.map +1 -0
- package/dist/rollback/input-buffer.js +483 -0
- package/dist/rollback/input-buffer.js.map +1 -0
- package/dist/rollback/snapshot-buffer.d.ts +119 -0
- package/dist/rollback/snapshot-buffer.d.ts.map +1 -0
- package/dist/rollback/snapshot-buffer.js +256 -0
- package/dist/rollback/snapshot-buffer.js.map +1 -0
- package/dist/session/desync-manager.d.ts +106 -0
- package/dist/session/desync-manager.d.ts.map +1 -0
- package/dist/session/desync-manager.js +136 -0
- package/dist/session/desync-manager.js.map +1 -0
- package/dist/session/lag-monitor.d.ts +69 -0
- package/dist/session/lag-monitor.d.ts.map +1 -0
- package/dist/session/lag-monitor.js +74 -0
- package/dist/session/lag-monitor.js.map +1 -0
- package/dist/session/message-builders.d.ts +86 -0
- package/dist/session/message-builders.d.ts.map +1 -0
- package/dist/session/message-builders.js +199 -0
- package/dist/session/message-builders.js.map +1 -0
- package/dist/session/message-router.d.ts +61 -0
- package/dist/session/message-router.d.ts.map +1 -0
- package/dist/session/message-router.js +105 -0
- package/dist/session/message-router.js.map +1 -0
- package/dist/session/player-manager.d.ts +100 -0
- package/dist/session/player-manager.d.ts.map +1 -0
- package/dist/session/player-manager.js +160 -0
- package/dist/session/player-manager.js.map +1 -0
- package/dist/session/session.d.ts +379 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/session/session.js +1294 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/topology.d.ts +66 -0
- package/dist/session/topology.d.ts.map +1 -0
- package/dist/session/topology.js +72 -0
- package/dist/session/topology.js.map +1 -0
- package/dist/transport/adapter.d.ts +99 -0
- package/dist/transport/adapter.d.ts.map +1 -0
- package/dist/transport/adapter.js +8 -0
- package/dist/transport/adapter.js.map +1 -0
- package/dist/transport/local.d.ts +192 -0
- package/dist/transport/local.d.ts.map +1 -0
- package/dist/transport/local.js +435 -0
- package/dist/transport/local.js.map +1 -0
- package/dist/transport/transforming.d.ts +177 -0
- package/dist/transport/transforming.d.ts.map +1 -0
- package/dist/transport/transforming.js +407 -0
- package/dist/transport/transforming.js.map +1 -0
- package/dist/transport/webrtc.d.ts +285 -0
- package/dist/transport/webrtc.d.ts.map +1 -0
- package/dist/transport/webrtc.js +734 -0
- package/dist/transport/webrtc.js.map +1 -0
- package/dist/types.d.ts +394 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +256 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +59 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +93 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core rollback netcode engine.
|
|
3
|
+
*
|
|
4
|
+
* Handles the rollback algorithm: detecting mispredictions,
|
|
5
|
+
* restoring state, and resimulating forward.
|
|
6
|
+
*/
|
|
7
|
+
import { DEFAULT_INPUT_PREDICTOR, GameError, RollbackError, asTick, } from "../types.js";
|
|
8
|
+
import { InputBuffer } from "./input-buffer.js";
|
|
9
|
+
import { SnapshotBuffer } from "./snapshot-buffer.js";
|
|
10
|
+
/**
|
|
11
|
+
* Core rollback engine that handles prediction, rollback, and resimulation.
|
|
12
|
+
*/
|
|
13
|
+
export class RollbackEngine {
|
|
14
|
+
game;
|
|
15
|
+
localPlayerId;
|
|
16
|
+
snapshotBuffer;
|
|
17
|
+
inputBuffer;
|
|
18
|
+
inputPredictor;
|
|
19
|
+
maxSpeculationTicks;
|
|
20
|
+
pruneBufferTicks;
|
|
21
|
+
onPlayerAddDuringResimulation;
|
|
22
|
+
onPlayerRemoveDuringResimulation;
|
|
23
|
+
onRollback;
|
|
24
|
+
_currentTick;
|
|
25
|
+
_confirmedTick;
|
|
26
|
+
localInputs = new Map();
|
|
27
|
+
/**
|
|
28
|
+
* Create a new rollback engine.
|
|
29
|
+
*/
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.game = config.game;
|
|
32
|
+
this.localPlayerId = config.localPlayerId;
|
|
33
|
+
this.maxSpeculationTicks = config.maxSpeculationTicks ?? 60;
|
|
34
|
+
this.pruneBufferTicks = config.pruneBufferTicks ?? 10;
|
|
35
|
+
this.inputPredictor = config.inputPredictor ?? DEFAULT_INPUT_PREDICTOR;
|
|
36
|
+
this.onPlayerAddDuringResimulation = config.onPlayerAddDuringResimulation;
|
|
37
|
+
this.onPlayerRemoveDuringResimulation =
|
|
38
|
+
config.onPlayerRemoveDuringResimulation;
|
|
39
|
+
this.onRollback = config.onRollback;
|
|
40
|
+
this.snapshotBuffer = new SnapshotBuffer(config.snapshotHistorySize ?? 120);
|
|
41
|
+
this.inputBuffer = new InputBuffer();
|
|
42
|
+
this._currentTick = asTick(0);
|
|
43
|
+
this._confirmedTick = asTick(-1);
|
|
44
|
+
// Add local player
|
|
45
|
+
this.inputBuffer.addPlayer(this.localPlayerId, asTick(0));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The current simulation tick.
|
|
49
|
+
*/
|
|
50
|
+
get currentTick() {
|
|
51
|
+
return this._currentTick;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The lowest confirmed tick across all active players.
|
|
55
|
+
* All inputs up to and including this tick are confirmed.
|
|
56
|
+
*/
|
|
57
|
+
get confirmedTick() {
|
|
58
|
+
return this._confirmedTick;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Add a player to the simulation.
|
|
62
|
+
*
|
|
63
|
+
* @param playerId - The player's ID
|
|
64
|
+
* @param joinTick - The tick at which they join
|
|
65
|
+
*/
|
|
66
|
+
addPlayer(playerId, joinTick) {
|
|
67
|
+
this.inputBuffer.addPlayer(playerId, joinTick);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Remove a player from the simulation.
|
|
71
|
+
*
|
|
72
|
+
* @param playerId - The player's ID
|
|
73
|
+
* @param leaveTick - The tick at which they leave
|
|
74
|
+
*/
|
|
75
|
+
removePlayer(playerId, leaveTick) {
|
|
76
|
+
this.inputBuffer.removePlayer(playerId, leaveTick);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get the confirmed tick for a specific player.
|
|
80
|
+
* Returns the highest tick for which we have received confirmed input from this player.
|
|
81
|
+
*
|
|
82
|
+
* @param playerId - The player's ID
|
|
83
|
+
* @returns The confirmed tick, or undefined if the player is not tracked
|
|
84
|
+
*/
|
|
85
|
+
getConfirmedTickForPlayer(playerId) {
|
|
86
|
+
return this.inputBuffer.getConfirmedTick(playerId);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Set the local player's input for the current tick.
|
|
90
|
+
* Call this before tick() to set what input the local player uses.
|
|
91
|
+
*
|
|
92
|
+
* @param tick - The tick the input is for
|
|
93
|
+
* @param input - The input data
|
|
94
|
+
*/
|
|
95
|
+
setLocalInput(tick, input) {
|
|
96
|
+
// Copy input
|
|
97
|
+
const inputCopy = new Uint8Array(input.length);
|
|
98
|
+
inputCopy.set(input);
|
|
99
|
+
this.localInputs.set(tick, inputCopy);
|
|
100
|
+
// Also register with input buffer so it's available for misprediction detection
|
|
101
|
+
this.inputBuffer.receiveInput(this.localPlayerId, tick, inputCopy);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Receive a remote player's input.
|
|
105
|
+
*
|
|
106
|
+
* @param playerId - The player's ID
|
|
107
|
+
* @param tick - The tick the input is for
|
|
108
|
+
* @param input - The input data
|
|
109
|
+
*/
|
|
110
|
+
receiveRemoteInput(playerId, tick, input) {
|
|
111
|
+
this.inputBuffer.receiveInput(playerId, tick, input);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get the local player's input for a tick.
|
|
115
|
+
*
|
|
116
|
+
* @param tick - The tick to get input for
|
|
117
|
+
* @returns The input, or undefined if not set
|
|
118
|
+
*/
|
|
119
|
+
getLocalInput(tick) {
|
|
120
|
+
return this.localInputs.get(tick);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Save the initial snapshot at tick -1.
|
|
124
|
+
* This allows rollback of tick 0 if there's a misprediction.
|
|
125
|
+
* Call this before the first tick() if the engine wasn't initialized via setState().
|
|
126
|
+
*/
|
|
127
|
+
saveInitialSnapshot() {
|
|
128
|
+
if (!this.snapshotBuffer.has(asTick(-1))) {
|
|
129
|
+
const tick = asTick(-1);
|
|
130
|
+
const state = this.gameSerialize(tick);
|
|
131
|
+
const hash = this.gameHash(tick);
|
|
132
|
+
this.snapshotBuffer.save(tick, state, hash);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Advance the simulation by one tick.
|
|
137
|
+
*
|
|
138
|
+
* This is the core rollback algorithm:
|
|
139
|
+
* 1. Check for mispredictions among remote players
|
|
140
|
+
* 2. If misprediction found, rollback and resimulate
|
|
141
|
+
* 3. Gather inputs (real or predicted) for all players
|
|
142
|
+
* 4. Step the simulation
|
|
143
|
+
* 5. Save snapshot
|
|
144
|
+
*
|
|
145
|
+
* @returns Result containing tick number and rollback info
|
|
146
|
+
*/
|
|
147
|
+
tick() {
|
|
148
|
+
// Ensure we have an initial snapshot for rollback on first tick
|
|
149
|
+
if (this._currentTick === 0) {
|
|
150
|
+
this.saveInitialSnapshot();
|
|
151
|
+
}
|
|
152
|
+
// Check for max speculation limit
|
|
153
|
+
const minConfirmed = this.inputBuffer.getMinConfirmedTick(this._currentTick);
|
|
154
|
+
if (minConfirmed !== undefined) {
|
|
155
|
+
const speculation = this._currentTick - minConfirmed;
|
|
156
|
+
if (speculation >= this.maxSpeculationTicks) {
|
|
157
|
+
// Can't advance further - would exceed max speculation
|
|
158
|
+
// Return early without advancing
|
|
159
|
+
return {
|
|
160
|
+
tick: this._currentTick,
|
|
161
|
+
rolledBack: false,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Check for mispredictions and rollback if needed
|
|
166
|
+
const rollbackResult = this.checkAndRollback();
|
|
167
|
+
// Gather inputs for current tick
|
|
168
|
+
const inputs = this.gatherInputs(this._currentTick);
|
|
169
|
+
// Step the simulation
|
|
170
|
+
this.gameStep(this._currentTick, inputs);
|
|
171
|
+
// Save snapshot
|
|
172
|
+
const state = this.gameSerialize(this._currentTick);
|
|
173
|
+
const hash = this.gameHash(this._currentTick);
|
|
174
|
+
this.snapshotBuffer.save(this._currentTick, state, hash);
|
|
175
|
+
// Update confirmed tick
|
|
176
|
+
this.updateConfirmedTick();
|
|
177
|
+
// Advance tick counter
|
|
178
|
+
const tickResult = this._currentTick;
|
|
179
|
+
this._currentTick = asTick(this._currentTick + 1);
|
|
180
|
+
const result = {
|
|
181
|
+
tick: tickResult,
|
|
182
|
+
rolledBack: rollbackResult.rolledBack,
|
|
183
|
+
};
|
|
184
|
+
if (rollbackResult.rollbackTicks !== undefined) {
|
|
185
|
+
result.rollbackTicks = rollbackResult.rollbackTicks;
|
|
186
|
+
}
|
|
187
|
+
if (rollbackResult.error !== undefined) {
|
|
188
|
+
result.error = rollbackResult.error;
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Check for mispredictions and rollback if found.
|
|
194
|
+
*/
|
|
195
|
+
checkAndRollback() {
|
|
196
|
+
// Find the earliest misprediction across all remote players
|
|
197
|
+
let earliestMisprediction;
|
|
198
|
+
const activePlayers = this.inputBuffer.getActivePlayers(this._currentTick);
|
|
199
|
+
for (const playerId of activePlayers) {
|
|
200
|
+
if (playerId === this.localPlayerId)
|
|
201
|
+
continue;
|
|
202
|
+
const mispredictTick = this.inputBuffer.findMisprediction(playerId, this._confirmedTick >= 0 ? asTick(this._confirmedTick + 1) : asTick(0));
|
|
203
|
+
if (mispredictTick !== undefined) {
|
|
204
|
+
if (earliestMisprediction === undefined ||
|
|
205
|
+
mispredictTick < earliestMisprediction) {
|
|
206
|
+
earliestMisprediction = mispredictTick;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (earliestMisprediction === undefined) {
|
|
211
|
+
return { rolledBack: false };
|
|
212
|
+
}
|
|
213
|
+
// We need to rollback to the tick BEFORE the misprediction
|
|
214
|
+
const restoreTick = asTick(earliestMisprediction - 1);
|
|
215
|
+
// Find snapshot to restore - first try exact match
|
|
216
|
+
let snapshot = this.snapshotBuffer.get(restoreTick);
|
|
217
|
+
let actualRestoreTick = restoreTick;
|
|
218
|
+
if (!snapshot) {
|
|
219
|
+
// Try to find the closest available snapshot at or before the target
|
|
220
|
+
snapshot = this.snapshotBuffer.getAtOrBefore(restoreTick);
|
|
221
|
+
if (!snapshot) {
|
|
222
|
+
// No snapshot available at all - this can happen at startup
|
|
223
|
+
// before any snapshots have been saved
|
|
224
|
+
return {
|
|
225
|
+
rolledBack: false,
|
|
226
|
+
error: new RollbackError(`Cannot rollback to tick ${restoreTick}: no snapshots available in buffer`, restoreTick),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// We found an earlier snapshot, adjust resimulation range
|
|
230
|
+
actualRestoreTick = snapshot.tick;
|
|
231
|
+
}
|
|
232
|
+
// Calculate resimulation range - from the tick AFTER the restored snapshot
|
|
233
|
+
const resimulateFromTick = asTick(actualRestoreTick + 1);
|
|
234
|
+
const ticksToResimulate = this._currentTick - resimulateFromTick;
|
|
235
|
+
// Restore game state
|
|
236
|
+
this.gameDeserialize(actualRestoreTick, snapshot.state);
|
|
237
|
+
// Notify about rollback before resimulation
|
|
238
|
+
this.onRollback?.(actualRestoreTick);
|
|
239
|
+
// Clear used inputs from the resimulation start tick onwards
|
|
240
|
+
this.inputBuffer.clearAllUsedInputsFrom(resimulateFromTick);
|
|
241
|
+
// Resimulate from the tick after restored snapshot to current tick
|
|
242
|
+
for (let tick = resimulateFromTick; tick < this._currentTick; tick++) {
|
|
243
|
+
const tickAsTick = asTick(tick);
|
|
244
|
+
// Handle player lifecycle events at this tick
|
|
245
|
+
this.handlePlayerLifecycleAtTick(tickAsTick);
|
|
246
|
+
const inputs = this.gatherInputs(tickAsTick);
|
|
247
|
+
this.gameStep(tickAsTick, inputs);
|
|
248
|
+
// Update snapshot
|
|
249
|
+
const state = this.gameSerialize(tickAsTick);
|
|
250
|
+
const hash = this.gameHash(tickAsTick);
|
|
251
|
+
this.snapshotBuffer.save(tickAsTick, state, hash);
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
rolledBack: true,
|
|
255
|
+
rollbackTicks: ticksToResimulate,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Handle player add/remove lifecycle events at a specific tick during resimulation.
|
|
260
|
+
* This ensures the game layer knows about player joins/leaves when replaying history.
|
|
261
|
+
* Uses O(1) tick-indexed lookups instead of iterating all players.
|
|
262
|
+
*/
|
|
263
|
+
handlePlayerLifecycleAtTick(tick) {
|
|
264
|
+
// Handle player joins at this tick
|
|
265
|
+
if (this.onPlayerAddDuringResimulation) {
|
|
266
|
+
const joiningPlayers = this.inputBuffer.getPlayersJoiningAtTick(tick);
|
|
267
|
+
for (const playerId of joiningPlayers) {
|
|
268
|
+
this.onPlayerAddDuringResimulation(playerId, tick);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Handle player leaves at this tick
|
|
272
|
+
if (this.onPlayerRemoveDuringResimulation) {
|
|
273
|
+
const leavingPlayers = this.inputBuffer.getPlayersLeavingAtTick(tick);
|
|
274
|
+
for (const playerId of leavingPlayers) {
|
|
275
|
+
this.onPlayerRemoveDuringResimulation(playerId, tick);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Gather inputs for all active players at a given tick.
|
|
281
|
+
* Uses real inputs when available, predicts otherwise.
|
|
282
|
+
*/
|
|
283
|
+
gatherInputs(tick) {
|
|
284
|
+
const inputs = new Map();
|
|
285
|
+
const activePlayers = this.inputBuffer.getActivePlayers(tick);
|
|
286
|
+
for (const playerId of activePlayers) {
|
|
287
|
+
let input;
|
|
288
|
+
if (playerId === this.localPlayerId) {
|
|
289
|
+
// Local player - use our stored input
|
|
290
|
+
input = this.localInputs.get(tick) ?? new Uint8Array(0);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
// Remote player - use received input or predict
|
|
294
|
+
const received = this.inputBuffer.getInput(playerId, tick);
|
|
295
|
+
if (received) {
|
|
296
|
+
input = received;
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
// Predict based on last confirmed input
|
|
300
|
+
const lastInput = this.inputBuffer.getLastConfirmedInput(playerId);
|
|
301
|
+
input = this.inputPredictor.predict(playerId, tick, lastInput);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
inputs.set(playerId, input);
|
|
305
|
+
// Record what we used for misprediction detection
|
|
306
|
+
this.inputBuffer.recordUsedInput(playerId, tick, input);
|
|
307
|
+
}
|
|
308
|
+
return inputs;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Update the confirmed tick based on all players' confirmed ticks.
|
|
312
|
+
*/
|
|
313
|
+
updateConfirmedTick() {
|
|
314
|
+
const minConfirmed = this.inputBuffer.getMinConfirmedTick(this._currentTick);
|
|
315
|
+
if (minConfirmed !== undefined && minConfirmed > this._confirmedTick) {
|
|
316
|
+
this._confirmedTick = minConfirmed;
|
|
317
|
+
// Prune old data only when we have enough confirmed ticks to keep a buffer
|
|
318
|
+
if (this._confirmedTick > this.pruneBufferTicks) {
|
|
319
|
+
const pruneBelow = asTick(this._confirmedTick - this.pruneBufferTicks);
|
|
320
|
+
this.inputBuffer.pruneBeforeTick(pruneBelow);
|
|
321
|
+
this.snapshotBuffer.pruneBeforeTick(pruneBelow);
|
|
322
|
+
// Also prune local inputs
|
|
323
|
+
for (const tick of this.localInputs.keys()) {
|
|
324
|
+
if (tick < pruneBelow) {
|
|
325
|
+
this.localInputs.delete(tick);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Get the hash at a specific tick.
|
|
333
|
+
*
|
|
334
|
+
* @param tick - The tick to get hash for
|
|
335
|
+
* @returns The hash, or undefined if not available
|
|
336
|
+
*/
|
|
337
|
+
getHash(tick) {
|
|
338
|
+
return this.snapshotBuffer.get(tick)?.hash;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Get the current game hash.
|
|
342
|
+
*/
|
|
343
|
+
getCurrentHash() {
|
|
344
|
+
return this.gameHash(this._currentTick);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Get the current game state and player timeline for sync.
|
|
348
|
+
*/
|
|
349
|
+
getState() {
|
|
350
|
+
const players = this.inputBuffer.getAllPlayers();
|
|
351
|
+
const playerTimeline = [];
|
|
352
|
+
for (const playerId of players) {
|
|
353
|
+
const joinTick = this.inputBuffer.getJoinTick(playerId);
|
|
354
|
+
const leaveTick = this.inputBuffer.getLeaveTick(playerId);
|
|
355
|
+
if (joinTick !== undefined) {
|
|
356
|
+
playerTimeline.push({
|
|
357
|
+
playerId,
|
|
358
|
+
joinTick,
|
|
359
|
+
leaveTick: leaveTick ?? null,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
tick: this._currentTick,
|
|
365
|
+
state: this.gameSerialize(this._currentTick),
|
|
366
|
+
playerTimeline,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Set the game state from a sync message.
|
|
371
|
+
* Used for late join or desync recovery.
|
|
372
|
+
*
|
|
373
|
+
* @param tick - The tick the state is from
|
|
374
|
+
* @param state - The serialized game state
|
|
375
|
+
* @param playerTimeline - Timeline of player join/leave events
|
|
376
|
+
*/
|
|
377
|
+
setState(tick, state, playerTimeline) {
|
|
378
|
+
// Restore game state
|
|
379
|
+
this.gameDeserialize(tick, state);
|
|
380
|
+
// Clear buffers
|
|
381
|
+
this.snapshotBuffer.clear();
|
|
382
|
+
this.inputBuffer.clear();
|
|
383
|
+
this.localInputs.clear();
|
|
384
|
+
// Restore player timeline
|
|
385
|
+
for (const entry of playerTimeline) {
|
|
386
|
+
this.inputBuffer.addPlayer(entry.playerId, entry.joinTick);
|
|
387
|
+
if (entry.leaveTick !== null) {
|
|
388
|
+
this.inputBuffer.removePlayer(entry.playerId, entry.leaveTick);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Set confirmed tick for all active players to tick-1
|
|
392
|
+
// This is critical for mid-game joins: the synced state represents
|
|
393
|
+
// a confirmed state, so all players who contributed to it have
|
|
394
|
+
// implicitly confirmed inputs up to that point
|
|
395
|
+
this.inputBuffer.setConfirmedTickForSync(tick);
|
|
396
|
+
// Save initial snapshot at tick - 1 (state before any simulation)
|
|
397
|
+
// This is needed so we can rollback tick 0 if there's a misprediction
|
|
398
|
+
const snapshotTick = asTick(tick - 1);
|
|
399
|
+
const hash = this.gameHash(snapshotTick);
|
|
400
|
+
this.snapshotBuffer.save(snapshotTick, state, hash);
|
|
401
|
+
// Set tick counters - currentTick is the next tick to simulate
|
|
402
|
+
this._currentTick = tick;
|
|
403
|
+
this._confirmedTick = asTick(tick - 1);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Reset buffers and tick counters for sync without changing game state.
|
|
407
|
+
*
|
|
408
|
+
* Used by the host when broadcasting sync to all players. The host's game
|
|
409
|
+
* state is already correct, but it needs to reset buffers and tick counters
|
|
410
|
+
* to match the synced state being sent to clients.
|
|
411
|
+
*
|
|
412
|
+
* This is more efficient than setState() when the game state doesn't need
|
|
413
|
+
* to be restored (avoids unnecessary serialize/deserialize round-trip).
|
|
414
|
+
*
|
|
415
|
+
* @param tick - The tick to reset to
|
|
416
|
+
* @param playerTimeline - Timeline of player join/leave events
|
|
417
|
+
*/
|
|
418
|
+
resetForSync(tick, playerTimeline) {
|
|
419
|
+
// Clear buffers
|
|
420
|
+
this.snapshotBuffer.clear();
|
|
421
|
+
this.inputBuffer.clear();
|
|
422
|
+
this.localInputs.clear();
|
|
423
|
+
// Restore player timeline
|
|
424
|
+
for (const entry of playerTimeline) {
|
|
425
|
+
this.inputBuffer.addPlayer(entry.playerId, entry.joinTick);
|
|
426
|
+
if (entry.leaveTick !== null) {
|
|
427
|
+
this.inputBuffer.removePlayer(entry.playerId, entry.leaveTick);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Set confirmed tick for all active players to tick-1
|
|
431
|
+
this.inputBuffer.setConfirmedTickForSync(tick);
|
|
432
|
+
// Save snapshot of current state at tick - 1
|
|
433
|
+
// (game state is already correct, just need to capture it)
|
|
434
|
+
const snapshotTick = asTick(tick - 1);
|
|
435
|
+
const state = this.gameSerialize(snapshotTick);
|
|
436
|
+
const hash = this.gameHash(snapshotTick);
|
|
437
|
+
this.snapshotBuffer.save(snapshotTick, state, hash);
|
|
438
|
+
// Set tick counters
|
|
439
|
+
this._currentTick = tick;
|
|
440
|
+
this._confirmedTick = asTick(tick - 1);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Check if we have all inputs for a given tick.
|
|
444
|
+
*
|
|
445
|
+
* @param tick - The tick to check
|
|
446
|
+
* @returns true if all inputs are available
|
|
447
|
+
*/
|
|
448
|
+
hasAllInputsForTick(tick) {
|
|
449
|
+
return this.inputBuffer.hasAllInputsForTick(tick);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Check if a tick is "settled" - meaning we've simulated past it
|
|
453
|
+
* and have all confirmed inputs for it.
|
|
454
|
+
*
|
|
455
|
+
* A settled tick's state is stable and won't change from future rollbacks,
|
|
456
|
+
* making it safe to compare hashes for desync detection.
|
|
457
|
+
*
|
|
458
|
+
* @param tick - The tick to check
|
|
459
|
+
* @param currentTick - The current simulation tick
|
|
460
|
+
* @returns true if the tick is settled
|
|
461
|
+
*/
|
|
462
|
+
isTickSettled(tick, currentTick) {
|
|
463
|
+
// Must have simulated past this tick
|
|
464
|
+
if (tick >= currentTick) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
// Must have all inputs confirmed
|
|
468
|
+
return this.inputBuffer.hasAllInputsForTick(tick);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Get the number of ticks we're speculating ahead.
|
|
472
|
+
*/
|
|
473
|
+
getSpeculationDistance() {
|
|
474
|
+
if (this._confirmedTick < 0) {
|
|
475
|
+
return this._currentTick;
|
|
476
|
+
}
|
|
477
|
+
return this._currentTick - this._confirmedTick - 1;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Get all active player IDs at the current tick.
|
|
481
|
+
*/
|
|
482
|
+
getActivePlayers() {
|
|
483
|
+
return this.inputBuffer.getActivePlayers(this._currentTick);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Get all player IDs.
|
|
487
|
+
*/
|
|
488
|
+
getAllPlayers() {
|
|
489
|
+
return this.inputBuffer.getAllPlayers();
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Reset the engine to initial state.
|
|
493
|
+
*/
|
|
494
|
+
reset() {
|
|
495
|
+
this.snapshotBuffer.clear();
|
|
496
|
+
this.inputBuffer.clear();
|
|
497
|
+
this.localInputs.clear();
|
|
498
|
+
this._currentTick = asTick(0);
|
|
499
|
+
this._confirmedTick = asTick(-1);
|
|
500
|
+
// Re-add local player
|
|
501
|
+
this.inputBuffer.addPlayer(this.localPlayerId, asTick(0));
|
|
502
|
+
}
|
|
503
|
+
// =========================================================================
|
|
504
|
+
// Game operation wrappers with error handling
|
|
505
|
+
// =========================================================================
|
|
506
|
+
/**
|
|
507
|
+
* Wrap a game operation with error handling.
|
|
508
|
+
* Catches any error and re-throws it wrapped in a GameError with context.
|
|
509
|
+
*/
|
|
510
|
+
wrapGameOperation(operation, tick, fn) {
|
|
511
|
+
try {
|
|
512
|
+
return fn();
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
throw new GameError(operation, tick, error instanceof Error ? error : new Error(String(error)));
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Call game.step() with error wrapping.
|
|
520
|
+
*/
|
|
521
|
+
gameStep(tick, inputs) {
|
|
522
|
+
this.wrapGameOperation("step", tick, () => this.game.step(inputs));
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Call game.serialize() with error wrapping.
|
|
526
|
+
*/
|
|
527
|
+
gameSerialize(tick) {
|
|
528
|
+
return this.wrapGameOperation("serialize", tick, () => this.game.serialize());
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Call game.deserialize() with error wrapping.
|
|
532
|
+
*/
|
|
533
|
+
gameDeserialize(tick, state) {
|
|
534
|
+
this.wrapGameOperation("deserialize", tick, () => this.game.deserialize(state));
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Call game.hash() with error wrapping.
|
|
538
|
+
*/
|
|
539
|
+
gameHash(tick) {
|
|
540
|
+
return this.wrapGameOperation("hash", tick, () => this.game.hash());
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/rollback/engine.ts"],"names":[],"mappings":"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"}
|