How to build a multiplayer browser game with an authoritative server
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 fastest way to a multiplayer browser game with an authoritative server is to start from one that already works: npm create netforge@latest my-game -- --template arena --yes makes a 16-player arena shooter whose server runs the simulation and whose browsers predict their own player from the same TypeScript code. You change the rules in src/game.ts; the netcode, rendering and bots are already done.
npm create netforge@latest my-game -- --template arena --yes
cd my-game
npm run play -- --players 2That opens two browser tabs in the same room on http://127.0.0.1:4663, with the room host running on your computer. Any of the ten templates works the same way; the arena shooter is the clearest example of an authoritative server because players fight each other.
What you get
| Part | Where it lives | Notes |
|---|---|---|
| Game rules | src/game.ts | Declarations only: weapons, loadout, round length, earn rules, store items. |
| Shared simulation | @netforge/engine-sim | ECS plus deterministic Rapier, fixed tick. The same code runs on the server and in the browser. |
| Client netcode | @netforge/engine/net | Prediction and reconciliation, interpolation, delta snapshots, input packets. |
| Room host | @netforge/kit-play (local) | Authoritative loop, one input queue per player, per-player delta snapshots at 20 Hz. |
| Browser client | @netforge/template-arena | three.js scene (WebGPU with WebGL2 fallback), HUD, keyboard, mouse, gamepad and touch. |
| Bots | the template | Seat bots fill empty seats while a person is in the room, and never earn. |
Measured: the arena template's playtest (4 bots for 4 minutes of game time, 14,400 ticks at 60 Hz) runs at 0.10 ms per tick on average and 0.37 ms at p99 on an Apple M2 Pro with Node 22, against a budget of 16.7 ms per tick.
1. Create the game
Run the create command above. It writes src/game.ts, netnet.json (which names the template), a package.json with the play, test and check scripts, and agent guides (AGENTS.md, CLAUDE.md). It installs the engine and the template package. Add --pm pnpm to use pnpm.
2. Play it with several players
npm run play -- --players 3
npm run play -- --host 0.0.0.0The first opens three tabs, each a separate player. The second listens on every interface so a phone on the same network can join through your computer's address. Each tab is a full client: it sends input, predicts its own wirejack and draws everyone else about 100 ms in the past.
3. Change the rules
The whole game is declarations against @netforge/engine. This is a complete, smaller arena game: three weapons, a heal drop, a three-minute round and one earn rule.
import { earn, game, prefab, weapon } from "@netforge/engine";
export default game({
template: "arena",
style: "lowpoly",
tickRate: 60,
places: { yard: { chunks: "places/yard", spawn: [0, 1, 0] } },
players: {
prefab: "style.lowpoly.mini_male_f",
max: 8,
loadout: ["arc_rifle", "scatter_coil", "rail_lance"],
bots: 4,
respawn: 3,
drop: "volt_cell",
},
arena: {
halfSize: 22,
pillars: [[0, 0, 2.4], [9, 9, 1.4], [-9, 9, 1.4], [9, -9, 1.4], [-9, -9, 1.4]],
spawns: [[18, 18], [-18, -18], [18, -18], [-18, 18]],
},
round: { seconds: 180, frags: 20 },
});
prefab("volt_cell", { model: "style.lowpoly.cell_heal", health: 1, speed: 0, kind: "pickup", value: 35 });
weapon("arc_rifle", { kind: "blaster", damage: 12, cooldown: 0.15, range: 26, speed: 44 });
weapon("scatter_coil", { kind: "blaster", damage: 9, cooldown: 0.75, range: 11, speed: 34, count: 7, spread: 26 });
weapon("rail_lance", { kind: "beam", damage: 60, cooldown: 1.4, range: 40 });
earn.rule("live_wire", { when: "opponent.killed", n: 3, pay: 4, oncePer: "run" });
earn.stake({ min: 10, max: 100, multiple: 2 });Every game declares at least one earn.rule and one earn.stake (a buy-in run). In local play both use play money and pay nothing; they exist so the same file runs unchanged on the hosted service.
4. Test it headless
npm test
npm run checknpm test compiles src/game.ts exactly as the play build does and plays it with bots, no browser and no server. It fails on a softlock, a check the genre requires (everyone scores kills, an earn rule fires) or a declaration the engine refuses. npm run check prints the same result as JSON for scripts and coding agents.
How it works
- Authority. The room host owns the world. Clients send only inputs: sequence-numbered commands, each one tick of one player's movement, with the last few unacknowledged commands repeated in every packet to ride out loss.
- One simulation. The host steps
@netforge/engine-simat the game's fixed tick (60 Hz for the arena shooter, 30 Hz for most templates). The browser steps the same module for its own player, so prediction matches the server bit for bit while nothing else interferes. - Reconciliation. When a snapshot arrives, the client resets its player to the server's state and replays every input the server has not acknowledged. A disagreement is shown as an offset that decays over about 100 ms instead of a snap.
- Snapshots. The host sends each player a delta against the last snapshot that player acknowledged, holding only what is near them. A resting entity costs about 3 bytes and a walking one about 8.
- Lag compensation. The hosted Net Forge room server rewinds other players to what the shooter saw for hitscan beams (capped at 200 ms). The local host in
kit-playresolves beams against the present, which is exact at local latency.
More detail: Netcode and prediction and Simulation and ECS.
Alternatives
- Colyseus is a mature, MIT-licensed multiplayer server framework for Node with rooms, matchmaking, schema-based state sync and client SDKs for many engines, and it now includes client prediction. Pick it when you already have a client engine and want a general server framework. Its getting-started command is
npm create colyseus-app. - Nakama is an open-source game backend with accounts, social features, matchmaking and authoritative multiplayer. Pick it when you need a full backend with user accounts and storage.
- Socket.IO is a library for two-way, event-driven communication. Pick it for a turn-based prototype or a chat-style game where you write the game loop yourself.
- PartyKit runs real-time rooms on a hosted edge platform. Pick it when you want rooms without running your own servers.
Pick Net Forge when you want the client, the server simulation and a finished game in one package, written in TypeScript end to end.
FAQ
What is an authoritative server in a browser game?
It is a server that runs the real game and decides every outcome. Browsers send only what the player pressed; the server steps the world and sends back what happened. Cheating by editing the client does not change the result, because the client never decides it.
Do I have to write netcode with Net Forge?
No. The templates include prediction, reconciliation, interpolation, delta snapshots and input handling. A game module declares rules and cannot reach the netcode, so a rule change cannot break it.
How many players can one room hold?
It depends on the template: 16 for the arena shooter, 8 for the kart racer, platformer, party game, idle game and monster collector, 4 for the survivor-like, dungeon crawl and tower defense, and 2 for the card battler.
What tick rate does the server run at?
30 Hz for most templates and 60 Hz for the arena shooter. The local host sends snapshots 20 times a second and clients draw other players about 100 ms in the past.
Can I host the server myself?
The public repository includes the local room host in @netforge/kit-play, which is built for playing on your own computer and network. Production hosting with lag compensation and run verification is the hosted Net Forge service. See Deploy and host your game.
Does it work on phones?
Yes. Every template has touch controls, and npm run play -- --host 0.0.0.0 lets a phone on the same network join a room on your computer.