@netforge/engine/net API reference
Net Forge, the open-source multiplayer browser game engine built for AI coding agents, is published on npm under @netforge with source at github.com/netnetdev/net-forge.
The client half of the netcode and the wire protocol: prediction and reconciliation, interpolation, delta snapshots, input packets, the client clock and the SimAdapter seam a room host drives.
import { InputQueue, encodeSnapshot } from "@netforge/engine/net";Generated from packages/engine/src/net/index.ts by apps/forge-docs/scripts/api.mjs. Signatures are what the TypeScript compiler reports for the current source; long types are cut and say so.
| Kind | Count |
|---|---|
| Functions | 23 |
| Classes | 11 |
| Constants | 9 |
| Types | 47 |
Functions
bitsForSigned
Bits needed for a zigzagged signed value of magnitude up to maxAbs.
function bitsForSigned(maxAbs: number): number;Source: packages/engine/src/net/bits.ts
connectReservation
function connectReservation<W, I>(sim: SimAdapter<W, I>, endpoint: string, reservation: Parameters<Client["consumeSeatReservation"]>[0]): Promise<RoomConnection<W, I>>;Source: packages/engine/src/net/room-client.ts
decodeInputPacket
function decodeInputPacket<I>(codec: InputCodec<I>, bytes: Uint8Array): InputPacket<I>;Source: packages/engine/src/net/input.ts
decodeSnapshot
Decodes a snapshot. baselineFor returns the client's stored entity set
for a tick it previously decoded.
function decodeSnapshot(schema: EntitySchema, bytes: Uint8Array, baselineFor: (tick: number) => ReadonlyMap<number, QEntity> | undefined): DecodedSnapshot;Source: packages/engine/src/net/snapshot.ts
dequantize
Wire integer back to the real value.
function dequantize(f: FieldSpec, q: number): number;Source: packages/engine/src/net/schema.ts
dequantizeValues
function dequantizeValues(schema: EntitySchema, q: readonly number[]): number[];Source: packages/engine/src/net/schema.ts
encodeInputPacket
function encodeInputPacket<I>(codec: InputCodec<I>, p: InputPacket<I>): Uint8Array;Source: packages/engine/src/net/input.ts
encodeSnapshot
Encodes current (any order) against baseline (null for a full snapshot).
function encodeSnapshot(schema: EntitySchema, header: SnapshotHeader, current: readonly QEntity[], baseline: ReadonlyMap<number, QEntity> | null): Uint8Array;Source: packages/engine/src/net/snapshot.ts
fieldBits
function fieldBits(f: FieldSpec): number;Source: packages/engine/src/net/schema.ts
fieldDeltaBits
Default bit width for a "small change" delta of a field, or 0 for none.
function fieldDeltaBits(f: FieldSpec): number;Source: packages/engine/src/net/schema.ts
guestId
This browser's guest id (made on first use). Storage blocked: one per page load.
function guestId(): string;Source: packages/engine/src/net/profile-client.ts
interpolateField
Interpolate one field between a and b (real values).
function interpolateField(f: FieldSpec, a: number, b: number, t: number): number;Source: packages/engine/src/net/schema.ts
joinRoom
function joinRoom<W, I>(sim: SimAdapter<W, I>, p: JoinParams): Promise<RoomConnection<W, I>>;Source: packages/engine/src/net/room-client.ts
kindFieldIndices
Field indices carried on the wire for an entity kind (all fields unless the schema narrows that kind).
function kindFieldIndices(schema: EntitySchema, kind: number): readonly number[];Source: packages/engine/src/net/snapshot.ts
pageGame
The page's ?game= slug (set by Net Forge on a launched game's frame), or null outside a browser or without one.
function pageGame(): string | null;Source: packages/engine/src/net/room-client.ts
quantize
Real value to the integer the wire carries.
function quantize(f: FieldSpec, v: number): number;Source: packages/engine/src/net/schema.ts
quantizeEntity
Quantize an entity's values. With only (the kind's wire fields), the
other fields are left 0: they never go on the wire for that kind.
function quantizeEntity(schema: EntitySchema, e: EntityState, only?: readonly number[]): number[];Source: packages/engine/src/net/schema.ts
rotateGuestId
A fresh guest id: after a guest profile merges into a wallet, the old id is spent.
function rotateGuestId(): string;Source: packages/engine/src/net/profile-client.ts
savedSession
The saved session token for a rooms server, if it has not expired.
function savedSession(endpoint: string): { token: string; address: string; } | null;Source: packages/engine/src/net/profile-client.ts
saveSession
function saveSession(endpoint: string, s: { token: string; address: string; expiresAt: number; } | null): void;Source: packages/engine/src/net/profile-client.ts
signInWithWallet
Sign in to a rooms server with a wallet: its nonce, an EIP-4361 message for its domain, one personal_sign (no gas), a session token. Saved for this endpoint until it expires.
function signInWithWallet(endpoint: string, provider: Eip1193, statement?: string): Promise<{ token: string; address: string; expiresAt: number; }>;Source: packages/engine/src/net/profile-client.ts
unzigzag
function unzigzag(z: number): number;Source: packages/engine/src/net/bits.ts
zigzag
function zigzag(n: number): number;Source: packages/engine/src/net/bits.ts
Classes
BaselineMissingError
class BaselineMissingError {
constructor(baselineTick: number);
}Source: packages/engine/src/net/snapshot.ts
BitReader
class BitReader {
constructor(buf: Uint8Array);
readBits(bits: number): number;
readBool(): boolean;
readSigned(bits: number): number;
readVarUint(): number;
get remainingBits(): number;
}Source: packages/engine/src/net/bits.ts
BitWriter
class BitWriter {
constructor(initialBytes?: number);
writeBits(value: number, bits: number): void;
writeBool(b: boolean): void;
writeSigned(value: number, bits: number): void;
writeVarUint(value: number): void;
get bitLength(): number;
finish(): Uint8Array;
}Source: packages/engine/src/net/bits.ts
InputQueue
Server-side per-player input queue with speed-hack protection.
class InputQueue {
constructor<I>(opts?: InputQueueOptions);
lastAppliedSeq: number;
dropped: number;
starved: number;
receive(commands: readonly InputCommand<I>[]): void;
consume(): InputCommand<I>[];
get pending(): number;
}Source: packages/engine/src/net/input.ts
InterpolationBuffer
class InterpolationBuffer {
constructor(schema: EntitySchema, capacity?: number);
push(tick: number, entities: ReadonlyMap<number, QEntity>): void;
sample(renderTick: number, exclude?: number): InterpolatedEntity[];
get newestTick(): number;
}Source: packages/engine/src/net/interpolation.ts
NetClient
class NetClient {
constructor<W, I>(sim: SimAdapter<W, I>, player: PlayerId, selfEntity: number, send: (bytes: Uint8Array) => void, opts?: NetClientOptions);
readonly stats: ClientStats;
get nextSeq(): number;
receiveSnapshot(bytes: Uint8Array, nowMs: number): void;
tick(input: I, nowMs: number): void;
tickIntervalMs(): number;
get serverInputBuffer(): number | null;
renderTick(nowMs: number): number;
render(nowMs: number): { self: EntityState | undefined; others: InterpolatedEntity[]; };
authoritativeSelf(): EntityState | undefined;
interpolatedSelf(nowMs: number): InterpolatedEntity | undefined;
predicted(): EntityState | undefined;
get ready(): boolean;
get corrections(): number;
get lastCorrection(): number;
get pendingInputs(): number;
}Source: packages/engine/src/net/client.ts
Predictor
class Predictor {
constructor<W, I>(sim: SimAdapter<W, I>, player: PlayerId, selfEntity: number, seed?: number, firstSeq?: number);
lastCorrection: number;
corrections: number;
get upcomingSeq(): number;
get isReady(): boolean;
predict(input: I, viewTick16: number): InputCommand<I> | null;
unacked(): readonly InputCommand<I>[];
reconcile(ackSeq: number, serverSelf: EntityState): void;
state(): EntityState | undefined;
renderState(dtMs: number): EntityState | undefined;
}Source: packages/engine/src/net/prediction.ts
ProfileApi
The room server's profile routes for one game.
class ProfileApi {
constructor(endpoint: string, game: string);
get(auth: ProfileAuth): Promise<GameProfile>;
importLocal(guest: string, local: LocalProgress): Promise<GameProfile>;
claim(session: string, guest: string): Promise<GameProfile>;
setSettings(auth: ProfileAuth, settings: Readonly<Record<string, unknown>>): Promise<GameProfile>;
setName(auth: ProfileAuth, displayName: string): Promise<GameProfile>;
redeem(session: string, item: string, key: string): Promise<GameProfile>;
}Source: packages/engine/src/net/profile-client.ts
ServerClock
Tracks server time from snapshot arrivals. The offset (server ms minus local ms) is taken from the freshest-looking arrival, eased toward later samples so jitter does not make the render time jump.
class ServerClock {
constructor(tickMs: number);
observe(tick: number, localMs: number): void;
serverTick(localMs: number): number;
get ready(): boolean;
}Source: packages/engine/src/net/interpolation.ts
SnapshotHistory
Per-client history of what was sent, keyed by tick, so the next snapshot can be encoded against whatever the client last acked.
class SnapshotHistory {
constructor(capacity?: number);
put(tick: number, entities: readonly QEntity[] | ReadonlyMap<number, QEntity>): void;
get(tick: number): ReadonlyMap<number, QEntity> | undefined;
pruneBefore(tick: number): void;
get size(): number;
}Source: packages/engine/src/net/snapshot.ts
VirtualNet
class VirtualNet {
constructor(seed?: number);
now: number;
sent: number;
dropped: number;
random(): number;
send(link: LinkOptions, deliver: () => void): void;
advanceTo(t: number): void;
}Source: packages/engine/src/net/netsim.ts
Constants
DEFAULT_CLIENT
const DEFAULT_CLIENT: NetClientOptions;Source: packages/engine/src/net/client.ts
DEFAULT_INPUT_QUEUE
const DEFAULT_INPUT_QUEUE: InputQueueOptions;Source: packages/engine/src/net/input.ts
GAME_SLUG_RE
A game slug as the GameFactory and the room server take it.
const GAME_SLUG_RE: RegExp;Source: packages/engine/src/net/protocol.ts
GUEST_ID_RE
Guest profile ids: 16-80 characters of A-Z, a-z, 0-9, _ or - (the same rule as Launcher guest drafts).
const GUEST_ID_RE: RegExp;Source: packages/engine/src/net/protocol.ts
GUEST_KEY
Storage key for the guest id: shared by every game on one origin, like a Launcher guest.
const GUEST_KEY: "netnet:guest:v1";Source: packages/engine/src/net/profile-client.ts
MSG_INPUT
const MSG_INPUT: 1;Source: packages/engine/src/net/protocol.ts
MSG_SNAPSHOT
const MSG_SNAPSHOT: 2;Source: packages/engine/src/net/protocol.ts
REDUNDANCY
How many unacked commands each packet repeats.
const REDUNDANCY: 4;Source: packages/engine/src/net/input.ts
ROOM_NAME
const ROOM_NAME: "instance";Source: packages/engine/src/net/protocol.ts
Types
ClientStats
interface ClientStats {
snapshots: number;
snapshotBytes: number;
inputPackets: number;
inputBytes: number;
baselineMisses: number;
staleSnapshots: number;
}Source: packages/engine/src/net/client.ts
CommandMessage
Client -> server: a store hook (paid revive, cosmetic). The room verifies before the sim applies it.
interface CommandMessage {
readonly name: string;
readonly value?: number;
readonly proof?: string;
}Source: packages/engine/src/net/protocol.ts
CommandResult
Server -> client: the outcome of a command.
interface CommandResult {
readonly name: string;
readonly ok: boolean;
readonly reason?: string;
}Source: packages/engine/src/net/protocol.ts
CreateOptions
interface CreateOptions {
readonly place: string;
readonly template: string;
readonly game?: string;
}Source: packages/engine/src/net/protocol.ts
DecodedSnapshot
interface DecodedSnapshot {
readonly header: SnapshotHeader;
readonly entities: ReadonlyMap<number, QEntity>;
readonly removed: readonly number[];
}Source: packages/engine/src/net/snapshot.ts
EarnEvent
Something the authoritative sim says a player earned.
interface EarnEvent {
readonly type: 'earn';
readonly player: PlayerId;
readonly rule: string;
readonly units: number;
}Source: packages/engine/src/net/sim-adapter.ts
EarnMessage
interface EarnMessage {
readonly rule: string;
readonly units: number;
}Source: packages/engine/src/net/protocol.ts
EarnRefusal
type EarnRefusal = 'guest' | 'wallet-busy' | 'session';Source: packages/engine/src/net/protocol.ts
Eip1193
The slice of an EIP-1193 provider sign-in needs.
interface Eip1193 {
request(args: { method: string; params?: unknown[] }): Promise<unknown>;
}Source: packages/engine/src/net/profile-client.ts
EntitySchema
interface EntitySchema {
readonly fields: readonly FieldSpec[];
readonly xField: number;
readonly yField: number;
readonly unitsPerWorld: number;
readonly kindFields?: Readonly<Record<number, readonly number[]>>;
}Source: packages/engine/src/net/schema.ts
EntityState
One entity as the sim reports it: values are real (dequantized) numbers.
interface EntityState {
readonly id: number;
readonly kind: number;
readonly owner: number;
readonly values: readonly number[];
}Source: packages/engine/src/net/schema.ts
FieldSpec
type FieldSpec =
| { readonly name: string; readonly kind: 'uint'; readonly bits: number; readonly interp?: Interp; readonly deltaBits?: number }
| { readonly name: string; readonly kind: 'int'; readonly bits: number; readonly interp?: Interp; readonly deltaBits?: number }
| {
readonly name: string;
readonly kind: 'quant';
readonly min: number;
readonly max: number;
readonly bits: number;
readonly interp?: Interp;
readonly deltaBits?: number;
}
| { readonly name: string; readonly kind: 'angle16'; readonly deltaBits?: number }
| { readonly name: string; readonly kind: 'bool' };Source: packages/engine/src/net/schema.ts
GameProfile
A player's saved progress in one game, kept by the room server: the same on every device the player signs in on. Achievements, goals and titles come from the authoritative sim only.
interface GameProfile {
readonly game: string;
readonly kind: 'wallet' | 'guest' | null;
readonly wallet: string | null;
readonly displayName: string | null;
readonly achievements: readonly { readonly id: string; readonly at: number; readonly source: 'sim' | 'import' }[];
readonly keepers: readonly string[];
readonly owned: readonly string[];
readonly cosmetics: readonly number[];
readonly meta: Readonly<Record<string, number>>;
readonly goals: { readonly day: number; readonly today: readonly string[]; readonly done: readonly string[] };
readonly titles: Readonly<Record<string, number>>;
readonly settings: Readonly<Record<string, unknown>>;
readonly store: readonly ProfileStoreItem[];
readonly imported: boolean;
readonly credit: number;
readonly totals: { readonly achievements: number; readonly titles: number; readonly games: number };
}Source: packages/engine/src/net/protocol.ts
Hitbox
interface Hitbox {
readonly entity: number;
readonly owner: PlayerId;
readonly x: number;
readonly y: number;
readonly r: number;
}Source: packages/engine/src/net/sim-adapter.ts
HitscanRequest
A hitscan the sim wants resolved against lag-compensated hitboxes.
interface HitscanRequest {
readonly shooter: PlayerId;
readonly ox: number;
readonly oy: number;
readonly dx: number;
readonly dy: number;
readonly range: number;
}Source: packages/engine/src/net/sim-adapter.ts
InputCodec
interface InputCodec<I> {
write(w: BitWriter, input: I): void;
read(r: BitReader): I;
readonly idle: I;
}Source: packages/engine/src/net/sim-adapter.ts
InputCommand
interface InputCommand<I> {
readonly seq: number;
readonly viewTick16: number;
readonly input: I;
}Source: packages/engine/src/net/input.ts
InputPacket
interface InputPacket<I> {
readonly ackTick: number;
readonly commands: readonly InputCommand<I>[];
}Source: packages/engine/src/net/input.ts
InputQueueOptions
interface InputQueueOptions {
readonly maxQueue: number;
readonly maxCredits: number;
readonly targetDepth?: number;
}Source: packages/engine/src/net/input.ts
Interp
type Interp = 'lerp' | 'step';Source: packages/engine/src/net/schema.ts
InterpolatedEntity
interface InterpolatedEntity {
readonly id: number;
readonly kind: number;
readonly owner: number;
readonly values: readonly number[];
}Source: packages/engine/src/net/interpolation.ts
JoinOptions
interface JoinOptions {
readonly wallet?: string;
readonly session?: string;
readonly guest?: string;
}Source: packages/engine/src/net/protocol.ts
JoinParams
interface JoinParams {
readonly endpoint: string; // http(s)://host:port
readonly place: string;
readonly template: string;
readonly game?: string;
readonly wallet?: string;
readonly session?: string;
readonly guest?: string;
readonly friendRoomId?: string;
}Source: packages/engine/src/net/room-client.ts
LinkOptions
interface LinkOptions {
readonly latencyMs: number;
readonly jitterMs: number;
readonly loss: number;
}Source: packages/engine/src/net/netsim.ts
LocalProgress
interface LocalProgress {
readonly achievements?: readonly string[];
readonly goals?: { readonly day: number; readonly done: readonly string[] };
readonly settings?: Readonly<Record<string, unknown>>;
}Source: packages/engine/src/net/profile-client.ts
NetClientOptions
interface NetClientOptions {
readonly interpDelayMs: number;
readonly inputBufferTarget?: number;
readonly firstSeq?: number;
}Source: packages/engine/src/net/client.ts
PlayerId
Small integer assigned by the room to each connected player (1..65535).
type PlayerId = number;Source: packages/engine/src/net/sim-adapter.ts
PlayerRights
What a player's profile lets them choose (the room checks it on every input).
interface PlayerRights {
readonly keepers: readonly number[] | null;
}Source: packages/engine/src/net/sim-adapter.ts
PotMessage
Server -> client ('pot'): 'in' this round's pot holds the player's entry
(pot is the whole pot so far); 'waiting' the entry is placed for the
next round; 'closed' the round split the pot (paid is this player's
part); 'refused' (reason). Whole coins.
interface PotMessage {
readonly state: 'in' | 'waiting' | 'closed' | 'refused';
readonly entry: number;
readonly pot?: number;
readonly paid?: number;
readonly reason?: string;
}Source: packages/engine/src/net/protocol.ts
ProfileAuth
Who is asking: a signed-in session (bearer) or a guest id.
type ProfileAuth = { readonly session: string } | { readonly guest: string };Source: packages/engine/src/net/profile-client.ts
ProfileStoreItem
One item of a game's permanent store as a profile sees it (cosmetics, unlocks, meta levels).
interface ProfileStoreItem {
readonly id: string;
readonly kind: 'cosmetic' | 'unlock' | 'meta' | 'gear';
readonly coins: number | null;
readonly owned: boolean;
readonly cosmetic?: number;
readonly keeper?: string;
readonly gear?: string;
readonly loseBelow?: number;
readonly stat?: string;
readonly step?: number;
readonly level?: number;
readonly levels?: number;
readonly bonus?: number;
}Source: packages/engine/src/net/protocol.ts
ProgressEvent
The sim says a player reached something their profile keeps: an achievement, one of today's goals, or an end-screen title. The room writes it to the player's profile on the server; the browser never grants these.
interface ProgressEvent {
readonly type: 'progress';
readonly player: PlayerId;
readonly kind: 'achievement' | 'goal' | 'title';
readonly id: string;
}Source: packages/engine/src/net/sim-adapter.ts
QEntity
One entity on the wire, values already quantized.
interface QEntity {
readonly id: number;
readonly kind: number;
readonly owner: number;
readonly q: readonly number[];
}Source: packages/engine/src/net/snapshot.ts
RebaseMessage
Server -> client: the room rebuilt its world at a safe point (between
rounds): a new logic module (hot swap, version changed) or a fresh
verification segment. The player keeps its id; its entity id may change,
baselines are void and the next snapshot is full.
interface RebaseMessage {
readonly entity: number;
readonly sim: string;
readonly version: string;
}Source: packages/engine/src/net/protocol.ts
RoomConnection
interface RoomConnection<W, I> {
readonly room: Room;
readonly net: NetClient<W, I>;
readonly welcome: WelcomeMessage;
readonly roomId: string;
bytesIn(): number;
messagesIn(): number;
onEarn(cb: (e: EarnMessage) => void): void;
onRun(cb: (info: unknown) => void): void;
latestRun(): unknown;
onRunId(cb: (m: RunIdMessage) => void): void;
onRebase(cb: (m: RebaseMessage) => void): void;
signIn(session: string): Promise<SignInResult>;
onProfile(cb: (p: GameProfile) => void): void;
latestProfile(): GameProfile | null;
stake(amount?: number): void;
onStake(cb: (m: StakeMessage) => void): void;
latestStake(): StakeMessage | null;
pot(): void;
onPot(cb: (m: PotMessage) => void): void;
command(cmd: CommandMessage): Promise<CommandResult>;
claim(): Promise<unknown>;
leave(): Promise<void>;
}Source: packages/engine/src/net/room-client.ts
RunIdMessage
Server -> client: a fresh referee run started (new runId after a run reset).
interface RunIdMessage {
readonly runId: string | null;
}Source: packages/engine/src/net/protocol.ts
RunResetEvent
The sim started a fresh run in place (game over or victory screen ended). Referee runs restart.
interface RunResetEvent {
readonly type: 'run.reset';
}Source: packages/engine/src/net/sim-adapter.ts
SignInMessage
Client -> server: sign in mid-session (a guest who just signed the SIWE message).
interface SignInMessage {
readonly session: string;
}Source: packages/engine/src/net/protocol.ts
SignInResult
Server -> client: the outcome of a mid-session sign-in.
interface SignInResult {
readonly ok: boolean;
readonly runId: string | null;
readonly earnRefused?: EarnRefusal;
}Source: packages/engine/src/net/protocol.ts
SimAdapter
interface SimAdapter<W, I> {
readonly name: string;
readonly version: string;
readonly tickHz: number;
readonly schema: EntitySchema;
readonly input: InputCodec<I>;
readonly maxInputsPerTick?: number;
createWorld(seed: number, role: 'server' | 'client'): W;
addPlayer(world: W, player: PlayerId): number;
removePlayer(world: W, player: PlayerId): void;
step(world: W, inputsByPlayer: ReadonlyMap<PlayerId, readonly I[]>): readonly SimEvent[];
snapshot(world: W): readonly EntityState[];
applySnapshot(world: W, entities: readonly EntityState[]): void;
hitscans?(world: W, player: PlayerId, input: I): HitscanRequest | null;
hitboxes?(world: W): readonly Hitbox[];
applyHit?(world: W, shooter: PlayerId, target: Hitbox): readonly SimEvent[];
info?(world: W): unknown;
infoFor?(world: W, player: PlayerId): unknown;
readonly infoEveryTicks?: number;
restrictInput?(input: I, rights: PlayerRights): I;
command?(world: W, player: PlayerId, cmd: SimCommand): boolean;
startRun?(world: W, player: PlayerId): void;
disposeWorld?(world: W): void;
}Source: packages/engine/src/net/sim-adapter.ts
SimCommand
A room-level command from a player (store hooks): a paid revive, a cosmetic.
interface SimCommand {
readonly name: string;
readonly value?: number;
}Source: packages/engine/src/net/sim-adapter.ts
SimEvent
type SimEvent = EarnEvent | RunResetEvent | ProgressEvent;Source: packages/engine/src/net/sim-adapter.ts
SnapshotHeader
interface SnapshotHeader {
readonly tick: number;
readonly baselineTick: number;
readonly ackSeq: number;
readonly self: number;
readonly inputBuffer?: number;
}Source: packages/engine/src/net/snapshot.ts
StakeMessage
Server -> client ('stake'): 'live' the run in progress is staked;
'waiting' a stake is placed for the next run; 'closed' a staked run ended
(paid is its stake share); 'refused' the stake was not taken (reason).
Amounts are whole coins.
interface StakeMessage {
readonly state: 'live' | 'waiting' | 'closed' | 'refused';
readonly stake: number;
readonly ceiling?: number;
readonly paid?: number;
readonly reason?: string;
readonly gear?: readonly string[];
readonly lost?: readonly string[];
}Source: packages/engine/src/net/protocol.ts
StakeRequest
Client -> server ('stake'): the player spent a stake (the Kit's
coins.spend(amount, "stake"), committed). The room fixes the stake from
its own ledger when a run starts: always the NEXT run (sign-in or the
next round; a run in progress never restarts), and a
staked run starts from zero earn progress. amount (whole coins) is read only
by a room whose store runs in mock mode (local play).
interface StakeRequest {
readonly amount?: number;
}Source: packages/engine/src/net/protocol.ts
StakeTermsView
A game's buy-in run terms as a client shows them (whole coins).
interface StakeTermsView {
readonly min: number;
readonly max: number;
readonly multiple: number;
}Source: packages/engine/src/net/protocol.ts
WelcomeMessage
interface WelcomeMessage {
readonly player: number;
readonly entity: number;
readonly tickHz: number;
readonly snapshotHz: number;
readonly sim: string;
readonly version: string;
readonly place: string;
readonly template: string;
readonly runId: string | null;
readonly earnLive: boolean;
readonly earnRefused?: EarnRefusal;
readonly stake?: StakeTermsView;
readonly pot?: { readonly entry: number; readonly recycle: number };
}