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