murow 0.1.1 → 0.1.3

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 (105) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/core/clock/clock.js +1 -0
  3. package/dist/cjs/core/clock/index.js +1 -0
  4. package/dist/cjs/core/hitbox/hitbox-library.js +1 -0
  5. package/dist/cjs/core/hitbox/hitbox.js +1 -0
  6. package/dist/cjs/core/hitbox/index.js +1 -0
  7. package/dist/cjs/core/hitbox/test.js +1 -0
  8. package/dist/cjs/core/index.js +1 -1
  9. package/dist/cjs/core/prediction/prediction.js +1 -1
  10. package/dist/cjs/core/ray/ray-3d.js +1 -1
  11. package/dist/cjs/core/raycast/hit-buffer.js +1 -0
  12. package/dist/cjs/core/raycast/index.js +1 -0
  13. package/dist/cjs/core/raycast/raycaster.js +1 -0
  14. package/dist/cjs/core/slot-map/index.js +1 -0
  15. package/dist/cjs/core/slot-map/slot-map.js +1 -0
  16. package/dist/cjs/core/state-machine/index.js +1 -0
  17. package/dist/cjs/core/state-machine/state-machine.js +1 -0
  18. package/dist/cjs/core/timeline/index.js +1 -0
  19. package/dist/cjs/core/timeline/timeline.js +1 -0
  20. package/dist/cjs/game/loop/loop.js +1 -1
  21. package/dist/cjs/game/loop/ticker-schedule.js +1 -0
  22. package/dist/cjs/renderer/index.js +1 -1
  23. package/dist/cjs/renderer/prefab-bucket/concrete.js +1 -1
  24. package/dist/cjs/renderer/prefab-bucket/index.js +1 -1
  25. package/dist/cjs/renderer/prefab-bucket/parsers.js +1 -1
  26. package/dist/cjs/renderer/prefab-bucket/specs.js +1 -1
  27. package/dist/cjs/renderer/raycast/index.js +1 -0
  28. package/dist/cjs/renderer/raycast/raycast.js +1 -0
  29. package/dist/esm/core/clock/clock.js +1 -0
  30. package/dist/esm/core/clock/index.js +1 -0
  31. package/dist/esm/core/hitbox/hitbox-library.js +1 -0
  32. package/dist/esm/core/hitbox/hitbox.js +1 -0
  33. package/dist/esm/core/hitbox/index.js +1 -0
  34. package/dist/esm/core/hitbox/test.js +1 -0
  35. package/dist/esm/core/index.js +1 -1
  36. package/dist/esm/core/prediction/prediction.js +1 -1
  37. package/dist/esm/core/ray/ray-3d.js +1 -1
  38. package/dist/esm/core/raycast/hit-buffer.js +1 -0
  39. package/dist/esm/core/raycast/index.js +1 -0
  40. package/dist/esm/core/raycast/raycaster.js +1 -0
  41. package/dist/esm/core/slot-map/index.js +1 -0
  42. package/dist/esm/core/slot-map/slot-map.js +1 -0
  43. package/dist/esm/core/state-machine/index.js +1 -0
  44. package/dist/esm/core/state-machine/state-machine.js +1 -0
  45. package/dist/esm/core/timeline/index.js +1 -0
  46. package/dist/esm/core/timeline/timeline.js +1 -0
  47. package/dist/esm/game/loop/loop.js +1 -1
  48. package/dist/esm/game/loop/ticker-schedule.js +1 -0
  49. package/dist/esm/renderer/index.js +1 -1
  50. package/dist/esm/renderer/prefab-bucket/concrete.js +1 -1
  51. package/dist/esm/renderer/prefab-bucket/index.js +1 -1
  52. package/dist/esm/renderer/prefab-bucket/parsers.js +1 -1
  53. package/dist/esm/renderer/raycast/index.js +1 -0
  54. package/dist/esm/renderer/raycast/raycast.js +1 -0
  55. package/dist/netcode/cjs/index.js +144 -140
  56. package/dist/netcode/esm/index.js +144 -140
  57. package/dist/netcode/types/client/game-client.d.ts +17 -3
  58. package/dist/netcode/types/client/strategies/snapshot-interpolation.d.ts +33 -0
  59. package/dist/netcode/types/codec/delta-codec.d.ts +1 -1
  60. package/dist/netcode/types/components/sync-spec.d.ts +6 -0
  61. package/dist/types/core/clock/clock.d.ts +37 -0
  62. package/dist/types/core/clock/index.d.ts +1 -0
  63. package/dist/types/core/hitbox/hitbox-library.d.ts +29 -0
  64. package/dist/types/core/hitbox/hitbox.d.ts +50 -0
  65. package/dist/types/core/hitbox/index.d.ts +3 -0
  66. package/dist/types/core/hitbox/test.d.ts +44 -0
  67. package/dist/types/core/index.d.ts +6 -0
  68. package/dist/types/core/prediction/prediction.d.ts +35 -58
  69. package/dist/types/core/ray/ray-3d.d.ts +21 -1
  70. package/dist/types/core/raycast/hit-buffer.d.ts +43 -0
  71. package/dist/types/core/raycast/index.d.ts +2 -0
  72. package/dist/types/core/raycast/raycaster.d.ts +54 -0
  73. package/dist/types/core/slot-map/index.d.ts +1 -0
  74. package/dist/types/core/slot-map/slot-map.d.ts +109 -0
  75. package/dist/types/core/state-machine/index.d.ts +1 -0
  76. package/dist/types/core/state-machine/state-machine.d.ts +114 -0
  77. package/dist/types/core/timeline/index.d.ts +1 -0
  78. package/dist/types/core/timeline/timeline.d.ts +34 -0
  79. package/dist/types/game/loop/loop.d.ts +30 -0
  80. package/dist/types/game/loop/ticker-schedule.d.ts +52 -0
  81. package/dist/types/renderer/index.d.ts +1 -0
  82. package/dist/types/renderer/prefab-bucket/concrete.d.ts +16 -6
  83. package/dist/types/renderer/prefab-bucket/index.d.ts +11 -7
  84. package/dist/types/renderer/prefab-bucket/specs.d.ts +10 -0
  85. package/dist/types/renderer/raycast/index.d.ts +1 -0
  86. package/dist/types/renderer/raycast/raycast.d.ts +24 -0
  87. package/dist/types/renderer/types.d.ts +1 -0
  88. package/dist/webgpu/cjs/index.js +1777 -587
  89. package/dist/webgpu/esm/index.js +1769 -573
  90. package/dist/webgpu/types/2d/raycast.d.ts +45 -0
  91. package/dist/webgpu/types/2d/renderer.d.ts +11 -0
  92. package/dist/webgpu/types/2d/sprite-accessor.d.ts +3 -1
  93. package/dist/webgpu/types/3d/hitbox.d.ts +32 -0
  94. package/dist/webgpu/types/3d/lights.d.ts +113 -0
  95. package/dist/webgpu/types/3d/lights.test.d.ts +1 -0
  96. package/dist/webgpu/types/3d/raycast.d.ts +44 -0
  97. package/dist/webgpu/types/3d/renderer.d.ts +50 -1
  98. package/dist/webgpu/types/3d/shader.d.ts +88 -5
  99. package/dist/webgpu/types/core/types.d.ts +55 -0
  100. package/dist/webgpu/types/geometry/geometry-builder.d.ts +1 -4
  101. package/dist/webgpu/types/index.d.ts +1 -0
  102. package/dist/webgpu/types/shaders/utils.d.ts +24 -0
  103. package/package.json +1 -1
  104. package/dist/netcode/types/client/interpolation-buffer.d.ts +0 -37
  105. /package/dist/netcode/types/client/{interpolation-buffer.test.d.ts → strategies/snapshot-interpolation.test.d.ts} +0 -0
@@ -34,7 +34,8 @@ __export(src_exports, {
34
34
  encodeDelta: () => encodeDelta,
35
35
  makeFieldsAccessor: () => makeFieldsAccessor,
36
36
  makeMarkDirty: () => makeMarkDirty,
37
- networked: () => networked
37
+ networked: () => networked,
38
+ rateIncludes: () => rateIncludes
38
39
  });
39
40
  module.exports = __toCommonJS(src_exports);
40
41
 
@@ -108,7 +109,7 @@ var import_protocol3 = require("murow/protocol");
108
109
 
109
110
  // src/codec/delta-codec.ts
110
111
  var HEADER_BYTES = 4 + 4 + 2 + 2;
111
- function encodeDelta(world, tick, entities, components, numMaskWords, despawned = [], clientAckTick = 0) {
112
+ function encodeDelta(world, tick, entities, components, numMaskWords, despawned = [], clientAckTick = 0, includeComponent = () => true) {
112
113
  const perEntityMasks = new Array(entities.length);
113
114
  const perEntityBitmaskBytes = numMaskWords * 4;
114
115
  let bodyBytes = 0;
@@ -120,6 +121,8 @@ function encodeDelta(world, tick, entities, components, numMaskWords, despawned
120
121
  const c = components[ci];
121
122
  if (!world.has(eid, c))
122
123
  continue;
124
+ if (!includeComponent(eid, c))
125
+ continue;
123
126
  const wordIndex = ci >>> 5;
124
127
  const bitIndex = ci & 31;
125
128
  mask[wordIndex] |= 1 << bitIndex;
@@ -271,6 +274,18 @@ function decodeDelta(world, buf, components, numMaskWords, ensureEntity, shouldA
271
274
  };
272
275
  }
273
276
 
277
+ // src/components/sync-spec.ts
278
+ function networked(spec) {
279
+ return spec;
280
+ }
281
+ function rateIncludes(rate, dirty, tick) {
282
+ if (rate === "every-tick")
283
+ return true;
284
+ if (rate === "on-change")
285
+ return dirty;
286
+ return dirty || rate.every > 0 && tick % rate.every === 0;
287
+ }
288
+
274
289
  // src/ctx.ts
275
290
  function makeFieldsAccessor(world, entity) {
276
291
  return function fields(component) {
@@ -501,6 +516,7 @@ var GameServer = class extends Network {
501
516
  }
502
517
  if (current.length === 0 && despawned.length === 0 && !peer.needsBaseline)
503
518
  continue;
519
+ const include = peer.needsBaseline ? void 0 : (eid, c) => rateIncludes(c.__sync.rate, this.world.isDirty(eid, c), this.tickCounter);
504
520
  const buf = encodeDelta(
505
521
  this.world,
506
522
  this.tickCounter,
@@ -508,7 +524,8 @@ var GameServer = class extends Network {
508
524
  this.syncedComponents,
509
525
  this.syncedNumMaskWords,
510
526
  despawned,
511
- peer.lastAckedClientTick
527
+ peer.lastAckedClientTick,
528
+ include
512
529
  );
513
530
  const framed = new Uint8Array(buf.length + 1);
514
531
  framed[0] = MSG_SNAPSHOT;
@@ -850,67 +867,67 @@ var LagCompensation = class {
850
867
  // src/client/game-client.ts
851
868
  var import_binary_codec2 = require("murow/core/binary-codec");
852
869
  var import_simple_rng2 = require("murow/core/simple-rng");
870
+ var import_prediction = require("murow/core/prediction");
853
871
  var import_protocol4 = require("murow/protocol");
854
872
 
855
- // src/client/interpolation-buffer.ts
873
+ // src/client/strategies/snapshot-interpolation.ts
856
874
  var import_lerp = require("murow/core/lerp");
875
+ var import_timeline = require("murow/core/timeline");
876
+ var import_clock = require("murow/core/clock");
857
877
  function modeFor(c) {
858
878
  const sync = c.__sync;
859
879
  return sync?.interp ?? "lerp";
860
880
  }
861
- var InterpolationBuffer = class {
862
- constructor(serverToLocal, capacity, delayMs, staleWindowMs) {
863
- this.buffer = [];
864
- this.renderTick = -Infinity;
865
- this.latestReceivedAt = -Infinity;
866
- this.smoothedTickRateMs = 0;
881
+ var DEFAULT_MAX_DESYNC = 500;
882
+ var DEFAULT_MAX_BRIDGE_GAP = 250;
883
+ var SnapshotInterpolation = class {
884
+ constructor(serverToLocal, capacity, delayMs, staleWindowMs, maxDesyncMs = DEFAULT_MAX_DESYNC, nominalTickMs = 0, maxBridgeGapMs = DEFAULT_MAX_BRIDGE_GAP) {
885
+ this.clock = new import_clock.SlewClock();
867
886
  this.serverToLocal = serverToLocal;
868
- this.capacity = capacity;
887
+ this.timeline = new import_timeline.Timeline(capacity, staleWindowMs);
869
888
  this.delay = delayMs;
870
- this.staleWindow = staleWindowMs;
889
+ this.maxDesync = maxDesyncMs;
890
+ this.nominalTickMs = nominalTickMs;
891
+ this.smoothedTickRateMs = nominalTickMs;
892
+ this.maxBridgeGap = maxBridgeGapMs;
893
+ }
894
+ get staleWindow() {
895
+ return this.timeline.staleWindow;
871
896
  }
872
897
  setDelay(delayMs) {
873
898
  this.delay = delayMs;
874
899
  }
875
900
  setStaleWindow(staleWindowMs) {
876
- this.staleWindow = staleWindowMs;
901
+ this.timeline.setStaleWindow(staleWindowMs);
902
+ }
903
+ setMaxDesync(maxDesyncMs) {
904
+ this.maxDesync = maxDesyncMs;
905
+ }
906
+ setMaxBridgeGap(maxBridgeGapMs) {
907
+ this.maxBridgeGap = maxBridgeGapMs;
877
908
  }
878
909
  record(snapshot) {
879
- const gap = snapshot.receivedAt - this.latestReceivedAt;
880
- if (this.buffer.length > 0 && gap > this.staleWindow) {
881
- this.buffer.length = 0;
882
- this.renderTick = -Infinity;
883
- this.latestReceivedAt = -Infinity;
884
- this.smoothedTickRateMs = 0;
885
- }
886
- if (snapshot.receivedAt > this.latestReceivedAt) {
887
- this.latestReceivedAt = snapshot.receivedAt;
910
+ const reset = this.timeline.record(snapshot.serverTick, snapshot.receivedAt, snapshot);
911
+ if (reset) {
912
+ this.smoothedTickRateMs = this.nominalTickMs;
913
+ this.clock.reset();
888
914
  }
889
- let insertAt = this.buffer.length;
890
- while (insertAt > 0 && this.buffer[insertAt - 1].serverTick >= snapshot.serverTick) {
891
- if (this.buffer[insertAt - 1].serverTick === snapshot.serverTick)
892
- return;
893
- insertAt--;
894
- }
895
- this.buffer.splice(insertAt, 0, snapshot);
896
- while (this.buffer.length > this.capacity)
897
- this.buffer.shift();
898
915
  }
899
916
  clear() {
900
- this.buffer.length = 0;
901
- this.renderTick = -Infinity;
902
- this.latestReceivedAt = -Infinity;
903
- this.smoothedTickRateMs = 0;
917
+ this.timeline.clear();
918
+ this.clock.reset();
919
+ this.smoothedTickRateMs = this.nominalTickMs;
904
920
  }
905
921
  apply(world, now, components, shouldSkip) {
906
- if (this.buffer.length === 0)
922
+ const tl = this.timeline;
923
+ if (tl.length === 0)
907
924
  return;
908
- const newest = this.buffer[this.buffer.length - 1];
909
- const oldest = this.buffer[0];
925
+ const newest = tl.newest().sample;
926
+ const oldest = tl.oldest().sample;
910
927
  let tickRateMs = 0;
911
- if (this.buffer.length >= 2) {
928
+ if (tl.length >= 2) {
912
929
  const tickSpan = newest.serverTick - oldest.serverTick;
913
- const wallSpan = this.latestReceivedAt - oldest.receivedAt;
930
+ const wallSpan = tl.latestReceivedAt - oldest.receivedAt;
914
931
  const rawTickRateMs = tickSpan > 0 && wallSpan > 0 ? wallSpan / tickSpan : 0;
915
932
  if (rawTickRateMs > 0) {
916
933
  if (this.smoothedTickRateMs === 0)
@@ -928,42 +945,23 @@ var InterpolationBuffer = class {
928
945
  }
929
946
  const ageBeyondDelay = now - newest.receivedAt - this.delay;
930
947
  const targetTick = newest.serverTick + ageBeyondDelay / tickRateMs;
931
- if (this.renderTick === -Infinity) {
932
- this.renderTick = targetTick;
933
- } else {
934
- const drift = targetTick - this.renderTick;
935
- if (drift > 2) {
936
- this.renderTick = targetTick;
937
- } else {
938
- const warp = Math.max(0.9, Math.min(1.1, 1 + drift * 0.05));
939
- this.renderTick += warp;
940
- }
941
- }
942
- const renderTick = this.renderTick;
943
- let a = null;
944
- let b = null;
945
- for (let i = 0; i < this.buffer.length - 1; i++) {
946
- const s0 = this.buffer[i];
947
- const s1 = this.buffer[i + 1];
948
- if (s0.serverTick <= renderTick && renderTick <= s1.serverTick) {
949
- a = s0;
950
- b = s1;
951
- break;
952
- }
953
- }
954
- if (a === null || b === null) {
948
+ const renderTick = this.clock.advance(targetTick, this.maxDesync / tickRateMs);
949
+ const straddle = tl.straddle(renderTick);
950
+ if (straddle === null) {
955
951
  if (renderTick < oldest.serverTick)
956
952
  return;
957
953
  this.writeSnapshot(world, newest, components, shouldSkip);
958
954
  return;
959
955
  }
956
+ const [aIndex, bIndex] = straddle;
957
+ const a = tl.at(aIndex).sample;
958
+ const b = tl.at(bIndex).sample;
960
959
  const seen = /* @__PURE__ */ new Set();
961
960
  for (const eid of a.entityIds)
962
961
  seen.add(eid);
963
962
  for (const eid of b.entityIds)
964
963
  seen.add(eid);
965
- const aIndex = this.buffer.indexOf(a);
966
- const bIndex = this.buffer.indexOf(b);
964
+ const maxBridgeTicks = this.maxBridgeGap / tickRateMs;
967
965
  for (const serverEid of seen) {
968
966
  const localEid = this.serverToLocal.get(serverEid);
969
967
  if (localEid === void 0)
@@ -975,13 +973,13 @@ var InterpolationBuffer = class {
975
973
  let va = a.componentValuesByEntity.get(serverEid)?.get(c);
976
974
  while (va === void 0 && aIdx > 0) {
977
975
  aIdx--;
978
- va = this.buffer[aIdx].componentValuesByEntity.get(serverEid)?.get(c);
976
+ va = tl.at(aIdx).sample.componentValuesByEntity.get(serverEid)?.get(c);
979
977
  }
980
978
  let bIdx = bIndex;
981
979
  let vb = b.componentValuesByEntity.get(serverEid)?.get(c);
982
- while (vb === void 0 && bIdx < this.buffer.length - 1) {
980
+ while (vb === void 0 && bIdx < tl.length - 1) {
983
981
  bIdx++;
984
- vb = this.buffer[bIdx].componentValuesByEntity.get(serverEid)?.get(c);
982
+ vb = tl.at(bIdx).sample.componentValuesByEntity.get(serverEid)?.get(c);
985
983
  }
986
984
  if (va === void 0 && vb === void 0)
987
985
  continue;
@@ -995,8 +993,10 @@ var InterpolationBuffer = class {
995
993
  if (mode === "none") {
996
994
  toWrite = vb;
997
995
  } else {
998
- const aTick = this.buffer[aIdx].serverTick;
999
- const bTick = this.buffer[bIdx].serverTick;
996
+ const bTick = tl.at(bIdx).sample.serverTick;
997
+ let aTick = tl.at(aIdx).sample.serverTick;
998
+ if (bTick - aTick > maxBridgeTicks)
999
+ aTick = bTick - 1;
1000
1000
  const wideSpan = bTick - aTick;
1001
1001
  const wideT = wideSpan > 0 ? Math.min(1, Math.max(0, (renderTick - aTick) / wideSpan)) : 0;
1002
1002
  if (mode === "step") {
@@ -1073,7 +1073,6 @@ var GameClient = class extends Network {
1073
1073
  "error"
1074
1074
  ]);
1075
1075
  this.predictionMap = null;
1076
- this.predictionHistory = [];
1077
1076
  this.localTick = 0;
1078
1077
  this.intentSequence = 0;
1079
1078
  this.lastServerTick = 0;
@@ -1084,6 +1083,8 @@ var GameClient = class extends Network {
1084
1083
  this.predictedEntities = /* @__PURE__ */ new Set();
1085
1084
  /** Set when MSG_ASSIGN_ENTITY lands before the matching spawn. */
1086
1085
  this.pendingAssignedServerEid = null;
1086
+ /** Spawns discovered during a decode, emitted once values are written. */
1087
+ this.deferredSpawns = [];
1087
1088
  /** Resolved local entity for the server's assignment. Default for sendIntent. */
1088
1089
  this._assignedEntity = null;
1089
1090
  this._rttMs = null;
@@ -1104,12 +1105,60 @@ var GameClient = class extends Network {
1104
1105
  this.rng = new import_simple_rng2.SimpleRNG(1);
1105
1106
  this.lastDt = opts.loop.ticker.intervalMs / 1e3;
1106
1107
  this.now = opts.now ?? (() => performance.now());
1107
- this.interpBuffer = new InterpolationBuffer(
1108
+ this.interpBuffer = new SnapshotInterpolation(
1108
1109
  this.serverToLocal,
1109
1110
  16,
1110
1111
  interpolationDelay,
1111
- staleWindowMs
1112
+ staleWindowMs,
1113
+ strategy.maxDesync,
1114
+ opts.loop.ticker.intervalMs,
1115
+ strategy.maxBridgeGap
1112
1116
  );
1117
+ this.reconciler = new import_prediction.Reconciler({
1118
+ bufferSize: this.predictionBufferSize,
1119
+ restore: ({ decoded, resetEntities }) => {
1120
+ for (const serverEid of decoded.serverEntityIds) {
1121
+ const localEid = this.serverToLocal.get(serverEid);
1122
+ if (localEid === void 0)
1123
+ continue;
1124
+ if (!this.predictedEntities.has(localEid))
1125
+ continue;
1126
+ const comps = decoded.valuesByServerEntity.get(serverEid);
1127
+ if (comps === void 0)
1128
+ continue;
1129
+ for (const [c, value] of comps) {
1130
+ if (this.world.has(localEid, c)) {
1131
+ this.world.update(localEid, c, value);
1132
+ } else {
1133
+ this.world.add(localEid, c, value);
1134
+ }
1135
+ }
1136
+ resetEntities.add(localEid);
1137
+ }
1138
+ },
1139
+ replay: (preds, { decoded, resetEntities }) => {
1140
+ let replayedCount = 0;
1141
+ for (const pred of preds) {
1142
+ if (!resetEntities.has(pred.entity))
1143
+ continue;
1144
+ const predFn = this.predictionMap?.[pred.name];
1145
+ if (predFn === void 0)
1146
+ continue;
1147
+ const ctx = {
1148
+ world: this.world,
1149
+ entity: pred.entity,
1150
+ tick: pred.tick,
1151
+ deltaTime: pred.deltaTime,
1152
+ rng: this.rng,
1153
+ fields: makeFieldsAccessor(this.world, pred.entity),
1154
+ markDirty: makeMarkDirty(this.world, pred.entity)
1155
+ };
1156
+ predFn(pred.payload, ctx);
1157
+ replayedCount++;
1158
+ }
1159
+ this.emit("reconciled", { rewindTick: decoded.clientAckTick, replayed: replayedCount });
1160
+ }
1161
+ });
1113
1162
  this.discoverSyncedComponents();
1114
1163
  this.wireTransport();
1115
1164
  this.wireLoop();
@@ -1196,6 +1245,7 @@ var GameClient = class extends Network {
1196
1245
  }
1197
1246
  handleSnapshot(payload) {
1198
1247
  try {
1248
+ this.deferredSpawns.length = 0;
1199
1249
  const decoded = decodeDelta(
1200
1250
  this.world,
1201
1251
  payload,
@@ -1209,6 +1259,12 @@ var GameClient = class extends Network {
1209
1259
  () => false
1210
1260
  );
1211
1261
  this.lastServerTick = decoded.tick;
1262
+ for (const spawn of this.deferredSpawns) {
1263
+ this.emit("spawn", { entity: spawn.entity, components: spawn.components });
1264
+ if (spawn.assigned)
1265
+ this.emit("assigned", { entity: spawn.entity });
1266
+ }
1267
+ this.deferredSpawns.length = 0;
1212
1268
  this.emit("snapshot", { tick: decoded.tick, byteSize: payload.length + 1 });
1213
1269
  this.interpBuffer.record({
1214
1270
  receivedAt: this.now(),
@@ -1241,13 +1297,14 @@ var GameClient = class extends Network {
1241
1297
  const components = {};
1242
1298
  for (const c of present)
1243
1299
  components[c.name] = true;
1244
- this.emit("spawn", { entity: localEid, components });
1300
+ let assigned = false;
1245
1301
  if (this.pendingAssignedServerEid === serverEid) {
1246
1302
  this.pendingAssignedServerEid = null;
1247
1303
  this.predictedEntities.add(localEid);
1248
1304
  this._assignedEntity = localEid;
1249
- this.emit("assigned", { entity: localEid });
1305
+ assigned = true;
1250
1306
  }
1307
+ this.deferredSpawns.push({ entity: localEid, components, assigned });
1251
1308
  }
1252
1309
  return localEid;
1253
1310
  }
@@ -1266,60 +1323,9 @@ var GameClient = class extends Network {
1266
1323
  * the server has acked, replay the rest on top.
1267
1324
  */
1268
1325
  reconcile(decoded) {
1269
- const resetEntities = /* @__PURE__ */ new Set();
1270
- for (const serverEid of decoded.serverEntityIds) {
1271
- const localEid = this.serverToLocal.get(serverEid);
1272
- if (localEid === void 0)
1273
- continue;
1274
- if (!this.predictedEntities.has(localEid))
1275
- continue;
1276
- const comps = decoded.valuesByServerEntity.get(serverEid);
1277
- if (comps === void 0)
1278
- continue;
1279
- for (const [c, value] of comps) {
1280
- if (this.world.has(localEid, c)) {
1281
- this.world.update(localEid, c, value);
1282
- } else {
1283
- this.world.add(localEid, c, value);
1284
- }
1285
- }
1286
- resetEntities.add(localEid);
1287
- }
1288
- const ackSequence = decoded.clientAckTick;
1289
- let cut = 0;
1290
- while (cut < this.predictionHistory.length && this.predictionHistory[cut].sequence <= ackSequence) {
1291
- cut++;
1292
- }
1293
- if (cut > 0)
1294
- this.predictionHistory.splice(0, cut);
1295
- const remaining = this.predictionHistory.length;
1296
- if (remaining === 0) {
1297
- this.emit("reconciled", { rewindTick: ackSequence, replayed: 0 });
1298
- return;
1299
- }
1300
- let replayedCount = 0;
1301
- for (let i = 0; i < remaining; i++) {
1302
- const pred = this.predictionHistory[i];
1303
- if (!resetEntities.has(pred.entity))
1304
- continue;
1305
- const predFn = this.predictionMap?.[pred.name];
1306
- if (predFn === void 0)
1307
- continue;
1308
- const ctx = {
1309
- world: this.world,
1310
- entity: pred.entity,
1311
- tick: pred.tick,
1312
- deltaTime: pred.deltaTime,
1313
- rng: this.rng,
1314
- fields: makeFieldsAccessor(this.world, pred.entity),
1315
- markDirty: makeMarkDirty(this.world, pred.entity)
1316
- };
1317
- predFn(pred.payload, ctx);
1318
- replayedCount++;
1319
- }
1320
- this.emit("reconciled", {
1321
- rewindTick: ackSequence,
1322
- replayed: replayedCount
1326
+ this.reconciler.reconcile(decoded.clientAckTick, {
1327
+ decoded,
1328
+ resetEntities: /* @__PURE__ */ new Set()
1323
1329
  });
1324
1330
  }
1325
1331
  handleRpc(payload) {
@@ -1393,7 +1399,7 @@ var GameClient = class extends Network {
1393
1399
  markDirty: makeMarkDirty(this.world, entity)
1394
1400
  };
1395
1401
  predFn(payload, ctx);
1396
- this.predictionHistory.push({
1402
+ this.reconciler.record(sequence, {
1397
1403
  tick: this.localTick,
1398
1404
  sequence,
1399
1405
  name,
@@ -1402,9 +1408,6 @@ var GameClient = class extends Network {
1402
1408
  deltaTime
1403
1409
  });
1404
1410
  this.predictedEntities.add(entity);
1405
- while (this.predictionHistory.length > this.predictionBufferSize) {
1406
- this.predictionHistory.shift();
1407
- }
1408
1411
  }
1409
1412
  return true;
1410
1413
  }
@@ -1435,7 +1438,7 @@ var GameClient = class extends Network {
1435
1438
  return this.localTick;
1436
1439
  }
1437
1440
  getPredictionDepth() {
1438
- return this.predictionHistory.length;
1441
+ return this.reconciler.pending;
1439
1442
  }
1440
1443
  get interpolationDelay() {
1441
1444
  return this.interpBuffer.delay;
@@ -1443,6 +1446,12 @@ var GameClient = class extends Network {
1443
1446
  setInterpolationDelay(ms) {
1444
1447
  this.interpBuffer.setDelay(ms);
1445
1448
  }
1449
+ setMaxDesync(ms) {
1450
+ this.interpBuffer.setMaxDesync(ms);
1451
+ }
1452
+ setMaxBridgeGap(ms) {
1453
+ this.interpBuffer.setMaxBridgeGap(ms);
1454
+ }
1446
1455
  };
1447
1456
 
1448
1457
  // src/transports/memory-transport.ts
@@ -1545,8 +1554,3 @@ var MemoryPeerTransport = class {
1545
1554
  queueMicrotask(() => this.clientOpenHandler?.());
1546
1555
  }
1547
1556
  };
1548
-
1549
- // src/components/sync-spec.ts
1550
- function networked(spec) {
1551
- return spec;
1552
- }