NET FORGE DOCS

Co-op dungeon crawler template (CRYPTWICK)

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.

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.

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

PartWhat it doesWhere
Procedural floor planRooms on a grid joined by corridors, from a seed; the same seed gives the same crypt on the server, every client and the playtestdungeon({...})
Walls and doorsWalls stop players, enemies and bolts; enemies behind a wall walk round through the doors on a flow fieldfield.walls, field.floors
The crawlWhich rooms wake, what they hold by depth, off-screen spawns inside the woken room, reliquaries, the respawn on clearthe crawl system in src/game.ts
CombatAuto-firing weapons, level-up cards, elites, a mini-boss and a boss with telegraphed attacksweapon(...), prefab(...)
PermadeathDowned for 12 s, crawl to a teammate, out until the party clears a roomplayers.downed
Netcode30 Hz authoritative room, your own character predictedthe template package
Playtest botsFour bots for four minutes: kills, rooms reached, levels, earnnpm 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.

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.

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

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

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.

The full guide is Make a dungeon crawler with procedural rooms in TypeScript.

Alternatives

  • Phaser is a 2D HTML5 framework with tilemaps and a large example library. Pick it for a 2D, single-player roguelike with tile art.
  • Godot 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.