@carverjs/multiplayer 0.0.1 → 0.0.2
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/dist/InputBuffer-J6XT_Tt0.d.mts +61 -0
- package/dist/InputBuffer-V7XfHbc6.d.ts +61 -0
- package/dist/{NetworkManager-nvVAOr1O.d.ts → NetworkManager-D-DxFgdM.d.mts} +66 -14
- package/dist/{NetworkManager-DrKM2tEx.d.mts → NetworkManager-DH9uGVMg.d.ts} +66 -14
- package/dist/{chunk-UD6FDZMX.mjs → chunk-CBTAOVXP.mjs} +34 -3
- package/dist/chunk-CBTAOVXP.mjs.map +1 -0
- package/dist/{chunk-EO3YNPRQ.mjs → chunk-Q25TJEY4.mjs} +494 -204
- package/dist/chunk-Q25TJEY4.mjs.map +1 -0
- package/dist/{chunk-3KT73N2S.mjs → chunk-UKEFWQ76.mjs} +0 -0
- package/dist/chunk-UKEFWQ76.mjs.map +1 -0
- package/dist/{firebase-CPu87KA0.d.ts → firebase-B5MgLlHk.d.ts} +6 -1
- package/dist/{firebase-PE6MxGdJ.d.mts → firebase-GrbVrNgs.d.mts} +6 -1
- package/dist/index.d.mts +27 -6
- package/dist/index.d.ts +27 -6
- package/dist/index.js +744 -245
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +172 -37
- package/dist/index.mjs.map +1 -1
- package/dist/strategy.d.mts +2 -2
- package/dist/strategy.d.ts +2 -2
- package/dist/strategy.js +33 -2
- package/dist/strategy.js.map +1 -1
- package/dist/strategy.mjs +1 -1
- package/dist/sync.d.mts +134 -50
- package/dist/sync.d.ts +134 -50
- package/dist/sync.js +499 -205
- package/dist/sync.js.map +1 -1
- package/dist/sync.mjs +15 -3
- package/dist/transport.d.mts +0 -0
- package/dist/transport.d.ts +0 -0
- package/dist/transport.js +0 -0
- package/dist/transport.js.map +1 -1
- package/dist/transport.mjs +2 -2
- package/dist/{types-5LHBOW08.d.mts → types-hNfCIBzj.d.mts} +7 -0
- package/dist/{types-5LHBOW08.d.ts → types-hNfCIBzj.d.ts} +7 -0
- package/dist/types.d.mts +2 -2
- package/dist/types.d.ts +2 -2
- package/dist/types.js.map +1 -1
- package/package.json +26 -5
- package/dist/chunk-3KT73N2S.mjs.map +0 -1
- package/dist/chunk-EO3YNPRQ.mjs.map +0 -1
- package/dist/chunk-UD6FDZMX.mjs.map +0 -1
package/dist/sync.js
CHANGED
|
@@ -22,7 +22,13 @@ var sync_exports = {};
|
|
|
22
22
|
__export(sync_exports, {
|
|
23
23
|
EventSync: () => EventSync,
|
|
24
24
|
PredictionSync: () => PredictionSync,
|
|
25
|
-
SnapshotSync: () => SnapshotSync
|
|
25
|
+
SnapshotSync: () => SnapshotSync,
|
|
26
|
+
applyRollback: () => applyRollback,
|
|
27
|
+
quatAngle: () => quatAngle,
|
|
28
|
+
quatInvert: () => quatInvert,
|
|
29
|
+
quatMultiply: () => quatMultiply,
|
|
30
|
+
quatNormalize: () => quatNormalize,
|
|
31
|
+
quatScaleAngle: () => quatScaleAngle
|
|
26
32
|
});
|
|
27
33
|
module.exports = __toCommonJS(sync_exports);
|
|
28
34
|
|
|
@@ -165,8 +171,9 @@ var HostAuthority = class {
|
|
|
165
171
|
/**
|
|
166
172
|
* Called every fixed tick by the sync engine.
|
|
167
173
|
* Collects entity states and decides whether to broadcast.
|
|
174
|
+
* `hostInput` (prediction mode) is embedded in the snapshot packet as `hi`.
|
|
168
175
|
*/
|
|
169
|
-
tick(currentTick, entities, delta) {
|
|
176
|
+
tick(currentTick, entities, delta, hostInput) {
|
|
170
177
|
this._tick = currentTick;
|
|
171
178
|
this._snapshotBuffer.store(currentTick, new Map(entities));
|
|
172
179
|
this._broadcastAccumulator += delta;
|
|
@@ -174,16 +181,16 @@ var HostAuthority = class {
|
|
|
174
181
|
if (this._broadcastAccumulator < broadcastInterval) return;
|
|
175
182
|
this._broadcastAccumulator -= broadcastInterval;
|
|
176
183
|
for (const peerId of this._transport.peers) {
|
|
177
|
-
this._broadcastToClient(peerId, currentTick, entities);
|
|
184
|
+
this._broadcastToClient(peerId, currentTick, entities, hostInput);
|
|
178
185
|
}
|
|
179
186
|
}
|
|
180
187
|
/** Force a keyframe broadcast to all clients (e.g., after host migration) */
|
|
181
|
-
forceKeyframe(currentTick, entities) {
|
|
188
|
+
forceKeyframe(currentTick, entities, hostInput) {
|
|
182
189
|
this._clientBaselines.clear();
|
|
183
190
|
this._clientLastKeyframeTick.clear();
|
|
184
191
|
this._snapshotBuffer.store(currentTick, new Map(entities));
|
|
185
192
|
for (const peerId of this._transport.peers) {
|
|
186
|
-
this._broadcastToClient(peerId, currentTick, entities);
|
|
193
|
+
this._broadcastToClient(peerId, currentTick, entities, hostInput);
|
|
187
194
|
}
|
|
188
195
|
}
|
|
189
196
|
destroy() {
|
|
@@ -192,7 +199,7 @@ var HostAuthority = class {
|
|
|
192
199
|
this._clientBaselines.clear();
|
|
193
200
|
this._clientLastKeyframeTick.clear();
|
|
194
201
|
}
|
|
195
|
-
_broadcastToClient(peerId, currentTick, entities) {
|
|
202
|
+
_broadcastToClient(peerId, currentTick, entities, hostInput) {
|
|
196
203
|
let clientEntities = entities;
|
|
197
204
|
if (this._interestFilter) {
|
|
198
205
|
clientEntities = /* @__PURE__ */ new Map();
|
|
@@ -216,7 +223,8 @@ var HostAuthority = class {
|
|
|
216
223
|
currentTick,
|
|
217
224
|
needsKeyframe ? -1 : clientBaseTick ?? -1,
|
|
218
225
|
clientEntities,
|
|
219
|
-
baseline
|
|
226
|
+
baseline,
|
|
227
|
+
hostInput
|
|
220
228
|
);
|
|
221
229
|
if (packet) {
|
|
222
230
|
this._snapshotChannel.send(packet, peerId);
|
|
@@ -238,6 +246,8 @@ var ClientReceiver = class {
|
|
|
238
246
|
this._packetCount = 0;
|
|
239
247
|
// Entity state: full accumulated state from keyframes + deltas
|
|
240
248
|
this._fullState = /* @__PURE__ */ new Map();
|
|
249
|
+
// Listeners fired after each snapshot is merged into the full world state
|
|
250
|
+
this._snapshotListeners = [];
|
|
241
251
|
this._transport = transport;
|
|
242
252
|
this._codec = codec;
|
|
243
253
|
this._bufferSize = options?.bufferSize ?? 3;
|
|
@@ -324,16 +334,21 @@ var ClientReceiver = class {
|
|
|
324
334
|
requestKeyframe() {
|
|
325
335
|
this._ackChannel.send("-1");
|
|
326
336
|
}
|
|
337
|
+
/** Register a listener fired after each snapshot is merged into the full world state */
|
|
338
|
+
onSnapshot(cb) {
|
|
339
|
+
this._snapshotListeners.push(cb);
|
|
340
|
+
}
|
|
327
341
|
destroy() {
|
|
328
342
|
this._snapshotChannel.close();
|
|
329
343
|
this._ackChannel.close();
|
|
330
344
|
this._buffer = [];
|
|
331
345
|
this._interpolatedState.clear();
|
|
332
346
|
this._fullState.clear();
|
|
347
|
+
this._snapshotListeners = [];
|
|
333
348
|
}
|
|
334
349
|
_handleSnapshot(data) {
|
|
335
350
|
try {
|
|
336
|
-
const { tick, baseTick, entities } = this._codec.deserializePacket(data);
|
|
351
|
+
const { tick, baseTick, entities, hostInput } = this._codec.deserializePacket(data);
|
|
337
352
|
const now = performance.now();
|
|
338
353
|
if (baseTick === -1) {
|
|
339
354
|
this._fullState.clear();
|
|
@@ -349,15 +364,19 @@ var ClientReceiver = class {
|
|
|
349
364
|
}
|
|
350
365
|
}
|
|
351
366
|
}
|
|
367
|
+
const fullStateClone = new Map(this._fullState);
|
|
352
368
|
this._buffer.push({
|
|
353
369
|
tick,
|
|
354
|
-
entities:
|
|
370
|
+
entities: fullStateClone,
|
|
355
371
|
receivedAt: now
|
|
356
372
|
});
|
|
357
373
|
while (this._buffer.length > this._bufferSize * 2) {
|
|
358
374
|
this._buffer.shift();
|
|
359
375
|
}
|
|
360
376
|
this._ackChannel.send(String(tick));
|
|
377
|
+
for (const cb of this._snapshotListeners) {
|
|
378
|
+
cb(tick, fullStateClone, hostInput);
|
|
379
|
+
}
|
|
361
380
|
this._lastSnapshotTime = now;
|
|
362
381
|
this._packetCount++;
|
|
363
382
|
} catch {
|
|
@@ -515,6 +534,8 @@ var SnapshotSync = class {
|
|
|
515
534
|
constructor(transport, codec, snapshotBuffer, options) {
|
|
516
535
|
this._hostAuthority = null;
|
|
517
536
|
this._clientReceiver = null;
|
|
537
|
+
// Snapshot listeners survive host migration (forwarder re-attached on demote)
|
|
538
|
+
this._snapshotListeners = [];
|
|
518
539
|
this._transport = transport;
|
|
519
540
|
this._codec = codec;
|
|
520
541
|
this._snapshotBuffer = snapshotBuffer;
|
|
@@ -535,6 +556,7 @@ var SnapshotSync = class {
|
|
|
535
556
|
extrapolateMs: options?.extrapolateMs,
|
|
536
557
|
is2D: options?.is2D
|
|
537
558
|
});
|
|
559
|
+
this._attachSnapshotForwarder(this._clientReceiver);
|
|
538
560
|
}
|
|
539
561
|
}
|
|
540
562
|
get isHost() {
|
|
@@ -547,8 +569,12 @@ var SnapshotSync = class {
|
|
|
547
569
|
return this._clientReceiver;
|
|
548
570
|
}
|
|
549
571
|
/** Host: called every fixed tick to potentially broadcast state */
|
|
550
|
-
hostTick(tick, entities, delta) {
|
|
551
|
-
this._hostAuthority?.tick(tick, entities, delta);
|
|
572
|
+
hostTick(tick, entities, delta, hostInput) {
|
|
573
|
+
this._hostAuthority?.tick(tick, entities, delta, hostInput);
|
|
574
|
+
}
|
|
575
|
+
/** Register a listener fired after each merged snapshot (client side). */
|
|
576
|
+
onSnapshot(cb) {
|
|
577
|
+
this._snapshotListeners.push(cb);
|
|
552
578
|
}
|
|
553
579
|
/** Client: called every render frame to interpolate */
|
|
554
580
|
clientInterpolate(renderTime) {
|
|
@@ -586,260 +612,528 @@ var SnapshotSync = class {
|
|
|
586
612
|
is2D: options?.is2D
|
|
587
613
|
}
|
|
588
614
|
);
|
|
615
|
+
this._attachSnapshotForwarder(this._clientReceiver);
|
|
589
616
|
}
|
|
590
617
|
destroy() {
|
|
591
618
|
this._hostAuthority?.destroy();
|
|
592
619
|
this._clientReceiver?.destroy();
|
|
593
620
|
this._hostAuthority = null;
|
|
594
621
|
this._clientReceiver = null;
|
|
622
|
+
this._snapshotListeners = [];
|
|
623
|
+
}
|
|
624
|
+
// ── Private ──
|
|
625
|
+
/** Forward receiver snapshots to registered listeners (survives host migration). */
|
|
626
|
+
_attachSnapshotForwarder(receiver) {
|
|
627
|
+
receiver.onSnapshot((t, e, hi) => {
|
|
628
|
+
for (const l of this._snapshotListeners) l(t, e, hi);
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
// src/core/InputBuffer.ts
|
|
634
|
+
var InputBuffer = class {
|
|
635
|
+
constructor(neutralInput, historySize = 120) {
|
|
636
|
+
/** Local player tick-keyed inputs (ring buffer). */
|
|
637
|
+
this._local = /* @__PURE__ */ new Map();
|
|
638
|
+
/** Last-received input per remote peer. */
|
|
639
|
+
this._remotes = /* @__PURE__ */ new Map();
|
|
640
|
+
/** Per-peer tick-keyed inputs for accurate rollback re-simulation. */
|
|
641
|
+
this._peerTicks = /* @__PURE__ */ new Map();
|
|
642
|
+
this._neutral = { ...neutralInput };
|
|
643
|
+
this._historySize = historySize;
|
|
644
|
+
}
|
|
645
|
+
// ── Local input ──
|
|
646
|
+
/** Record a snapshot of the local input at the given tick. Evicts the entry exactly historySize back. */
|
|
647
|
+
storeTick(tick, input) {
|
|
648
|
+
this._local.set(tick, { ...input });
|
|
649
|
+
this._local.delete(tick - this._historySize);
|
|
650
|
+
}
|
|
651
|
+
/** Return the local input at `tick`, or a neutral copy if out of range. */
|
|
652
|
+
getTick(tick) {
|
|
653
|
+
return this._local.get(tick) ?? { ...this._neutral };
|
|
654
|
+
}
|
|
655
|
+
/** True if we have a stored local input for this tick (used to avoid spurious justPressed after snap/rejoin). */
|
|
656
|
+
hasTick(tick) {
|
|
657
|
+
return this._local.has(tick);
|
|
658
|
+
}
|
|
659
|
+
/** Neutral payload with every boolean field forced false (use for justPressed when prev tick is unknown). */
|
|
660
|
+
getJustPressedZero() {
|
|
661
|
+
const out = {};
|
|
662
|
+
for (const key in this._neutral) {
|
|
663
|
+
const v = this._neutral[key];
|
|
664
|
+
out[key] = typeof v === "boolean" ? false : v;
|
|
665
|
+
}
|
|
666
|
+
return out;
|
|
667
|
+
}
|
|
668
|
+
// ── Remote (peer) inputs ──
|
|
669
|
+
/**
|
|
670
|
+
* Record a remote peer's input. If `tick` is given (the sender's local tick),
|
|
671
|
+
* also store it in the per-peer ring buffer for rollback.
|
|
672
|
+
*/
|
|
673
|
+
setRemote(peerId, input, tick) {
|
|
674
|
+
this._remotes.set(peerId, input);
|
|
675
|
+
if (tick !== void 0) {
|
|
676
|
+
let peerMap = this._peerTicks.get(peerId);
|
|
677
|
+
if (!peerMap) {
|
|
678
|
+
peerMap = /* @__PURE__ */ new Map();
|
|
679
|
+
this._peerTicks.set(peerId, peerMap);
|
|
680
|
+
}
|
|
681
|
+
peerMap.set(tick, input);
|
|
682
|
+
peerMap.delete(tick - this._historySize);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
/** Last-known input for a peer, or a neutral copy if never received. */
|
|
686
|
+
getRemote(peerId) {
|
|
687
|
+
return this._remotes.get(peerId) ?? { ...this._neutral };
|
|
688
|
+
}
|
|
689
|
+
/** Snapshot of all remote peers' last-known inputs (shallow copy of the map). */
|
|
690
|
+
allRemotes() {
|
|
691
|
+
return new Map(this._remotes);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Return a peer's exact input at the given tick (for rollback accuracy),
|
|
695
|
+
* falling back to their last-known input when history does not reach that far.
|
|
696
|
+
*/
|
|
697
|
+
getRemoteAtTick(peerId, tick) {
|
|
698
|
+
return this._peerTicks.get(peerId)?.get(tick) ?? this.getRemote(peerId);
|
|
699
|
+
}
|
|
700
|
+
/** Override the last-known input for a peer (does NOT touch tick history). */
|
|
701
|
+
overrideRemote(peerId, input) {
|
|
702
|
+
this._remotes.set(peerId, input);
|
|
703
|
+
}
|
|
704
|
+
/** Number of currently tracked remote peers. */
|
|
705
|
+
get peerCount() {
|
|
706
|
+
return this._remotes.size;
|
|
707
|
+
}
|
|
708
|
+
/** Iterate tracked peer IDs. */
|
|
709
|
+
peerIds() {
|
|
710
|
+
return this._remotes.keys();
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Keep only these peer IDs; remove any other remotes (e.g. after leave/rejoin).
|
|
714
|
+
* Call when the room's peer list changes so stale peers stop receiving input.
|
|
715
|
+
*/
|
|
716
|
+
setPeerIds(peerIds) {
|
|
717
|
+
const set = peerIds instanceof Set ? peerIds : new Set(peerIds);
|
|
718
|
+
for (const id of this._remotes.keys()) {
|
|
719
|
+
if (!set.has(id)) {
|
|
720
|
+
this._remotes.delete(id);
|
|
721
|
+
this._peerTicks.delete(id);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
// ── Lifecycle ──
|
|
726
|
+
/** Clear local history, remote last-known inputs, and per-peer tick history. */
|
|
727
|
+
clear() {
|
|
728
|
+
this._local.clear();
|
|
729
|
+
this._remotes.clear();
|
|
730
|
+
this._peerTicks.clear();
|
|
595
731
|
}
|
|
596
732
|
};
|
|
597
733
|
|
|
734
|
+
// src/core/InputUtils.ts
|
|
735
|
+
function computeJustPressed(curr, prev) {
|
|
736
|
+
const out = {};
|
|
737
|
+
for (const key in curr) {
|
|
738
|
+
const c = curr[key];
|
|
739
|
+
out[key] = typeof c === "boolean" ? c === true && prev[key] !== true : c;
|
|
740
|
+
}
|
|
741
|
+
return out;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// src/sync/Rollback.ts
|
|
745
|
+
var IDENTITY_QUAT = { x: 0, y: 0, z: 0, w: 1 };
|
|
746
|
+
function quatMultiply(a, b) {
|
|
747
|
+
return {
|
|
748
|
+
x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
|
|
749
|
+
y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
|
|
750
|
+
z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
|
|
751
|
+
w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function quatInvert(q) {
|
|
755
|
+
return { x: -q.x, y: -q.y, z: -q.z, w: q.w };
|
|
756
|
+
}
|
|
757
|
+
function quatNormalize(q) {
|
|
758
|
+
const mag = Math.hypot(q.x, q.y, q.z, q.w);
|
|
759
|
+
if (mag < 1e-12) return { ...IDENTITY_QUAT };
|
|
760
|
+
return { x: q.x / mag, y: q.y / mag, z: q.z / mag, w: q.w / mag };
|
|
761
|
+
}
|
|
762
|
+
function quatAngle(q) {
|
|
763
|
+
return 2 * Math.acos(Math.min(1, Math.abs(q.w)));
|
|
764
|
+
}
|
|
765
|
+
function quatScaleAngle(q, factor) {
|
|
766
|
+
let n = quatNormalize(q);
|
|
767
|
+
if (n.w < 0) n = { x: -n.x, y: -n.y, z: -n.z, w: -n.w };
|
|
768
|
+
const angle = 2 * Math.acos(Math.min(1, n.w));
|
|
769
|
+
if (angle < 1e-6) return { ...IDENTITY_QUAT };
|
|
770
|
+
const s = Math.sin(angle / 2);
|
|
771
|
+
const ax = n.x / s;
|
|
772
|
+
const ay = n.y / s;
|
|
773
|
+
const az = n.z / s;
|
|
774
|
+
const na = angle * factor;
|
|
775
|
+
const ns = Math.sin(na / 2);
|
|
776
|
+
return { x: ax * ns, y: ay * ns, z: az * ns, w: Math.cos(na / 2) };
|
|
777
|
+
}
|
|
778
|
+
function applyRollback(params) {
|
|
779
|
+
const {
|
|
780
|
+
serverTick,
|
|
781
|
+
serverState,
|
|
782
|
+
localTick,
|
|
783
|
+
localPeerId,
|
|
784
|
+
inputs,
|
|
785
|
+
currentErrors,
|
|
786
|
+
driver,
|
|
787
|
+
callback,
|
|
788
|
+
dt,
|
|
789
|
+
driftTargetTicks,
|
|
790
|
+
maxRewindTicks,
|
|
791
|
+
snapThreshold
|
|
792
|
+
} = params;
|
|
793
|
+
const preState = driver.captureState();
|
|
794
|
+
const preMap = /* @__PURE__ */ new Map();
|
|
795
|
+
for (const [id, s] of preState) {
|
|
796
|
+
const e = currentErrors.get(id);
|
|
797
|
+
const ex = e?.x ?? 0;
|
|
798
|
+
const ey = e?.y ?? 0;
|
|
799
|
+
if ("z" in s) {
|
|
800
|
+
const errQ = e ? { x: e.qx, y: e.qy, z: e.qz, w: e.qw } : { ...IDENTITY_QUAT };
|
|
801
|
+
preMap.set(id, {
|
|
802
|
+
x: s.x + ex,
|
|
803
|
+
y: s.y + ey,
|
|
804
|
+
z: s.z + (e?.z ?? 0),
|
|
805
|
+
a: 0,
|
|
806
|
+
q: quatMultiply(errQ, { x: s.qx, y: s.qy, z: s.qz, w: s.qw }),
|
|
807
|
+
is3D: true
|
|
808
|
+
});
|
|
809
|
+
} else {
|
|
810
|
+
preMap.set(id, {
|
|
811
|
+
x: s.x + ex,
|
|
812
|
+
y: s.y + ey,
|
|
813
|
+
z: 0,
|
|
814
|
+
a: s.a + (e?.a ?? 0),
|
|
815
|
+
q: { ...IDENTITY_QUAT },
|
|
816
|
+
is3D: false
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
driver.applyState(serverState.values());
|
|
821
|
+
const targetTick = serverTick + driftTargetTicks;
|
|
822
|
+
const tickDiff = localTick - targetTick;
|
|
823
|
+
let newLocalTick = localTick;
|
|
824
|
+
let snapped = false;
|
|
825
|
+
if (Math.abs(tickDiff) > maxRewindTicks) {
|
|
826
|
+
newLocalTick = targetTick;
|
|
827
|
+
snapped = true;
|
|
828
|
+
} else {
|
|
829
|
+
for (let i = serverTick + 1; i <= localTick; i++) {
|
|
830
|
+
if (callback) {
|
|
831
|
+
const tickInputs = /* @__PURE__ */ new Map();
|
|
832
|
+
const justPressed = /* @__PURE__ */ new Map();
|
|
833
|
+
for (const peerId of inputs.peerIds()) {
|
|
834
|
+
if (peerId === localPeerId) continue;
|
|
835
|
+
const curr = inputs.getRemoteAtTick(peerId, i);
|
|
836
|
+
tickInputs.set(peerId, curr);
|
|
837
|
+
justPressed.set(
|
|
838
|
+
peerId,
|
|
839
|
+
computeJustPressed(curr, inputs.getRemoteAtTick(peerId, i - 1))
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
const localCurr = inputs.getTick(i);
|
|
843
|
+
tickInputs.set(localPeerId, localCurr);
|
|
844
|
+
justPressed.set(
|
|
845
|
+
localPeerId,
|
|
846
|
+
computeJustPressed(localCurr, inputs.getTick(i - 1))
|
|
847
|
+
);
|
|
848
|
+
callback(tickInputs, justPressed, i, true, dt);
|
|
849
|
+
}
|
|
850
|
+
driver.stepWorld?.();
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
const postState = driver.captureState();
|
|
854
|
+
const errors = /* @__PURE__ */ new Map();
|
|
855
|
+
for (const [id, post] of postState) {
|
|
856
|
+
const pre = preMap.get(id);
|
|
857
|
+
if (!pre) continue;
|
|
858
|
+
const errX = pre.x - post.x;
|
|
859
|
+
const errY = pre.y - post.y;
|
|
860
|
+
let errZ = 0;
|
|
861
|
+
let errA = 0;
|
|
862
|
+
let q = { ...IDENTITY_QUAT };
|
|
863
|
+
if ("z" in post) {
|
|
864
|
+
errZ = pre.z - post.z;
|
|
865
|
+
let dq = quatNormalize(
|
|
866
|
+
quatMultiply(
|
|
867
|
+
pre.q,
|
|
868
|
+
quatInvert({ x: post.qx, y: post.qy, z: post.qz, w: post.qw })
|
|
869
|
+
)
|
|
870
|
+
);
|
|
871
|
+
if (dq.w < 0) dq = { x: -dq.x, y: -dq.y, z: -dq.z, w: -dq.w };
|
|
872
|
+
q = dq;
|
|
873
|
+
} else {
|
|
874
|
+
const ad = pre.a - post.a;
|
|
875
|
+
errA = ad - Math.PI * 2 * Math.floor((ad + Math.PI) / (Math.PI * 2));
|
|
876
|
+
}
|
|
877
|
+
if (Math.abs(errX) > snapThreshold || Math.abs(errY) > snapThreshold || Math.abs(errZ) > snapThreshold) {
|
|
878
|
+
errors.set(id, { x: 0, y: 0, z: 0, a: 0, qx: 0, qy: 0, qz: 0, qw: 1 });
|
|
879
|
+
} else {
|
|
880
|
+
errors.set(id, {
|
|
881
|
+
x: errX,
|
|
882
|
+
y: errY,
|
|
883
|
+
z: errZ,
|
|
884
|
+
a: errA,
|
|
885
|
+
qx: q.x,
|
|
886
|
+
qy: q.y,
|
|
887
|
+
qz: q.z,
|
|
888
|
+
qw: q.w
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return { newLocalTick, snapped, errors };
|
|
893
|
+
}
|
|
894
|
+
|
|
598
895
|
// src/sync/PredictionSync.ts
|
|
599
|
-
var import_msgpackr = require("msgpackr");
|
|
600
896
|
var DEFAULT_OPTIONS = {
|
|
601
897
|
maxRewindTicks: 15,
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
898
|
+
snapThreshold: 150,
|
|
899
|
+
errorDecay: 0.85,
|
|
900
|
+
maxErrorPerFrame: 0,
|
|
901
|
+
neutralInput: {},
|
|
902
|
+
inputHistorySize: 120,
|
|
903
|
+
driftTargetTicks: 4
|
|
606
904
|
};
|
|
607
905
|
var PredictionSync = class {
|
|
608
|
-
constructor(transport,
|
|
609
|
-
//
|
|
610
|
-
this.
|
|
611
|
-
//
|
|
612
|
-
this.
|
|
613
|
-
//
|
|
614
|
-
this.
|
|
615
|
-
// Error correction vectors per entity
|
|
616
|
-
this._errorCorrections = /* @__PURE__ */ new Map();
|
|
617
|
-
// Server state (last received authoritative snapshot)
|
|
618
|
-
this._serverState = /* @__PURE__ */ new Map();
|
|
906
|
+
constructor(transport, tickKeeper, snapshots, options) {
|
|
907
|
+
// Local input; persists across ticks until replaced (hold-input semantics)
|
|
908
|
+
this._currentInput = null;
|
|
909
|
+
// Newest pending server snapshot awaiting rollback (only the newest survives)
|
|
910
|
+
this._pending = null;
|
|
911
|
+
// Per-entity accumulated visual error offsets
|
|
912
|
+
this._errors = /* @__PURE__ */ new Map();
|
|
619
913
|
this._serverTick = 0;
|
|
620
|
-
|
|
914
|
+
this._lastAppliedServerTick = 0;
|
|
915
|
+
this._worldDriver = null;
|
|
621
916
|
this._onPhysicsStep = null;
|
|
622
|
-
// Own input for current tick
|
|
623
|
-
this._currentInput = null;
|
|
624
917
|
this._transport = transport;
|
|
625
|
-
this._codec = codec;
|
|
626
918
|
this._tickKeeper = tickKeeper;
|
|
627
919
|
this._options = { ...DEFAULT_OPTIONS, ...options };
|
|
628
|
-
this.
|
|
920
|
+
this._inputs = new InputBuffer(
|
|
921
|
+
this._options.neutralInput,
|
|
922
|
+
this._options.inputHistorySize
|
|
923
|
+
);
|
|
629
924
|
this._inputChannel = transport.createChannel("carver:inputs", {
|
|
630
925
|
reliable: true,
|
|
631
926
|
ordered: true
|
|
632
927
|
});
|
|
633
|
-
this.
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
928
|
+
this._inputChannel.onReceive(
|
|
929
|
+
(data, peerId) => {
|
|
930
|
+
try {
|
|
931
|
+
const packet = typeof data === "string" ? JSON.parse(data) : data;
|
|
932
|
+
if (typeof packet.t === "number" && packet.i !== null && typeof packet.i === "object") {
|
|
933
|
+
this._inputs.setRemote(peerId, packet.i, packet.t);
|
|
934
|
+
}
|
|
935
|
+
} catch (err) {
|
|
936
|
+
if (typeof console !== "undefined")
|
|
937
|
+
console.debug("[CarverJS] Malformed input packet:", err);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
);
|
|
941
|
+
snapshots.onSnapshot((tick, entities, hostInput) => {
|
|
942
|
+
if (this._transport.isHost) return;
|
|
943
|
+
if (tick <= this._lastAppliedServerTick) return;
|
|
944
|
+
this._serverTick = tick;
|
|
945
|
+
this._tickKeeper.setServerTick(tick);
|
|
946
|
+
if (!this._pending || tick > this._pending.t) {
|
|
947
|
+
this._pending = { t: tick, entities, hostInput };
|
|
948
|
+
}
|
|
637
949
|
});
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
ordered: true
|
|
950
|
+
transport.onPeerLeave(() => {
|
|
951
|
+
this._inputs.setPeerIds(this._transport.peers);
|
|
641
952
|
});
|
|
642
|
-
if (this._isHost) {
|
|
643
|
-
this._setupHostListeners();
|
|
644
|
-
} else {
|
|
645
|
-
this._setupClientListeners();
|
|
646
|
-
}
|
|
647
953
|
}
|
|
648
|
-
|
|
954
|
+
// ── Wiring ──
|
|
955
|
+
/** Set the game simulation callback (forward sim + rollback resim). */
|
|
649
956
|
setPhysicsStep(cb) {
|
|
650
957
|
this._onPhysicsStep = cb;
|
|
651
958
|
}
|
|
652
|
-
/** Set the
|
|
959
|
+
/** Set the world driver used for forward stepping and rollback. */
|
|
960
|
+
setWorldDriver(driver) {
|
|
961
|
+
this._worldDriver = driver;
|
|
962
|
+
}
|
|
963
|
+
// ── Input ──
|
|
964
|
+
/** Set the local player's input. PERSISTS across ticks until replaced. */
|
|
653
965
|
setInput(input) {
|
|
654
966
|
this._currentInput = input;
|
|
655
967
|
}
|
|
968
|
+
/** Local input stored at the given tick (neutral fallback). Used by the host to embed `hi`. */
|
|
969
|
+
getLocalInput(tick) {
|
|
970
|
+
return this._inputs.getTick(tick);
|
|
971
|
+
}
|
|
972
|
+
// ── Frame lifecycle ──
|
|
656
973
|
/**
|
|
657
|
-
*
|
|
658
|
-
*
|
|
974
|
+
* Apply the newest pending server snapshot (full-world rollback).
|
|
975
|
+
* Call once per render frame BEFORE tickKeeper.update().
|
|
976
|
+
* No-op on host or when nothing is pending.
|
|
659
977
|
*/
|
|
660
|
-
|
|
661
|
-
if (this.
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
const inputs = /* @__PURE__ */ new Map();
|
|
672
|
-
inputs.set(this._transport.peerId, this._currentInput);
|
|
673
|
-
this._onPhysicsStep(inputs, tick, false);
|
|
674
|
-
}
|
|
675
|
-
this._currentInput = null;
|
|
978
|
+
beginFrame() {
|
|
979
|
+
if (this._transport.isHost || !this._pending) return;
|
|
980
|
+
const pending = this._pending;
|
|
981
|
+
this._pending = null;
|
|
982
|
+
this._lastAppliedServerTick = pending.t;
|
|
983
|
+
if (pending.hostInput !== void 0) {
|
|
984
|
+
this._inputs.setRemote(
|
|
985
|
+
this._transport.hostId,
|
|
986
|
+
pending.hostInput,
|
|
987
|
+
pending.t
|
|
988
|
+
);
|
|
676
989
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
990
|
+
if (!this._worldDriver) return;
|
|
991
|
+
const result = applyRollback({
|
|
992
|
+
serverTick: pending.t,
|
|
993
|
+
serverState: pending.entities,
|
|
994
|
+
localTick: this._tickKeeper.tick,
|
|
995
|
+
localPeerId: this._transport.peerId,
|
|
996
|
+
inputs: this._inputs,
|
|
997
|
+
currentErrors: this._errors,
|
|
998
|
+
driver: this._worldDriver,
|
|
999
|
+
callback: this._onPhysicsStep,
|
|
1000
|
+
dt: this._tickKeeper.tickDelta,
|
|
1001
|
+
driftTargetTicks: this._options.driftTargetTicks,
|
|
1002
|
+
maxRewindTicks: this._options.maxRewindTicks,
|
|
1003
|
+
snapThreshold: this._options.snapThreshold
|
|
1004
|
+
});
|
|
1005
|
+
this._errors = result.errors;
|
|
1006
|
+
if (result.newLocalTick !== this._tickKeeper.tick) {
|
|
1007
|
+
this._tickKeeper.snapTick(result.newLocalTick);
|
|
680
1008
|
}
|
|
681
1009
|
}
|
|
682
1010
|
/**
|
|
683
|
-
*
|
|
684
|
-
*
|
|
1011
|
+
* Run one forward fixed tick (host AND client): store + broadcast input,
|
|
1012
|
+
* build per-tick input maps, invoke the callback, then step the world.
|
|
685
1013
|
*/
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1014
|
+
tick(tick) {
|
|
1015
|
+
const localInput = this._currentInput ?? { ...this._options.neutralInput };
|
|
1016
|
+
this._inputs.storeTick(tick, localInput);
|
|
1017
|
+
this._inputChannel.send({
|
|
1018
|
+
t: tick,
|
|
1019
|
+
i: localInput,
|
|
1020
|
+
p: this._transport.peerId
|
|
1021
|
+
});
|
|
1022
|
+
const prevTick = tick - 1;
|
|
1023
|
+
const tickInputs = this._inputs.allRemotes();
|
|
1024
|
+
tickInputs.delete(this._transport.peerId);
|
|
1025
|
+
const justPressed = /* @__PURE__ */ new Map();
|
|
1026
|
+
for (const [peerId, inp] of tickInputs) {
|
|
1027
|
+
justPressed.set(
|
|
1028
|
+
peerId,
|
|
1029
|
+
computeJustPressed(inp, this._inputs.getRemoteAtTick(peerId, prevTick))
|
|
1030
|
+
);
|
|
698
1031
|
}
|
|
1032
|
+
tickInputs.set(this._transport.peerId, localInput);
|
|
1033
|
+
justPressed.set(
|
|
1034
|
+
this._transport.peerId,
|
|
1035
|
+
this._inputs.hasTick(prevTick) ? computeJustPressed(localInput, this._inputs.getTick(prevTick)) : this._inputs.getJustPressedZero()
|
|
1036
|
+
// suppress spurious edges after snap/rejoin
|
|
1037
|
+
);
|
|
1038
|
+
if (this._onPhysicsStep) {
|
|
1039
|
+
this._onPhysicsStep(
|
|
1040
|
+
tickInputs,
|
|
1041
|
+
justPressed,
|
|
1042
|
+
tick,
|
|
1043
|
+
false,
|
|
1044
|
+
this._tickKeeper.tickDelta
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
this._worldDriver?.stepWorld?.();
|
|
699
1048
|
}
|
|
700
1049
|
/**
|
|
701
|
-
*
|
|
702
|
-
*
|
|
1050
|
+
* Decay stored error offsets and return the portion to ADD to rendered
|
|
1051
|
+
* transforms this frame. Call exactly once per render frame.
|
|
703
1052
|
*/
|
|
704
|
-
|
|
1053
|
+
getRenderErrorOffsets() {
|
|
705
1054
|
const result = /* @__PURE__ */ new Map();
|
|
706
|
-
const decay = this._options.
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1055
|
+
const decay = this._options.errorDecay;
|
|
1056
|
+
const maxErr = this._options.maxErrorPerFrame;
|
|
1057
|
+
for (const [id, e] of this._errors) {
|
|
1058
|
+
e.x *= decay;
|
|
1059
|
+
e.y *= decay;
|
|
1060
|
+
e.z *= decay;
|
|
1061
|
+
e.a *= decay;
|
|
1062
|
+
let q = quatScaleAngle({ x: e.qx, y: e.qy, z: e.qz, w: e.qw }, decay);
|
|
1063
|
+
if (Math.abs(e.x) < 0.1) e.x = 0;
|
|
1064
|
+
if (Math.abs(e.y) < 0.1) e.y = 0;
|
|
1065
|
+
if (Math.abs(e.z) < 0.1) e.z = 0;
|
|
1066
|
+
if (Math.abs(e.a) < 1e-3) e.a = 0;
|
|
1067
|
+
if (quatAngle(q) < 1e-3) q = { x: 0, y: 0, z: 0, w: 1 };
|
|
1068
|
+
e.qx = q.x;
|
|
1069
|
+
e.qy = q.y;
|
|
1070
|
+
e.qz = q.z;
|
|
1071
|
+
e.qw = q.w;
|
|
1072
|
+
let ax = e.x;
|
|
1073
|
+
let ay = e.y;
|
|
1074
|
+
let az = e.z;
|
|
1075
|
+
if (maxErr > 0) {
|
|
1076
|
+
const mag = Math.hypot(e.x, e.y, e.z);
|
|
1077
|
+
if (mag > maxErr) {
|
|
1078
|
+
const s = maxErr / mag;
|
|
1079
|
+
ax = e.x * s;
|
|
1080
|
+
ay = e.y * s;
|
|
1081
|
+
az = e.z * s;
|
|
1082
|
+
e.x -= ax;
|
|
1083
|
+
e.y -= ay;
|
|
1084
|
+
e.z -= az;
|
|
1085
|
+
} else {
|
|
1086
|
+
e.x = 0;
|
|
1087
|
+
e.y = 0;
|
|
1088
|
+
e.z = 0;
|
|
1089
|
+
}
|
|
712
1090
|
}
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1091
|
+
const quatIsIdentity = e.qx === 0 && e.qy === 0 && e.qz === 0 && e.qw === 1;
|
|
1092
|
+
if (ax !== 0 || ay !== 0 || az !== 0 || e.a !== 0 || !quatIsIdentity) {
|
|
1093
|
+
result.set(id, {
|
|
1094
|
+
x: ax,
|
|
1095
|
+
y: ay,
|
|
1096
|
+
z: az,
|
|
1097
|
+
a: e.a,
|
|
1098
|
+
qx: e.qx,
|
|
1099
|
+
qy: e.qy,
|
|
1100
|
+
qz: e.qz,
|
|
1101
|
+
qw: e.qw
|
|
1102
|
+
});
|
|
718
1103
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
correction.z *= decay;
|
|
722
|
-
const mag = Math.abs(correction.x) + Math.abs(correction.y) + Math.abs(correction.z);
|
|
723
|
-
if (mag < 1e-3) {
|
|
724
|
-
this._errorCorrections.delete(id);
|
|
1104
|
+
if (e.x === 0 && e.y === 0 && e.z === 0 && e.a === 0 && quatIsIdentity) {
|
|
1105
|
+
this._errors.delete(id);
|
|
725
1106
|
}
|
|
726
|
-
result.set(id, corrected);
|
|
727
1107
|
}
|
|
728
1108
|
return result;
|
|
729
1109
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
}
|
|
1110
|
+
// ── State ──
|
|
1111
|
+
/** Tick of the newest RECEIVED snapshot. */
|
|
733
1112
|
get serverTick() {
|
|
734
1113
|
return this._serverTick;
|
|
735
1114
|
}
|
|
1115
|
+
/** Tick of the newest APPLIED (rolled-back) snapshot, 0 initially. */
|
|
1116
|
+
get lastAppliedServerTick() {
|
|
1117
|
+
return this._lastAppliedServerTick;
|
|
1118
|
+
}
|
|
736
1119
|
destroy() {
|
|
737
1120
|
this._inputChannel.close();
|
|
738
|
-
this.
|
|
739
|
-
this.
|
|
740
|
-
this.
|
|
741
|
-
this.
|
|
742
|
-
this._errorCorrections.clear();
|
|
743
|
-
this._serverState.clear();
|
|
744
|
-
}
|
|
745
|
-
// ── Private: Host-side ──
|
|
746
|
-
_setupHostListeners() {
|
|
747
|
-
this._inputChannel.onReceive((rawData, peerId) => {
|
|
748
|
-
try {
|
|
749
|
-
const packet = JSON.parse(rawData);
|
|
750
|
-
if (this._onPhysicsStep) {
|
|
751
|
-
const inputs = /* @__PURE__ */ new Map();
|
|
752
|
-
inputs.set(peerId, packet.i);
|
|
753
|
-
this._onPhysicsStep(inputs, packet.t, false);
|
|
754
|
-
}
|
|
755
|
-
const prevTick = this._clientLastProcessedTick.get(peerId) ?? -1;
|
|
756
|
-
this._clientLastProcessedTick.set(peerId, Math.max(prevTick, packet.t));
|
|
757
|
-
} catch (err) {
|
|
758
|
-
if (typeof console !== "undefined") console.debug("[CarverJS] Malformed input packet:", err);
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
// ── Private: Client-side ──
|
|
763
|
-
_setupClientListeners() {
|
|
764
|
-
this._stateChannel.onReceive((data) => {
|
|
765
|
-
try {
|
|
766
|
-
const packet = (0, import_msgpackr.unpack)(data);
|
|
767
|
-
const entities = this._codec.deserialize(packet.s);
|
|
768
|
-
const serverTick = packet.t;
|
|
769
|
-
const lastInputTick = packet.li;
|
|
770
|
-
this._serverTick = serverTick;
|
|
771
|
-
this._tickKeeper.setServerTick(serverTick);
|
|
772
|
-
this._serverState.clear();
|
|
773
|
-
for (const entity of entities) {
|
|
774
|
-
this._serverState.set(entity.id, entity);
|
|
775
|
-
}
|
|
776
|
-
this._reconcile(lastInputTick);
|
|
777
|
-
} catch (err) {
|
|
778
|
-
if (typeof console !== "undefined") console.debug("[CarverJS] Malformed state packet:", err);
|
|
779
|
-
}
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
_reconcile(lastInputTick) {
|
|
783
|
-
this._inputBuffer = this._inputBuffer.filter((entry) => entry.tick > lastInputTick);
|
|
784
|
-
let needsRollback = false;
|
|
785
|
-
let maxError = 0;
|
|
786
|
-
for (const [id, serverEntity] of this._serverState) {
|
|
787
|
-
const predicted = this._predictedState.get(id);
|
|
788
|
-
if (!predicted) continue;
|
|
789
|
-
const error = this._computeError(predicted, serverEntity);
|
|
790
|
-
maxError = Math.max(maxError, error);
|
|
791
|
-
if (error > this._options.maxErrorPerFrame) {
|
|
792
|
-
needsRollback = true;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
if (maxError > this._options.snapThreshold) {
|
|
796
|
-
this._predictedState = new Map(this._serverState);
|
|
797
|
-
this._errorCorrections.clear();
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
if (needsRollback) {
|
|
801
|
-
const oldPositions = /* @__PURE__ */ new Map();
|
|
802
|
-
for (const [id, entity] of this._predictedState) {
|
|
803
|
-
oldPositions.set(id, {
|
|
804
|
-
x: entity.x,
|
|
805
|
-
y: entity.y,
|
|
806
|
-
z: "z" in entity ? entity.z : 0
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
this._predictedState = new Map(this._serverState);
|
|
810
|
-
if (this._onPhysicsStep) {
|
|
811
|
-
for (const entry of this._inputBuffer) {
|
|
812
|
-
const inputs = /* @__PURE__ */ new Map();
|
|
813
|
-
inputs.set(this._transport.peerId, entry.input);
|
|
814
|
-
this._onPhysicsStep(inputs, entry.tick, true);
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
for (const [id, newEntity] of this._predictedState) {
|
|
818
|
-
const oldPos = oldPositions.get(id);
|
|
819
|
-
if (oldPos) {
|
|
820
|
-
this._errorCorrections.set(id, {
|
|
821
|
-
x: oldPos.x - newEntity.x,
|
|
822
|
-
y: oldPos.y - newEntity.y,
|
|
823
|
-
z: oldPos.z - ("z" in newEntity ? newEntity.z : 0)
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
_computeError(predicted, server) {
|
|
830
|
-
const dx = predicted.x - server.x;
|
|
831
|
-
const dy = predicted.y - server.y;
|
|
832
|
-
let dz = 0;
|
|
833
|
-
if ("z" in predicted && "z" in server) {
|
|
834
|
-
dz = predicted.z - server.z;
|
|
835
|
-
}
|
|
836
|
-
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
1121
|
+
this._inputs.clear();
|
|
1122
|
+
this._errors.clear();
|
|
1123
|
+
this._pending = null;
|
|
1124
|
+
this._currentInput = null;
|
|
837
1125
|
}
|
|
838
1126
|
};
|
|
839
1127
|
// Annotate the CommonJS export names for ESM import in node:
|
|
840
1128
|
0 && (module.exports = {
|
|
841
1129
|
EventSync,
|
|
842
1130
|
PredictionSync,
|
|
843
|
-
SnapshotSync
|
|
1131
|
+
SnapshotSync,
|
|
1132
|
+
applyRollback,
|
|
1133
|
+
quatAngle,
|
|
1134
|
+
quatInvert,
|
|
1135
|
+
quatMultiply,
|
|
1136
|
+
quatNormalize,
|
|
1137
|
+
quatScaleAngle
|
|
844
1138
|
});
|
|
845
1139
|
//# sourceMappingURL=sync.js.map
|