NET FORGE DOCS

Survivor-like co-op template (EMBERWATCH)

Last updated Checked against @netforge/engine 0.2.0View as Markdown

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

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.

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

PartWhat it doesWhere
Spawn directorSpawns every enemy just off screen on the run's clock, with curves for count, rate, health, damage and speeddirector({...}) in src/game.ts
Enemy roleschase, tank, runner, charger, ranged, bomber, shielder, healer, summoner, flee, splitters, elitesprefab(...) with ai
Weaponsnova, aura, bolt, orbit, cone, chain, meteor, boomerang; 5 to 8 levels each; evolutionsweapon(...)
Passivesspeed, maxHealth, magnet, might, haste, regen, armor, area, amount (up to 8)upgrade(...)
BossesTwo mini-bosses and a final boss with telegraphed attacksprefab(..., { boss })
Co-opShared XP nearby, downed state, revive by standing closeplayers in game({...})
NetcodeAuthoritative room at 30 Hz, your keeper predicted, delta snapshotsthe template package
Playtest botsPlays the game headless and reports kills, waves, levels and earnnpm 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.

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.

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.

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

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 and Simulation and ECS.

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.

Alternatives

  • Phaser 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 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.

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.