# Net Forge documentation (full text) Generated from https://forge.netnet.capital/docs on each build. Each page starts with a level-one heading and its source URL. The API reference is not included; its pages are listed in https://forge.netnet.capital/docs/llms.txt. --- # Net Forge documentation > Net Forge is the open-source multiplayer browser game engine built for AI coding agents, published on npm under @netforge. One simulation runs in the browser and on the server, a three.js renderer draws it, and ten finished multiplayer games are ready to start from with one command. 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 Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. Net Forge (two words) is the open-source multiplayer browser game engine built for AI coding agents: three.js plus the game engine, physics, entity system and authoritative multiplayer you would otherwise build yourself (`@netforge/engine`, `@netforge/engine-sim` with Rapier, and the `@netforge/kit-play` room host). It is an MIT-licensed TypeScript engine, published on npm under `@netforge` with source at [github.com/netnetdev/net-forge](https://github.com/netnetdev/net-forge). One simulation (ECS plus Rapier physics at a fixed tick, 30 Hz by default) runs in the browser and on an authoritative server, a three.js renderer draws it with WebGPU and a WebGL2 fallback, and ten finished multiplayer games are ready to start from with one command. ```sh npm create netforge@latest my-game -- --template survivor --yes cd my-game npm run play ``` ## What makes it different - **One simulation on both sides.** The same `@netforge/engine-sim` code steps the world on the server and predicts it in the browser, so there is no second copy of the rules to keep in sync. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md). - **Ten finished multiplayer games.** Each template is a complete game with netcode, rendering, touch controls, bots and a store already working. You change the rules, not the plumbing. See [Templates](https://forge.netnet.capital/docs/templates.md). - **Built for your own coding agent.** A game is one typed, declarative file (`src/game.ts`) that is validated with one actionable line per mistake, and a headless bot playtest checks every change. See [Let an AI agent build your game](https://forge.netnet.capital/docs/guides/build-a-game-with-an-ai-agent.md). ## Start here | If you want to | Read | |---|---| | Run a game in two minutes | [Quickstart](https://forge.netnet.capital/docs/quickstart.md) | | Pick a genre | [Templates](https://forge.netnet.capital/docs/templates.md) | | Do one specific job | [Guides](https://forge.netnet.capital/docs/guides.md) | | Understand the engine | [Concepts](https://forge.netnet.capital/docs/concepts.md) | | Decide between tools | [Compare and migrate](https://forge.netnet.capital/docs/compare.md), or the [3D multiplayer engines roundup](https://forge.netnet.capital/docs/compare/3d-multiplayer-engines.md) | | Look up a function | [API reference](https://forge.netnet.capital/docs/reference.md) | ## The packages | Package | What it holds | |---|---| | `@netforge/engine` | The typed game API (`game`, `prefab`, `weapon`, `system`, `earn`, `store`), the three.js runtime and the client netcode. | | `@netforge/engine-sim` | The shared simulation: ECS, Rapier physics, fixed tick, bit-exact snapshots. | | `@netforge/kit-play` | Runs a game folder locally with play money: builds the client and starts a local room host. | | `@netforge/template-*` | The ten templates: each game module, its browser client and its playtest bots. | | `@netforge/asset-library` | CC0 models and sounds addressed by id, in six art styles. | | `create-netforge` | `npm create netforge@latest`: a new game folder from a template. | All of it is MIT licensed and published from [github.com/netnetdev/net-forge](https://github.com/netnetdev/net-forge). The library assets are CC0. ## Play money and the optional economy Everything in the public repository runs with play money. Games made in the hosted Net Forge studio can also launch with their own token and rewards; that economy runs only on the hosted service, decided by its server, and is optional. See [Add an in-game store and currency](https://forge.netnet.capital/docs/guides/in-game-store-and-currency.md). ## For agents Every page is also served as markdown at its URL plus `.md` (for example [/docs/quickstart.md](https://forge.netnet.capital/docs/quickstart.md.md)). The index is at [/docs/llms.txt](https://forge.netnet.capital/docs/llms.txt.md) and the full text at [/docs/llms-full.txt](https://forge.netnet.capital/docs/llms-full.txt.md). ## FAQ ### What is Net Forge? Net Forge is an MIT-licensed TypeScript engine and set of templates for multiplayer browser games. It pairs a deterministic simulation that runs on both the server and the client with a three.js renderer and a declarative game API. ### Is Net Forge free? Yes. The engine, simulation, templates, local play tool and asset library are free and open source under MIT, and the assets are CC0. ### What kinds of games can I make? Ten genres ship as templates: survivor-like, arena shooter, tower defense, dungeon crawl, idle, platformer, card battler, monster collector, kart racer and party game. Each can be reskinned in six art styles. ### Does Net Forge use WebGPU? Yes. The renderer uses three.js with WebGPU and falls back to WebGL2 on browsers without it. ### Is Net Forge related to other projects named NetForge? No. Net Forge, the open-source multiplayer browser game engine built for AI coding agents, is a separate project from anything else called NetForge, such as .NET starter templates or network simulators. Its packages are the ones under `@netforge` on npm, plus `create-netforge` and `netforge`, and its source is at github.com/netnetdev/net-forge. --- # Net Forge quickstart > Start a multiplayer browser game with one command, play it in two browser tabs, change the rules in src/game.ts and run the headless playtest bots. Everything runs on your computer with play money. 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/quickstart Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Run `npm create netforge@latest my-game -- --template survivor --yes`, then `cd my-game && npm run play`. You get a four-player co-op survivor-like that opens at `http://127.0.0.1:4663`; open the same link in a second tab and you are two players in one room, with the server running on your machine. ```sh npm create netforge@latest my-game -- --template survivor --yes cd my-game npm run play ``` You need Node.js 20 or newer. No account, wallet or API key is involved: local play uses play money and talks to no Net Forge server. ## What the command makes | File | What it is | |---|---| | `src/game.ts` | The whole game: declarations against `@netforge/engine` (enemies, weapons, waves, earn rules, store items). This is the file you and your agent edit. | | `llms.txt` | How to change this template, written for coding agents. | | `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/netforge.mdc` | The same guide for Claude Code, Cursor, Copilot and other agents. | | `.mcp.json` | Connects MCP-capable agents to the Net Forge MCP server. | | `netnet.json` | Names the template (`"template": "survivor"`). | | `package.json` | Scripts `play`, `test`, `check` and `typecheck`, and the engine and template packages. | The template's renderer, netcode, physics and bots live in the installed packages, not in your folder. That is deliberate: `src/game.ts` may import only `@netforge/engine`, so a change can never break the netcode. ## 1. Pick a template `--template` takes any of the ten ids. `npm create netforge@latest -- --list` prints them. | Id | Genre | Players per room | |---|---|---| | `survivor` | survivor-like co-op, up to 2,000 enemies | 4 | | `arena` | arena shooter with bots that fill empty seats | 16 | | `tower-defense` | co-op tower defense | 4 | | `dungeon` | co-op dungeon crawl with procedural rooms | 4 | | `idle` | shared idle and incremental game | 8 | | `platformer` | 3D platformer race | 8 | | `cards` | card battler, one against one or against the AI | 2 | | `collector` | monster collector with turn-based battles | 8 | | `racer` | kart racer | 8 | | `party` | party game with minigame rounds | 8 | Each template has its own page under [Templates](https://forge.netnet.capital/docs/templates.md). ## 2. Play it with more than one player `npm run play` builds the template's browser client with your `src/game.ts`, starts a local authoritative room host on the same port and opens your browser. Every tab you open on that link joins the same room. ```sh npm run play -- --players 2 npm run play -- --host 0.0.0.0 ``` The first line opens two tabs at once. The second lets phones and laptops on your network join through your computer's address. Stop it with Ctrl+C. ## 3. Change the game Open `src/game.ts` and change a number. For the survivor-like, this makes a fast enemy that joins the swarm after two and a half minutes: ```ts title=src/game.ts import { prefab } from "@netforge/engine"; prefab("wisp", { model: "style.lowpoly.bat", health: 8, speed: 5, radius: 0.25, damage: 4, ai: "chase", drops: ["ember_small"], }); ``` Then add it to the director's `pool` in the same file: `{ prefab: "wisp", from: 150, weight: 3 }`. Run `npm run play` again to see it. A declaration the engine cannot use fails at once with one line that says what to fix, for example `model: reference assets by library id (e.g. "style.lowpoly.bat"), never by URL or path`. ## 4. Test it without a browser ```sh npm test npm run check ``` `npm test` compiles `src/game.ts` the way the play build does and plays it headless with bots: kills, waves, levels reached, earn rules fired, softlocks and the server cost per tick. `npm run check` prints the same report as JSON, which is what a coding agent should read. On an Apple M2 Pro the survivor template holds 2,000 enemies with four bots at 2.2 ms per tick on average (p99 5.2 ms), against a 33 ms budget at the 30 Hz tick rate. ## 5. Hand it to your coding agent The folder already carries `AGENTS.md` and an MCP config, so Claude Code, Cursor or Copilot can work in it straight away. Ask for a change in plain words, such as "add a fast enemy that appears after two minutes", and have the agent run `npm run check` after each edit. See [Let an AI agent build your game](https://forge.netnet.capital/docs/guides/build-a-game-with-an-ai-agent.md). ## Without the create command The same thing by hand, using the published `@netforge/kit-play` package directly. A game folder is two files: `netnet.json` naming the template and `src/game.ts`. ```sh mkdir -p my-game/src && cd my-game echo '{"template":"survivor"}' > netnet.json npm init -y && npm install @netforge/kit-play npx kit-play ``` `npx kit-play` with no `src/game.ts` stops with a message. Copy a starting `game.ts` from the template's package (`node_modules/@netforge/template-survivor/src/game.ts`) into `src/` first. ## FAQ ### Do I need a wallet, an account or crypto to use Net Forge? No. Everything in the public repository runs on your computer with play money. Coins in local play are play money and nothing pays out. The on-chain economy (a game token and rewards) is an optional upgrade that runs only on the hosted Net Forge service. ### What does npm run play actually start? It builds the template's three.js client with your `src/game.ts`, then starts one Node process that serves the page and runs an authoritative room host on port 4663. The host runs the same simulation as the browser at the template's fixed tick (30 Hz for the survivor-like) and sends delta snapshots 20 times a second. ### Can friends join from another computer? On your own network, yes: run `npm run play -- --host 0.0.0.0` and share your computer's address. To put a game on the internet, see [Deploy and host your game](https://forge.netnet.capital/docs/guides/deploy-and-host.md). ### Which Node.js version do I need? Node.js 20 or newer. The engine packages declare `"node": ">=20"`. ### Why can src/game.ts import only @netforge/engine? So that the game stays a description the engine can validate, replay and run on the server. Rendering, netcode and physics live in the template packages; the game declares what exists and the rules, and the template does the rest. ### Can I use pnpm, yarn or bun? Yes. Run `pnpm create netforge my-game` (or yarn or bun), or pass `--pm pnpm`. The project's scripts work the same way with each. ### Is Net Forge related to other projects named NetForge? No. This Net Forge is the open-source multiplayer browser game engine built for AI coding agents, with packages under `@netforge` on npm and source at github.com/netnetdev/net-forge. It is not related to .NET starter templates, network simulators or other projects with a similar name. --- # Set up Net Forge for your coding agent (MCP server, AGENTS.md, skills) > Run npx netforge setup agent in a game folder to write AGENTS.md, CLAUDE.md, Cursor rules and MCP configs, or add the @netforge/mcp server to Claude Code, Cursor, VS Code, Codex or Gemini CLI with one line. The server lists templates, scaffolds games, searches the docs, validates game modules and runs bot playtests, with no keys and no network. 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/agents Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Run `npx netforge setup agent` in your game folder. It writes `AGENTS.md`, `CLAUDE.md`, Cursor rules and MCP configs for Claude Code, Cursor and VS Code, merged with whatever you already have, and prints the install line for agents that keep their MCP config elsewhere. ```sh npx netforge setup agent ``` A project made with `npm create netforge@latest` already has these files, so you only need this command for a folder you made another way. ## Add the MCP server to your agent The `@netforge/mcp` server runs on your computer over stdio. It needs no account, no API key, no chain and no network. Claude Code: ```sh claude mcp add netforge -- npx -y @netforge/mcp ``` Cursor: [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=netforge&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBuZXRmb3JnZS9tY3AiXX0=), or put this in `.cursor/mcp.json` in the project (or `~/.cursor/mcp.json` for every project): ```json { "mcpServers": { "netforge": { "command": "npx", "args": ["-y", "@netforge/mcp"] } } } ``` VS Code and GitHub Copilot, in `.vscode/mcp.json`: ```json { "servers": { "netforge": { "type": "stdio", "command": "npx", "args": ["-y", "@netforge/mcp"] } } } ``` Codex and Gemini CLI: ```sh codex mcp add netforge -- npx -y @netforge/mcp gemini mcp add netforge npx -y @netforge/mcp ``` The Gemini line was checked with Gemini CLI 0.65.0: it stores `npx` with the arguments `-y @netforge/mcp`. Do not put `--` before `npx` there; Gemini CLI then reports "Not enough non-option arguments". The same server in `.gemini/settings.json` (or `~/.gemini/settings.json`): ```json { "mcpServers": { "netforge": { "command": "npx", "args": ["-y", "@netforge/mcp"] } } } ``` Any other MCP client can run the `netforge-mcp` command over stdio. The server is listed in the MCP registry as `io.github.netnetdev/net-forge`. ## What the MCP server can do | Tool | What it does | |---|---| | `netforge_list_templates` | The ten genre templates: id, genre, players per room, package and the declarations each uses. | | `netforge_create_game` | A new game folder from a template, with `AGENTS.md`, `CLAUDE.md` and MCP configs. | | `netforge_search_docs` | Ranked sections from every package's `llms.txt`, README, `AGENTS.md` and the game design rules. | | `netforge_explain_api` | One API name (`weapon`, `director`, `earn.rule`, `createPhysics`) with its typed declaration and doc comment. | | `netforge_validate_game` | Compiles `src/game.ts` with its template and returns each error with its stage, line and a hint. | | `netforge_run_playtest` | Runs the bot playtest: kills, deaths, waves, softlocks, earn rules fired, ms per tick and design findings. | | `netforge_play_locally` | Starts local play for the folder and returns the URL (play money; more tabs are more players). | Validation and playtests run the game's code in a child Node process with a time limit and your permissions, the same as `npm run play`. Run only game code you trust. ## What setup agent writes | File | For | |---|---| | `AGENTS.md` | Every agent: where the game lives, the rules, how to validate and playtest. Added as a section when the file exists. | | `CLAUDE.md` | Claude Code: imports `AGENTS.md`. | | `.cursor/rules/netforge.mdc` | Cursor rules. | | `.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json` | The `@netforge/mcp` server, merged next to your other servers. | [AGENTS.md](https://agents.md/) is a plain markdown file that coding agents read for project instructions; Claude Code reads `CLAUDE.md`, which here imports it. ## Agent Skills The `netforge` package also ships four Agent Skills (folders with a `SKILL.md`): creating a game, adding content, playtesting and playing locally. See [Agent Skills for Net Forge](https://forge.netnet.capital/docs/skills.md). ## Docs for agents Every docs page is markdown at its URL plus `.md`, and a request with `Accept: text/markdown` gets the markdown too. The index is [/docs/llms.txt](https://forge.netnet.capital/docs/llms.txt.md) and the whole set is [/docs/llms-full.txt](https://forge.netnet.capital/docs/llms-full.txt.md). Each template folder also carries its own `llms.txt`. ## FAQ ### Which coding agents work with Net Forge? Any agent that can run shell commands works with the CLI (`npm create netforge`, `npm test`, `npm run check`). Agents that support MCP, such as Claude Code, Cursor, VS Code with Copilot, Codex and Gemini CLI, can also use the `@netforge/mcp` server. ### Does the Net Forge MCP server need an API key? No. It runs locally over stdio and makes no network calls of its own. It reads the docs bundled in the packages and runs the game's code on your computer. ### What is the difference between the MCP server and npx netforge setup agent? The MCP server is the set of tools the agent calls. `npx netforge setup agent` writes the files that tell agents about the project and connect them to that server. ### Does setup agent overwrite my AGENTS.md or MCP config? No. It adds a section to an existing `AGENTS.md` and merges the `netforge` server next to the servers already in each MCP config. --- # Agent Skills for Net Forge (SKILL.md) > The netforge package ships four Agent Skills, each a folder with a SKILL.md, that teach a coding agent to create a Net Forge game, add content, playtest after every change and play the game locally. Install them with npx skills add netnetdev/net-forge or copy them with npx netforge skills copy. 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/skills Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. The `netforge` package ships four Agent Skills, each a folder with a `SKILL.md` that an agent loads when the task matches its description. Together they cover the whole loop: create a game from a template, change `src/game.ts`, validate and playtest with bots, and play it in the browser. ```sh npx skills add netnetdev/net-forge ``` Or copy them yourself into your agent's skills folder: ```sh npx netforge skills list npx netforge skills copy .claude/skills ``` ## The four skills | Skill | When the agent uses it | |---|---| | `netforge-create-game` | The user asks to make, scaffold or start a browser game, a multiplayer game, a co-op game or an .io-style game. It picks one of the ten templates and runs `npm create netforge@latest`. | | `netforge-add-content` | The user asks to add an enemy, a boss, a weapon, a power-up, a reward or to rebalance the game. It edits `src/game.ts` with the typed declarations. | | `netforge-playtest` | After every change, or when the user asks whether the game works, is fun or is balanced. It validates the game module and runs headless bot playtests. | | `netforge-play-locally` | The user wants to try or see the game, or test multiplayer in several tabs. It starts local play with play money. | ## What a skill looks like A skill is a folder with a `SKILL.md`: a short front matter with a `name` and a `description` that says when to use it, then the steps and rules. This is the start of `netforge-create-game`: ```text --- name: netforge-create-game description: Start a new multiplayer browser game with Net Forge from one of ten finished genre templates (survivor-like, arena shooter, tower defense, dungeon crawl, idle, platformer, card battler, monster collector, kart racer, party). Use when the user asks to make, scaffold or start a browser game, a multiplayer game, a co-op game or an .io-style game. --- # Create a Net Forge game 1. Pick the template that matches the request. 2. Create the folder: npm create netforge@latest my-game -- --template survivor --yes 3. cd my-game && npm install 4. Read AGENTS.md in the new folder, then make the user's changes in src/game.ts. 5. Validate and playtest, then let the user play it. ``` Skills work alongside the MCP server: with it, the skills call tools such as `netforge_create_game` and `netforge_run_playtest`; without it, they use the CLI. See [Set up your coding agent](https://forge.netnet.capital/docs/agents.md). ## FAQ ### What are Agent Skills? Folders of instructions, each with a `SKILL.md`, that a coding agent loads on demand when a task matches the skill's description. Claude Code and other agents that support the format read them from a skills folder. ### Where do I put the Net Forge skills for Claude Code? In `.claude/skills` in your project, or `~/.claude/skills` for every project: `npx netforge skills copy .claude/skills`. ### Do I need the skills if I use the MCP server? No, but they help. The MCP server gives the agent tools; the skills tell it when and in what order to use them, and what the rules of `src/game.ts` are. ### Do the skills need a network connection or an API key? No API key. They are plain markdown. Installing them and the packages needs the network once; after that, the commands they run work offline. --- # Net Forge docs changelog > What changed in the Net Forge docs and the package versions they were checked against, newest first. 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/changelog Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. What changed in these docs, newest first. Every page also shows its own "Last updated" date, and every code sample is typechecked against the package versions named here on each build. ## 2026-10-23 - First release of the docs at forge.netnet.capital/docs, checked against `@netforge/engine` 0.2.0, `@netforge/engine-sim` 0.2.0 and `@netforge/kit-play` 0.2.0. - Quickstart around `npm create netforge@latest`, one page per template, job guides, concepts, comparisons with Phaser, Colyseus, Godot web export, PlayCanvas, Babylon.js, three.js and boardgame.io, migration pages, and an API reference generated from the packages' TypeScript. - Coding-agent setup: `npx netforge setup agent`, the `@netforge/mcp` server and Agent Skills. - Every page is available as markdown at its URL plus `.md`, and the site publishes `/docs/llms.txt` and `/docs/llms-full.txt`. --- # Net Forge templates > Ten finished multiplayer browser games to start from, one per genre. Each one starts with a single command and plays locally with play money. 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/templates Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Each template is a complete multiplayer game: rules in one `src/game.ts`, plus the browser client, netcode, bots and playtest that the package provides. Start any of them with `npm create netforge@latest my-game -- --template `. - [Survivor-like (EMBERWATCH)](https://forge.netnet.capital/docs/templates/survivor-like.md): EMBERWATCH is a four-player co-op survivor-like that holds up to 2,000 enemies at once in the browser: auto-firing weapons, XP gems, level-up cards, evolutions, bosses and co-op revives. Start it with one command and change it in src/game.ts. - [Arena shooter (VOLTYARD)](https://forge.netnet.capital/docs/templates/arena-shooter.md): VOLTYARD is a twin-stick arena shooter for up to 16 players with a server-authoritative room at 60 Hz, lag-compensated beams, loadouts, rounds and bots that fill empty seats. Start it with one command and change it in src/game.ts. - [Tower defense (COGSPIRE)](https://forge.netnet.capital/docs/templates/tower-defense.md): COGSPIRE is a co-op tower defense for up to four players on one shared board: a winding road, twenty waves, four tower kinds with upgrades, shared lives and per-player gold. Start it with one command and change it in src/game.ts. - [Dungeon crawler (CRYPTWICK)](https://forge.netnet.capital/docs/templates/dungeon-crawler.md): CRYPTWICK is a four-player co-op dungeon crawl with procedural rooms grown from a seed, walls and doors, enemies that path around them, room-by-room encounters, upgrade chests and permadeath. Start it with one command and change it in src/game.ts. - [Idle game (BRINEWORKS)](https://forge.netnet.capital/docs/templates/idle-game.md): BRINEWORKS is a shared idle game for up to eight players: each runs a production chain on one salt flat, buys and levels plants, and everyone fills one contract together during a fixed-length shift. Start it with one command and change it in src/game.ts. - [Platformer (SKYLEDGE)](https://forge.netnet.capital/docs/templates/platformer.md): SKYLEDGE is a side-view platformer race for up to eight players with coyote time, jump buffering, variable jump height, an air dash, springs, moving platforms, per-player coins and seat bots that run planned routes. Start it with one command and change it in src/game.ts. - [Card battler (INKWRIT)](https://forge.netnet.capital/docs/templates/card-battler.md): INKWRIT is a one-against-one online card battler: decks shuffled on the server, hidden hands, mana, units and spells, guards and rush, a draft between duels, and a gauntlet of AI opponents for a player alone. Start it with one command and change it in src/game.ts. - [Monster collector (MOSSBOUND)](https://forge.netnet.capital/docs/templates/monster-collector.md): MOSSBOUND is a multiplayer monster collector for up to eight players: pick a starter, walk a shared marsh, meet wild creatures in tall grass, battle turn by turn with a team of three, catch the ones you want and bring down a shared boss before dusk. Start it with one command and change it in src/game.ts. - [Kart racer (TORQUEBAY)](https://forge.netnet.capital/docs/templates/kart-racer.md): TORQUEBAY is an online kart racer for up to eight players: arcade kart physics at 60 Hz, drift mini-turbos in three tiers, boost pads, coins, laps through ordered checkpoints, a streamed track and bots that drive the racing line. Start it with one command and change it in src/game.ts. - [Party game (ROMPHOUSE)](https://forge.netnet.capital/docs/templates/party-game.md): ROMPHOUSE is a co-op party game for two to eight players on phones and laptops: one playroom, five quick minigame rounds a show, auto-firing weapons, revives and a finale boss. Start it with one command and change the minigames in src/game.ts. --- # Survivor-like co-op template (EMBERWATCH) > EMBERWATCH is a four-player co-op survivor-like that holds up to 2,000 enemies at once in the browser: auto-firing weapons, XP gems, level-up cards, evolutions, bosses and co-op revives. Start it with one command and change it in src/game.ts. 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/templates/survivor-like Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. EMBERWATCH is a complete co-op survivor-like for up to four players: weapons fire by themselves, enemies drop XP gems, every level offers three upgrade cards, and the swarm grows each minute to as many as 2,000 enemies before the final boss. Start your own copy with one command; the rules live in one file, `src/game.ts`. ```sh npm create netforge@latest my-game -- --template survivor --yes cd my-game npm run play ``` `npm run play` opens `http://127.0.0.1:4663`. Open the same link in a second tab, or on a phone on your network, and you are two keepers in one room. Keys: WASD or arrows to move, Space to dash, 1 2 3 to pick a card. On touch, drag on the left half of the screen. ## What you get | Part | What it does | Where | |---|---|---| | Spawn director | Spawns every enemy just off screen on the run's clock, with curves for count, rate, health, damage and speed | `director({...})` in `src/game.ts` | | Enemy roles | chase, tank, runner, charger, ranged, bomber, shielder, healer, summoner, flee, splitters, elites | `prefab(...)` with `ai` | | Weapons | nova, aura, bolt, orbit, cone, chain, meteor, boomerang; 5 to 8 levels each; evolutions | `weapon(...)` | | Passives | speed, maxHealth, magnet, might, haste, regen, armor, area, amount (up to 8) | `upgrade(...)` | | Bosses | Two mini-bosses and a final boss with telegraphed attacks | `prefab(..., { boss })` | | Co-op | Shared XP nearby, downed state, revive by standing close | `players` in `game({...})` | | Netcode | Authoritative room at 30 Hz, your keeper predicted, delta snapshots | the template package | | Playtest bots | Plays the game headless and reports kills, waves, levels and earn | `npm test` | Measured with the template's own playtest (`engine.test.run`) on an Apple M2 Pro, Node 22: four bots for three minutes cleared 1,350 enemies at 0.27 ms per server tick on average (p99 0.64 ms); the stress run held 2,000 enemies with four bots at 2.22 ms per tick on average (p99 5.21 ms). At 30 Hz the tick budget is 33 ms. ## 1. Add an enemy Declare it in `src/game.ts`. Health is in hit points, speed in metres per second (keepers run at 7), radius in metres. ```ts title=src/game.ts import { prefab } from "@netforge/engine"; prefab("wisp", { model: "style.lowpoly.bat", health: 8, speed: 5, radius: 0.25, damage: 4, ai: "chase", drops: ["ember_small"], }); ``` Then add it to the director's `pool` from the minute it should join, for example `{ prefab: "wisp", from: 150, weight: 3 }`. A model is a library id such as `style.lowpoly.bat`, never a URL. ## 2. Add a weapon and its evolution Each level lists what changes from the one before. A number adds and a percent scales. At max level, with the paired passive taken, the evolution card always shows. ```ts title=src/game.ts import { upgrade, weapon } from "@netforge/engine"; weapon("frost_ring", { kind: "nova", damage: 6, cooldown: 0.8, range: 5, knockback: 8, levels: [{ damage: "+30%" }, { range: "+15%" }, { cooldown: "-15%" }, { damage: "+30%" }, { knockback: 3 }], evolve: { into: "blizzard", requires: "iron_skin" }, }); weapon("blizzard", { kind: "nova", damage: 20, cooldown: 0.6, range: 9, knockback: 12 }); upgrade("iron_skin", { stat: "armor", step: 1, maxLevel: 4 }); ``` A game holds at most 15 weapons (evolutions count) and 8 passives. Add display names in `src/names.json`. ## 3. Tune the difficulty Raise or lower the director's `alive`, `rate`, `health` or `damage` keyframes after the first minute, and keep the opening quiet. Each curve is a number or a list of `[seconds, value]` pairs, linear between them. More players raise `alive` and `rate` by 60% each; a living boss halves the spawn rate. ```ts title=src/game.ts import { director } from "@netforge/engine"; director({ start: 8, alive: [[0, 12], [60, 45], [180, 160], [300, 400], [540, 1600]], rate: [[0, 0.8], [60, 2], [180, 6], [300, 14], [540, 60]], health: [[0, 1], [60, 1.4], [180, 2.4], [540, 5.7]], damage: [[0, 1], [180, 1.45], [540, 2.3]], pool: [ { prefab: "shade", weight: 10 }, { prefab: "gnat", from: 45, weight: 2, group: [8, 12] }, { prefab: "husk", from: 90, weight: 3 }, ], }); ``` ## 4. Check the change with bots ```sh npm test ``` The playtest compiles `src/game.ts` the way the play build does, plays it with bots and fails on a softlock, on spawns that land on screen, or when no earn rule fires. The design rules it scores (a quiet opening, off-screen spawns, telegraphed big hits) are in the engine's `GAME-DESIGN.md`. ## How it works The game module only declares things: `game()`, `prefab()`, `weapon()`, `upgrade()`, `director()`, `system()`, `earn.rule()` and `store.sku()`. The template compiles it into content for the shared simulation (`@netforge/engine-sim`), which steps the world at 30 Hz on the room host and predicts your own keeper in the browser. Enemies are drawn as instanced crowds, so thousands cost a handful of draw calls; the renderer uses three.js with WebGPU and a WebGL2 fallback. Locally, `npm run play` runs an authoritative room host on your computer with play money. See [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). The template also declares earn rules (for example `first_light` after two waves) and store items in coins. In local play these fire and show but pay nothing. Paying them out in a game token is an optional upgrade that runs only on the hosted Net Forge service. The full walkthrough of this genre is [Make a survivor-like with thousands of enemies in the browser](https://forge.netnet.capital/docs/guides/horde-survivor-browser-game.md). ## Alternatives - **[Phaser](https://docs.phaser.io/)** is a mature 2D framework for HTML5 games with a very large example library. Pick it for a single-player 2D survivor-like where you want full control of the game loop and sprite art. - **[Godot](https://docs.godotengine.org/en/stable/)** is an open-source engine with an editor and a web export. Pick it if you want a visual editor, GDScript and the option to ship the same game on desktop and mobile. - **Net Forge** fits when you want four-player co-op with an authoritative server from the first minute, 3D crowds in the thousands, and a game file your coding agent can change safely. ## FAQ ### How many enemies can a browser survivor-like handle with Net Forge? The EMBERWATCH stress playtest holds 2,000 enemies with four players at about 2.2 ms per server tick on an Apple M2 Pro. The room sends each player only what is near them and draws enemies as instanced crowds. ### Is it multiplayer out of the box? Yes. Up to four players share one room. The room host is authoritative and each player's keeper is predicted in their own browser, so movement never waits for the server. ### Can I play it alone? Yes. One keeper can play a full run. The director scales enemy counts with the number of players in the room. ### Can I change the art style? Yes. `game({ style })` accepts lowpoly, toon, ps1, pixel, voxel and handdrawn, and the template draws every model in that style. See [Assets and art styles](https://forge.netnet.capital/docs/concepts/assets-art-styles.md). ### Do I need to write netcode or a renderer? No. `src/game.ts` may import only `@netforge/engine`, and the template provides the renderer, netcode, physics, input and HUD. --- # Arena shooter template (VOLTYARD) > VOLTYARD is a twin-stick arena shooter for up to 16 players with a server-authoritative room at 60 Hz, lag-compensated beams, loadouts, rounds and bots that fill empty seats. Start it with one command and change it in src/game.ts. 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/templates/arena-shooter Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. VOLTYARD is a complete twin-stick arena shooter for up to 16 players: move with one hand, aim with the other, swap between three weapons, and take the round by topping the board in three minutes or reaching 25 kills. Bots fill empty seats, so one player alone still gets a fight, and the room is server-authoritative at 60 Hz with lag compensation for hitscan beams. ```sh npm create netforge@latest my-game -- --template arena --yes cd my-game npm run play ``` Open `http://127.0.0.1:4663` in two tabs and you are two players in the same yard with the seat bots. Keys: WASD to move, mouse to aim, click to fire, 1 2 3 or Q to swap, Space to dash, Tab for the board. On touch, the left stick moves and the right stick aims and fires. ## What you get | Part | What it does | Where | |---|---|---| | Loadout | 1 to 4 weapons of kind `blaster` (with spread and pierce), `beam` (hitscan) or `nova` | `weapon(...)`, `players.loadout` | | The yard | Floor size, pillars as cover, spawn points | `arena` in `game({...})` | | Rounds | Round length and kill target; the top of the board takes the round | `round` in `game({...})` | | Seat bots | Fill empty seats while a person is in the room, adapt to that person's record, never earn | `players.bots` | | Spawns | Out of every opponent's sight (physics raycasts), 12 m clear, 2 s of protection | the template | | Netcode | 60 Hz authoritative room, your own player predicted, others interpolated about 100 ms in the past, beams rewound up to 200 ms | the template package | | Playtest bots | Four players plus seat bots for four minutes: kills, deaths, rounds, earn | `npm test` | Measured with the template's playtest on an Apple M2 Pro, Node 22: four players and two seat bots over four minutes (14,400 ticks at 60 Hz) traded 121 kills, finished a round with a top score of 25 and fired every earn rule, at 0.59 ms per server tick on average. The budget at 60 Hz is 16.7 ms. ## 1. Tune a weapon A `blaster` fires `count` projectiles over `spread` degrees at `speed` m/s for `range` m. A `beam` is an instant hit to `range` m that pillars stop; the server rewinds the other players to what the shooter saw, so keep its cooldown at 0.8 s or more. ```ts title=src/game.ts import { weapon } from "@netforge/engine"; weapon("arc_rifle", { kind: "blaster", damage: 12, cooldown: 0.15, range: 26, speed: 44 }); weapon("ion_cannon", { kind: "blaster", damage: 40, cooldown: 1.2, range: 30, speed: 22, pierce: 2 }); ``` Add `"ion_cannon"` to `players.loadout` (at most four; slot four is reached by cycling) and give it a label in `src/names.json`. ## 2. Change the yard and the round `arena` is `{ halfSize, pillars: [[x, z, radius], ...], spawns: [[x, z], ...] }`. A smaller yard with more pillars makes closer fights. `round: { seconds: 180, frags: 25 }` sets the round; 120 to 300 seconds plays well. `players.bots: 6` fills a room up to six with bots, and `0` turns them off. Spawns must sit 1 m inside the floor and 1 m clear of pillars, or the build says which one is wrong. ## 3. Track a kill streak with a system Systems are small functions that react to game events. `emit(event, ids)` credits those players, which earn rules can count. ```ts title=src/game.ts import { earn, system } from "@netforge/engine"; system("streaks", ({ on, emit }) => { const streak = new Map(); on("opponent.killed", (ids) => { for (const id of ids) { const n = (streak.get(id) ?? 0) + 1; streak.set(id, n); if (n === 5) emit("streak.five", [id]); } }); on("player.died", (ids) => { for (const id of ids) streak.set(id, 0); }); }); earn.rule("overload", { when: "streak.five", n: 1, pay: 12, oncePer: "run" }); ``` Killing a bot scores and shows in the kill feed but never counts toward an earn rule, and `round.top` counts only when another person was in the round. The engine enforces that for every arena game. ## 4. Check it with bots ```sh npm test ``` The playtest requires that four players plus seat bots all score kills, that a round ends within about four minutes, and that an earn rule fires. ## How it works Your own player (movement, aim, weapon, cooldowns) is predicted in the browser and reconciled with the server's snapshots. Everyone else is drawn about 100 ms in the past by interpolation. Rifle and scatter shots are server projectiles; the rail lance is a hitscan that the server resolves with lag compensation. The local room host that `npm run play` starts is simpler: it resolves hitscans against the current hitboxes, which is exact at local latency, and uses play money. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). This template is the base for [How to make a .io-style arena shooter](https://forge.netnet.capital/docs/guides/io-game.md) and [How to build a multiplayer browser game](https://forge.netnet.capital/docs/guides/multiplayer-browser-game.md). ## Alternatives - **[Colyseus](https://docs.colyseus.io/)** is an open-source multiplayer framework for Node.js with rooms, matchmaking and state sync, and client SDKs for many engines. Pick it when you already have a client engine and want a server framework to build your own netcode on. - **[PlayCanvas](https://developer.playcanvas.com/)** is a WebGL and WebGPU engine with a hosted visual editor. Pick it when your team wants to build scenes in an editor and bring its own networking. - **Net Forge** fits when you want a finished shooter with prediction, interpolation, lag-compensated beams and bots already working, and you want to change the rules rather than the plumbing. ## FAQ ### How many players does the arena shooter hold? Up to 16 people in one room, with seat bots filling empty places up to `players.bots`. ### Does it have lag compensation? Yes, on the hosted room server: beam weapons are resolved by rewinding the other players to what the shooter saw, capped at 200 ms. The local room host used by `npm run play` resolves them against current positions, which matches what you see on one computer. ### Do bots count toward earn rules? No. Bots never earn, killing a bot pays no earn rule, and topping a board of bots pays nothing. Earn rules only fire on what people do against people. ### Can I play it on a phone? Yes. The client has twin-stick touch controls, with SWAP and DASH buttons. Run `npm run play -- --host 0.0.0.0` to let a phone on your network join. ### Which weapon kinds can an arena game use? `blaster`, `beam` and `nova`. The build refuses survivor-only kinds such as `bolt` or `orbit` in an arena, with a line that says so. --- # Tower defense template (COGSPIRE) > COGSPIRE is a co-op tower defense for up to four players on one shared board: a winding road, twenty waves, four tower kinds with upgrades, shared lives and per-player gold. Start it with one command and change it in src/game.ts. 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/templates/tower-defense Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. COGSPIRE is a complete co-op tower defense: creeps walk a fixed road toward the spire in waves, and up to four players build and upgrade towers beside the road with their own gold. Every kill pays its tower's builder a bounty and the rest of the team a share, the team shares the spire's lives, and clearing all twenty waves wins the run. ```sh npm create netforge@latest my-game -- --template tower-defense --yes cd my-game npm run play ``` `--template tower` and `--template td` work too. Click a tile to build the tower picked in the bar (keys 1 to 4), click a tower to upgrade or sell it, and press Space to call the next wave. On touch, tap a tile to aim and tap again to build. ## What you get | Part | What it does | Where | |---|---|---| | The board | Size in tiles (a tile is 2 m), the road as tile waypoints, rocks nobody can build on, lives and starting gold | `towerBoard({...})` | | Towers | 1 to 8 towers of kind `bolt`, `splash`, `frost` or `chain`, each with upgrade levels | `tower(...)` | | Creeps | Health, speed, gold bounty, lives a leak costs, bosses with a health bar | `prefab(...)` | | Waves | Groups that join at a wave, grow each wave, and a boss every tenth wave | `waves({...})` | | Co-op | Shared lives, per-player gold, kill bounties shared with the team, a ready vote | the template | | Netcode | 30 Hz authoritative room; your cursor is predicted; builds, upgrades and sells are validated actions | the template package | | Playtest bots | Four builders for four minutes: kills, wave reached, leaks, earn | `npm test` | Measured with the template's playtest on an Apple M2 Pro, Node 22: four builders over four minutes (7,200 ticks at 30 Hz) killed 132 creeps and reached wave 8 with no leaks, at 0.008 ms per server tick on average. A board game's rules are integer state, so they cost far less than an action game's physics. ## 1. Change the road The road is a list of tile waypoints. Each step changes x or y, never both, and the road starts and ends on the board's edge. Longer roads with more turns give towers more time. ```ts title=src/game.ts import { towerBoard } from "@netforge/engine"; towerBoard({ size: [20, 12], path: [[0, 5], [6, 5], [6, 1], [13, 1], [13, 10], [19, 10]], lives: 20, gold: 100, }); ``` A game has exactly one `towerBoard(...)` and one `waves(...)`. ## 2. Add a tower and a creep Damage is per hit, `rate` is shots a second, and `range` and `radius` are in tiles. `splash` hits a radius around the target, `frost` slows by `slow` for `slowSeconds`, and `chain` jumps `chains` times, 30% weaker each jump. ```ts title=src/game.ts import { prefab, tower } from "@netforge/engine"; tower("sky_harpoon", { model: "style.lowpoly.rocket", kind: "bolt", cost: 120, damage: 60, rate: 0.4, range: 6, levels: [{ cost: 100, damage: 90 }], }); prefab("ironclad", { model: "style.lowpoly.robot_walker", health: 160, speed: 0.9, value: 14, damage: 3 }); ``` Bring the creep in by adding a group to `waves`, for example `{ prefab: "ironclad", from: 8, count: 2, grow: 0.5, gap: 2 }`, and give the tower a label in `src/names.json`. The bar shows towers in the order you declare them. ## 3. Make it harder or easier Raise `waves` health growth, group counts and `grow`, or lower starting gold and lives. A shorter `prep` and `breather` on the board makes it faster. More builders make tougher creeps (30% more health for each player past the first). ## 4. Check it with bots ```sh npm test ``` The playtest requires that four builders kill creeps, clear wave 3 and fire an earn rule within four minutes, and that the spire survives to wave 4. ## How it works Tower defense is a board genre: its rules are a deterministic module in `@netforge/engine/rules` (integer state, a seeded random number, no clocks) behind the same simulation interface as the action templates. That means the same room host, netcode, run verification and playtest serve it. Towers aim at the creep furthest along the road in range. Your cursor is predicted so pointing never waits for the server, and every build, upgrade, sell and ready vote is an action the room checks (gold, tile, ownership). See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md). The template also declares earn rules and store items priced in coins. Locally they show and pay nothing; paying them out in a game token is an optional upgrade on the hosted Net Forge service. The full guide for this genre is [Make a browser tower defense game](https://forge.netnet.capital/docs/guides/tower-defense-browser-game.md). ## Alternatives - **[Phaser](https://docs.phaser.io/)** is a 2D HTML5 framework with tilemap support and a large example library. Pick it for a single-player 2D tower defense with hand-drawn sprites. - **[Godot](https://docs.godotengine.org/en/stable/)** has a tilemap editor, navigation and a web export. Pick it when you want to lay out maps in an editor and ship to desktop as well. - **Net Forge** fits when you want four players on one board with server-checked builds, shared lives and bots that playtest every change. ## FAQ ### Can four people build on the same tower defense board? Yes. Up to four builders share one board, each with their own gold, and share the spire's lives. ### How do creeps find the path? They follow the road you declare as tile waypoints in `towerBoard({ path })`. Towers cannot be built on the road or on blocked tiles. ### Which tower types are built in? `bolt` (single target), `splash` (an area around the target), `frost` (slows) and `chain` (jumps between creeps). Each tower can declare upgrade levels with their own cost and stats. ### Can a player join a run that has already started? Yes. They build from that point on. A player who joins mid-run is never credited for waves cleared before they joined. ### Is there a boss wave? Yes. A creep declared with `boss: true` gets a health bar, and a wave group with `every: 10` brings one every tenth wave. --- # Co-op dungeon crawler template (CRYPTWICK) > CRYPTWICK is a four-player co-op dungeon crawl with procedural rooms grown from a seed, walls and doors, enemies that path around them, room-by-room encounters, upgrade chests and permadeath. Start it with one command and change it in src/game.ts. 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/templates/dungeon-crawler Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. CRYPTWICK is a complete co-op dungeon crawl for up to four players: the floor plan is grown from a seed (ten rooms joined by corridors by default), rooms wake as the party nears them, a cleared room leaves an upgrade chest and brings back anyone who fell, and the boss waits in the deepest room. Runs are permadeath: if the whole party is down at once, the descent is over. ```sh npm create netforge@latest my-game -- --template dungeon --yes cd my-game npm run play ``` Keys: WASD or arrows, Space to dash, 1 2 3 to pick a card. Open the link in a second tab to bring a second player down with you. ## What you get | Part | What it does | Where | |---|---|---| | Procedural floor plan | Rooms on a grid joined by corridors, from a seed; the same seed gives the same crypt on the server, every client and the playtest | `dungeon({...})` | | Walls and doors | Walls stop players, enemies and bolts; enemies behind a wall walk round through the doors on a flow field | `field.walls`, `field.floors` | | The crawl | Which rooms wake, what they hold by depth, off-screen spawns inside the woken room, reliquaries, the respawn on clear | the `crawl` system in `src/game.ts` | | Combat | Auto-firing weapons, level-up cards, elites, a mini-boss and a boss with telegraphed attacks | `weapon(...)`, `prefab(...)` | | Permadeath | Downed for 12 s, crawl to a teammate, out until the party clears a room | `players.downed` | | Netcode | 30 Hz authoritative room, your own character predicted | the template package | | Playtest bots | Four bots for four minutes: kills, rooms reached, levels, earn | `npm test` | Measured with the template's playtest on an Apple M2 Pro, Node 22: four bots over four minutes (7,200 ticks) killed 219 enemies, reached level 7 and lost two lives, at 0.40 ms per server tick on average (p99 0.77 ms). ## 1. Grow a different crypt `seed` is any whole number; a different seed is a different floor plan. `rooms` is 3 to 20 (about 10 makes a 6 to 10 minute descent), `cell` is room spacing in metres, `size` is the smallest and largest room side, `corridor` is the corridor width, and `loops` adds extra doors. ```ts title=src/game.ts import { dungeon } from "@netforge/engine"; const CRYPT = dungeon({ seed: 7, rooms: 14, cell: 28, size: [16, 22], corridor: 6, loops: 2 }); const deepest = CRYPT.boss; ``` Pass `field: CRYPT.field` to `game({...})`. The start room is centred on `[0, 0]`, `CRYPT.boss` is the deepest room, and `CRYPT.rooms` and `CRYPT.roomAt(x, z)` let the crawl system reason about rooms. `loops: 0` makes a pure tree where every dead end is a dead end. ## 2. Add an enemy Enemies use the same roles as the survivor-like: chase, tank, runner, charger, ranged, bomber, shielder, healer, summoner and flee. ```ts title=src/game.ts import { prefab } from "@netforge/engine"; prefab("bone_archer", { model: "style.lowpoly.skeleton", health: 24, speed: 2.8, radius: 0.4, damage: 5, ai: "ranged", attack: { damage: 9, range: 10, speed: 7 }, drops: ["wick_small"], }); ``` Then list it in the crawl system's `ENCOUNTERS` for the room depths where it should appear. Each extra player adds 35% to a room's count. ## 3. Soften permadeath ```ts title=src/game.ts import { dungeon, game } from "@netforge/engine"; const CRYPT = dungeon({ seed: 2026, rooms: 10, cell: 24, size: [12, 18], corridor: 6, loops: 2 }); export default game({ template: "dungeon", style: "lowpoly", places: { crypt: { chunks: "places/crypt", spawn: [0, 1, 0] } }, field: CRYPT.field, players: { prefab: "style.lowpoly.rogue", max: 4, weapon: "candle_burst", // One free get-up a run; the template ships with selfRevives: 0. downed: { seconds: 12, crawl: 0.35, selfRevives: 1, respawn: 0 }, }, }); ``` In your copy, change only the `downed` line inside the existing `game({...})` call; the rest of its `players` block (XP curve, magnet, crits) stays as it is. A respawn time in seconds makes it softer still. ## 4. Check it with bots ```sh npm test ``` ## How it works CRYPTWICK runs on the same machinery as the survivor-like (auto-firing weapons, level-up cards, co-op downs and revives, bosses with telegraphed attacks) plus walls. The walls are boxes in the shared simulation: players meet them in Rapier physics, and enemy pathing uses a flow field through the doors, with a wider grid for big bodies. The crawl itself is a system in `src/game.ts`, not hidden machinery, so you and your agent can read and change how rooms wake and what they hold. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md). The full guide is [Make a dungeon crawler with procedural rooms in TypeScript](https://forge.netnet.capital/docs/guides/dungeon-crawler-typescript.md). ## Alternatives - **[Phaser](https://docs.phaser.io/)** is a 2D HTML5 framework with tilemaps and a large example library. Pick it for a 2D, single-player roguelike with tile art. - **[Godot](https://docs.godotengine.org/en/stable/)** is an open-source engine with an editor, navigation meshes and a web export. Pick it when you want hand-built levels in an editor and desktop builds. - **Net Forge** fits when you want a seeded 3D crypt that four players explore together over an authoritative server, and a crawl you can rewrite in one file. ## FAQ ### How are the dungeon rooms generated? `dungeon({ seed, rooms, cell, size, corridor, loops })` places rooms on a grid, joins them with corridors and adds loop doors. The same spec gives the same floor plan everywhere, so nothing is downloaded and every client agrees with the server. ### Do enemies path around walls? Yes. Enemies behind a wall walk round through the doors on a flow field computed in the shared simulation, big bodies included. ### Is it permadeath? By default, yes: a fallen player can crawl to a teammate to be revived, and stays out until the party clears a room. If the whole party is down at once, the run ends. `selfRevives` and `respawn` make it softer. ### Can I make it an eight-player crawl? The template's llms.txt has a section on making co-op eight players. Each extra player already adds 35% to every room's encounter. ### Can my agent change how rooms work? Yes. The crawl is a system declared in `src/game.ts`, so room wakes, encounters by depth, the quiet timer and the reliquaries are all ordinary code in the game file. --- # Idle and incremental game template (BRINEWORKS) > BRINEWORKS is a shared idle game for up to eight players: each runs a production chain on one salt flat, buys and levels plants, and everyone fills one contract together during a fixed-length shift. Start it with one command and change it in src/game.ts. 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/templates/idle-game Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. BRINEWORKS is a complete multiplayer idle game: up to eight players each run a works on one salt flat, where pumps draw brine, pans boil it into salt, kilns cut salt into crystals, and salt buys and levels more plants. Everyone ships crystals to one shared contract, and a shift lasts fifteen minutes before a fresh one starts. ```sh npm create netforge@latest my-game -- --template idle --yes cd my-game npm run play ``` Space rakes salt by hand (or click your plot), keys 1 to 5 buy a plant, and S ships crystals. Open the link in more tabs to see several works on the same flat. ## What you get | Part | What it does | Where | |---|---|---| | The flat | Shift length, how long a works runs after its last tend, raking, conversion ratios, the shared contract | `saltFlat({...})` | | Plants | 1 to 8 plants of kind `pump`, `pan` or `kiln`, each with a cost, a price growth per level and a rate | `plant(...)` | | Upgrades | Plant output (`might`), raking speed (`haste`), salt per rake (`amount`) | `upgrade(...)` | | Shared goal | One contract barge everyone ships crystals to | `saltFlat({ contract })` | | Fairness rules | No offline accrual, works stall when untended, a raking cooldown, a minimum time before the first payout | enforced by the build | | Playtest bots | Four players for four minutes, plus a check of the fastest possible first payout | `npm test` | Measured on an Apple M2 Pro, Node 22: four bots over four minutes (7,200 ticks at 30 Hz) packed 172 crates and each fired the first earn rule, at 0.003 ms per server tick on average. ## 1. Tune progress `cost` is kg of salt for the first level, `growth` is the price rise per level, and `rate` is output per second per level (litres of brine for pumps, kg of salt for pans, crystals for kilns). A lower growth keeps levels cheap for longer. ```ts title=src/game.ts import { plant } from "@netforge/engine"; plant("hand_pump", { model: "style.lowpoly.barrel", kind: "pump", cost: 15, growth: 1.15, rate: 3 }); plant("deep_bore", { model: "style.lowpoly.generator_large", kind: "pump", cost: 5000, growth: 1.2, rate: 200 }); ``` A game needs at least one pump, one pan and one kiln. Give a new plant a label in `src/names.json`; the bar shows plants in the order you declare them. ## 2. Change the shift and the contract ```ts title=src/game.ts import { saltFlat } from "@netforge/engine"; saltFlat({ shift: 1200, tend: 120, start: 20, rake: { salt: 3, cooldown: 0.4 }, brinePerSalt: 3, saltPerCrystal: 25, packEvery: 150, contract: { crystals: 120 }, }); ``` A shift is 600 to 3,600 seconds, and `tend` is at most 300 seconds: a works nobody tends stops, so an idle tab makes nothing. ## 3. Add an earn rule ```ts title=src/game.ts import { earn } from "@netforge/engine"; earn.rule("first_crate", { when: "salt.packed", n: 30, pay: 5, oncePer: "run" }); ``` Events are `salt.packed`, `crystal.cut`, `crystal.shipped`, `plant.built`, `plant.upgraded` (each credits the player) and `contract.filled`. The build simulates a fresh works with perfect play and with pure raking, and fails a rule that would pay sooner than 180 seconds in, printing the number. Raise `packEvery` or the rule's `n` if it does. ## 4. Check it with bots ```sh npm test ``` ## How it works This is a board genre: the rules run as a deterministic module in `@netforge/engine/rules` on the room's clock, with integer state and a seeded random number. Production happens only on the room's ticks while a player is in the room. Nothing accrues offline, and a player who leaves and comes back starts a fresh works. That is a deliberate design choice for a shared, server-checked idle game, and it differs from single-player incremental games that keep progress while closed. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md). Earn rules fire locally and pay nothing. Paying them in a game token is an optional upgrade on the hosted Net Forge service. The full guide is [Build an idle or incremental game](https://forge.netnet.capital/docs/guides/idle-game.md). ## Alternatives - **[Phaser](https://docs.phaser.io/)** is a 2D HTML5 framework. Pick it for a single-player idle game with sprite art and your own save format. - **A plain web stack** (a UI framework plus local storage) is often all a single-player clicker needs, and keeps offline progress simple. - **Net Forge** fits when several players should share one world, fill one goal together, and have every number computed on the server. ## FAQ ### Does the idle template have offline progress? No. Production runs only on the room's clock while you are in the room, and an untended works stalls after `tend` seconds. This keeps a shared world fair. For a single-player game with offline progress, a plain web stack is a better fit. ### How many players share one idle world? Up to eight, each with their own works on the same flat, all shipping to one contract. ### How long is a round? A shift is 600 to 3,600 seconds (the template uses 900). When it ends, a new shift starts from nothing. ### Why does the build reject my earn rule? The build checks that no earn rule can pay sooner than 180 seconds into a fresh works, even with perfect play. Raise `packEvery` or the rule's `n`, or slow raking, and run `npm test` again. --- # Platformer race template (SKYLEDGE) > SKYLEDGE is a side-view platformer race for up to eight players with coyote time, jump buffering, variable jump height, an air dash, springs, moving platforms, per-player coins and seat bots that run planned routes. Start it with one command and change it in src/game.ts. 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/templates/platformer Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. SKYLEDGE is a complete multiplayer platformer race: up to eight climbers race along cliffside courses to the flag, with tuned jump feel (coyote time, jump buffering, variable jump height, an air dash), springs, one-way ledges, moving platforms and thorns. A round is one course; seat bots fill empty places so one person still has a race. ```sh npm create netforge@latest my-game -- --template platformer --yes cd my-game npm run play ``` Keys: A D or arrows to run, Space W or Up to jump (hold for height), Shift to dash, Down plus Jump to drop through a ledge, R or Enter to ready the next course. ## What you get | Part | What it does | Where | |---|---|---| | Courses | Blocks, one-way ledges, thorns, springs, moving platforms, coins and checkpoints, in metres | `levels` in `game({...})` | | Motor | Run speed, jump height, time to apex, fall gravity, jump cut, coyote and buffer windows, air jumps, dash | `motor` in `game({...})` | | Race rounds | Countdown, race, results, next course; places in order of finishing | the engine's race runner | | Route planner | Checks every course can be finished, and drives the seat bots | runs in the build and in `npm test` | | Netcode | 60 Hz deterministic platform physics on a 1/1024 m grid; your climber is predicted bit for bit | the template package | | Playtest bots | Four climbers plus seat bots for four minutes: rounds, finishes, falls, earn | `npm test` | Measured on an Apple M2 Pro, Node 22: four playtest climbers plus seat bots over four minutes (14,400 ticks at 60 Hz) ran 7 courses, all four finished every one, nobody fell, and the server tick stayed under 0.02 ms at p99. ## 1. Build a course Coordinates are metres in side view: x runs right from the start to the flag and y runs up. `blocks` are `[x, y, width, height]` from the bottom-left corner. A full running jump clears about 6 m and about 8.5 m with the dash. ```ts title=src/game.ts import { game } from "@netforge/engine"; const ground = (x0: number, x1: number, y = 0): [number, number, number, number] => [x0, y - 4, x1 - x0, 4]; export default game({ template: "platformer", style: "lowpoly", tickRate: 60, places: { cliffs: { chunks: "places/cliffs", spawn: [0, 0, 0] } }, players: { prefab: "style.lowpoly.rogue", max: 8, bots: 3 }, round: { seconds: 180 }, levels: [ { name: "First Steps", start: [0, 0], flag: [70, 2.4], blocks: [ground(-8, 24), [14, 0, 3, 1.2], ground(28, 50), [50, -4, 24, 6.4]], ledges: [[38, 2.6, 4]], hazards: [[33, 0, 2, 0.5]], springs: [[45, 0, 21]], coins: [[4, 1], [5.2, 1], [6.4, 1]], checkpoints: [[30, 0]], }, ], }); ``` Steps up of 1 to 2.5 m and gaps of 2 to 5 m play well, with a checkpoint every 30 to 50 m. If the route planner finds no way to the flag, the build fails and names the course. ## 2. Tune the jump feel Add `motor: { runSpeed: 8, jumpHeight: 3.6, airJumps: 1 }` to `game({...})`. The ranges are `runSpeed` 3 to 14, `jumpHeight` 1 to 8, `timeToApex` 0.2 to 0.7, `fallGravity` 1 to 3, `jumpCut` 0.2 to 1, `coyoteSeconds` and `bufferSeconds` 0 to 0.25, `airJumps` 0 to 2 and `dashSpeed` 6 to 30. Change a few at a time; the planner re-checks every course with the new numbers. ## 3. Reward a clean climb ```ts title=src/game.ts import { earn, system } from "@netforge/engine"; system("clean_climb", ({ on, emit }) => { const falls = new Map(); on("player.fell", (ids) => { for (const id of ids) falls.set(id, (falls.get(id) ?? 0) + 1); }); on("player.finished", (ids) => { const clean = ids.filter((id) => !falls.get(id)); if (clean.length) emit("climb.clean", clean); }); on("run.started", (ids) => { for (const id of ids) falls.delete(id); }); }); earn.rule("clean_climb", { when: "climb.clean", n: 1, pay: 12, oncePer: "run" }); ``` `race.won` and `race.podium` count only when another person was in the round, and seat bots never earn. ## 4. Check it with bots ```sh npm test ``` ## How it works The simulation is `@netforge/engine-sim`'s platform mode: plain TypeScript swept box movement on a 1/1024 m grid at 60 Hz, so the browser replays its own climber exactly the way the server does. Climbers pass through each other and each collects their own coins, so nothing another player does can make your prediction wrong. That is why a race feels instant even on a slow connection. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). The template draws the side view with three.js in every art style. The full guide is [Make a 3D platformer for the web](https://forge.netnet.capital/docs/guides/3d-platformer-web.md). ## Alternatives - **[Phaser](https://docs.phaser.io/)** has arcade physics and tilemaps and is a common choice for 2D platformers in the browser. Pick it for a single-player platformer with pixel art and hand-made levels. - **[Godot](https://docs.godotengine.org/en/stable/)** has a 2D physics engine, a character body node and a level editor. Pick it when you want to build levels visually and export to desktop too. - **Net Forge** fits when you want an eight-player race with server-checked finishes, prediction that never mispredicts, and courses an agent can write as data. ## FAQ ### Does the platformer have coyote time and jump buffering? Yes: 0.1 s of coyote time and a 0.13 s jump buffer by default, plus variable jump height, a lighter apex while Jump is held, a heavier fall and ceiling corner nudges. ### Can players collide with each other? No. Climbers are ghosts to each other in this template, which is what lets each browser predict its own climber exactly. ### How do the bots know the course? A route planner finds a path to the flag on every course, and the seat bots run those routes. The same planner is the build's softlock check. ### How many players can race? Up to eight per room, with seat bots filling places up to `players.bots`. ### Is it 2D or 3D? The game plays in side view, and the template renders it as a 3D three.js scene in any of the six art styles. --- # Card battler template with AI opponents (INKWRIT) > INKWRIT is a one-against-one online card battler: decks shuffled on the server, hidden hands, mana, units and spells, guards and rush, a draft between duels, and a gauntlet of AI opponents for a player alone. Start it with one command and change it in src/game.ts. 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/templates/card-battler Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. INKWRIT is a complete online card battler for two players: each hero has health, ink (mana) rises each turn, hands are drawn from decks the server shuffles and keeps secret, and units and spells fight on a shared desk. A run is a gauntlet of four duels with a draft of one of three cards after each, and a player alone faces AI opponents of rising strength. ```sh npm create netforge@latest my-game -- --template cards --yes cd my-game npm run play ``` Open the link in one tab to play the AI gauntlet. Open it in a second tab and the two of you duel each other. ## What you get | Part | What it does | Where | |---|---|---| | The table | Hero health, starting hand, hand and board limits, mana cap, turn and draft timers, duels per run, the starter deck | `cardTable({...})` | | Cards | 4 to 40 cards: units (attack, health, guard, rush) and spells, each with an optional effect | `card(...)` | | Effects | `damage`, `heal`, `draw`, `buff` on targets such as one enemy, all enemies, your hero or one of your units | `card({ effect })` | | AI opponents | 1 to 12 tiers of health and play strength; a seat bot joins a player who is alone | `cardTable({ ai })` | | Hidden information | Deck order never leaves the server; each hand and draft offer goes only to its owner | the engine | | Netcode | 30 Hz authoritative room; plays, attacks, end of turn and draft picks are validated actions | the template package | | Playtest bots | Two players for four minutes: duels decided, earn | `npm test` | Measured on an Apple M2 Pro, Node 22: two bots over four minutes (7,200 ticks at 30 Hz) played into the fourth duel of the gauntlet and completed three runs, at 0.002 ms per server tick on average. ## 1. Add a card ```ts title=src/game.ts import { card } from "@netforge/engine"; card("mirror_moth", { model: "style.lowpoly.jelly_flyer", kind: "unit", cost: 3, attack: 2, health: 2, effect: { kind: "damage", amount: 1, target: "all_enemies" }, }); card("final_word", { model: "style.lowpoly.crystal_rock", kind: "spell", cost: 5, effect: { kind: "damage", amount: 5, target: "enemy" } }); ``` A unit needs health of 1 or more and a spell needs an effect. Add a label in `src/names.json`. The draft offers every card unless `cardTable({ pool })` lists a subset. ## 2. Tune the table and the AI ```ts title=src/game.ts import { cardTable } from "@netforge/engine"; cardTable({ health: 25, startHand: 3, handMax: 7, boardMax: 5, manaMax: 10, turnSeconds: 40, draftSeconds: 15, duels: 5, starter: ["blot_imp", "blot_imp", "quill_sprite", "quill_sprite", "ink_frog", "ink_frog", "margin_bat", "ledger_crab", "serif_fox", "splash", "splash", "blotting_sand"], ai: [ { health: 14, smart: 0.3 }, { health: 16, smart: 0.55 }, { health: 20, smart: 0.75 }, { health: 24, smart: 0.9 }, { health: 28, smart: 1 }, ], }); ``` Duel n against the AI uses tier n. Lower hero health makes faster duels; more duels make longer runs. The starter deck holds 8 to 30 declared cards, with at least one unit and some cards costing 2 or less. ## 3. Add an earn rule ```ts title=src/game.ts import { earn } from "@netforge/engine"; earn.rule("ink_master", { when: "ai.beaten", n: 3, pay: 15, oncePer: "run" }); ``` `duel.won` counts duels won against a person (never against the AI, never a forfeit), `ai.beaten` counts AI opponents beaten, and `run.won` counts runs with more duels won than lost. The AI never earns. ## 4. Check it with bots ```sh npm test ``` ## How it works A duel is a board genre: a deterministic rules module in `@netforge/engine/rules` with integer state and a seeded random number on the room server. The server shuffles, keeps the deck order to itself, and sends each player only their own hand and draft offer; snapshots carry the cursors only. The hosted room server is tested for hidden-card leaks every few ticks of a run, and nothing in `src/game.ts` can change that. Turns, the turn timer, guards and rush are part of the genre, so your file only declares the cards and the table. The full guide is [Build an online card game with turns and hidden information](https://forge.netnet.capital/docs/guides/online-card-game.md). ## Alternatives - **[boardgame.io](https://boardgame.io/documentation/)** is an open-source JavaScript engine for turn-based games, with moves, phases, secret state, a lobby and multiplayer built in. Pick it when you want to describe any turn-based rules in your own code and draw them with your own UI. - **[Colyseus](https://docs.colyseus.io/)** is a multiplayer server framework with rooms and state sync. Pick it when you have a card client already and want a server to hold the game state. - **Net Forge** fits when you want a finished card battler with a 3D desk, server-side shuffles, hidden hands, AI opponents and a draft, and you want to change the cards rather than build the duel. ## FAQ ### How does the card game keep hands hidden? The room server shuffles from its own seeded random number and keeps deck order to itself. A hand and a draft offer are sent only to their owner through a per-player channel, and snapshots carry only the players' cursors. ### Can I play against the computer? Yes. A player alone for 3 seconds gets an AI opponent from the gauntlet's tier for that duel. When a second person joins, the AI duel is called off and the two people play each other. ### How many cards can a game have? From 4 to 40 cards, with a starter deck of 8 to 30. ### Is it turn-based with a timer? Yes. Each turn has `turnSeconds` (30 in the template), and the timer ends the turn if the player does not. A duel that reaches the turn cap is a draw. ### Can I make it a deckbuilder? The draft between duels already adds one of three cards to your deck each time. You control which cards it offers with `cardTable({ pool })`. --- # Monster collector template (MOSSBOUND) > MOSSBOUND is a multiplayer monster collector for up to eight players: pick a starter, walk a shared marsh, meet wild creatures in tall grass, battle turn by turn with a team of three, catch the ones you want and bring down a shared boss before dusk. Start it with one command and change it in src/game.ts. 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/templates/monster-collector Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. MOSSBOUND is a complete multiplayer monster collector: each player picks one of three starters at camp, walks a marsh shared with up to seven others, meets wild creatures in tall grass and battles them turn by turn with a team of three, catching the ones worth keeping. Every player in the marsh wears down the same boss, the warden, which has to fall before dusk. ```sh npm create netforge@latest my-game -- --template collector --yes cd my-game npm run play ``` Walk with WASD, the arrows or the left thumb. Open the link in a second tab and you meet the other keeper in the marsh. ## What you get | Part | What it does | Where | |---|---|---| | The map | Size in tiles, camp, water, blocked tiles, grass zones with encounter rates and creature pools, the warden, dusk, snares | `marsh({...})` | | Creatures | Up to 24 creatures with an element, base stats that grow 12% a level, 1 to 4 moves and a catch rate | `creature(...)` | | Moves | Up to 48 moves with element, power and accuracy; effects `drain`, `heal`, `guard` | `move(...)` | | Elements | A four-way cycle (root, tide, spark, stone); strong hits deal 1.5x, weak ones 0.67x | the engine | | Battles | Turn order by speed, swaps, snares, flee, XP for the active and benched creatures | the engine | | Shared boss | One health pool for the whole marsh; everyone who hurt it is credited when it falls | `marsh({ warden })` | | Netcode | 30 Hz authoritative room; walking is predicted with the room's own tile collision; battles are private to each player | the template package | | Playtest bots | Four keepers for four minutes: battles won, catches, levels, team wipes, earn | `npm test` | Measured on an Apple M2 Pro, Node 22: four bots over four minutes (7,200 ticks at 30 Hz) caught 14 creatures, raised a creature to level 6 and fired the catch rules, at 0.002 ms per server tick on average. ## 1. Add a creature and a move ```ts title=src/game.ts import { creature, move } from "@netforge/engine"; move("thorn_curl", { element: "root", power: 0, effect: "guard" }); move("tide_crash", { element: "tide", power: 70, accuracy: 0.8 }); creature("thistlehog", { model: "style.lowpoly.fox", element: "root", hp: 50, atk: 52, def: 48, spd: 46, moves: ["leaf_lash", "thorn_curl"], catchRate: 0.7, }); ``` `power: 40` is a plain hit. `drain` heals half the damage dealt, `heal` (with `power: 0`) restores `amount` of max health, and `guard` halves the next hit taken. Then put the creature in a grass pool and add its label to `src/names.json`. ## 2. Lay out the marsh ```ts title=src/game.ts import { marsh } from "@netforge/engine"; marsh({ size: [30, 20], camp: [2, 10], starters: ["mossling", "cindertail", "puddlewump"], starterLevel: 4, water: [[11, 0, 2, 6]], blocked: [[19, 0, 1, 8]], grass: [ { at: [4, 2, 6, 5], rate: 0.22, pool: [{ creature: "thistlehog", weight: 3, level: [2, 3] }] }, { at: [21, 4, 4, 12], rate: 0.18, pool: [{ creature: "bogling", weight: 2, level: [6, 8] }] }, ], warden: { creature: "fenwarden", at: [27, 10], level: 9, hp: 700 }, dusk: 600, snares: 6, }); ``` Rectangles are `[x, y, width, height]` in tiles of 2 m. Put weak creatures near camp and stronger ones toward the warden, and leave a walkable way from camp to every grass zone. The build checks that the camp and warden stand on open tiles and that every pool names a declared creature. ## 3. Add an earn rule ```ts title=src/game.ts import { earn } from "@netforge/engine"; earn.rule("ten_wins", { when: "creature.defeated", n: 10, pay: 15, oncePer: "run" }); ``` Events are `creature.caught`, `creature.defeated`, `creature.levelup`, `level.five` and `level.ten` (each credits the player), `warden.defeated` (everyone who hurt it) and `run.won` (everyone in the marsh). ## 4. Check it with bots ```sh npm test ``` ## How it works The collector is a board genre: its rules are a deterministic module in `@netforge/engine/rules`. Every roll (encounters, accuracy, damage spread, snares and the wild creature's move) comes from the room's seeded random number, so a run replays exactly from its inputs. Walking is predicted with the same tile collision the room runs, so it never waits for the server. Battles are private: your team, your opponent and the battle log reach only you, while other players see the creature walking beside you. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md). The full guide is [Build a monster collecting game with battles and a party](https://forge.netnet.capital/docs/guides/monster-collector-game.md). ## Alternatives - **[Godot](https://docs.godotengine.org/en/stable/)** has a tilemap editor, a scripting language and a web export. Pick it for a single-player collector with a hand-built overworld and story. - **[Phaser](https://docs.phaser.io/)** is a 2D HTML5 framework with tilemaps. Pick it for a top-down 2D collector with pixel art. - **Net Forge** fits when you want a shared world where players catch and battle side by side, with every roll made on the server and a shared boss fight. ## FAQ ### How do wild encounters work? Each step into a new tile of tall grass rolls the zone's `rate`. A hit starts a battle with a creature from that zone's `pool`, at a level in its range. ### How is the catch chance calculated? A snare succeeds with chance `catchRate x (0.25 + 0.75 x the opponent's missing health)`, so weakening a creature first makes it easier to catch. ### Can players battle each other? Not in this template. Players share the marsh and the warden fight; battles are against wild creatures and the warden. ### How big can a team be? Three creatures travel with you; more catches go into your lantern. The `amount` upgrade adds extra snares. ### What happens if my whole team faints? You are sent back to camp with half health. Standing at camp heals your team. --- # Kart racer template (TORQUEBAY) > TORQUEBAY is an online kart racer for up to eight players: arcade kart physics at 60 Hz, drift mini-turbos in three tiers, boost pads, coins, laps through ordered checkpoints, a streamed track and bots that drive the racing line. Start it with one command and change it in src/game.ts. 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/templates/kart-racer Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. TORQUEBAY is a complete online kart racer for up to eight players: hold the throttle, drift through bends to charge a mini-turbo, hit boost pads, collect coins that raise your top speed and keep off the grass. A round is one race of a few laps on one of three harbour circuits, and seat bots fill the grid so one person alone still has a field. ```sh npm create netforge@latest my-game -- --template racer --yes cd my-game npm run play ``` `--template kart` works too. Keys: W or Up to accelerate, S or Down to brake, A D or arrows to steer, hold Space or Shift through a bend to drift and release for the boost, R or Enter to ready the next race. ## What you get | Part | What it does | Where | |---|---|---| | Circuits | 1 to 8 tracks from a closed centreline of control points, with width, laps, boost pads and coin rows | `tracks` in `game({...})` | | Kart handling | Top speed, acceleration, turn rate, grip, drift grip, grass loss, boost strength | `kart` in `game({...})` | | Drift mini-turbo | Three charge tiers (blue, orange, purple) for a 0.5, 0.9 or 1.4 s boost | the engine's kart mode | | Laps and places | Four quarter checkpoints in order per lap, a race clock, places, results | the engine's race runner | | Seat bots | Drive the racing line, pass, hold drifts through long bends, shaded by skill, never earn | `players.bots` | | Streamed track | The client builds road, kerbs, barriers and scenery in chunks near the camera only | the template client | | Netcode | 60 Hz kart physics on a 1/1024 grid; your kart is predicted; kart bumps run on the server | the template package | | Playtest bots | Four drivers plus seat bots for four minutes: races finished, wall hits, earn | `npm test` | Measured on an Apple M2 Pro, Node 22: four playtest drivers plus seat bots over four minutes (14,400 ticks at 60 Hz) ran two full races, every driver finished both, nobody hit a wall hard, and the server tick stayed under 0.09 ms at p99. ## 1. Add a circuit `points` is the closed centreline in driving order, in metres on the ground plane; a smooth curve runs through them. `boosts` and `coins` are `[lap fraction 0..1, lateral -1..1]`. ```ts title=src/game.ts import { game } from "@netforge/engine"; const coinRow = (f: number, lateral: number, n: number, gap = 0.006): [number, number][] => Array.from({ length: n }, (_, i) => [f + i * gap, lateral]); export default game({ template: "racer", style: "lowpoly", tickRate: 60, places: { harbour: { chunks: "places/harbour", spawn: [0, 0, 0] } }, players: { prefab: "style.lowpoly.kart_a", max: 8, bots: 6 }, round: { seconds: 240 }, tracks: [ { name: "Pier Sprint", laps: 3, width: 14, points: [[0, 0], [90, 0], [150, 15], [175, 65], [150, 120], [90, 135], [50, 175], [-20, 190], [-85, 160], [-110, 100], [-90, 35]], boosts: [[0.12, 0], [0.47, -0.4], [0.78, 0.4]], coins: [...coinRow(0.04, 0.5, 5), ...coinRow(0.3, -0.5, 5)], }, ], }); ``` A track has 4 to 64 control points at least 6 m apart and is at least 200 m round. As a rule of thumb, 500 to 900 m a lap at 14 m wide runs 25 to 40 s a lap, a hairpin needs about 25 m of radius, and boosts belong on the exits of bends. ## 2. Tune the handling Add `kart: { topSpeed: 30, turn: 2.6, grip: 10, driftGrip: 1.5 }` to `game({...})`. The ranges are `topSpeed` 10 to 45 m/s (default 26), `accel` 8 to 80, `turn` 1 to 4, `grip` 3 to 20, `driftGrip` 0.5 to 6, `grass` 0.2 to 1 and `boostSpeed` 1 to 1.8. ## 3. Reward a clean race ```ts title=src/game.ts import { earn, system } from "@netforge/engine"; system("clean_line", ({ on, emit }) => { const scrapes = new Map(); on("kart.bumped", (ids) => { for (const id of ids) scrapes.set(id, (scrapes.get(id) ?? 0) + 1); }); on("player.finished", (ids) => { const clean = ids.filter((id) => (scrapes.get(id) ?? 0) === 0); if (clean.length > 0) emit("race.clean", clean); }); on("run.started", (ids) => { for (const id of ids) scrapes.delete(id); }); }); earn.rule("clean_line", { when: "race.clean", n: 1, pay: 12, oncePer: "run" }); ``` Other events include `player.finished`, `race.won` and `race.podium` (which count only with another person in the round), `lap.completed`, `coin.collected`, `drift.boosted` and `drift.ultra`. ## 4. Check it with bots ```sh npm test ``` ## How it works The simulation is `@netforge/engine-sim`'s kart mode: arcade vehicle physics on a 1/1024 grid at 60 Hz. Each tick, velocity splits into forward and sideways parts; the engine pushes the forward part toward top speed, grip bleeds the sideways part (much less while drifting), and steering turns with speed. Your browser predicts your own kart; kart-to-kart bumps run only on the server and the next snapshot corrects them. The client streams the track in chunks of about 40 m within 170 m of the camera, so long circuits stay light. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). The full guide is [Build a multiplayer kart racer in the browser](https://forge.netnet.capital/docs/guides/kart-racer-browser.md). ## Alternatives - **[Babylon.js](https://doc.babylonjs.com/)** is a full 3D engine for the web with physics plugins and a node material editor. Pick it when you want to build your own vehicle physics and scene from engine primitives. - **[PlayCanvas](https://developer.playcanvas.com/)** is a WebGL and WebGPU engine with a hosted visual editor. Pick it when your team wants to build tracks and scenes in an editor. - **Net Forge** fits when you want an eight-player kart racer with drift, boosts, laps, prediction and bots already working, and circuits you declare as data. ## FAQ ### How does drifting work in the kart racer? Hold Space or Shift while steering through a bend. The mini-turbo charges in three tiers (blue, orange, purple) and fires a 0.5, 0.9 or 1.4 s boost when you release. ### Do karts collide with each other? Yes, on the server. Your browser predicts your own kart alone, and the next snapshot corrects your position after a bump. ### How many players can race? Up to eight per room. Seat bots fill the grid up to `players.bots` (six in the template) and leave as people join. ### Can I design my own track? Yes. A track is a list of control points in metres plus width, laps, boost pads and coin rows. Add it to `tracks`; the room draws one circuit per round. ### How are laps counted? Through four quarter checkpoints in order. Skipping a checkpoint does not count the lap. --- # Party game template for 2 to 8 players (ROMPHOUSE) > ROMPHOUSE is a co-op party game for two to eight players on phones and laptops: one playroom, five quick minigame rounds a show, auto-firing weapons, revives and a finale boss. Start it with one command and change the minigames in src/game.ts. 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/templates/party-game Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. ROMPHOUSE is a complete co-op party game for two to eight friends in one playroom: a show is five quick minigame rounds with one goal for the whole team (pop the poppers before the timer, crash every gift box, catch the runaway pup, hold the floor against a tide, and burst the Big Balloon as the finale). Win three and the show is a hit. Weapons fire by themselves, so every round is about where you stand and who you help up. ```sh npm create netforge@latest my-game -- --template party --yes cd my-game npm run play -- --host 0.0.0.0 ``` With `--host 0.0.0.0`, friends on the same network open your computer's address on their phones and join the same room. Keys: WASD or arrows, Space to dash, 1 2 3 to pick a card. On touch, drag on the left half and tap the DASH button. ## What you get | Part | What it does | Where | |---|---|---| | The show | A list of minigames, each with a name, length, a `start()` that spawns its crowd and a `won()` check; shuffled each show with the finale last | the `show` system in `src/game.ts` | | The playroom | One floor, four walls and party tables as cover, bigger than the screen so crowds arrive off screen | `field` in `game({...})` | | Forgiving downs | 8 s down, crawl, two free get-ups a show, back in 3 s; everyone out returns between rounds | `players.downed` | | Combat | Auto-firing weapons, level-up cards, a finale boss with telegraphed attacks | `weapon(...)`, `prefab(...)` | | Netcode | 30 Hz authoritative room, your own player predicted | the template package | | Playtest bots | Four bots for four minutes: rounds won, kills, softlock check on every round | `npm test` | Measured on an Apple M2 Pro, Node 22: four bots over four minutes (7,200 ticks at 30 Hz) played six rounds, cleared 305 enemies, reached level 10 and won the first-round earn rule, at 0.15 ms per server tick on average (p99 0.51 ms). ## 1. Add a minigame Each entry in the show system's `ROUNDS` list is an object with `name`, `seconds`, `start()`, `won()` and an optional `lastsOut`. `start()` runs as the round opens (spawn its crowd with `spawnEdge`, scatter props with `scatter`, start a trickle with `every`). `won()` is checked every half second and ends the round early when it returns true. A `lastsOut: true` round is won at the bell if nobody went down. For example, a dodgeball round is `{ name: "DODGEBALL", seconds: 35, lastsOut: true, start: () => spawnEdge("spritzer", { count: 6 + party() }), won: () => false }`. Append it to `ROUNDS` and include it in the shuffle, which leaves the last entry (the finale) where it is. Keep 3 to 8 rounds, and make something happen every few seconds: a round with 30 quiet seconds fails the playtest as a softlock. ## 2. Add a crowd enemy ```ts title=src/game.ts import { prefab } from "@netforge/engine"; prefab("bouncer", { model: "style.lowpoly.blob_ninja", appearance: { tint: "#9be37a" }, health: 30, speed: 3.2, radius: 0.45, damage: 5, ai: "charger", drops: ["star_small"], }); ``` Then spawn it from a round's `start()`, for example `spawnEdge("bouncer", { count: 4 })`. ## 3. Add an earn rule ```ts title=src/game.ts import { earn } from "@netforge/engine"; earn.rule("crowd_favourite", { when: "round.won", n: 4, pay: 20, oncePer: "run" }); ``` Every won round emits `round.won`, every round's end emits `wave.cleared`, and a clean sweep emits `show.perfect`. A rule counts once per show per player. ## 4. Check it with bots ```sh npm test ``` ## How it works ROMPHOUSE runs on the survivor-like machinery (auto-firing weapons, level-up cards, co-op downs and revives, a boss with telegraphed attacks) inside one walled room. The show is ordinary code in `src/game.ts`, so you or your coding agent can add, remove or reorder minigames without touching the template. The room is authoritative and each player's own character is predicted, which keeps phones on a home network responsive. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). The full guide is [Make a party game for 2 to 8 players on phones and laptops](https://forge.netnet.capital/docs/guides/party-game-browser.md). ## Alternatives - **[Colyseus](https://docs.colyseus.io/)** is a multiplayer server framework with rooms and matchmaking. Pick it when you already have a party game client and want a server for rooms and state. - **[Phaser](https://docs.phaser.io/)** is a 2D HTML5 framework. Pick it for a 2D party game where each minigame has its own scene and art. - **Net Forge** fits when you want a finished co-op show with rounds, revives and bots, where a new minigame is one object in a list. ## FAQ ### How many players can join a party game? Two to eight per room. One player can also play a show alone; crowd sizes scale with the number of players. ### Can friends join from their phones? Yes. The client has touch controls. Run `npm run play -- --host 0.0.0.0` and share your computer's address on the same network. ### How long is a show? Five rounds of 30 to 60 seconds with a short breather between them, so a show takes a few minutes. ### What happens when a player goes down? They are down for 8 seconds and can crawl to a friend to be revived, with two free get-ups a show. Anyone still out comes back between rounds. ### Is it competitive or co-op? Co-op. Every minigame has one goal for the whole team. --- # Net Forge guides > Step-by-step guides for specific jobs, from a multiplayer browser game to hosting it, each paired with the template that does it. 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 Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Each guide answers one job and names the template that already does it, so you start from a working game. - [Multiplayer browser game](https://forge.netnet.capital/docs/guides/multiplayer-browser-game.md): 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. - [.io-style arena shooter](https://forge.netnet.capital/docs/guides/io-game.md): Make a .io-style browser shooter where players join a room from a link, the server owns all movement and hits, and bots fill empty seats so a lone player still gets a fight. One command starts it. - [Authoritative multiplayer for three.js](https://forge.netnet.capital/docs/guides/threejs-authoritative-multiplayer.md): Put the rules in one simulation that the server runs as the authority and the browser runs again to predict, and send only inputs from each client. With Net Forge that simulation is one game file shared by server and browser, so there is no separate server codebase; three.js only draws what it says. - [Three.js netcode](https://forge.netnet.capital/docs/guides/threejs-multiplayer-netcode.md): How client-side prediction, server reconciliation, interpolation and delta snapshots work in a three.js multiplayer game, with a runnable TypeScript example that converges exactly under 80 ms latency and 5% packet loss. - [Deterministic Rapier physics](https://forge.netnet.capital/docs/guides/threejs-rapier-physics.md): Use @netforge/engine-sim's createPhysics for Rapier rigid bodies, triggers, ray casts and a character controller on a fixed tick, with bit-exact snapshots and a state hash, so the server and every browser compute the same world. - [Shared client and server simulation](https://forge.netnet.capital/docs/guides/shared-client-server-simulation.md): @netforge/engine-sim is one TypeScript module that steps the same world on a Node server and in the browser: fixed tick, quantized inputs, integer state and snapshots that restore bit-exact. A runnable example proves two worlds stay identical. - [Free CC0 models and sounds](https://forge.netnet.capital/docs/guides/cc0-game-assets.md): The Net Forge asset library gives a web game CC0 characters, creatures, vehicles, props, sprites and sound effects addressed by id, in four model sets and six art styles, with each pack's licence verified and every file pinned by hash. - [WebGPU with a WebGL2 fallback](https://forge.netnet.capital/docs/guides/webgpu-browser-game.md): Net Forge draws with three.js's WebGPURenderer, which uses WebGPU where the browser has it and WebGL 2 everywhere else, and wraps it in a render stack with lighting, post effects, GPU particles and quality tiers that adapt to the device. - [Test locally with bots](https://forge.netnet.capital/docs/guides/local-multiplayer-testing.md): Open one room in several browser tabs with npm run play, let phones on your network join, and run headless playtest bots with npm test and npm run check. Everything runs on your computer with play money. - [Let an AI agent build your game](https://forge.netnet.capital/docs/guides/build-a-game-with-an-ai-agent.md): Scaffold a Net Forge game with npm create netforge, open it in Claude Code, Cursor or Copilot, and ask for changes in plain words. The agent edits one typed file, src/game.ts, and checks every edit with npm run check, a headless bot playtest that prints JSON. - [Deploy and host](https://forge.netnet.capital/docs/guides/deploy-and-host.md): Two honest options: play and playtest on your own computer and network with the kit-play local host, or publish through the hosted Net Forge studio, which runs the production room servers, lag compensation and run verification. - [Survivor-like with thousands of enemies](https://forge.netnet.capital/docs/guides/horde-survivor-browser-game.md): Start from the EMBERWATCH template with one command: a four-player co-op survivor-like that holds 2,000 enemies on an authoritative server at 30 Hz, drawn with instanced three.js crowds. Then tune the swarm, the weapons and the waves in src/game.ts. - [Co-op game joined with a link](https://forge.netnet.capital/docs/guides/co-op-browser-game.md): Start a four-player co-op game with one command, share a room link so friends drop into the same run, and tune revives, shared XP and boss scaling in src/game.ts. Rooms, the authoritative server and prediction come with the template. - [Host a game server](https://forge.netnet.capital/docs/guides/host-browser-game-server.md): What a browser game server needs (WebSockets, a fixed-tick authoritative loop, TLS, rooms that stay on one process), how Net Forge's local room host does it on one port, and which hosted and self-hosted options fit a public game. - [Vibe-code a multiplayer game](https://forge.netnet.capital/docs/guides/vibe-code-multiplayer-game.md): Start from a finished multiplayer template instead of a blank page, let your agent change only the declarative game file, and gate every change on a headless bot playtest. The netcode, rendering and server authority are already done, so prompts change the game, not the plumbing. - [In-game store and currency](https://forge.netnet.capital/docs/guides/in-game-store-and-currency.md): Declare store items, coin prices and earn rules in src/game.ts with store.sku and earn.rule. Locally they run with play money; on the hosted Net Forge service a game can optionally upgrade its coins to its own token. - [Online card battler with AI opponents](https://forge.netnet.capital/docs/guides/online-card-game.md): Start the INKWRIT card battler with one command: one-against-one duels with turns, a turn timer, hidden hands shuffled on the server, a draft between duels and AI opponents in four tiers. Change cards, the table and the AI in src/game.ts. - [Turn-based multiplayer game](https://forge.netnet.capital/docs/guides/turn-based-browser-game.md): Start from a turn-based template (the card battler or the monster collector) with one command. Turns, validated actions, hidden information and replays come from Net Forge rules genres: deterministic modules the room server runs at 30 Hz behind the same netcode as the action games. - [Co-op tower defense](https://forge.netnet.capital/docs/guides/tower-defense-browser-game.md): Start the COGSPIRE tower defense template with one command: up to four players build and upgrade towers on one shared board against twenty waves. Change the road, towers, creeps and waves in src/game.ts; targeting, gold, co-op and netcode come with it. - [Online kart racer](https://forge.netnet.capital/docs/guides/kart-racer-browser.md): Start the TORQUEBAY kart racer with one command: up to eight karts race laps with drift boosts, boost pads and coins, seat bots fill the grid, and the server runs arcade kart physics at 60 Hz. Add circuits and tune the handling in src/game.ts. - [Dungeon crawler with procedural rooms](https://forge.netnet.capital/docs/guides/dungeon-crawler-typescript.md): Start the CRYPTWICK template with one command: a four-player co-op crawl through a crypt grown from a seed, with rooms that wake as you approach, flow-field pathing round walls, reliquary upgrades and permadeath. Change the seed, the rooms and the encounters in src/game.ts. - [Multiplayer idle game](https://forge.netnet.capital/docs/guides/idle-game.md): Start the BRINEWORKS idle template with one command: up to eight players each run a works on one shared salt flat and fill a contract together. Production runs only on the server's clock while you are in the room, so the game cannot be farmed. Tune plants, costs and shifts in src/game.ts. - [Multiplayer 3D platformer](https://forge.netnet.capital/docs/guides/3d-platformer-web.md): Start the SKYLEDGE platformer with one command: up to eight climbers race cliffside courses with coyote time, jump buffering, variable jump height, dashes, springs and moving platforms. The movement is predicted bit for bit, and a route planner checks every course. Tune the feel in src/game.ts. - [Monster collector with battles](https://forge.netnet.capital/docs/guides/monster-collector-game.md): Start the MOSSBOUND monster collector with one command: up to eight players walk a shared marsh, meet wild creatures in tall grass, battle them turn by turn with a team of three, catch the ones worth keeping and wear down one shared boss. Add creatures, moves and grass zones in src/game.ts. - [Party game for 2 to 8 players](https://forge.netnet.capital/docs/guides/party-game-browser.md): Start the ROMPHOUSE party template with one command: two to eight friends in one playroom play five quick co-op minigame rounds a show, on phones or laptops. Each minigame is an entry in a ROUNDS list in src/game.ts that you can add to or change. --- # 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. --- # How to make a .io-style arena shooter > Make a .io-style browser shooter where players join a room from a link, the server owns all movement and hits, and bots fill empty seats so a lone player still gets a fight. One command starts it. 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/io-game Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. A .io-style game is a browser game you join from a link with no install, drop into a shared arena, and leave whenever you like. Net Forge's arena shooter template already is one: `npm create netforge@latest my-io -- --template arena --yes` makes a twin-stick shooter for up to 16 players per room, with server-authoritative movement and hits and bots that fill the seats nobody has taken. ```sh npm create netforge@latest my-io -- --template arena --yes cd my-io npm run play ``` ## What you get | Feature | How the template does it | |---|---| | Join from a link | In local play every tab joins the open room until it is full. On the hosted service the Invite link (`?room=`) picks a specific room. | | Matchmaking | A new player joins an open room with a free seat, or a new room is created. | | Server-authoritative movement | Clients send inputs only. The server steps the world at 60 Hz and sends delta snapshots. | | Instant feel | Your own player is predicted locally from the same simulation code and reconciled with the server. | | Hits you can trust | Projectiles are simulated on the server. Hitscan beams are lag compensated on the hosted server (rewound to what the shooter saw, capped at 200 ms). | | Bots fill empty seats | `players.bots` seats join while at least one person is in the room and leave as people join. Bots never earn. | | Short sessions | A round lasts `round.seconds` or ends at `round.frags` kills, then a new round starts. | | Phones | A left stick moves, a right stick aims and fires, plus SWAP and DASH buttons. | ## 1. Create and play Run the commands above, then open the same URL in a second tab. Both tabs land in the same room because the local host fills an open room before it creates a new one. To play from a phone on your network: ```sh npm run play -- --host 0.0.0.0 ``` ## 2. Size the room and the bots The room size, the number of seat bots and the round all live in `game()`: ```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: 16, loadout: ["arc_rifle", "rail_lance"], bots: 6, respawn: 2, drop: "volt_cell", }, arena: { halfSize: 30, pillars: [[0, 0, 3], [12, 12, 1.5], [-12, 12, 1.5], [12, -12, 1.5], [-12, -12, 1.5]], spawns: [[25, 25], [-25, -25], [25, -25], [-25, 25], [0, 25], [0, -25], [25, 0], [-25, 0]], }, round: { seconds: 120, frags: 15 }, }); 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("rail_lance", { kind: "beam", damage: 60, cooldown: 1.4, range: 40 }); earn.rule("first_blood", { when: "opponent.killed", n: 1, pay: 2, oncePer: "run" }); earn.stake({ min: 10, max: 100, multiple: 2 }); ``` A bigger `halfSize` with more spawns suits a larger room. Spawns must sit 1 m inside the floor and 1 m clear of pillars; the engine refuses the file with a one-line reason if they do not. ## 3. Add a reason to keep playing Custom game events come from `system()`. This one rewards a five-kill streak, which you can then pay with an earn rule or show on the HUD: ```ts title=src/game.ts import { earn, system } from "@netforge/engine"; system("streaks", ({ on, emit }) => { const streak = new Map(); on("opponent.killed", (ids) => { for (const id of ids) { const n = (streak.get(id) ?? 0) + 1; streak.set(id, n); if (n === 5) emit("streak.five", [id]); } }); on("player.died", (ids) => { for (const id of ids) streak.set(id, 0); }); }); earn.rule("overload", { when: "streak.five", n: 1, pay: 12, oncePer: "run" }); ``` Killing a bot scores and shows in the kill feed, but the engine never pays an earn rule for it, and `round.top` pays only when another person was in the round. That rule is enforced by the engine for every arena game, not by your code. ## 4. Check it with bots ```sh npm test ``` The playtest runs the game with bots and fails if players stop scoring, no earn rule fires or the game softlocks. On an Apple M2 Pro the template's own playtest (4 bots, 4 minutes of game time at 60 Hz) costs 0.10 ms per tick on average. ## How it works The browser never decides where anyone is. Each tick, the client samples the sticks or keys into a small command with a sequence number and sends it, repeating the last few unacknowledged commands in each packet. It applies the same command to its own copy of the simulation at once, so movement feels immediate. The server applies every player's commands in order, steps the world and sends each player a delta snapshot of what is near them. When a snapshot says the server saw something different, the client resets to it and replays the commands still in flight. Everyone else is drawn about 100 ms in the past, between two snapshots, so remote players move smoothly. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). ## Alternatives - **[Colyseus](https://docs.colyseus.io/)** gives you rooms, matchmaking and state sync on a Node server, with client SDKs for many engines and [client prediction](https://docs.colyseus.io/netcode/client-prediction). It is a strong choice when you already have a client and want a general server framework. Net Forge's local room host itself runs on Colyseus. - **[PartyKit](https://docs.partykit.io/)** handles the real-time infrastructure and scaling for multiplayer rooms. Pick it when you want rooms without operating servers and are writing the game logic yourself. - **[Phaser](https://docs.phaser.io/)** is a widely used 2D HTML5 game framework with a large example library. Pick it for a 2D .io game where you will add your own networking layer. Pick Net Forge when you want a 3D .io-style shooter that already has authority, prediction, bots and touch controls, and you want to change only the rules. ## FAQ ### What makes a game ".io-style"? Usually four things: it runs in the browser with no download, you join from a link, the arena is shared with strangers or friends, and sessions are short. The arena template has all four, with rounds of two to three minutes. ### How do friends join the same room? In local play, every tab or device that opens the link joins the open room until it is full, then a new room opens. On the hosted service, share the Invite link, which carries the room id as `?room=` and puts your friend in your room. ### What happens when only one person is online? Seat bots fill the empty seats while at least one person is in the room, so a lone player still has opponents. Bots leave as people join. ### Can players cheat by changing the client? Not in the ways that matter. The client sends only inputs. Positions, hits, kills and round results are decided by the server, and hitscan beams are checked against what the shooter could have seen. ### How many players fit in one room? The arena template seats up to 16 per room (`players.max`). Each extra room is another instance of the same game. ### Does it need WebGPU? No. The renderer uses WebGPU where the browser has it and falls back to WebGL2 everywhere else. --- # How to add authoritative multiplayer to a three.js game > Put the rules in one simulation that the server runs as the authority and the browser runs again to predict, and send only inputs from each client. With Net Forge that simulation is one game file shared by server and browser, so there is no separate server codebase; three.js only draws what it says. 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/threejs-authoritative-multiplayer Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. To add authoritative multiplayer to a three.js game, move the rules into one fixed-tick simulation that the server runs as the authority and each browser runs again to predict its own player, and have every client send only its inputs. With Net Forge that simulation is one game file: the same `@netforge/engine-sim` code steps the world on the room server and in the browser, so there is no separate server codebase to keep in sync, and three.js only draws what the simulation says. ```sh npm create netforge@latest my-game -- --template arena --yes npm install @netforge/engine @netforge/engine-sim ``` The first line starts from a finished multiplayer game, where the rules are `src/game.ts` and the server is already written. The second adds the netcode packages to a three.js project of your own, which is what the rest of this page does. A runnable version of that, a plain three.js scene with the netcode under it, is in [the example](#run-the-example). ## What moves where | In your three.js game today | With an authoritative server | |---|---| | `requestAnimationFrame` moves objects by `delta` | A fixed tick (`tickHz`) moves them by a constant step, on the server and in the browser | | Input handlers change positions directly | Input handlers build one small command per tick, and the server applies it | | Collision and hit checks in the browser | The same checks inside the simulation's `step`, decided by the server | | `mesh.position` is the truth | The simulation's state is the truth, and `mesh.position` copies it every frame | | Floats everywhere | Positions on a fixed-point grid, so the client's prediction matches the server exactly | ## Clients send intentions, not outcomes A client never tells the server where it is. It sends what the player wants to do this tick, and the server decides what happens. In Net Forge the intention is an `InputCommand`: ```ts title=intent.ts import type { InputCommand } from "@netforge/engine-sim"; // What the browser sends for one tick: an intention, never a position. const intent: InputCommand = { seq: 42, tick: 1200, moveX: 1, moveZ: 0, yaw: 0, buttons: 0 }; ``` `moveX` and `moveZ` are the stick or keys from -1 to 1, `yaw` is where the player faces, and `buttons` is a bitmask (fire, dash and so on). On the wire the engine packs the move axes as signed bytes, the facing as a 16-bit turn and the buttons as 16 bits, and each input packet repeats the last few unacknowledged commands so a lost packet costs nothing. The server's `InputQueue` ignores a command it already has, applies one per tick for engine-sim games, and gives a player only as many commands as its credit allows, so sending more inputs never moves anyone faster. ```text browser, every tick room server, every tick (authoritative) keys -> InputCommand { seq, moveX, moveZ, ----> InputQueue -> step(world, inputs) yaw, buttons } speed, walls, physics, hits decided here predict: step(own world, [own input]) | draw: predicted self, interpolated others <---- delta snapshot (positions, health, flags) ``` The tick is the template's `tickRate`: 30 Hz for the survivor-like and the shared simulation's default, 60 Hz for the arena shooter, platformer and kart racer. The local room host in `@netforge/kit-play` sends snapshots 20 times a second. Because the browser predicts with the same step function the server runs, its guess and the server's answer agree unless something the client could not know happened, such as another player pushing it; then the next snapshot corrects it, and the correction is smoothed over about 100 ms. ## 1. Describe the game as a SimAdapter A `SimAdapter` is the narrow seam the netcode drives: create a world, add and remove players, step one tick with each player's inputs, and report every networked entity. Keep positions on an integer grid (here 1/32 m) so a predicted step on the client gives the same numbers as the server. The three blocks in steps 1 to 3 form one file. ```ts group=net import type { EntitySchema, InputCodec, PlayerId, SimAdapter } from "@netforge/engine/net"; /** What one player pressed in one tick. */ interface Move { readonly x: number; // -1, 0 or 1 readonly z: number; } /** The world: one position per player, in 1/32 m units so the client predicts it exactly. */ interface World { readonly players: Map; nextId: number; } const UNITS_PER_METRE = 32; const SPEED = 4; // units per tick: 4/32 m at 30 Hz is 3.75 m/s const schema: EntitySchema = { fields: [ { name: "x", kind: "int", bits: 20 }, { name: "z", kind: "int", bits: 20 }, ], xField: 0, yField: 1, unitsPerWorld: UNITS_PER_METRE, }; const input: InputCodec = { write(w, m) { w.writeBits(m.x + 1, 2); w.writeBits(m.z + 1, 2); }, read(r) { return { x: r.readBits(2) - 1, z: r.readBits(2) - 1 }; }, idle: { x: 0, z: 0 }, }; const movers: SimAdapter = { name: "movers", version: "1", tickHz: 30, schema, input, createWorld: () => ({ players: new Map(), nextId: 1 }), addPlayer(w, player) { const e = { id: w.nextId++, x: 0, z: 0 }; w.players.set(player, e); return e.id; }, removePlayer(w, player) { w.players.delete(player); }, step(w, inputsByPlayer) { for (const [player, moves] of inputsByPlayer) { const e = w.players.get(player); if (!e) continue; for (const m of moves) { e.x += m.x * SPEED; e.z += m.z * SPEED; } } return []; }, snapshot: (w) => [...w.players].map(([owner, e]) => ({ id: e.id, kind: 1, owner, values: [e.x, e.z] })), applySnapshot(w, states) { for (const s of states) w.players.set(s.owner, { id: s.id, x: s.values[0] ?? 0, z: s.values[1] ?? 0 }); }, }; ``` ## 2. Run the authoritative room The room owns the world. It decodes input packets into a per-player queue, steps the world, and sends each player a delta snapshot against the last tick that player acknowledged. The queue ignores commands it already has and applies at most what its credit allows, so a modified client cannot move faster by sending more inputs. ```ts group=net import { decodeInputPacket, encodeSnapshot, InputQueue, quantizeEntity, SnapshotHistory, type QEntity } from "@netforge/engine/net"; interface Conn { readonly entity: number; readonly queue: InputQueue; readonly history: SnapshotHistory; readonly send: (bytes: Uint8Array) => void; ackTick: number; } /** The authoritative side: owns the world, takes inputs, sends each player a delta snapshot. */ class Room { readonly world = movers.createWorld(1, "server"); private tickN = 0; private readonly conns = new Map(); join(player: PlayerId, send: (bytes: Uint8Array) => void): number { const entity = movers.addPlayer(this.world, player); this.conns.set(player, { entity, queue: new InputQueue(), history: new SnapshotHistory(), send, ackTick: 0 }); return entity; } receive(player: PlayerId, bytes: Uint8Array): void { const c = this.conns.get(player); if (!c) return; const packet = decodeInputPacket(movers.input, bytes); if (packet.ackTick > c.ackTick && packet.ackTick <= this.tickN) c.ackTick = packet.ackTick; c.queue.receive(packet.commands); } step(): void { this.tickN++; const inputs = new Map(); for (const [player, c] of this.conns) inputs.set(player, c.queue.consume().map((cmd) => cmd.input)); movers.step(this.world, inputs); const all: QEntity[] = movers.snapshot(this.world).map((e) => ({ id: e.id, kind: e.kind, owner: e.owner, q: quantizeEntity(movers.schema, e) })); for (const c of this.conns.values()) { const baseline = c.ackTick > 0 ? (c.history.get(c.ackTick) ?? null) : null; const header = { tick: this.tickN, baselineTick: baseline ? c.ackTick : 0, ackSeq: c.queue.lastAppliedSeq, self: c.entity }; c.send(encodeSnapshot(movers.schema, header, all, baseline)); c.history.put(this.tickN, all); c.history.pruneBefore(c.ackTick); } } } ``` ## 3. Connect clients `NetClient` predicts its own player, sends input packets (each one repeats the last four unacknowledged commands), reconciles when a snapshot arrives and interpolates everyone else. Here the network is a direct function call, so the example runs in Node as it is: ```ts group=net import { NetClient } from "@netforge/engine/net"; const room = new Room(); let now = 0; // Two players. In a real game `send` is a WebSocket; here the calls are direct. const clients = [1, 2].map((player) => { const holder: { client?: NetClient } = {}; const entity = room.join(player, (bytes) => holder.client?.receiveSnapshot(bytes, now)); const client = new NetClient(movers, player, entity, (bytes) => room.receive(player, bytes)); holder.client = client; return client; }); // One second of play: player 1 walks right, player 2 walks forward. for (let t = 0; t < 30; t++) { clients[0]?.tick({ x: 1, z: 0 }, now); clients[1]?.tick({ x: 0, z: 1 }, now); room.step(); now += 1000 / movers.tickHz; } for (const c of clients) console.log(`player ${c.player}: predicted`, c.predicted()?.values, "server", c.authoritativeSelf()?.values); ``` Running it prints the same position from the client's prediction and from the server for both players. In a browser, `send` writes to a WebSocket and `receiveSnapshot` is called from its message handler. ## 4. Draw it with three.js Each frame, ask the client what to draw and copy it onto your meshes. Your own player comes from prediction, with any correction smoothed over about 100 ms. Everyone else is interpolated about 100 ms in the past. ```ts import type { NetClient } from "@netforge/engine/net"; /** Anything with a three.js-style position, such as a THREE.Mesh. */ interface Drawable { readonly position: { set(x: number, y: number, z: number): void }; } const UNITS_PER_METRE = 32; export function drawFrame(client: NetClient, meshFor: (entityId: number) => Drawable, nowMs: number): void { const { self, others } = client.render(nowMs); for (const e of self ? [self, ...others] : others) { const x = (e.values[0] ?? 0) / UNITS_PER_METRE; const z = (e.values[1] ?? 0) / UNITS_PER_METRE; meshFor(e.id).position.set(x, 0, z); } } ``` Call `client.tick(input, now)` on your fixed tick, using `client.tickIntervalMs()` as the interval (it nudges the input clock to keep the server's queue short), and call `drawFrame` from `requestAnimationFrame`. ## 5. Put the room behind a server The room above does not care about the transport. Net Forge's own local host in `@netforge/kit-play` runs the same kind of loop inside a [Colyseus](https://docs.colyseus.io/) room on one port, with per-player area of interest and snapshots at 20 Hz. The hosted Net Forge room server adds lag compensation for hitscans, a run log and replay. If you want those without writing them, start from a template, where all of it is already wired. ## Run the example The engine repository has this page as a runnable three.js scene: [`packages/engine/examples/threejs-scene`](https://github.com/netnetdev/net-forge/tree/main/packages/engine/examples/threejs-scene). It is an ordinary three.js scene (a `WebGLRenderer`, a grid and two capsule meshes) where the arrow keys become `InputCommand` intentions, an authoritative room steps the real `@netforge/engine-sim` world at 30 Hz behind a simulated 60 ms link, and the scene draws your player from prediction and the other player interpolated. From a clone of the public repository: ```sh git clone https://github.com/netnetdev/net-forge cd net-forge pnpm install pnpm build pnpm --filter @netforge/engine example:threejs ``` It opens on `http://localhost:5312`. The room runs in the page so the example needs no server; in a real game the two `net.send` calls in its `room.ts` become a WebSocket and the room runs in Node. A test in the same package (`test/example-threejs-scene.test.ts`) runs the room and two clients headless and checks that each prediction lands where the server says. ## When Colyseus on its own is the better fit Colyseus is an open-source multiplayer framework for Node.js with rooms, matchmaking and binary state sync, and since 0.18 it runs client prediction, reconciliation and server rewind loops for you while you supply the deterministic step function ([Netcode](https://docs.colyseus.io/netcode)). It also lists open-source three.js example games ([three.js examples](https://docs.colyseus.io/learn/examples/threejs)). Pick it on its own when you want to keep your own client loop and state model, when the same server must serve clients in other engines through its SDKs, or when you want its managed hosting. Net Forge's rooms are built on Colyseus too; what Net Forge adds is the shared simulation and the finished games. See [Net Forge and Colyseus](https://forge.netnet.capital/docs/compare/colyseus.md). ## How it works The netcode follows the client-side prediction and server reconciliation model: inputs never wait for a round trip, and the client converges on the server's state. Snapshots are bit-packed per field from the schema, and a delta carries only the fields that changed since the acknowledged baseline. If a baseline is missing, the client acknowledges nothing until a full snapshot arrives. When your game needs real physics, use [deterministic Rapier](https://forge.netnet.capital/docs/guides/threejs-rapier-physics.md) on the same fixed tick. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). ## Alternatives - **[Colyseus](https://docs.colyseus.io/)** is a full multiplayer server framework with rooms, matchmaking, schema state sync and [client prediction](https://docs.colyseus.io/netcode/client-prediction), plus SDKs for many client engines. Pick it when you want a server framework and will keep your own client loop. - **[Nakama](https://heroiclabs.com/docs/nakama/concepts/multiplayer/authoritative/)** runs authoritative match handlers on an open-source game backend that also provides accounts, storage and social features. - **[Socket.IO](https://socket.io/docs/v4/)** gives you event-based messaging. You write the tick, the authority and the prediction yourself, which suits a turn-based or low-rate game. Pick Net Forge when you want prediction, reconciliation, interpolation and delta snapshots as a TypeScript library that shares code between the browser and the server, or a finished template that already uses them. ## FAQ ### Can I keep my existing three.js scene? Yes. three.js stays the renderer. What changes is who decides where things are: the simulation does, on a fixed tick, and the scene copies its state every frame. ### Why does the simulation use integers instead of floats? So the client can predict exactly what the server will compute. When both sides step the same integers with the same inputs, prediction errors come only from things the client could not know, such as another player pushing you. ### Do the server and the client have to be the same language? With Net Forge, yes, and that is the point: the same TypeScript module steps the world on the server in Node and in the browser. ### How much bandwidth does this use? The snapshot codec packs each field in the bits the schema gives it and sends only changed fields. In the engine's shared simulation a resting entity costs about 3 bytes and a walking one about 8 per snapshot. ### Does Net Forge include a production server I can deploy? The public repository includes the local host in `@netforge/kit-play` for your computer and network. Production rooms with lag compensation and replay run on the hosted Net Forge service. See [Deploy and host your game](https://forge.netnet.capital/docs/guides/deploy-and-host.md). --- # Three.js multiplayer netcode: prediction, reconciliation and snapshots > How client-side prediction, server reconciliation, interpolation and delta snapshots work in a three.js multiplayer game, with a runnable TypeScript example that converges exactly under 80 ms latency and 5% packet loss. 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/threejs-multiplayer-netcode Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Good three.js multiplayer netcode has four parts: the server owns the world, the client predicts its own player at once, the client reconciles when the server's snapshot arrives, and everyone else is drawn slightly in the past between two snapshots. Net Forge ships all four as `@netforge/engine/net`, and every template already uses them; `npm create netforge@latest my-game -- --template arena --yes` gives you a game where you can watch them work. ```sh npm install @netforge/engine ``` ## The four parts | Part | What it does | Net Forge API | |---|---|---| | Inputs | One small command per tick with a sequence number. Each packet repeats the last 4 unacknowledged commands, so a lost packet is covered by the next. | `encodeInputPacket`, `decodeInputPacket`, `InputQueue` | | Prediction | The client applies its own command to a local copy of the simulation the moment it is sampled. | `Predictor`, `NetClient.tick` | | Reconciliation | On each snapshot the client resets its player to the server's state and replays every command the server has not applied yet. A difference shows as an offset that decays over about 100 ms. | `Predictor.reconcile`, inside `NetClient.receiveSnapshot` | | Interpolation | Other players are drawn about 100 ms behind the server's estimated clock, between two snapshots. | `InterpolationBuffer`, `ServerClock`, `NetClient.render` | | Snapshots | Bit-packed per field from a schema, as a delta against the last tick the client acknowledged. | `encodeSnapshot`, `decodeSnapshot`, `SnapshotHistory` | ## 1. Define a simulation both sides can run Prediction is only exact when the client's step gives the same numbers as the server's. Keep state on an integer grid and make a player's movement depend only on that player's input. This adapter moves one point per player in 1/32 m units. The blocks in steps 1 to 3 form one file. ```ts group=lag import type { EntitySchema, InputCodec, PlayerId, SimAdapter } from "@netforge/engine/net"; interface Move { readonly x: number; // -1, 0 or 1 readonly z: number; } interface World { readonly players: Map; nextId: number; } const SPEED = 4; // 1/32 m per tick const schema: EntitySchema = { fields: [ { name: "x", kind: "int", bits: 20 }, { name: "z", kind: "int", bits: 20 }, ], xField: 0, yField: 1, unitsPerWorld: 32, }; const input: InputCodec = { write: (w, m) => { w.writeBits(m.x + 1, 2); w.writeBits(m.z + 1, 2); }, read: (r) => ({ x: r.readBits(2) - 1, z: r.readBits(2) - 1 }), idle: { x: 0, z: 0 }, }; const movers: SimAdapter = { name: "movers", version: "1", tickHz: 30, schema, input, createWorld: () => ({ players: new Map(), nextId: 1 }), addPlayer: (w, p) => { const e = { id: w.nextId++, x: 0, z: 0 }; w.players.set(p, e); return e.id; }, removePlayer: (w, p) => void w.players.delete(p), step: (w, inputs) => { for (const [p, moves] of inputs) { const e = w.players.get(p); if (!e) continue; for (const m of moves) { e.x += m.x * SPEED; e.z += m.z * SPEED; } } return []; }, snapshot: (w) => [...w.players].map(([owner, e]) => ({ id: e.id, kind: 1, owner, values: [e.x, e.z] })), applySnapshot: (w, states) => { for (const s of states) w.players.set(s.owner, { id: s.id, x: s.values[0] ?? 0, z: s.values[1] ?? 0 }); }, }; ``` ## 2. Write the authoritative loop ```ts group=lag import { decodeInputPacket, encodeSnapshot, InputQueue, quantizeEntity, SnapshotHistory, type QEntity } from "@netforge/engine/net"; class Room { readonly world = movers.createWorld(1, "server"); private tickN = 0; private readonly conns = new Map; history: SnapshotHistory; send: (b: Uint8Array) => void; ackTick: number }>(); join(player: PlayerId, send: (bytes: Uint8Array) => void): number { const entity = movers.addPlayer(this.world, player); this.conns.set(player, { entity, queue: new InputQueue(), history: new SnapshotHistory(), send, ackTick: 0 }); return entity; } receive(player: PlayerId, bytes: Uint8Array): void { const c = this.conns.get(player); if (!c) return; const packet = decodeInputPacket(movers.input, bytes); if (packet.ackTick > c.ackTick && packet.ackTick <= this.tickN) c.ackTick = packet.ackTick; c.queue.receive(packet.commands); } step(): void { this.tickN++; const inputs = new Map(); for (const [p, c] of this.conns) inputs.set(p, c.queue.consume().map((cmd) => cmd.input)); movers.step(this.world, inputs); const all: QEntity[] = movers.snapshot(this.world).map((e) => ({ id: e.id, kind: e.kind, owner: e.owner, q: quantizeEntity(movers.schema, e) })); for (const c of this.conns.values()) { const baseline = c.ackTick > 0 ? (c.history.get(c.ackTick) ?? null) : null; c.send(encodeSnapshot(movers.schema, { tick: this.tickN, baselineTick: baseline ? c.ackTick : 0, ackSeq: c.queue.lastAppliedSeq, self: c.entity }, all, baseline)); c.history.put(this.tickN, all); c.history.pruneBefore(c.ackTick); } } } ``` ## 3. Play it over a bad network `VirtualNet` is a deterministic simulated network with a virtual clock, latency, jitter that reorders packets, and loss. It models unreliable datagrams, which is harder than a WebSocket. ```ts group=lag import { NetClient, VirtualNet, type LinkOptions } from "@netforge/engine/net"; // 80 ms each way, up to 20 ms of jitter and 5% loss. const link: LinkOptions = { latencyMs: 80, jitterMs: 20, loss: 0.05 }; const net = new VirtualNet(7); const room = new Room(); const tickMs = 1000 / movers.tickHz; const holder: { client?: NetClient } = {}; const entity = room.join(1, (bytes) => net.send(link, () => holder.client?.receiveSnapshot(bytes, net.now))); const client = new NetClient(movers, 1, entity, (bytes) => net.send(link, () => room.receive(1, bytes))); holder.client = client; // Walk right for two seconds, stop for one, walk forward for two, then idle while packets land. for (let t = 0; t < 160; t++) { const move = t < 60 ? { x: 1, z: 0 } : t < 90 || t >= 150 ? { x: 0, z: 0 } : { x: 0, z: 1 }; client.tick(move, net.now); room.step(); net.advanceTo(net.now + tickMs); } console.log("server:", room.world.players.get(1), "predicted:", client.predicted()?.values); console.log("packets:", net.sent, "dropped:", net.dropped, "snapshot bytes:", client.stats.snapshotBytes, "in", client.stats.snapshots, "snapshots"); ``` Run the file with `node --experimental-strip-types` (Node 22). It prints the same position from the server and from the client's prediction (`228, 240` with this seed), after 10 of 320 packets were dropped: the repeated commands in each input packet covered every loss. The 153 snapshots took 2,742 bytes, about 18 bytes each, because each one is a delta against what the client acknowledged. The first few inputs are not predicted, because the client starts predicting once its first snapshot arrives (about 80 ms here). ## 4. Draw it In a three.js client, call `client.tick(input, now)` on the fixed tick and, each animation frame, `client.render(now)`. It returns `self` (the predicted player, with any correction smoothed) and `others` (interpolated). Copy their values onto mesh positions, dividing by `unitsPerWorld`. The [three.js authoritative multiplayer guide](https://forge.netnet.capital/docs/guides/threejs-authoritative-multiplayer.md) has the drawing code. ## How it works - **Why replay works.** A player's own fields depend only on that player's inputs and the static world, so replaying the unacknowledged commands on top of the server's state gives the server's answer. Interactions the client cannot know about, such as another player pushing you, are corrected by the next snapshot and smoothed. - **Why the input clock drifts.** For simulations that take exactly one command per tick (the engine's physics sims), the snapshot header reports how many commands are waiting in the server's queue. `NetClient.tickIntervalMs()` runs the client's input clock up to 8% faster or slower to hold that queue near 2, which keeps added latency low without starving the server. - **Why remote players are 100 ms behind.** Drawing between two received snapshots needs both to have arrived. 100 ms at a 20 to 30 Hz snapshot rate leaves room for one late or lost packet. - **What the templates add.** Hitscan weapons in the arena shooter are lag compensated on the hosted server, which rewinds other players to what the shooter saw (capped at 200 ms). Area of interest sends each player only nearby entities. The survivor template's 2,000 enemies use a per-kind field subset so each costs only a few bits. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md) for the concepts in more depth. ## Alternatives - **[Colyseus](https://docs.colyseus.io/netcode)** offers [client prediction](https://docs.colyseus.io/netcode/client-prediction) on top of its room and schema state sync. Pick it when you want a full server framework with SDKs for many client engines. - **[Nakama](https://heroiclabs.com/docs/nakama/concepts/multiplayer/authoritative/)** provides authoritative match handlers inside a complete open-source game backend. - Writing your own on **[Socket.IO](https://socket.io/docs/v4/)** or raw WebSockets is reasonable for slow or turn-based games, where prediction matters less. Pick Net Forge when you want the netcode as a TypeScript library whose simulation runs unchanged in the browser and on the server, or ten finished games that already use it. ## FAQ ### What is client-side prediction? The client applies the player's input to its own copy of the game immediately instead of waiting for the server, so movement feels instant even with 100 ms of latency. The server still decides the result. ### What is server reconciliation? When the server's snapshot arrives, the client rewinds its player to the server's state and replays the inputs the server has not processed yet. If the server agreed, nothing visible changes. If not, the client moves toward the server's answer smoothly. ### Why not just send positions from the client? A client that sends its own position can send any position. Sending only inputs means the server decides where everyone is, which removes speed and teleport cheats. ### What is a delta snapshot? A snapshot that encodes only what changed since a snapshot the client has confirmed it received. If the client never got that baseline, it acknowledges nothing until a full snapshot arrives. ### Does this work over WebSockets? Yes. The templates use WebSockets through a Colyseus room. The netcode is written for the harder unreliable case too, which is why the example above survives reordering and loss. ### Do I need three.js for this? No. `@netforge/engine/net` has no renderer dependency. The templates draw with three.js, but any renderer can read `client.render(now)`. --- # How to add deterministic Rapier physics to a three.js game > Use @netforge/engine-sim's createPhysics for Rapier rigid bodies, triggers, ray casts and a character controller on a fixed tick, with bit-exact snapshots and a state hash, so the server and every browser compute the same world. 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/threejs-rapier-physics Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. `createPhysics()` in `@netforge/engine-sim` gives a three.js game Rapier physics that is deterministic on a fixed tick: rigid bodies, triggers, ray and shape casts, joints and a character controller, with `snapshot()`, `restorePhysics()` and `stateHash()` for multiplayer. It uses Rapier's deterministic WebAssembly build, so the same calls on two worlds give bit-identical states. ```sh npm install @netforge/engine-sim ``` The package has no renderer dependency and compiles with `lib: ES2022` only, so the same file runs in the browser and in Node. ## What you get | Feature | API | |---|---| | Bodies: dynamic, kinematic, static; box, sphere, capsule, cylinder | `addBody`, `addStaticBox`, `setKinematicTarget` | | Sensors with enter and exit events | `addTrigger`, `step().events` | | Collision layers (16 bits, pairwise masks) | `Layer`, `layerMask`, `layer` and `mask` on a body | | Queries | `raycast`, `raycastAll`, `shapeCast`, `overlap` | | Forces | `applyImpulse`, `applyForce`, `applyKnockback` (also works on characters) | | Joints | `addJoint({ kind: "fixed" \| "ball" \| "hinge" \| "slider" })` | | Character motor | slopes, steps, ground snapping, coyote time, jump buffer, variable jump, air control, dash, pushing bodies | | Multiplayer | `snapshot()`, `restorePhysics(bytes)`, `stateHash()`, `characterState` and `setCharacterState` | ## 1. Build a world and step it ```ts title=physics.ts import { initSim, createPhysics, Layer, layerMask } from "@netforge/engine-sim"; await initSim(); // loads Rapier's deterministic WebAssembly build, once per process const phys = createPhysics({ tickRate: 30, gravity: [0, -20, 0] }); phys.addStaticBox([30, 0.5, 30], [0, -0.5, 0]); // the floor, on Layer.Level const crate = phys.addBody({ type: "dynamic", shape: { box: [0.4, 0.4, 0.4] }, position: [0, 1, 3], mass: 8, restitution: 0.2, layer: Layer.Prop }); const zone = phys.addTrigger({ box: [1, 2, 2] }, [6, 1, 0]); // a sensor: enter and exit events only const hero = phys.addCharacter({ position: [0, 1, 0], maxSpeed: 7, jumpVelocity: 8, coyoteTicks: 5, jumpBufferTicks: 5 }); // Two seconds at 30 Hz: run along +x and jump once. for (let t = 0; t < 60; t++) { const { events, characters } = phys.step(new Map([[hero, { moveX: 1, moveZ: 0, jump: t === 10, dash: false }]])); if (characters.find((c) => c.id === hero)?.jumped) console.log(`tick ${t}: jumped`); for (const e of events) if (e.type === "trigger.enter" && (e.a === zone || e.b === zone)) console.log(`tick ${t}: the hero entered the zone`); } phys.applyKnockback(crate, [0, 0.4, 0], 6, { lift: 2 }); // 6 m/s away from the origin, 2 m/s up const hit = phys.raycast([0, 0.4, 0], [0, 0, 1], 50, { mask: layerMask("Prop") }); console.log(hit?.body === crate ? `ray hit the crate at ${hit.distance.toFixed(2)} m` : "ray missed"); ``` Run it with `node --experimental-strip-types physics.ts` on Node 22. It prints `tick 10: jumped`, `tick 24: the hero entered the zone` and `ray hit the crate at 2.61 m`. ## 2. Draw it with three.js `step()` never reads a clock, so drive it from a fixed-step accumulator and draw between ticks. `FixedStepper` from the same package turns frame deltas into a tick count and an interpolation `alpha`. Each frame, copy `phys.position(id)` and `phys.rotation(id)` onto the matching `THREE.Mesh`, blending the previous and current tick by `alpha` for smooth motion at any refresh rate. ## 3. Snapshot, restore and compare This is what makes physics usable in multiplayer: a client can take the server's bytes, rebuild the same world and keep stepping in lockstep. ```ts title=snapshot.ts import { initSim, createPhysics, restorePhysics } from "@netforge/engine-sim"; await initSim(); const a = createPhysics({ tickRate: 30, gravity: [0, -20, 0] }); a.addStaticBox([30, 0.5, 30], [0, -0.5, 0]); const ball = a.addBody({ type: "dynamic", shape: { sphere: 0.5 }, position: [0, 4, 0], restitution: 0.6 }); for (let t = 0; t < 20; t++) a.step(new Map()); const saved = a.snapshot(); // bytes: Rapier's world plus the wrapper's ids, characters and joints const b = restorePhysics(saved); // a second world from the same bytes a.applyImpulse(ball, [3, 0, 0]); b.applyImpulse(ball, [3, 0, 0]); for (let t = 0; t < 120; t++) { a.step(new Map()); b.step(new Map()); } console.log(a.stateHash() === b.stateHash() ? "identical worlds" : "diverged"); ``` It prints `identical worlds`. `stateHash()` hashes the raw float bits of every pose, velocity and motor state in body order, so equal hashes mean identical worlds. Use it in tests and to detect a desync. ## How it works - **Deterministic build.** The package uses `@dimforge/rapier3d-deterministic-compat` 0.21. Rapier documents that its WebAssembly and JavaScript version is [cross-platform deterministic](https://rapier.rs/docs/user_guides/javascript/determinism). - **Stable order.** Every body gets a `BodyId` in creation order that is never reused, every loop runs in that order, and events come back sorted by `(a, b, type)` with `a < b`. - **Queries see the present.** Rapier's scene queries only see colliders after a step, so the wrapper runs a zero-length step before a query when bodies were added, removed or teleported since the last one. That flag rides in snapshots too. - **Fast objects.** `ccd: true` adds a swept pass for bullets and thrown props. Rapier 0.21 already stops plain fast spheres on static walls (tested to 2,000 m/s), but a thin body spinning fast can still tunnel, so keep projectiles round. - **Free memory.** Call `destroy()` to release the WebAssembly memory when a world is done. For the netcode around it, see [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md) and [Run the same simulation on the server and in the browser](https://forge.netnet.capital/docs/guides/shared-client-server-simulation.md). ## Alternatives - **[Rapier](https://rapier.rs/docs/)** directly, through its official JavaScript packages. Pick it when you want the full Rapier API and will handle ordering, snapshots and the character motor yourself. - **[cannon-es](https://github.com/pmndrs/cannon-es)** is a lightweight JavaScript physics engine maintained by pmndrs. Pick it for single-player three.js scenes where determinism across machines is not a requirement. - **[Ammo.js](https://github.com/kripken/ammo.js)** is a WebAssembly port of the Bullet physics engine. Pick it when you need Bullet's feature set in the browser. Pick `@netforge/engine-sim` when the physics must give the same answer on the server and in every browser, with snapshots you can send over the network. ## FAQ ### Is Rapier deterministic in JavaScript? Rapier states that its WebAssembly and JavaScript version is fully cross-platform deterministic. `@netforge/engine-sim` builds on that with a stable body order, sorted events and snapshots that restore bit-exact, and its tests check lockstep and replay determinism over 600 ticks. ### Do I have to use the Net Forge engine to use this physics? No. `@netforge/engine-sim` is its own MIT package with no renderer and no network code. Use `createPhysics()` in any three.js project. ### How do I make a moving platform? Make it a `kinematic` body and call `setKinematicTarget(id, position, rotation)` each tick. Kinematic bodies push dynamic bodies and carry characters. ### Why does my query not see a body I just added? Rapier queries only see colliders after a step. The wrapper handles this for you by running a zero-length step before a query when bodies changed since the last step. ### How do I reconcile a predicted character? Read the server's motor state with `characterState(id)`, apply it with `setCharacterState(id, s)` on the client, then replay the inputs the server has not applied yet. --- # How to run the same game simulation on the server and in the browser > @netforge/engine-sim is one TypeScript module that steps the same world on a Node server and in the browser: fixed tick, quantized inputs, integer state and snapshots that restore bit-exact. A runnable example proves two worlds stay identical. 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/shared-client-server-simulation Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. To run one game simulation on both the server and the browser, it has to be a pure function of the world and that tick's inputs: no clocks, no DOM, no network, and state that both sides round the same way. `@netforge/engine-sim` is built that way. It is a bitECS world with deterministic Rapier physics, a fixed tick, quantized inputs and binary snapshots, and it compiles with `lib: ES2022` only, so the same import works in Node and in a browser bundle. ```sh npm install @netforge/engine-sim ``` ## The rules that make it work | Rule | How engine-sim does it | |---|---| | One fixed tick | `step(world, inputs)` advances exactly `1 / tickRate` seconds and never reads a clock | | Same inputs on both sides | Every command is quantized on entry (moves to 1/127, yaw to a 16-bit turn) | | Same rounding | State is snapped onto a fixed-point grid (1/1024 m) at the end of every tick | | Same order | Entities iterate in `netId` order; the PRNG seed lives in the state | | Same physics | The deterministic Rapier build, with cross-platform determinism enabled | | Restorable | `applyState(world, captureState(other))` reproduces `other` exactly | ## 1. Step two worlds with the same inputs ```ts title=shared.ts import { addPlayer, applyState, Button, captureState, createWorld, encodeSnapshot, initSim, step, type InputCommand } from "@netforge/engine-sim"; await initSim(); // once per process: loads the deterministic Rapier build // The same config on both sides: one plays the server, one plays a browser. const config = { tickRate: 30, seed: 7, enemies: { target: 500 } }; const server = createWorld(config); const browser = createWorld(config); for (const w of [server, browser]) { addPlayer(w, 1); addPlayer(w, 2); } // Ten seconds of the same inputs on both. for (let tick = 0; tick < 300; tick++) { const inputs = new Map([ [1, { seq: tick + 1, tick, moveX: 0, moveZ: 1, yaw: tick / 30, buttons: tick % 20 === 0 ? Button.Fire : 0 }], [2, { seq: tick + 1, tick, moveX: 1, moveZ: 0, yaw: 0, buttons: 0 }], ]); step(server, inputs); step(browser, inputs); } const a = encodeSnapshot(captureState(server)); const b = encodeSnapshot(captureState(browser)); console.log(`snapshot ${a.length} bytes, identical: ${a.length === b.length && a.every((x, i) => x === b[i])}`); // A late joiner: copy the server's state into a fresh world and keep stepping in lockstep. const late = createWorld(config); applyState(late, captureState(server)); const next = new Map([[1, { seq: 301, tick: 300, moveX: 0, moveZ: 0, yaw: 0, buttons: 0 }]]); step(server, next); step(late, next); const c = encodeSnapshot(captureState(server)); const d = encodeSnapshot(captureState(late)); console.log(`after restore, identical: ${c.length === d.length && c.every((x, i) => x === d[i])}`); ``` Run it with `node --experimental-strip-types shared.ts` on Node 22. It prints `snapshot 9521 bytes, identical: true` with 500 enemies and two players after 300 ticks, then `after restore, identical: true`. ## 2. Drive it from a real clock `step` takes no time argument, so each side decides when to call it. `FixedStepper` turns wall-clock deltas into whole ticks plus a render `alpha`, and drops ticks past a cap so a hidden tab cannot spiral: ```ts title=loop.ts import { FixedStepper, renderView, type SimWorld } from "@netforge/engine-sim"; export function startLoop(world: SimWorld, tick: () => void, draw: (alpha: number) => void): void { const stepper = new FixedStepper(30); let last = performance.now(); const frame = (now: number) => { const { ticks, alpha } = stepper.advance(now - last); last = now; for (let i = 0; i < ticks; i++) tick(); draw(alpha); requestAnimationFrame(frame); }; requestAnimationFrame(frame); void renderView(world); // dense typed arrays (x, y, z, yaw and their previous-tick values) for the renderer } ``` On the server the same `tick()` runs from a timer instead of `requestAnimationFrame`. In the browser, `renderView(world)` gives `netId`, `kind`, positions and the previous tick's positions as typed arrays, so a three.js renderer can interpolate with `alpha` without touching the ECS. ## 3. Send snapshots `encodeSnapshot(state)` is a full snapshot and `encodeSnapshot(state, baseline)` is a delta against a state the receiver acknowledged. Fields are varints of the difference from the baseline with a changed-field mask per entity, so a resting entity costs about 3 bytes and a walking one about 8. `snapshotBaseTick(bytes)` tells the receiver which baseline a delta needs, and `decodeSnapshot(bytes, baseline)` throws if it is given the wrong one. `captureState(world, include)` takes a filter per client for area of interest. ## How it works in the templates Every Net Forge template runs on this module. The hosted room server and the local host in `@netforge/kit-play` step the authoritative world; the browser steps a copy for its own player to predict movement and reconciles to the server's snapshots. Because the code is the same, the browser can never disagree with the server about the rules, only about things it could not know yet. Across different JavaScript engines, `Math.sin` and `Math.cos` can differ in the last bit; the server is authoritative and clients reconcile, so that is harmless. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md). Measured: in content mode the shared simulation holds 4 players and 2,000 enemies at 2.22 ms per tick on average (p99 5.21 ms) in the survivor template's playtest on an Apple M2 Pro with Node 22. Enemies steer in the ECS rather than as Rapier bodies, which is what keeps that many cheap. ## Alternatives - **[Colyseus](https://docs.colyseus.io/netcode)** lets you supply a deterministic step function for its [client prediction](https://docs.colyseus.io/netcode/client-prediction), alongside its schema state sync. Pick it when your server framework is Colyseus and you will write the simulation yourself. - **[bitECS](https://github.com/NateTheGreatt/bitECS)**, which engine-sim builds on, is a small, fast ECS for JavaScript. Pick it on its own when you want an ECS and will design determinism and snapshots yourself. - **[Rapier](https://rapier.rs/docs/)** on its own provides [cross-platform deterministic](https://rapier.rs/docs/user_guides/javascript/determinism) physics in JavaScript, without the ECS or the snapshot format. ## FAQ ### Can the same TypeScript code run on the server and in the browser? Yes, if it avoids browser and Node APIs and never reads the clock. `@netforge/engine-sim` compiles with `lib: ES2022` only and takes time only as a tick count, so the same module runs in both. ### Is JavaScript floating point deterministic? Basic arithmetic is, on IEEE 754 doubles. Transcendental functions such as `Math.sin` can differ in the last bit between engines. engine-sim snaps state onto an integer grid every tick, so tiny differences do not accumulate, and the server stays authoritative. ### What is the difference between lockstep and server-authoritative? In lockstep, every peer steps the same inputs and must stay identical. In a server-authoritative game, the server's world is the truth and clients only predict. engine-sim supports both properties: two worlds stay identical with the same inputs, and a client can always snap to the server with `applyState`. ### How do late joiners get the current state? Send them a full snapshot. `applyState(world, captureState(server))` on the client reproduces the server's world exactly, and both keep stepping identically from there. ### Does engine-sim include networking? No. It has no network code by design. The client netcode is `@netforge/engine/net`; see [the netcode guide](https://forge.netnet.capital/docs/guides/threejs-multiplayer-netcode.md). --- # How to use free CC0 3D models and sounds in a web game > The Net Forge asset library gives a web game CC0 characters, creatures, vehicles, props, sprites and sound effects addressed by id, in four model sets and six art styles, with each pack's licence verified and every file pinned by hash. 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/cc0-game-assets Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. The quickest way to put free, CC0-licensed 3D models and sounds in a web game is to reference them by library id: a Net Forge game says `model: "style.lowpoly.bat"`, and the template loads the optimized glTF file and draws it in the game's art style. The ids come from `@netforge/asset-library`, whose built files (`@netforge/asset-library-files`, 536 files, about 36 MB) are installed with every template. ```sh npm create netforge@latest my-game -- --template survivor --yes npm install @netforge/asset-library @netforge/asset-library-files ``` The first line is all you need inside a Net Forge game. The second is for using the library in a project of your own. ## What is in the library | Set | Entries | What | |---|---|---| | `lowpoly` (3D) | 228 | Animated characters, monsters, animals, dinosaurs, robots, tanks, weapons and props from Quaternius, and Kenney's character, car, blaster, nature, space, food and city kits | | `ps1` (3D) | 238 | The same kinds of models in a late-1990s console look | | `voxel` (3D) | 227 | Block-built versions | | `pixel` (2D) | 58 | Sprites from Kenney's Tiny Dungeon, Pixel Platformer and Pixel Vehicle packs | | `audio` | 18 | 17 sound effects, one per game-feel cue, and one music loop | The `toon` and `handdrawn` art styles are drawn by the engine from the 3D models, so a game can switch between all six styles: `lowpoly`, `toon`, `ps1`, `pixel`, `voxel` and `handdrawn`. Every model and sound is third-party art under [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/), a public domain dedication, so it can be used in commercial games without attribution. The package's `NOTICE` lists every pack, where its licence was verified, and credits the authors anyway: [Quaternius](https://quaternius.com), [Kenney](https://kenney.nl) and others. Each file's bytes are pinned by sha256. ## 1. Use models in a Net Forge game In `src/game.ts`, models are always library ids, never URLs or paths: ```ts title=src/game.ts import { prefab } from "@netforge/engine"; prefab("wisp", { model: "style.lowpoly.bat", health: 8, speed: 5, radius: 0.25, damage: 4, ai: "chase", drops: ["ember_small"] }); prefab("brute", { model: "style.lowpoly.orc_brute", health: 160, speed: 2.2, radius: 0.8, mass: 6, damage: 14, ai: "tank", drops: ["ember_small"] }); ``` The game's `style` decides how they draw. A `style.lowpoly.` id draws as that style's own model of the same name when the style has one, so the same file works in every style. An id that is not in the library fails at once with a line that says so. ## 2. Find models by description ```ts title=find-assets.ts import { assetById, inEveryStyle, restyleId, search, soundBank, stockedStyles } from "@netforge/asset-library"; console.log("styles with models:", stockedStyles().join(", ")); // Find models by description, in one style. for (const hit of search("lowpoly", "monster truck", 3)) console.log(hit.asset.id, "-", hit.asset.line); // The same model in another style. console.log(restyleId("style.lowpoly.bat", "voxel")); // The same ids in every stocked style, for example to preload before a style switch. console.log(inEveryStyle(["style.lowpoly.bat", "style.lowpoly.truck"])); // Where an asset came from. const bat = assetById("style.lowpoly.bat"); console.log(bat?.source); // Sound effects for the engine's SoundPlayer: cue name to URL under your library base. const bank = soundBank("/library"); console.log(Object.keys(bank.cues).join(", ")); ``` The package ships TypeScript source, so run this with a TypeScript runner such as `npx tsx find-assets.ts` or inside a Vite project. It prints `lowpoly, pixel, ps1, voxel`, then `style.lowpoly.monster_truck - pickup truck on giant wheels` and two more trucks, then `style.voxel.bat`, the source of the bat (its Poly Pizza model page and file hash), and the cue names `hit, impact, kill, bigKill, hurt, levelUp, gem, coin, dash, step, land, boss, ui, pick, downed, revive, shoot`. ## 3. Use the files in your own Vite project `@netforge/asset-library/vite` exports `assetLibrary()`, a Vite plugin that serves the library at `./library` in development and copies only the models a game uses into its build. Point its `dir` at `node_modules/@netforge/asset-library-files/library`. `@netforge/asset-library/three` exports `LibraryClient`, which loads models through the engine's `AssetLoader` (glTF with meshopt and KTX2), prepares them (facing, scale, tint) and animates characters through shared rigs with the canonical clips `idle`, `walk`, `run`, `jump`, `attack`, `shoot`, `hit` and `die`. ## How it works Each style is one JSON manifest. Every entry has an id, a kind, a body plan (biped, quadruped, flyer, blob, wheeled or static), a rig, its source (an archive path or a pinned file URL with its licence page), a height, one plain description and search tags. The fetch script downloads each pack from its official source, and the Net Forge asset pipeline optimizes it into the built files. Characters from the same author share one rig and clip set, which is why one animation system drives all of them. ## Alternatives - **[Kenney](https://kenney.nl/assets)** publishes large CC0 asset packs for 2D and 3D games. Download them directly when you want the original packs and formats. - **[Quaternius](https://quaternius.com)** releases CC0 low-poly model packs, many of them animated. - **[Poly Pizza](https://poly.pizza)** is a searchable library of low-poly models with the licence shown on each model page. Check each one, because not every model there is CC0. - **[OpenGameArt](https://opengameart.org)** hosts free art and audio under several licences, CC0 among them. Pick the Net Forge library when you want those CC0 assets already optimized, addressed by id, rigged to shared clips and drawable in six art styles. ## FAQ ### What does CC0 mean for my game? CC0 1.0 is a public domain dedication: you may copy, modify and use the work, including commercially, without asking or crediting. Crediting is still a kind gesture, and the library's `NOTICE` does it for you. ### Can I use these assets outside Net Forge? Yes. The models and sounds are CC0, and the library package's code is MIT licensed. ### Can I add my own models? In a Net Forge game, `src/game.ts` references library ids only, so a model has to be in the library. In your own project you can load any glTF file with three.js next to the library's models. ### Why ids instead of file paths? An id is checked against the library when the game is defined, so a typo fails at once with a clear line. It also lets the same game draw in another art style, where the id maps to that style's own model. ### How big are the files? The full built library is 536 files and about 36 MB unpacked. The Vite plugin copies only the models a game uses into its build. --- # How to make a WebGPU browser game with a WebGL2 fallback > Net Forge draws with three.js's WebGPURenderer, which uses WebGPU where the browser has it and WebGL 2 everywhere else, and wraps it in a render stack with lighting, post effects, GPU particles and quality tiers that adapt to the device. 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/webgpu-browser-game Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. A WebGPU browser game that still runs everywhere needs one renderer with two backends. Net Forge's runtime uses three.js's `WebGPURenderer`, which picks WebGPU when the browser supports it and falls back to a WebGL 2 backend otherwise, so one code path covers every browser. Every template already draws this way: ```sh npm create netforge@latest my-game -- --template survivor --yes cd my-game npm run play ``` The three.js docs describe the fallback directly: "By default, the renderer tries to use a WebGPU backend if the browser supports WebGPU. If not, `WebGPURenderer` falls backs to a WebGL 2 backend." ([three.js WebGPURenderer](https://threejs.org/docs/pages/WebGPURenderer.html)) ## What the runtime gives you | Part | What it does | |---|---| | `createRenderer(canvas, { forceWebGL })` | Creates the `WebGPURenderer`, waits for `init()` and reports which backend it got (`"webgpu"` or `"webgl2"`) | | `createRenderStack(handle, scene, camera, options)` | Lighting with a shadow that follows the focus, fog and sky, bloom, colour grade, vignette, FXAA, GPU particles, ground decals and rings | | `QualityManager` | Quality tiers `low`, `medium` and `high`, detected from the device, plus dynamic resolution held to the frame budget | | `GpuParticles` | Every particle preset in one draw, with motion on the GPU | | `AnimatedCrowd`, `VatCrowd` | Thousands of animated instances in one draw (procedural motion, or skinned clips baked to a vertex-animation texture) | | Art styles | Six style kits (`lowpoly`, `toon`, `ps1`, `pixel`, `voxel`, `handdrawn`) that the stack applies by name | ## 1. Create the renderer and the stack This is the setup a template runs. It typechecks against `@netforge/engine/runtime`; your project needs `three` installed next to it for the scene. ```ts title=render.ts import { CameraRig, createRenderer, createRenderStack } from "@netforge/engine/runtime"; type Scene = Parameters[1]; export async function startRendering(canvas: HTMLCanvasElement, scene: Scene, focus: () => { x: number; y: number; z: number }): Promise { // ?webgl=1 forces the WebGL 2 backend, handy for testing the fallback. const handle = await createRenderer(canvas, { forceWebGL: new URLSearchParams(location.search).has("webgl") }); console.info(`drawing with ${handle.backend}`); const rig = new CameraRig(canvas.clientWidth / canvas.clientHeight); const stack = createRenderStack(handle, scene, rig.camera, { preset: "dusk", quality: "auto", bloom: true, style: "toon" }); let last = performance.now(); handle.renderer.setAnimationLoop((now: number) => { const dt = (now - last) / 1000; last = now; if (stack.frame(dt, focus())) stack.resize(canvas.clientWidth, canvas.clientHeight); }); } ``` `stack.frame(dt, focus)` replaces `renderer.render(scene, camera)`. It advances effect time, moves the shadow to the focus, steps dynamic resolution and draws. It returns true when the pixel ratio changed, which is the moment to resize. ## 2. Declare the look from game code In a Net Forge game you do not touch the renderer at all. `src/game.ts` declares the look, and the template's render stack draws it: ```ts title=src/game.ts import { game } from "@netforge/engine"; export default game({ template: "survivor", style: "lowpoly", places: { field: { chunks: "places/field", spawn: [0, 1, 0] } }, players: { prefab: "style.lowpoly.hooded_adventurer", max: 4, weapon: "ember_nova" }, look: { preset: "dusk", bloom: 1.3, fog: 0.4, vignette: 0.4, tint: "#ffd9a0" }, }); ``` `preset` is `night`, `dusk`, `day` or `studio`. `bloom` runs from 0 to 3, `fog` from 0 to 1, and unknown values fail with a message. Slow devices drop shadows, post and resolution by themselves, so turning effects on does not break the frame rate. ## 3. Warm up the pipelines WebGPU builds render pipelines on first use. Draw one of everything out of sight while the game loads (the survivor template's scene does this in its `warmup` step), so the first explosion does not hitch. ## How it works `createRenderer` asks three.js for a `WebGPURenderer` with `forceWebGL` off by default. After `await renderer.init()`, three.js has chosen WebGPU when `navigator.gpu` yields an adapter and WebGL 2 otherwise, and the runtime reports which. Materials are three.js node materials, which compile to WGSL on WebGPU and GLSL on WebGL 2, so the same scene, instancing and post chain run on both. The quality tier comes from device hints (a coarse pointer or mobile user agent, core count, memory), and dynamic resolution lowers the pixel ratio when frames run long. You can check it yourself in the engine demo: `?webgl=1` forces the WebGL 2 backend and `?enemies=2000` stresses the crowd. ## Alternatives - **[three.js](https://threejs.org/docs/pages/WebGPURenderer.html)** on its own, with `WebGPURenderer`, gives you the same backend selection. Pick it when you want to build your own render pipeline. - **[Babylon.js](https://doc.babylonjs.com/setup/support/webGPU)** has a full WebGPU engine alongside its WebGL engine, with an editor and a large feature set. Pick it when you want a complete 3D engine with official tooling. - **[PlayCanvas](https://developer.playcanvas.com/)** is a WebGL and WebGPU engine with a collaborative browser-based editor. Pick it when your team wants to build scenes in an editor. Pick Net Forge when you want the WebGPU and WebGL 2 rendering already tuned for multiplayer games, with a simulation and netcode underneath it. ## FAQ ### Which browsers support WebGPU? Support depends on the browser, version and platform, and changes quickly. Because Net Forge falls back to WebGL 2 automatically, a game does not need to check: it runs on either backend. ### How do I test the WebGL 2 fallback on a browser that has WebGPU? Pass `forceWebGL: true` to `createRenderer`. The action templates and the engine demo map `?webgl=1` in the URL to it. ### Do I need to write WGSL shaders? No. three.js node materials compile to WGSL for WebGPU and GLSL for WebGL 2. Net Forge's effects are written once as node materials. ### Is WebGPU faster than WebGL 2 for games? It can be, mainly by lowering CPU cost per draw call and enabling GPU compute. Net Forge keeps draw counts low on both backends with instanced crowds and one-draw particles, so games run well on WebGL 2 too. ### Does it run on phones? Yes. The quality tier detects mobile devices and starts lower, and dynamic resolution holds the frame budget. Every template has touch controls. --- # How to test a multiplayer game locally with bots and play money > Open one room in several browser tabs with npm run play, let phones on your network join, and run headless playtest bots with npm test and npm run check. Everything runs on your computer with play money. 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/local-multiplayer-testing Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. To test a Net Forge multiplayer game on your own computer, run `npm run play -- --players 2` to open two players in one room, and `npm test` to have bots play it headless and report what broke. Both run entirely on your machine: no wallet, no chain, no Net Forge servers, and coins are play money. ```sh npm create netforge@latest my-game -- --template arena --yes cd my-game npm run play -- --players 2 npm test ``` ## The tools | Command | What it does | |---|---| | `npm run play` | Builds the client with your `src/game.ts`, starts the local room host and opens `http://127.0.0.1:4663` | | `npm run play -- --players 3` | Opens three tabs, each a separate player in the same room | | `npm run play -- --host 0.0.0.0` | Lets phones and laptops on your network join | | `npm run play -- --port 5000 --no-open` | Another port, and print the link instead of opening a browser | | `npm test` | Compiles `src/game.ts` as the play build does and plays it headless with bots | | `npm test -- --minutes 5 --bots 4 --seed 2` | A longer run, more bots, another seed | | `npm run check` | The same playtest, printed as JSON for scripts and coding agents | ## 1. Play several players at once Each tab or device that opens the link is its own player with its own input, prediction and view. Every tab joins the open room until it is full, then a new room opens (the local host holds up to 8). Seat bots join the arena shooter while at least one person is in the room, so one tab already has opponents. To try a phone, run `npm run play -- --host 0.0.0.0`, then open the printed link on the phone with `localhost` replaced by your computer's network address, in the page address and in its `server` parameter. ## 2. Run the playtest bots ```sh npm test ``` The test compiles `src/game.ts` exactly as the play build does, so a disallowed import or an invalid declaration fails here first, with its file, line and reason. Then bots play the game with no browser and no server, and it prints a short report: ```text Compiling src/game.ts (arena shooter)... Playtest: 2 bots for 3 minutes of game time... kills 41, downs 41, wave 1, top level 0 earn rules fired 2 times (first_blood 2) 0.10 ms per tick on this computer PASS: the game compiles and the playtest checks pass. ``` It exits 1 when the game does not compile, a playtest check fails (for example nobody scores, no earn rule fires, or one of the genre's own checks fails), or the game softlocks: nothing changes for 30 seconds of game time. ## 3. Read the JSON in scripts and agents ```sh npm run check ``` prints the whole result: `ok`, `template`, `problems` (one line each) and `report` with `ticks`, `bots`, `kills`, `deaths`, `wave`, `maxLevel`, `earned` per rule, `softlock`, `msPerTick` and `p99MsPerTick`, plus genre measures such as the seconds to the first threat and the first reward. A coding agent should run it after every change and fix each line in `problems`. ## 4. Write your own playtest The same harness is the engine's `engine.test.run`. Template packages use it in their own tests with named assertions: ```ts title=playtest.test.ts import { engine } from "@netforge/engine"; export async function playtest(): Promise { const r = await engine.test.run({ ticks: 30 * 180, bots: 4, seed: 3, assert: [ { name: "kills", check: (x) => x.kills > 500 }, { name: "levels", check: (x) => x.maxLevel >= 5 }, { name: "earn emitted", check: (x) => x.earnEmitted > 0 }, { name: "no softlock", check: (x) => !x.softlock }, ], }); if (r.failed.length > 0) throw new Error(`playtest failed: ${r.failed.join(", ")}`); console.info(`${r.msPerTick.toFixed(2)} ms per tick, peak ${r.peakEnemies} enemies`); } ``` Call it after your game module has been loaded (the template's manifest loads `src/game.ts`). Measured with this harness on an Apple M2 Pro and Node 22: the survivor template holds 2,000 enemies with 4 bots at 2.22 ms per tick on average (p99 5.21 ms), and the arena shooter runs 4 bots for 4 minutes at 60 Hz at 0.10 ms per tick. ## How it works `kit-play` builds the template's browser client with your `src/game.ts` in place of the template's own, and bundles a local room host from the engine's open simulation and netcode: one world, a fixed tick, one input queue per player and per-player delta snapshots at 20 Hz. Sign-in, the referee and every money route answer 403, so earn rules fire and show but pay nothing. Bots play through the same simulation as people, and in arena games they never earn. See [Testing with playtest bots](https://forge.netnet.capital/docs/concepts/playtest-bots.md). ## Alternatives - **[Colyseus Playground](https://docs.colyseus.io/tools/playground)** lets you connect test clients to Colyseus rooms from the browser and inspect their state. - **[Playwright](https://playwright.dev/)** drives real browsers from tests, which suits checking menus and page flows around a game. - **[Vitest](https://vitest.dev/)** runs fast TypeScript tests in Node. Net Forge's own template tests run `engine.test.run` inside Vitest. Pick `npm test` when you want a bot playtest of the actual game rules, with no browser, in seconds. ## FAQ ### How do I test multiplayer on one computer? Open the game in two or more tabs or windows. `npm run play -- --players 2` opens them for you, and each one is a separate player in the same room. ### Do the bots need a browser or a GPU? No. `npm test` runs the simulation headless in Node with no renderer, so it runs in CI and in a coding agent's terminal. ### Is any real money involved in local testing? No. Coins are play money, there is no wallet and no chain, and the local host makes no calls to Net Forge servers. ### Why did my test fail with a softlock? Nothing changed for 30 seconds of game time: no kills, no damage and no movement. It usually means enemies cannot reach players, a wave never ends, or a rule stopped the run. The report's numbers show where it stalled. ### Can I make the test reproducible? Yes. Runs are deterministic for a seed, so `npm test -- --seed 2` gives the same result every time. Change the seed to explore other runs. ### Where does progress go in local play? Into memory. Achievements and looks bought with play money last until `kit-play` stops. --- # How to let an AI agent build your game (Claude Code, Cursor, MCP) > Scaffold a Net Forge game with npm create netforge, open it in Claude Code, Cursor or Copilot, and ask for changes in plain words. The agent edits one typed file, src/game.ts, and checks every edit with npm run check, a headless bot playtest that prints JSON. 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/build-a-game-with-an-ai-agent Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Create the game with `npm create netforge@latest`, open the folder in your coding agent and ask for changes in plain words. The project already tells the agent what to do: `AGENTS.md` explains that the whole game is `src/game.ts`, and `npm run check` lets the agent verify each edit with a headless bot playtest before you look at it. ```sh npm create netforge@latest my-game -- --template survivor --yes cd my-game claude ``` Swap `claude` for Cursor, VS Code with Copilot, Codex or Gemini CLI; the project carries instructions and MCP configs for each. ## Why this works with an agent | What the agent needs | What Net Forge gives it | |---|---| | A small surface to change | One declarative file, `src/game.ts`, that may import only `@netforge/engine`. The renderer, netcode and physics are out of reach, so an edit cannot break multiplayer. | | Instructions in the repo | `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/netforge.mdc` and `.github/copilot-instructions.md`, written for this template. Each template also ships an `llms.txt` with its common changes. | | Types and clear errors | Every declaration is typed, and a mistake fails with one line naming the field and the fix. | | A way to check its own work | `npm run check` plays the game headless with bots and prints JSON: kills, waves, levels, softlocks, earn rules fired and ms per tick. | | Tools, if it speaks MCP | `.mcp.json` connects the `@netforge/mcp` server: templates, docs search, API lookup, validation, playtests and local play. | ## 1. Create the project ```sh npm create netforge@latest my-game -- --template arena --yes ``` With no terminal attached (as when an agent runs it), the command never asks a question. `--agent claude`, `--agent cursor` or `--agent copilot` limits the agent files it writes; by default it writes all of them. ## 2. Connect the MCP server (optional) Claude Code reads `.mcp.json` in the project. To add the server for every project instead: ```sh claude mcp add netforge -- npx -y @netforge/mcp ``` For Cursor, VS Code, Codex and Gemini CLI, see [Set up your coding agent](https://forge.netnet.capital/docs/agents.md). ## 3. Ask for a change in plain words Good first requests are about the game, not the code: - "Add a fast enemy that appears after two minutes." - "Make the first minute easier, then harder after five minutes." - "Add a boss at ten minutes with a telegraphed slam." - "Sell a cosmetic skin for 300 coins." The template's `llms.txt` maps requests like these to declarations, and the engine's design rules (quiet opening, off-screen spawns, telegraphed hits) are part of what the agent reads. ## 4. Have the agent check every edit ```sh npm run check npm test ``` Ask the agent to run `npm run check` after each change and fix what it reports before you play. A good instruction is: "After every change to src/game.ts, run npm run check and keep going until it passes." ## 5. Play the result ```sh npm run play ``` Open the link in a second tab to play together. Everything runs on your computer with play money. ## A typical session ```text you: Add a ranged enemy that keeps its distance, from minute three. agent: (reads AGENTS.md and llms.txt, edits src/game.ts) prefab("spitter", { model: "style.lowpoly.frog", health: 20, speed: 2.8, ai: "ranged", attack: { damage: 8, range: 10, speed: 7 }, drops: ["ember_small"] }); director pool: { prefab: "spitter", from: 180, weight: 3 } agent: (runs npm run check) pass: 1,350 kills, level 9, no softlock you: npm run play ``` ## Alternatives - **[Phaser](https://phaser.io/)** publishes an official Game Agent MCP and a large examples index for agents, and is a strong choice for 2D games. See [Net Forge and Phaser](https://forge.netnet.capital/docs/compare/phaser.md). - **[Godot](https://godotengine.org/)** is a full desktop editor and engine with web export, a good fit when you want an editor and native builds as well as the web. See [Net Forge and Godot web export](https://forge.netnet.capital/docs/compare/godot-web-export.md). - **Plain three.js** gives your agent the most freedom and the most to build: networking, physics and game rules are yours to write. See [Net Forge and three.js](https://forge.netnet.capital/docs/compare/threejs.md). Pick Net Forge when you want the agent to change a finished multiplayer game rather than write the engine around it. ## FAQ ### Can Claude Code build a multiplayer game? Yes. Start from a Net Forge template with `npm create netforge@latest`, open the folder in Claude Code and ask for changes. The multiplayer netcode is already in the template, so the agent works on the rules in `src/game.ts` and verifies them with `npm run check`. ### What is the best game engine for AI coding agents? It depends on the game. For multiplayer browser games, an engine where the game is a small typed file with validated declarations and a headless test the agent can run gives the agent the shortest loop from request to verified change. That is the shape Net Forge is built around. ### Does the agent need an API key or account for Net Forge? No. The packages, the MCP server and local play need no key, account or wallet. ### Can the agent break the netcode or rendering? Not from `src/game.ts`. The play build rejects any import other than `@netforge/engine`, and the renderer, netcode and physics live in the template packages. ### How does the agent know the change is fun and not just valid? `npm run check` includes design metrics measured by the playtest bots: time to the first threat and the first reward, deaths in the first minute, spawns on screen and the share of telegraphed deaths. See [Testing with playtest bots](https://forge.netnet.capital/docs/concepts/playtest-bots.md). --- # How to deploy and host your browser game > Two honest options: play and playtest on your own computer and network with the kit-play local host, or publish through the hosted Net Forge studio, which runs the production room servers, lag compensation and run verification. 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/deploy-and-host Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. A Net Forge game runs in two places: on your own computer and network through the local room host in `@netforge/kit-play`, and in production on the hosted Net Forge service at [forge.netnet.capital](https://forge.netnet.capital), which runs the room servers for published games. The public repository does not include a production room server, so for a game anyone on the internet can join, publish it through the hosted studio. ```sh npm run play -- --host 0.0.0.0 --no-open ``` That line serves your game to every device on your local network from your computer, which is the right setup for playtests in one place. ## The options | Option | Who can play | What runs it | Good for | |---|---|---|---| | `npm run play` | You, in as many tabs as you like | `@netforge/kit-play` on `127.0.0.1:4663` | Building and testing | | `npm run play -- --host 0.0.0.0` | Anyone on your network (phones, laptops) | The same local host, listening on every interface | Playtests in a room, a classroom or an office | | Hosted Net Forge studio | Anyone with the link | Net Forge's room servers | Publishing a game for the public | ## 1. Host a playtest on your network ```sh npm run play -- --host 0.0.0.0 --port 4663 --no-open ``` `kit-play` prints a link such as `http://localhost:4663/?server=http%3A%2F%2Flocalhost%3A4663`. On the other devices, open it with `localhost` replaced by your computer's address on the network, in both places (the page and the `server` parameter). Every device joins the same room until it is full, then a new room opens. Stop it with Ctrl+C. What the local host is, exactly: - One Node process serves the built page, the asset library and the room (WebSockets through a Colyseus room) on one port. - It runs the same simulation as the hosted server at the game's fixed tick and sends each player delta snapshots at 20 Hz. - It holds at most 8 rooms. Progress (achievements, looks bought with play money) lives in memory and is gone when it stops. - It has no lag compensation history (hitscans resolve against current hitboxes, which is exact at local latency), no run log, no replay and no sign-in. Every money route answers 403. Coins are play money. - It is not a sandbox: it runs the game's code with your permissions, so host only games whose code you trust. ## 2. Publish through the hosted studio The hosted studio at [forge.netnet.capital](https://forge.netnet.capital) builds, plays and publishes games on Net Forge's servers. A studio draft is the same kind of game folder: `src/game.ts` and `src/names.json` on one of the ten templates. To bring rules you wrote locally into a studio draft on the same template: 1. In the studio, type `/agent`. You get a one-time key and the draft's git address. 2. Clone the draft with the key as the password, copy in your `src/game.ts` (and `src/names.json` if you changed labels), and push to the `agent` branch. ```sh git push origin HEAD:agent ``` Only `src/game.ts` and `src/names.json` are taken. Playtest bots play every change before it becomes a new version, so a change that softlocks or breaks a rule is refused with the reason. Or connect Claude Code with the line the studio shows, and let it push for you. What the hosted service adds over the local host: rooms for the public, lag compensation for hitscan weapons (rewound to what the shooter saw, capped at 200 ms), a run log and replay for every run, saved progress per player, and the optional on-chain economy (a game token and rewards), which runs only there and is decided by its server. ## 3. Keep the local copy in sync A studio draft can be downloaded as source at any version: `src/game.ts`, `src/names.json`, `netnet.json` and an `llms.txt` for agents. Inside that folder, `npx @netforge/kit-play` plays it locally with play money, the same way as a game made with `npm create netforge`. ## How it works A game module (`src/game.ts`) may import only `@netforge/engine`, and the template supplies everything else. That is what lets the same file run under `kit-play` on your laptop and on the hosted room servers without changes: both load the template's game module, step the same `@netforge/engine-sim` world, and use the same client build. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md). ## Alternatives If you want to run and scale your own game servers instead, these are the established choices, each with its own server model: - **[Colyseus](https://docs.colyseus.io/deployment)** documents self-hosting with PM2, Nginx, Apache and Docker, and offers a managed option in [Colyseus Cloud](https://docs.colyseus.io/cloud). - **[Nakama](https://heroiclabs.com/docs/nakama/getting-started/)** is an open-source game backend you can run yourself, with authoritative multiplayer, accounts and storage. - **[PartyKit](https://docs.partykit.io/)** runs real-time rooms on a hosted platform that handles the infrastructure and scaling. Pick the hosted Net Forge studio when your game is built on a Net Forge template and you want it published without operating servers. ## FAQ ### Can I deploy a Net Forge game to Vercel, Netlify or GitHub Pages? Not as a complete multiplayer game. Those hosts serve static files, and a Net Forge game also needs its room server running the simulation. The client is static, but it needs a room to join. ### Can I run kit-play on a public server? It is built and supported for your own computer and network. It has no persistence, a cap of 8 rooms, no lag compensation history and no sandbox for the game's code. For a public game, publish through the hosted studio. ### Does publishing cost money or require a token? A token is not required. The on-chain economy is an optional upgrade on the hosted service. Check the studio for its current terms. ### Will my game play the same locally and in production? Yes for the rules: both run the same game module and the same simulation. Hitscan feel can differ slightly over the internet, because production adds lag compensation that local play does not need. ### How do friends on my Wi-Fi join? Run `npm run play -- --host 0.0.0.0`, then open the printed link on their devices with `127.0.0.1` replaced by your computer's address on the network. ### Where do I see the room server's code? The local host's authoritative loop is in `packages/kit-play/src/host` in the [public repository](https://github.com/netnetdev/net-forge). The hosted room server is not open source. --- # How to make a survivor-like with thousands of enemies in the browser > Start from the EMBERWATCH template with one command: a four-player co-op survivor-like that holds 2,000 enemies on an authoritative server at 30 Hz, drawn with instanced three.js crowds. Then tune the swarm, the weapons and the waves in src/game.ts. 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/horde-survivor-browser-game Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. The EMBERWATCH template holds 2,000 enemies with four players at 1.3 to 1.5 ms of server time per tick on average (p99 under 4.5 ms), against a 33 ms budget at 30 Hz, measured on an Apple M2 Pro. Run `npm create netforge@latest my-horde -- --template survivor --yes` and you have that working survivor-like: auto-firing weapons, XP gems, level-up cards, bosses and a swarm that grows to 2,000 enemies, with up to four players in one room. You change the game by editing the declarations in `src/game.ts`; the crowd rendering, the spawn director, the netcode and the bots are already done. ```sh npm create netforge@latest my-horde -- --template survivor --yes cd my-horde npm run play ``` The hard part of this genre in a browser is not the rules. It is moving and drawing thousands of enemies every frame while a server stays in charge of every hit. The template solves both, and the playtest bots measure it. ## What you get | Part | Where | Notes | |---|---|---| | Spawn director | `director({...})` in `src/game.ts` | Spawns just off screen on the run's clock, with curves for living count, rate, health and damage. | | Enemies and roles | `prefab({...})` | `chase`, `runner`, `charger`, `ranged`, `bomber`, `shielder`, `healer`, `summoner`, splitters and elites. Big hits are telegraphed on the ground. | | Weapons | `weapon({...})` | `nova`, `aura`, `bolt`, `orbit`, `cone`, `chain`, `meteor`, `boomerang`, 5 to 8 levels each, evolutions paired with passives. | | Crowd rendering | template client | Instanced three.js crowds with procedural vertex animation, GPU particles, one draw per crowd. | | Server cost | measured 2026-10-24 | 2,000 enemies with 4 bots: 1.32 to 1.49 ms per tick on average, p99 2.63 to 4.41 ms, over three 30-second runs; with every enemy role in the crowd, 1.70 to 2.14 ms, p99 up to 7.05 ms. The budget is 33 ms at 30 Hz (Apple M2 Pro, Node 22, `engine.test.run`). | | Players | `players.max` | 4 per room, co-op revive, shared XP for teammates nearby. | ## 1. Start the template ```sh npm create netforge@latest my-horde -- --template survivor --yes cd my-horde npm run play -- --players 2 ``` Two tabs open on `http://127.0.0.1:4663`, both in the same room. WASD or the arrows move, Space dashes, 1 2 3 pick a level-up card. The second tab is a second player against the same server. ## 2. Make the swarm bigger, later The director decides how many enemies are alive at each minute. Curves are a number or `[seconds, value]` keyframes. Keep the opening quiet and push the late game: ```ts title=src/game.ts import { director } from "@netforge/engine"; director({ start: 8, alive: [[0, 12], [60, 45], [180, 160], [300, 400], [540, 2000]], rate: [[0, 0.8], [60, 2], [180, 6], [300, 14], [540, 70]], health: [[0, 1], [60, 1.4], [180, 2.4], [540, 5.7]], damage: [[0, 1], [180, 1.45], [540, 2.3]], pool: [ { prefab: "shade", weight: 10 }, { prefab: "gnat", from: 45, weight: 2, group: [8, 12] }, ], }); ``` More players raise `alive` and `rate` by 0.6 each. At the cap, new spawns get tougher instead of adding bodies, and enemies left far behind are moved back ahead of the players. ## 3. Add an enemy with a role ```ts title=src/game.ts import { prefab } from "@netforge/engine"; prefab("wisp", { model: "style.lowpoly.bat", health: 8, speed: 5, radius: 0.25, damage: 4, ai: "runner", drops: ["ember_small"] }); prefab("brood", { model: "style.lowpoly.slime_green", health: 40, speed: 3, split: { into: "shade", count: 3 }, drops: ["ember_small"] }); ``` Then add `{ prefab: "wisp", from: 150, weight: 3 }` to the director's `pool`. A runner aims at where you will be; a splitter bursts into three smaller enemies. Keep chasers at 3 to 4 m/s: players run at 7. ## 4. Add a weapon that clears crowds ```ts title=src/game.ts import { weapon } from "@netforge/engine"; weapon("frost_ring", { kind: "nova", damage: 6, cooldown: 0.8, range: 5, knockback: 8, levels: [{ damage: "+30%" }, { range: "+15%" }, { cooldown: "-15%" }, { damage: "+30%" }, { knockback: 3 }], }); ``` The level-up cards offer it automatically and show what each level changes. Knockback is physics: it is divided by the enemy's mass, so a brute (mass 6) barely moves while fodder slides into the crowd behind it. ## 5. Measure it with the bots ```sh npm test npm run check ``` The playtest plays the game headless with bots and reports kills, waves, levels, earn rules fired, softlocks and the server cost per tick. It also scores design rules: a quiet first minute, no spawns on screen and no death before the first minute. On the stock template, four bots playing three minutes clear 1,350 enemies, reach level 9 and nobody goes down in the first minute. ## How it works The room server is authoritative. It steps one shared simulation (`@netforge/engine-sim`, an ECS with Rapier physics) at a fixed 30 Hz and sends each player a delta snapshot of what is near them 20 times a second (the local host caps a survivor snapshot at 250 enemies within 40 m). Your own keeper is predicted in the browser from the same simulation code, so movement never waits for the network; enemies are interpolated. The client draws the crowd with instanced meshes, so 2,000 enemies are a handful of draw calls rather than 2,000. On slow devices the render stack drops shadows, post effects and resolution by itself to hold the frame rate. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md) and [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md) for the details, and the [survivor-like template page](https://forge.netnet.capital/docs/templates/survivor-like.md) for every file. ## Alternatives - **[Phaser](https://docs.phaser.io/)** is a free, open-source 2D framework with WebGL and Canvas rendering and a very large example library. It is a strong choice for a single-player 2D survivor-like with sprite art, where you write the game loop yourself. See [Net Forge and Phaser](https://forge.netnet.capital/docs/compare/phaser.md). - **[Babylon.js](https://doc.babylonjs.com/)** is a full 3D engine with thin instances for large crowds and Havok physics. Pick it when you want an editor-style 3D toolset and plan to write your own networking. - **[Colyseus](https://docs.colyseus.io/)** is an authoritative Node.js game server with rooms, matchmaking and binary delta state sync, and client SDKs for many engines. Pair it with any renderer when you want to own the server framework. For Phaser and Colyseus used together, see [Net Forge vs Phaser + Colyseus](https://forge.netnet.capital/docs/compare/phaser-colyseus.md). Pick Net Forge when you want the multiplayer survivor-like already finished, with one simulation on both sides and a bot playtest your agent can run. ## FAQ ### How many enemies can a browser survivor-like handle? The EMBERWATCH template holds 2,000 enemies with four players. On the server side, the simulation measured 1.32 to 1.49 ms per tick on average with 2,000 enemies and four bots on an Apple M2 Pro (three 30-second runs, 2026-10-24); the browser draws them as instanced crowds. The template caps the living crowd at 2,000, so that is the most it holds. To repeat the measurement from a clone of the public repository, run `pnpm --filter @netforge/template-survivor bench -- --repeats 3 --seconds 30`. ### Is the game multiplayer out of the box? Yes. Up to four players share one room on an authoritative server. Open the link in another tab, or run `npm run play -- --host 0.0.0.0` so devices on your network can join. ### Do I need to write a game loop or a renderer? No. `src/game.ts` declares enemies, weapons, upgrades, the director and earn rules. The template's client and the engine do the loop, rendering, input and netcode. ### Can I change the art style? Yes. `game({ style })` takes `lowpoly`, `toon`, `ps1`, `pixel`, `voxel` or `handdrawn`, and models are referenced by library id such as `style.lowpoly.ghost`. ### Why do enemies never spawn on screen? The director spawns every enemy just off screen by design, and the playtest fails a game where more than 2% of spawns land on screen. It is one of the design rules the bots score. ### Does it pay real money? No. Locally everything is play money: earn rules fire so you can see them and pay nothing. A token economy is an optional upgrade that runs only on the hosted Net Forge service. --- # How to make a co-op browser game players join with a link > Start a four-player co-op game with one command, share a room link so friends drop into the same run, and tune revives, shared XP and boss scaling in src/game.ts. Rooms, the authoritative server and prediction come with the template. 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/co-op-browser-game Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. Run `npm create netforge@latest my-coop -- --template survivor --yes`, then `npm run play`, and open the same link in another tab (or on a phone on your network, after starting with `--host 0.0.0.0`): everyone who opens it joins your run, up to four players. On the hosted service the Invite link carries the room id (`?room=`) so friends join that exact room. The co-op rules (revives, shared XP, bosses that scale with the party) are declarations in `src/game.ts` that you can change. ```sh npm create netforge@latest my-coop -- --template survivor --yes cd my-coop npm run play -- --host 0.0.0.0 ``` With `--host 0.0.0.0`, phones and laptops on the same network can open the link through your computer's address. Every template is a co-op or versus game in rooms; the survivor-like is the one built around helping each other. ## What you get | Part | Where | Notes | |---|---|---| | Rooms and invite links | template client and room host | Locally every tab joins the open room. On the hosted service a link with `?room=` joins that room. One room holds 4 players. | | Co-op revive | `players.downed` | Downed for a few seconds, crawl to a teammate, who revives you by standing close. | | Shared progress | `players.share` | Half of every XP gem goes to teammates within 20 m, so the party levels together. | | Boss scaling | `players.bossHealthPerPlayer` | Bosses grow per extra player (40% in the template). | | Late joiners | engine rule | A player who joins mid-run is never paid for progress made before their run started. | | Touch controls | template client | Drag on the left half to move, a DASH button, tap a card. | ## 1. Start it and invite someone ```sh npm create netforge@latest my-coop -- --template survivor --yes cd my-coop npm run play -- --players 2 ``` `--players 2` opens two tabs in the same room so you can try co-op alone. For a friend on your network, run with `--host 0.0.0.0` and send the Invite link with your computer's address in place of `127.0.0.1`. ## 2. Tune how the party plays together The co-op rules live in the `players` block of `game({...})`: ```ts title=src/game.ts import { game } from "@netforge/engine"; export default game({ template: "survivor", style: "lowpoly", places: { field: { chunks: "places/field", spawn: [0, 1, 0] } }, field: { halfSize: 75, pillars: [[14, 14, 2], [-14, 14, 2], [14, -14, 2], [-14, -14, 2]] }, players: { prefab: "style.lowpoly.hooded_adventurer", max: 4, weapon: "ember_nova", xp: { base: 2, growth: 2, exponent: 2.6 }, magnet: 4, hitIframes: 0.7, share: "nearby", reviveBuff: { seconds: 5, damage: 1.3, speed: 1.15 }, bossHealthPerPlayer: 0.4, downed: { seconds: 10, crawl: 0.35, selfRevives: 1, respawn: 5 }, }, }); ``` - `downed` sets how long a fallen player bleeds, how fast they crawl, how many free get-ups they have and how soon they respawn beside a teammate. `selfRevives: 0, respawn: 0` makes it hardcore. - `reviveBuff` rewards the player who helps: more damage and speed for a few seconds. - `share: "nearby"` keeps the party together; a player who wanders off levels alone. ## 3. Give the party a shared goal Earn rules and systems can count what the whole party does. This rule fires once per run for every player when the party clears seven waves: ```ts title=src/game.ts import { earn } from "@netforge/engine"; earn.rule("night_watch", { when: "wave.cleared", n: 7, pay: 20, oncePer: "run" }); ``` Locally the payout is play money; the rule fires so you can see it in the HUD and in `npm test`. ## 4. Check co-op with the bots ```sh npm test ``` The playtest runs four bots together and checks that waves clear, players level and nobody is down in the first minute. Four bots playing three minutes of the stock template level to 9 and clear 1,350 enemies, at 0.27 ms of server time per tick (Apple M2 Pro, Node 22). ## How it works Each room is one authoritative world. The host steps the simulation at 30 Hz, takes each player's inputs from a queue and sends every player a delta snapshot of what is near them 20 times a second. Your own character is predicted in the browser with the same simulation code and corrected by the next snapshot when they disagree; teammates and enemies are interpolated. Because the server owns every hit and every revive, nobody's tab can decide the outcome for the room. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). To put the room on the internet rather than your network, see [Deploy and host your game](https://forge.netnet.capital/docs/guides/deploy-and-host.md). The full file list is on the [survivor-like template page](https://forge.netnet.capital/docs/templates/survivor-like.md). ## Alternatives - **[Colyseus](https://docs.colyseus.io/)** is an open-source authoritative game server for Node.js, built around rooms with matchmaking, lobbies and binary delta state sync, with client SDKs for the web, Unity, Godot, Defold and more. Pick it when you have a client already and want a mature room server. See [Net Forge and Colyseus](https://forge.netnet.capital/docs/compare/colyseus.md), and for a 2D co-op game on Phaser with a Colyseus server, [Net Forge vs Phaser + Colyseus](https://forge.netnet.capital/docs/compare/phaser-colyseus.md). - **[Godot](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)** exports to the web as WebAssembly with WebGL 2, and has its own high-level multiplayer API. Pick it when you want an editor-first workflow and a native build too. Pick Net Forge when you want the whole co-op game, rooms included, from one command. ## FAQ ### How do players join the same game? They open the same link. In local play the host puts every player in the open room until it is full (4 for the survivor-like). On the hosted service the Invite link carries the room id (`?room=`) and puts friends in that exact room. ### Can people on phones join? Yes. The templates have touch controls, and `npm run play -- --host 0.0.0.0` lets devices on your network reach your computer. ### What happens if someone joins halfway through a run? They drop into the current run and play at once. The engine never pays a late joiner for progress made before their run started, so joining late cannot be used to collect rewards. ### Do I need a server? For local play, no: `npm run play` runs the room host on your computer. To host a game for people elsewhere, see [Deploy and host your game](https://forge.netnet.capital/docs/guides/deploy-and-host.md). ### What if only one person is playing? The game plays solo; the director scales the swarm by the number of players. The arena shooter, card battler, platformer and kart racer also add seat bots that fill empty places and never earn. --- # How to host a game server for browser games > What a browser game server needs (WebSockets, a fixed-tick authoritative loop, TLS, rooms that stay on one process), how Net Forge's local room host does it on one port, and which hosted and self-hosted options fit a public game. 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/host-browser-game-server Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. A browser game server is a long-running process that accepts WebSocket connections, keeps each room's world in memory, steps it at a fixed tick and sends snapshots back. Browsers cannot open raw sockets, so the server must speak WebSockets (or WebTransport) over TLS when the page is served over HTTPS, and every player in a room must reach the same process. Net Forge's local host does all of this on one port for your own computer and network: ```sh npm create netforge@latest my-game -- --template arena --yes cd my-game npm run play -- --host 0.0.0.0 --no-open ``` ## What a game server needs | Need | Why | How the Net Forge local host does it | |---|---|---| | WebSockets | Browsers can only open WebSocket, WebRTC or WebTransport connections | A Colyseus room over its WebSocket transport, on the same port as the page | | An authoritative loop | The server decides outcomes, clients send inputs | Fixed tick (30 or 60 Hz), one input queue per player | | Snapshots per player | Each player needs what is near them, as cheaply as possible | Area of interest per template, delta snapshots at 20 Hz | | Room placement | Everyone in a room must reach the same process | One process holds every room (at most 8) | | TLS | An HTTPS page cannot open an unencrypted WebSocket | Not included: the local host serves plain HTTP on your network | | Persistence | Saved progress survives a restart | Not included: progress lives in memory | | Lag compensation and replay | Fair hitscans over the internet, auditable runs | Not included locally; the hosted Net Forge server has both | ## 1. Run the local host `npm run play` builds the client and starts the host. The flags that matter for hosting: ```sh npm run play -- --port 8080 --host 0.0.0.0 --no-open ``` `--port` sets the one port for the page and the room, `--host 0.0.0.0` listens on every interface instead of only `127.0.0.1`, and `--no-open` prints the link without opening a browser. The client finds its room from the `server` parameter in the printed link. ## 2. Decide where production rooms run For a game anyone on the internet can join, you need TLS, rooms placed close to players, restarts that do not drop everyone and saved progress. For Net Forge games, the hosted studio at [forge.netnet.capital](https://forge.netnet.capital) runs those room servers; see [Deploy and host your game](https://forge.netnet.capital/docs/guides/deploy-and-host.md) for how a local game reaches it. If you build your own server stack, the options below are the established ones. ## 3. Load test before you ship Measure the server cost per tick before choosing machine sizes. `npm test` prints it for your game: ```sh npm test -- --bots 4 --minutes 5 ``` For scale, the templates' own playtests on an Apple M2 Pro with Node 22: the survivor template holds 2,000 enemies with 4 bots at 2.22 ms per tick on average (p99 5.21 ms), against a 33 ms budget at 30 Hz. The arena shooter costs 0.10 ms per tick at 60 Hz with 4 bots. One process can run many rooms of a cheap game, and few of an expensive one. ## How it works The simulation is a plain function of the world and that tick's inputs: `step(world, inputs)` never reads a clock. The host's loop calls it on a timer, decodes input packets into each player's queue, and encodes one snapshot per player against the tick that player last acknowledged. Because the same module runs in the browser for prediction, the server and the client never disagree about the rules. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md) and [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). ## Alternatives - **[Colyseus](https://docs.colyseus.io/deployment)** is an MIT-licensed Node framework for room-based game servers. Its deployment docs cover PM2, Nginx, Apache and Docker, and [Colyseus Cloud](https://docs.colyseus.io/cloud) is its managed hosting. Net Forge's local host is itself built on a Colyseus room. - **[Nakama](https://heroiclabs.com/docs/nakama/getting-started/)** is an open-source game server with accounts, matchmaking, storage and [authoritative multiplayer](https://heroiclabs.com/docs/nakama/concepts/multiplayer/authoritative/), which you can run yourself. - **[PartyKit](https://docs.partykit.io/)** runs real-time rooms on a hosted platform and handles the infrastructure and scaling for you. - **[Socket.IO](https://socket.io/docs/v4/)** gives you a WebSocket server with rooms and fallbacks, on which you write the game loop yourself. ## FAQ ### Do browser games need a dedicated server? Real-time multiplayer games with fair results do: a server that runs the game and decides outcomes. Turn-based games can use a lighter server that validates moves. Peer-to-peer over WebRTC is possible but leaves one player's browser in charge. ### Can I host a WebSocket game server on serverless functions? Not the classic kind, because a room's world must stay in memory in one process for the whole match. Use long-running processes, containers, or a platform built for stateful rooms. ### What tick rate should a browser game server use? 30 Hz suits most action games and is what most Net Forge templates use. Fast shooters use 60 Hz, as the arena template does. Snapshots can go out less often than the tick (the local host sends 20 a second) because clients interpolate. ### Why does my HTTPS page fail to connect to my game server? Browsers block an unencrypted `ws://` connection from an `https://` page. The server needs TLS (`wss://`), usually from a reverse proxy in front of it. ### How many players can one server handle? It depends on the game's cost per tick, which `npm test` reports. A room of the arena template costs about 0.1 ms per tick on a laptop core, so CPU is rarely the first limit for small rooms; bandwidth and memory per room usually come first. --- # How to vibe-code a multiplayer game you can actually ship > Start from a finished multiplayer template instead of a blank page, let your agent change only the declarative game file, and gate every change on a headless bot playtest. The netcode, rendering and server authority are already done, so prompts change the game, not the plumbing. 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/vibe-code-multiplayer-game Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Start from a finished multiplayer game instead of a blank page, let your agent change only the rules file, and gate every change on a bot playtest. With Net Forge that is three commands, and the parts that usually sink a prompted multiplayer game (netcode, server authority, rendering, bots) are already done before your first prompt. ```sh npm create netforge@latest my-game -- --template party --yes cd my-game npm run play -- --players 2 ``` ## Where prompted multiplayer games usually break | Problem | What Net Forge does instead | |---|---| | The agent writes networking from scratch and it desyncs. | The template's netcode runs the same simulation on the server and the client. The agent never writes it. | | Clients decide outcomes, so anyone can cheat. | The server decides every hit, kill and pickup. Game code is declarations the server runs. | | Each prompt touches many files and something unrelated breaks. | The game is one file, `src/game.ts`, and it may import only `@netforge/engine`. | | Nobody can tell if a change made the game worse. | `npm run check` plays the game with bots and reports kills, progress, softlocks and design metrics as JSON. | | It works on one machine and nowhere else. | The same game folder plays locally with `npm run play` and can be published through the hosted studio. | ## 1. Pick the closest template Choose the template nearest to the game you imagine and change it, rather than describing a new game from nothing. `npm create netforge@latest -- --list` prints all ten. See [Templates](https://forge.netnet.capital/docs/templates.md). ## 2. Give the agent one rule Put this in your first prompt, or rely on `AGENTS.md`, which already says it: "Only edit src/game.ts. After every change, run npm run check and fix what it reports before you stop." ## 3. Prompt in small, playable steps One idea per prompt keeps each change testable: a new enemy, then a boss, then a store item. Play between prompts with `npm run play`. ## 4. Ship it When it plays well locally, publish it through the hosted Net Forge studio, or keep it as an open-source project that anyone can run with `npm run play`. See [Deploy and host your game](https://forge.netnet.capital/docs/guides/deploy-and-host.md). ## Alternatives - **[Rosebud](https://rosebud.ai/)** is a hosted, browser-based builder for prompting games into existence, a good fit when you want everything in one web app. - **[Phaser](https://phaser.io/)** with its official agent tooling is a strong choice for 2D games where you want full control of the code. - **Your own engine** gives you every choice and every bug. Net Forge suits you when you want the multiplayer foundation finished and owned by you under MIT. ## FAQ ### Can you vibe-code a multiplayer game? Yes, if the multiplayer foundation exists before the first prompt. Starting from a Net Forge template, the agent changes the game rules in one file while the template supplies the server authority, netcode and rendering. ### Which template is best for a first vibe-coded game? The survivor-like (`survivor`) and the party game (`party`) respond most visibly to small prompts: enemies, weapons, rounds and pacing are all one-line declarations. ### How do I stop the agent from breaking things? Keep it in `src/game.ts` and make `npm run check` part of every change. The play build already rejects imports other than `@netforge/engine`. ### Does vibe-coding with Net Forge cost money? The engine, templates and tools are free and MIT licensed. Your coding agent's own pricing is separate. --- # How to add an in-game store and currency (optional token economy) > Declare store items, coin prices and earn rules in src/game.ts with store.sku and earn.rule. Locally they run with play money; on the hosted Net Forge service a game can optionally upgrade its coins to its own token. 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/in-game-store-and-currency Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. In Net Forge, a game's store and currency are declarations in `src/game.ts`: `earn.rule()` says what a run pays in coins, and `store.sku()` lists what coins buy (a revive, a cosmetic, an unlock). Every template already has both, and in local play they run with play money, so you can build and test the whole economy without a wallet or a server of ours. ```sh npm create netforge@latest my-game -- --template survivor --yes cd my-game npm run play ``` The on-chain part (coins that are a real game token, with rewards) is an optional upgrade that runs only on the hosted Net Forge service and is decided by its server. Nothing on this page needs it. ## What you get | Declaration | What it does | In local play | |---|---|---| | `earn.rule(id, { when, n, pay, oncePer })` | Pays `pay` coins when the event `when` reaches count `n`, once per run, day or lifetime | Fires and shows, pays play money | | `earn.stake({ min, max, multiple })` | The buy-in run every game declares | Play money only | | `store.sku(id, { coins })` | An in-run consumable, such as a revive | Buyable with play money | | `store.sku(id, { coins, grants: { cosmetic: n } })` | A look owned for good | Owned until `kit-play` stops | | `store.sku(id, { coins, grants: { keeper: id } })` | An unlock (a playable character) | Owned until `kit-play` stops | | `economy({ store: "cosmetics" })` | Restricts the store to cosmetics only | Same | Coins buy cosmetics, unlocks and in-run consumables, and never permanent stat power, unless a game opts in with `economy({ metaUpgrades: true })`, and then only under the engine's caps (+15% to one stat, +30% across all of them). ## 1. Declare what a run pays Earn rules hang off game events. Templates emit events such as `wave.cleared`, `boss.killed`, `enemy.killed` and `opponent.killed`, and a `system()` can emit your own. ```ts title=src/game.ts import { earn } from "@netforge/engine"; earn.rule("first_light", { when: "wave.cleared", n: 2, pay: 5, oncePer: "run" }); earn.rule("night_watch", { when: "wave.cleared", n: 7, pay: 20, oncePer: "run" }); earn.rule("bell_breaker", { when: "boss.killed", n: 1, pay: 50, oncePer: "run" }); earn.rule("daily_sweep", { when: "enemy.killed", n: 500, pay: 5, oncePer: "day" }); earn.stake({ min: 20, max: 200, multiple: 2 }); ``` Every game needs at least one `earn.rule` and one `earn.stake`, and the engine refuses a file without them with a line that says so. ## 2. Declare the store ```ts title=src/game.ts import { store } from "@netforge/engine"; store.sku("revive", { coins: 50 }); // in-run consumable store.sku("skin_aurora", { coins: 300, grants: { cosmetic: 1 } }); // a look, owned for good ``` To sell a playable character, declare it with `keeper("ranger", { ... })` and add `store.sku("unlock_ranger", { coins: 400, grants: { keeper: "ranger" } })`. The engine refuses a SKU that sells an undeclared keeper or the free default one. A coin-priced item id is 2 to 31 characters of `a-z`, `0-9` and `_`. Add a label for each id in `src/names.json` so players see a name, not the id. ## 3. Try it with play money ```sh npm run play npm test ``` In the browser, coins earned by your rules show in the HUD under the game's coin name, and the store spends them. `npm test` plays the game with bots and reports which earn rules fired and how often, so you can see how fast coins come in before anyone plays. In local play everything is play money: there is no wallet, no chain and no payout, and progress is gone when `kit-play` stops. ## 4. Optional: upgrade to a game token on the hosted service A game published through the hosted Net Forge studio can launch its own token. Then 1 coin is 1 game token, shown under the token's own name, symbol and logo, and the store can also price items in dollars. Rewards are decided by Net Forge's referee on its servers, which pays only for runs it can verify by replaying them on the Net Forge engine. A copy of your code cannot pay anyone, and nothing in `src/game.ts` can change that decision. The template clients call the store through the Net Forge Kit's API names (`kit.coins.balance()`, `kit.coins.spend()`, `kit.store.buy()`), which run on a mock backend in local play. The Kit as a separate package for games on other engines (`@netforge/kit`) is not published to npm yet. ## How it works The room server is the referee. Earn rules are counted per player per run by the engine (`EarnTracker`) from events the authoritative simulation emits, never from the browser. Bots never earn, and in the arena shooter killing a bot pays no rule. In the shared-world genres, world progress never pays a run that started after the world began. See [The typed game API](https://forge.netnet.capital/docs/concepts/game-api.md). ## Alternatives - **[PlayFab](https://learn.microsoft.com/en-us/gaming/playfab/)** from Microsoft provides virtual currencies, catalogs and player inventory as a hosted backend for games on any engine. - **[Nakama](https://heroiclabs.com/docs/nakama/getting-started/)** includes wallets, storage and server-side logic in an open-source game backend you can run yourself. - **[Colyseus](https://docs.colyseus.io/)** rooms can hold any game state, including a currency, with your own persistence behind them. Pick Net Forge when the store and the currency should be part of the game's own declarations, checked by the engine and paid out only from server-verified runs. ## FAQ ### Do I need crypto or a wallet to have a store in my game? No. Coins, earn rules and the store work with play money in local play, and a game in the hosted studio can stay that way. A game token is an optional upgrade. ### Can players buy power with coins? Not by default. Coins buy cosmetics, unlocks and in-run consumables. Permanent stat upgrades need `economy({ metaUpgrades: true })` and are capped by the engine at +15% per stat and +30% in total. ### Where do earn events come from? From the authoritative simulation: the template emits events like `wave.cleared` and `boss.killed`, and your own `system()` can emit more with `emit("streak.five", [playerId])`. ### Can I test the economy without publishing? Yes. `npm test` reports which earn rules fired and how often in a bot playtest, and `npm run play` lets you earn and spend play money in the browser. ### Can a modified client grant itself coins? No. Earn is counted on the server from the simulation's events. In local play nothing is real money anyway, and on the hosted service the referee replays runs before it pays. --- # How to make an online card battler with AI opponents > Start the INKWRIT card battler with one command: one-against-one duels with turns, a turn timer, hidden hands shuffled on the server, a draft between duels and AI opponents in four tiers. Change cards, the table and the AI in src/game.ts. 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/online-card-game Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Run `npm create netforge@latest my-cards -- --template cards --yes` and you have an online card battler: two players duel with a deck, a hand, mana that rises each turn, creatures and spells, and when you play alone an AI opponent takes the other seat. Decks are shuffled on the server and a hand is only ever sent to its owner, so hidden information is handled for you. ```sh npm create netforge@latest my-cards -- --template cards --yes cd my-cards npm run play ``` You change the game by declaring cards and the table in `src/game.ts`. The turn structure, the shuffle, targeting, guards and rush, the draft, the AI, the netcode and the 3D desk are the template's. ## What you get | Part | Where | Notes | |---|---|---| | Turns and timer | `cardTable({ turnSeconds })` | A coin decides who goes first; the second player draws one extra card; the timer ends a slow turn. | | Hidden information | room server | The deck order never leaves the server. A hand and a draft offer go only to their owner. | | Cards | `card({...})` | 4 to 40 units and spells. Effects: `damage`, `heal`, `draw`, `buff`, with targets such as `enemy`, `all_enemies`, `own_unit`. | | AI opponents | `cardTable({ ai })` | 1 to 12 tiers of `{ health, smart }`; duel n against the AI uses tier n. | | Seat filling | room | A person alone for 3 seconds gets an AI opponent; it leaves when a second person joins. | | Draft | `cardTable({ duels })` | After each duel but the last, pick one of three cards into your deck. | | Playtest | `npm test` | Four playtests that together simulate 17 minutes of duels finish in under half a second (Apple M2 Pro, Node 22). | ## 1. Start the template ```sh npm create netforge@latest my-cards -- --template cards --yes cd my-cards npm run play -- --players 2 ``` With two tabs you duel yourself; with one, an AI scribe sits down after three seconds. ## 2. Add a card ```ts title=src/game.ts import { card } from "@netforge/engine"; card("mirror_moth", { model: "style.lowpoly.jelly_flyer", kind: "unit", cost: 3, attack: 2, health: 2, effect: { kind: "damage", amount: 1, target: "all_enemies" }, }); card("ink_storm", { model: "style.lowpoly.meteor", kind: "spell", cost: 4, effect: { kind: "damage", amount: 2, target: "all_enemies" } }); ``` Add the card's name to `src/names.json` under `weapons` (`{ "name": "Mirror Moth", "line": "..." }`). The draft offers every declared card unless the table's `pool` lists some. ## 3. Shape the table and the AI ```ts title=src/game.ts import { cardTable } from "@netforge/engine"; cardTable({ health: 20, startHand: 3, handMax: 7, boardMax: 5, manaMax: 10, turnSeconds: 30, draftSeconds: 15, duels: 4, starter: ["blot_imp", "blot_imp", "quill_sprite", "quill_sprite", "ink_frog", "ink_frog", "margin_bat", "ledger_crab", "serif_fox", "splash", "splash", "mirror_moth"], ai: [ { health: 14, smart: 0.3 }, { health: 16, smart: 0.55 }, { health: 20, smart: 0.75 }, { health: 24, smart: 0.9 }, ], }); ``` Lower `health` makes faster duels and more `duels` a longer run. Raise each tier's `health` and `smart` for a harder gauntlet. The starter deck must hold 8 to 30 declared cards, at least one unit and some cards costing 2 or less; the build tells you which rule a deck breaks. ## 4. Reward the right things ```ts title=src/game.ts import { earn } from "@netforge/engine"; earn.rule("ink_master", { when: "ai.beaten", n: 3, pay: 15, oncePer: "run" }); ``` Events are `duel.won` (against a person, never a forfeit), `ai.beaten` and `run.won`. The AI never earns. Locally every payout is play money. ## 5. Test it ```sh npm test ``` The playtest plays duels between two players and one person against the AI, and checks that duels are decided, a run ends and starts over, and no softlock happens. ## How it works The card battler is a rules genre: a deterministic module with integer state and a seeded random number generator, run by the room at 30 Hz. A play, an attack, an end of turn and a draft pick are actions the server validates (whose turn it is, the card in that slot, the mana, guards). Your cursor is predicted so pointing never waits; everything else comes from the server. The room sends each player their own private state (their hand, their draft offer) separately from the public board, so another player's client never receives your cards. Because every shuffle comes from the room's seeded generator, a whole run replays exactly from its inputs. See [Make a turn-based multiplayer game](https://forge.netnet.capital/docs/guides/turn-based-browser-game.md) for the rules-genre model, and the [card battler template page](https://forge.netnet.capital/docs/templates/card-battler.md) for every file. ## Alternatives - **[boardgame.io](https://github.com/boardgameio/boardgame.io)** is an MIT-licensed engine for turn-based games in JavaScript. It keeps game state in sync across clients, server and storage, supports game phases with different rules and turn orders per phase, generates bots that can play your game, and includes a lobby for matchmaking. It is view-layer agnostic, so you bring your own rendering. Its latest npm release is 0.50.2, published on 2022-11-10 ([npm registry](https://registry.npmjs.org/boardgame.io)). Pick it when you want a small, proven state model for a 2D board or card game and are happy to write the UI. - **[Colyseus](https://docs.colyseus.io/)** is an authoritative Node.js room server with matchmaking and binary delta state sync. Pick it when you want to write the card rules as server code yourself and use any client. Pick Net Forge when you want a finished, rendered card battler with AI opponents and server-side hidden information, and a declarative file your agent can extend. ## FAQ ### How do I keep players from seeing each other's hands? The template does it on the server: the deck is shuffled by the room's seeded generator, the order never leaves the server, and each hand is sent only to its owner. Nothing in `src/game.ts` can change that. ### Can players play against the computer? Yes. A person alone gets an AI opponent after three seconds. Its strength per duel comes from `cardTable({ ai })`, from 1 to 12 tiers of `{ health, smart }`. ### Is there a turn timer? Yes, `turnSeconds` (30 in the template). When it runs out, the turn ends. ### Can I make it two players only, with no AI? The table seats two players. With a second person in the room the AI leaves; a duel against a person is the only one that counts as `duel.won`. ### Can I make a deckbuilder roguelike instead? The template already drafts one of three cards after each duel of a run. Raise `duels` and tune the AI tiers for a longer climb. ### Does the AI cheat by seeing my hand? No. The AI picks its moves from its own hand and the cards on the board; its code does not read the other player's hand or deck. It plays through the same rules module and never earns. --- # How to make a turn-based multiplayer game > Start from a turn-based template (the card battler or the monster collector) with one command. Turns, validated actions, hidden information and replays come from Net Forge rules genres: deterministic modules the room server runs at 30 Hz behind the same netcode as the action games. 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/turn-based-browser-game Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Start with `npm create netforge@latest my-turns -- --template cards --yes` for a one-against-one turn-based duel, or `--template collector` for turn-based battles in a shared world of up to eight players. Both are rules genres: the turn order, the action checks and each player's private view run on the server, and you change the content in `src/game.ts`. ```sh npm create netforge@latest my-turns -- --template cards --yes cd my-turns npm run play -- --players 2 ``` Turn-based games still need a server that decides whose turn it is, rejects moves out of turn and keeps secrets secret. Net Forge runs them on the same room server, netcode and playtest bots as its real-time games, so you do not trade one stack for another. ## What you get | Template | Turn structure | Players | Hidden information | |---|---|---|---| | `cards` (card battler) | Alternating turns with a timer, mana per turn, a draft between duels | 2 (or 1 against the AI) | Hands, deck order and draft offers | | `collector` (monster collector) | Turn-by-turn battles: a move, a swap, a snare or flee; the faster creature acts first | 8 in one marsh | Each player's battle, team and log | | `tower-defense` | Real-time waves with a shared build phase and a ready vote | 4 | None; one shared board | All three run as rules modules under `@netforge/engine/rules`, drawn by the shared `@netforge/tabletop` client kit. ## 1. Pick the template that matches your turns ```sh npm create netforge@latest my-duel -- --template cards --yes npm create netforge@latest my-collector -- --template collector --yes ``` The card battler is the closest match for a classic two-player turn-based game. The monster collector shows turn-based battles inside a world people walk around in together. ## 2. Change the turn rules in game.ts In the card battler, the turn timer, the hand, the board and the mana curve are one declaration: ```ts title=src/game.ts import { cardTable } from "@netforge/engine"; cardTable({ health: 25, startHand: 4, handMax: 8, boardMax: 6, manaMax: 10, turnSeconds: 45, draftSeconds: 20, duels: 3, starter: ["blot_imp", "blot_imp", "quill_sprite", "quill_sprite", "ink_frog", "ink_frog", "margin_bat", "ledger_crab", "serif_fox", "splash"], ai: [{ health: 14, smart: 0.3 }, { health: 18, smart: 0.6 }, { health: 22, smart: 0.85 }], }); ``` In the monster collector, a battle's moves are declarations too: ```ts title=src/game.ts import { creature, move } from "@netforge/engine"; move("tide_crash", { element: "tide", power: 70, accuracy: 0.8 }); move("thorn_curl", { element: "root", power: 0, effect: "guard" }); creature("thistlehog", { model: "style.lowpoly.fox", element: "root", hp: 50, atk: 52, def: 48, spd: 46, moves: ["tide_crash", "thorn_curl"], catchRate: 0.7 }); ``` ## 3. Understand the rules module behind it You do not write this part to make a game from a template, but it is how the genre stays fair. A rules genre implements `RulesGenre` from `@netforge/engine/rules`: `act` applies one player's action after validating it, `step` advances the world one tick, and `view` returns what one player may see. This sketch shows the two pieces that matter most for a turn-based game, a turn check and a private view: ```ts title=turns-sketch.ts import type { RulesGenre } from "@netforge/engine/rules"; interface DuelState { turn: number; hands: Map; board: number[]; } // act: a move out of turn does nothing; the server never trusts the client. const act: RulesGenre["act"] = (s, player, input, _avatar, emit) => { if (s.turn !== player || input.act === 0) return; const hand = s.hands.get(player) ?? []; const card = hand[input.a]; if (card === undefined) return; hand.splice(input.a, 1); s.board.push(card); emit({ name: "card.played", players: [player] }); }; // view: the board is public, a hand is sent only to its owner. const view: RulesGenre["view"] = (s, player) => ({ turn: s.turn, board: s.board, hand: player === null ? [] : (s.hands.get(player) ?? []), }); export { act, view }; ``` State is integers and a seeded random word, with no clocks, so a run replays exactly from its inputs and a bot can play it headless. ## 4. Test turns with the bots ```sh npm test npm run check ``` The playtest seats bots, plays whole runs and reports duels or battles won, runs finished and softlocks, with the genre's own checks under `rules.checks`. For the card battler, four playtests that together simulate 17 minutes of duels finish in under half a second; for the monster collector, four keepers playing four minutes take 349 ms (Apple M2 Pro, Node 22). ## How it works The room runs the rules module at 30 Hz even though the game is turn-based: the tick drives the turn timer, animations of the board and each player's cursor or walking avatar, which the client predicts so pointing and walking never wait. A turn action travels as an input; the server validates it in `act` and applies it, or ignores it. Each player gets the public state plus their own private state, sent only to them when it changes. Because the module is deterministic, the same seed and inputs replay the same run, which is what the playtest and run verification rely on. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md) and [Testing with playtest bots](https://forge.netnet.capital/docs/concepts/playtest-bots.md). ## Alternatives - **[boardgame.io](https://github.com/boardgameio/boardgame.io)** is an MIT-licensed engine for turn-based games in JavaScript, with state synced across clients, server and storage, game phases with their own rules and turn orders, generated bots and a lobby. It is view-layer agnostic. Its latest npm release is 0.50.2 from 2022-11-10 ([npm registry](https://registry.npmjs.org/boardgame.io)). Pick it for a 2D board or card game where you want a compact state model and your own UI. - **[Colyseus](https://docs.colyseus.io/)** is an authoritative Node.js room server with matchmaking and binary delta state sync, with client SDKs for many engines. Pick it when you want to write turn logic as server code against your own client. Pick Net Forge when you want a finished turn-based game with a 3D client, AI or seat bots and server-side hidden information already working. ## FAQ ### Can I make a turn-based game with Net Forge? Yes. The card battler and the monster collector are turn-based templates. They run as rules genres on the room server, which validates every action and decides whose turn it is. ### How is hidden information kept hidden? A rules genre's `view(state, player)` returns only what that player may see, and the room sends each player's private state to that player alone. In the card battler, deck order never leaves the server. ### Does a turn-based game need a 30 Hz server? The tick drives timers and cursors, not turns. A turn changes only when a validated action arrives or the turn timer ends it. The cost is small: these modules run in well under a millisecond per tick. ### Can I write my own rules genre? The `RulesGenre` interface is public in `@netforge/engine/rules`. A new genre is template-level work: the ten templates are what `npm run play` runs locally today, so start from the nearest one. ### Is there a chess or board game template? Not yet. The nearest templates are the card battler (two players, hidden hands) and the tower defense (one shared board). --- # How to make a co-op tower defense game in the browser > Start the COGSPIRE tower defense template with one command: up to four players build and upgrade towers on one shared board against twenty waves. Change the road, towers, creeps and waves in src/game.ts; targeting, gold, co-op and netcode come with it. 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/tower-defense-browser-game Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Run `npm create netforge@latest my-td -- --template tower-defense --yes` and you have a co-op tower defense: creeps walk a road in waves, up to four players build towers beside it with their own gold, every kill pays its builder a bounty and the rest of the team a share, and the team shares the lives. The road, the towers, the creeps and the waves are declarations in `src/game.ts`. ```sh npm create netforge@latest my-td -- --template tower-defense --yes cd my-td npm run play ``` `--template tower` and `--template td` are accepted as the same template. ## What you get | Part | Where | Notes | |---|---|---| | Board and road | `towerBoard({...})` | Tile waypoints from edge to edge; blocked tiles nobody builds on. A tile is 2 m. | | Towers | `tower({...})` | 1 to 8 towers of kind `bolt`, `splash`, `frost` or `chain`, each with upgrade levels. Towers aim at the creep furthest along the road. | | Creeps and bosses | `prefab({...})` | Health, speed, gold bounty and the lives a leak costs; `boss: true` adds a health bar. | | Waves | `waves({...})` | Groups by wave number, with growth, gaps and delays. More builders make creeps tougher (+30% health per extra player). | | Co-op economy | template | Bounties to the builder, shares to the team, a ready vote that skips the breather for a little gold. | | Server cost | measured | Four bots for four minutes reach wave 8 at 0.026 ms per tick on average, p99 0.098 ms (Apple M2 Pro, Node 22). | ## 1. Start it ```sh npm create netforge@latest my-td -- --template tower-defense --yes cd my-td npm run play -- --players 2 ``` Click a tile to build the tower picked in the bar (keys 1 to 4), click a tower to upgrade or sell it, and press Space to call the next wave. On touch, tap a tile to aim and tap again to build. ## 2. Draw a new road ```ts title=src/game.ts import { towerBoard } from "@netforge/engine"; towerBoard({ size: [20, 12], path: [[0, 5], [6, 5], [6, 1], [13, 1], [13, 10], [19, 10]], lives: 20, gold: 100 }); ``` Each step of the path changes x or y, never both, and the road starts and ends on the board's edge. Longer roads with more turns give towers more time; a straight road is hard. ## 3. Add a tower and a creep ```ts title=src/game.ts import { prefab, tower } from "@netforge/engine"; tower("sky_harpoon", { model: "style.lowpoly.rocket", kind: "bolt", cost: 120, damage: 60, rate: 0.4, range: 6 }); tower("bolt_post", { model: "style.lowpoly.turret", kind: "bolt", cost: 40, damage: 8, rate: 1.5, range: 3, levels: [{ cost: 45, damage: 14 }, { cost: 80, damage: 24, range: 3.5 }], }); prefab("ironclad", { model: "style.lowpoly.robot_walker", health: 160, speed: 0.9, value: 14, damage: 3 }); ``` Then bring the creep into the waves with a group such as `{ prefab: "ironclad", from: 8, count: 2, grow: 0.5, gap: 2 }`. `splash` towers take a `radius`, `frost` towers a `slow` and `slowSeconds`, `chain` towers a number of `chains`. ## 4. Test it ```sh npm test ``` The playtest seats four builders and requires that they kill creeps, clear wave 3 and fire an earn rule within four minutes without losing the spire before wave 4. A road that is too short or a wave that is too strong fails here with the check that broke. ## How it works Tower defense runs as a rules genre: a deterministic module the room server steps at 30 Hz, with integer state and a seeded random word. Your cursor is predicted so pointing never waits; builds, upgrades, sells and the ready vote are actions the server validates against your gold, the tile and who owns the tower. Creeps are networked entities the client interpolates. The same netcode, playtest and store serve every template. See [Make a turn-based multiplayer game](https://forge.netnet.capital/docs/guides/turn-based-browser-game.md) for rules genres in general and the [tower defense template page](https://forge.netnet.capital/docs/templates/tower-defense.md) for every file. ## Alternatives - **[Phaser](https://docs.phaser.io/)** is a free, open-source 2D framework with WebGL and Canvas rendering and a large set of examples. Pick it for a single-player 2D tower defense with sprite art where you write the pathing and targeting yourself. - **[Godot](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)** has a scene editor and navigation tools and exports to the web as WebAssembly with WebGL 2. Pick it when you want to design levels in an editor. Pick Net Forge when you want the co-op version, with a server that owns the gold and the waves, already working. ## FAQ ### Can several people build on the same board? Yes, up to four. Each builds with their own gold; kills pay the tower's builder a bounty and the rest of the team a share. ### How do I make the game harder? Raise the waves' health growth, counts and `grow`, or lower starting gold and lives. A shorter prep time and breather make it faster. ### Do towers target the closest creep? They target the creep furthest along the road within range, which is the usual tower defense rule and keeps leaks rare. ### Can I add a boss wave? Yes. Declare a prefab with `boss: true` and add a group such as `{ prefab: "boilerback", from: 10, every: 10, count: 1, delay: 8 }` for a boss every tenth wave. ### Can creeps take a path the players draw? The road is declared in `towerBoard({ path })`. Players build beside it, not on it. --- # How to make an online kart racer > Start the TORQUEBAY kart racer with one command: up to eight karts race laps with drift boosts, boost pads and coins, seat bots fill the grid, and the server runs arcade kart physics at 60 Hz. Add circuits and tune the handling in src/game.ts. 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/kart-racer-browser Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Run `npm create netforge@latest my-kart -- --template racer --yes` and you have an online kart racer: up to eight karts on harbour circuits, drifting through bends to charge a mini-turbo, boost pads, coins that raise top speed, and seat bots so a single player still has a field. Circuits are lists of points in `src/game.ts`, and the handling is a handful of numbers. ```sh npm create netforge@latest my-kart -- --template racer --yes cd my-kart npm run play ``` `--template kart` and `--template racing` are accepted as the same template. ## What you get | Part | Where | Notes | |---|---|---| | Circuits | `game({ tracks })` | A closed centreline in metres, laps, width, boost pads and coin rows. One circuit per round. | | Handling | `game({ kart })` | Top speed, acceleration, turn, grip, drift grip, grass drag and boost speed. | | Drift boosts | template sim | A held drift charges a mini-turbo in three tiers; release for the boost. | | Seat bots | `players.bots` | Fill the grid, drive the racing line, are shaded by skill and never earn. | | Streaming track | template client | Road, kerbs, barriers and scenery built in chunks of about 40 m near the camera. | | Server cost | measured | Four playtest drivers finish two races with a p99 of 0.79 ms per tick (Apple M2 Pro, Node 22). | ## 1. Start it ```sh npm create netforge@latest my-kart -- --template racer --yes cd my-kart npm run play -- --players 2 ``` W or Up accelerates, S or Down brakes, A and D steer, and holding Space or Shift through a bend drifts. R or Enter readies the next race. ## 2. Add a circuit and tune the karts Circuits and handling are part of the `game({...})` declaration. Points are the closed centreline in driving order; boosts and coins are `[lap fraction, lateral]`: ```ts title=src/game.ts import { game } from "@netforge/engine"; export default game({ template: "racer", style: "lowpoly", tickRate: 60, places: { harbour: { chunks: "places/harbour", spawn: [0, 0, 0] } }, players: { prefab: "style.lowpoly.kart_a", max: 8, bots: 6 }, round: { seconds: 240 }, kart: { topSpeed: 30, turn: 2.6, grip: 10, driftGrip: 1.5 }, tracks: [ { name: "Breakwater Sprint", laps: 3, width: 14, points: [[0, 0], [110, 0], [160, 40], [150, 110], [80, 140], [10, 120], [-40, 70], [-30, 20]], boosts: [[0.1, 0], [0.5, 0.3]], coins: [[0.2, 0], [0.21, 0], [0.22, 0], [0.7, -0.3], [0.71, -0.3]], }, ], }); ``` Ranges the build accepts: `topSpeed` 10 to 45, `turn` 1 to 4, `grip` 3 to 20, `driftGrip` 0.5 to 6, `round.seconds` 60 to 600. `players.bots: 0` turns the seat bots off. ## 3. Reward clean driving A system can watch race events and emit your own. This one rewards finishing without a hard wall hit: ```ts title=src/game.ts import { earn, system } from "@netforge/engine"; system("clean_line", ({ on, emit }) => { const scrapes = new Map(); on("kart.bumped", (ids) => { for (const id of ids) scrapes.set(id, (scrapes.get(id) ?? 0) + 1); }); on("player.finished", (ids) => { const clean = ids.filter((id) => (scrapes.get(id) ?? 0) === 0); if (clean.length > 0) emit("race.clean", clean); }); on("run.started", (ids) => { for (const id of ids) scrapes.delete(id); }); }); earn.rule("clean_line", { when: "race.clean", n: 1, pay: 12, oncePer: "run" }); ``` Race events include `player.finished`, `race.won`, `race.podium`, `lap.completed`, `coin.collected`, `drift.boosted`, `drift.ultra` and `kart.bumped`. `race.won` and `race.podium` count only with another person in the round, so beating bots pays nothing extra. ## 4. Test it ```sh npm test ``` The playtest races four drivers, checks that races finish, laps count and earn rules fire, and reports the server cost per tick. ## How it works The kart physics is arcade vehicle physics in `@netforge/engine-sim`'s kart mode, on a 1/1024 grid at 60 Hz: each tick the forward part of the velocity is pushed toward top speed and grip bleeds the sideways part, much less while drifting. Laps count through four quarter checkpoints in order, so cutting the course does not count. Your own kart is predicted in the browser with the same code, so steering never waits for the network. Kart bumps run only on the server, and the next snapshot corrects your kart when one happens. The local host sends each player every kart within 400 m. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md) and the [kart racer template page](https://forge.netnet.capital/docs/templates/kart-racer.md). ## Alternatives - **[Babylon.js](https://doc.babylonjs.com/features/featuresDeepDive/physics/havokPlugin)** is a full 3D engine whose physics runs on the Havok engine through its Havok plugin. Pick it when you want rigid-body vehicle physics and a rich 3D toolset, and plan to add networking yourself. - **[PlayCanvas](https://developer.playcanvas.com/user-manual/)** is a web 3D engine with a browser-based editor. Pick it when your team wants to build tracks visually in an editor. - **[Godot](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)** exports to the web as WebAssembly with WebGL 2 and has vehicle nodes in its 3D physics. Pick it when you also want a native build. Pick Net Forge when you want an online racer with prediction, seat bots and lap rules already working, and circuits your agent can add as data. ## FAQ ### Is the racing multiplayer? Yes. Up to eight karts race in one room on an authoritative server. Empty places fill with seat bots. ### Why does the kart racer tick at 60 Hz when other templates use 30 Hz? Fast vehicles need a finer step for steering and drifting to feel right. The template declares `tickRate: 60`, and the kart mode is cheap enough to run it: the playtest measured a p99 of 0.79 ms per tick. ### Can players bump each other? Yes. Kart bumps are resolved on the server only, and each client is corrected by the next snapshot. ### How do I make a new track? Add an object to `tracks` with a closed list of `points` in metres, `laps`, `width`, and optional `boosts` and `coins`. Circuits rotate one per round. ### Can I race other vehicles? `players.prefab` takes any vehicle model in the asset library, for example `style.lowpoly.race_car` or `kart_a` to `kart_e`. --- # How to make a co-op dungeon crawler with procedural rooms in TypeScript > Start the CRYPTWICK template with one command: a four-player co-op crawl through a crypt grown from a seed, with rooms that wake as you approach, flow-field pathing round walls, reliquary upgrades and permadeath. Change the seed, the rooms and the encounters in src/game.ts. 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/dungeon-crawler-typescript Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Run `npm create netforge@latest my-crypt -- --template dungeon --yes` and you have a co-op dungeon crawler: a crypt of ten rooms grown from a seed, up to four players going down room by room, enemies that walk round walls through the doors, an upgrade at every cleared room and a boss in the deepest one. The floor plan is one line of TypeScript, `dungeon({ seed, rooms, ... })`, and the room logic is a system you can read and change. ```sh npm create netforge@latest my-crypt -- --template dungeon --yes cd my-crypt npm run play ``` `--template crawler` is accepted as the same template. ## What you get | Part | Where | Notes | |---|---|---| | Procedural floor plan | `dungeon({...})` | Rooms on a grid joined by corridors, from a seed. The same seed gives the same crypt on the server, every client and the playtest; nothing is downloaded. | | Walls and pathing | engine-sim | Walls stop players, enemies and bolts. Enemies behind a wall path round through the doors on a flow field, big bodies included. | | The crawl | `system("crawl", ...)` in `src/game.ts` | Rooms wake as the party nears them, encounters by depth, a quiet timer, reliquaries and respawns on clear. | | Permadeath | `players.downed` | No free get-up; if the whole party is down at once the descent is over. | | Fog of war and x-ray walls | template client | Rooms nobody entered stay dark; a wall never hides your character. | | Server cost | measured | Four bots for four minutes clear 219 enemies across 8 rooms at 0.40 ms per tick on average, p99 0.77 ms (Apple M2 Pro, Node 22). | ## 1. Start it ```sh npm create netforge@latest my-crypt -- --template dungeon --yes cd my-crypt npm run play -- --players 2 ``` ## 2. Grow a different crypt ```ts title=src/game.ts import { dungeon } from "@netforge/engine"; const CRYPT = dungeon({ seed: 7, rooms: 14, cell: 28, size: [16, 22], corridor: 6, loops: 1 }); const deepest = CRYPT.boss; const firstRoom = CRYPT.roomAt(0, 0); export { CRYPT, deepest, firstRoom }; ``` - `seed` is any whole number; a different seed is a different floor plan. - `rooms` is 3 to 20, the start and boss rooms included. About 10 makes a 6 to 10 minute descent, about 40 seconds a room. - `cell` is the room spacing in metres (16 to 32), `size` the smallest and largest room side, `corridor` the corridor width (6 lets a boss through), and `loops` the extra doors that make loops (0 is a pure tree). Pass `field: CRYPT.field` to `game({...})`. `CRYPT.boss` is the deepest room, `CRYPT.rooms` gives each room's centre, half sizes, depth and links, and `CRYPT.roomAt(x, z)` the room holding a point (or -1 in a corridor), so your systems can reason about rooms. ## 3. Change what each depth holds The crawl system reads an `ENCOUNTERS` table: what a woken room sends, by its depth, for one player. Each extra player adds 35%. ```ts title=src/game.ts const ENCOUNTERS: readonly (readonly (readonly [string, number])[])[] = [ [["bone_rattler", 7], ["crypt_bat", 5]], [["bone_rattler", 9], ["bone_archer", 3], ["grave_shambler", 2]], [["grave_shambler", 3], ["crypt_bat", 9], ["wax_wisp", 4], ["bone_archer", 3]], ]; export { ENCOUNTERS }; ``` Add a new enemy with `prefab(...)`, put it in a row of `ENCOUNTERS` and in the `DEAD` list, so a room that still has one standing is not cleared: ```ts title=src/game.ts import { prefab } from "@netforge/engine"; prefab("ghoul", { model: "style.lowpoly.zombie", health: 30, speed: 3.2, radius: 0.4, damage: 8, ai: "chase", drops: ["wick_small"] }); prefab("bone_archer", { model: "style.lowpoly.skeleton", health: 24, speed: 2.8, ai: "ranged", attack: { damage: 9, range: 10, speed: 7 }, drops: ["wick_small"] }); ``` A ranged enemy shows an aim line before it fires and needs a clear line, so walls are cover. ## 4. Test it ```sh npm test ``` The playtest sends four bots down the crypt and checks that rooms clear, players level and earn rules fire, with no softlock. A crypt whose boss room cannot be reached, or a room that never clears, shows up here. ## How it works The crawler runs on the same machinery as the survivor-like: auto-firing weapons, level-up cards, co-op downs and revives, bosses with telegraphed attacks. The server steps it at 30 Hz and is authoritative; your character is predicted in the browser. Walls are boxes in the Rapier world for players, and enemies, bolts and spawns are resolved against them, with a flow field (a wider grid for big bodies) guiding enemies round to the doors. A woken room's enemies come in inside that room but off every player's screen. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md) and the [dungeon crawler template page](https://forge.netnet.capital/docs/templates/dungeon-crawler.md). ## Alternatives - **[Phaser](https://docs.phaser.io/)** is a free, open-source 2D framework with WebGL and Canvas rendering. Pick it for a 2D tile-based crawler where you write the generation and the loop yourself. - **[Godot](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)** has a scene editor and tile tools and exports to the web as WebAssembly with WebGL 2. Pick it when you want to hand-design rooms in an editor. Pick Net Forge when you want the co-op 3D crawl with procedural rooms, pathing and netcode already working. ## FAQ ### Is the dungeon generated on the server or the client? Both, from the same spec. `dungeon({ seed, ... })` is deterministic, so the server, every client and the playtest grow the same crypt without sending it over the network. ### How do enemies find players behind walls? They follow a flow field computed over the floor plan, which leads them round to the doors. Big enemies use a wider grid so they do not get stuck in corridors. ### Can I turn permadeath off? Yes. Set `players.downed` to allow a free get-up (`selfRevives: 1`) or a respawn time in seconds. ### How long is a run? About 40 seconds a room. Ten rooms make a 6 to 10 minute descent. ### Can I hand-design the rooms instead of generating them? The template grows the crypt from `dungeon(...)`. A game can also declare its own `field` with `walls` and `floors` as boxes, which is how the party template builds its single playroom. --- # How to make a multiplayer idle game in the browser > Start the BRINEWORKS idle template with one command: up to eight players each run a works on one shared salt flat and fill a contract together. Production runs only on the server's clock while you are in the room, so the game cannot be farmed. Tune plants, costs and shifts in src/game.ts. 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/idle-game Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Run `npm create netforge@latest my-idle -- --template idle --yes` and you have a shared idle game: up to eight players each run a works on one salt flat (pumps draw brine, pans boil it into salt, kilns cut crystals) and everyone ships crystals to one contract. The production chain, costs and growth curves are declarations in `src/game.ts`. ```sh npm create netforge@latest my-idle -- --template idle --yes cd my-idle npm run play ``` One design choice is different from most idle games, on purpose: there is no offline progress. Production happens only on the room's clock while you are in the room, and a works stalls two minutes after you last tend it. That is what lets a multiplayer idle game stay fair and keeps rewards from being farmed by an open tab. ## What you get | Part | Where | Notes | |---|---|---| | Production chain | `plant({...})` | 1 to 8 plants with at least one `pump`, one `pan` and one `kiln`. Each level costs `growth` times the last. | | Shared goal | `saltFlat({ contract })` | A barge takes crystals from everyone for one contract. | | Shifts | `saltFlat({ shift })` | A run is a shift of 600 to 3,600 seconds (15 minutes in the template). | | Anti-farm rules | build checks | `tend` is at most 300 s; no earn rule may pay sooner than 180 s into a fresh works, which the playtest measures. | | Players | room | Up to 8 per flat. | | Playtest | `npm test` | Four players for four minutes, plus a full shift, run in 1.35 s in total (Apple M2 Pro, Node 22). The first payout comes 199 s into a fresh works. | ## 1. Start it ```sh npm create netforge@latest my-idle -- --template idle --yes cd my-idle npm run play -- --players 2 ``` ## 2. Change the progression curve ```ts title=src/game.ts import { plant } from "@netforge/engine"; plant("hand_pump", { model: "style.lowpoly.barrel", kind: "pump", cost: 15, growth: 1.15, rate: 3 }); plant("deep_bore", { model: "style.lowpoly.generator_large", kind: "pump", cost: 5000, growth: 1.2, rate: 200 }); ``` A lower `growth` keeps levels cheap for longer; a higher `rate` makes each level count more. Add each new plant's label to `src/names.json` under `weapons`. The bar shows plants in order, on keys 1 to 8. ## 3. Lengthen the shift and set a bigger contract ```ts title=src/game.ts check=skip saltFlat({ shift: 1200, contract: { crystals: 120 }, ... }); ``` The template's `saltFlat(...)` has more fields (the flat, its layout and timings); change `shift` and `contract` in place rather than writing a new one. ## 4. Pay for real play, not for time ```ts title=src/game.ts import { earn } from "@netforge/engine"; earn.rule("first_crate", { when: "salt.packed", n: 30, pay: 5, oncePer: "run" }); ``` Events are `salt.packed`, `crystal.cut`, `crystal.shipped`, `plant.built`, `plant.upgraded` and `contract.filled`. Every rule is once per run. If a change makes a rule pay sooner than 180 seconds into a fresh works, the playtest fails with the number and tells you to raise the rule's `n` or slow the curve. ## How it works The idle game is a rules genre: a deterministic module the room steps at 30 Hz, with integer state and no wall clock. Output is computed only on the room's ticks for players in the room, never from a client-reported number or elapsed real time. A player who leaves and comes back starts a fresh works from 20 kg of salt. Contract progress is world progress, so it never pays a player whose run started after the shift began. See [Testing with playtest bots](https://forge.netnet.capital/docs/concepts/playtest-bots.md) and the [idle game template page](https://forge.netnet.capital/docs/templates/idle-game.md). ## Alternatives - **[Phaser](https://docs.phaser.io/)** suits a single-player 2D incremental game in the browser, with offline progress computed on the client from saved timestamps. - **[Colyseus](https://docs.colyseus.io/)** gives you an authoritative room server with state sync if you want to write a shared idle economy's rules as server code. Pick Net Forge when you want a shared idle game where everyone's progress is decided on the server and a bot playtest checks the pacing. ## FAQ ### Does the idle template have offline progress? No, by design. Production runs only on the room's clock while you are in the room, and a works stops two minutes after its last tend. That keeps a shared game fair; for a single-player idle game with offline progress, a client-side approach fits better. ### How many players share one game? Up to eight per salt flat, each with their own works and one shared contract. ### How long is a run? A shift, 15 minutes in the template, adjustable from 10 to 60 minutes with `saltFlat({ shift })`. ### Can clicking faster earn more? Raking has a cooldown, so mashing adds nothing. The playtest simulates perfect play and pure raking to check that no rule pays too early. ### What happens when I leave? Your works stops. Rejoining starts a fresh works, so time away never accumulates. --- # How to make a multiplayer 3D platformer for the web > Start the SKYLEDGE platformer with one command: up to eight climbers race cliffside courses with coyote time, jump buffering, variable jump height, dashes, springs and moving platforms. The movement is predicted bit for bit, and a route planner checks every course. Tune the feel in src/game.ts. 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/3d-platformer-web Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Run `npm create netforge@latest my-climb -- --template platformer --yes` and you have a multiplayer platformer race: up to eight climbers run, jump, dash, ride moving platforms and bounce off springs to reach the flag first, with seat bots so one person still has a race. The jump feel is a handful of numbers in `src/game.ts`, and a route planner proves every course can be finished. ```sh npm create netforge@latest my-climb -- --template platformer --yes cd my-climb npm run play ``` ## What you get | Part | Where | Notes | |---|---|---| | Jump feel | `game({ motor })` | Run speed, jump height, time to apex, fall gravity, jump cut, coyote time (0.1 s), jump buffer (0.13 s), air jumps, dash speed. | | Courses | `game({ levels })` | Platforms, moving platforms, springs, thorns, coins and checkpoints. One course per round. | | Race rounds | engine `RaceRunner` | Countdown, race, results, next course. | | Seat bots | `players.bots` | Run routes found by the route planner; never earn. | | Softlock check | route planner | The playtest fails a course with no route to the flag. | | Server cost | measured | Four playtest climbers finish seven rounds with a p99 of 0.033 ms per tick (Apple M2 Pro, Node 22). | ## 1. Start it ```sh npm create netforge@latest my-climb -- --template platformer --yes cd my-climb npm run play -- --players 2 ``` ## 2. Tune the feel The movement numbers sit in the `motor` field of `game({...})`. Change a few at a time: ```ts title=src/game.ts check=skip export default game({ template: "platformer", // ...the rest of the template's game({...}) stays as it is motor: { runSpeed: 8, jumpHeight: 3.6, airJumps: 1 }, }); ``` Accepted ranges: `runSpeed` 3 to 14, `jumpHeight` 1 to 8, `timeToApex` 0.2 to 0.7, `fallGravity` 1 to 3, `jumpCut` 0.2 to 1, `coyoteSeconds` and `bufferSeconds` 0 to 0.25, `airJumps` 0 to 2, `dashSpeed` 6 to 30. The route planner re-checks every course with the new numbers, so a jump made too low to clear a gap fails the playtest instead of shipping. ## 3. Reward a clean climb ```ts title=src/game.ts import { earn, system } from "@netforge/engine"; system("clean_climb", ({ on, emit }) => { const falls = new Map(); on("player.fell", (ids) => { for (const id of ids) falls.set(id, (falls.get(id) ?? 0) + 1); }); on("player.finished", (ids) => { const clean = ids.filter((id) => !falls.get(id)); if (clean.length) emit("climb.clean", clean); }); on("run.started", (ids) => { for (const id of ids) falls.delete(id); }); }); earn.rule("speedy", { when: "player.finished", n: 1, pay: 6, oncePer: "run" }); ``` Race events include `player.finished`, `race.won`, `race.podium`, `coin.collected`, `checkpoint.reached`, `player.fell` and `spring.bounced`. Winning against a field of bots pays nothing extra. ## 4. Test it ```sh npm test ``` The playtest first asks the planner for a route to the flag on every course, then races four climbers through the rounds and checks that they finish and that earn rules fire. ## How it works The platform sim is pure TypeScript: swept AABB movement on a 1/1024 m grid at 60 Hz in `@netforge/engine-sim`'s platform mode. Because it is deterministic, the browser predicts your own climber and replays it bit for bit when a snapshot arrives. Climbers pass through each other and coins are per climber, so nothing another player does can make your prediction wrong, which is why the race feels local even online. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md) and the [platformer template page](https://forge.netnet.capital/docs/templates/platformer.md). ## Alternatives - **[Phaser](https://docs.phaser.io/)** is a free, open-source 2D framework with WebGL and Canvas rendering. Pick it for a single-player 2D platformer with tile maps and sprite art. - **[Godot](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)** has a scene editor and a character body for platformer movement, and exports to the web as WebAssembly with WebGL 2. Pick it for a level-editor-first workflow and a native build. Pick Net Forge when you want an online race with prediction and seat bots, and courses that a planner verifies after every change. ## FAQ ### What is coyote time and does the template have it? Coyote time lets a jump still work for a moment after you run off a ledge. The template has 0.1 s of it, plus a 0.13 s jump buffer, variable jump height and a lighter apex while Jump is held. ### Can players knock each other off? No. Climbers are ghosts to each other, which keeps every player's movement predicted exactly. ### How does the game know a course can be finished? A route planner searches each course with the current movement numbers. If it finds no route to the flag, the playtest fails, so a broken course never reaches players. ### Is the platformer 3D? The courses are 3D scenes in every art style, played from a side view. ### How many players can race? Up to eight per room, with seat bots filling empty places. --- # How to make an online monster collector with turn-based battles > Start the MOSSBOUND monster collector with one command: up to eight players walk a shared marsh, meet wild creatures in tall grass, battle them turn by turn with a team of three, catch the ones worth keeping and wear down one shared boss. Add creatures, moves and grass zones in src/game.ts. 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/monster-collector-game Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Run `npm create netforge@latest my-collector -- --template collector --yes` and you have an online monster collector: pick a starter at camp, walk the grass, battle wild creatures turn by turn with a team of three, catch them, and join everyone in the marsh to bring down the warden before dusk. Creatures, moves, elements and the map are declarations in `src/game.ts`. ```sh npm create netforge@latest my-collector -- --template collector --yes cd my-collector npm run play ``` `--template monsters` is accepted as the same template. ## What you get | Part | Where | Notes | |---|---|---| | Creatures | `creature({...})` | 1 to 24, with element, hp, attack, defence, speed, 1 to 4 moves and a catch rate. Stats grow 12% a level. | | Moves | `move({...})` | Up to 48, with power, accuracy and effects `drain`, `heal` and `guard`. | | Elements | engine | `root`, `tide`, `spark`, `stone` in a cycle; strong hits deal 1.5x, weak ones 0.67x. | | The map | `marsh({...})` | Size, camp, water, blocked rects and grass zones with encounter rates and pools. | | Shared boss | `marsh({ warden })` | One health pool for the whole marsh; everyone who hurt it shares the win. | | Private battles | room server | Your team, your foe and the battle log reach only you. | | Playtest | `npm test` | Four keepers playing four minutes take 349 ms; the warden-falls test plays a whole run in 1.2 s (Apple M2 Pro, Node 22). | ## 1. Start it ```sh npm create netforge@latest my-collector -- --template collector --yes cd my-collector npm run play -- --players 2 ``` Walk with WASD, the arrows or the left thumb on touch. ## 2. Add a creature and its moves ```ts title=src/game.ts import { creature, move } from "@netforge/engine"; move("leaf_lash", { element: "root", power: 40 }); move("thorn_curl", { element: "root", power: 0, effect: "guard" }); move("tide_crash", { element: "tide", power: 70, accuracy: 0.8 }); creature("thistlehog", { model: "style.lowpoly.fox", element: "root", hp: 50, atk: 52, def: 48, spd: 46, moves: ["leaf_lash", "thorn_curl"], catchRate: 0.7, }); ``` Then put it in a grass zone's `pool` in `marsh({...})` and add its label to `src/names.json` under `weapons`. A power of 40 is a plain hit. ## 3. Shape the marsh Grass zones decide what you meet and how often. Put weak creatures near camp and stronger ones toward the warden: ```ts title=src/game.ts check=skip marsh({ size: [30, 20], camp: [2, 10], grass: [{ at: [4, 2, 6, 5], rate: 0.22, pool: [{ creature: "thistlehog", weight: 3, level: [2, 3] }] }], // ...the template's water, blocked tiles, warden and dusk stay as they are }); ``` Rects are `[x, y, width, height]` in tiles of 2 m. Leave a walkable way from camp to every grass zone and to the warden; the build checks it. ## 4. Test it ```sh npm test ``` Four playtest keepers must win battles, catch creatures and fire an earn rule within four minutes, without losing their whole team more than twice each. ## How it works The collector is a rules genre: the room runs it at 30 Hz with integer state and a seeded generator. Every roll (encounters, accuracy, damage spread, snares and the foe's move) is the room's, so a run replays exactly from its inputs. Your keeper is predicted with the same tile collision the room runs, so walking never waits. A battle is private: the room sends your team, your foe and the log only to you, and others see the creature beside you. See [Make a turn-based multiplayer game](https://forge.netnet.capital/docs/guides/turn-based-browser-game.md) and the [monster collector template page](https://forge.netnet.capital/docs/templates/monster-collector.md). ## Alternatives - **[Godot](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)** has a scene editor and tile tools suited to hand-built RPG maps, and exports to the web as WebAssembly with WebGL 2. Pick it for a single-player creature RPG with a long story. - **[Phaser](https://docs.phaser.io/)** is a free, open-source 2D framework with WebGL and Canvas rendering. Pick it for a 2D top-down collector in sprite art. - **[Colyseus](https://docs.colyseus.io/)** is an authoritative room server with state sync, for writing the battle rules as server code against your own client. Pick Net Forge when you want the shared-world, turn-based collector already online, with private battles and a playtest. ## FAQ ### Are battles turn-based or real-time? Turn-based. Each turn you pick a move, a swap, a snare or flee; the faster creature acts first and the foe answers with its best move. ### Can players see each other's battles? They see the creature beside you, not the battle. Your team, your foe and the log are sent only to you. ### How does catching work? A snare's chance is the creature's catch rate times (0.25 plus 0.75 times the foe's missing health), so weakened creatures are easier to catch. A team holds three; more go into the lantern. ### How many players share the world? Up to eight per marsh, all wearing down the same warden. ### Can I add more element types? The four elements and their cycle are fixed by the genre. Variety comes from creatures, moves and effects. --- # How to make a party game for 2 to 8 players on phones and laptops > Start the ROMPHOUSE party template with one command: two to eight friends in one playroom play five quick co-op minigame rounds a show, on phones or laptops. Each minigame is an entry in a ROUNDS list in src/game.ts that you can add to or change. 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/party-game-browser Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Run `npm create netforge@latest my-party -- --template party --yes` and you have a party game: two to eight friends in one playroom, five quick rounds a show, each a minigame with one goal for the whole team, and touch controls so phones work. A minigame is a small object in the `ROUNDS` list of the show system in `src/game.ts`: a name, a length, what starts and what counts as a win. ```sh npm create netforge@latest my-party -- --template party --yes cd my-party npm run play -- --host 0.0.0.0 ``` With `--host 0.0.0.0`, everyone on the same network opens the link on their phone or laptop and joins the room. ## What you get | Part | Where | Notes | |---|---|---| | Minigames | `ROUNDS` in the `show` system | `{ name, seconds, start(), won(), lastsOut }`. The first four are shuffled each show; the finale is always last. | | A show | `HIT_AT` | Win three rounds and the show is a hit; either way the next show starts. | | Forgiving downs | `players.downed` | Short downs, a crawl, two free get-ups a show, everyone back between rounds. | | The playroom | `game({ field })` | One floor, four walls and party tables, bigger than the screen so crowds arrive off screen. | | Touch controls | template client | Every template plays on phones. | | Server cost | measured | Four bots playing four minutes: 305 kills, 6 rounds, 0.42 ms per tick on average, p99 4.20 ms (Apple M2 Pro, Node 22). | ## 1. Start it ```sh npm create netforge@latest my-party -- --template party --yes cd my-party npm run play -- --players 2 ``` ## 2. Add a minigame Inside the `show` system, append a round to `ROUNDS`. This one is won if nobody goes down for 35 seconds: ```ts title=src/game.ts check=skip { name: "DODGEBALL", seconds: 35, lastsOut: true, start: () => { spawnEdge("spritzer", { count: 6 + party() }); every(4, () => { if (current === 5) spawnEdge("spritzer", { count: 2 }); }); }, won: () => false, }, ``` Its index in `ROUNDS` is its `current` value, and the shuffle leaves the last entry, the finale, where it is. `won()` is checked every half second and ends the round early when it returns true. Counters you can use include `kills`, `downs`, `alive("prefab")` and `on(".killed")`. Keep 3 to 8 rounds, and give every round something to do every few seconds: a round with 30 quiet seconds fails the playtest as a softlock. ## 3. Reward the team ```ts title=src/game.ts import { earn } from "@netforge/engine"; earn.rule("first_win", { when: "round.won", n: 1, pay: 5, oncePer: "run" }); earn.rule("clean_sweep", { when: "show.perfect", n: 1, pay: 25, oncePer: "run" }); ``` Every won round emits `round.won`, every round's end emits `wave.cleared`, and a clean sweep emits `show.perfect`. Locally all of it is play money. ## 4. Test it ```sh npm test ``` The playtest plays shows with four bots and checks that rounds are decided, players level and nothing goes quiet. ## How it works The party game runs on the same action machinery as the survivor-like: the room is authoritative at 30 Hz, your own player is predicted, and weapons fire by themselves so every round is about where you stand and who you help up. The show is a system written in `src/game.ts`, not hidden machinery, so you and your agent can read every rule. See [The typed game API](https://forge.netnet.capital/docs/concepts/game-api.md) and the [party game template page](https://forge.netnet.capital/docs/templates/party-game.md). ## Alternatives - **[Colyseus](https://docs.colyseus.io/)** is an authoritative room server with matchmaking and state sync, and client SDKs for the web and several engines. Pick it when you have your own minigames and want a room server under them. - **[Godot](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)** exports to the web as WebAssembly with WebGL 2 and has a high-level multiplayer API. Pick it for an editor-first party game with a native build as well. Pick Net Forge when you want a room of friends playing a finished co-op show within a minute, with minigames your agent can add. ## FAQ ### How many players can play together? Two to eight in one room. ### Do players need to install anything? No. They open the link in a browser on a phone or laptop. Only the person hosting runs `npm run play`. ### Is it competitive or co-op? Co-op: each round has one goal for the whole team, and the show is a hit if the team wins three rounds. ### How do I make a round easier? Lower its goal, give it more seconds or smaller trickles, or lower `HIT_AT`. More `selfRevives` and a longer `hitIframes` help too. ### Can I make the show longer? Add rounds to `ROUNDS` (keep 3 to 8), or change `ROUND_SECONDS`, the default length of a round. --- # Net Forge concepts > How the engine works: the shared simulation, netcode and prediction, the typed game API, assets and art styles, and testing with playtest bots. 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/concepts Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Read these when you want to know why the engine is shaped the way it is. - [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md): Net Forge runs one simulation module, @netforge/engine-sim, on the server for authority and in the browser for prediction. It is a bitECS world with deterministic Rapier physics, stepped at a fixed tick, with integer state and bit-exact snapshots. - [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md): The server runs the simulation and decides what happened. Each browser predicts its own player with the same simulation, reconciles when the server's snapshot arrives, and interpolates everyone else. Inputs are sequence-numbered and sent with redundancy; snapshots are bit-packed deltas. - [The typed game API](https://forge.netnet.capital/docs/concepts/game-api.md): A Net Forge game is one TypeScript file, src/game.ts, that declares what exists and the rules with game(), prefab(), weapon(), director(), system(), earn and store from @netforge/engine. Declarations are Zod-validated, and a mistake fails with one line that says what to fix. - [Assets and art styles](https://forge.netnet.capital/docs/concepts/assets-art-styles.md): Net Forge games reference models and sounds from a CC0 asset library by id, such as style.lowpoly.ghost, never by URL. Every template draws in six art styles (lowpoly, toon, ps1, pixel, voxel, handdrawn), and an id resolves to that style's own model when the style has one. - [Testing with playtest bots](https://forge.netnet.capital/docs/concepts/playtest-bots.md): engine.test.run plays a declared Net Forge game headless with bots and reports kills, waves, levels, earn rules fired, softlocks, the server cost per tick and design metrics such as time to first threat. npm test runs it on your game folder; npm run check prints it as JSON for coding agents. --- # The shared simulation: ECS, Rapier physics and a fixed tick > Net Forge runs one simulation module, @netforge/engine-sim, on the server for authority and in the browser for prediction. It is a bitECS world with deterministic Rapier physics, stepped at a fixed tick, with integer state and bit-exact snapshots. 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/concepts/simulation-ecs Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Net Forge runs one simulation module, `@netforge/engine-sim`, in two places: on the server, where it is the authority, and in the browser, where it predicts the local player. Because both sides run the same code on the same quantized inputs, there is no second copy of the game rules to keep in sync. ```sh npm install @netforge/engine-sim ``` ```ts title=sim.ts import { initSim, createWorld, addPlayer, step, captureState, encodeSnapshot, Button } from "@netforge/engine-sim"; await initSim(); // once per process: loads the Rapier WebAssembly build const world = createWorld({ tickRate: 30, seed: 7, enemies: { target: 2000 } }); addPlayer(world, 1); // One fixed tick, with every player's input for that tick. const { tick, events } = step(world, new Map([[1, { seq: 1, tick: 0, moveX: 0, moveZ: 1, yaw: 0, buttons: Button.Fire }]])); const snapshot = encodeSnapshot(captureState(world)); // a full snapshot as bytes console.log(tick, events.length, snapshot.byteLength); ``` ## What is in it | Part | What it does | |---|---| | ECS world | bitECS entities and components, iterated in `netId` order so both sides agree. | | Physics | Rapier's deterministic WebAssembly build (`@dimforge/rapier3d-deterministic-compat`): bodies, triggers, joints, raycasts and a kinematic character controller. | | Fixed tick | `step(world, inputs)` advances exactly one tick of `1 / tickRate` seconds and never reads a clock. Templates run at 30 Hz. | | Inputs | `InputCommand { seq, tick, moveX, moveZ, yaw, buttons }`, quantized on entry (moves to 1/127, yaw to a 16-bit turn) and 15 bytes encoded. | | State | `captureState` returns integers only (positions in 1/1024 m). The sim snaps itself to that grid every tick. | | Snapshots | `encodeSnapshot(state, baseline?)` writes a full or delta snapshot: zigzag varints with a changed-field mask, about 3 bytes for a resting entity and 8 for a moving one. | There is no DOM, timer, network or renderer in the package. It compiles with `lib: ES2022` only, so the same file runs in Node and in a browser worker. ## Why one simulation A multiplayer game usually needs the rules twice: once on the server that decides what happened, and again on the client so the player's own movement feels instant. When they drift apart, players see rubber-banding and wrong hits. Net Forge removes the second copy. The browser imports the same `step` the server runs, applies its own inputs ahead of the server, and when the server's snapshot arrives it restores that state and replays the inputs the server has not seen yet. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). ## Determinism, honestly On one JavaScript engine the same inputs give the same snapshot bytes; the package tests this over 1,000 ticks. Across engines, `Math.sin` and `Math.cos` can differ in the last bit. That is acceptable because the server is authoritative and clients reconcile to it, rather than the other way round. Randomness comes from a seeded generator stored in the state, never from `Math.random`. ## Fixed tick and rendering `FixedStepper` turns frame deltas into a whole number of ticks plus a render `alpha`. The browser feeds it from `requestAnimationFrame` and the server from its own loop. `renderView(world)` returns typed arrays of positions with the previous tick's values, so the renderer interpolates with `alpha` without touching the ECS. ```ts title=loop.ts import { FixedStepper } from "@netforge/engine-sim"; const stepper = new FixedStepper(30); let last = performance.now(); function frame(now: number): void { const { ticks, alpha } = stepper.advance(now - last); last = now; for (let i = 0; i < ticks; i++) { // step(world, inputs) once per tick } // draw, interpolating by alpha void alpha; requestAnimationFrame(frame); } requestAnimationFrame(frame); ``` ## How much it costs Enemies steer inside the ECS on a uniform grid rather than as Rapier bodies, which is what lets a survivor-like keep thousands alive. Measured with the survivor template's playtest bots on an Apple M2 Pro under Node 22: 2,000 enemies and 4 players cost 2.22 ms per tick on average and 5.21 ms at the 99th percentile, against a 33 ms budget at 30 Hz. ## Game code never touches this layer A game's `src/game.ts` does not import `@netforge/engine-sim`. It declares prefabs, weapons and rules with [the typed game API](https://forge.netnet.capital/docs/concepts/game-api.md), and the template compiles those declarations into sim content. You use engine-sim directly when you write a template or a tool, or when you want the physics world on its own: see [How to add deterministic Rapier physics to a three.js game](https://forge.netnet.capital/docs/guides/threejs-rapier-physics.md). Full API: [@netforge/engine-sim reference](https://forge.netnet.capital/docs/reference/engine-sim.md). ## FAQ ### Is the Net Forge simulation deterministic? Yes on one JavaScript engine: the same inputs produce the same snapshot bytes, and the package tests this. Across different engines trigonometry can differ in the last bit, so the server stays authoritative and clients reconcile to it. ### What ECS does Net Forge use? bitECS, with entities iterated in `netId` order so the server and every client process them in the same sequence. ### What physics engine does Net Forge use? Rapier, in its deterministic cross-platform WebAssembly build, version 0.21. It provides rigid bodies, triggers, joints, raycasts, shape casts and a kinematic character controller with coyote time, jump buffering and dash. ### What tick rate does Net Forge run at? The templates run the simulation at a fixed tick: 30 Hz by default, 60 Hz in the arena shooter, kart racer and platformer. The local room host sends snapshots to clients 20 times a second. ### Can I run the simulation in a Web Worker or on a server? Yes. The package has no DOM or timer dependency and compiles against `lib: ES2022`, so it runs in Node, a Web Worker or on the main thread. --- # Netcode in Net Forge: authority, prediction, reconciliation and snapshots > The server runs the simulation and decides what happened. Each browser predicts its own player with the same simulation, reconciles when the server's snapshot arrives, and interpolates everyone else. Inputs are sequence-numbered and sent with redundancy; snapshots are bit-packed deltas. 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/concepts/netcode-prediction Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. In a Net Forge game the server runs the simulation and is the only authority on what happened. Each browser predicts its own player with the same simulation code so input feels instant, reconciles to the server's state when a snapshot arrives, and draws every other entity slightly in the past by interpolating between snapshots. You get all of this by starting from any template; none of it lives in `src/game.ts`. ```sh npm create netforge@latest my-game -- --template arena --yes cd my-game npm run play -- --players 2 ``` ## The loop, step by step | Step | Where | What happens | |---|---|---| | Sample input | browser | Each frame's input becomes an `InputCommand` with a rising `seq`, quantized so both sides read identical values. | | Predict | browser | `Predictor.predict` applies it to the local world at once and keeps it until the server acknowledges it. | | Send | browser | An input packet carries the newest command plus the last few unacknowledged ones (`REDUNDANCY = 4`), so a lost packet costs nothing. | | Queue | server | A per-player `InputQueue` buffers commands (16 deep, with credits that stop a client from sending faster than real time). | | Step | server | The room steps the world at a fixed tick (30 Hz by default, 60 Hz in the arena, racer and platformer templates) with every player's command for that tick. | | Snapshot | server | Each player gets a delta snapshot against the last state they acknowledged, filtered to what is near them, with the `seq` the server last applied. The local host sends 20 a second. | | Reconcile | browser | `Predictor.reconcile` resets the player to the server's state and replays the commands the server has not applied yet. A disagreement is shown as an offset that fades over about 100 ms instead of a snap. | | Interpolate | browser | `InterpolationBuffer` holds recent snapshots and samples remote entities between two of them, so other players move smoothly. | ## Why the same simulation matters here Prediction only works if the client's guess matches what the server will compute. In Net Forge the client predicts with the server's own step function from [@netforge/engine-sim](https://forge.netnet.capital/docs/concepts/simulation-ecs.md), on bit-identical quantized inputs, so a correction happens only when something the client could not know (another player, a hit) changed the outcome. Prediction itself is a well-known technique (Gabriel Gambetta's [Fast-Paced Multiplayer](https://www.gabrielgambetta.com/client-server-game-architecture.html) series is the usual reference, and other frameworks such as [Colyseus](https://docs.colyseus.io/) offer it too). What Net Forge adds is that you never write the step function twice. ## Snapshots on the wire Snapshots are schema-driven and bit-packed. Each entity kind declares its fields (`EntitySchema`), each field is quantized to its own precision, and a delta snapshot writes only the fields that changed since the acknowledged baseline. `SnapshotHistory` keeps the baselines on the sending side; a receiver asked to decode a delta against a baseline it does not have gets a `BaselineMissingError` instead of a wrong state. ## The SimAdapter seam Every template exposes its game to the netcode through one interface, `SimAdapter`: create a world, add and remove players, step one tick with each player's inputs, snapshot and restore entities, and say how inputs are encoded. The room host, the predictor and the playtest bots all drive the game through it, which is why a board game like the card battler and an action game like the arena shooter share the same netcode. See the [SimAdapter reference](https://forge.netnet.capital/docs/reference/engine-net.md#simadapter). ```ts title=inspect.ts import type { SimAdapter, InputCommand } from "@netforge/engine/net"; import { REDUNDANCY, DEFAULT_INPUT_QUEUE } from "@netforge/engine/net"; function describe(sim: SimAdapter): string { return `${sim.name} v${sim.version}: ${sim.tickHz} Hz, ${REDUNDANCY} commands per packet, queue ${DEFAULT_INPUT_QUEUE.maxQueue}`; } type Move = InputCommand<{ moveX: number; moveZ: number }>; export type { Move }; console.log(typeof describe); ``` ## What runs where | Piece | Public packages | Hosted Net Forge room server | |---|---|---| | Prediction, reconciliation, interpolation, input packets, delta snapshots | yes (`@netforge/engine/net`) | yes | | Authoritative fixed-tick loop with per-player delta snapshots and area of interest | yes, in `@netforge/kit-play`'s local host | yes | | Lag compensation (rewinding hitboxes to what the shooter saw) | no; the local host resolves hits against current hitboxes, which is exact at local latency | yes | | Run logs, replay verification, the referee | no | yes | The room transport is [Colyseus](https://docs.colyseus.io/): the client uses the Colyseus SDK and the local host is built on `@colyseus/core`. ## FAQ ### Does Net Forge have client-side prediction? Yes. The local player is predicted with the same simulation the server runs, and reconciled against each snapshot by replaying unacknowledged inputs. Corrections are smoothed over about 100 ms. ### Is Net Forge server-authoritative? Yes. The server steps the world and decides every hit, kill and pickup. Clients only predict their own player and display what the server sends. ### Does Net Forge do lag compensation? The hosted room server rewinds hitboxes for hitscan weapons. The local host in `@netforge/kit-play` does not, because on one computer or one network there is almost no latency to compensate. ### How often does the server send snapshots? The simulation ticks at 30 Hz by default and at 60 Hz in the arena, racer and platformer templates. The local host sends each player a delta snapshot 20 times a second, filtered to the entities near that player. ### Does Net Forge use WebSockets or WebRTC? WebSockets, through Colyseus's transport. ### Do I need to write netcode for my game? No. A game is declared in `src/game.ts`, and the template supplies the netcode through its `SimAdapter`. You only touch `@netforge/engine/net` when you write a new template. --- # The typed game API: a whole game in one declarative file > A Net Forge game is one TypeScript file, src/game.ts, that declares what exists and the rules with game(), prefab(), weapon(), director(), system(), earn and store from @netforge/engine. Declarations are Zod-validated, and a mistake fails with one line that says what to fix. 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/concepts/game-api Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. A Net Forge game is one TypeScript file, `src/game.ts`, that declares what exists and how it behaves: the game and its players, prefabs for enemies and pickups, weapons, the spawn director, systems for timed events, earn rules and store items. It imports only `@netforge/engine`, never the renderer, netcode or physics, so the same file runs on the server, in the browser and under the playtest bots. ```sh npm create netforge@latest my-game -- --template survivor --yes ``` ## A complete game module This is a whole, playable survivor-like. The docs build plays it with two bots for a minute on every check. ```ts title=src/game.ts run import { director, earn, game, prefab, store, weapon } from "@netforge/engine"; export default game({ template: "survivor", style: "lowpoly", places: { field: { chunks: "places/field", spawn: [0, 1, 0] } }, field: { halfSize: 40, pillars: [[10, 10, 2], [-10, -10, 2]] }, players: { prefab: "style.lowpoly.hooded_adventurer", max: 4, weapon: "spark", magnet: 4 }, }); prefab("shade", { model: "style.lowpoly.ghost", health: 20, speed: 3.6, radius: 0.35, damage: 6, ai: "chase", drops: ["gem"] }); prefab("gem", { model: "style.lowpoly.gem_small", health: 1, speed: 0, kind: "pickup", value: 1 }); weapon("spark", { kind: "bolt", damage: 10, cooldown: 0.5, range: 9, speed: 16, count: 1, levels: [{ damage: "+25%" }, { count: 1 }, { cooldown: "-15%" }, { damage: "+25%" }, { pierce: 1 }], }); director({ start: 4, alive: [[0, 10], [60, 40], [180, 160]], rate: [[0, 1], [60, 3], [180, 8]], pool: [{ prefab: "shade", weight: 1 }], }); earn.rule("hundred_down", { when: "enemy.killed", n: 100, pay: 5, oncePer: "run" }); earn.stake({ min: 20, max: 200, multiple: 2 }); store.sku("revive", { coins: 50 }); ``` Played headless by two bots for 90 seconds on an Apple M2 Pro, this module scored 488 kills, fired `hundred_down` once per bot and cost 0.13 ms per tick. ## The declarations | Function | Declares | Notes | |---|---|---| | `game({ template, style, players, ... })` | The game: which template runs it, the art style, the players' prefab, starting weapon and limits, the field, the look. | Exactly one per file, as the default export. | | `prefab(id, { model, health, speed, ai, drops, ... })` | Anything that exists in the world: enemies, bosses, pickups, props. | `model` is an asset library id such as `style.lowpoly.ghost`. | | `weapon(id, { kind, damage, cooldown, levels, evolve })` | A weapon: survivor kinds (`nova`, `bolt`, `orbit`, `cone`, `chain`, `meteor`, `boomerang`, `aura`) or arena kinds (`blaster`, `beam`). | `levels` lists what each level changes. | | `upgrade(id, { stat, step, maxLevel })` | A passive offered at level-up. | Stats such as `speed`, `armor`, `area`, `amount`. | | `director({ alive, rate, health, pool, ... })` | Who spawns, how many and how tough over the run's clock. | Curves are numbers or `[seconds, value]` keyframes. | | `system(id, setup)` | Timed and event logic: `every`, `after`, `on(event)`, spawns, announcements. | Runs on the server's tick, never on a clock. | | `earn.rule(id, { when, n, pay, oncePer })` | When a run pays coins. | Required: at least one per game. | | `store.sku(id, { coins, grants })` | Something players can buy with coins: a revive, a cosmetic, a character. | Play money in local play. | | `achievement`, `mission`, `title`, `keeper` | Reasons to come back: unlocks, daily goals, end-screen titles, playable characters. | None of them pay coins. | Board genres add their own typed declarations to the same module, for example `towerBoard`, `tower` and `waves` for tower defense, and `card` and `cardTable` for the card battler. The full list is in the [@netforge/engine reference](https://forge.netnet.capital/docs/reference/engine.md). ## Errors written for the person or agent fixing them Every declaration is validated with Zod when the module loads. A bad one throws `GameDefinitionError` with one line per problem that names the field and the fix: ```text model: reference assets by library id (e.g. "style.lowpoly.bat"), never by URL or path ``` Cross-references are checked when the manifest is compiled: a director pool entry that names a missing prefab, a weapon evolution whose passive does not exist, or an arena weapon in a survivor game each fail with a line the model can act on. The aim is to fail at definition time rather than at tick 3,000. ## Why a declarative file - **It is safe to hand to an agent.** The file cannot reach the renderer, netcode or physics, so an edit can make the game harder or add an enemy but cannot break multiplayer. - **The server can trust it.** The same declarations run on the server, which decides every outcome. The client never computes a result the server accepts on faith. - **It can be tested without a browser.** `engine.test.run` plays the declared game with bots and reports what happened. See [Testing with playtest bots](https://forge.netnet.capital/docs/concepts/playtest-bots.md). - **It is small.** The survivor template's full game, with 17 enemy types, 15 weapons and evolutions, 8 passives and a boss, is 354 lines. ## Units Seconds, metres, metres per second and hit points. The simulation ticks at 30 Hz. In the survivor template players run at 7 m/s, so chasers sit at 3 to 4 m/s and fast enemies stay under 6. ## FAQ ### Can src/game.ts import other npm packages? No. The play build rejects any import other than `@netforge/engine`, with the message `src/game.ts may import only "@netforge/engine"`. This keeps a game portable between local play and the hosted service and keeps it replayable on the server. ### Is the game API typed? Yes. Every declaration has TypeScript types generated from its Zod schema (`PrefabInput`, `WeaponInput`, `DirectorInput` and so on), so editors and agents get completion and type errors before anything runs. ### How do I add logic that is not a number, like a boss at ten minutes? Use a `system`: `after(600, () => spawnEdge("boss_id", { count: 1 }))` inside its setup spawns a boss just off screen at ten minutes. Systems run on the server's fixed tick. ### What are earn rules? Declarations of when a run pays coins, such as "100 enemies killed, pay 5 once a run". In local play coins are play money. On the hosted service the same rules can pay a game's own token, decided by the server. ### Where do models come from? From the CC0 asset library, by id (`style.lowpoly.ghost`). See [Assets and art styles](https://forge.netnet.capital/docs/concepts/assets-art-styles.md). --- # Assets and art styles: CC0 models by id, six looks per game > Net Forge games reference models and sounds from a CC0 asset library by id, such as style.lowpoly.ghost, never by URL. Every template draws in six art styles (lowpoly, toon, ps1, pixel, voxel, handdrawn), and an id resolves to that style's own model when the style has one. 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/concepts/assets-art-styles Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. A Net Forge game names its models and sounds by library id, such as `style.lowpoly.ghost`, never by URL or file path. The ids come from `@netforge/asset-library`, a set of CC0 models and sounds with their licences verified per pack, and every template can draw the same game in six art styles. ```ts title=src/game.ts import { prefab } from "@netforge/engine"; prefab("shade", { model: "style.lowpoly.ghost", health: 20, speed: 3.6, radius: 0.35, damage: 6, ai: "chase" }); ``` ## The six art styles `game({ style })` picks one of `lowpoly`, `toon`, `ps1`, `pixel`, `voxel` or `handdrawn`. The template draws everything in it. | Style | How it is drawn | Own model set | |---|---|---| | `lowpoly` | The templates' own drawing: flat-shaded low-poly models. | yes | | `ps1` | The late-1990s console look: about 240 lines, vertices snapped to the pixel grid, affine textures, 15-bit colour with an ordered dither. | yes | | `voxel` | Chunky cube-built worlds and characters, with cube particles. | yes | | `pixel` | A low-resolution render snapped to a 16-colour palette with a 4x4 dither, so 3D ground and sprites read as one piece of pixel art. | yes | | `toon` | Cel-shaded cartoon rendering over the low-poly models. | no | | `handdrawn` | Ink and watercolour: models repainted in washes, with pen lines drawn from the scene's depth, on paper. | no | An id written for one style draws as another style's own model of the same name when that style has one: with `style: "voxel"`, `style.lowpoly.ghost` draws `style.voxel.ghost` if it exists. So a game written once can be reskinned by changing one word. ## What is in the library Counted from the library's manifests for this release: | Set | Entries | |---|---| | `lowpoly` models | 228 | | `ps1` models | 238 | | `voxel` models | 227 | | `pixel` sprites | 58 | | Sound effects | 17, one per game-feel cue (hit, kill, levelUp, coin, dash and more), plus one music loop | Entries are characters, creatures, vehicles, weapons, props, buildings, nature, food and pickups, each with a height, a body plan (biped, quadruped, flyer, blob, wheeled or static), a rig, search tags and a one-line description. Animated characters share canonical clips (idle, walk, run, jump, attack, shoot, hit, die) with fallbacks. ## Finding an id ```ts title=find.ts import { assetById, restyleId, search } from "@netforge/asset-library"; for (const hit of search("lowpoly", "ghost", 3)) console.log(hit); console.log(assetById("style.lowpoly.ghost")?.line); console.log(restyleId("style.lowpoly.ghost", "voxel")); // the voxel id when the voxel set has one ``` ## Licences Every model and sound in the library is CC0 1.0, from packs whose licence page is recorded in the manifest, with the bytes pinned by sha256. The packs come from creators such as Kenney and Quaternius; the full credit list is in the package's `NOTICE`. The engine and templates are MIT. ## Why ids instead of URLs - **The build can check them.** An id that is not in the library fails when the game loads, with a line that says so, instead of a broken model at runtime. - **Styles can swap them.** An id is a name, so the same game draws in any style that has the model. - **Agents cannot fetch arbitrary files.** A game cannot load a URL, so an edit cannot pull in an unlicensed or unsafe asset. Loading is handled by the template's `AssetLoader` (glTF with meshopt and KTX2). Until a model arrives, the template draws a fallback shape. `npm run play -- --library-url ` loads the library from a mirror instead of the installed copy. ## FAQ ### Can I use my own 3D models in a Net Forge game? Not from `src/game.ts`: game code references library ids only. Custom art means adding it to an asset library build, which is template-level work, not game-module work. ### Are the Net Forge assets free for commercial use? Yes. Every library asset is CC0 1.0, which places it in the public domain. Credit is not required, and the package's `NOTICE` credits the packs anyway. ### How do I change my game's art style? Change `style` in `game({ ... })`, for example from `"lowpoly"` to `"voxel"`, and run `npm run play` again. ### Does the library include sound? Yes: 17 CC0 sound effects, one per game-feel cue, and one CC0 music loop. The template's sound player loads them under the same cue names, and synthesized sounds play until a sample loads. --- # Testing a game with playtest bots > engine.test.run plays a declared Net Forge game headless with bots and reports kills, waves, levels, earn rules fired, softlocks, the server cost per tick and design metrics such as time to first threat. npm test runs it on your game folder; npm run check prints it as JSON for coding agents. 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/concepts/playtest-bots Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. `engine.test.run` plays your declared game with bots, headless, at full speed, and reports what happened: kills, deaths, waves, levels reached, which earn rules fired, whether the game softlocked, the server cost per tick and design metrics such as how long until the first threat. In a project made with `npm create netforge`, `npm test` runs it on your `src/game.ts`, and `npm run check` prints the report as JSON. ```sh npm test npm run check ``` No browser, server or network is involved, so it takes seconds and is the check a coding agent should run after every edit. ## What the report contains | Field | Meaning | |---|---| | `kills`, `deaths`, `wave`, `maxLevel` | What the bots achieved over the run. | | `runs.won`, `runs.lost` | Runs that ended in a win or a loss. | | `earned` | How many times each earn rule paid (play money). | | `earnEmitted` | Earn events the game emitted. | | `softlock` | `true` if 30 seconds of game time passed with no kill and no hit. | | `msPerTick`, `p99MsPerTick` | Server cost of one simulation tick, mean and 99th percentile. The budget at 30 Hz is 33 ms. | | `peakEnemies` | The most enemies alive at once. | | `fun` | Design metrics: seconds to the first threat and the first reward, level at 1, 3, 6 and 9 minutes, deaths in the first minute, share of spawns that appeared on screen, share of deaths that were telegraphed. | | `rules.checks` | For board genres: each rules check with a pass or fail line. | | `failed` | The names of assertions that failed. Empty means pass. | ## Write your own assertions ```ts title=test/playtest.ts import { engine } from "@netforge/engine"; const report = await engine.test.run({ ticks: 30 * 180, // three minutes at 30 Hz bots: 4, seed: 3, bot: "human", // reacts 200 ms late, kites, reads cards for a second assert: [ { name: "nobody down in the first minute", check: (r) => (r.fun?.deathsBeforeMinute ?? 0) === 0 }, { name: "levels come fast", check: (r) => r.maxLevel >= 5 }, { name: "an earn rule fired", check: (r) => r.earnEmitted > 0 }, { name: "no softlock", check: (r) => !r.softlock }, ], }); if (report.failed.length) throw new Error(`playtest failed: ${report.failed.join(", ")}`); ``` Import your game module before the call (for example `import "../src/game.ts";`) so its declarations are registered. `seed` makes a run repeatable: the same seed and the same game play out the same way (only the timing fields depend on the machine). ## Stress the server Pass `enemies` to hold a crowd at a fixed size and measure what one tick costs: ```ts title=test/stress.ts import { engine } from "@netforge/engine"; const r = await engine.test.run({ ticks: 30 * 30, bots: 4, seed: 4, enemies: 2000 }); console.log(`peak ${r.peakEnemies} enemies, ${r.msPerTick.toFixed(2)} ms/tick mean, p99 ${r.p99MsPerTick.toFixed(2)} ms`); ``` With the survivor template on an Apple M2 Pro under Node 22 this printed `peak 2000 enemies, 2.22 ms/tick mean, p99 5.21 ms`. ## The design rules the bots measure The engine ships a design playbook (`GAME-DESIGN.md` in `@netforge/engine`) built from measured play. Among its rules: the first reward within about 10 seconds, real danger by 0:30, nobody dies in the first minute, enemies never spawn on screen, and big hits are telegraphed for 0.5 to 1 second. The `fun` metrics in the report check these, so a change that makes the opening empty or unfair shows up as numbers, not as a feeling after a play session. ## Bots in multiplayer The same bots fill empty seats in rooms that ask for them (the arena shooter's `players.bots`). Seat bots never earn: killing one scores and appears in the kill feed but pays no earn rule, and a "top of the round" rule pays only when another person was in the round. See [How to make a .io-style arena shooter](https://forge.netnet.capital/docs/guides/io-game.md). ## FAQ ### How do I test a multiplayer browser game without opening browsers? Run `npm test`. The playtest plays your game headless with several bots at full speed and reports kills, progress, earn rules fired, softlocks and the cost per tick. Use `npm run play -- --players 4` when you want to see it in real tabs. ### What does softlock mean in the report? That the run went 30 seconds of game time with nothing happening: no kill and no hit. It usually means enemies stopped spawning or cannot reach the players. ### Are playtest results repeatable? Yes. With the same `seed`, game and engine version, a run plays out the same way. The `msPerTick` timings depend on the machine. ### Can a coding agent read the results? Yes. `npm run check` prints the result as JSON: whether it passed, one line per problem, and the full report. --- # Compare Net Forge with other tools > When to pick Phaser, Colyseus, Godot, PlayCanvas, Babylon.js, three.js or boardgame.io, when to pick Net Forge, and how they fit together. 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/compare Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. Each page describes the other tool from its own docs, says which jobs it is the better choice for, and where Net Forge fits. Start with [3D multiplayer browser game engines compared](https://forge.netnet.capital/docs/compare/3d-multiplayer-engines.md) for a table of Babylon.js, PlayCanvas, three.js, Godot, Unity and Net Forge, or [Net Forge vs Phaser + Colyseus](https://forge.netnet.capital/docs/compare/phaser-colyseus.md) for the common 2D stack. - [3D multiplayer engines compared](https://forge.netnet.capital/docs/compare/3d-multiplayer-engines.md): Babylon.js, PlayCanvas, three.js, Godot web export, Unity Web and Net Forge side by side for a 3D multiplayer browser game: rendering, editor, physics, multiplayer and the backends each pairs with, language, license and AI-agent tooling, each cited from its own docs. - [Net Forge and Phaser](https://forge.netnet.capital/docs/compare/phaser.md): Phaser is a mature 2D framework for browser games with a very large example library. Net Forge is a 3D multiplayer engine with one simulation shared by client and server and ten finished multiplayer games to start from. - [Net Forge vs Phaser + Colyseus](https://forge.netnet.capital/docs/compare/phaser-colyseus.md): Phaser + Colyseus is a proven open-source stack for 2D multiplayer browser games, with the client and the server as two codebases you own. Net Forge is a 3D engine where one simulation runs on the server and in the browser, with ten finished multiplayer games to start from. - [Net Forge and Colyseus](https://forge.netnet.capital/docs/compare/colyseus.md): Colyseus is an open-source multiplayer server framework for Node.js with SDKs for many engines. Net Forge uses Colyseus for its rooms and transport and adds a shared simulation, a three.js renderer, a typed game API and ten finished games on top. - [Net Forge and Godot web export](https://forge.netnet.capital/docs/compare/godot-web-export.md): Godot is a free, open-source 2D and 3D engine with an editor that also exports to the web. Net Forge is a TypeScript engine made for the browser, with one simulation shared by client and server and ten finished multiplayer games. - [Net Forge and PlayCanvas](https://forge.netnet.capital/docs/compare/playcanvas.md): PlayCanvas is an open-source WebGL2 and WebGPU engine with a browser-based visual editor. Net Forge is a code-first TypeScript engine with one simulation shared by client and server and ten finished multiplayer games. - [Net Forge and Babylon.js](https://forge.netnet.capital/docs/compare/babylonjs.md): Babylon.js is an open game and rendering engine for the web with WebGPU support and a large Playground. Net Forge is a TypeScript engine with one simulation shared by client and server and ten finished multiplayer games. - [Net Forge and three.js](https://forge.netnet.capital/docs/compare/threejs.md): three.js is a general-purpose JavaScript 3D library, and Net Forge is built on it. Net Forge adds a simulation shared by client and server, netcode, physics, a typed game API and ten finished multiplayer games. - [Net Forge and boardgame.io](https://forge.netnet.capital/docs/compare/boardgame-io.md): boardgame.io is an open-source engine for turn-based games that turns move functions into a multiplayer game with secret state, phases, bots and a lobby. Net Forge offers a finished 3D card battler and three other board-style templates on an authoritative room. --- # 3D multiplayer browser game engines compared (2026) > Babylon.js, PlayCanvas, three.js, Godot web export, Unity Web and Net Forge side by side for a 3D multiplayer browser game: rendering, editor, physics, multiplayer and the backends each pairs with, language, license and AI-agent tooling, each cited from its own docs. 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/compare/3d-multiplayer-engines Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. For a 3D multiplayer browser game, Babylon.js, PlayCanvas and three.js are strong rendering foundations that pair with a multiplayer backend such as Colyseus, Photon or Socket.IO, while Godot and Unity are full editors whose web exports can carry their own networking. Net Forge is the one where a new project is already an authoritative multiplayer game: the server and the browser run one shared simulation, and ten finished games are ready to start from. ```sh npm create netforge@latest my-game -- --template arena --yes ``` Net Forge is the open-source multiplayer browser game engine built for AI coding agents, published on npm under `@netforge`. Every other tool on this page is described from its own documentation, checked on 2026-10-24. ## The table | | Babylon.js | PlayCanvas | three.js | Godot (web export) | Unity (Web) | Net Forge | |---|---|---|---|---|---|---| | Rendering | WebGL and WebGPU ([WebGPU](https://doc.babylonjs.com/setup/support/webGPU)) | WebGL2 and WebGPU ([GitHub](https://github.com/playcanvas/engine)) | WebGL and WebGPU renderers ([README](https://github.com/mrdoob/three.js)) | WebGL 2.0 through the Compatibility renderer ([Exporting for the Web](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)) | WebGL builds for the browser ([Web](https://docs.unity3d.com/Manual/webgl.html)) | three.js `WebGPURenderer` with a WebGL2 fallback | | Editor | [Babylon.js Editor](https://editor.babylonjs.com/) and the Playground | PlayCanvas Editor, in the browser | Code first; a browser [scene editor](https://threejs.org/editor/) | Godot editor | Unity Editor | Code first; one declarative `src/game.ts` | | Physics | Havok through the `HavokPlugin` ([Havok plugin](https://doc.babylonjs.com/features/featuresDeepDive/physics/havokPlugin)) | ammo.js rigid bodies ([Physics](https://developer.playcanvas.com/user-manual/physics/)) | Not included; you pick a library | Built in ([Physics](https://docs.godotengine.org/en/stable/tutorials/physics/index.html)) | Built in | Rapier (deterministic WebAssembly build), the same on client and server | | Multiplayer | Not included; Colyseus documents a Babylon.js tutorial and examples ([examples](https://docs.colyseus.io/learn/examples/babylonjs)) | Not included; official tutorials for Socket.IO, Colyseus and Photon ([tutorial](https://developer.playcanvas.com/tutorials/real-time-multiplayer/), [Colyseus](https://developer.playcanvas.com/tutorials/real-time-multiplayer-colyseus/), [Photon](https://developer.playcanvas.com/tutorials/real-time-multiplayer-photon/)) | Not included; Colyseus lists three.js example games ([examples](https://docs.colyseus.io/learn/examples/threejs)) | High-level multiplayer API built in ([docs](https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html)); on the web, WebSocket and WebRTC; Colyseus has a Godot SDK | Netcode for GameObjects works on Web ([Web networking](https://docs.unity3d.com/Manual/webgl-networking.html)), over WebSockets with Unity Transport 2 ([Relay with Netcode](https://docs.unity.com/relay/relay-and-ngo.html)); Colyseus has a Unity SDK | Built in: authoritative rooms (on Colyseus) stepping the shared simulation, with prediction, reconciliation and interpolation | | Language | JavaScript or TypeScript | JavaScript or TypeScript | JavaScript, with TypeScript types | GDScript; C# projects in Godot 4 cannot export to the web yet | C# | TypeScript | | License | Apache-2.0 ([npm](https://www.npmjs.com/package/@babylonjs/core)) | MIT ([npm](https://www.npmjs.com/package/playcanvas)) | MIT ([npm](https://www.npmjs.com/package/three)) | MIT ([license](https://godotengine.org/license/)) | Proprietary ([Unity legal](https://unity.com/legal)) | MIT; library assets CC0 | | AI-agent tooling | Not listed in its docs | PlayCanvas Editor MCP server ([docs](https://developer.playcanvas.com/user-manual/editor/mcp-server/)) | Not listed in its docs | Not listed in its docs | Unity MCP in the AI Assistant package, in beta ([docs](https://docs.unity3d.com/Packages/com.unity.ai.assistant@2.0/manual/unity-mcp-overview.html)) | `@netforge/mcp`, plus `AGENTS.md`, `CLAUDE.md`, `llms.txt` and `.mcp.json` in every new game | | Latest version | `@babylonjs/core` 9.32.0 | 2.23.3 | 0.186.1 | 4.7 docs | Unity 6.6 manual | `@netforge/engine` 0.2.0 | | Best for | Rich 3D scenes, games and apps, with a large Playground of samples | Visual, editor-driven 3D work for the web | Any 3D on the web, with every other layer your choice | Games built in an editor that ship to desktop, mobile and the web | Teams already in Unity who also want a web build | 3D online games that start from a finished multiplayer template, built in code with an agent | ## What each one does best - **Babylon.js** is a complete web rendering and game engine with Havok physics, an editor and the Playground for trying ideas in the browser. Pair it with Colyseus when you want an authoritative server; Colyseus documents a Babylon.js tutorial and a tech demo ([examples](https://docs.colyseus.io/learn/examples/babylonjs)). See [Net Forge and Babylon.js](https://forge.netnet.capital/docs/compare/babylonjs.md). - **PlayCanvas** gives you a browser-based visual editor on an open-source engine, and its own docs teach three multiplayer routes: build your own with Node.js and Socket.IO, or use Colyseus or Photon ([tutorial](https://developer.playcanvas.com/tutorials/real-time-multiplayer/)). Its Editor MCP server lets a coding agent change an open project ([docs](https://developer.playcanvas.com/user-manual/editor/mcp-server/)). See [Net Forge and PlayCanvas](https://forge.netnet.capital/docs/compare/playcanvas.md). - **three.js** is a general-purpose 3D library ([README](https://github.com/mrdoob/three.js)). It draws scenes beautifully and leaves the game loop, physics, entity model, input and networking to you, which is exactly right when you want to choose every layer. See [Net Forge and three.js](https://forge.netnet.capital/docs/compare/threejs.md). - **Godot** is a free, open-source editor for 2D and 3D games with a high-level multiplayer API built in. Its web export uses WebGL 2.0, and in the browser its networking runs over WebSocket and WebRTC, because browsers do not offer raw sockets ([Exporting for the Web](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)). See [Net Forge and Godot web export](https://forge.netnet.capital/docs/compare/godot-web-export.md). - **Unity** brings its editor, asset pipeline and C# to the web. Netcode for GameObjects can be used on the Web platform, and web clients connect over WebSockets ([Web networking](https://docs.unity3d.com/Manual/webgl-networking.html), [Relay with Netcode](https://docs.unity.com/relay/relay-and-ngo.html)). - **Net Forge** builds on three.js and adds the parts a multiplayer game needs: `@netforge/engine-sim` (a bitECS world with Rapier physics at a fixed tick), `@netforge/engine/net` (input commands, delta snapshots, prediction, reconciliation, interpolation), the three.js runtime in `@netforge/engine/runtime`, the typed game API in `@netforge/engine`, and ten finished games. ## Where Net Forge is different Net Forge is the only engine on this page where a new project is already an authoritative multiplayer game. The room host steps the same simulation the browser predicts with, so the rules exist once, in `src/game.ts`, and there is no separate server codebase to keep in sync. Every template ships with seat bots and a headless playtest, so a change can be checked with `npm test` before anyone plays it. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md) and [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). It is also the youngest tool here (0.2.0), it covers ten genres rather than every kind of game, it has no visual editor, and it targets the browser only. The public repository's room host is for play on your own computer and network; production rooms run on the hosted Net Forge service. ## FAQ ### What is the best engine for a 3D multiplayer browser game? It depends on what you want to own. If you want a finished, authoritative multiplayer game to change in code, start with Net Forge. If you want a visual editor, PlayCanvas (in the browser), Godot or Unity fit, with a backend such as Colyseus or Photon or their own networking. If you want to choose every layer, three.js or Babylon.js with Colyseus is a well-trodden path. ### Is three.js a game engine? three.js describes itself as a general-purpose 3D library ([README](https://github.com/mrdoob/three.js)). It renders; physics, an entity system, input and networking come from other libraries. Net Forge is three.js plus those layers, built for multiplayer. ### Which of these work with Colyseus? All of them can. Colyseus publishes examples or tutorials for three.js, Babylon.js, PlayCanvas and Phaser, and client SDKs for Unity and Godot among others ([Colyseus llms.txt](https://docs.colyseus.io/llms.txt)). Net Forge's own rooms are built on Colyseus. ### Can Godot or Unity web builds be multiplayer? Yes, within what browsers allow. Godot's web export supports WebSocket and WebRTC networking ([Exporting for the Web](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)), and Unity's Web platform supports Netcode for GameObjects while direct TCP and UDP sockets are not available in browsers ([Web networking](https://docs.unity3d.com/Manual/webgl-networking.html)). ### Which ones have tools for AI coding agents? PlayCanvas documents an Editor MCP server, Unity documents a Unity MCP server in beta, and Net Forge ships `@netforge/mcp` and agent instructions in every new game. For 2D, Phaser has a Phaser Game Agent MCP; see [Net Forge and Phaser](https://forge.netnet.capital/docs/compare/phaser.md). ### Is there a 2D version of this comparison? For 2D multiplayer, the common stack is Phaser with Colyseus. See [Net Forge vs Phaser + Colyseus](https://forge.netnet.capital/docs/compare/phaser-colyseus.md). --- # Net Forge and Phaser: when to use each > Phaser is a mature 2D framework for browser games with a very large example library. Net Forge is a 3D multiplayer engine with one simulation shared by client and server and ten finished multiplayer games to start from. 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/compare/phaser Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. Pick Phaser when you are making a 2D browser game and want the largest library of 2D examples and a long-established community. Pick Net Forge when you want a 3D multiplayer game in the browser that starts from a finished, server-authoritative template and is shaped so your own coding agent can change it safely. ```sh npm create netforge@latest my-game -- --template arena --yes ``` ## At a glance | | Phaser | Net Forge | |---|---|---| | What it is | A 2D game framework for web browsers ([Phaser docs](https://docs.phaser.io/phaser/getting-started/what-is-phaser)) | A multiplayer browser game engine plus ten genre templates | | Language | JavaScript or TypeScript | TypeScript | | Rendering | WebGL and Canvas | three.js, WebGPU with a WebGL2 fallback | | Multiplayer model | Your choice of server library (Phaser's overview page does not describe a networking layer) | Authoritative room host running the same simulation as the client | | License | MIT ([npm](https://www.npmjs.com/package/phaser)) | MIT | | Latest version (checked 2026-10-23) | 4.2.1, released 2026-07-09 | `@netforge/engine` 0.2.0 | | Best for | 2D games of every kind, including platform targets such as Discord Activities and YouTube Playables | 3D online games in ten genres, built and changed by people working with coding agents | ## When to pick Phaser - **Your game is 2D.** Phaser describes itself as "a 2D game framework" with WebGL and Canvas rendering across desktop and mobile browsers ([What is Phaser?](https://docs.phaser.io/phaser/getting-started/what-is-phaser)). Sprites, tilemaps, arcade physics and cameras are its home ground. Phaser 4 shipped in April 2026 (4.0.0 on npm on 2026-04-10, announced on 2026-04-23) with a WebGL renderer rebuilt from scratch around a new RenderNode architecture ([Phaser news](https://phaser.io/news/2026/04/phaser-4-renderer-faster-cleaner-and-built-for-modern-games), [release notes](https://github.com/phaserjs/phaser/releases/tag/v4.0.0)). - **You learn by example.** Phaser publishes an examples index for agents at [phaser.io/llms.txt](https://phaser.io/llms.txt); on 2026-10-23 it was about 400 KB of example entries, each with a version, folder, title and description. - **You want an agent service built around Phaser.** The Phaser Game Agent MCP connects Claude Code, Cursor, Copilot, Codex, Gemini CLI and Windsurf to Phaser's hosted game agent ([Phaser news, 2026-07](https://phaser.io/news/2026/07/phaser-game-agent-mcp-setup)). - **You target platforms beyond the open web.** Phaser's docs list YouTube Playables, Discord Activities and Twitch Overlays, and native apps through third-party wrappers ([What is Phaser?](https://docs.phaser.io/phaser/getting-started/what-is-phaser)). ## When to pick Net Forge - **You want multiplayer from the first minute.** Every template is already an online game: an authoritative host steps the world at a fixed tick (30 or 60 Hz, by template) and each browser predicts its own player with the same simulation code. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md). - **You want to start from a finished game.** Ten templates (survivor-like, arena shooter, tower defense, dungeon crawl, idle, platformer, card battler, monster collector, kart racer, party game) ship with netcode, rendering, touch controls, bots and a playtest. See [Templates](https://forge.netnet.capital/docs/templates.md). - **Your coding agent does the editing.** A game is one declarative file, `src/game.ts`, that may import only `@netforge/engine`. Bad declarations fail with one actionable line, and `npm run check` plays the game headless with bots and reports JSON. See [Let an AI agent build your game](https://forge.netnet.capital/docs/guides/build-a-game-with-an-ai-agent.md). - **You want 3D.** The renderer is three.js with WebGPU and a WebGL2 fallback, and the asset library has CC0 models in six art styles. ## Honest limits of Net Forge Net Forge is young (0.2.0). It covers ten genres, not every kind of game, and a 2D-first game with hand-drawn sprites and tilemaps is more natural in Phaser. Game code is restricted to the declarative API on purpose, so a mechanic the API does not describe needs a change to the template, not to `src/game.ts`. The public repository's room host is for playing on your own computer and network; the production room server is the hosted Net Forge service. ## Moving from Phaser | Phaser | Net Forge | |---|---| | `Phaser.Game` config | `game({ template, style, players, ... })` in `src/game.ts` | | Scene `create()` and `update()` | Declarations plus `system()` callbacks that run on the simulation tick | | Sprites and animations | Prefabs with a library model id, such as `"style.lowpoly.bat"` | | Arcade or Matter physics | Rapier physics inside `@netforge/engine-sim`, the same on client and server | | Your own server (often Socket.IO or Colyseus) | The template's authoritative room host | | Asset loader with file paths | Asset ids from the library, never URLs | A first step: pick the template closest to your game and port one enemy or item at a time. If your Phaser game already has a Colyseus server, [Net Forge vs Phaser + Colyseus](https://forge.netnet.capital/docs/compare/phaser-colyseus.md) maps both halves. ```sh npm create netforge@latest my-game -- --template platformer --yes cd my-game npm run play ``` ```ts title=src/game.ts import { prefab } from "@netforge/engine"; // A Phaser enemy sprite becomes a prefab with a library model and numbers. prefab("bat", { model: "style.lowpoly.bat", health: 8, speed: 5, radius: 0.25, damage: 4, ai: "chase" }); ``` ## FAQ ### Is Net Forge a Phaser alternative? For 3D multiplayer browser games, yes. For 2D games, Phaser is usually the better fit: it is built for 2D and has a far larger example library. For other 3D options, see [3D multiplayer browser game engines compared](https://forge.netnet.capital/docs/compare/3d-multiplayer-engines.md). ### Can I use Phaser and Net Forge together? Not in one game client: Net Forge templates draw with three.js. A Phaser game can still use other parts of the ecosystem, such as a Colyseus server. ### Does Phaser have multiplayer? Phaser's overview page describes rendering, input and platforms, not networking, so Phaser games pair with a server library. Colyseus, for example, documents a JavaScript client that works with browser frameworks ([Colyseus docs](https://docs.colyseus.io/llms.txt)). ### Is Net Forge free like Phaser? Yes. Both are MIT licensed. Net Forge's library assets are CC0. --- # Net Forge vs Phaser + Colyseus > Phaser + Colyseus is a proven open-source stack for 2D multiplayer browser games, with the client and the server as two codebases you own. Net Forge is a 3D engine where one simulation runs on the server and in the browser, with ten finished multiplayer games to start from. 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/compare/phaser-colyseus Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. Pick Phaser + Colyseus when your game is 2D, you want the most mature ecosystem of examples and tutorials for that pairing, and you want to own the client code and the server code as two separate projects. Pick Net Forge when your game is 3D and you want one simulation shared by the client and the server (so there is no second codebase for the server), ten finished multiplayer games to start from, authoritative rooms by default, and a project shaped for your own coding agent. ```sh npm create netforge@latest my-game -- --template survivor --yes ``` Net Forge is the open-source multiplayer browser game engine built for AI coding agents, published on npm under `@netforge`. Its rooms are built on Colyseus, so this page compares a stack you assemble with a stack that is already assembled, not two rival server frameworks. ## At a glance Checked 2026-10-24 against each project's own docs and npm. | | Phaser + Colyseus | Net Forge | |---|---|---| | Rendering | 2D | 3D (games can play on a plane, such as the side-view platformer) | | Renderer | Phaser 4, WebGL and Canvas; the WebGL renderer was rebuilt for Phaser 4 ([Phaser news, 2026-04-23](https://phaser.io/news/2026/04/phaser-4-renderer-faster-cleaner-and-built-for-modern-games)) | three.js 0.186.1, `WebGPURenderer` with a WebGL2 fallback | | Multiplayer model | Authoritative Colyseus rooms with schema state sync and matchmaking ([Colyseus llms.txt](https://docs.colyseus.io/llms.txt)) | Authoritative rooms (on Colyseus) that step a shared ECS and Rapier simulation and send delta snapshots | | Server code | You write the room in Node.js, separately from the Phaser client | None to write for a template: the room runs the same `src/game.ts` and `@netforge/engine-sim` the browser runs | | Prediction and reconciliation | Built into Colyseus 0.18: "the server stays authoritative, and the client predicts", and "you supply the deterministic step function" ([Netcode](https://docs.colyseus.io/netcode)); rewind for hits ([Lag compensation](https://docs.colyseus.io/netcode/lag-compensation)) | Built in: the step function is the shared simulation, and the client predicts its own player with it | | Physics | Phaser's Arcade and Matter physics on the client ([Arcade](https://docs.phaser.io/phaser/concepts/physics/arcade), [Matter](https://docs.phaser.io/phaser/concepts/physics/matter)); on the server, your choice | Rapier (deterministic WebAssembly build, 3D), the same on client and server | | Starting points | Colyseus's four-part Phaser tutorial (movement, interpolation, client-predicted input, fixed tick rate) and Phaser examples ([tutorial](https://docs.colyseus.io/learn/tutorial/phaser), [examples](https://docs.colyseus.io/learn/examples/phaser)) | Ten finished multiplayer games: survivor-like, arena shooter, tower defense, dungeon crawl, idle, platformer, card battler, monster collector, kart racer, party game | | AI-agent tooling | Phaser's examples index at [phaser.io/llms.txt](https://phaser.io/llms.txt) and the Phaser Game Agent MCP ([Phaser news, 2026-07](https://phaser.io/news/2026/07/phaser-game-agent-mcp-setup)); Colyseus publishes [llms.txt](https://docs.colyseus.io/llms.txt) | `@netforge/mcp` (scaffold, validate, playtest with bots, play locally), and `AGENTS.md`, `CLAUDE.md`, `llms.txt` and `.mcp.json` in every new game | | License | Both MIT ([phaser](https://www.npmjs.com/package/phaser), [colyseus](https://www.npmjs.com/package/colyseus)) | MIT; library assets CC0 | | Latest version | `phaser` 4.2.1 (2026-07-09); `colyseus` 0.18.10 and `@colyseus/core` 0.18.23 (2026-10-22) | `@netforge/engine` 0.2.0 | | Hosting | Any Node.js host, or Colyseus Cloud for deployment, scaling and monitoring ([llms.txt](https://docs.colyseus.io/llms.txt)) | `npm run play` runs a local room host on your computer and network; production rooms run on the hosted Net Forge service | ## When Phaser + Colyseus is the right pick - **The game is 2D.** Phaser describes itself as a 2D game framework ([What is Phaser?](https://docs.phaser.io/phaser/getting-started/what-is-phaser)), and sprites, tilemaps, cameras and Arcade physics are its home ground. Phaser 4 was published on npm as 4.0.0 on 2026-04-10 and announced on 2026-04-23 with a WebGL renderer "rebuilt from scratch" around a new RenderNode architecture ([Phaser news](https://phaser.io/news/2026/04/phaser-4-renderer-faster-cleaner-and-built-for-modern-games), [release notes](https://github.com/phaserjs/phaser/releases/tag/v4.0.0)). - **You want a well-documented path for the pair.** Colyseus maintains a Phaser tutorial that walks from basic movement to client-predicted input and a fixed tick rate ([Colyseus Phaser tutorial](https://docs.colyseus.io/learn/tutorial/phaser)). - **You want to own both sides.** The client and the server are separate projects, so you choose the server's structure, its state schema and its deployment, and you can reuse the server with clients in other engines through Colyseus's many SDKs. - **You want prediction you tune yourself.** Colyseus 0.18 provides the prediction, reconciliation and rewind loops and asks you for the deterministic step ([Netcode](https://docs.colyseus.io/netcode), [Migrating to 0.18](https://docs.colyseus.io/migrating/0.18)). ## When Net Forge is the right pick - **The game is 3D.** The renderer is three.js with WebGPU and a WebGL2 fallback, and the asset library has CC0 models in six art styles. - **You do not want a second codebase for the server.** The rules live in one declarative `src/game.ts`. The room host and the browser both run it on `@netforge/engine-sim`, so the server is authoritative and the client predicts with the same code. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md). - **You want to start from a finished multiplayer game.** Every template already has netcode, rendering, touch controls, seat bots and a headless playtest. See [Templates](https://forge.netnet.capital/docs/templates.md). - **Your coding agent does the editing.** Mistakes in `src/game.ts` fail with one actionable line, and `npm test` plays the game with bots and reports what happened. See [Let an AI agent build your game](https://forge.netnet.capital/docs/guides/build-a-game-with-an-ai-agent.md). ## Hundreds or thousands of enemies The survivor-like template (EMBERWATCH) is built for a big swarm: the director caps the living crowd at 2,000 enemies, the client draws each crowd as instanced meshes in a handful of draw calls, and the room sends each player only what is near them (the local host caps a survivor snapshot at 250 enemies within 40 m). We measured the server side on 2026-10-24 with the template's bench script, which holds the crowd at 2,000 and plays four bots through `engine.test.run` on the server simulation, 30 seconds per run at 30 Hz, three seeds: | Crowd | Mean ms per tick | p99 ms per tick | |---|---|---| | 2,000 enemies, the template's enemy mix | 1.32 to 1.49 | 2.63 to 4.41 | | 2,000 enemies, every non-boss role at once (`--mix 1`) | 1.70 to 2.14 | 3.49 to 7.05 | The budget at 30 Hz is 33 ms per tick. The machine was an Apple M2 Pro (10 cores, 16 GB) on Node 22.22.3. These numbers are the simulation step with four bots, run in one process; they do not include snapshot encoding or the network. The template does not go above 2,000 living enemies, so we did not measure more. To repeat the measurement from a clone of the public repository: ```sh git clone https://github.com/netnetdev/net-forge cd net-forge pnpm install pnpm build pnpm --filter @netforge/template-survivor bench -- --repeats 3 --seconds 30 ``` With Phaser + Colyseus, a crowd of this size is something you design: Phaser 4 adds GPU-powered sprites for large counts ([Phaser 3 vs Phaser 4](https://phaser.io/news/2026/05/phaser-3-vs-phaser-4)), and on the server you decide how enemies move, how they are synced (Colyseus schema state or your own messages) and how much each client receives. See [How to make a survivor-like with thousands of enemies](https://forge.netnet.capital/docs/guides/horde-survivor-browser-game.md) for the template. ## Moving from Phaser + Colyseus | Phaser + Colyseus | Net Forge | |---|---| | Colyseus `Room` with `onCreate`, `onJoin`, `onMessage` | The template's room server, created for you; rules go in `src/game.ts` | | Colyseus Schema state (`@type` fields) | The simulation's state, sent as quantized delta snapshots | | `room.send("move", data)` from the client | Sequence-numbered input commands (move, facing, buttons) that the room applies on its tick | | Your deterministic step for prediction | `step(world, inputs)` from `@netforge/engine-sim`, shared by both sides | | Phaser scenes with `create()` and `update()` | Declarations (`game`, `prefab`, `weapon`, `director`) plus `system()` callbacks on the simulation tick | | Sprites and animations | Prefabs with a library model id, drawn by the three.js runtime in the template's client | | Arcade or Matter physics | Rapier bodies, triggers and raycasts in `@netforge/engine-sim` | | `matchMaker` | Locally, every tab joins the open room until it is full; on the hosted service, matchmaking and room links (`?room=`) | A first step: start the template nearest your game, play it in two tabs, then port one enemy or one rule at a time. ```sh npm create netforge@latest my-game -- --template survivor --yes cd my-game npm run play -- --players 2 ``` ```ts title=src/game.ts import { prefab } from "@netforge/engine"; // A Phaser enemy sprite with a Colyseus schema entry becomes one prefab. prefab("bat", { model: "style.lowpoly.bat", health: 8, speed: 5, radius: 0.25, damage: 4, ai: "chase" }); ``` See also [Net Forge and Phaser](https://forge.netnet.capital/docs/compare/phaser.md), [Net Forge and Colyseus](https://forge.netnet.capital/docs/compare/colyseus.md) and [3D multiplayer browser game engines compared](https://forge.netnet.capital/docs/compare/3d-multiplayer-engines.md). ## FAQ ### Can I use Net Forge for 2D? Partly. Net Forge always renders a 3D scene with three.js, but a game can play on a plane: the platformer template is a side-view game, the survivor-like plays on a flat field under a top-down camera, and the `pixel` art style draws 2D sprites as camera-facing billboards in a low-resolution, palette-snapped picture. There is no 2D-only renderer, tilemap editor or 2D physics engine (physics is Rapier 3D). For a game that is 2D through and through, Phaser is the better fit. ### Is Net Forge built on Colyseus? Its room layer is. The local room host in `@netforge/kit-play` runs `@colyseus/core`, and the browser connects with the Colyseus SDK. Net Forge sends its own binary delta snapshots over the room instead of schema state, because the shared simulation is the source of truth. ### Does Phaser + Colyseus have client prediction? Yes. Colyseus 0.18 documents client prediction with reconciliation, and server rewind for hits ([Netcode](https://docs.colyseus.io/netcode)), and its Phaser tutorial includes a client-predicted input chapter ([tutorial](https://docs.colyseus.io/learn/tutorial/phaser)). ### Do I write server code with Net Forge? Not for a template game. The rules in `src/game.ts` are what the room runs, so changing them changes the server. You write server-side code only if you fork a template to add a mechanic the declarative API does not describe. ### Can a Phaser client join a Net Forge room? No. Net Forge rooms expect the engine's own input commands and snapshots, and the templates draw with three.js. A Phaser client pairs with a Colyseus server you write. ### Which is better for hundreds of enemies? Both can do it. With Net Forge the swarm, its sync and its drawing are already built and measured (above). With Phaser + Colyseus you build that part yourself and keep full control of how it works. --- # Net Forge and Colyseus: when to use each, and using them together > Colyseus is an open-source multiplayer server framework for Node.js with SDKs for many engines. Net Forge uses Colyseus for its rooms and transport and adds a shared simulation, a three.js renderer, a typed game API and ten finished games on top. 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/compare/colyseus Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. Pick Colyseus when you want a multiplayer server framework for any engine, with rooms, matchmaking, state sync and managed hosting. Pick Net Forge when you want a complete browser game on top of that kind of room layer: Net Forge's own rooms are built on Colyseus, and it adds one simulation shared by client and server, a three.js renderer, ten finished multiplayer games and a game API made for your coding agent. ```sh npm create netforge@latest my-game -- --template arena --yes ``` ## At a glance | | Colyseus | Net Forge | |---|---|---| | What it is | "An open-source multiplayer framework for Node.js" for authoritative game servers ([Colyseus llms.txt](https://docs.colyseus.io/llms.txt)) | A browser game engine and ten genre templates, with rooms on Colyseus | | Language | TypeScript or JavaScript on the server; clients in many languages | TypeScript on both sides | | Rendering | None; bring any engine | three.js, WebGPU with a WebGL2 fallback | | Multiplayer model | Authoritative rooms, schema state sync, matchmaking, client prediction and server rewind in 0.18 ([Netcode](https://docs.colyseus.io/netcode)) | Authoritative rooms that step a shared ECS and Rapier simulation; the client predicts with the same code | | Client SDKs | TypeScript, Unity, Godot, Defold, GameMaker, Construct 3, Cocos Creator, Haxe, Flutter, Swift and more ([llms.txt](https://docs.colyseus.io/llms.txt)) | The browser | | License | MIT ([npm](https://www.npmjs.com/package/colyseus)) | MIT | | Latest version (checked 2026-10-23) | `colyseus` 0.18.10, `@colyseus/core` 0.18.23, both released 2026-10-22 | `@netforge/engine` 0.2.0 | | Best for | Multiplayer servers for any client engine | 3D browser games that start from a finished multiplayer template | ## When to pick Colyseus - **You already have a game client**, in Unity, Godot, Defold, GameMaker, Phaser or your own engine, and need a server. Colyseus documents client SDKs for all of these ([llms.txt](https://docs.colyseus.io/llms.txt)). - **You want the server framework and nothing else.** Rooms, matchmaking with lobby and queue rooms, and schema state sync with binary patches are its core ([llms.txt](https://docs.colyseus.io/llms.txt)). - **You want prediction you control.** Colyseus 0.18 runs prediction, reconciliation and rewind loops for you: "the server stays authoritative, and the client predicts", and "you supply the deterministic step function" ([Netcode](https://docs.colyseus.io/netcode)). - **You want managed hosting.** Colyseus Cloud provides deployment, scaling and monitoring for Colyseus apps ([llms.txt](https://docs.colyseus.io/llms.txt)). ## When to pick Net Forge - **You want the step function written for you.** In Net Forge that deterministic step is `@netforge/engine-sim`: a bitECS world with Rapier physics at a fixed tick (30 Hz by default), the same module on the server and in the browser. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md). - **You want a finished game, not a blank room.** Ten templates ship with rules, rendering, input, touch controls, seat-filling bots and a headless playtest. See [Templates](https://forge.netnet.capital/docs/templates.md). - **Your coding agent writes the game.** Game rules live in one declarative `src/game.ts` that may import only `@netforge/engine`, so an edit cannot break the netcode. See [The typed game API](https://forge.netnet.capital/docs/concepts/game-api.md). ## Using them together They already work together. Net Forge's client netcode includes a Colyseus room client (`@colyseus/sdk` in `@netforge/engine`), and the local room host in `@netforge/kit-play` is a Colyseus server (`@colyseus/core` 0.18.18 with `@colyseus/ws-transport` 0.18.4). Net Forge sends its own binary, delta-compressed snapshots over the Colyseus room instead of schema state, because the simulation, not a schema, is the source of truth. If you know Colyseus, the room lifecycle will feel familiar. What Net Forge adds sits on top: the shared simulation, the snapshot format, interpolation and prediction wired to that simulation, and the games. If your client is Phaser, see [Net Forge vs Phaser + Colyseus](https://forge.netnet.capital/docs/compare/phaser-colyseus.md). For other engines that pair with Colyseus, see [3D multiplayer browser game engines compared](https://forge.netnet.capital/docs/compare/3d-multiplayer-engines.md) for a roundup. ## Honest limits of Net Forge Net Forge is young (0.2.0) and browser only. It does not provide SDKs for other engines; for a Unity or Godot client, use Colyseus directly. The public repository's room host is meant for play on your own computer and network; it has no lag-compensation history and no run log. The production room server, with lag compensation, interest management and the referee, is the hosted Net Forge service. ## Moving from Colyseus | Colyseus | Net Forge | |---|---| | `Room` with `onCreate`, `onJoin`, `onMessage` | The template's room, created for you; rules go in `src/game.ts` | | Schema state (`@type` fields) | The simulation's state, sent as quantized delta snapshots | | Your deterministic step function for prediction | `step(world, inputs)` from `@netforge/engine-sim` | | `client.send("move", data)` | Sequence-numbered input commands, quantized so client and server see identical inputs | | `matchMaker` | Locally, every tab joins the open room until it is full; on the hosted service, matchmaking and room links (`?room=`) | A first step: start the template nearest your game and play it in two tabs to see the room behaviour. ```sh npm create netforge@latest my-game -- --template arena --yes cd my-game npm run play -- --players 2 ``` ## FAQ ### Is Net Forge built on Colyseus? Its room layer is. The local host in `@netforge/kit-play` runs `@colyseus/core`, and the browser connects with the Colyseus SDK. The simulation, snapshots, renderer and games are Net Forge's own. ### Does Colyseus have client prediction? Yes. Colyseus 0.18 documents client prediction with reconciliation and server-side rewind for hits ([Netcode](https://docs.colyseus.io/netcode)). Net Forge also predicts, using the shared simulation as the step function. ### Can I use a Net Forge template with a Unity or Godot client? No. Net Forge templates are browser games drawn with three.js. Colyseus is the better fit when the client is another engine. ### Which should I learn first? If you want to understand game servers in general, Colyseus is a good place to start, and it starts with `npm create colyseus-app@latest ./my-server` ([Create a server](https://docs.colyseus.io/getting-started/server)). If you want a playable online game to change today, start a Net Forge template. --- # Net Forge and Godot web export: when to use each > Godot is a free, open-source 2D and 3D engine with an editor that also exports to the web. Net Forge is a TypeScript engine made for the browser, with one simulation shared by client and server and ten finished multiplayer games. 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/compare/godot-web-export Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. Pick Godot when you want a full desktop editor, 2D and 3D in one engine, and the option to ship the same project to desktop, mobile and the web. Pick Net Forge when the browser is the main target and the game is online: it is TypeScript end to end, starts from a finished multiplayer template, and its rooms run the same simulation as the page. ```sh npm create netforge@latest my-game -- --template survivor --yes ``` ## At a glance | | Godot (web export) | Net Forge | |---|---|---| | What it is | "Your free, open-source game engine" for 2D and 3D, with an editor ([godotengine.org](https://godotengine.org/)) | A browser game engine plus ten multiplayer templates | | Language | GDScript, C# (desktop and mobile only in Godot 4), C++ through GDExtension | TypeScript | | Rendering on the web | WebGL 2.0 through the Compatibility renderer ([Exporting for the Web](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)) | three.js, WebGPU with a WebGL2 fallback | | Multiplayer on the web | WebSocket client and WebRTC; raw sockets are not available in browsers ([Exporting for the Web](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)) | Authoritative room host stepping the same simulation the client predicts with | | License | MIT ([Godot license](https://godotengine.org/license/)) | MIT | | Latest version (checked 2026-10-23) | 4.7.2 | `@netforge/engine` 0.2.0 | | Best for | Games that ship on several platforms and are built in an editor | Online games that live in the browser and are built in code, often with an agent | ## When to pick Godot - **You want an editor.** Godot is a complete visual editor with scenes and nodes for 2D and 3D ([godotengine.org](https://godotengine.org/)). - **You ship to many platforms.** The same project exports to desktop, mobile and the web, and even XR. - **You want Godot's 2D engine.** Godot has a dedicated 2D engine that works in pixel coordinates. - **Single-player or small web builds.** Godot 4.3 and later default to a single-threaded web export that needs no special server headers ([Exporting for the Web](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)). ## When to pick Net Forge - **The browser is the platform.** Net Forge is plain TypeScript and three.js, so it uses WebGPU where the browser has it and WebGL2 otherwise. Godot's web export targets WebGL 2.0 through its Compatibility renderer. - **The game is online.** One simulation, `@netforge/engine-sim`, steps the world on the room host and predicts it in each browser. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). - **You want to start from a finished game.** Ten templates already have netcode, bots, touch controls and a store. See [Templates](https://forge.netnet.capital/docs/templates.md). - **An agent does the editing.** Rules are one typed file, `src/game.ts`, checked by `npm run check` with headless bots. See [Let an AI agent build your game](https://forge.netnet.capital/docs/guides/build-a-game-with-an-ai-agent.md). ## Honest limits of Net Forge Net Forge has no visual editor and does not export to desktop or mobile apps; it runs in the browser. It is young (0.2.0) and covers ten genres. Game code may import only `@netforge/engine`, so new mechanics outside the declarative API need changes in the template. The public repository's room host is for play on your own computer and network; the production room server is the hosted Net Forge service. ## Moving from Godot | Godot | Net Forge | |---|---| | Scene tree and nodes | `game()` plus prefabs declared in `src/game.ts` | | GDScript `_process` and `_physics_process` | `system()` callbacks that run on the 30 Hz simulation tick | | `CharacterBody3D` and physics bodies | The Rapier character motor and bodies in `@netforge/engine-sim` | | High-level multiplayer (`MultiplayerAPI`, RPCs) | The template's authoritative room; clients send inputs, the room sends snapshots | | Imported `.glb` and `.tscn` resources | Library asset ids such as `"style.lowpoly.bat"`, in six art styles | | Export templates and HTML shell | `npm run play` builds the client; see [Deploy and host your game](https://forge.netnet.capital/docs/guides/deploy-and-host.md) | A first step: start the template nearest your game, then port one mechanic at a time into `src/game.ts`. ```sh npm create netforge@latest my-game -- --template dungeon --yes cd my-game npm run play ``` For a side-by-side table of the 3D options, see [3D multiplayer browser game engines compared](https://forge.netnet.capital/docs/compare/3d-multiplayer-engines.md). ## FAQ ### Can a Godot game run in the browser? Yes. Godot 4 exports to the web with the Compatibility renderer on WebGL 2.0. C# projects in Godot 4 cannot be exported to the web; Godot's docs point to Godot 3 for that ([Exporting for the Web](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)). ### Do Godot web exports need special server headers? Only the multithreaded export does. It needs cross-origin isolation (`Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp`) or the PWA option's workaround. The single-threaded export, the default since 4.3, does not. ### Is Net Forge a Godot alternative? For online games that live in the browser and are written in TypeScript, it can be. For an editor-driven game that ships to several platforms, Godot is the better fit. ### Can I reuse my Godot models? Not directly. Net Forge games reference models by library id. Use the closest library models, or see [Assets and art styles](https://forge.netnet.capital/docs/concepts/assets-art-styles.md) for how the library is organised. --- # Net Forge and PlayCanvas: when to use each > PlayCanvas is an open-source WebGL2 and WebGPU engine with a browser-based visual editor. Net Forge is a code-first TypeScript engine with one simulation shared by client and server and ten finished multiplayer games. 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/compare/playcanvas Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. Pick PlayCanvas when you want a mature 3D web engine with a browser-based visual editor, and when the project is a 3D app, product viewer or single-player game as much as an online one. Pick Net Forge when the project is an online game you want to start from a finished multiplayer template and change in code, with your own coding agent. ```sh npm create netforge@latest my-game -- --template racer --yes ``` ## At a glance | | PlayCanvas | Net Forge | |---|---|---| | What it is | "An open-source game engine built on WebGL2 and WebGPU" for 3D apps, games and visualizations ([GitHub](https://github.com/playcanvas/engine)) | A browser game engine plus ten multiplayer templates | | Language | JavaScript or TypeScript | TypeScript | | Rendering | WebGL2 and WebGPU, glTF 2.0 with Draco and Basis compression | three.js, WebGPU with a WebGL2 fallback | | Physics | ammo.js rigid bodies | Rapier (deterministic WebAssembly build), the same on client and server | | Editor | PlayCanvas Editor, browser-based and visual | None; code and a declarative game file | | Multiplayer model | Your choice of networking library | Authoritative room host stepping the shared simulation | | License | MIT ([npm](https://www.npmjs.com/package/playcanvas)) | MIT | | Latest version (checked 2026-10-23) | 2.23.3, released 2026-10-15 | `@netforge/engine` 0.2.0 | | Best for | Visual, editor-driven 3D work for the web | Online games in ten genres, built in code | ## When to pick PlayCanvas - **You want a visual editor in the browser.** The PlayCanvas Editor is a browser-based visual editor ([GitHub](https://github.com/playcanvas/engine)). - **You want a general 3D engine.** PlayCanvas covers 2D and 3D graphics on WebGL2 and WebGPU, glTF asset streaming, ammo.js physics, positional audio and mouse, keyboard, touch and gamepad input. - **You work in React or with web components.** `@playcanvas/react` and `@playcanvas/web-components` give a declarative way to build scenes. - **Your project is not only a game.** Product configurators, visualizations and interactive scenes are a natural fit. ## When to pick Net Forge - **The game is online.** The room host and each browser run the same `@netforge/engine-sim` simulation; the browser predicts its own player and the host stays authoritative. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). - **You want a finished game to start from.** Ten templates ship with rules, rendering, input, bots and a headless playtest. See [Templates](https://forge.netnet.capital/docs/templates.md). - **Your coding agent writes the rules.** `src/game.ts` is declarative and validated, and `npm run check` plays it headless with bots. See [The typed game API](https://forge.netnet.capital/docs/concepts/game-api.md). ## Honest limits of Net Forge Net Forge has no visual editor and is young (0.2.0). It is built for games in its ten genres, not for general 3D apps. Game code may import only `@netforge/engine`. The public repository's room host is for play on your own computer and network; the production room server is the hosted Net Forge service. ## Moving from PlayCanvas | PlayCanvas | Net Forge | |---|---| | Entities with components in the Editor | Prefabs declared in `src/game.ts` | | Script components (`initialize`, `update`) | `system()` callbacks on the 30 Hz simulation tick | | Rigid bodies and collision components (ammo.js) | Rapier bodies and the character motor in `@netforge/engine-sim` | | Assets uploaded to the project | Library asset ids such as `"style.lowpoly.bat"` | | Your networking layer | The template's authoritative room | A first step: start the template nearest your game and play it in two tabs. ```sh npm create netforge@latest my-game -- --template racer --yes cd my-game npm run play -- --players 2 ``` For a side-by-side table of the 3D options, see [3D multiplayer browser game engines compared](https://forge.netnet.capital/docs/compare/3d-multiplayer-engines.md). ## FAQ ### Is PlayCanvas open source? Yes. The PlayCanvas engine is MIT licensed ([npm](https://www.npmjs.com/package/playcanvas)). ### Does PlayCanvas support WebGPU? Yes. Its README describes the engine as built on WebGL2 and WebGPU ([GitHub](https://github.com/playcanvas/engine)). Net Forge also uses WebGPU, through three.js, with a WebGL2 fallback. ### Can I use the PlayCanvas Editor with Net Forge? No. Net Forge templates draw with three.js and are set up in code. The two do not share a scene format. ### Which one is better for a multiplayer game? If you want to choose and wire your own networking, PlayCanvas gives you the engine and leaves that choice open. If you want the multiplayer already working in a finished game, start from a Net Forge template. --- # Net Forge and Babylon.js: when to use each > Babylon.js is an open game and rendering engine for the web with WebGPU support and a large Playground. Net Forge is a TypeScript engine with one simulation shared by client and server and ten finished multiplayer games. 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/compare/babylonjs Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. Pick Babylon.js when you want a full-featured 3D rendering and game engine for the web, backed by a large Playground of samples and a long history of WebGPU support. Pick Net Forge when you want an online game that starts from a finished multiplayer template, with the server and the browser running the same simulation, and that your coding agent can change safely. ```sh npm create netforge@latest my-game -- --template arena --yes ``` ## At a glance | | Babylon.js | Net Forge | |---|---|---| | What it is | "A powerful, beautiful, simple, and open game and rendering engine packed into a friendly JavaScript framework" ([GitHub](https://github.com/BabylonJS/Babylon.js)) | A browser game engine plus ten multiplayer templates | | Language | JavaScript or TypeScript | TypeScript | | Rendering | WebGL and WebGPU (`WebGPUEngine`, available since 5.0 in May 2022, per [the docs](https://doc.babylonjs.com/setup/support/webGPU)) | three.js, WebGPU with a WebGL2 fallback | | Multiplayer model | Your choice of networking library | Authoritative room host stepping the shared simulation | | License | Apache-2.0 ([npm](https://www.npmjs.com/package/@babylonjs/core)) | MIT | | Latest version (checked 2026-10-23) | `@babylonjs/core` 9.32.0, released 2026-10-22 | `@netforge/engine` 0.2.0 | | Best for | Rich 3D scenes, games and apps on the web, and native apps through Babylon Native | Online games in ten genres, built in code | ## When to pick Babylon.js - **You want one engine for rendering and game features.** Babylon.js calls itself a game and rendering engine, with a [Playground](https://playground.babylonjs.com/) where you "play directly with the Babylon.js API" and learn from its samples ([GitHub](https://github.com/BabylonJS/Babylon.js)). - **You want WebGPU with a long track record.** WebGPU support has been available since Babylon.js 5.0 ([WebGPU support](https://doc.babylonjs.com/setup/support/webGPU)). - **You want to reach native apps.** The docs cover building native apps with Babylon.js under cross-platform ([Babylon.js docs](https://doc.babylonjs.com/)). - **Your project is a 3D app as much as a game**, such as a visualization or a configurator. ## When to pick Net Forge - **The game is online.** `@netforge/engine-sim` steps the world on the room host and predicts it in each browser with the same code. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md). - **You want to start from a finished game.** Ten templates ship with netcode, bots, touch controls and a playtest. See [Templates](https://forge.netnet.capital/docs/templates.md). - **Your coding agent writes the rules.** `src/game.ts` is declarative, validated with one actionable line per mistake, and checked by `npm run check`. See [The typed game API](https://forge.netnet.capital/docs/concepts/game-api.md). ## Honest limits of Net Forge Net Forge is young (0.2.0), browser only and built for ten game genres rather than general 3D apps. Game code may import only `@netforge/engine`. The public repository's room host is for play on your own computer and network; the production room server is the hosted Net Forge service. ## Moving from Babylon.js | Babylon.js | Net Forge | |---|---| | `Engine` and `Scene` setup | Done by the template; you declare `game({ template, style, ... })` | | Meshes loaded with `SceneLoader` | Prefabs with library asset ids such as `"style.lowpoly.bat"` | | `scene.onBeforeRenderObservable` game logic | `system()` callbacks on the 30 Hz simulation tick | | Physics plugin bodies | Rapier bodies and the character motor in `@netforge/engine-sim` | | Post-processing pipelines | `game({ look: { preset, bloom, fog, vignette } })` | | Your networking layer | The template's authoritative room | A first step: start the template nearest your game and port one mechanic at a time. ```sh npm create netforge@latest my-game -- --template arena --yes cd my-game npm run play ``` For a side-by-side table of the 3D options, see [3D multiplayer browser game engines compared](https://forge.netnet.capital/docs/compare/3d-multiplayer-engines.md). ## FAQ ### Is Babylon.js free? Yes. Babylon.js is open source under the Apache-2.0 license ([npm](https://www.npmjs.com/package/@babylonjs/core)). Net Forge is MIT licensed. ### Does Babylon.js support WebGPU? Yes, through `WebGPUEngine`, since version 5.0 ([WebGPU support](https://doc.babylonjs.com/setup/support/webGPU)). ### Can I use Babylon.js with Net Forge? Not inside a Net Forge template, which draws with three.js. You can still use Net Forge's simulation package, `@netforge/engine-sim`, which has no rendering dependency, under your own renderer. ### Which one is better for a multiplayer game? Babylon.js leaves networking to you. If you want multiplayer already working in a finished game, start from a Net Forge template. --- # Net Forge and three.js: what Net Forge adds on top > three.js is a general-purpose JavaScript 3D library, and Net Forge is built on it. Net Forge adds a simulation shared by client and server, netcode, physics, a typed game API and ten finished multiplayer games. 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/compare/threejs Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. Net Forge is three.js plus the game engine, physics, entity system and authoritative multiplayer you would otherwise build yourself: `@netforge/engine-sim` (a bitECS entity system with Rapier physics at a fixed tick), `@netforge/engine/net` (input commands, delta snapshots, prediction and reconciliation), and the room host in `@netforge/kit-play`, all under a three.js renderer (`three` 0.186.1, with `WebGPURenderer` and a WebGL2 fallback). Use three.js on its own when you want full control of a 3D scene; use Net Forge when you want a multiplayer game on three.js where the simulation, netcode, physics and a finished game are already done. ```sh npm create netforge@latest my-game -- --template survivor --yes ``` ## At a glance | | three.js | Net Forge | |---|---|---| | What it is | "An easy-to-use, lightweight, cross-browser, general-purpose 3D library" ([README](https://github.com/mrdoob/three.js)) | A game engine and ten multiplayer templates that render with three.js | | Language | JavaScript, with TypeScript types | TypeScript | | Rendering | WebGL and WebGPU renderers, with SVG and CSS3D as addons | three.js `WebGPURenderer`, falling back to WebGL2 | | Game loop, physics, netcode | Not included; bring your own | Included: fixed tick (30 or 60 Hz), Rapier physics, authoritative rooms, prediction | | License | MIT ([npm](https://www.npmjs.com/package/three)) | MIT | | Latest version (checked 2026-10-23) | 0.186.1, released 2026-09-24 | `@netforge/engine` 0.2.0, on `three` 0.186.1 | | Best for | Any 3D on the web: scenes, visualizations, games, tools | Online games in ten genres | ## What Net Forge adds to three.js | Layer | Package | What it does | |---|---|---| | Simulation | `@netforge/engine-sim` | A bitECS world with Rapier physics at a fixed tick, no DOM and no clock, so the same code runs in Node and the browser | | Netcode | `@netforge/engine/net` | Sequence-numbered inputs, quantized delta snapshots, prediction, reconciliation and interpolation | | Rendering stack | `@netforge/engine/runtime` | `createRenderStack` (lighting rig, bloom, colour grade, fog, quality tiers, GPU particles), instanced and vertex-animated crowds for thousands of enemies | | Game feel | `@netforge/engine/runtime` | `FeelKit`: hit-stop, screen shake, hit flash, damage numbers, procedural audio cues, rumble | | Game API | `@netforge/engine` | `game`, `prefab`, `weapon`, `system`, `earn`, `store`: declarative and validated | | Games | `@netforge/template-*` | Ten finished multiplayer games | ## When to use three.js on its own - **Your project is not a game, or not this kind of game.** three.js is general purpose: product viewers, data visualization, art and any scene graph you can imagine. - **You want to choose every layer yourself**, including physics, networking and the game loop. - **You want the largest pool of examples.** The [three.js examples](https://threejs.org/examples/) cover most rendering techniques. ## When to use Net Forge - **You want one simulation on both sides.** The room host and the browser step the same `@netforge/engine-sim` world, so there is no second copy of the rules to keep in sync. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md). - **You want to start from a finished game.** See [Templates](https://forge.netnet.capital/docs/templates.md). - **Your coding agent writes the game.** See [The typed game API](https://forge.netnet.capital/docs/concepts/game-api.md). ## Honest limits of Net Forge Net Forge is young (0.2.0) and covers ten genres. Game code in `src/game.ts` may import only `@netforge/engine` and never touches three.js directly; scene code lives in the template's `client/` folder. The public repository's room host is for play on your own computer and network; the production room server is the hosted Net Forge service. ## Using the simulation under your own three.js scene `@netforge/engine-sim` has no rendering dependency, so you can step it under a scene you wrote yourself and draw its entities however you like. This runs one tick in Node or the browser: ```ts title=sim.ts import { initSim, createWorld, addPlayer, step, Button } from "@netforge/engine-sim"; await initSim(); // once per process (loads Rapier's WebAssembly) const world = createWorld({ tickRate: 30, seed: 7 }); addPlayer(world, 1); // One fixed tick with every player's input for that tick. const { tick, events } = step(world, new Map([[1, { seq: 1, tick: 0, moveX: 0, moveZ: 1, yaw: 0, buttons: Button.Fire }]])); console.log(tick, events.length); ``` For moving an existing scene over step by step, see [Migrating a three.js scene to Net Forge](https://forge.netnet.capital/docs/migrate/from-threejs-scene.md). For a plain three.js scene with Net Forge's netcode under it, see the runnable example in [How to add authoritative multiplayer to a three.js game](https://forge.netnet.capital/docs/guides/threejs-authoritative-multiplayer.md). For three.js next to Babylon.js, PlayCanvas, Godot and Unity, see [3D multiplayer browser game engines compared](https://forge.netnet.capital/docs/compare/3d-multiplayer-engines.md). ## FAQ ### Is Net Forge built on three.js? Yes. `@netforge/engine` depends on `three` 0.186.1 and renders with `WebGPURenderer`, which falls back to WebGL2. ### Is three.js a game engine? three.js describes itself as a general-purpose 3D library ([README](https://github.com/mrdoob/three.js)). It draws scenes; game loops, physics and networking come from other libraries. Net Forge is one way to add those. ### Can I write three.js code inside a Net Forge game? Not in `src/game.ts`, which may import only `@netforge/engine`. Scene code lives in the template's browser client, which you can read and fork. ### How does Net Forge draw thousands of enemies with three.js? With instanced meshes and vertex-animation crowds (`InstancedCrowd`, `AnimatedCrowd`, `VatCrowd`), so a whole crowd is one draw. The survivor-like template holds 2,000 enemies; see [Templates](https://forge.netnet.capital/docs/templates.md). --- # Net Forge and boardgame.io: when to use each > boardgame.io is an open-source engine for turn-based games that turns move functions into a multiplayer game with secret state, phases, bots and a lobby. Net Forge offers a finished 3D card battler and three other board-style templates on an authoritative room. 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/compare/boardgame-io Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. Pick boardgame.io when you want to describe a turn-based game as plain move functions and get multiplayer, secret state, phases, bots and a lobby around them, with a React or vanilla JavaScript client you draw yourself. Pick Net Forge when you want to start from a finished, drawn multiplayer card battler (or tower defense, idle or monster collector game) and change its cards and rules in one typed file. ```sh npm create netforge@latest my-game -- --template cards --yes ``` ## At a glance | | boardgame.io | Net Forge | |---|---|---| | What it is | An "Open Source Game Engine for Turn-Based Games" ([boardgame.io](https://boardgame.io/)) | A browser game engine with ten multiplayer templates, four of them board-style | | Language | JavaScript or TypeScript | TypeScript | | Rendering | Bring your own; vanilla JS client or React bindings | Finished 3D clients drawn with three.js | | Multiplayer model | State kept in sync across clients, server and storage; you write moves | Authoritative room validates each action; deterministic rules module with a seeded shuffle | | Hidden information | `playerView` strips secrets before state reaches a player ([Secret State](https://github.com/boardgameio/boardgame.io/blob/main/docs/documentation/secret-state.md)) | Hands and draft offers are sent only to their owner; the deck order stays on the server | | Bots | Automatically generated bots | Template AI opponents that fill an empty seat and never earn | | License | MIT ([npm](https://www.npmjs.com/package/boardgame.io)) | MIT | | Latest version (checked 2026-10-23) | 0.50.2, released 2022-11-10 | `@netforge/engine` 0.2.0 | | Best for | Any turn-based design you want to define from scratch | Card battlers and board-style games you start from a finished template | ## When to pick boardgame.io - **Your game is a new turn-based design.** You "write simple functions that describe how the game state changes when a particular move is made", and boardgame.io turns them into a playable game ([boardgame.io](https://boardgame.io/)). - **You want phases and turn orders as data.** It supports "different game rules and turn orders per phase". - **You want the state layer and nothing else.** It keeps state "in sync in realtime and across platforms", has a lobby for matchmaking and game creation, and lets you render with React or anything else. - **You want bots for free.** It generates bots that can play your game, which also helps when testing. ## When to pick Net Forge - **You want a finished card battler to start from.** INKWRIT, the card battler template, already has the duel, turns and the turn timer, a server-side shuffle, hidden hands, guards and rush keywords, a draft between duels, AI opponents, rendering and touch controls. See [Card battler template](https://forge.netnet.capital/docs/templates/card-battler.md). - **You want a 3D board and real-time touches.** Each player's cursor is predicted at 30 Hz while plays, attacks and turn ends are actions the room validates. - **Your coding agent edits the cards.** Cards and rules are declarations in `src/game.ts` (`card()`, `cardTable()`), validated by the build. See [The typed game API](https://forge.netnet.capital/docs/concepts/game-api.md). - **You want other board genres too.** Tower defense, idle and monster collector use the same deterministic rules seam. See [Templates](https://forge.netnet.capital/docs/templates.md). ## Honest limits of Net Forge Net Forge's board genres are the four in its templates. A new turn-based design that is not a card battler, tower defense, idle or monster collector game is closer to what boardgame.io is built for. Net Forge is young (0.2.0), and game code may import only `@netforge/engine`. The public repository's room host is for play on your own computer and network; the production room server is the hosted Net Forge service. ## Moving from boardgame.io | boardgame.io | Net Forge | |---|---| | `Game` object with `setup` and `moves` | `game({ template: "cards", ... })`, `cardTable()` and `card()` declarations | | `playerView` | Built in: the room sends hands and offers only to their owner | | `turn` and `phases` config | The template's duel and gauntlet structure, with a turn timer | | `ctx.random.Shuffle` | The room's seeded shuffle | | Generated bots | The template's AI scribes, in tiers set by `ai` | | Lobby | Room links in local play; matchmaking on the hosted service | A first step: start the card battler and play it against the AI. ```sh npm create netforge@latest my-game -- --template cards --yes cd my-game npm run play ``` For a side-by-side table of the 3D options, see [3D multiplayer browser game engines compared](https://forge.netnet.capital/docs/compare/3d-multiplayer-engines.md). ## FAQ ### Is boardgame.io still maintained? Its latest npm release is 0.50.2, published on 2022-11-10 (checked 2026-10-23 on [npm](https://www.npmjs.com/package/boardgame.io)). The library is MIT licensed and its docs remain online. ### How do I hide a player's hand in an online card game? In boardgame.io, return a stripped copy of the state from `playerView`. In Net Forge's card battler, the room keeps the deck order to itself and sends each hand only to its owner, so the other player's browser never receives it. ### Can I play against an AI in a Net Forge card game? Yes. A player alone in a room for three seconds gets an AI opponent, which leaves when a second person joins. AI opponents never earn. ### Does Net Forge support games with more than two players per table? The card battler seats two. The monster collector and idle templates seat up to eight, and tower defense up to four. --- # Migrate to Net Forge > Moving a browser game or its backend to Net Forge, step by step. 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/migrate Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Each page maps the concepts you already use to their Net Forge equivalents. - [From Hathora](https://forge.netnet.capital/docs/migrate/from-hathora.md): Hathora's game server hosting shut down on 2026-05-05. If your game is a browser game in TypeScript, this page shows what moves to Net Forge, what Net Forge does not replace, and the other options. - [From a Socket.IO prototype](https://forge.netnet.capital/docs/migrate/from-socket-io-prototype.md): A Socket.IO prototype relays events between browsers. Net Forge replaces the hand-written relay with an authoritative room that steps one shared simulation, so movement, hits and scores are decided in one place. - [From a three.js scene](https://forge.netnet.capital/docs/migrate/from-threejs-scene.md): Net Forge renders with three.js, so a three.js game moves over by splitting it in two: rules go into a declarative src/game.ts that runs on the server and the client, and drawing stays in the template's three.js client. --- # Migrating from Hathora to Net Forge > Hathora's game server hosting shut down on 2026-05-05. If your game is a browser game in TypeScript, this page shows what moves to Net Forge, what Net Forge does not replace, and the other options. 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/migrate/from-hathora Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Hathora closed its game server hosting platform on 2026-05-05, after announcing the closure on 2026-03-04 ([Gameye](https://gameye.com/gameye-vs-hathora/); [Supercraft](https://crux.supercraft.host/blog/game-backend-shutdowns-2026-who-died-where-studios-went/)). If your game runs in the browser and its server logic is TypeScript, Net Forge can take over the room: start from the template closest to your game, move your rules into `src/game.ts`, and run the same simulation on the room host and in the page. ```sh npm create netforge@latest my-game -- --template arena --yes cd my-game npm run play -- --players 2 ``` Both sources above are published by companies that sell game server hosting, so read their framing with that in mind. On 2026-10-23, `hathora.com` and `hathora.dev` answered with a permanent redirect (HTTP 301) to `gamefabric.com`. ## Who this page is for | Your game | Good next step | |---|---| | Browser game, TypeScript server logic, one of Net Forge's ten genres | This page | | Browser game, you want to keep your own server code | A room framework such as Colyseus ([Net Forge and Colyseus](https://forge.netnet.capital/docs/compare/colyseus.md)) | | Native client (Unity, Unreal, Godot) on dedicated servers | A dedicated server host; Gameye describes GameFabric by Nitrado as Hathora's recommended migration path ([Gameye](https://gameye.com/gameye-vs-hathora/)) | ## What maps to what | On Hathora you had | In Net Forge | |---|---| | A server process per room or match | A room on the room host, created when the first player joins | | Your authoritative game loop | `@netforge/engine-sim`'s fixed-tick `step` (30 Hz by default, 60 Hz in the arena, racer and platformer templates), driven by the template | | State you serialized and sent | Quantized delta snapshots, 20 a second in local play, encoded for you | | A room id or code shared with friends | A room link (`?room=`) on the hosted service; locally, every tab joins the open room | | Regions and fleet scaling | The hosted Net Forge service; the public repository's host runs on one machine | ## 1. Pick the template nearest your game Start from a finished game rather than an empty room. The ten ids are `survivor`, `arena`, `tower-defense`, `dungeon`, `idle`, `platformer`, `cards`, `collector`, `racer` and `party`. See [Templates](https://forge.netnet.capital/docs/templates.md). ```sh npm create netforge@latest my-game -- --template survivor --yes ``` ## 2. Move your rules into src/game.ts Port one entity or rule at a time as declarations. An enemy your server spawned becomes a prefab, and a timed event becomes a system: ```ts title=src/game.ts import { prefab } from "@netforge/engine"; prefab("raider", { model: "style.lowpoly.bat", health: 30, speed: 4, radius: 0.4, damage: 6, ai: "chase" }); ``` See [The typed game API](https://forge.netnet.capital/docs/concepts/game-api.md) for what the declarations cover. ## 3. Test it headless, then with people ```sh npm test npm run play -- --host 0.0.0.0 ``` `npm test` plays the game with bots and no browser. The second line lets phones and laptops on your network join through your computer. ## 4. Decide where it runs The public repository's room host (in `@netforge/kit-play`) is built for your own computer and network: one process, play money, no lag-compensation history. For a game on the internet, publish it through the hosted Net Forge service. See [Deploy and host your game](https://forge.netnet.capital/docs/guides/deploy-and-host.md). ## What Net Forge does not replace - **Arbitrary server code.** Net Forge rooms run Net Forge games; `src/game.ts` may import only `@netforge/engine`. If your server does work outside the game's rules, keep it in a service of your own. - **Native clients.** Net Forge is browser only. - **Self-hosted production fleets from the public repository.** The local host is not a production server. ## FAQ ### When did Hathora shut down? Hathora's game server hosting shut down on 2026-05-05, after an announcement on 2026-03-04, according to [Gameye](https://gameye.com/gameye-vs-hathora/) and [Supercraft](https://crux.supercraft.host/blog/game-backend-shutdowns-2026-who-died-where-studios-went/), both of which sell competing hosting. ### What is a Hathora alternative for browser games? For a browser game in one of Net Forge's ten genres, a Net Forge template gives you the server and the client together. To keep your own TypeScript server code, Colyseus is a room framework with managed hosting in Colyseus Cloud ([Colyseus docs](https://docs.colyseus.io/llms.txt)). ### Can I run Net Forge rooms on my own servers? The public repository's host runs on one machine and is meant for local and LAN play. The production room server is the hosted Net Forge service. ### Do I have to rewrite my client? Yes, if it is not a three.js game. Net Forge templates bring their own browser client; your work is mostly moving rules into `src/game.ts`. --- # Turning a Socket.IO multiplayer prototype into a Net Forge game > A Socket.IO prototype relays events between browsers. Net Forge replaces the hand-written relay with an authoritative room that steps one shared simulation, so movement, hits and scores are decided in one place. 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/migrate/from-socket-io-prototype Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0. Socket.IO is a library for "low-latency, bidirectional and event-based communication" between a client and a server, with rooms, broadcasting and automatic reconnection ([Socket.IO docs](https://socket.io/docs/v4/)), and it is a quick way to get two browsers talking. When a prototype outgrows hand-written events, move it to Net Forge: the room host steps one simulation, each browser predicts its own player with the same code, and your rules live in one declarative file. ```sh npm create netforge@latest my-game -- --template arena --yes cd my-game npm run play -- --players 2 ``` ## Why prototypes move A typical Socket.IO prototype broadcasts each player's position and lets every browser draw what it receives. That is the right way to start. As the game grows, the same questions come up: who decides a hit, how to stop a client from teleporting, how to hide the wait for the server, and how to keep two copies of the rules in step. Net Forge answers them with one design: | In the prototype | In Net Forge | |---|---| | `socket.emit("move", { x, y })` | Sequence-numbered input commands (move, aim, buttons), quantized so client and server read identical values | | `io.to(room).emit("state", players)` | Quantized delta snapshots per player, 20 a second in local play | | Rules in the client, the server, or both | Rules once, in the shared simulation, decided by the room | | Drawing whatever arrives | The local player predicted and reconciled; others interpolated | | `socket.join(roomId)` | A room per game. Locally every tab joins the open room; on the hosted service a link with `?room=` joins a specific one | | Reconnect handling | The room client's connection, on Colyseus | ## 1. Pick the template nearest your prototype | Prototype | Template id | |---|---| | Top-down shooter or `.io` arena | `arena` | | Co-op against waves of enemies | `survivor` | | Racing | `racer` | | Platform race | `platformer` | | Turn-based or card game | `cards` | | Party minigames | `party` | ```sh npm create netforge@latest my-game -- --template arena --yes ``` ## 2. Move rules, not messages You do not port your events. Port what they meant: the things in your world and what happens to them, as declarations in `src/game.ts`. ```ts title=src/game.ts import { prefab } from "@netforge/engine"; // What your server used to spawn and broadcast is now declared once. prefab("drone", { model: "style.lowpoly.bat", health: 20, speed: 4.5, radius: 0.35, damage: 5, ai: "chase" }); ``` ## 3. Check it with bots, then with people ```sh npm test npm run play -- --players 2 ``` `npm test` plays the game headless with bots and reports kills, rounds, softlocks and the cost per tick. The second line opens two tabs in the same room. ## When to keep Socket.IO Keep Socket.IO for the parts that are messages rather than simulation: chat, presence, notifications, dashboards or anything outside the game's rules. It also suits games whose state is small enough to send whole. Net Forge's `src/game.ts` may import only `@netforge/engine`, so side channels like chat belong in a service of your own. ## FAQ ### Is Socket.IO a WebSocket library? Socket.IO can use WebSocket as its transport, but its docs say it is "NOT a WebSocket implementation": it adds metadata to each packet, so plain WebSocket clients and Socket.IO servers do not interoperate ([Socket.IO docs](https://socket.io/docs/v4/)). ### What does Net Forge use for transport? Rooms run on Colyseus over WebSocket. The local host in `@netforge/kit-play` uses `@colyseus/core` with `@colyseus/ws-transport`, and the browser connects with the Colyseus SDK. ### Do I need to write prediction myself? No. The template predicts the local player with the shared simulation and reconciles against the room's snapshots. See [Netcode and prediction](https://forge.netnet.capital/docs/concepts/netcode-prediction.md). ### Can I keep my own server? Net Forge games run on Net Forge rooms. If your game logic must stay in your own server, a room framework such as Colyseus is the better step up from Socket.IO ([Net Forge and Colyseus](https://forge.netnet.capital/docs/compare/colyseus.md)). --- # Migrating a three.js scene to Net Forge > Net Forge renders with three.js, so a three.js game moves over by splitting it in two: rules go into a declarative src/game.ts that runs on the server and the client, and drawing stays in the template's three.js client. 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/migrate/from-threejs-scene Last updated: 2026-10-24. Checked against @netforge/engine 0.2.0. Net Forge renders with three.js (`three` 0.186.1, `WebGPURenderer` with a WebGL2 fallback), so moving a three.js game over is mostly a split: the rules go into `src/game.ts`, which the room host and the browser both run, and the drawing stays in a three.js client. Start from the template nearest your game and port one thing at a time. ```sh npm create netforge@latest my-game -- --template survivor --yes cd my-game npm run play ``` ## What maps to what | In your three.js game | In Net Forge | |---|---| | `requestAnimationFrame` loop that moves things | The simulation's fixed tick (30 or 60 Hz, set by the template); the client renders between ticks | | `Mesh` per enemy | A prefab with a library model id, drawn as an instanced crowd | | Your collision checks | Rapier bodies, triggers and raycasts in `@netforge/engine-sim` | | `GLTFLoader` with file paths | Library asset ids such as `"style.lowpoly.bat"` (glTF, meshopt and KTX2 under the hood) | | `EffectComposer` passes | `game({ look })`: preset, bloom, fog, vignette, tint | | Screen shake and hit effects by hand | `prefab({ feel })` and `weapon({ feel })` | ## 1. Pick a template and play it The template gives you the renderer, camera, input, netcode and HUD. Play it once so you know what you are changing. ```sh npm create netforge@latest my-game -- --template survivor --yes ``` ## 2. Move one enemy ```ts title=src/game.ts import { prefab } from "@netforge/engine"; prefab("wisp", { model: "style.lowpoly.bat", health: 8, speed: 5, radius: 0.25, damage: 4, ai: "chase", feel: { particles: "magic", color: "#7ad7ff", shake: 0.1 }, }); ``` Add it to the director's `pool` in the same file so it spawns. A model id the library does not have fails the build with one line that says so. ## 3. Bring your own drawing only where you need it Scene code lives in the template's browser client, not in `src/game.ts`. If you need custom drawing, fork the template package and work in its `client/` folder with `@netforge/engine/runtime` (`createRenderStack`, `FeelKit`, `InstancedCrowd`, `CameraRig`). If you want the simulation under a scene you already have, `@netforge/engine-sim` has no rendering dependency; see [Net Forge and three.js](https://forge.netnet.capital/docs/compare/threejs.md). The runnable example [`packages/engine/examples/threejs-scene`](https://github.com/netnetdev/net-forge/tree/main/packages/engine/examples/threejs-scene) is exactly that: a plain three.js scene whose keys become input commands for an authoritative room stepping `@netforge/engine-sim`. Run it from a clone of the public repository after `pnpm install` and `pnpm build`: ```sh pnpm --filter @netforge/engine example:threejs ``` [How to add authoritative multiplayer to a three.js game](https://forge.netnet.capital/docs/guides/threejs-authoritative-multiplayer.md#run-the-example) walks through it. ## 4. Test it ```sh npm test ``` ## FAQ ### Do I have to throw away my three.js code? Not the knowledge, and not shaders or effects you move into a forked template client. Game rules move into `src/game.ts`, because that file is what runs on the server. ### Can src/game.ts import three? No. It may import only `@netforge/engine`, which keeps the game something the room can run and replay without a renderer. ### Can I use my own models? Games reference models by library id. The library has CC0 models in six art styles; see [Assets and art styles](https://forge.netnet.capital/docs/concepts/assets-art-styles.md). ### Does my game get multiplayer for free? Yes, once its rules are in `src/game.ts`: every template is already an authoritative multiplayer game. Open the play link in a second tab to join the same room.