# Adding a game to Boardgamers

Once you have an [engine](./engine-api.md) and a [viewer](./viewer-api.md) (see the [tictactoe tutorial](./tictactoe.md) to build them), an admin registers the game on the platform.

## Release checklist

Before making a version **Public**, check the built engine and viewer in BGS:

- [ ] **Languages:** translate the game UI, tooltips, rules explanations and journal text into every [BGS-supported locale](https://codeberg.org/boardgamers/boardgamers/src/branch/main/packages/models/locale.ts). Write and review translations directly, yourself or with other contributors/coding agents; do not use automated translation APIs. Apply live [`preferences.locale`](./viewer-api.md#interface-language) changes, including in replays, analyses and tutorials. Check text overflow and right-to-left layouts.
- [ ] **Protocol coverage:** audit both integrations against the current [engine](./engine-api.md) and [viewer](./viewer-api.md) contracts, including lifecycle, player/spectator state and live preference updates. Use the [protocol diagnostics](./protocol-library.md#validation-and-coverage); declare capabilities accurately. If a needed capability is missing, add it to the shared protocol and document it.
- [ ] **Replays and analysis:** verify deterministic [replay](./engine-api.md#replay), navigation back to the live position, and playable [analysis](./engine-api.md#createanalysis) from historical positions. Test every seat and spectators. Ongoing analyses must protect hidden information through [analysis scenarios](./engine-api.md#createanalysisscenario); disable them in unsupported phases.
- [ ] **Bounded history:** repeatedly editing a choice, undoing, reopening or reconfirming it must not grow saved history indefinitely without advancing the game. Coalesce tentative/replacement choices while keeping published log entries and indexes stable. Use [live updates, out-of-turn moves and time increments](./engine-api.md#isliveupdate) so revisions neither spam turn summaries nor repeatedly award time.
- [ ] **Pictogram and naming:** upload a recognizable BGS pictogram with a transparent background and tight bounds; remove empty margins and decorative borders. Keep the base game name as the main display title and edition details in the subtitle (`displayTitle.title` / `displayTitle.subtitle`).
- [ ] **Credits:** fill in the boardgame's Credits field with the designer(s) and publisher of the implemented game/edition, plus other applicable attributions.
- [ ] **Description and metadata:** write a few sentences describing the game's premise, objective and style of play. Complete the player counts, publication year and relevant links, including the publisher's website, source-code repository and BoardGameGeek page (`links.publisher`, `links.source`, `links.bgg`), plus a purchase link when available. Verify the information and URLs against the implemented game/edition.
- [ ] **Player rules:** fill in the game's CMS rules page (`<game>:rules`) with clear boardgame instructions: objective, turn flow, main actions, end conditions and scoring. Focus on how to play the boardgame. Keep it concise where the game UI already explains or guides the details; avoid a software manual or exhaustive explanations of UI-assisted steps. Provide translations under the language requirement above and verify that the Rules link opens the page.
- [ ] **Tags:** select accurate tags from the [shared boardgame tag list](https://codeberg.org/boardgamers/boardgamers/src/branch/main/packages/models/boardgame-tags.ts). If an important category or mechanic is missing, flag it in the release report and suggest a new shared tag rather than choosing an inaccurate substitute.
- [ ] **Player appearance:** apply [avatars](./viewer-api.md#avatars), host-resolved player colors and [Supporter badges](./viewer-api.md#preferences), including live changes. Show the badge beside the avatar or name. Preserve meaningful faction colors and never change engine identities or rules to recolor a player.
- [ ] **Color-blind mode:** declare and honor the shared [`colorBlind` preference](./viewer-api.md#color-blind-support). Provide readable ownership markers across the board, pieces and player panels; use host-supplied `preferences.bgs.playerSymbols` when present, with game defaults otherwise. Do not rely on color alone.
- [ ] **Chat:** provide an accessible entry point on desktop and mobile, for example beside the journal. Reuse the [shared protocol chat panel](./protocol-library.md#ready-made-panel) and its unread state, permissions and error handling. Enable the viewer's Chat capability and configure [integrated controls](./viewer-api.md#chat) to avoid duplicate shortcuts. Hide live chat in analysis mode.
- [ ] **Mobile usability:** verify touch controls and readable layouts on small screens, in portrait and landscape. Avoid scroll traps and competing nested scroll areas; players must be able to navigate the board, panels and page without getting stuck. Minimize scrolling during normal play by keeping the current choices and primary actions visible together where practical. Check that dialogs and the on-screen keyboard do not hide essential controls.
- [ ] **Publication:** verify the actual built viewer in a browser on desktop and mobile, save distinct [gameplay screenshots](#screenshot-gallery-and-publication), and upload the [complete viewer bundle](./viewer-api.md#publishing-a-viewer-with-multiple-files), including styles, translations and other assets. Commit and push the published source, version and dependency changes to the game's `main` or `master` branch.

## 1. Register the game

In the admin panel, **Boardgames → + New game**: set the game id + version, label, player counts, and any game options/preferences/settings — see [Game options, preferences & settings](./game-options.md) for the field shapes. Save.

## Screenshot gallery and publication

Save new games privately first. In the game's **Screenshot gallery**, upload gameplay images, add optional captions, arrange them with **Earlier / Later**, then **Save gallery**. The first image is featured on the boardgame page; players can open all images fullscreen. Up to eight screenshots are supported. Images are shared across versions. The featured screenshot also appears in the logged-out homepage cards. Choose distinct views of the actual interface rather than several similar board overviews.

For games with a light and dark interface, use **Add dark version** on each screenshot. The site selects the matching image for the player’s theme, including system mode. Leave it unset when the game looks the same in both modes. Both files belong to one gallery entry; the regular image is the fallback. Scripts can set `darkAsset: "screenshot-board-dark"` alongside `asset`.

At least one saved screenshot is required before setting a version **Public**. The API enforces this for scripted releases too. Published games must retain one image; remove an image from the gallery before deleting its asset.

For scripts, upload raw image bytes to `PUT /api/admin/gameinfo/:game/assets/screenshot-<name>` with an image content type, then save `{ "gallery": [{ "asset": "screenshot-<name>", "caption": "Board overview" }] }` to `PUT /api/admin/gameinfo/:game/gallery`. The array order controls presentation. Only images uploaded for that boardgame under a `screenshot-` asset key may be selected. These screenshot assets are public; configure anonymous `s3:GetObject` for `assets/*/screenshot-*/*` in the storage bucket policy. Screenshot assets are stored as WebP, up to 2400 px on the longest edge; uploads have a 10 MB limit. Gallery and asset fields are protected from ordinary version/metadata saves.

## 2. Viewer

**Recommended: Upload bundle…** Pick the pre-built viewer `.js` (plus optional extra
`.js`/`.css`/`.map`). BGS hosts the files and fills in the URL/dependencies. Save afterwards.

Alternatively, set **URL** to an externally hosted bundle, such as a CDN URL, and
add any dependency scripts/stylesheets.

The viewer bundle must be self-contained (already bundled by the game's own build).

**Full screen on mobile** (`viewer.fullScreenMobile`) gives the game a screen-height frame below the navbar on screens narrower than 768px. Scrolling happens inside the game, so its fixed action buttons stay visible without scroll synchronization. Desktop keeps the content-height layout. Set this separately for an alternate viewer if needed. **Full screen** (`viewer.fullScreen`) applies at every screen width.

**Sourcemaps.** An optional `.map` is hosted purely for browser devtools — nothing references it server-side. By default each uploaded file is content-hashed into its **own** S3 directory, so a relative `//# sourceMappingURL=viewer.js.map` in the JS won't resolve (the map sits in a different hash directory). To make devtools find the map, upload the `.js` and `.map` with a shared `?bundle=<id>` query param (`POST /api/admin/gameinfo/:game/:version/viewer/file?filename=…&bundle=<id>`) — files with the same bundle id share a directory, so a relative `sourceMappingURL` resolves. Use a fresh bundle id per build. (The admin **Upload bundle…** picker does this automatically for the files you select together.)

## 3. Engine

**Recommended: Upload .tgz…** Pick a built [`npm pack`](https://docs.npmjs.com/cli/commands/npm-pack)
tarball (with `dist/` included). BGS hosts it and installs the engine from that URL;
you do not need to publish the game on npm. Runtime `dependencies` are still resolved
at install time.

Alternatively, fill in **Package name** and **Package version** to install an engine
published on the npm registry.

The tarball must contain the built engine — the platform does **not** build game source.

## 4. Play

Open the game page, create a game, and play. Bumping the engine version (or re-uploading a bundle) makes new games use the new code; ongoing games follow the engine package of the game version they were created on — see [hot-swapping](#hot-swapping-ongoing-games) below.

Registering a game is an admin action — to get your game added to the site, reach out via [Contact](mailto:contact@boardgamers.space) (or the [Forum](https://forum.boardgamers.space)) — the links in the site footer.

## Publishing from the command line (admin tokens)

Everything the panel flow above does can be scripted against the admin API — handy for CI or release scripts. You need an **admin token**: a platform admin creates one in the admin panel (**Admin Tokens** page, or `POST /api/admin/tokens` with `{ name, ttlDays? }` from their session) and hands you the raw value, shown once at creation. Raw tokens carry a `bgs_admin_` prefix, only authenticate under `/api/admin/*`, expire (default 30 days, max 90), can be revoked, and stop working if the owner loses admin rights. For these endpoints the owner needs the `gameinfo` grant (or a per-game `gameinfo:<game>` grant).

Send the token as a bearer header. On production the admin API is reachable at `https://admin.boardgamers.space/api`:

```bash
export BGS_ADMIN_TOKEN=…   # bgs_admin_…, from a platform admin
API=https://admin.boardgamers.space/api
AUTH="Authorization: Bearer $BGS_ADMIN_TOKEN"
```

### List versions / fetch a version

```bash
curl "$API/admin/gameinfo/tictactoe/versions" -H "$AUTH"
# → [{ "version": 2, "archived": false }, { "version": 1, "archived": false }]

curl "$API/admin/gameinfo/tictactoe/1" -H "$AUTH"
# → the full version doc (engine, viewer, options, …)
```

### Upload an engine

The command-line equivalent of **Upload .tgz…**: `POST` the raw [`npm pack`](https://docs.npmjs.com/cli/commands/npm-pack) tarball (max 50 MB). The server reads the package name/version from `package/package.json` inside the tarball, hosts the file on S3, and immediately sets `engine.package = { name, version, url }` on the version doc — no follow-up save needed. The version doc must already exist (404 otherwise — register the game first).

```bash
npm pack   # → tictactoe-engine-1.2.3.tgz
curl -X POST "$API/admin/gameinfo/tictactoe/1/engine" \
	-H "$AUTH" -H "Content-Type: application/octet-stream" \
	--data-binary @tictactoe-engine-1.2.3.tgz
```

### Upload viewer files

The command-line equivalent of **Upload bundle…**: one `POST` per file (`.js`, `.css` or `.map`, max 25 MB each). Each call returns the hosted URL but **persists nothing by itself** — you then save the URLs into the version doc (next step). Add `&alternate=1` to target the alternate viewer, and a shared `&bundle=<id>` on the `.js` + `.map` uploads so a relative `sourceMappingURL` resolves (see [step 2](#2-viewer)).

```bash
JS_URL=$(curl -X POST "$API/admin/gameinfo/tictactoe/1/viewer/file?filename=viewer.umd.js" \
	-H "$AUTH" -H "Content-Type: application/octet-stream" \
	--data-binary @dist/viewer.umd.js | jq -r .url)

CSS_URL=$(curl -X POST "$API/admin/gameinfo/tictactoe/1/viewer/file?filename=viewer.css" \
	-H "$AUTH" -H "Content-Type: application/octet-stream" \
	--data-binary @dist/viewer.css | jq -r .url)
```

### Save the version doc

`PUT` (or `POST`) `/:game/:version` upserts the version doc. The body is loose: fetch the current doc, update the fields you care about, and send it back — game-level metadata fields (label, description, players, …) are split off into the game's metadata doc server-side, and server-managed fields (`meta.archived`, `meta.bots`, timestamps) are protected, so round-tripping the GET response is safe.

```bash
curl "$API/admin/gameinfo/tictactoe/1" -H "$AUTH" |
	jq --arg js "$JS_URL" --arg css "$CSS_URL" \
		'.viewer.url = $js | .viewer.dependencies.stylesheets = [$css]' |
	curl -X PUT "$API/admin/gameinfo/tictactoe/1" \
		-H "$AUTH" -H "Content-Type: application/json" --data-binary @-
```

### Hot-swapping ongoing games

Ongoing games are pinned to the game **version integer**. Its metadata declares the requested engine package. About once a minute, the game-server installs pending updates under a separate path, imports the entry point in an isolated worker, and checks its required exports. Only a successful validation activates the update. Download or validation failures leave the previous engine active, and the next installer pass retries.

All serving processes read the same atomically updated `games/active-engines.json` beside the installed packages. Reads, moves, bots and newly created analyses resolve the active artifact; an analysis keeps its pinned artifact after later updates. Re-uploading a tarball with the same package version also gets a distinct install path when its content-hashed URL changes. Superseded paths are retained for 24 hours so in-flight requests can finish, and pinned analysis packages remain installed while referenced.

A new game version without an installed engine returns a temporary 503 until activation. Creating a **new version integer** only affects newly created games: ongoing games keep using their original version integer. The manifest and `games/node_modules` must be shared by serving processes on the same host; each additional host needs its own completed installation before serving games.
