INPUT, HASH, WELCOME, LEAVE const
Wire kinds. Every message is 12 bytes: kind, slot, players, 0, u32 frame (or seed), u32 value.
export declare const INPUT = 1, HASH = 2, WELCOME = 3, LEAVE = 4;
PRESENCE V0.10.0
fizlet/presenceLockstep input exchange for multiplayer, behind a tiny transport adapter. Every peer runs the same fixed-step simulation from the same seed. Each sim frame needs every player's input for that frame; your own input is sent delay frames ahead, so on a good link the game never waits. A missing input stalls the sim (never guesses), so peers can't drift apart. Inputs live in fixed rings of typed arrays: a confirmed per-frame input log a rollback layer can replay. State hashes are exchanged every hashEvery frames to catch a desync early.
Measured on this build: minified and gzipped with everything it imports, the way npm run size checks the budget in CI.
import { INPUT, HASH, WELCOME, LEAVE, … } from "https://coded-dot-design.github.io/fizlet/dist/presence.js";
import { syncCheck } from "https://coded-dot-design.github.io/fizlet/dist/presence-check.js";npm install fizlet # not published yet
import { INPUT, HASH, WELCOME, LEAVE, … } from "fizlet/presence";
import { syncCheck } from "fizlet/presence/check";Every export, with its doc comment, read from the published type declarations of v0.10.0.
fizlet/presenceLockstep input exchange for multiplayer, behind a tiny transport adapter. Every peer runs the same fixed-step simulation from the same seed. Each sim frame needs every player's input for that frame; your own input is sent delay frames ahead, so on a good link the game never waits. A missing input stalls the sim (never guesses), so peers can't drift apart. Inputs live in fixed rings of typed arrays: a confirmed per-frame input log a rollback layer can replay. State hashes are exchanged every hashEvery frames to catch a desync early.
INPUT, HASH, WELCOME, LEAVE constWire kinds. Every message is 12 bytes: kind, slot, players, 0, u32 frame (or seed), u32 value.
export declare const INPUT = 1, HASH = 2, WELCOME = 3, LEAVE = 4;
Transport interfaceMoves 12-byte messages to the room (or the other peers) and back.
export interface Transport {
/** Send one message. The buffer is reused: copy it before returning. */
send(data: Uint8Array): void;
/** Deliver every incoming message to `fn` (messages that arrived earlier are flushed to it). */
listen(fn: ((data: Uint8Array) => void) | null): void;
close(): void;
}
PresenceOptions interfaceexport interface PresenceOptions {
transport: Transport;
/** This peer's seat, 0..players-1 (the room says which). */
slot: number;
players: number;
/** Frames of input delay (default 2: 33 ms at 60 Hz). */
delay?: number;
/** Input frames kept per player (default 64; at least 2 × delay + 2). */
cap?: number;
/** This player's input for the frame being scheduled, as bits (read latched intents here). */
input(): number;
/** The simulation for one frame: same inputs + same seed → same state on every peer. */
step(e: Engine, inputs: Uint32Array, frame: number): void;
/** A hash of the sim state (see `hash32`); compared across peers to catch a desync. */
hash?(): number;
/** Frames between hash checks (default 30). */
hashEvery?: number;
}
presence functionexport declare function presence(e: Engine, o: PresenceOptions): {
system: System;
/** Sim frames run so far (every peer passes the same frames, maybe at different times). */
readonly frame: number;
/** Engine steps spent waiting for someone's input. */
readonly stalls: number;
/** Messages refused (out of the window, bad seat). */
readonly dropped: number;
/** First frame whose state hash differed from a peer's, or -1. */
readonly desync: number;
/** Bitmask of seats the room said left (their frames never arrive: the sim waits). */
readonly lost: number;
/** The confirmed input of `slot` for `frame`, or -1 if it isn't known (yet, or any more). */
confirmed(slot: number, frame: number): number;
destroy: () => void;
};
Presence typeexport type Presence = ReturnType<typeof presence>;
hash32 functionFNV-1a over bytes: hash a byte view of your state's typed arrays (make the view once).
export declare function hash32(bytes: Uint8Array, h?: number): number;
memory functionIn-memory hub: peers transports that broadcast to each other (like the room relay) after latency ± jitter ticks. Call tick() once per frame. Bounded: cap messages in flight.
export declare function memory(o: {
peers: number;
latency?: number;
jitter?: number;
seed?: number;
cap?: number;
}): {
ends: Transport[];
/** Advance the link clock one tick and deliver what's due (later messages may overtake). */
tick(): void;
readonly queued: number;
readonly dropped: number;
};
websocket functionWrap an open-or-opening WebSocket as a transport. Messages before listen are kept (≤ 256).
export declare function websocket(ws: WebSocket): Transport;
join functionJoin a room (fizlet-rooms): resolves when the room is full with your seat and the shared seed.
export declare function join(url: string, signal?: AbortSignal): Promise<{
transport: Transport;
slot: number;
players: number;
seed: number;
}>;
fizlet/presence/checkThe determinism check as one function any game can call from a test. It runs players copies of your sim as lockstep clients over the in-memory link (each with only its own input), hashes the state after every frame, and reports the first frame that differs.
SyncGame interfaceexport interface SyncGame {
/** This player's input bits for the next scheduled frame (only this client knows them). */
input(): number;
/** One sim frame from every player's inputs. Use `e.random`, never Math.random or the clock. */
step(e: Engine, inputs: Uint32Array, frame: number): void;
/** A byte view of the whole sim state (typed arrays; make the view once). */
state: Uint8Array;
}
SyncOptions interfaceexport interface SyncOptions {
players?: number;
frames?: number;
seed?: number;
delay?: number;
latency?: number;
jitter?: number;
}
SyncResult interfaceexport interface SyncResult {
/** Every client reached `frames` with the same state hash on every frame. */
ok: boolean;
/** Frames every client completed (≤ frames). */
frames: number;
/** First frame whose state differed between clients, or -1. */
diverged: number;
/** Engine steps spent waiting for inputs, summed over clients. */
stalls: number;
}
syncCheck functionexport declare function syncCheck(setup: (e: Engine, slot: number, players: number) => SyncGame, o?: SyncOptions): SyncResult;