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