@carverjs/multiplayer 0.0.1 → 0.0.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 (43) hide show
  1. package/README.md +154 -0
  2. package/dist/InputBuffer-J6XT_Tt0.d.mts +61 -0
  3. package/dist/InputBuffer-V7XfHbc6.d.ts +61 -0
  4. package/dist/{NetworkManager-nvVAOr1O.d.ts → NetworkManager-D-DxFgdM.d.mts} +66 -14
  5. package/dist/{NetworkManager-DrKM2tEx.d.mts → NetworkManager-DH9uGVMg.d.ts} +66 -14
  6. package/dist/{chunk-UD6FDZMX.mjs → chunk-GOTAQDBJ.mjs} +47 -4
  7. package/dist/chunk-GOTAQDBJ.mjs.map +1 -0
  8. package/dist/{chunk-3KT73N2S.mjs → chunk-LPNEP2VH.mjs} +0 -0
  9. package/dist/chunk-LPNEP2VH.mjs.map +1 -0
  10. package/dist/{chunk-EO3YNPRQ.mjs → chunk-Q25TJEY4.mjs} +494 -204
  11. package/dist/chunk-Q25TJEY4.mjs.map +1 -0
  12. package/dist/{firebase-CPu87KA0.d.ts → firebase-B5MgLlHk.d.ts} +6 -1
  13. package/dist/{firebase-PE6MxGdJ.d.mts → firebase-GrbVrNgs.d.mts} +6 -1
  14. package/dist/index.d.mts +27 -6
  15. package/dist/index.d.ts +27 -6
  16. package/dist/index.js +821 -258
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.mjs +172 -37
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/strategy.d.mts +2 -2
  21. package/dist/strategy.d.ts +2 -2
  22. package/dist/strategy.js +46 -3
  23. package/dist/strategy.js.map +1 -1
  24. package/dist/strategy.mjs +1 -1
  25. package/dist/sync.d.mts +134 -50
  26. package/dist/sync.d.ts +134 -50
  27. package/dist/sync.js +499 -205
  28. package/dist/sync.js.map +1 -1
  29. package/dist/sync.mjs +15 -3
  30. package/dist/transport.d.mts +0 -0
  31. package/dist/transport.d.ts +0 -0
  32. package/dist/transport.js +0 -0
  33. package/dist/transport.js.map +1 -1
  34. package/dist/transport.mjs +2 -2
  35. package/dist/{types-5LHBOW08.d.mts → types-hNfCIBzj.d.mts} +7 -0
  36. package/dist/{types-5LHBOW08.d.ts → types-hNfCIBzj.d.ts} +7 -0
  37. package/dist/types.d.mts +2 -2
  38. package/dist/types.d.ts +2 -2
  39. package/dist/types.js.map +1 -1
  40. package/package.json +26 -5
  41. package/dist/chunk-3KT73N2S.mjs.map +0 -1
  42. package/dist/chunk-EO3YNPRQ.mjs.map +0 -1
  43. package/dist/chunk-UD6FDZMX.mjs.map +0 -1
package/dist/sync.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { C as CarverTransport, d as Codec, S as SnapshotBuffer, E as EntityState, N as NetworkQuality, e as TickKeeper } from './NetworkManager-nvVAOr1O.js';
2
- export { f as EntityState2D, g as EntityState3D, h as EventPacket, I as InputPacket, i as SnapshotPacket, j as SyncMode } from './NetworkManager-nvVAOr1O.js';
3
- import './types-5LHBOW08.js';
1
+ import { C as CarverTransport, e as Codec, S as SnapshotBuffer, E as EntityState, P as PlayerInput, N as NetworkQuality, f as SnapshotListener, g as SnapshotSource, h as TickKeeper, i as PredictionSyncOptions, j as PhysicsStepCallback, k as PredictionWorldDriver, l as ErrorOffset } from './NetworkManager-DH9uGVMg.js';
2
+ export { m as EntityState2D, n as EntityState3D, o as EventPacket, I as InputPacket, p as SnapshotPacket, q as SyncMode } from './NetworkManager-DH9uGVMg.js';
3
+ import { I as InputBuffer } from './InputBuffer-V7XfHbc6.js';
4
+ import './types-hNfCIBzj.js';
4
5
 
5
6
  /**
6
7
  * Layer 1: Event-based messaging over a reliable+ordered channel.
@@ -59,10 +60,11 @@ declare class HostAuthority {
59
60
  /**
60
61
  * Called every fixed tick by the sync engine.
61
62
  * Collects entity states and decides whether to broadcast.
63
+ * `hostInput` (prediction mode) is embedded in the snapshot packet as `hi`.
62
64
  */
63
- tick(currentTick: number, entities: Map<string, EntityState>, delta: number): void;
65
+ tick(currentTick: number, entities: Map<string, EntityState>, delta: number, hostInput?: PlayerInput): void;
64
66
  /** Force a keyframe broadcast to all clients (e.g., after host migration) */
65
- forceKeyframe(currentTick: number, entities: Map<string, EntityState>): void;
67
+ forceKeyframe(currentTick: number, entities: Map<string, EntityState>, hostInput?: PlayerInput): void;
66
68
  destroy(): void;
67
69
  private _broadcastToClient;
68
70
  }
@@ -87,6 +89,7 @@ declare class ClientReceiver {
87
89
  private _packetCount;
88
90
  private _is2D;
89
91
  private _fullState;
92
+ private _snapshotListeners;
90
93
  constructor(transport: CarverTransport, codec: Codec, options?: {
91
94
  bufferSize?: number;
92
95
  method?: "hermite" | "linear";
@@ -103,6 +106,8 @@ declare class ClientReceiver {
103
106
  interpolate(renderTime: number): Map<string, EntityState>;
104
107
  /** Request a keyframe from the host */
105
108
  requestKeyframe(): void;
109
+ /** Register a listener fired after each snapshot is merged into the full world state */
110
+ onSnapshot(cb: SnapshotListener): void;
106
111
  destroy(): void;
107
112
  private _handleSnapshot;
108
113
  private _interpolateEntity;
@@ -123,18 +128,21 @@ interface SnapshotSyncOptions {
123
128
  * Layer 2: Snapshot interpolation sync engine.
124
129
  * Host broadcasts state at fixed intervals, clients interpolate.
125
130
  */
126
- declare class SnapshotSync {
131
+ declare class SnapshotSync implements SnapshotSource {
127
132
  private _transport;
128
133
  private _hostAuthority;
129
134
  private _clientReceiver;
130
135
  private _codec;
131
136
  private _snapshotBuffer;
137
+ private _snapshotListeners;
132
138
  constructor(transport: CarverTransport, codec: Codec, snapshotBuffer: SnapshotBuffer, options?: SnapshotSyncOptions);
133
139
  get isHost(): boolean;
134
140
  get hostAuthority(): HostAuthority | null;
135
141
  get clientReceiver(): ClientReceiver | null;
136
142
  /** Host: called every fixed tick to potentially broadcast state */
137
- hostTick(tick: number, entities: Map<string, EntityState>, delta: number): void;
143
+ hostTick(tick: number, entities: Map<string, EntityState>, delta: number, hostInput?: PlayerInput): void;
144
+ /** Register a listener fired after each merged snapshot (client side). */
145
+ onSnapshot(cb: SnapshotListener): void;
138
146
  /** Client: called every render frame to interpolate */
139
147
  clientInterpolate(renderTime: number): Map<string, EntityState>;
140
148
  /** Set interest filter on host authority */
@@ -144,69 +152,145 @@ declare class SnapshotSync {
144
152
  /** Handle host migration: switch from host to client mode */
145
153
  demoteToClient(options?: SnapshotSyncOptions): void;
146
154
  destroy(): void;
155
+ /** Forward receiver snapshots to registered listeners (survives host migration). */
156
+ private _attachSnapshotForwarder;
147
157
  }
148
158
 
149
- interface PredictionOptions {
150
- maxRewindTicks: number;
151
- errorSmoothingDecay: number;
152
- maxErrorPerFrame: number;
153
- snapThreshold: number;
154
- lagCompensation: boolean;
155
- }
156
159
  /**
157
- * Layer 3: Client-side prediction with server reconciliation.
158
- * Builds on top of Layer 2 (SnapshotSync).
160
+ * Layer 3: Full-world prediction with full-world rollback.
161
+ * Ported from LumberNet, built on top of Layer 2 (SnapshotSync) as the
162
+ * authoritative state channel.
159
163
  *
160
164
  * Flow:
161
- * Client: input -> apply locally (predict) -> store in buffer -> send to host
162
- * Host: receive input -> apply to simulation -> broadcast state + lastProcessedInputTick
163
- * Client: receive state -> compare with prediction ->
164
- * if mismatch: reset to server state + replay unacked inputs -> visual smoothing
165
+ * Every peer (host included): broadcast tick-stamped input to ALL peers each
166
+ * fixed tick on carver:inputs, simulate EVERY networked entity forward with
167
+ * last-known remote inputs (hold-last-input extrapolation).
168
+ *
169
+ * Host: stays authoritative; SnapshotSync broadcasts delta-compressed,
170
+ * ACK-driven snapshots with the host's own input embedded (`hi`).
171
+ *
172
+ * Client: on each accepted snapshot, reset ALL networked entities to server
173
+ * state, resimulate from serverTick + 1 to localTick replaying per-tick
174
+ * inputs for every peer, and convert the visual discontinuity into
175
+ * per-entity error offsets decayed per render frame.
176
+ *
177
+ * Role is checked dynamically via transport.isHost at every use site (host
178
+ * migration is best-effort).
165
179
  */
180
+
166
181
  declare class PredictionSync {
167
182
  private _transport;
168
- private _codec;
169
183
  private _tickKeeper;
170
184
  private _options;
171
185
  private _inputChannel;
172
- private _stateChannel;
173
- private _ackChannel;
174
- private _inputBuffer;
175
- private _clientLastProcessedTick;
176
- private _predictedState;
177
- private _errorCorrections;
178
- private _serverState;
186
+ private _inputs;
187
+ private _currentInput;
188
+ private _pending;
189
+ private _errors;
179
190
  private _serverTick;
191
+ private _lastAppliedServerTick;
192
+ private _worldDriver;
180
193
  private _onPhysicsStep;
181
- private _currentInput;
182
- private _isHost;
183
- constructor(transport: CarverTransport, codec: Codec, tickKeeper: TickKeeper, options?: Partial<PredictionOptions>);
184
- /** Set the physics step callback (required for rollback re-simulation) */
185
- setPhysicsStep(cb: (inputs: Map<string, unknown>, tick: number, isRollback: boolean) => void): void;
186
- /** Set the current input for this tick (client-side) */
187
- setInput(input: unknown): void;
194
+ constructor(transport: CarverTransport, tickKeeper: TickKeeper, snapshots: SnapshotSource, options?: PredictionSyncOptions);
195
+ /** Set the game simulation callback (forward sim + rollback resim). */
196
+ setPhysicsStep(cb: PhysicsStepCallback): void;
197
+ /** Set the world driver used for forward stepping and rollback. */
198
+ setWorldDriver(driver: PredictionWorldDriver): void;
199
+ /** Set the local player's input. PERSISTS across ticks until replaced. */
200
+ setInput(input: PlayerInput): void;
201
+ /** Local input stored at the given tick (neutral fallback). Used by the host to embed `hi`. */
202
+ getLocalInput(tick: number): PlayerInput;
188
203
  /**
189
- * Called every fixed tick on the client.
190
- * Applies input locally (prediction), buffers it, and sends to host.
204
+ * Apply the newest pending server snapshot (full-world rollback).
205
+ * Call once per render frame BEFORE tickKeeper.update().
206
+ * No-op on host or when nothing is pending.
191
207
  */
192
- clientTick(tick: number): void;
208
+ beginFrame(): void;
193
209
  /**
194
- * Called every fixed tick on the host.
195
- * Processes received inputs and broadcasts authoritative state.
210
+ * Run one forward fixed tick (host AND client): store + broadcast input,
211
+ * build per-tick input maps, invoke the callback, then step the world.
196
212
  */
197
- hostTick(tick: number, entities: Map<string, EntityState>, _delta: number): void;
213
+ tick(tick: number): void;
198
214
  /**
199
- * Called every render frame on the client to apply visual error smoothing.
200
- * Returns the corrected entity states.
215
+ * Decay stored error offsets and return the portion to ADD to rendered
216
+ * transforms this frame. Call exactly once per render frame.
201
217
  */
202
- applyErrorSmoothing(entities: Map<string, EntityState>): Map<string, EntityState>;
203
- get predictedState(): Map<string, EntityState>;
218
+ getRenderErrorOffsets(): Map<string, ErrorOffset>;
219
+ /** Tick of the newest RECEIVED snapshot. */
204
220
  get serverTick(): number;
221
+ /** Tick of the newest APPLIED (rolled-back) snapshot, 0 initially. */
222
+ get lastAppliedServerTick(): number;
205
223
  destroy(): void;
206
- private _setupHostListeners;
207
- private _setupClientListeners;
208
- private _reconcile;
209
- private _computeError;
210
224
  }
211
225
 
212
- export { EntityState, EventSync, PredictionSync, SnapshotSync, type SnapshotSyncOptions };
226
+ /**
227
+ * Rollback — full-world rollback for prediction mode.
228
+ *
229
+ * Ported from LumberNet's LumberRollback, adapted to CarverJS entity state
230
+ * (2D and 3D). On each accepted server snapshot the client:
231
+ * 1. captures the pre-rollback visual pose (raw physics + accumulated error),
232
+ * 2. hard-applies server state to EVERY networked entity,
233
+ * 3. resimulates from serverTick + 1 to localTick replaying per-tick inputs
234
+ * (or hard-snaps the tick when drift exceeds maxRewindTicks),
235
+ * 4. converts the visual discontinuity into per-entity error offsets.
236
+ *
237
+ * Pure module: no transport, no React, no three.js. Quaternion math is
238
+ * hand-rolled below.
239
+ */
240
+
241
+ interface Quat {
242
+ x: number;
243
+ y: number;
244
+ z: number;
245
+ w: number;
246
+ }
247
+ /** Hamilton product (a ⊗ b). */
248
+ declare function quatMultiply(a: Quat, b: Quat): Quat;
249
+ /** Conjugate (inputs assumed normalized). */
250
+ declare function quatInvert(q: Quat): Quat;
251
+ /** Normalize; degenerate (near-zero) quaternions return identity. */
252
+ declare function quatNormalize(q: Quat): Quat;
253
+ /** Rotation angle in radians: 2*acos(clamp(|w|, 0, 1)). */
254
+ declare function quatAngle(q: Quat): number;
255
+ /** Scale the rotation angle toward identity by `factor`, preserving the axis. */
256
+ declare function quatScaleAngle(q: Quat, factor: number): Quat;
257
+ interface RollbackParams {
258
+ /** Tick embedded in the accepted server snapshot. */
259
+ serverTick: number;
260
+ /** Authoritative full-world state from the snapshot. */
261
+ serverState: ReadonlyMap<string, EntityState>;
262
+ /** Client's current simulation tick. */
263
+ localTick: number;
264
+ /** This peer's id (local inputs are replayed from the local ring buffer). */
265
+ localPeerId: string;
266
+ /** Input buffer holding local and per-peer tick history. */
267
+ inputs: InputBuffer;
268
+ /** Currently accumulated visual error offsets (kept continuous across rollbacks). */
269
+ currentErrors: ReadonlyMap<string, ErrorOffset>;
270
+ /** World access for capture/apply/step. */
271
+ driver: PredictionWorldDriver;
272
+ /** Game physics-step callback, re-invoked during resimulation. */
273
+ callback: PhysicsStepCallback | null;
274
+ /** Fixed timestep in seconds. */
275
+ dt: number;
276
+ /** Snap target offset: snap target = serverTick + driftTargetTicks. */
277
+ driftTargetTicks: number;
278
+ /** Max |localTick - (serverTick + driftTargetTicks)| before hard tick snap. */
279
+ maxRewindTicks: number;
280
+ /** Per-axis positional jump above which correction is suppressed (teleport). */
281
+ snapThreshold: number;
282
+ }
283
+ interface RollbackResult {
284
+ /** New local tick (differs from localTick only when a hard snap occurred). */
285
+ newLocalTick: number;
286
+ snapped: boolean;
287
+ /** Per-entity visual error offsets (pre-visual minus post-resim). */
288
+ errors: Map<string, ErrorOffset>;
289
+ }
290
+ /**
291
+ * Apply a server snapshot to the whole world and resimulate forward.
292
+ * Returns the new local tick and per-entity visual error offsets.
293
+ */
294
+ declare function applyRollback(params: RollbackParams): RollbackResult;
295
+
296
+ export { EntityState, ErrorOffset, EventSync, PhysicsStepCallback, PlayerInput, PredictionSync, PredictionSyncOptions, PredictionWorldDriver, type Quat, type RollbackParams, type RollbackResult, SnapshotListener, SnapshotSource, SnapshotSync, type SnapshotSyncOptions, applyRollback, quatAngle, quatInvert, quatMultiply, quatNormalize, quatScaleAngle };