---
type: tutorial
title: Bangun game swahosting
summary: Bangun bundel game ber-sandbox dengan game SDK, hosting sendiri, arahkan widget ke ia dengan game-src, dan hasilkan trace deterministik yang diputar-ulang server.
---

Di akhir tutorial ini kamu akan punya game Caputchin sungguhan: sebuah bundel JavaScript yang berjalan di iframe ber-sandbox widget, berbicara ke host lewat game SDK, dan melaporkan sebuah **trace** yang bisa dijalankan-ulang server. Tak seperti [mode manual](/docs/custom-game-development/manual-mode), sebuah game swahosting bisa [menggerbang sebuah kunci situs](/docs/site-keys/games), karena rondenya bisa-diputar-ulang. Tutorial ini mencakup membangun dan menghosting game; [putar-ulang dan penggerbangan](/docs/custom-game-development/replay-and-gating) mencakup artefak yang sebenarnya menyalakan gerbang.

Baca [jalankan game-mu sendiri](/docs/custom-game-development/overview) untuk di mana ini cocok. Kamu butuh tempat untuk menghosting berkas JavaScript statis (CDN atau host statis mana pun) dan widget di halamanmu.

## Bentuk sebuah game swahosting

Sebuah game swahosting adalah satu bundel JavaScript mandiri yang:

1. Mengimpor fungsi `register` `@caputchin/game-sdk` dan mendaftarkan satu **game factory**.
2. Merender ke kontainer yang diberikan widget ke factory, memakai bahasa, skin, dan konfigurasi terselesaikan yang dilewatkan bersamanya.
3. Bermain secara deterministik di bawah seed per-ronde, merekam permainan sebagai **trace**.
4. Saat menang, memanggil `bridge.pass({ trace })` dengan catatan itu.

Widget memuat bundel-mu ke iframe-nya dan memanggil factory-mu; factory yang dipanggil *adalah* sinyal mulai (tak ada event mulai terpisah untuk ditunggu).

## 1. Daftarkan sebuah game factory

Pasang SDK dan panggil `register` sekali dengan factory-mu. Kamu **tak** melewatkan manifest; server menyelesaikan preset dan menyerahkannya ke factory.

```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. Baca konteks per-ronde

Argumen ketiga factory, `ctx`, membawa semua yang dibutuhkan game-mu untuk pengunjung ini:

- `ctx.seed` — seed putar-ulang per-ronde. **Turunkan semua keacakan dari ini.** Null saat game berjalan di luar sesi terverifikasi.
- `ctx.locale` — objek bahasa terselesaikan: `ctx.locale._lang` plus kunci string terjemahanmu.
- `ctx.skin` — objek skin terselesaikan: `ctx.skin._theme` (selalu `light` atau `dark`) plus kunci warna dan asetmu.
- `ctx.config` — konfigurasi gameplay terselesaikan (atau null).

Ini adalah **preset terselesaikan** untuk pengunjung ini, dihasilkan Caputchin dari [skema dan preset](/docs/custom-game-development/dashboard-schema) yang kamu definisikan di dasbor. Kamu membaca kunci-mu sendiri darinya; kamu tak menyelesaikan apa pun sendiri.

Jika tata letakmu butuh ukuran eksplisit, beri tahu host sekali setelah cat pertamamu:

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

## 3. Buat permainan deterministik

Inilah aturan yang membuat sebuah game bisa-digerbang: **game harus deterministik diberi seed dan input pemain.** Jangan panggil `Math.random()` atau membaca jam dinding untuk apa pun yang memengaruhi hasil; turunkan setiap pilihan acak dari `ctx.seed`. Server menjalankan-ulang logikamu di bawah seed yang sama dan trace terekam yang sama dan harus mencapai vonis yang sama.

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

Rekam input yang mengemudikan hasil (target mana yang dikenai pemain, dalam urutan, dengan waktu jika penting) selagi kamu bermain. Serialisasikan catatan itu ke sebuah string: string itu adalah trace-mu.

## 4. Pass dengan trace

Saat pengunjung menang, serahkan trace ke host. `pass` menerima sebuah objek dengan satu string `trace`:

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

Trace **opaque bagi platform**: ia adalah string apa pun yang hanya game-mu definisikan, sedemikian sehingga logikamu, digabung dengan seed, mereproduksi hasil. Game tak melaporkan skor di sini; penilaian, jika ada, adalah urusan dalam-iframe-mu sendiri.

Dua fakta jembatan lagi:

- `pass` pertama menebus ronde; ronde yang gagal atau ditinggalkan ditandakan dengan sekadar **tak memanggil** `pass` (tak ada metode `fail` di jembatan ini).
- `bridge.error({ code, message })` untuk kegagalan internal-game (sebuah aset gagal memuat, sebuah eksepsi), bukan kekalahan pemain. Ia memunculkan event `error` ke host.

Setelah host menebus pass, ia melepaskan token widget, dan backend-mu [memverifikasi token itu](/docs/integration-guides/verify-on-your-backend) seperti biasa.

## 5. Bundel untuk sandbox

Widget memuat persis satu URL skrip, jadi semua harus ada di satu berkas itu. Konfigurasi bundler-mu (esbuild, rollup, vite, webpack; salah satu) untuk satu keluaran mandiri:

- inline aset sebagai data URL (sprite, suara, font);
- nonaktifkan pemisahan kode;
- jika kamu memakai WASM, sematkan ia sebagai base64 dan instansiasi dari byte.

Pola yang **tidak** berfungsi di dalam sandbox, karena iframe ber-origin-opaque dengan CSP ketat:

- `fetch('./sprite.png')` relatif-path — tak ada path untuk diambil;
- `import('./chunk.js')` dinamis — URL kedua diblokir;
- `new Worker('./worker.js')` — munculkan worker dari URL `Blob` inline sebagai gantinya;
- pengambilan CDN eksternal saat runtime — `connect-src` memblokirnya.

## 6. Hosting bundel dan arahkan widget ke ia

Sajikan berkas terbangun dari host statis-mu sendiri lewat `https` (loopback `http` diizinkan untuk dev lokal), lalu arahkan widget ke ia:

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

Widget memuat bundel-mu ke iframe ber-sandbox-nya, memanggil factory-mu dengan konteks, dan menunggu `pass`-mu. Pada titik ini game berjalan dan memverifikasi, tapi ia belum diizinkan **menggerbang** sebuah key.

## 7. Buat ia bisa-digerbang

Untuk memakai game swahosting-mu sebagai gerbang verifikasi, dua hal lagi dibutuhkan, keduanya dibahas berikutnya:

1. **Daftarkan ia sebagai game kustom** di dasbor (sebuah id yang kamu pilih) dan definisikan [skema dan preset](/docs/custom-game-development/dashboard-schema) bidangnya agar konteks membawa locale/skin/config nyata.
2. **Unggah sebuah artefak putar-ulang**: sebuah build headless dari logika game-mu yang dijalankan server untuk menurunkan-ulang vonis dari seed dan trace. Lihat [putar-ulang dan penggerbangan](/docs/custom-game-development/replay-and-gating).

Sampai artefak putar-ulang diunggah dan lolos pemeriksaan-dirinya, game kustom menunjukkan **Tidak bisa diputar ulang** dan tak bisa menggerbang.

## Lihat juga

- [Putar-ulang dan penggerbangan](/docs/custom-game-development/replay-and-gating): artefak yang menyalakan gerbang.
- [Referensi skema dasbor](/docs/custom-game-development/dashboard-schema): mendefinisikan bidang yang diselesaikan konteks.
- [Mode manual](/docs/custom-game-development/manual-mode): alternatif lebih ringan, tak-menggerbang.
- [Verifikasi di backend-mu](/docs/integration-guides/verify-on-your-backend): memastikan token yang dihasilkan sebuah pass.
