# Idle and incremental game template (BRINEWORKS)

> BRINEWORKS is a shared idle game for up to eight players: each runs a production chain on one salt flat, buys and levels plants, and everyone fills one contract together during a fixed-length shift. 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/idle-game  
Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0.

BRINEWORKS is a complete multiplayer idle game: up to eight players each run a works on one salt flat, where pumps draw brine, pans boil it into salt, kilns cut salt into crystals, and salt buys and levels more plants. Everyone ships crystals to one shared contract, and a shift lasts fifteen minutes before a fresh one starts.

```sh
npm create netforge@latest my-game -- --template idle --yes
cd my-game
npm run play
```

Space rakes salt by hand (or click your plot), keys 1 to 5 buy a plant, and S ships crystals. Open the link in more tabs to see several works on the same flat.

## What you get

| Part | What it does | Where |
|---|---|---|
| The flat | Shift length, how long a works runs after its last tend, raking, conversion ratios, the shared contract | `saltFlat({...})` |
| Plants | 1 to 8 plants of kind `pump`, `pan` or `kiln`, each with a cost, a price growth per level and a rate | `plant(...)` |
| Upgrades | Plant output (`might`), raking speed (`haste`), salt per rake (`amount`) | `upgrade(...)` |
| Shared goal | One contract barge everyone ships crystals to | `saltFlat({ contract })` |
| Fairness rules | No offline accrual, works stall when untended, a raking cooldown, a minimum time before the first payout | enforced by the build |
| Playtest bots | Four players for four minutes, plus a check of the fastest possible first payout | `npm test` |

Measured on an Apple M2 Pro, Node 22: four bots over four minutes (7,200 ticks at 30 Hz) packed 172 crates and each fired the first earn rule, at 0.003 ms per server tick on average.

## 1. Tune progress

`cost` is kg of salt for the first level, `growth` is the price rise per level, and `rate` is output per second per level (litres of brine for pumps, kg of salt for pans, crystals for kilns). A lower growth keeps levels cheap for longer.

```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 game needs at least one pump, one pan and one kiln. Give a new plant a label in `src/names.json`; the bar shows plants in the order you declare them.

## 2. Change the shift and the contract

```ts title=src/game.ts
import { saltFlat } from "@netforge/engine";

saltFlat({
  shift: 1200,
  tend: 120,
  start: 20,
  rake: { salt: 3, cooldown: 0.4 },
  brinePerSalt: 3,
  saltPerCrystal: 25,
  packEvery: 150,
  contract: { crystals: 120 },
});
```

A shift is 600 to 3,600 seconds, and `tend` is at most 300 seconds: a works nobody tends stops, so an idle tab makes nothing.

## 3. Add an earn rule

```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` (each credits the player) and `contract.filled`. The build simulates a fresh works with perfect play and with pure raking, and fails a rule that would pay sooner than 180 seconds in, printing the number. Raise `packEvery` or the rule's `n` if it does.

## 4. Check it with bots

```sh
npm test
```

## How it works

This is a board genre: the rules run as a deterministic module in `@netforge/engine/rules` on the room's clock, with integer state and a seeded random number. Production happens only on the room's ticks while a player is in the room. Nothing accrues offline, and a player who leaves and comes back starts a fresh works. That is a deliberate design choice for a shared, server-checked idle game, and it differs from single-player incremental games that keep progress while closed. See [Simulation and ECS](https://forge.netnet.capital/docs/concepts/simulation-ecs.md).

Earn rules fire locally and pay nothing. Paying them in a game token is an optional upgrade on the hosted Net Forge service.

The full guide is [Build an idle or incremental game](https://forge.netnet.capital/docs/guides/idle-game.md).

## Alternatives

- **[Phaser](https://docs.phaser.io/)** is a 2D HTML5 framework. Pick it for a single-player idle game with sprite art and your own save format.
- **A plain web stack** (a UI framework plus local storage) is often all a single-player clicker needs, and keeps offline progress simple.
- **Net Forge** fits when several players should share one world, fill one goal together, and have every number computed on the server.

## FAQ

### Does the idle template have offline progress?

No. Production runs only on the room's clock while you are in the room, and an untended works stalls after `tend` seconds. This keeps a shared world fair. For a single-player game with offline progress, a plain web stack is a better fit.

### How many players share one idle world?

Up to eight, each with their own works on the same flat, all shipping to one contract.

### How long is a round?

A shift is 600 to 3,600 seconds (the template uses 900). When it ends, a new shift starts from nothing.

### Why does the build reject my earn rule?

The build checks that no earn rule can pay sooner than 180 seconds into a fresh works, even with perfect play. Raise `packEvery` or the rule's `n`, or slow raking, and run `npm test` again.
