---
type: tutorial
title: セルフホストのゲームを作る
summary: ゲーム SDK でサンドボックス化されたゲームバンドルを作り、自分でホストし、game-src でウィジェットをそれに向け、サーバーがリプレイする決定論的なトレースを生みます。
---

このチュートリアルの終わりまでに、本物の Caputchin のゲームを持っていることでしょう。ウィジェットのサンドボックス化された iframe で走り、ゲーム SDK を通じてホストと話し、サーバーが再実行できる **トレース** を報告する JavaScript のバンドルです。[手動モード](/docs/custom-game-development/manual-mode) と違い、セルフホストのゲームは [サイトキーをゲートできます](/docs/site-keys/games)。そのラウンドがリプレイ可能だからです。このチュートリアルはゲームの構築とホスティングを扱います。[リプレイとゲート](/docs/custom-game-development/replay-and-gating) が、実際にゲートをオンにするアーティファクトを扱います。

これがどこに合うかは [自分のゲームを動かす](/docs/custom-game-development/overview) を読んでください。静的な JavaScript ファイルをホストする場所（どの CDN か静的ホスト）と、あなたのページのウィジェットが必要です。

## セルフホストのゲームの形

セルフホストのゲームは、次をする 1 つの自己完結した JavaScript のバンドルです：

1. `@caputchin/game-sdk` の `register` 関数をインポートし、単一の **ゲームファクトリー** を登録します。
2. ウィジェットがファクトリーに渡すコンテナに、それと一緒に渡される解決済みの言語、スキン、構成を使ってレンダリングします。
3. ラウンドごとのシードのもとで決定論的にプレイし、プレイを **トレース** として記録します。
4. 勝ったら、その記録とともに `bridge.pass({ trace })` を呼びます。

ウィジェットはあなたのバンドルをその iframe に読み込み、あなたのファクトリーを呼びます。ファクトリーが呼ばれること *が* 開始のシグナルです（待つべき別の開始イベントはありません）。

## 1. ゲームファクトリーを登録する

SDK をインストールし、あなたのファクトリーで `register` を一度呼びます。マニフェストは渡し **ません**。サーバーがプリセットを解決してファクトリーに渡します。

```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;
});
```

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

## 2. ラウンドごとのコンテキストを読む

ファクトリーの 3 つ目の引数 `ctx` は、この訪問者のためにゲームが必要とするすべてを運びます：

- `ctx.seed`：ラウンドごとのリプレイのシード。**すべてのランダム性をこれから導きます。** ゲームが検証済みのセッションの外で走るときは null。
- `ctx.locale`：解決済みの言語オブジェクト：`ctx.locale._lang` に加えてあなたの翻訳された文字列キー。
- `ctx.skin`：解決済みのスキンオブジェクト：`ctx.skin._theme`（常に `light` か `dark`）に加えてあなたの色とアセットのキー。
- `ctx.config`：解決済みのゲームプレイの構成（または null）。

これらは、あなたのダッシュボードで定義した [スキーマとプリセット](/docs/custom-game-development/dashboard-schema) から Caputchin が生む、この訪問者のための **解決済みのプリセット** です。あなたは自分のキーをそれらから読み取ります。あなた自身は何も解決しません。

レイアウトに明示的なサイズが要るなら、最初の描画の後に一度ホストに伝えます：

```js
bridge.setSize(360, 480);
```

## 3. プレイを決定論的にする

これがゲームをゲート可能にするルールです：**ゲームは、シードとプレイヤーの入力が与えられれば決定論的でなければなりません。** 結果に影響する何かのために `Math.random()` を呼んだり、壁時計を読んだりしないでください。すべてのランダムな選択を `ctx.seed` から導いてください。サーバーは、同じシードと同じ記録されたトレースのもとであなたのロジックを再実行し、同じ判定に達しなければなりません。

```js
// A tiny seeded PRNG; feed it ctx.seed so the server reproduces the run.
function makeRng(seed) {
  let s = hashToInt(seed);
  return () => (s = (s * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
}
```

結果を駆動する入力（プレイヤーがどのターゲットを、どの順で当てたか、関係するならタイミングつき）を、プレイしながら記録します。その記録を文字列にシリアライズします。その文字列があなたのトレースです。

## 4. トレースとともに pass する

訪問者が勝ったら、ホストにトレースを渡します。`pass` は単一の `trace` 文字列を持つオブジェクトを取ります：

```js
function onWin(traceString) {
  bridge.pass({ trace: traceString });
}
```

トレースは **プラットフォームには不透明** です。それは、あなたのロジックがシードと組み合わさって結果を再現するような、あなたのゲームだけが定義する任意の文字列です。ゲームはここでスコアを報告しません。スコアリングは、もしあるなら、あなた自身の iframe 内の関心事です。

ブリッジの事実をあと 2 つ：

- 最初の `pass` がラウンドを引き換えます。失敗または放棄されたラウンドは、単に `pass` を **呼ばない** ことで知らされます（このブリッジに `fail` メソッドはありません）。
- `bridge.error({ code, message })` は、ゲーム内部の失敗（アセットの読み込み失敗、例外）のためで、プレイヤーの敗北のためではありません。それはホストに `error` イベントを表に出します。

ホストが pass を引き換えた後、それはウィジェットのトークンを解放し、あなたのバックエンドが [そのトークンを検証](/docs/integration-guides/verify-on-your-backend) します。いつもどおりです。

## 5. サンドボックスのためにバンドルする

ウィジェットはちょうど 1 つのスクリプト URL を読み込むので、すべてがその単一のファイルに入っていなければなりません。1 つの自己完結した出力のために、バンドラー（esbuild、rollup、vite、webpack。どれでも）を設定してください：

- アセット（スプライト、音、フォント）をデータ URL としてインライン化する。
- コード分割を無効にする。
- WASM を使うなら、base64 として埋め込み、バイトからインスタンス化する。

サンドボックスの中で **動かない** パターン。iframe が不透明オリジンで厳格な CSP を持つからです：

- パス相対の `fetch('./sprite.png')`：取得する元のパスがありません。
- 動的な `import('./chunk.js')`：2 つ目の URL がブロックされます。
- `new Worker('./worker.js')`：代わりにインラインの `Blob` URL から worker を生成してください。
- 実行時の外部 CDN のフェッチ：`connect-src` がそれらをブロックします。

## 6. バンドルをホストし、ウィジェットをそれに向ける

ビルドしたファイルを、あなた自身の静的ホストから `https` 越しに提供し（ループバックの `http` はローカル開発で許可されます）、それからウィジェットをそれに向けます：

```html
<caputchin-game
  sitekey="cpt_pub_..."
  game-src="https://cdn.example.com/my-game/game.js"
></caputchin-game>
```

ウィジェットはあなたのバンドルをそのサンドボックス化された iframe に読み込み、コンテキストとともにあなたのファクトリーを呼び、あなたの `pass` を待ちます。この時点でゲームは走って検証しますが、まだキーを **ゲート** することは許されていません。

## 7. ゲート可能にする

セルフホストのゲームを検証ゲートとして使うには、あと 2 つのことが必要で、どちらも次に扱います：

1. ダッシュボードで **カスタムゲームとして登録** し（あなたが選ぶ id）、コンテキストが本物の locale/skin/config を運ぶよう、そのフィールド [スキーマとプリセット](/docs/custom-game-development/dashboard-schema) を定義します。
2. **リプレイアーティファクトをアップロード** します。サーバーがシードとトレースから判定を再導出するために走らせる、あなたのゲームロジックのヘッドレスビルドです。[リプレイとゲート](/docs/custom-game-development/replay-and-gating) を参照してください。

リプレイアーティファクトがアップロードされてセルフチェックを通るまで、カスタムゲームは **リプレイ不可** と表示され、ゲートできません。

## あわせて読む

- [リプレイとゲート](/docs/custom-game-development/replay-and-gating)：ゲートをオンにするアーティファクト。
- [ダッシュボードスキーマリファレンス](/docs/custom-game-development/dashboard-schema)：コンテキストが解決するフィールドを定義する。
- [手動モード](/docs/custom-game-development/manual-mode)：より軽い、ゲートしない選択肢。
- [バックエンドで検証する](/docs/integration-guides/verify-on-your-backend)：pass が生むトークンを確認する。
