Easy Multiplayer

Author deterministic browser games as host-agnostic modules, then run them through rollback, schema-backed performance mode, and Trystero peer transport.

Get Started See Demos
import { defineGame } from './defineGame.js';

export default defineGame({
    initialState: () => ({ ball: { x: 50, y: 30, vx: 1, vy: 1 } }),
    sampleInput: () => ({ dy: (keys.ArrowDown ? 1 : 0) - (keys.ArrowUp ? 1 : 0) }),
    step(state, ctx) {
        state.ball.x += state.ball.vx;
        state.ball.y += state.ball.vy;
    },
    draw(state, ctx) { /* render from state or a mirror */ }
});

Current Authoring Model

New games should start from defineGame: a plain module with initialState, deterministic step, optional draw, and optional sampleInput. The module owns gameplay only. The host supplies transport, clock, participants, rollback, and rendering environment.

For larger games, use V0.2/V0.3 performance mode. Canonical state is declared with schemaScalar, schemaRecord, and schemaTable; simulation writes through ctx.world() inside step(ctx); render code reads a RenderMirror. That schema-backed performance mode is the current scalable path.

Which Mode Should I Use?

Use caseRecommended path
New small game, tutorial, or host integrationStart with defineGame. Keep step(state, ctx) deterministic and host-agnostic.
High-entity-count game, Pac-Man-scale port, worker rendering, or rollback-heavy simulationUse V0.2/V0.3 performance mode: schema-backed state, ctx.world(), deterministic step(ctx), and mirror-backed rendering.
Easy/proxy mode object graphsFuture/deferred work. Do not choose easy/proxy mode as a current recommended path.
Existing callback code using new EasyMultiplayer, defineInput, and on('tick')Treat it as legacy compatibility. Do not teach new developers that path first.
Bus managers, scheduled buses, runtime adapters, and workersRuntime infrastructure only. They are not a separate game-authoring API.

Key Concepts

defineGame Modules

A game definition is pure data plus functions. It imports gameplay utilities, not networking or browser transport.

Schema-Backed Performance Mode

Declare canonical rows up front. The backend snapshots, hashes, patches, transfers, and mirrors plain records efficiently.

ctx.world()

The world facade exposes class-bound get, all, spawn, destroy, input, participants, tick, and dt.

Deterministic step(ctx)

Simulation reads routed inputs and schema rows only. No wall-clock time, DOM, renderer state, or ambient randomness.

Mirror-Backed Rendering

The renderer consumes RenderMirror snapshot and patch data. Render objects never become authoritative simulation state.

Trystero Transport

Standalone browser demos use TrysteroTransport for peer-to-peer rooms; tests inject deterministic transports.

Current Demos

Advanced Diagnostics

The live V0.3 worker Pac-Man proof is available at https://libs.letsinspire.com/em-pacman-v03-worker-trystero/. Treat time-sliced and worker proof URLs as advanced diagnostics for runtime validation, not first-path getting-started material. Start tutorials from the world-facade demo above.

Deployment Note

Publish the refreshed public docs by deploying the contents of docs/ to https://libs.letsinspire.com/easy_multiplayer/docs/. After upload, smoke-check index.html, getting-started.html, examples.html, and the linked Pac-Man demo pages from that base URL.