Configuration Reference
Complete reference for every configurable option in the CarverJS multiplayer system. All options have sensible defaults — you only need to override what you want to change.
import { useMultiplayer, useRoom } from "@carverjs/multiplayer";useMultiplayer Options #
Pass these options to useMultiplayer() to control synchronization behaviour.
const multiplayer = useMultiplayer({
mode: "prediction",
tickRate: 60,
broadcastRate: 20,
keyframeInterval: 60,
quantize: { position: 0.01 },
prediction: { errorDecay: 0.85 },
interpolation: { delay: 100 },
});Top-Level Options #
| Option | Type | Default | Description |
|---|---|---|---|
mode | 'events' | 'snapshot' | 'prediction' | 'snapshot' | Sync strategy. events sends only inputs; snapshot sends full state; prediction adds client-side prediction and reconciliation |
tickRate | number | 60 | Fixed-timestep simulation rate in Hz. Higher = more precise physics but more CPU |
broadcastRate | number | 20 | How often the host sends state updates per second. Independent of tickRate |
keyframeInterval | number | 60 | Number of ticks between full keyframe snapshots. Between keyframes, only deltas are sent |
quantize | QuantizeOptions | — | Reduce floating-point precision to save bandwidth |
deltaThresholds | DeltaThresholds | — | Minimum change required before a field is included in a delta update |
prediction | PredictionSyncOptions | — | Full-world prediction and rollback settings (see below) |
interpolation | InterpolationOptions | — | How remote entities are smoothed between updates |
interestManagement | InterestManagementOptions | — | Area-of-interest filtering for large worlds |
debug | DebugOptions | — | Debug overlay and network simulation tools |
stepWorld | () => void | — | Steps the physics world one fixed tick. Used for both forward simulation and rollback resimulation in prediction mode |
onPhysicsStep | PhysicsStepCallback | — | (inputs, justPressed, tick, isRollback, dt) — simulation callback invoked once per fixed tick and once per resimulated rollback tick. Required for prediction mode |
QuantizeOptions #
Quantization rounds values to a fixed step size, reducing the number of bits needed on the wire.
| Field | Type | Default | Description |
|---|---|---|---|
position | number | — | Step size for position axes. 0.01 = centimetre precision |
rotation | number | — | Step size for rotation (radians). 0.001 is typically sufficient |
velocity | number | — | Step size for linear/angular velocity |
quantize: {
position: 0.01, // ~1cm accuracy
rotation: 0.001, // ~0.06° accuracy
velocity: 0.05,
}Quantization is one of the cheapest ways to reduce bandwidth. A position quantized to 0.01 is visually indistinguishable from full float64 in most games.
DeltaThresholds #
Only include a field in the delta if it changed by more than the threshold since the last broadcast.
| Field | Type | Default | Description |
|---|---|---|---|
position | number | 0.001 | Minimum positional change (world units) |
rotation | number | 0.001 | Minimum rotational change (radians) |
velocity | number | 0.01 | Minimum velocity change |
deltaThresholds: {
position: 0.001,
rotation: 0.001,
velocity: 0.01,
}PredictionSyncOptions #
Controls full-world prediction and rollback. Only relevant when mode is 'prediction'.
| Field | Type | Default | Description |
|---|---|---|---|
maxRewindTicks | number | 15 | Max drift (ticks) between the local tick and serverTick + driftTargetTicks before the client hard-snaps its tick instead of resimulating |
snapThreshold | number | 150 | Per-axis position jump (world units) above which rollback visual correction is suppressed — intentional teleports stay instant |
errorDecay | number | 0.85 | Multiplicative decay applied to per-entity visual error offsets each render frame |
maxErrorPerFrame | number | 0 | Maximum positional correction (units) applied per render frame. 0 disables the cap (the full decaying error is applied each frame) |
neutralInput | PlayerInput | {} | Fallback input payload used for unknown ticks or peers |
inputHistorySize | number | 120 | Tick-history ring size for local and per-peer inputs (about 2 seconds at 60 Hz) |
driftTargetTicks | number | 4 | Rollback snap target offset: snap target = serverTick + driftTargetTicks |
prediction: {
maxRewindTicks: 15,
snapThreshold: 150,
errorDecay: 0.85,
maxErrorPerFrame: 0,
neutralInput: {},
inputHistorySize: 120,
driftTargetTicks: 4,
}Prediction mode requires onPhysicsStep so the engine can run the simulation forward and replay inputs during rollback. The game must also drive physics stepping deterministically — either pass stepWorld or step the world inside onPhysicsStep. Auto-stepping physics outside the network tick loop is incompatible with rollback resimulation.
The prediction options were rebuilt around the full-world rollback model:
errorSmoothingDecaywas renamed toerrorDecay.lagCompensationwas removed.maxRewindTicks,snapThreshold, andmaxErrorPerFramechanged semantics — see the table above for the new meanings and defaults.onPhysicsStepgainedjustPressedas its 2nd parameter anddtas its 5th:(inputs, justPressed, tick, isRollback, dt).
InterpolationOptions #
Controls how remote (non-local) entities are smoothed between network updates.
| Field | Type | Default | Description |
|---|---|---|---|
delay | number | 100 | Interpolation delay in milliseconds. Higher = smoother but more latency |
maxExtrapolation | number | 200 | Maximum time (ms) to extrapolate beyond the last received state before freezing |
method | 'linear' | 'hermite' | 'linear' | Interpolation curve. Hermite produces smoother motion for accelerating objects |
interpolation: {
delay: 100,
maxExtrapolation: 200,
method: "hermite",
}InterestManagementOptions #
Area-of-interest filtering. Only entities within range of the player are synchronized, dramatically reducing bandwidth in large worlds.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable spatial interest management |
cellSize | number | 50 | Spatial hash cell size in world units |
viewDistance | number | 200 | Maximum distance (world units) at which entities are synced to a client |
hysteresis | number | 20 | Buffer zone to prevent entities flickering in/out at the boundary |
interestManagement: {
enabled: true,
cellSize: 50,
viewDistance: 200,
hysteresis: 20,
}DebugOptions #
Developer tools for visualizing and simulating network conditions.
| Field | Type | Default | Description |
|---|---|---|---|
overlay | boolean | false | Show the on-screen debug overlay with live stats |
simulatedLatencyMs | number | 0 | Artificial one-way latency added to every message (ms) |
simulatedPacketLoss | number | 0 | Fraction of packets to randomly drop (0–1). 0.05 = 5% loss |
logLevel | 'none' | 'error' | 'warn' | 'info' | 'debug' | 'warn' | Console log verbosity |
debug: {
overlay: true,
simulatedLatencyMs: 80,
simulatedPacketLoss: 0.02,
logLevel: "info",
}useRoom Options #
Pass these options as the second argument to useRoom(roomId?, options?) — the first argument is the room to auto-join, or undefined to join later with room.join(id).
const room = useRoom("room-123", {
displayName: "Player1",
password: "secret",
hostMigration: true,
reconnectAttempts: 5,
privacy: "relay",
});| Option | Type | Default | Description |
|---|---|---|---|
transport | CarverTransport | -- | Pass a custom CarverTransport instance to bypass the built-in WebRTCTransport |
password | string | -- | Room password. Joining peers must provide the same value |
displayName | string | -- | Human-readable name shown in the player list |
playerMetadata | Record<string, unknown> | -- | Arbitrary metadata attached to this player (avatar, team, skin, etc.) |
iceServers | RTCIceServer[] | Provider defaults | Custom STUN/TURN servers for this room (overrides provider-level config) |
hostMigration | boolean | true | Automatically elect a new host when the current host disconnects |
reconnectAttempts | number | 3 | Number of automatic reconnection attempts on disconnect |
reconnectIntervalMs | number | 2000 | Delay between reconnection attempts (ms) |
privacy | 'all' | 'relay' | 'all' | Set to 'relay' to force all traffic through TURN servers (hides player IP addresses) |
onConnected | () => void | -- | Callback when successfully connected to the room |
onDisconnected | (reason: string) => void | -- | Callback when disconnected from the room |
onHostMigration | (newHostId: string) => void | -- | Callback when the host changes |
onError | (error: CarverMultiplayerError) => void | -- | Callback for any multiplayer error |
Signaling Strategy Options #
Pass a strategy config to <MultiplayerProvider strategy={...}> to choose how peers discover each other. Defaults to { type: 'mqtt' } — free public brokers, zero configuration. The Firebase RTDB strategy takes the options below.
FirebaseStrategyConfig #
<MultiplayerProvider
appId="my-game"
strategy={{
type: "firebase",
databaseURL: "https://my-project.firebaseio.com",
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authTokenProvider: () => fetchCustomTokenFromYourBackend(),
onAuthError: (error) => showReconnectPrompt(error),
}}
>| Field | Type | Default | Description |
|---|---|---|---|
type | 'firebase' | — | Required. Selects the Firebase RTDB signaling strategy |
databaseURL | string | — | Required. Realtime Database URL. Used to initialize a namespaced Firebase app unless firebaseApp is supplied |
firebaseApp | FirebaseApp | — | An already-configured Firebase app instance. Avoids double-init when your game uses Firebase elsewhere |
authTokenProvider | () => Promise<string> | — | Returns a Firebase Auth custom token, minted by your own backend. Omit it and the strategy signals anonymously and never imports firebase/auth. Must return a fresh token on every call |
apiKey | string | — | Firebase Web API key. Required when authTokenProvider is set and no firebaseApp is supplied — Auth cannot sign in on an app initialized with databaseURL alone. A public project identifier, not a secret, but it must arrive through config and never be hardcoded in game source |
onAuthError | (error: Error) => void | — | Called when authentication fails in a way the strategy cannot recover from on its own. See Authentication Errors |
Signaling paths are written under ${appId}/__carver__/.... An appId containing a slash simply nests deeper, which is how you scope security rules per namespace.
The custom token expires in an hour, but the session does not: signInWithCustomToken exchanges the token for a refresh token that the SDK renews indefinitely with the same claims. Ending access when a game session ends is a server responsibility — revoke refresh tokens for that uid, or mint a time-bound claim your rules check. Security rules cannot see this on their own. See Authentication for the full claims contract.
Networked Config Prop Reference #
The config prop on <Networked> controls per-entity sync behaviour.
<Networked id="player-1" config={{ sync: "transform", owner: peerId, interpolate: true }}>
<Actor type="primitive" shape="box" />
</Networked>| Field | Type | Default | Description |
|---|---|---|---|
sync | 'transform' | 'rigid-body' | 'custom' | 'transform' | What data is synchronized. transform syncs position/rotation/scale; rigid-body adds velocity and angular velocity; custom sends only what you provide |
owner | string | host peer ID | Peer ID that has authority over this entity. Only the owner can write state |
custom | Record<string, unknown> | — | Arbitrary key-value pairs synced alongside transform. Useful for health, score, animation state |
interpolate | boolean | true | Whether remote copies of this entity use interpolation. Disable for instant-snap objects like UI cursors |
Debug Tools #
DebugOverlay #
Enable the on-screen overlay to see live network statistics:
const multiplayer = useMultiplayer({
debug: { overlay: true },
});The overlay displays:
RTT — round-trip time to each peer (ms)
Bandwidth — inbound/outbound bytes per second
Tick — current simulation tick and drift
Entities — total synced entity count
Packet loss — detected packet loss percentage
Toggle the overlay at runtime by pressing F3.
Network Simulator #
Inject artificial latency and packet loss for testing poor network conditions:
debug: {
simulatedLatencyMs: 150, // 150ms one-way delay
simulatedPacketLoss: 0.05, // 5% random packet drop
}Test with simulatedLatencyMs: 200 and simulatedPacketLoss: 0.1 to approximate a poor mobile connection. If your game still feels playable, it will work well on most real networks.
Error Codes #
Errors delivered to useRoom's onError use CarverError with a code field from the CarverErrorCode enum. Firebase signaling authentication takes a separate path — see the uncoded row in the table and Authentication Errors below.
| Code | Meaning | Common Cause | Recovery |
|---|---|---|---|
ROOM_NOT_FOUND | The room ID does not exist | Typo in room ID, or room expired | Verify the room ID and retry |
ROOM_FULL | Room has reached max players | All player slots are taken | Show "room full" UI, retry later |
ROOM_LOCKED | Room is locked by the host | Host called room.lock() | Inform the user, wait for unlock |
INVALID_PASSWORD | Wrong room password | User entered incorrect password | Prompt for correct password |
CONNECTION_FAILED | Could not establish a connection | Firewall, NAT, or network issue — also a Firebase custom-token sign-in that failed every retry, which rejects the join | Retry, or set privacy: 'relay' to force TURN |
HOST_UNREACHABLE | Cannot reach the room host | Host went offline without migration | Wait for host migration, or rejoin |
KICKED | Kicked from the room by the host | Host called room.kick(peerId) | Show "kicked" message to the user |
SIGNALING_ERROR | Signaling strategy error | MQTT broker or Firebase unreachable | Check network, retry after a delay |
| (no code) | Firebase signaling authentication failure | Token provider or sign-in failed, a write or listener came back permission_denied, or a listener was cancelled by the server | Not delivered as a CarverMultiplayerError — handle it in the strategy's onAuthError. See Authentication Errors |
TURN_CREDENTIAL_ERROR | TURN server credential failure | Expired or invalid TURN credentials | Refresh credentials and retry |
TRANSPORT_ERROR | Low-level transport failure | WebRTC data channel closed unexpectedly | Automatic reconnection will attempt recovery |
MIGRATION_FAILED | Host migration did not complete | All candidate hosts disconnected simultaneously | Rejoin or create a new room |
const room = useRoom("room-123", {
onError: (error) => {
switch (error.code) {
case "ROOM_FULL":
showToast("Room is full. Try again later.");
break;
case "INVALID_PASSWORD":
promptPassword();
break;
default:
console.error(`[Carver] ${error.code}: ${error.message}`);
}
},
});Authentication Errors #
Firebase signaling auth failures are reported through the strategy's own onAuthError callback, not as a CarverErrorCode. There is no auth code to switch on in useRoom's onError:
<MultiplayerProvider
appId="my-game"
strategy={{
type: "firebase",
databaseURL: "https://my-project.firebaseio.com",
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authTokenProvider: () => fetchCustomTokenFromYourBackend(),
onAuthError: (error) => {
console.error(error.message); // context + the RTDB error, kept as `cause`
rejoinRoom();
},
}}
>onAuthError receives a plain Error — not a CarverMultiplayerError — naming the operation that failed and carrying the underlying RTDB error as cause. It fires in three situations:
| Situation | Strategy behaviour | What your app should do |
|---|---|---|
The token provider or sign-in failed during init() | The pair is retried 4 times in total (250ms, 1s, 4s backoff), then init() rejects. A failed init is not cached, so a later join retries from scratch | Surface a connection error. The same failure also rejects the join, which useRoom reports as CONNECTION_FAILED |
| A re-auth cycle failed | A permission_denied on the presence re-arm triggers exactly one re-auth cycle per connection. A further denial ends it; a new connection earns a fresh attempt | Treat the room as unreachable for this player and rejoin |
| A signaling listener was cancelled | One re-auth cycle runs so writes can recover, but the listener is not re-subscribed | Rejoin the room — the only way to restore peer discovery |
Type Definitions #
See Types for all type definitions including UseMultiplayerOptions, UseRoomOptions, NetworkedConfig, QuantizeOptions, PredictionSyncOptions, PlayerInput, PhysicsStepCallback, InterpolationOptions, InterestManagementOptions, DebugOptions, CarverError, and CarverErrorCode.