# Board minigames

Voxel Party's board party is one optional way to play: 2–4 friends (topped up with CPUs) take
turns on a board, and between turns everyone plays a minigame from the host's line-up. The
winner gets coins. A **board minigame** is a game with a `board` block in `game.json`; it can go
in party line-ups *and* be played in a session (timed runs, `vp docs sessions`).

Start one with `bunx vp init <id> --minigame` (the template is Star Catch, a complete board
minigame). `vp docs design` (sections 1–9) has what makes them fun.

## The manifest

```json
{ "id": "star-catch", "name": "Star Catch", "players": { "min": 2, "max": 4 }, "board": { "maxMs": 50000 } }
```
- `board.maxMs`: the server's hard stop, 3 000 – 300 000 ms. Set it to your round length plus
  5–10 s (the FINISH beat and the score reports need it), and end the game yourself before it.
- `players`: `min <= 2` and `max >= 4` (a party can be 2, 3 or 4, with CPUs in empty seats).
  `max` can be higher for sessions; in a party it's never more than 4.
- `input` must be `[]`: the board stays pick-up-and-play, so move + `action` only (no mouse
  aiming, no extra keys; a left click still counts as `action`).

## What a board minigame must do

- **Play every seat as a CPU.** Parties fill empty seats with CPUs, and `vp check` plays with
  CPUs only (`link.you` is null, `seats.you` is -1): the game must run and end with nobody at the
  keyboard.
- **Work with 2, 3 and 4 players.** Size everything by `players.length`, never a hard-coded 4.
- **End on its own and score everyone**, well within `maxMs`:
  - host-authoritative and discrete games: the host calls `flow.end(scores, headline)` with a
    score for every seat;
  - per-player games: each client calls `flow.finish(scores, headline)` with a score for the
    seats it owns and `NaN` for the rest.
  A round the server's hard stop has to end is a bug (`vp check` fails it).
- **Rounds of 30–90 s**, understood within 3 seconds of the title card, at most 3 controls.
- **Higher scores are better; ties share a place.** Payouts: 1st 10 coins, 2nd 5, 3rd 2, 4th 0.
- The roster is fixed for a board round (nobody joins mid-minigame; someone who drops is played by
  the host as a CPU). The same game in a session also has a fixed roster per run, so board
  minigames don't need the drop-in handling of `vp docs sessions` §3. Using `Seats` indices is fine.
- **Survive drops mid-round.** Who plays a seat can change while the round runs: a player whose
  connection drops turns `'cpu'` on the host (`seats.role(i) === 'bot'`), and when the host drops,
  another client becomes the host (`link.isHost` turns true) with every CPU seat. So never decide
  once, in a constructor, who simulates what:

  ```ts
  // ✗ both freeze the round (it waits for the hard stop), or crash on a seat with no CPU
  const host = link.isHost;
  const bots = link.players.map((_, i) => (seats.role(i) === 'bot' ? new Bot(i) : null));

  // ✓ ask every frame, and make a CPU when a seat first needs one
  if (link.isHost) simulate(dt);
  for (let i = 0; i < seats.count; i++) if (seats.role(i) === 'bot') (bots[i] ??= new Bot(i)).update(dt);
  ```

  A new host carries on from the world it last received (`HostSync` does: `vp docs netcode`), runs
  every rule the old host ran, and reports the scores. `vp check` plays two rounds where this
  happens.

## The frame

`defineGame({ round: 45, … })` gives the HUD timer; `flow.timeLeft` counts down from the
smaller of your round and `maxMs`. Title card ("Ready!") → 3-2-1-GO → play → FINISH → results
(coins), all owned by the platform. Your game only simulates while `flow.live`.

## `vp check` for board minigames

Typecheck, pack, source warnings, `bun test`, then 3 bots-only rounds in a muted headless
browser with 4, 2 and 3 players (`--seeds 6` for more; the counts keep cycling). Screenshots in
`.vp/check/`: `<n>p-seed-<s>-1-intro.png`, `2-play` (~4 s after GO), `3-late` (~19 s after GO),
`4-results`. It fails when a round errors, doesn't finish, or needs the hard stop; ⚠ when every
player tied (usually a soft lock). Then two rounds where someone drops, you on autopilot: a
player's connection drops a quarter of the way in (at most 10 s), so their seat turns `'cpu'`
here (`drop-player-*.png`), and one where the host drops and you become the host
(`drop-host-*.png`). Both must still end by themselves. Then a round with your seat on autopilot (`input.autopilot`:
your CPU plays you, the view is yours) and one on a phone, and every screenshot gets the visual
checks (`vp docs testing`). Look at every screenshot, for every player count. `bunx vp shot`
takes more pictures, and film strips of movement, whenever you want them.
`--long` then fast-forwards round after round (a new seed each, 3 to 20 rounds, about
`--minutes 20` of play) and reports what each round's frames cost and how big the game got:
see `vp docs sessions` §7.

## The quality bar

- 30–90 s, at most 3 controls, the blurb says the goal and the twist in one or two sentences.
- Your own actions respond on the same frame, online too.
- Bots play every seat competently, differ in skill, and sometimes make human mistakes.
- Every round ends cleanly, everyone is scored, the podium makes sense.
- A readable field, juice on every event, a camera that frames everyone.
- 8+ sounds and an original theme, rivals quieter than you.
- `vp check` all ✔ with no ⚠, screenshots looked at, well under 1 MB, 60 fps.
