---
type: tutorial
title: Build a marketplace game
summary: "Build a Caputchin game from an empty folder: install the SDK, register a factory, render from the per-round context, record a trace, and bundle it for the sandboxed iframe."
---

By the end of this tutorial you will have a complete, playable Caputchin game in one JavaScript bundle, ready to make replayable and publish. This is the boring, do-every-step version; read [Ship a game to the marketplace](/docs/marketplace-game-development/overview) first for where it fits.

You need Node and a bundler (esbuild, rollup, vite, or webpack, any of them). You do not need a Caputchin account to build, only to publish.

## 1. Install the SDK

```bash
npm install @caputchin/game-sdk
```

The SDK is tiny: a `register` helper plus TypeScript types. It does not bundle the widget runtime, so your game stays small.

## 2. Register a game factory

A game is a single call to `register` with a **factory function**. The widget invokes your factory inside the sandboxed iframe; that call is itself the start signal (there is no separate start event to wait for).

```js
import { register } from "@caputchin/game-sdk";

register((container, bridge, ctx) => {
  // container - a DOM element inside the sandboxed iframe; render into it.
  // bridge    - push-only channel to the host (pass / error / setSize).
  // ctx       - the per-round context (seed + resolved locale/skin/config).

  const cleanup = startGame(container, bridge, ctx);

  // Return an optional cleanup function; the widget calls it on unmount.
  return cleanup;
});
```

You do **not** pass the manifest to `register`; the server reads `caputchin.json` at index time and ships the resolved presets down to your factory as `ctx`. See the [SDK reference](/docs/marketplace-game-development/game-sdk-reference) for the full surface.

## 3. Render from the per-round context

The factory's third argument, `ctx`, carries everything that changes per visitor:

- `ctx.seed` - the per-round seed. **Derive all randomness from this** (see step 5). `null` outside a verified session.
- `ctx.locale` - resolved language strings (`ctx.locale._lang` plus your keys), or `null`.
- `ctx.skin` - resolved colors and asset URLs (`ctx.skin._theme`, always `light` or `dark`, plus your keys), or `null`.
- `ctx.config` - resolved gameplay configuration, or `null`.

Read your own keys off these; you never resolve presets yourself. Each is `null` when your manifest declares no matching block, so always fall back to a built-in default:

```js
function startGame(container, bridge, ctx) {
  const title = ctx.locale?.title ?? "Tap the targets";
  const accent = ctx.skin?.accent_color ?? "#2da44e";
  const targetCount = ctx.config?.target_count ?? 5;
  // ...render with these...
}
```

If your layout needs an explicit size, call `bridge.setSize(width, height)` once after your first paint.

## 4. Win, lose, and errors

Your game decides when the player wins and tells the host through the bridge:

- On a win, call `bridge.pass({ trace })` with the round's [trace](#5-record-a-trace) (step 5).
- On a loss or abandonment, call **nothing**; silence is the failure signal.
- If the game itself breaks (an asset failed, an exception), call `bridge.error({ code, message })`. That is for game-internal failure, not a player losing.

```js
function onWin(traceString) {
  bridge.pass({ trace: traceString });
}
function onAssetFailure(err) {
  bridge.error({ code: "asset-load-failed", message: String(err) });
}
```

## 5. Record a trace [#5-record-a-trace]

This is the step that makes the game **replayable**. As the player acts, record the inputs that drive the outcome (which target they hit, in order, with timing if it matters) and serialize that record to a string or byte array: that is your **trace**. Combined with the seed, the trace must let your logic reproduce the exact result, because the server re-runs it.

Two rules make that possible:

1. **Derive every random choice from `ctx.seed`.** Never call `Math.random()` or read the wall clock for anything that affects the outcome.
2. **Record enough to replay.** The trace plus the seed is the complete input to your game logic.

```js
function makeRng(seed) {
  // seed is ctx.seed; feed it so the server reproduces the same sequence.
  let s = hashSeed(seed);
  return () => (s = (s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
}
```

The shape of `seed`, the trace, and the verdict are defined by [the replay contract](/docs/marketplace-game-development/replay-contract); that page covers turning this recorded play into the headless `run` artifact the server replays. If hand-writing deterministic logic sounds fiddly, the [engine kit](/docs/marketplace-game-development/engine-kit) does it for you, optionally.

## 6. Bundle for the sandbox

The widget loads exactly one script URL, so everything must be in that one file. Configure your bundler for a single self-contained output:

- inline assets as data URLs (sprites, sounds, fonts);
- disable code splitting;
- if you use WASM, embed it as base64 and instantiate from bytes.

Patterns that do **not** work inside the opaque-origin, strict-CSP iframe:

- path-relative `fetch('./sprite.png')` - there is no path to fetch from;
- dynamic `import('./chunk.js')` - the second URL is blocked;
- `new Worker('./worker.js')` - spawn workers from inline `Blob` URLs instead;
- external CDN fetches at runtime - `connect-src` blocks them.

## 7. Test it locally

There is no special harness. Embed the widget in a static HTML page, point `game-src` at your local bundle output, and play it in a browser:

```html
<caputchin-game sitekey="cpt_pub_..." game-src="http://localhost:8080/game.js"></caputchin-game>
```

This is the same `game-src` path a [custom game](/docs/custom-game-development/self-hosted-game) uses; for the marketplace you will instead publish the repo so the platform pins the bundle for you.

## Next steps

- [The replay contract](/docs/marketplace-game-development/replay-contract): turn your recorded play into the `run` artifact that makes the game replayable.
- [The caputchin.json manifest](/docs/marketplace-game-development/the-caputchin-json-manifest): describe the game, its presets, and its bundle.
- [Publish to the marketplace](/docs/marketplace-game-development/publish-to-the-marketplace): tag the repo and go live.

## See also

- [Game SDK reference](/docs/marketplace-game-development/game-sdk-reference): every export, in full.
- [The engine kit](/docs/marketplace-game-development/engine-kit): the optional way to get determinism for free.
