
AuraJS currently exposes two different online surfaces:
aura.net for lower-level transport/HTTP-style workaura.multiplayer for higher-level session/state/input replicationEnable with:
modules.network = trueNamespace methods:
connect(transport, host, port)websocket(url)fetch(url, options?)get(url, options?)post(url, bodyOrOptions?)Connection objects returned by connect(...) and websocket(...) expose a callback-oriented surface with methods such as:
send(...)onMessage(callback)onDisconnect(callback)close()Practical notes:
onDisconnect listeners are intended to fire once per close transition.optional_module_net_disabled guidance.ws:// websocket URLs. The source still has rough edges around wss://, so treat secure websocket support as something to verify against current host behavior before relying on it.Enable with:
modules.multiplayer = trueThe simplified API is the recommended way to use multiplayer:
// Host: create a room. Auto room code, auto internet mode.
const room = aura.multiplayer.host({ maxPlayers: 4 });
// room.code = "ABCD" (auto-generated 4-char unambiguous code)
// Client: join by room code. Tries local, then internet automatically.
aura.multiplayer.join("ABCD");
When maxPlayers is set without an explicit internetMode, the runtime
auto-infers internetMode: "auto" and resolves platform-default relay URLs.
No manual URLs or room codes are needed for the common case.
In scaffolded projects, the same room-code model is exposed through the generated wrapper:
npm run dev
npm run join -- ABCD
Treat that wrapper as the default local-first dev path. The current
local-multiplayer starter, also exposed as the multiplayer template alias,
defaults to local-first hosting, but the same npm run dev + npm run join -- CODE
flow upgrades to internet-backed hosting when you either:
aura.config.json -> multiplayer.relay = "relay.aurajs.gg"AURA_MULTIPLAYER_RELAY_HOST=relay.aurajs.ggIf a relay host or explicit relay URLs are present, the starter auto-promotes
hosting to internet-backed auto mode. mode: "relay" is the advanced opt-in
when you want to skip direct UDP and force relay transport.
If you also set aura.config.json -> multiplayer.launcherBaseUrl, the starter
can surface a web join page like https://auralauncher.gg/join/AURA2P while
keeping the same npm run dev + npm run join -- CODE runtime flow.
If you want the smallest copyable example with host and join in one project,
use examples/multiplayer-party.
It keeps the same room-code model and adds diagnostics plus room chat.
AuraJS now has three multiplayer read paths on purpose:
getState() / getAllState() are the raw authoritative replicated snapshotgetInterpolatedState() / getInterpolatedAllState() are additive client
render helpersgetRollbackState() / getRollbackAllState() are rollback-aware gameplay
reads for configured supported lanesUse the raw state when you need canonical replicated truth. Use the interpolated helpers when a client wants smoother rendering. Use the rollback helpers when the client has explicitly configured a supported rollback lane. On the host, the additive helper reads resolve back to authoritative state.
onStateUpdate(function (snapshot, metadata) { ... }) now receives additive
metadata with:
sequenceserverTickserverTimeMstickIntervalMsinputAckTickssourcejitterMsbufferDelayMshistoryDepthbufferedServerTimeMssource: "transfer" marks a continuity boundary such as migration/state
transfer and should snap rather than smear.
Current truthful limit:
configureRollbackLane, getRollbackState, getRollbackAllState,
clearRollbackLane, and getRollbackDiagnostics are now the shipped
supported rollback lane for runtime-owned gameplay state.configureLocalPrediction, setLocalPredictedState, and
getLocalPredictionDiagnostics remain available as the lighter Stage 173
overlay lane for authored opt-in overlays that do not use the supported
rollback lane.getRoomInfo().transportStatus and getNetworkDiagnostics() now surface
reason-coded recovery states such as resume_pending, resumed, and
explicit fallback/error transitions.configureNetworkImpairment() / clearNetworkImpairment() provide
deterministic latency, jitter, loss, and short-outage hooks for tests.../../multiplayer-internet-hardening-and-rollback-grade-feel-contract-v1.md.Minimal host-authoritative loop:
aura.multiplayer.configure({ maxPlayers: 2, tickRate: 20 });
function startHost() {
return aura.multiplayer.host({
roomCode: "AURA2P",
maxPlayers: 2,
internetMode: "local", // switch to "auto" or "relay" for internet-backed rooms
});
}
function hostTick(localInput) {
aura.multiplayer.setState("player_0", stepPlayer(aura.multiplayer.getState("player_0"), localInput));
const inputs = aura.multiplayer.getAllPlayerInputs() || {};
for (const [playerId, input] of Object.entries(inputs)) {
const key = `player_${playerId}`;
aura.multiplayer.setState(key, stepPlayer(aura.multiplayer.getState(key), input));
}
}
function clientTick(input) {
if (aura.multiplayer.isConnected()) {
aura.multiplayer.sendInput(input);
}
}
function draw() {
const allPlayers = aura.multiplayer.isHost()
? (aura.multiplayer.getAllState() || {})
: (aura.multiplayer.getInterpolatedAllState()
|| aura.multiplayer.getAllState()
|| {});
// draw shared state here
}
TL;DR:
configure(...) oncehost({ roomCode, maxPlayers, internetMode })npm run join -- CODE or aura.multiplayer.join("CODE")getAllPlayerInputs() and publishes authoritative state with setState(...)getState() / getAllState()getInterpolatedState() /
getInterpolatedAllState() for drawStarter TL;DR:
auramaxx create my-room-game --template multiplayer
cd my-room-game
npm run dev
# second terminal
npm run join -- AURA2P
Fastest internet-backed upgrade for that starter:
{
"multiplayer": {
"relay": "relay.aurajs.gg",
"launcherBaseUrl": "https://auralauncher.gg"
}
}
Or keep the project untouched and launch with:
AURA_MULTIPLAYER_RELAY_HOST=relay.aurajs.gg npm run dev
AURA_MULTIPLAYER_RELAY_HOST=relay.aurajs.gg npm run join -- AURA2P
If launcherBaseUrl is configured, the starter HUD can also show a shareable
join page:
https://auralauncher.gg/join/AURA2P
Room codes are 4-character strings from an unambiguous alphabet
(A-Z excluding O/I/L, 2-9 excluding 0/1). If a specific code is needed,
pass roomCode explicitly:
const room = aura.multiplayer.host({ maxPlayers: 4, roomCode: "MYROOM" });
Platform defaults:
tcp://relay.auramaxx.com:43110tcp://relay.auramaxx.com:43111Override with environment variables:
AURA_COORDINATOR_URL=tcp://myhost:43110AURA_RELAY_URL=tcp://myhost:43111Override with explicit options:
aura.multiplayer.host({
maxPlayers: 4,
coordinatorUrl: "tcp://myhost:43110",
relayUrl: "tcp://myhost:43111",
});
Relay shorthand expands a hostname to both ports:
aura.multiplayer.host({ maxPlayers: 4, relay: "mygame.example.com" });
// Equivalent to coordinatorUrl: "tcp://mygame.example.com:43110",
// relayUrl: "tcp://mygame.example.com:43111"
Precedence: explicit option > relay shorthand > env var > platform default.
join("CODE") with no internet options follows this resolution order:
getRoomInfo().joinPath reports how the room was resolved:
"local" -- found via local registry"internet_fallback" -- local lookup failed, found via coordinator"internet_explicit" -- developer specified internet options"direct" -- developer used join(host, port) direct-connect pathconfigure(options) -- set maxPlayers (2--16), tickRate, protocolprotocol: "tcp" keeps reliable state/messages/input on the reliable laneprotocol: "udp" / "both" only switch local/direct gameplay input to UDP
after room metadata reports transportStatus: "udp_input_ready"host(portOrOptions) -- create a room (returns room metadata object)join(codeOrHost, portOrOptions?) -- join a room by code or direct addressleave() -- disconnect from the session (client)stop() -- stop hosting and disconnect all clients (host)isHost() / isClient() / isConnected()getLocalId() -- this player's unique ID (0 = host)getPlayers() -- [{ id, name, rtt, connected, isHost, transport }]getPlayerCount() -- number of connected players including hostgetPing() -- local player's RTT in ms (0 for host)getServerTime() -- synchronized clock (ms since session start)getRoomInfo() -- current room/session metadata (see below)isMigrating() -- true during host migration grace periodgetInviteCode() -- current room code string or nullgetInviteLink() -- "aurajs://join/CODE" URI or nullcopyInviteLink() -- copy invite URI to clipboard; returns booleansend(targetId, type, payload) -- send a message to one playerbroadcast(type, payload) -- send a message to all playerssendInput(payload) -- client sends input snapshot to hostgetPlayerInput(playerId) -- host reads a player's latest inputgetAllPlayerInputs() -- host reads all player inputssetState(key, value) -- host sets replicated stategetState(key) -- read replicated stategetAllState() -- read full state snapshotgetInterpolatedState(key) -- read additive smoothed state for one keygetInterpolatedAllState() -- read additive smoothed full state snapshotconfigureRollbackLane(options) -- enable a supported rollback/prediction/replay lanegetRollbackState(key) -- read rollback-aware state for one keygetRollbackAllState() -- read rollback-aware full state snapshotclearRollbackLane(key?) -- clear one rollback lane or all lanesgetRollbackDiagnostics(key?) -- inspect rollback/replay history and correctionsonMessage(type, callback) -- receive game messagesonStateUpdate(callback) -- called on clients when state arrives as
(snapshot, metadata)onPlayerJoin(callback) -- { id, name }onPlayerLeave(callback) -- { id, name, reason }onDisconnect(callback) -- called on client when connection lostonHostMigration(callback) -- { newHostId, previousHostId, isNewHost }onHostChanged(callback) -- { newHostId, previousHostId }kick(playerId, reason?) -- host kicks a playersetPlayerData(playerId, key, value) / getPlayerData(playerId, key)advertise(options) -- LAN discovery broadcastdiscover(discoveryPort, callback) -- scan for LAN hostsReturns truthful room/session metadata:
{
role: "host" | "client",
code: "ABCD",
scope: "local" | "direct" | "internet",
transportPath: "local_room" | "direct_tcp" | "direct_udp_pending" | "direct_udp" | "relay_pending" | "relay_room",
transportStatus: null | "registered" | "relay_ready" | "direct_udp_pending" | "direct_udp_ready" | "direct_udp_timeout" | "relay_fallback" | "input_udp_pending" | "udp_input_ready" | "tcp_input_fallback" | "error",
lastReasonCode: null | "direct_udp_timeout" | "direct_runtime_attach_failed" | "relay_runtime_attach_failed" | "room_expired" | "relay_attach_failed" | "control_heartbeat_timeout" | "peer_left" | "host_migrating" | "input_udp_bind_failed" | "input_udp_connect_failed" | "input_udp_address_resolution_failed" | "input_udp_io_error" | "input_udp_not_ready" | "input_protocol_fallback_tcp",
peerCandidate: { host: "203.0.113.10", port: 54321 } | null,
requestedMode: "local" | "relay" | "auto" | "p2p",
joinPath: "local" | "internet_fallback" | "internet_explicit" | "direct" | null,
migrating: boolean,
migrationCompleted: boolean,
previousHostId: number | null,
newHostId: number | null,
connectivity: {
mode: "local" | "relay" | "auto" | "p2p",
coordinatorUrl: "tcp://relay.auramaxx.com:43110" | null,
relayUrl: "tcp://relay.auramaxx.com:43111" | null,
},
}
Direct-address sessions report scope: "direct" and transportPath: "direct_tcp". For local/direct TCP-only sessions, transportStatus may stay
null until there is a real setup or fallback state to surface.
During Stage 174 recovery, client room metadata may temporarily report
transportStatus: "resume_pending" and then transportStatus: "resumed" on
success. Failed recovery leaves an explicit lastReasonCode and transitions to
terminal cleanup instead of pretending the session never degraded.
Host migration is opt-in and only works for relay-backed sessions. When enabled,
if the host disconnects, the relay elects the connected client with the lowest
peer_id as the new host. State is preserved from each client's cached
replication snapshot.
Truthful limit:
Enable host migration:
aura.multiplayer.configure({
hostMigration: true,
migrationGraceMs: 3000, // grace period in ms (default 3000)
});
Both host and clients should call configure() before host() / join().
Callbacks:
onHostMigration(callback) -- fires when migration starts.aura.multiplayer.onHostMigration(function (info) {
// info: { newHostId, previousHostId, isNewHost }
// isNewHost is true if this peer is the elected new host.
});
onHostChanged(callback) -- fires when migration completes.aura.multiplayer.onHostChanged(function (info) {
// info: { newHostId, previousHostId }
});
Query methods:
isMigrating() -- returns true during the migration grace period.
During migration, update() is paused but draw() still runs, so the game
can display a "Migrating..." overlay.getRoomInfo() migration fields:
{
migrating: true | false,
migrationCompleted: true | false,
previousHostId: 0 | null,
newHostId: 2 | null,
}
Convenience helpers for sharing room codes:
getInviteCode() -- returns the current room code string, or null.getInviteLink() -- returns an aurajs://join/CODE URI string, or null.copyInviteLink() -- copies the invite URI to the system clipboard via SDL2.
Returns true on success, false if no session or clipboard unavailable.var link = aura.multiplayer.getInviteLink();
// "aurajs://join/ABCD"
aura.multiplayer.copyInviteLink();
// Copies "aurajs://join/ABCD" to clipboard.
--join CLI flag:
Players can join a room directly from the command line:
auramaxx run . --join ABCD
This auto-joins the specified room after setup() completes. It is equivalent
to calling aura.multiplayer.join("ABCD") in game code.
In a scaffolded game, npm run join -- ABCD delegates to the same join flow
through the generated wrapper.
getPlayers() returns a transport field for each player indicating how they
are connected:
var players = aura.multiplayer.getPlayers();
// [{ id: 0, connected: true, isHost: true, rtt: 0, ping: 0, transport: "local" },
// { id: 1, connected: true, isHost: false, rtt: 12, ping: 12, transport: "direct_udp" },
// { id: 2, connected: true, isHost: false, rtt: 45, ping: 45, transport: "relay" }]
Transport values:
"direct_udp" -- connected via direct UDP (NAT punch succeeded)"relay" -- connected via relay"direct_tcp" -- connected via direct TCP"local" -- local player (host self-reference)In a single session, different players may have different transport types (mixed transport). The host handles both direct and relay traffic transparently.
optional_module_multiplayer_disabled guidance.internetMode: 'auto' and 'p2p' try direct UDP gameplay first and fall
back to relay when the direct attempt times out.room_expired,
relay_attach_failed, direct_runtime_attach_failed,
relay_runtime_attach_failed, and control_heartbeat_timeout surface
through getRoomInfo() after expiry or attach failure.join(host, port) path still exists for advanced use cases.internetMode, relay URLs, and advertise() / discover()
as advanced or operator-facing flows rather than the default starter story.@aurajs/multiplayer-relay
package in the monorepo as coordinator plus fallback transport.configure({ hostMigration: true }) before host() or join().getInviteCode, getInviteLink, copyInviteLink)
require an active session. copyInviteLink requires SDL2 clipboard access.--join CODE CLI flag auto-joins a room after setup without modifying
game code.relay.auramaxx.com) is a placeholder for when
the public fleet launches; developers must run their own relay for now.Use aura.net when you need:
Use aura.multiplayer when you need: