@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sync/EventSync.ts","../src/core/HostAuthority.ts","../src/core/ClientReceiver.ts","../src/sync/SnapshotSync.ts","../src/core/InputBuffer.ts","../src/core/InputUtils.ts","../src/sync/Rollback.ts","../src/sync/PredictionSync.ts"],"sourcesContent":["import type { CarverTransport, CarverChannel, EventPacket } from \"../types\";\n\n/**\n * Layer 1: Event-based messaging over a reliable+ordered channel.\n * Used for turn-based games, chat, and infrequent state changes.\n */\nexport class EventSync {\n private _transport: CarverTransport;\n private _channel: CarverChannel<string>;\n private _handlers = new Map<string, ((payload: unknown, peerId: string) => void)[]>();\n private _hostValidation: boolean;\n\n constructor(transport: CarverTransport, options?: { hostValidation?: boolean }) {\n this._transport = transport;\n this._hostValidation = options?.hostValidation ?? false;\n\n // Create a single reliable+ordered channel for events\n this._channel = transport.createChannel<string>('carver:events', {\n reliable: true,\n ordered: true,\n });\n\n // Listen for incoming events\n this._channel.onReceive((rawData: string, peerId: string) => {\n try {\n const packet: EventPacket = typeof rawData === 'string' ? JSON.parse(rawData) : rawData as unknown as EventPacket;\n\n // If host validation is enabled and we're the host, rebroadcast\n if (this._hostValidation && this._transport.isHost && packet.sender !== this._transport.peerId) {\n // Rebroadcast to all peers (except the original sender)\n const targets = Array.from(this._transport.peers).filter(p => p !== peerId);\n if (targets.length > 0) {\n this._channel.send(JSON.stringify(packet), targets);\n }\n }\n\n // If host validation is enabled and we're NOT the host,\n // only accept events from host (who rebroadcasts validated events)\n if (this._hostValidation && !this._transport.isHost && peerId !== this._transport.hostId) {\n return;\n }\n\n // Fire handlers for this event type\n const handlers = this._handlers.get(packet.type);\n if (handlers) {\n for (const handler of handlers) {\n handler(packet.payload, packet.sender);\n }\n }\n } catch {\n // Ignore malformed events\n }\n });\n }\n\n /**\n * Send a typed event to a specific peer or all peers.\n */\n sendEvent(type: string, payload: unknown, target?: string): void {\n const packet: EventPacket = {\n type,\n payload,\n sender: this._transport.peerId,\n target,\n };\n const serialized = JSON.stringify(packet);\n\n if (this._hostValidation && !this._transport.isHost) {\n // Route through host for validation\n this._channel.send(serialized, this._transport.hostId);\n } else if (target) {\n this._channel.send(serialized, target);\n } else {\n // Broadcast to all peers\n this._channel.send(serialized);\n }\n }\n\n /**\n * Broadcast a typed event to all connected peers.\n */\n broadcast(type: string, payload: unknown): void {\n this.sendEvent(type, payload);\n }\n\n /**\n * Register a handler for a specific event type.\n * Returns an unsubscribe function.\n */\n onEvent(type: string, callback: (payload: unknown, peerId: string) => void): () => void {\n let handlers = this._handlers.get(type);\n if (!handlers) {\n handlers = [];\n this._handlers.set(type, handlers);\n }\n handlers.push(callback);\n\n return () => {\n const arr = this._handlers.get(type);\n if (arr) {\n const idx = arr.indexOf(callback);\n if (idx >= 0) arr.splice(idx, 1);\n if (arr.length === 0) this._handlers.delete(type);\n }\n };\n }\n\n /**\n * Clean up the event channel.\n */\n destroy(): void {\n this._channel.close();\n this._handlers.clear();\n }\n}\n","import type {\n CarverTransport,\n CarverChannel,\n EntityState,\n PlayerInput,\n} from \"../types\";\nimport { Codec, SnapshotBuffer } from \"./codec\";\n\n/**\n * Host-side authority: reads networked actor states, serializes with delta compression,\n * and broadcasts to all clients at the configured broadcast rate.\n */\nexport class HostAuthority {\n private _transport: CarverTransport;\n private _codec: Codec;\n private _snapshotBuffer: SnapshotBuffer;\n private _snapshotChannel: CarverChannel<Uint8Array>;\n private _ackChannel: CarverChannel<string>;\n private _tick = 0;\n private _broadcastRate: number;\n private _broadcastAccumulator = 0;\n private _keyframeInterval: number;\n\n // Per-client last ACK'd tick for delta compression\n private _clientBaselines = new Map<string, number>();\n // Per-client last keyframe tick for scheduling\n private _clientLastKeyframeTick = new Map<string, number>();\n\n // Interest management callback (optional)\n private _interestFilter:\n | ((entityId: string, peerId: string) => boolean)\n | null = null;\n\n constructor(\n transport: CarverTransport,\n codec: Codec,\n snapshotBuffer: SnapshotBuffer,\n options?: {\n broadcastRate?: number;\n keyframeInterval?: number;\n },\n ) {\n this._transport = transport;\n this._codec = codec;\n this._snapshotBuffer = snapshotBuffer;\n this._broadcastRate = options?.broadcastRate ?? 20;\n this._keyframeInterval = options?.keyframeInterval ?? 300;\n\n // Unreliable channel for snapshots (fast, may drop)\n this._snapshotChannel = transport.createChannel<Uint8Array>(\n \"carver:snapshots\",\n {\n reliable: false,\n ordered: false,\n maxRetransmits: 0,\n },\n );\n\n // Reliable channel for ACKs\n this._ackChannel = transport.createChannel<string>(\"carver:acks\", {\n reliable: true,\n ordered: true,\n });\n\n // Listen for client ACKs\n this._ackChannel.onReceive((data: string, peerId: string) => {\n try {\n const ackTick =\n typeof data === \"string\"\n ? parseInt(data, 10)\n : (data as unknown as number);\n if (ackTick === -1) {\n // Client requesting keyframe\n this._clientBaselines.delete(peerId);\n } else {\n this._clientBaselines.set(peerId, ackTick);\n }\n } catch {\n /* ignore malformed ACKs */\n }\n });\n\n // Handle new peer joins — they need a keyframe\n transport.onPeerJoin((peerId) => {\n this._clientBaselines.delete(peerId); // Will force keyframe\n });\n\n transport.onPeerLeave((peerId) => {\n this._clientBaselines.delete(peerId);\n });\n }\n\n /** Set optional interest management filter */\n setInterestFilter(\n filter: ((entityId: string, peerId: string) => boolean) | null,\n ): void {\n this._interestFilter = filter;\n }\n\n /**\n * Called every fixed tick by the sync engine.\n * Collects entity states and decides whether to broadcast.\n * `hostInput` (prediction mode) is embedded in the snapshot packet as `hi`.\n */\n tick(\n currentTick: number,\n entities: Map<string, EntityState>,\n delta: number,\n hostInput?: PlayerInput,\n ): void {\n this._tick = currentTick;\n\n // Store snapshot in ring buffer\n this._snapshotBuffer.store(currentTick, new Map(entities));\n\n // Check if we should broadcast this tick\n this._broadcastAccumulator += delta;\n const broadcastInterval = 1 / this._broadcastRate;\n if (this._broadcastAccumulator < broadcastInterval) return;\n this._broadcastAccumulator -= broadcastInterval;\n\n // Broadcast to each connected client\n for (const peerId of this._transport.peers) {\n this._broadcastToClient(peerId, currentTick, entities, hostInput);\n }\n }\n\n /** Force a keyframe broadcast to all clients (e.g., after host migration) */\n forceKeyframe(\n currentTick: number,\n entities: Map<string, EntityState>,\n hostInput?: PlayerInput,\n ): void {\n this._clientBaselines.clear();\n this._clientLastKeyframeTick.clear();\n this._snapshotBuffer.store(currentTick, new Map(entities));\n for (const peerId of this._transport.peers) {\n this._broadcastToClient(peerId, currentTick, entities, hostInput);\n }\n }\n\n destroy(): void {\n this._snapshotChannel.close();\n this._ackChannel.close();\n this._clientBaselines.clear();\n this._clientLastKeyframeTick.clear();\n }\n\n private _broadcastToClient(\n peerId: string,\n currentTick: number,\n entities: Map<string, EntityState>,\n hostInput?: PlayerInput,\n ): void {\n // Apply interest management filter\n let clientEntities = entities;\n if (this._interestFilter) {\n clientEntities = new Map<string, EntityState>();\n for (const [id, entity] of entities) {\n if (this._interestFilter(id, peerId)) {\n clientEntities.set(id, entity);\n }\n }\n }\n\n // Determine baseline for delta compression (per-client keyframe scheduling)\n const clientBaseTick = this._clientBaselines.get(peerId);\n const clientLastKeyframe = this._clientLastKeyframeTick.get(peerId) ?? 0;\n const needsKeyframe =\n clientBaseTick === undefined ||\n currentTick - clientLastKeyframe >= this._keyframeInterval;\n\n let baseline: Map<string, EntityState> | undefined;\n if (!needsKeyframe && clientBaseTick !== undefined) {\n baseline = this._snapshotBuffer.get(clientBaseTick);\n }\n\n if (needsKeyframe) {\n this._clientLastKeyframeTick.set(peerId, currentTick);\n }\n\n // Serialize (delta or keyframe)\n const packet = this._codec.serializeDelta(\n currentTick,\n needsKeyframe ? -1 : (clientBaseTick ?? -1),\n clientEntities,\n baseline,\n hostInput,\n );\n\n if (packet) {\n this._snapshotChannel.send(packet, peerId);\n }\n }\n}\n","import type {\n CarverTransport,\n CarverChannel,\n EntityState,\n EntityState2D,\n EntityState3D,\n NetworkQuality,\n SnapshotListener,\n} from \"../types\";\nimport { Codec } from \"./codec\";\n\ninterface BufferedSnapshot {\n tick: number;\n entities: Map<string, EntityState>;\n receivedAt: number;\n}\n\n/**\n * Client-side state receiver: buffers incoming snapshots and interpolates\n * between them for smooth rendering.\n */\nexport class ClientReceiver {\n private _transport: CarverTransport;\n private _codec: Codec;\n private _snapshotChannel: CarverChannel<Uint8Array>;\n private _ackChannel: CarverChannel<string>;\n\n // Snapshot buffer (ring buffer of last N snapshots)\n private _buffer: BufferedSnapshot[] = [];\n private _bufferSize: number;\n\n // Interpolation settings\n private _method: \"hermite\" | \"linear\";\n private _extrapolateMs: number;\n\n // Current interpolated state\n private _interpolatedState = new Map<string, EntityState>();\n\n // Network quality tracking\n private _lastSnapshotTime = 0;\n private _networkQuality: NetworkQuality = \"good\";\n private _packetLossCount = 0;\n private _packetCount = 0;\n\n // Is2D mode\n private _is2D: boolean;\n\n // Entity state: full accumulated state from keyframes + deltas\n private _fullState = new Map<string, EntityState>();\n\n // Listeners fired after each snapshot is merged into the full world state\n private _snapshotListeners: SnapshotListener[] = [];\n\n constructor(\n transport: CarverTransport,\n codec: Codec,\n options?: {\n bufferSize?: number;\n method?: \"hermite\" | \"linear\";\n extrapolateMs?: number;\n is2D?: boolean;\n },\n ) {\n this._transport = transport;\n this._codec = codec;\n this._bufferSize = options?.bufferSize ?? 3;\n this._method = options?.method ?? \"hermite\";\n this._extrapolateMs = options?.extrapolateMs ?? 250;\n this._is2D = options?.is2D ?? false;\n\n // Listen on the unreliable snapshot channel\n this._snapshotChannel = transport.createChannel<Uint8Array>(\n \"carver:snapshots\",\n {\n reliable: false,\n ordered: false,\n maxRetransmits: 0,\n },\n );\n\n this._ackChannel = transport.createChannel<string>(\"carver:acks\", {\n reliable: true,\n ordered: true,\n });\n\n this._snapshotChannel.onReceive((data: Uint8Array) => {\n this._handleSnapshot(data);\n });\n }\n\n /** Get the current interpolated entity states */\n get state(): Map<string, EntityState> {\n return this._interpolatedState;\n }\n\n get networkQuality(): NetworkQuality {\n return this._networkQuality;\n }\n\n /**\n * Called every render frame to interpolate between buffered snapshots.\n * @param renderTime - current render time in ms\n */\n interpolate(renderTime: number): Map<string, EntityState> {\n if (this._buffer.length < 2) {\n // Not enough snapshots to interpolate, return latest\n return this._buffer.length > 0\n ? this._buffer[this._buffer.length - 1].entities\n : this._interpolatedState;\n }\n\n // Find two snapshots to interpolate between\n // We render \"behind\" by one buffer interval\n const interpDelay = (this._bufferSize - 1) * (1000 / 20); // Assume ~20 pps\n const targetTime = renderTime - interpDelay;\n\n let from: BufferedSnapshot | null = null;\n let to: BufferedSnapshot | null = null;\n\n for (let i = 0; i < this._buffer.length - 1; i++) {\n if (\n this._buffer[i].receivedAt <= targetTime &&\n this._buffer[i + 1].receivedAt > targetTime\n ) {\n from = this._buffer[i];\n to = this._buffer[i + 1];\n break;\n }\n }\n\n if (!from || !to) {\n // Extrapolation case: we're past all buffered snapshots\n const latest = this._buffer[this._buffer.length - 1];\n const timeSinceLatest = renderTime - latest.receivedAt;\n\n if (timeSinceLatest > this._extrapolateMs) {\n // Too far behind, just use latest snapshot\n this._updateNetworkQuality(\"poor\");\n return latest.entities;\n }\n\n if (this._buffer.length >= 2) {\n from = this._buffer[this._buffer.length - 2];\n to = latest;\n this._updateNetworkQuality(\"degraded\");\n } else {\n return latest.entities;\n }\n } else {\n this._updateNetworkQuality(\"good\");\n }\n\n // Compute interpolation factor\n const range = to.receivedAt - from.receivedAt;\n const t =\n range > 0\n ? Math.min(1, Math.max(0, (targetTime - from.receivedAt) / range))\n : 1;\n\n // Interpolate all entities\n const result = new Map<string, EntityState>();\n const allIds = new Set([...from.entities.keys(), ...to.entities.keys()]);\n\n for (const id of allIds) {\n const fromEntity = from.entities.get(id);\n const toEntity = to.entities.get(id);\n\n if (toEntity && toEntity.c?.__removed) continue; // Removed entity\n\n if (fromEntity && toEntity) {\n result.set(id, this._interpolateEntity(fromEntity, toEntity, t));\n } else if (toEntity) {\n result.set(id, toEntity);\n }\n }\n\n this._interpolatedState = result;\n return result;\n }\n\n /** Request a keyframe from the host */\n requestKeyframe(): void {\n this._ackChannel.send(\"-1\");\n }\n\n /** Register a listener fired after each snapshot is merged into the full world state */\n onSnapshot(cb: SnapshotListener): void {\n this._snapshotListeners.push(cb);\n }\n\n destroy(): void {\n this._snapshotChannel.close();\n this._ackChannel.close();\n this._buffer = [];\n this._interpolatedState.clear();\n this._fullState.clear();\n this._snapshotListeners = [];\n }\n\n private _handleSnapshot(data: Uint8Array): void {\n try {\n const { tick, baseTick, entities, hostInput } =\n this._codec.deserializePacket(data);\n const now = performance.now();\n\n if (baseTick === -1) {\n // Keyframe: replace full state\n this._fullState.clear();\n for (const entity of entities) {\n this._fullState.set(entity.id, entity);\n }\n } else {\n // Delta: apply changes to full state\n for (const entity of entities) {\n if (entity.c?.__removed) {\n this._fullState.delete(entity.id);\n } else {\n this._fullState.set(entity.id, entity);\n }\n }\n }\n\n // Buffer the full state snapshot\n const fullStateClone = new Map(this._fullState);\n this._buffer.push({\n tick,\n entities: fullStateClone,\n receivedAt: now,\n });\n\n // Keep buffer bounded\n while (this._buffer.length > this._bufferSize * 2) {\n this._buffer.shift();\n }\n\n // Send ACK\n this._ackChannel.send(String(tick));\n\n // Notify snapshot listeners with the merged full world state\n for (const cb of this._snapshotListeners) {\n cb(tick, fullStateClone, hostInput);\n }\n\n // Track timing for network quality\n this._lastSnapshotTime = now;\n this._packetCount++;\n } catch {\n this._packetLossCount++;\n }\n }\n\n private _interpolateEntity(\n from: EntityState,\n to: EntityState,\n t: number,\n ): EntityState {\n if (this._is2D || !(\"z\" in from)) {\n return this._interpolateEntity2D(\n from as EntityState2D,\n to as EntityState2D,\n t,\n );\n }\n return this._interpolateEntity3D(\n from as EntityState3D,\n to as EntityState3D,\n t,\n );\n }\n\n private _interpolateEntity2D(\n from: EntityState2D,\n to: EntityState2D,\n t: number,\n ): EntityState2D {\n if (this._method === \"hermite\") {\n return {\n id: to.id,\n x: hermite(from.x, from.vx, to.x, to.vx, t),\n y: hermite(from.y, from.vy, to.y, to.vy, t),\n a: lerpAngle(from.a, to.a, t),\n vx: lerp(from.vx, to.vx, t),\n vy: lerp(from.vy, to.vy, t),\n va: lerp(from.va, to.va, t),\n c: interpolateCustom(from.c, to.c, t),\n };\n }\n // Linear\n return {\n id: to.id,\n x: lerp(from.x, to.x, t),\n y: lerp(from.y, to.y, t),\n a: lerpAngle(from.a, to.a, t),\n vx: lerp(from.vx, to.vx, t),\n vy: lerp(from.vy, to.vy, t),\n va: lerp(from.va, to.va, t),\n c: interpolateCustom(from.c, to.c, t),\n };\n }\n\n private _interpolateEntity3D(\n from: EntityState3D,\n to: EntityState3D,\n t: number,\n ): EntityState3D {\n // Quaternion SLERP for rotation\n const [qx, qy, qz, qw] = slerp(\n from.qx,\n from.qy,\n from.qz,\n from.qw,\n to.qx,\n to.qy,\n to.qz,\n to.qw,\n t,\n );\n\n if (this._method === \"hermite\") {\n return {\n id: to.id,\n x: hermite(from.x, from.vx, to.x, to.vx, t),\n y: hermite(from.y, from.vy, to.y, to.vy, t),\n z: hermite(from.z, from.vz, to.z, to.vz, t),\n qx,\n qy,\n qz,\n qw,\n vx: lerp(from.vx, to.vx, t),\n vy: lerp(from.vy, to.vy, t),\n vz: lerp(from.vz, to.vz, t),\n wx: lerp(from.wx, to.wx, t),\n wy: lerp(from.wy, to.wy, t),\n wz: lerp(from.wz, to.wz, t),\n c: interpolateCustom(from.c, to.c, t),\n };\n }\n\n // Linear\n return {\n id: to.id,\n x: lerp(from.x, to.x, t),\n y: lerp(from.y, to.y, t),\n z: lerp(from.z, to.z, t),\n qx,\n qy,\n qz,\n qw,\n vx: lerp(from.vx, to.vx, t),\n vy: lerp(from.vy, to.vy, t),\n vz: lerp(from.vz, to.vz, t),\n wx: lerp(from.wx, to.wx, t),\n wy: lerp(from.wy, to.wy, t),\n wz: lerp(from.wz, to.wz, t),\n c: interpolateCustom(from.c, to.c, t),\n };\n }\n\n private _updateNetworkQuality(quality: NetworkQuality): void {\n this._networkQuality = quality;\n }\n}\n\n// ── Math utilities ──\n\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\nfunction lerpAngle(a: number, b: number, t: number): number {\n let diff = b - a;\n // Wrap to [-PI, PI]\n while (diff > Math.PI) diff -= Math.PI * 2;\n while (diff < -Math.PI) diff += Math.PI * 2;\n return a + diff * t;\n}\n\n/**\n * Hermite spline interpolation using velocity for smooth curves.\n * h(t) = (2t^3 - 3t^2 + 1)p0 + (t^3 - 2t^2 + t)v0 + (-2t^3 + 3t^2)p1 + (t^3 - t^2)v1\n */\nfunction hermite(\n p0: number,\n v0: number,\n p1: number,\n v1: number,\n t: number,\n): number {\n const t2 = t * t;\n const t3 = t2 * t;\n return (\n (2 * t3 - 3 * t2 + 1) * p0 +\n (t3 - 2 * t2 + t) * v0 +\n (-2 * t3 + 3 * t2) * p1 +\n (t3 - t2) * v1\n );\n}\n\n/**\n * Quaternion SLERP (Spherical Linear Interpolation).\n */\nfunction slerp(\n ax: number,\n ay: number,\n az: number,\n aw: number,\n bx: number,\n by: number,\n bz: number,\n bw: number,\n t: number,\n): [number, number, number, number] {\n // Compute dot product\n let dot = ax * bx + ay * by + az * bz + aw * bw;\n\n // Ensure shortest path\n if (dot < 0) {\n bx = -bx;\n by = -by;\n bz = -bz;\n bw = -bw;\n dot = -dot;\n }\n\n if (dot > 0.9995) {\n // Very close, use linear interpolation\n return [lerp(ax, bx, t), lerp(ay, by, t), lerp(az, bz, t), lerp(aw, bw, t)];\n }\n\n const theta = Math.acos(Math.min(1, Math.max(-1, dot)));\n const sinTheta = Math.sin(theta);\n const wa = Math.sin((1 - t) * theta) / sinTheta;\n const wb = Math.sin(t * theta) / sinTheta;\n\n return [\n ax * wa + bx * wb,\n ay * wa + by * wb,\n az * wa + bz * wb,\n aw * wa + bw * wb,\n ];\n}\n\n/**\n * Interpolate custom properties: lerp numbers, instant-swap others.\n */\nfunction interpolateCustom(\n from: Record<string, unknown> | undefined,\n to: Record<string, unknown> | undefined,\n t: number,\n): Record<string, unknown> | undefined {\n if (!from && !to) return undefined;\n if (!from) return to;\n if (!to) return from;\n\n const result: Record<string, unknown> = {};\n const allKeys = new Set([...Object.keys(from), ...Object.keys(to)]);\n\n for (const key of allKeys) {\n const fromVal = from[key];\n const toVal = to[key];\n if (typeof fromVal === \"number\" && typeof toVal === \"number\") {\n result[key] = lerp(fromVal, toVal, t);\n } else {\n // Instant swap: use target value after halfway\n result[key] = t >= 0.5 ? toVal : fromVal;\n }\n }\n\n return result;\n}\n","import type {\n CarverTransport,\n EntityState,\n PlayerInput,\n SnapshotListener,\n SnapshotSource,\n} from \"../types\";\nimport { Codec, SnapshotBuffer } from \"../core/codec\";\nimport { HostAuthority } from \"../core/HostAuthority\";\nimport { ClientReceiver } from \"../core/ClientReceiver\";\n\nexport interface SnapshotSyncOptions {\n broadcastRate?: number;\n keyframeInterval?: number;\n bufferSize?: number;\n interpolationMethod?: \"hermite\" | \"linear\";\n extrapolateMs?: number;\n is2D?: boolean;\n}\n\n/**\n * Layer 2: Snapshot interpolation sync engine.\n * Host broadcasts state at fixed intervals, clients interpolate.\n */\nexport class SnapshotSync implements SnapshotSource {\n private _transport: CarverTransport;\n private _hostAuthority: HostAuthority | null = null;\n private _clientReceiver: ClientReceiver | null = null;\n private _codec: Codec;\n private _snapshotBuffer: SnapshotBuffer;\n\n // Snapshot listeners survive host migration (forwarder re-attached on demote)\n private _snapshotListeners: SnapshotListener[] = [];\n\n constructor(\n transport: CarverTransport,\n codec: Codec,\n snapshotBuffer: SnapshotBuffer,\n options?: SnapshotSyncOptions,\n ) {\n this._transport = transport;\n this._codec = codec;\n this._snapshotBuffer = snapshotBuffer;\n\n if (transport.isHost) {\n this._hostAuthority = new HostAuthority(\n transport,\n codec,\n snapshotBuffer,\n {\n broadcastRate: options?.broadcastRate,\n keyframeInterval: options?.keyframeInterval,\n },\n );\n } else {\n this._clientReceiver = new ClientReceiver(transport, codec, {\n bufferSize: options?.bufferSize,\n method: options?.interpolationMethod,\n extrapolateMs: options?.extrapolateMs,\n is2D: options?.is2D,\n });\n this._attachSnapshotForwarder(this._clientReceiver);\n }\n }\n\n get isHost(): boolean {\n return this._hostAuthority !== null;\n }\n\n get hostAuthority(): HostAuthority | null {\n return this._hostAuthority;\n }\n\n get clientReceiver(): ClientReceiver | null {\n return this._clientReceiver;\n }\n\n /** Host: called every fixed tick to potentially broadcast state */\n hostTick(\n tick: number,\n entities: Map<string, EntityState>,\n delta: number,\n hostInput?: PlayerInput,\n ): void {\n this._hostAuthority?.tick(tick, entities, delta, hostInput);\n }\n\n /** Register a listener fired after each merged snapshot (client side). */\n onSnapshot(cb: SnapshotListener): void {\n this._snapshotListeners.push(cb);\n }\n\n /** Client: called every render frame to interpolate */\n clientInterpolate(renderTime: number): Map<string, EntityState> {\n return this._clientReceiver?.interpolate(renderTime) ?? new Map();\n }\n\n /** Set interest filter on host authority */\n setInterestFilter(\n filter: ((entityId: string, peerId: string) => boolean) | null,\n ): void {\n this._hostAuthority?.setInterestFilter(filter);\n }\n\n /** Handle host migration: switch from client to host mode */\n promoteToHost(options?: SnapshotSyncOptions): void {\n this._clientReceiver?.destroy();\n this._clientReceiver = null;\n this._hostAuthority = new HostAuthority(\n this._transport,\n this._codec,\n this._snapshotBuffer,\n {\n broadcastRate: options?.broadcastRate,\n keyframeInterval: options?.keyframeInterval,\n },\n );\n }\n\n /** Handle host migration: switch from host to client mode */\n demoteToClient(options?: SnapshotSyncOptions): void {\n this._hostAuthority?.destroy();\n this._hostAuthority = null;\n this._clientReceiver = new ClientReceiver(\n this._transport,\n this._codec,\n {\n bufferSize: options?.bufferSize,\n method: options?.interpolationMethod,\n extrapolateMs: options?.extrapolateMs,\n is2D: options?.is2D,\n },\n );\n this._attachSnapshotForwarder(this._clientReceiver);\n }\n\n destroy(): void {\n this._hostAuthority?.destroy();\n this._clientReceiver?.destroy();\n this._hostAuthority = null;\n this._clientReceiver = null;\n this._snapshotListeners = [];\n }\n\n // ── Private ──\n\n /** Forward receiver snapshots to registered listeners (survives host migration). */\n private _attachSnapshotForwarder(receiver: ClientReceiver): void {\n receiver.onSnapshot((t, e, hi) => {\n for (const l of this._snapshotListeners) l(t, e, hi);\n });\n }\n}\n","/**\n * InputBuffer — unified ring-buffer for local and peer inputs.\n *\n * Ported from LumberNet's LumberInputBuffer. Stores:\n * - local player tick-keyed inputs (storeTick / getTick / hasTick)\n * - last-received input per remote peer (setRemote / getRemote / allRemotes)\n * - per-peer tick-keyed inputs for accurate rollback resimulation (getRemoteAtTick)\n *\n * Generic over per-tick payload I. Caller supplies the neutral payload.\n */\n\nimport type { PlayerInput } from \"../types\";\n\nexport class InputBuffer<I extends PlayerInput = PlayerInput> {\n private readonly _historySize: number;\n private readonly _neutral: I;\n\n /** Local player tick-keyed inputs (ring buffer). */\n private readonly _local = new Map<number, I>();\n\n /** Last-received input per remote peer. */\n private readonly _remotes = new Map<string, I>();\n\n /** Per-peer tick-keyed inputs for accurate rollback re-simulation. */\n private readonly _peerTicks = new Map<string, Map<number, I>>();\n\n constructor(neutralInput: I, historySize = 120) {\n this._neutral = { ...neutralInput } as I;\n this._historySize = historySize;\n }\n\n // ── Local input ──\n\n /** Record a snapshot of the local input at the given tick. Evicts the entry exactly historySize back. */\n storeTick(tick: number, input: I): void {\n this._local.set(tick, { ...input } as I);\n this._local.delete(tick - this._historySize);\n }\n\n /** Return the local input at `tick`, or a neutral copy if out of range. */\n getTick(tick: number): I {\n return this._local.get(tick) ?? ({ ...this._neutral } as I);\n }\n\n /** True if we have a stored local input for this tick (used to avoid spurious justPressed after snap/rejoin). */\n hasTick(tick: number): boolean {\n return this._local.has(tick);\n }\n\n /** Neutral payload with every boolean field forced false (use for justPressed when prev tick is unknown). */\n getJustPressedZero(): I {\n const out = {} as I;\n for (const key in this._neutral) {\n const v = this._neutral[key];\n (out as Record<string, boolean | number | undefined>)[key] =\n typeof v === \"boolean\" ? false : v;\n }\n return out;\n }\n\n // ── Remote (peer) inputs ──\n\n /**\n * Record a remote peer's input. If `tick` is given (the sender's local tick),\n * also store it in the per-peer ring buffer for rollback.\n */\n setRemote(peerId: string, input: I, tick?: number): void {\n this._remotes.set(peerId, input);\n if (tick !== undefined) {\n let peerMap = this._peerTicks.get(peerId);\n if (!peerMap) {\n peerMap = new Map();\n this._peerTicks.set(peerId, peerMap);\n }\n peerMap.set(tick, input);\n peerMap.delete(tick - this._historySize);\n }\n }\n\n /** Last-known input for a peer, or a neutral copy if never received. */\n getRemote(peerId: string): I {\n return this._remotes.get(peerId) ?? ({ ...this._neutral } as I);\n }\n\n /** Snapshot of all remote peers' last-known inputs (shallow copy of the map). */\n allRemotes(): Map<string, I> {\n return new Map(this._remotes);\n }\n\n /**\n * Return a peer's exact input at the given tick (for rollback accuracy),\n * falling back to their last-known input when history does not reach that far.\n */\n getRemoteAtTick(peerId: string, tick: number): I {\n return this._peerTicks.get(peerId)?.get(tick) ?? this.getRemote(peerId);\n }\n\n /** Override the last-known input for a peer (does NOT touch tick history). */\n overrideRemote(peerId: string, input: I): void {\n this._remotes.set(peerId, input);\n }\n\n /** Number of currently tracked remote peers. */\n get peerCount(): number {\n return this._remotes.size;\n }\n\n /** Iterate tracked peer IDs. */\n peerIds(): IterableIterator<string> {\n return this._remotes.keys();\n }\n\n /**\n * Keep only these peer IDs; remove any other remotes (e.g. after leave/rejoin).\n * Call when the room's peer list changes so stale peers stop receiving input.\n */\n setPeerIds(peerIds: ReadonlySet<string> | string[]): void {\n const set = peerIds instanceof Set ? peerIds : new Set(peerIds);\n for (const id of this._remotes.keys()) {\n if (!set.has(id)) {\n this._remotes.delete(id);\n this._peerTicks.delete(id);\n }\n }\n }\n\n // ── Lifecycle ──\n\n /** Clear local history, remote last-known inputs, and per-peer tick history. */\n clear(): void {\n this._local.clear();\n this._remotes.clear();\n this._peerTicks.clear();\n }\n}\n","/**\n * Input utilities for prediction mode.\n * Ported from LumberNet's InputUtils.\n */\n\nimport type { PlayerInput } from \"../types\";\n\n/**\n * Given the input state at the current tick and the previous tick, return a new\n * input object where each boolean field is `true` only if it transitioned\n * false -> true (a \"rising edge\" / \"just pressed\").\n *\n * Non-boolean fields are passed through unchanged from `curr`.\n * Iterates `curr` keys only; keys present only in `prev` are absent from the result.\n *\n * PredictionSync calls this automatically and passes the result as `justPressed`\n * to every onPhysicsStep callback, so games normally do not call it directly.\n */\nexport function computeJustPressed<I extends PlayerInput>(curr: I, prev: I): I {\n const out = {} as I;\n for (const key in curr) {\n const c = curr[key];\n (out as Record<string, boolean | number | undefined>)[key] =\n typeof c === \"boolean\" ? c === true && prev[key] !== true : c;\n }\n return out;\n}\n","/**\n * Rollback — full-world rollback for prediction mode.\n *\n * Ported from LumberNet's LumberRollback, adapted to CarverJS entity state\n * (2D and 3D). On each accepted server snapshot the client:\n * 1. captures the pre-rollback visual pose (raw physics + accumulated error),\n * 2. hard-applies server state to EVERY networked entity,\n * 3. resimulates from serverTick + 1 to localTick replaying per-tick inputs\n * (or hard-snaps the tick when drift exceeds maxRewindTicks),\n * 4. converts the visual discontinuity into per-entity error offsets.\n *\n * Pure module: no transport, no React, no three.js. Quaternion math is\n * hand-rolled below.\n */\n\nimport type { InputBuffer } from \"../core/InputBuffer\";\nimport { computeJustPressed } from \"../core/InputUtils\";\nimport type {\n EntityState,\n ErrorOffset,\n PhysicsStepCallback,\n PlayerInput,\n PredictionWorldDriver,\n} from \"../types\";\n\n// ── Quaternion helpers ──\n\nexport interface Quat {\n x: number;\n y: number;\n z: number;\n w: number;\n}\n\nconst IDENTITY_QUAT: Quat = { x: 0, y: 0, z: 0, w: 1 };\n\n/** Hamilton product (a ⊗ b). */\nexport function quatMultiply(a: Quat, b: Quat): Quat {\n return {\n x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,\n y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,\n z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,\n w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,\n };\n}\n\n/** Conjugate (inputs assumed normalized). */\nexport function quatInvert(q: Quat): Quat {\n return { x: -q.x, y: -q.y, z: -q.z, w: q.w };\n}\n\n/** Normalize; degenerate (near-zero) quaternions return identity. */\nexport function quatNormalize(q: Quat): Quat {\n const mag = Math.hypot(q.x, q.y, q.z, q.w);\n if (mag < 1e-12) return { ...IDENTITY_QUAT };\n return { x: q.x / mag, y: q.y / mag, z: q.z / mag, w: q.w / mag };\n}\n\n/** Rotation angle in radians: 2*acos(clamp(|w|, 0, 1)). */\nexport function quatAngle(q: Quat): number {\n return 2 * Math.acos(Math.min(1, Math.abs(q.w)));\n}\n\n/** Scale the rotation angle toward identity by `factor`, preserving the axis. */\nexport function quatScaleAngle(q: Quat, factor: number): Quat {\n let n = quatNormalize(q);\n if (n.w < 0) n = { x: -n.x, y: -n.y, z: -n.z, w: -n.w };\n const angle = 2 * Math.acos(Math.min(1, n.w));\n if (angle < 1e-6) return { ...IDENTITY_QUAT };\n const s = Math.sin(angle / 2);\n const ax = n.x / s;\n const ay = n.y / s;\n const az = n.z / s;\n const na = angle * factor;\n const ns = Math.sin(na / 2);\n return { x: ax * ns, y: ay * ns, z: az * ns, w: Math.cos(na / 2) };\n}\n\n// ── Rollback ──\n\nexport interface RollbackParams {\n /** Tick embedded in the accepted server snapshot. */\n serverTick: number;\n /** Authoritative full-world state from the snapshot. */\n serverState: ReadonlyMap<string, EntityState>;\n /** Client's current simulation tick. */\n localTick: number;\n /** This peer's id (local inputs are replayed from the local ring buffer). */\n localPeerId: string;\n /** Input buffer holding local and per-peer tick history. */\n inputs: InputBuffer;\n /** Currently accumulated visual error offsets (kept continuous across rollbacks). */\n currentErrors: ReadonlyMap<string, ErrorOffset>;\n /** World access for capture/apply/step. */\n driver: PredictionWorldDriver;\n /** Game physics-step callback, re-invoked during resimulation. */\n callback: PhysicsStepCallback | null;\n /** Fixed timestep in seconds. */\n dt: number;\n /** Snap target offset: snap target = serverTick + driftTargetTicks. */\n driftTargetTicks: number;\n /** Max |localTick - (serverTick + driftTargetTicks)| before hard tick snap. */\n maxRewindTicks: number;\n /** Per-axis positional jump above which correction is suppressed (teleport). */\n snapThreshold: number;\n}\n\nexport interface RollbackResult {\n /** New local tick (differs from localTick only when a hard snap occurred). */\n newLocalTick: number;\n snapped: boolean;\n /** Per-entity visual error offsets (pre-visual minus post-resim). */\n errors: Map<string, ErrorOffset>;\n}\n\ninterface PreVisualPose {\n x: number;\n y: number;\n z: number;\n a: number;\n q: Quat;\n is3D: boolean;\n}\n\n/**\n * Apply a server snapshot to the whole world and resimulate forward.\n * Returns the new local tick and per-entity visual error offsets.\n */\nexport function applyRollback(params: RollbackParams): RollbackResult {\n const {\n serverTick,\n serverState,\n localTick,\n localPeerId,\n inputs,\n currentErrors,\n driver,\n callback,\n dt,\n driftTargetTicks,\n maxRewindTicks,\n snapThreshold,\n } = params;\n\n // Step 0: pre-rollback visual pose (raw physics + accumulated error)\n const preState = driver.captureState();\n const preMap = new Map<string, PreVisualPose>();\n for (const [id, s] of preState) {\n const e = currentErrors.get(id);\n const ex = e?.x ?? 0;\n const ey = e?.y ?? 0;\n if (\"z\" in s) {\n const errQ: Quat = e\n ? { x: e.qx, y: e.qy, z: e.qz, w: e.qw }\n : { ...IDENTITY_QUAT };\n preMap.set(id, {\n x: s.x + ex,\n y: s.y + ey,\n z: s.z + (e?.z ?? 0),\n a: 0,\n q: quatMultiply(errQ, { x: s.qx, y: s.qy, z: s.qz, w: s.qw }),\n is3D: true,\n });\n } else {\n preMap.set(id, {\n x: s.x + ex,\n y: s.y + ey,\n z: 0,\n a: s.a + (e?.a ?? 0),\n q: { ...IDENTITY_QUAT },\n is3D: false,\n });\n }\n }\n\n // Step 1: hard-apply server state to the WHOLE world (local player included)\n driver.applyState(serverState.values());\n\n // Step 2: snap-vs-resim decision\n const targetTick = serverTick + driftTargetTicks;\n const tickDiff = localTick - targetTick;\n let newLocalTick = localTick;\n let snapped = false;\n\n if (Math.abs(tickDiff) > maxRewindTicks) {\n newLocalTick = targetTick;\n snapped = true;\n } else {\n // Step 3: resimulate from serverTick + 1 to localTick replaying tick-exact inputs\n for (let i = serverTick + 1; i <= localTick; i++) {\n if (callback) {\n const tickInputs = new Map<string, PlayerInput>();\n const justPressed = new Map<string, PlayerInput>();\n for (const peerId of inputs.peerIds()) {\n if (peerId === localPeerId) continue;\n const curr = inputs.getRemoteAtTick(peerId, i);\n tickInputs.set(peerId, curr);\n justPressed.set(\n peerId,\n computeJustPressed(curr, inputs.getRemoteAtTick(peerId, i - 1)),\n );\n }\n const localCurr = inputs.getTick(i);\n tickInputs.set(localPeerId, localCurr);\n justPressed.set(\n localPeerId,\n computeJustPressed(localCurr, inputs.getTick(i - 1)),\n );\n callback(tickInputs, justPressed, i, true, dt);\n }\n driver.stepWorld?.();\n }\n }\n\n // Step 4: error vectors = pre-rollback visual pose minus post-resim state\n const postState = driver.captureState();\n const errors = new Map<string, ErrorOffset>();\n for (const [id, post] of postState) {\n const pre = preMap.get(id);\n if (!pre) continue;\n\n const errX = pre.x - post.x;\n const errY = pre.y - post.y;\n let errZ = 0;\n let errA = 0;\n let q: Quat = { ...IDENTITY_QUAT };\n\n if (\"z\" in post) {\n errZ = pre.z - post.z;\n let dq = quatNormalize(\n quatMultiply(\n pre.q,\n quatInvert({ x: post.qx, y: post.qy, z: post.qz, w: post.qw }),\n ),\n );\n if (dq.w < 0) dq = { x: -dq.x, y: -dq.y, z: -dq.z, w: -dq.w };\n q = dq;\n } else {\n const ad = pre.a - post.a;\n // Wrap to (-PI, PI]\n errA = ad - Math.PI * 2 * Math.floor((ad + Math.PI) / (Math.PI * 2));\n }\n\n // Large per-axis jump -> suppress ALL correction (intentional teleport)\n if (\n Math.abs(errX) > snapThreshold ||\n Math.abs(errY) > snapThreshold ||\n Math.abs(errZ) > snapThreshold\n ) {\n errors.set(id, { x: 0, y: 0, z: 0, a: 0, qx: 0, qy: 0, qz: 0, qw: 1 });\n } else {\n errors.set(id, {\n x: errX,\n y: errY,\n z: errZ,\n a: errA,\n qx: q.x,\n qy: q.y,\n qz: q.z,\n qw: q.w,\n });\n }\n }\n\n return { newLocalTick, snapped, errors };\n}\n","/**\n * Layer 3: Full-world prediction with full-world rollback.\n * Ported from LumberNet, built on top of Layer 2 (SnapshotSync) as the\n * authoritative state channel.\n *\n * Flow:\n * Every peer (host included): broadcast tick-stamped input to ALL peers each\n * fixed tick on carver:inputs, simulate EVERY networked entity forward with\n * last-known remote inputs (hold-last-input extrapolation).\n *\n * Host: stays authoritative; SnapshotSync broadcasts delta-compressed,\n * ACK-driven snapshots with the host's own input embedded (`hi`).\n *\n * Client: on each accepted snapshot, reset ALL networked entities to server\n * state, resimulate from serverTick + 1 to localTick replaying per-tick\n * inputs for every peer, and convert the visual discontinuity into\n * per-entity error offsets decayed per render frame.\n *\n * Role is checked dynamically via transport.isHost at every use site (host\n * migration is best-effort).\n */\n\nimport type {\n CarverTransport,\n CarverChannel,\n EntityState,\n ErrorOffset,\n InputPacket,\n PhysicsStepCallback,\n PlayerInput,\n PredictionSyncOptions,\n PredictionWorldDriver,\n SnapshotSource,\n} from \"../types\";\nimport { TickKeeper } from \"../core/TickKeeper\";\nimport { InputBuffer } from \"../core/InputBuffer\";\nimport { computeJustPressed } from \"../core/InputUtils\";\nimport { applyRollback, quatAngle, quatScaleAngle } from \"./Rollback\";\n\nconst DEFAULT_OPTIONS: Required<PredictionSyncOptions> = {\n maxRewindTicks: 15,\n snapThreshold: 150,\n errorDecay: 0.85,\n maxErrorPerFrame: 0,\n neutralInput: {},\n inputHistorySize: 120,\n driftTargetTicks: 4,\n};\n\ninterface PendingSnapshot {\n t: number;\n entities: Map<string, EntityState>;\n hostInput: PlayerInput | undefined;\n}\n\nexport class PredictionSync {\n private _transport: CarverTransport;\n private _tickKeeper: TickKeeper;\n private _options: Required<PredictionSyncOptions>;\n\n // Reliable ordered all-to-all input channel\n private _inputChannel: CarverChannel<InputPacket>;\n\n // Local + per-peer tick-stamped input history\n private _inputs: InputBuffer;\n\n // Local input; persists across ticks until replaced (hold-input semantics)\n private _currentInput: PlayerInput | null = null;\n\n // Newest pending server snapshot awaiting rollback (only the newest survives)\n private _pending: PendingSnapshot | null = null;\n\n // Per-entity accumulated visual error offsets\n private _errors = new Map<string, ErrorOffset>();\n\n private _serverTick = 0;\n private _lastAppliedServerTick = 0;\n\n private _worldDriver: PredictionWorldDriver | null = null;\n private _onPhysicsStep: PhysicsStepCallback | null = null;\n\n constructor(\n transport: CarverTransport,\n tickKeeper: TickKeeper,\n snapshots: SnapshotSource,\n options?: PredictionSyncOptions,\n ) {\n this._transport = transport;\n this._tickKeeper = tickKeeper;\n this._options = { ...DEFAULT_OPTIONS, ...options };\n\n this._inputs = new InputBuffer(\n this._options.neutralInput,\n this._options.inputHistorySize,\n );\n\n // All-to-all input broadcast channel (registered regardless of role)\n this._inputChannel = transport.createChannel<InputPacket>(\"carver:inputs\", {\n reliable: true,\n ordered: true,\n });\n\n this._inputChannel.onReceive(\n (data: InputPacket | string, peerId: string) => {\n try {\n const packet =\n typeof data === \"string\" ? (JSON.parse(data) as InputPacket) : data;\n if (\n typeof packet.t === \"number\" &&\n packet.i !== null &&\n typeof packet.i === \"object\"\n ) {\n // Key by the transport-provided sender id; never trust packet.p\n this._inputs.setRemote(peerId, packet.i, packet.t);\n }\n } catch (err) {\n if (typeof console !== \"undefined\")\n console.debug(\"[CarverJS] Malformed input packet:\", err);\n }\n },\n );\n\n // Accepted snapshots become pending rollbacks (clients only)\n snapshots.onSnapshot((tick, entities, hostInput) => {\n if (this._transport.isHost) return;\n if (tick <= this._lastAppliedServerTick) return;\n this._serverTick = tick;\n this._tickKeeper.setServerTick(tick);\n if (!this._pending || tick > this._pending.t) {\n this._pending = { t: tick, entities, hostInput };\n }\n });\n\n // Drop input state for departed peers\n transport.onPeerLeave(() => {\n this._inputs.setPeerIds(this._transport.peers);\n });\n }\n\n // ── Wiring ──\n\n /** Set the game simulation callback (forward sim + rollback resim). */\n setPhysicsStep(cb: PhysicsStepCallback): void {\n this._onPhysicsStep = cb;\n }\n\n /** Set the world driver used for forward stepping and rollback. */\n setWorldDriver(driver: PredictionWorldDriver): void {\n this._worldDriver = driver;\n }\n\n // ── Input ──\n\n /** Set the local player's input. PERSISTS across ticks until replaced. */\n setInput(input: PlayerInput): void {\n this._currentInput = input;\n }\n\n /** Local input stored at the given tick (neutral fallback). Used by the host to embed `hi`. */\n getLocalInput(tick: number): PlayerInput {\n return this._inputs.getTick(tick);\n }\n\n // ── Frame lifecycle ──\n\n /**\n * Apply the newest pending server snapshot (full-world rollback).\n * Call once per render frame BEFORE tickKeeper.update().\n * No-op on host or when nothing is pending.\n */\n beginFrame(): void {\n if (this._transport.isHost || !this._pending) return;\n\n const pending = this._pending;\n this._pending = null;\n this._lastAppliedServerTick = pending.t;\n\n // Consume the host's embedded input: last-known AND tick history at the snapshot tick\n if (pending.hostInput !== undefined) {\n this._inputs.setRemote(\n this._transport.hostId,\n pending.hostInput,\n pending.t,\n );\n }\n\n // Rollback is impossible without world access (bookkeeping above still happened)\n if (!this._worldDriver) return;\n\n const result = applyRollback({\n serverTick: pending.t,\n serverState: pending.entities,\n localTick: this._tickKeeper.tick,\n localPeerId: this._transport.peerId,\n inputs: this._inputs,\n currentErrors: this._errors,\n driver: this._worldDriver,\n callback: this._onPhysicsStep,\n dt: this._tickKeeper.tickDelta,\n driftTargetTicks: this._options.driftTargetTicks,\n maxRewindTicks: this._options.maxRewindTicks,\n snapThreshold: this._options.snapThreshold,\n });\n this._errors = result.errors;\n if (result.newLocalTick !== this._tickKeeper.tick) {\n this._tickKeeper.snapTick(result.newLocalTick);\n }\n }\n\n /**\n * Run one forward fixed tick (host AND client): store + broadcast input,\n * build per-tick input maps, invoke the callback, then step the world.\n */\n tick(tick: number): void {\n const localInput = this._currentInput ?? { ...this._options.neutralInput };\n\n // Store a copy so each tick gets its own history entry\n this._inputs.storeTick(tick, localInput);\n\n // Broadcast to ALL peers every tick (reliable-ordered guarantees tick history)\n this._inputChannel.send({\n t: tick,\n i: localInput,\n p: this._transport.peerId,\n });\n\n // Build per-tick maps: last-known remote inputs (hold-last-input extrapolation)\n const prevTick = tick - 1;\n const tickInputs = this._inputs.allRemotes();\n tickInputs.delete(this._transport.peerId); // defensive: never simulate self as remote\n const justPressed = new Map<string, PlayerInput>();\n for (const [peerId, inp] of tickInputs) {\n justPressed.set(\n peerId,\n computeJustPressed(inp, this._inputs.getRemoteAtTick(peerId, prevTick)),\n );\n }\n tickInputs.set(this._transport.peerId, localInput);\n justPressed.set(\n this._transport.peerId,\n this._inputs.hasTick(prevTick)\n ? computeJustPressed(localInput, this._inputs.getTick(prevTick))\n : this._inputs.getJustPressedZero(), // suppress spurious edges after snap/rejoin\n );\n\n if (this._onPhysicsStep) {\n this._onPhysicsStep(\n tickInputs,\n justPressed,\n tick,\n false,\n this._tickKeeper.tickDelta,\n );\n }\n\n // Callback first (applies forces), then step\n this._worldDriver?.stepWorld?.();\n }\n\n /**\n * Decay stored error offsets and return the portion to ADD to rendered\n * transforms this frame. Call exactly once per render frame.\n */\n getRenderErrorOffsets(): Map<string, ErrorOffset> {\n const result = new Map<string, ErrorOffset>();\n const decay = this._options.errorDecay;\n const maxErr = this._options.maxErrorPerFrame;\n\n for (const [id, e] of this._errors) {\n // Decay\n e.x *= decay;\n e.y *= decay;\n e.z *= decay;\n e.a *= decay;\n let q = quatScaleAngle({ x: e.qx, y: e.qy, z: e.qz, w: e.qw }, decay);\n\n // Zero-clamp\n if (Math.abs(e.x) < 0.1) e.x = 0;\n if (Math.abs(e.y) < 0.1) e.y = 0;\n if (Math.abs(e.z) < 0.1) e.z = 0;\n if (Math.abs(e.a) < 0.001) e.a = 0;\n if (quatAngle(q) < 0.001) q = { x: 0, y: 0, z: 0, w: 1 };\n e.qx = q.x;\n e.qy = q.y;\n e.qz = q.z;\n e.qw = q.w;\n\n // Applied portion (position cap only; angular error always applied in full)\n let ax = e.x;\n let ay = e.y;\n let az = e.z;\n if (maxErr > 0) {\n const mag = Math.hypot(e.x, e.y, e.z);\n if (mag > maxErr) {\n const s = maxErr / mag;\n ax = e.x * s;\n ay = e.y * s;\n az = e.z * s;\n e.x -= ax;\n e.y -= ay;\n e.z -= az;\n } else {\n // Fully applied and consumed\n e.x = 0;\n e.y = 0;\n e.z = 0;\n }\n }\n\n const quatIsIdentity =\n e.qx === 0 && e.qy === 0 && e.qz === 0 && e.qw === 1;\n\n if (ax !== 0 || ay !== 0 || az !== 0 || e.a !== 0 || !quatIsIdentity) {\n result.set(id, {\n x: ax,\n y: ay,\n z: az,\n a: e.a,\n qx: e.qx,\n qy: e.qy,\n qz: e.qz,\n qw: e.qw,\n });\n }\n\n if (e.x === 0 && e.y === 0 && e.z === 0 && e.a === 0 && quatIsIdentity) {\n this._errors.delete(id);\n }\n }\n\n return result;\n }\n\n // ── State ──\n\n /** Tick of the newest RECEIVED snapshot. */\n get serverTick(): number {\n return this._serverTick;\n }\n\n /** Tick of the newest APPLIED (rolled-back) snapshot, 0 initially. */\n get lastAppliedServerTick(): number {\n return this._lastAppliedServerTick;\n }\n\n destroy(): void {\n this._inputChannel.close();\n this._inputs.clear();\n this._errors.clear();\n this._pending = null;\n this._currentInput = null;\n }\n}\n"],"mappings":"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","names":[]}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transport/webrtc/ice.ts","../src/transport/webrtc/peer.ts","../src/transport/webrtc/WebRTCTransport.ts"],"sourcesContent":["/** Default STUN servers (free, public) */\nconst DEFAULT_STUN_SERVERS: RTCIceServer[] = [\n { urls: 'stun:stun.l.google.com:19302' },\n { urls: 'stun:stun1.l.google.com:19302' },\n { urls: 'stun:stun2.l.google.com:19302' },\n { urls: 'stun:stun.cloudflare.com:3478' },\n];\n\n/**\n * Build RTCConfiguration from user-provided ICE servers.\n *\n * If the user provides `iceServers`, those are used as-is (STUN + TURN).\n * Otherwise, default public STUN servers are used.\n *\n * TURN servers should be included in the `iceServers` array by the user:\n * ```ts\n * iceServers: [\n * { urls: 'stun:stun.cloudflare.com:3478' },\n * { urls: 'turn:turn.cloudflare.com:3478', username: '...', credential: '...' },\n * ]\n * ```\n */\nexport function buildICEConfig(options?: {\n iceServers?: RTCIceServer[];\n iceTransportPolicy?: RTCIceTransportPolicy;\n}): RTCConfiguration {\n const servers: RTCIceServer[] =\n options?.iceServers && options.iceServers.length > 0\n ? options.iceServers\n : DEFAULT_STUN_SERVERS;\n\n return {\n iceServers: servers,\n iceCandidatePoolSize: 10,\n iceTransportPolicy: options?.iceTransportPolicy ?? 'all',\n };\n}\n","import type { ChannelOptions } from \"../../types\";\nimport type { PeerState } from \"../types\";\n\nexport interface PeerConnectionEvents {\n onStateChange: (state: PeerState) => void;\n onDataChannel: (channel: RTCDataChannel) => void;\n onIceCandidate: (candidate: RTCIceCandidate) => void;\n}\n\n/**\n * Manages a single RTCPeerConnection to one remote peer.\n *\n * ICE candidates that arrive before the remote description is set are\n * buffered and flushed automatically once setRemoteDescription completes.\n * This is critical for Firebase/MQTT signaling where offer, answer, and\n * candidates can arrive nearly simultaneously.\n */\nexport class PeerConnection {\n readonly peerId: string;\n private _connection: RTCPeerConnection;\n private _channels = new Map<string, RTCDataChannel>();\n private _events: PeerConnectionEvents;\n private _state: PeerState = 'connecting';\n private _remoteDescriptionSet = false;\n private _pendingCandidates: RTCIceCandidateInit[] = [];\n\n constructor(\n peerId: string,\n config: RTCConfiguration,\n events: PeerConnectionEvents,\n ) {\n this.peerId = peerId;\n this._events = events;\n this._connection = new RTCPeerConnection(config);\n\n this._connection.onicecandidate = (e) => {\n if (e.candidate) {\n this._events.onIceCandidate(e.candidate);\n }\n };\n\n this._connection.oniceconnectionstatechange = () => {\n this._updateState();\n };\n\n this._connection.onconnectionstatechange = () => {\n this._updateState();\n };\n\n this._connection.ondatachannel = (e) => {\n const channel = e.channel;\n this._channels.set(channel.label, channel);\n this._events.onDataChannel(channel);\n };\n }\n\n get state(): PeerState {\n return this._state;\n }\n\n get connection(): RTCPeerConnection {\n return this._connection;\n }\n\n private _updateState(): void {\n const iceState = this._connection.iceConnectionState;\n const connState = this._connection.connectionState;\n\n let newState: PeerState;\n if (connState === 'connected' || iceState === 'connected') {\n newState = 'connected';\n } else if (connState === 'failed' || iceState === 'failed') {\n newState = 'failed';\n } else if (connState === 'closed' || iceState === 'closed' || iceState === 'disconnected') {\n newState = 'disconnected';\n } else {\n newState = 'connecting';\n }\n\n if (newState !== this._state) {\n this._state = newState;\n this._events.onStateChange(newState);\n }\n }\n\n async createOffer(): Promise<RTCSessionDescriptionInit> {\n const offer = await this._connection.createOffer();\n await this._connection.setLocalDescription(offer);\n return offer;\n }\n\n async handleOffer(offer: RTCSessionDescriptionInit): Promise<RTCSessionDescriptionInit> {\n await this._connection.setRemoteDescription(new RTCSessionDescription(offer));\n this._remoteDescriptionSet = true;\n await this._flushPendingCandidates();\n const answer = await this._connection.createAnswer();\n await this._connection.setLocalDescription(answer);\n return answer;\n }\n\n async handleAnswer(answer: RTCSessionDescriptionInit): Promise<void> {\n await this._connection.setRemoteDescription(new RTCSessionDescription(answer));\n this._remoteDescriptionSet = true;\n await this._flushPendingCandidates();\n }\n\n async addIceCandidate(candidate: RTCIceCandidateInit): Promise<void> {\n if (!this._remoteDescriptionSet) {\n // Buffer until remote description is set -- prevents silent drops\n this._pendingCandidates.push(candidate);\n return;\n }\n try {\n await this._connection.addIceCandidate(new RTCIceCandidate(candidate));\n } catch {\n // Ignore ICE candidate errors (can happen during race conditions)\n }\n }\n\n private async _flushPendingCandidates(): Promise<void> {\n const candidates = this._pendingCandidates;\n this._pendingCandidates = [];\n for (const c of candidates) {\n try {\n await this._connection.addIceCandidate(new RTCIceCandidate(c));\n } catch {\n // Ignore errors on individual candidates\n }\n }\n }\n\n createDataChannel(name: string, options?: ChannelOptions): RTCDataChannel {\n // If a channel with this label already exists (e.g. received via\n // ondatachannel from the remote peer), reuse it instead of creating\n // a duplicate that fragments send/receive across two channels.\n const existing = this._channels.get(name);\n if (existing && existing.readyState !== 'closed') {\n return existing;\n }\n\n const dcOptions: RTCDataChannelInit = {};\n if (options?.reliable === false) {\n dcOptions.ordered = options?.ordered ?? false;\n dcOptions.maxRetransmits = options?.maxRetransmits ?? 0;\n } else {\n dcOptions.ordered = options?.ordered ?? true;\n }\n const channel = this._connection.createDataChannel(name, dcOptions);\n this._channels.set(name, channel);\n return channel;\n }\n\n getDataChannel(name: string): RTCDataChannel | undefined {\n return this._channels.get(name);\n }\n\n close(): void {\n for (const channel of this._channels.values()) {\n try { channel.close(); } catch { /* ignore */ }\n }\n this._channels.clear();\n this._pendingCandidates = [];\n this._remoteDescriptionSet = false;\n try { this._connection.close(); } catch { /* ignore */ }\n this._state = 'disconnected';\n }\n}\n","import type {\n CarverTransport,\n CarverChannel,\n ChannelOptions,\n TransportConfig,\n Player,\n Room,\n RoomState,\n} from \"../../types\";\nimport type { TransportCallbacks, RateLimitConfig } from \"../types\";\nimport type { SignalingStrategy, PeerMetadata } from \"../strategy/types\";\nimport { buildICEConfig } from \"./ice\";\nimport { PeerConnection } from \"./peer\";\n\nconst ROOM_CONTROL_CHANNEL = 'carver:room-control';\n\ninterface ChannelState<T> {\n name: string;\n options: ChannelOptions;\n receivers: ((data: T, peerId: string) => void)[];\n}\n\n/** Room control messages exchanged over the room-control data channel */\ntype RoomControlMessage =\n | { type: 'player-updated'; player: Player }\n | { type: 'room-updated'; room: Partial<Room> }\n | { type: 'kick'; peerId: string; reason?: string }\n | { type: 'host-changed'; newHostId: string }\n | { type: 'request-ready'; ready: boolean }\n | { type: 'request-metadata'; metadata: Record<string, unknown> }\n | { type: 'request-room-metadata'; metadata: Record<string, unknown> }\n | { type: 'request-room-state'; state: RoomState }\n | { type: 'request-max-players'; maxPlayers: number }\n | { type: 'request-lock' }\n | { type: 'request-unlock' }\n | { type: 'request-transfer-host'; peerId: string }\n | { type: 'sync-state'; room: Room; players: Player[] };\n\n/** Deterministic host election: lowest peerId alphabetically */\nfunction electHost(peerIds: string[]): string {\n return [...peerIds].sort()[0];\n}\n\n/**\n * Implements CarverTransport using WebRTC data channels for game data\n * and a pluggable SignalingStrategy for peer discovery + SDP/ICE relay.\n *\n * No WebSocket server required. The strategy handles signaling through\n * MQTT brokers, Firebase RTDB, or any other network.\n */\nexport class WebRTCTransport implements CarverTransport {\n private _strategy: SignalingStrategy;\n private _peers = new Map<string, PeerConnection>();\n private _peerSet = new Set<string>();\n private _peerId: string;\n private _hostId = '';\n private _isHost = false;\n private _callbacks: TransportCallbacks = {\n onPeerJoin: [],\n onPeerLeave: [],\n onPeerUpdated: [],\n onHostChanged: [],\n };\n private _roomUpdatedCallbacks: ((room: Room) => void)[] = [];\n private _channels = new Map<string, ChannelState<any>>();\n private _iceConfig: RTCConfiguration;\n private _rateLimitConfig: RateLimitConfig = { maxMessagesPerSecond: 60, windowMs: 1000 };\n private _rateLimitCounters = new Map<string, { count: number; resetAt: number }>();\n private _connected = false;\n private _room: Room | null = null;\n private _playerMap = new Map<string, Player>();\n private _initialPeers: Player[] = [];\n private _strategyUnsubs: (() => void)[] = [];\n\n /**\n * @param strategy Shared SignalingStrategy instance (managed by MultiplayerProvider)\n * @param iceServers Optional ICE servers (STUN + TURN). Defaults to public STUN.\n * @param iceTransportPolicy 'all' (default) or 'relay' (force TURN only).\n */\n constructor(\n strategy: SignalingStrategy,\n iceServers?: RTCIceServer[],\n iceTransportPolicy?: RTCIceTransportPolicy,\n ) {\n this._strategy = strategy;\n this._peerId = strategy.selfId;\n this._iceConfig = buildICEConfig({ iceServers, iceTransportPolicy });\n }\n\n // ── CarverTransport getters ──\n\n get peerId(): string { return this._peerId; }\n get peers(): ReadonlySet<string> { return this._peerSet; }\n get hostId(): string { return this._hostId; }\n get isHost(): boolean { return this._isHost; }\n get room(): Room | undefined { return this._room ?? undefined; }\n get initialPlayers(): Player[] { return this._initialPeers; }\n\n // ── Event registration ──\n\n onPeerJoin(cb: (peerId: string) => void): void { this._callbacks.onPeerJoin.push(cb); }\n onPeerLeave(cb: (peerId: string) => void): void { this._callbacks.onPeerLeave.push(cb); }\n onPeerUpdated(cb: (player: Player) => void): void { this._callbacks.onPeerUpdated.push(cb); }\n onRoomUpdated(cb: (room: Room) => void): void { this._roomUpdatedCallbacks.push(cb); }\n onHostChanged(cb: (newHostId: string) => void): void { this._callbacks.onHostChanged.push(cb); }\n\n // ── Channel management ──\n\n createChannel<T>(name: string, options?: ChannelOptions): CarverChannel<T> {\n // Idempotent: return existing channel if already created\n const existing = this._channels.get(name);\n if (existing) {\n return {\n send: (data: T, target?: string | string[]) => this._sendOnChannel(name, data, target),\n onReceive: (cb: (data: T, peerId: string) => void) => { existing.receivers.push(cb); },\n close: () => { this._channels.delete(name); },\n };\n }\n\n const state: ChannelState<T> = {\n name,\n options: options ?? { reliable: true, ordered: true },\n receivers: [],\n };\n this._channels.set(name, state);\n\n // Create data channels on existing peers if already connected\n if (this._connected) {\n for (const peer of this._peers.values()) {\n this._createDataChannelOnPeer(peer, name, state.options);\n }\n }\n\n return {\n send: (data: T, target?: string | string[]) => this._sendOnChannel(name, data, target),\n onReceive: (cb: (data: T, peerId: string) => void) => { state.receivers.push(cb); },\n close: () => { this._channels.delete(name); },\n };\n }\n\n // ── Connect / Disconnect ──\n\n async connect(roomId: string, config?: TransportConfig): Promise<void> {\n // Override ICE config if user passed custom servers\n if (config?.iceServers) {\n this._iceConfig = buildICEConfig({\n iceServers: config.iceServers,\n iceTransportPolicy: config.iceTransportPolicy,\n });\n }\n\n // Pre-register ALL standard channels so the initiator includes them\n // in the initial WebRTC offer. Channels created after the peer connection\n // is established won't get a proper data channel on the remote side.\n this._setupRoomControlChannel();\n this._preRegisterChannel('carver:events', { reliable: true, ordered: true });\n this._preRegisterChannel('carver:snapshots', { reliable: false, ordered: false });\n this._preRegisterChannel('carver:acks', { reliable: true, ordered: true });\n this._preRegisterChannel('carver:inputs', { reliable: true, ordered: true });\n this._preRegisterChannel('carver:network-state', { reliable: true, ordered: true });\n\n // Bind strategy callbacks (store unsubs for cleanup)\n this._strategyUnsubs.push(\n this._strategy.onPeerDiscovered((peerId, meta) => {\n this._onStrategyPeerDiscovered(peerId, meta);\n }),\n );\n this._strategyUnsubs.push(\n this._strategy.onPeerLeft((peerId) => {\n this._removePeer(peerId);\n this._playerMap.delete(peerId);\n this._electAndSetHost();\n for (const cb of this._callbacks.onPeerLeave) cb(peerId);\n }),\n );\n this._strategyUnsubs.push(\n this._strategy.onSignal((fromPeerId, data) => {\n this._handleSignal(fromPeerId, data);\n }),\n );\n\n // Join room via strategy (publishes presence, subscribes to room)\n await this._strategy.joinRoom(roomId, {\n displayName: config?.displayName,\n ...(config?.playerMetadata ?? {}),\n });\n\n // Create self Player\n const selfPlayer: Player = {\n peerId: this._peerId,\n displayName: config?.displayName ?? `Player-${this._peerId.slice(0, 4)}`,\n isHost: false,\n isSelf: true,\n isReady: false,\n isConnected: true,\n metadata: config?.playerMetadata ?? {},\n latencyMs: 0,\n joinedAt: Date.now(),\n };\n this._playerMap.set(this._peerId, selfPlayer);\n\n // Elect host (may just be us if we're the first in the room)\n this._electAndSetHost();\n\n // Create initial Room object\n this._room = {\n id: roomId,\n name: roomId,\n hostId: this._hostId,\n playerCount: this._playerMap.size,\n maxPlayers: config?.maxPlayers ?? 8,\n isPrivate: false,\n metadata: {},\n createdAt: Date.now(),\n state: 'lobby',\n };\n\n this._initialPeers = Array.from(this._playerMap.values());\n this._connected = true;\n }\n\n disconnect(): void {\n this._connected = false;\n\n // Unsubscribe from strategy callbacks\n for (const unsub of this._strategyUnsubs) unsub();\n this._strategyUnsubs = [];\n\n // Close all peer connections\n for (const peer of this._peers.values()) peer.close();\n this._peers.clear();\n this._peerSet.clear();\n this._channels.clear();\n this._rateLimitCounters.clear();\n this._playerMap.clear();\n\n // Leave room via strategy (don't destroy -- provider manages lifecycle)\n this._strategy.leaveRoom().catch(() => {});\n\n this._hostId = '';\n this._isHost = false;\n this._room = null;\n }\n\n /** Expose strategy for lobby hooks */\n get strategy(): SignalingStrategy { return this._strategy; }\n\n // ── Channel pre-registration ──\n\n /**\n * Register a channel name and options without creating data channels yet.\n * When _connectToPeer runs, it iterates this._channels and creates data\n * channels for every registered name in the initial WebRTC offer.\n * Later, when EventSync/SnapshotSync call createChannel(), the idempotent\n * check returns the pre-registered entry and they just attach receivers.\n */\n private _preRegisterChannel(name: string, options: ChannelOptions): void {\n if (this._channels.has(name)) return;\n this._channels.set(name, { name, options, receivers: [] });\n }\n\n // ── Room management (over WebRTC data channels) ──\n\n setReady(ready: boolean): void {\n this._sendControlMessage({ type: 'request-ready', ready });\n }\n\n setMetadata(metadata: Record<string, unknown>): void {\n this._sendControlMessage({ type: 'request-metadata', metadata });\n }\n\n setRoomMetadata(metadata: Record<string, unknown>): void {\n if (!this._isHost) return;\n this._sendControlMessage({ type: 'request-room-metadata', metadata });\n }\n\n kick(peerId: string, reason?: string): void {\n if (!this._isHost) return;\n // Broadcast kick so the target peer and everyone else knows\n this._broadcastControlMessage({ type: 'kick', peerId, reason });\n }\n\n transferHost(peerId: string): void {\n if (!this._isHost) return;\n this._sendControlMessage({ type: 'request-transfer-host', peerId });\n }\n\n setRoomState(state: RoomState): void {\n if (!this._isHost) return;\n this._sendControlMessage({ type: 'request-room-state', state });\n }\n\n setMaxPlayers(n: number): void {\n if (!this._isHost) return;\n this._sendControlMessage({ type: 'request-max-players', maxPlayers: n });\n }\n\n lockRoom(): void {\n if (!this._isHost) return;\n this._sendControlMessage({ type: 'request-lock' });\n }\n\n unlockRoom(): void {\n if (!this._isHost) return;\n this._sendControlMessage({ type: 'request-unlock' });\n }\n\n /** No-op: lobby uses strategy.subscribeToLobby() directly */\n requestRoomList(): void {}\n\n // ── Private: Strategy callbacks ──\n\n private _onStrategyPeerDiscovered(peerId: string, meta: PeerMetadata): void {\n this._connectToPeer(peerId);\n this._peerSet.add(peerId);\n\n const player: Player = {\n peerId,\n displayName: (meta.displayName as string) ?? `Player-${peerId.slice(0, 4)}`,\n isHost: false,\n isSelf: false,\n isReady: false,\n isConnected: true,\n metadata: meta as Record<string, unknown>,\n latencyMs: 0,\n joinedAt: Date.now(),\n };\n this._playerMap.set(peerId, player);\n this._electAndSetHost();\n\n for (const cb of this._callbacks.onPeerJoin) cb(peerId);\n for (const cb of this._callbacks.onPeerUpdated) cb(player);\n }\n\n // ── Private: Room control channel ──\n\n private _setupRoomControlChannel(): void {\n const ch = this.createChannel<RoomControlMessage>(ROOM_CONTROL_CHANNEL, {\n reliable: true,\n ordered: true,\n });\n ch.onReceive((msg, peerId) => {\n this._handleControlMessage(msg, peerId);\n });\n }\n\n private _handleControlMessage(msg: RoomControlMessage, fromPeerId: string): void {\n switch (msg.type) {\n case 'player-updated': {\n this._playerMap.set(msg.player.peerId, msg.player);\n for (const cb of this._callbacks.onPeerUpdated) cb(msg.player);\n break;\n }\n case 'room-updated': {\n if (this._room) {\n Object.assign(this._room, msg.room);\n for (const cb of this._roomUpdatedCallbacks) cb(this._room);\n }\n break;\n }\n case 'kick': {\n if (msg.peerId === this._peerId) {\n // We were kicked\n this.disconnect();\n }\n break;\n }\n case 'host-changed': {\n this._hostId = msg.newHostId;\n this._isHost = msg.newHostId === this._peerId;\n for (const cb of this._callbacks.onHostChanged) cb(msg.newHostId);\n break;\n }\n case 'sync-state': {\n // Full state sync from host (sent to newly connected peers)\n this._room = msg.room;\n for (const p of msg.players) {\n this._playerMap.set(p.peerId, { ...p, isSelf: p.peerId === this._peerId });\n for (const cb of this._callbacks.onPeerUpdated) cb(p);\n }\n for (const cb of this._roomUpdatedCallbacks) cb(msg.room);\n break;\n }\n\n // Host processes requests from peers\n case 'request-ready': {\n if (!this._isHost) break;\n const p = this._playerMap.get(fromPeerId);\n if (p) {\n p.isReady = msg.ready;\n this._broadcastControlMessage({ type: 'player-updated', player: p });\n }\n break;\n }\n case 'request-metadata': {\n if (!this._isHost) break;\n const pm = this._playerMap.get(fromPeerId);\n if (pm) {\n pm.metadata = { ...pm.metadata, ...msg.metadata };\n this._broadcastControlMessage({ type: 'player-updated', player: pm });\n }\n break;\n }\n case 'request-room-metadata': {\n if (!this._isHost || !this._room) break;\n this._room.metadata = { ...this._room.metadata, ...msg.metadata };\n this._broadcastControlMessage({ type: 'room-updated', room: this._room });\n break;\n }\n case 'request-room-state': {\n if (!this._isHost || !this._room) break;\n this._room.state = msg.state;\n this._strategy.updateRoomOccupancy?.(this._room.id, this._room.playerCount, this._room.state);\n this._broadcastControlMessage({ type: 'room-updated', room: this._room });\n break;\n }\n case 'request-max-players': {\n if (!this._isHost || !this._room) break;\n this._room.maxPlayers = msg.maxPlayers;\n this._broadcastControlMessage({ type: 'room-updated', room: this._room });\n break;\n }\n case 'request-lock': {\n if (!this._isHost || !this._room) break;\n (this._room as any).locked = true;\n this._broadcastControlMessage({ type: 'room-updated', room: this._room });\n break;\n }\n case 'request-unlock': {\n if (!this._isHost || !this._room) break;\n (this._room as any).locked = false;\n this._broadcastControlMessage({ type: 'room-updated', room: this._room });\n break;\n }\n case 'request-transfer-host': {\n if (!this._isHost) break;\n this._hostId = msg.peerId;\n this._isHost = false;\n this._broadcastControlMessage({ type: 'host-changed', newHostId: msg.peerId });\n break;\n }\n }\n }\n\n private _sendControlMessage(msg: RoomControlMessage): void {\n if (this._isHost && msg.type.startsWith('request-')) {\n // Host processes locally and broadcasts result\n this._handleControlMessage(msg, this._peerId);\n return;\n }\n // Non-host: send to host\n if (this._hostId && this._hostId !== this._peerId) {\n this._sendOnChannel(ROOM_CONTROL_CHANNEL, msg, this._hostId);\n }\n }\n\n private _broadcastControlMessage(msg: RoomControlMessage): void {\n this._sendOnChannel(ROOM_CONTROL_CHANNEL, msg);\n // Handle locally too so host updates its own state\n this._handleControlMessage(msg, this._peerId);\n }\n\n // ── Private: Host election ──\n\n private _electAndSetHost(): void {\n const allIds = [this._peerId, ...this._peerSet];\n const newHostId = electHost(allIds);\n const changed = newHostId !== this._hostId;\n this._hostId = newHostId;\n this._isHost = newHostId === this._peerId;\n\n for (const [id, p] of this._playerMap) {\n p.isHost = id === newHostId;\n }\n if (this._room) {\n this._room.hostId = newHostId;\n this._room.playerCount = this._playerMap.size;\n // Keep the lobby announcement's occupancy fresh (no-op on non-announcers)\n this._strategy.updateRoomOccupancy?.(this._room.id, this._room.playerCount, this._room.state);\n }\n\n if (changed) {\n for (const cb of this._callbacks.onHostChanged) cb(newHostId);\n }\n }\n\n // ── Private: WebRTC peer management ──\n\n /** Grace timers for transient ICE 'disconnected' states */\n private _disconnectTimers = new Map<string, ReturnType<typeof setTimeout>>();\n /** Sends queued while a data channel is not yet open: key = peerId\u0000channel */\n private _pendingSends = new Map<string, (string | ArrayBuffer | Uint8Array)[]>();\n\n private _connectToPeer(peerId: string): void {\n if (this._peers.has(peerId)) return;\n\n const peer = new PeerConnection(peerId, this._iceConfig, {\n onStateChange: (state) => {\n if (state === 'connected') {\n // ICE recovered — cancel any pending transient-disconnect teardown\n const timer = this._disconnectTimers.get(peerId);\n if (timer) {\n clearTimeout(timer);\n this._disconnectTimers.delete(peerId);\n }\n }\n if (state === 'connected' && this._isHost && this._room) {\n // Send full state sync to the new peer\n const syncMsg: RoomControlMessage = {\n type: 'sync-state',\n room: this._room,\n players: Array.from(this._playerMap.values()),\n };\n setTimeout(() => {\n this._sendOnChannel(ROOM_CONTROL_CHANNEL, syncMsg, peerId);\n }, 100);\n }\n if (state === 'failed') {\n this._teardownPeer(peerId);\n } else if (state === 'disconnected' && !this._disconnectTimers.has(peerId)) {\n // ICE 'disconnected' is frequently transient. Tearing down\n // instantly left half-alive sessions (old closures kept\n // receiving while sends routed to a replacement connection).\n // Grace period: only tear down if it doesn't recover.\n this._disconnectTimers.set(peerId, setTimeout(() => {\n this._disconnectTimers.delete(peerId);\n const p = this._peers.get(peerId);\n if (p && p.state !== 'connected') this._teardownPeer(peerId);\n }, 5000));\n }\n },\n onDataChannel: (channel) => {\n this._setupDataChannelReceiver(channel, peerId);\n },\n onIceCandidate: (candidate) => {\n this._strategy.signal(peerId, { type: 'ice-candidate', candidate: candidate.toJSON() });\n },\n });\n\n this._peers.set(peerId, peer);\n this._peerSet.add(peerId);\n\n // Deterministic initiator: lower peerId creates the offer\n if (this._peerId < peerId) {\n for (const [name, state] of this._channels) {\n this._createDataChannelOnPeer(peer, name, state.options);\n }\n peer.createOffer().then((offer) => {\n this._strategy.signal(peerId, { type: 'offer', sdp: offer });\n });\n }\n }\n\n private async _handleSignal(peerId: string, data: unknown): Promise<void> {\n try {\n const signal = data as {\n type: string;\n sdp?: RTCSessionDescriptionInit;\n candidate?: RTCIceCandidateInit;\n };\n\n let peer = this._peers.get(peerId);\n\n if (signal.type === 'offer') {\n if (!peer) {\n this._connectToPeer(peerId);\n peer = this._peers.get(peerId)!;\n }\n const answer = await peer.handleOffer(signal.sdp!);\n this._strategy.signal(peerId, { type: 'answer', sdp: answer });\n } else if (signal.type === 'answer' && peer) {\n await peer.handleAnswer(signal.sdp!);\n } else if (signal.type === 'ice-candidate' && peer) {\n await peer.addIceCandidate(signal.candidate!);\n }\n } catch (err) {\n if (typeof console !== 'undefined') {\n console.error('[CarverJS] Signal handling failed:', err);\n }\n }\n }\n\n // ── Private: Data channel helpers ──\n\n private _createDataChannelOnPeer(peer: PeerConnection, name: string, options: ChannelOptions): void {\n const channel = peer.createDataChannel(name, options);\n this._setupDataChannelReceiver(channel, peer.peerId);\n }\n\n private _setupDataChannelReceiver(dataChannel: RTCDataChannel, peerId: string): void {\n const channelName = dataChannel.label;\n dataChannel.onmessage = (event) => {\n if (!this._checkRateLimit(peerId)) return;\n const channelState = this._channels.get(channelName);\n if (!channelState) return;\n try {\n const data =\n typeof event.data === 'string' ? JSON.parse(event.data) : event.data;\n for (const receiver of channelState.receivers) receiver(data, peerId);\n } catch {\n // Ignore malformed messages\n }\n };\n // Flush any sends that were queued before this channel was usable\n dataChannel.onopen = () => this._flushPendingSends(peerId, channelName);\n if (dataChannel.readyState === 'open') this._flushPendingSends(peerId, channelName);\n }\n\n private _sendOnChannel<T>(channelName: string, data: T, target?: string | string[]): void {\n const serialized =\n typeof data === 'object' &&\n data !== null &&\n !(data instanceof ArrayBuffer) &&\n !(data instanceof Uint8Array)\n ? JSON.stringify(data)\n : data;\n\n const targets = target\n ? Array.isArray(target) ? target : [target]\n : Array.from(this._peers.keys());\n\n for (const pid of targets) {\n const peer = this._peers.get(pid);\n if (!peer) continue;\n const ch = peer.getDataChannel(channelName);\n if (ch?.readyState === 'open') {\n try { ch.send(serialized as string); } catch { /* closed between check and send */ }\n } else {\n // Channel not open yet (the answering side waits for ondatachannel).\n // Queue instead of silently dropping — flushed on channel open.\n const key = pid + '\u0000' + channelName;\n const q = this._pendingSends.get(key) ?? [];\n if (q.length < 200) q.push(serialized as string | ArrayBuffer | Uint8Array);\n this._pendingSends.set(key, q);\n }\n }\n }\n\n private _flushPendingSends(peerId: string, channelName: string): void {\n const key = peerId + '\u0000' + channelName;\n const q = this._pendingSends.get(key);\n if (!q || q.length === 0) return;\n const ch = this._peers.get(peerId)?.getDataChannel(channelName);\n if (ch?.readyState !== 'open') return;\n this._pendingSends.delete(key);\n for (const msg of q) {\n try { ch.send(msg as string); } catch { break; }\n }\n }\n\n private _teardownPeer(peerId: string): void {\n this._removePeer(peerId);\n this._playerMap.delete(peerId);\n this._electAndSetHost();\n for (const cb of this._callbacks.onPeerLeave) cb(peerId);\n }\n\n private _removePeer(peerId: string): void {\n const peer = this._peers.get(peerId);\n if (peer) { peer.close(); this._peers.delete(peerId); }\n this._peerSet.delete(peerId);\n this._rateLimitCounters.delete(peerId);\n const timer = this._disconnectTimers.get(peerId);\n if (timer) { clearTimeout(timer); this._disconnectTimers.delete(peerId); }\n for (const key of [...this._pendingSends.keys()]) {\n if (key.startsWith(peerId + '\u0000')) this._pendingSends.delete(key);\n }\n }\n\n private _checkRateLimit(peerId: string): boolean {\n const now = Date.now();\n let c = this._rateLimitCounters.get(peerId);\n if (!c || now >= c.resetAt) {\n c = { count: 0, resetAt: now + this._rateLimitConfig.windowMs };\n this._rateLimitCounters.set(peerId, c);\n }\n c.count++;\n return c.count <= this._rateLimitConfig.maxMessagesPerSecond;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SignalingStrategy, M as MqttStrategyConfig, P as PeerMetadata, R as RoomAnnouncement, F as FirebaseStrategyConfig } from './types-
|
|
1
|
+
import { S as SignalingStrategy, M as MqttStrategyConfig, P as PeerMetadata, R as RoomAnnouncement, F as FirebaseStrategyConfig } from './types-hNfCIBzj.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* MQTT-based signaling strategy.
|
|
@@ -37,6 +37,8 @@ declare class MqttStrategy implements SignalingStrategy {
|
|
|
37
37
|
leaveRoom(): Promise<void>;
|
|
38
38
|
signal(targetPeerId: string, data: unknown): void;
|
|
39
39
|
subscribeToLobby(cb: (rooms: RoomAnnouncement[]) => void): () => void;
|
|
40
|
+
private _lastAnnouncement;
|
|
41
|
+
updateRoomOccupancy(roomId: string, playerCount: number, state?: 'lobby' | 'playing' | 'ended'): void;
|
|
40
42
|
announceRoom(announcement: RoomAnnouncement): void;
|
|
41
43
|
removeRoomAnnouncement(roomId: string): void;
|
|
42
44
|
onPeerDiscovered(cb: (peerId: string, meta: PeerMetadata) => void): () => void;
|
|
@@ -80,6 +82,8 @@ declare class FirebaseStrategy implements SignalingStrategy {
|
|
|
80
82
|
private _onLobby;
|
|
81
83
|
private _knownPeers;
|
|
82
84
|
private _lobbyAnnounceTimer;
|
|
85
|
+
private _lastAnnouncement;
|
|
86
|
+
private _lobbyWired;
|
|
83
87
|
private _destroyed;
|
|
84
88
|
constructor(appId: string, config: FirebaseStrategyConfig);
|
|
85
89
|
init(): Promise<void>;
|
|
@@ -88,6 +92,7 @@ declare class FirebaseStrategy implements SignalingStrategy {
|
|
|
88
92
|
signal(targetPeerId: string, data: unknown): void;
|
|
89
93
|
subscribeToLobby(cb: (rooms: RoomAnnouncement[]) => void): () => void;
|
|
90
94
|
announceRoom(announcement: RoomAnnouncement): void;
|
|
95
|
+
updateRoomOccupancy(roomId: string, playerCount: number, state?: 'lobby' | 'playing' | 'ended'): void;
|
|
91
96
|
removeRoomAnnouncement(roomId: string): void;
|
|
92
97
|
onPeerDiscovered(cb: (peerId: string, meta: PeerMetadata) => void): () => void;
|
|
93
98
|
onPeerLeft(cb: (peerId: string) => void): () => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SignalingStrategy, M as MqttStrategyConfig, P as PeerMetadata, R as RoomAnnouncement, F as FirebaseStrategyConfig } from './types-
|
|
1
|
+
import { S as SignalingStrategy, M as MqttStrategyConfig, P as PeerMetadata, R as RoomAnnouncement, F as FirebaseStrategyConfig } from './types-hNfCIBzj.mjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* MQTT-based signaling strategy.
|
|
@@ -37,6 +37,8 @@ declare class MqttStrategy implements SignalingStrategy {
|
|
|
37
37
|
leaveRoom(): Promise<void>;
|
|
38
38
|
signal(targetPeerId: string, data: unknown): void;
|
|
39
39
|
subscribeToLobby(cb: (rooms: RoomAnnouncement[]) => void): () => void;
|
|
40
|
+
private _lastAnnouncement;
|
|
41
|
+
updateRoomOccupancy(roomId: string, playerCount: number, state?: 'lobby' | 'playing' | 'ended'): void;
|
|
40
42
|
announceRoom(announcement: RoomAnnouncement): void;
|
|
41
43
|
removeRoomAnnouncement(roomId: string): void;
|
|
42
44
|
onPeerDiscovered(cb: (peerId: string, meta: PeerMetadata) => void): () => void;
|
|
@@ -80,6 +82,8 @@ declare class FirebaseStrategy implements SignalingStrategy {
|
|
|
80
82
|
private _onLobby;
|
|
81
83
|
private _knownPeers;
|
|
82
84
|
private _lobbyAnnounceTimer;
|
|
85
|
+
private _lastAnnouncement;
|
|
86
|
+
private _lobbyWired;
|
|
83
87
|
private _destroyed;
|
|
84
88
|
constructor(appId: string, config: FirebaseStrategyConfig);
|
|
85
89
|
init(): Promise<void>;
|
|
@@ -88,6 +92,7 @@ declare class FirebaseStrategy implements SignalingStrategy {
|
|
|
88
92
|
signal(targetPeerId: string, data: unknown): void;
|
|
89
93
|
subscribeToLobby(cb: (rooms: RoomAnnouncement[]) => void): () => void;
|
|
90
94
|
announceRoom(announcement: RoomAnnouncement): void;
|
|
95
|
+
updateRoomOccupancy(roomId: string, playerCount: number, state?: 'lobby' | 'playing' | 'ended'): void;
|
|
91
96
|
removeRoomAnnouncement(roomId: string): void;
|
|
92
97
|
onPeerDiscovered(cb: (peerId: string, meta: PeerMetadata) => void): () => void;
|
|
93
98
|
onPeerLeft(cb: (peerId: string) => void): () => void;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { S as SignalingStrategy, a as StrategyConfig } from './types-
|
|
4
|
-
export { F as FirebaseStrategyConfig, M as MqttStrategyConfig, P as PeerMetadata, R as RoomAnnouncement } from './types-
|
|
5
|
-
import {
|
|
6
|
-
export {
|
|
7
|
-
export { F as FirebaseStrategy, M as MqttStrategy } from './firebase-
|
|
3
|
+
import { S as SignalingStrategy, a as StrategyConfig } from './types-hNfCIBzj.mjs';
|
|
4
|
+
export { F as FirebaseStrategyConfig, M as MqttStrategyConfig, P as PeerMetadata, R as RoomAnnouncement } from './types-hNfCIBzj.mjs';
|
|
5
|
+
import { r as NetworkManager, U as UseRoomOptions, s as ConnectionState, R as Room, t as CarverMultiplayerError, C as CarverTransport, u as UseLobbyOptions, v as RoomConfig, a as Player, d as RoomState, w as UseMultiplayerOptions, N as NetworkQuality, q as SyncMode, P as PlayerInput, E as EntityState } from './NetworkManager-D-DxFgdM.mjs';
|
|
6
|
+
export { c as CarverChannel, x as CarverErrorCode, b as ChannelOptions, m as EntityState2D, n as EntityState3D, l as ErrorOffset, o as EventPacket, I as InputPacket, J as JoinOptions, M as MultiplayerContextValue, j as PhysicsStepCallback, i as PredictionSyncOptions, k as PredictionWorldDriver, f as SnapshotListener, p as SnapshotPacket, g as SnapshotSource, T as TransportConfig } from './NetworkManager-D-DxFgdM.mjs';
|
|
7
|
+
export { F as FirebaseStrategy, M as MqttStrategy } from './firebase-GrbVrNgs.mjs';
|
|
8
|
+
export { I as InputBuffer } from './InputBuffer-J6XT_Tt0.mjs';
|
|
8
9
|
|
|
9
10
|
interface MultiplayerContextValue {
|
|
10
11
|
appId: string;
|
|
@@ -137,6 +138,8 @@ interface UseMultiplayerReturn {
|
|
|
137
138
|
serverTick: number;
|
|
138
139
|
drift: number;
|
|
139
140
|
syncEngine: SyncMode;
|
|
141
|
+
/** Set the local player's input (prediction mode). Stable callback; no-op in events/snapshot modes. */
|
|
142
|
+
setInput: (input: PlayerInput) => void;
|
|
140
143
|
}
|
|
141
144
|
declare function useMultiplayer(options?: UseMultiplayerOptions): UseMultiplayerReturn;
|
|
142
145
|
|
|
@@ -313,4 +316,22 @@ declare class InterestManager {
|
|
|
313
316
|
private _cellKey;
|
|
314
317
|
}
|
|
315
318
|
|
|
316
|
-
|
|
319
|
+
/**
|
|
320
|
+
* Input utilities for prediction mode.
|
|
321
|
+
* Ported from LumberNet's InputUtils.
|
|
322
|
+
*/
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Given the input state at the current tick and the previous tick, return a new
|
|
326
|
+
* input object where each boolean field is `true` only if it transitioned
|
|
327
|
+
* false -> true (a "rising edge" / "just pressed").
|
|
328
|
+
*
|
|
329
|
+
* Non-boolean fields are passed through unchanged from `curr`.
|
|
330
|
+
* Iterates `curr` keys only; keys present only in `prev` are absent from the result.
|
|
331
|
+
*
|
|
332
|
+
* PredictionSync calls this automatically and passes the result as `justPressed`
|
|
333
|
+
* to every onPhysicsStep callback, so games normally do not call it directly.
|
|
334
|
+
*/
|
|
335
|
+
declare function computeJustPressed<I extends PlayerInput>(curr: I, prev: I): I;
|
|
336
|
+
|
|
337
|
+
export { CarverMultiplayerError, CarverTransport, ConnectionState, DebugOverlay, type DebugOverlayOptions, type DebugStats, EntityState, InterestManager, type InterestManagerOptions, MultiplayerBridge, MultiplayerProvider, type MultiplayerProviderProps, NetworkQuality, NetworkSimulator, type NetworkSimulatorOptions, Player, PlayerInput, Room, RoomConfig, RoomState, SignalingStrategy, StrategyConfig, SyncMode, UseLobbyOptions, UseMultiplayerOptions, UseRoomOptions, computeJustPressed, useHost, useLobby, useMultiplayer, useNetworkEvents, useNetworkState, usePlayers, useRoom };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { S as SignalingStrategy, a as StrategyConfig } from './types-
|
|
4
|
-
export { F as FirebaseStrategyConfig, M as MqttStrategyConfig, P as PeerMetadata, R as RoomAnnouncement } from './types-
|
|
5
|
-
import {
|
|
6
|
-
export {
|
|
7
|
-
export { F as FirebaseStrategy, M as MqttStrategy } from './firebase-
|
|
3
|
+
import { S as SignalingStrategy, a as StrategyConfig } from './types-hNfCIBzj.js';
|
|
4
|
+
export { F as FirebaseStrategyConfig, M as MqttStrategyConfig, P as PeerMetadata, R as RoomAnnouncement } from './types-hNfCIBzj.js';
|
|
5
|
+
import { r as NetworkManager, U as UseRoomOptions, s as ConnectionState, R as Room, t as CarverMultiplayerError, C as CarverTransport, u as UseLobbyOptions, v as RoomConfig, a as Player, d as RoomState, w as UseMultiplayerOptions, N as NetworkQuality, q as SyncMode, P as PlayerInput, E as EntityState } from './NetworkManager-DH9uGVMg.js';
|
|
6
|
+
export { c as CarverChannel, x as CarverErrorCode, b as ChannelOptions, m as EntityState2D, n as EntityState3D, l as ErrorOffset, o as EventPacket, I as InputPacket, J as JoinOptions, M as MultiplayerContextValue, j as PhysicsStepCallback, i as PredictionSyncOptions, k as PredictionWorldDriver, f as SnapshotListener, p as SnapshotPacket, g as SnapshotSource, T as TransportConfig } from './NetworkManager-DH9uGVMg.js';
|
|
7
|
+
export { F as FirebaseStrategy, M as MqttStrategy } from './firebase-B5MgLlHk.js';
|
|
8
|
+
export { I as InputBuffer } from './InputBuffer-V7XfHbc6.js';
|
|
8
9
|
|
|
9
10
|
interface MultiplayerContextValue {
|
|
10
11
|
appId: string;
|
|
@@ -137,6 +138,8 @@ interface UseMultiplayerReturn {
|
|
|
137
138
|
serverTick: number;
|
|
138
139
|
drift: number;
|
|
139
140
|
syncEngine: SyncMode;
|
|
141
|
+
/** Set the local player's input (prediction mode). Stable callback; no-op in events/snapshot modes. */
|
|
142
|
+
setInput: (input: PlayerInput) => void;
|
|
140
143
|
}
|
|
141
144
|
declare function useMultiplayer(options?: UseMultiplayerOptions): UseMultiplayerReturn;
|
|
142
145
|
|
|
@@ -313,4 +316,22 @@ declare class InterestManager {
|
|
|
313
316
|
private _cellKey;
|
|
314
317
|
}
|
|
315
318
|
|
|
316
|
-
|
|
319
|
+
/**
|
|
320
|
+
* Input utilities for prediction mode.
|
|
321
|
+
* Ported from LumberNet's InputUtils.
|
|
322
|
+
*/
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Given the input state at the current tick and the previous tick, return a new
|
|
326
|
+
* input object where each boolean field is `true` only if it transitioned
|
|
327
|
+
* false -> true (a "rising edge" / "just pressed").
|
|
328
|
+
*
|
|
329
|
+
* Non-boolean fields are passed through unchanged from `curr`.
|
|
330
|
+
* Iterates `curr` keys only; keys present only in `prev` are absent from the result.
|
|
331
|
+
*
|
|
332
|
+
* PredictionSync calls this automatically and passes the result as `justPressed`
|
|
333
|
+
* to every onPhysicsStep callback, so games normally do not call it directly.
|
|
334
|
+
*/
|
|
335
|
+
declare function computeJustPressed<I extends PlayerInput>(curr: I, prev: I): I;
|
|
336
|
+
|
|
337
|
+
export { CarverMultiplayerError, CarverTransport, ConnectionState, DebugOverlay, type DebugOverlayOptions, type DebugStats, EntityState, InterestManager, type InterestManagerOptions, MultiplayerBridge, MultiplayerProvider, type MultiplayerProviderProps, NetworkQuality, NetworkSimulator, type NetworkSimulatorOptions, Player, PlayerInput, Room, RoomConfig, RoomState, SignalingStrategy, StrategyConfig, SyncMode, UseLobbyOptions, UseMultiplayerOptions, UseRoomOptions, computeJustPressed, useHost, useLobby, useMultiplayer, useNetworkEvents, useNetworkState, usePlayers, useRoom };
|