# 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<number, number>();
  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.
