# How to build a multiplayer browser game with an authoritative server

> Start from a finished multiplayer game with one command: the server runs the simulation, every browser predicts its own player from the same code, and you change the rules in one TypeScript file.

Net Forge, the open-source multiplayer browser game engine built for AI coding agents, is published on npm under `@netforge` with source at https://github.com/netnetdev/net-forge.

Source: https://forge.netnet.capital/docs/guides/multiplayer-browser-game  
Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0.

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.

```sh
npm create netforge@latest my-game -- --template arena --yes
cd my-game
npm run play -- --players 2
```

That 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

```sh
npm run play -- --players 3
npm run play -- --host 0.0.0.0
```

The 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.

```ts title=src/game.ts run
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

```sh
npm test
npm run check
```

`npm 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-sim` at 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-play` resolves beams against the present, which is exact at local latency.

More detail: [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md) and [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md).

## Alternatives

- **[Colyseus](https://docs.colyseus.io/)** 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](https://heroiclabs.com/docs/nakama/getting-started/)** is an open-source game backend with accounts, social features, matchmaking and [authoritative multiplayer](https://heroiclabs.com/docs/nakama/concepts/multiplayer/authoritative/). Pick it when you need a full backend with user accounts and storage.
- **[Socket.IO](https://socket.io/docs/v4/)** 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](https://docs.partykit.io/)** 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](https://forge.netnet.capital/docs/guides/deploy-and-host.md).

### 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.
