---
type: tutorial
title: Construa um jogo auto-hospedado
summary: Construa um bundle de jogo em sandbox com o SDK de jogo, hospede-o você mesmo, aponte o widget para ele com game-src, e produza o trace determinístico que o servidor reexecuta.
---

Ao final deste tutorial você terá um jogo Caputchin de verdade: um bundle JavaScript que roda no iframe em sandbox do widget, fala com o anfitrião pelo SDK de jogo, e reporta um **trace** que o servidor pode reexecutar. Diferente do [modo manual](/docs/custom-game-development/manual-mode), um jogo auto-hospedado pode [proteger uma chave de site](/docs/site-keys/games), porque sua rodada é reproduzível. Este tutorial cobre construir e hospedar o jogo; [replay e proteção](/docs/custom-game-development/replay-and-gating) cobre o artefato que de fato liga o portão.

Leia [rode seu próprio jogo](/docs/custom-game-development/overview) para onde isto se encaixa. Você precisa de um lugar para hospedar um arquivo JavaScript estático (qualquer CDN ou host estático) e do widget na sua página.

## O formato de um jogo auto-hospedado

Um jogo auto-hospedado é um bundle JavaScript autossuficiente que:

1. Importa a função `register` do `@caputchin/game-sdk` e registra uma única **fábrica de jogo**.
2. Renderiza no contêiner que o widget dá à fábrica, usando o idioma, skin e configuração resolvidos passados junto.
3. Joga de forma determinística sob a semente por rodada, registrando o jogo como um **trace**.
4. Numa vitória, chama `bridge.pass({ trace })` com esse registro.

O widget carrega seu bundle no seu iframe e invoca sua fábrica; a fábrica sendo chamada *é* o sinal de início (não há um evento de início separado para esperar).

## 1. Registre uma fábrica de jogo

Instale o SDK e chame `register` uma vez com sua fábrica. Você **não** passa o manifesto; o servidor resolve os presets e os entrega à fábrica.

```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. Leia o contexto por rodada

O terceiro argumento da fábrica, `ctx`, carrega tudo o que seu jogo precisa para este visitante:

- `ctx.seed`: a semente de replay por rodada. **Derive toda aleatoriedade dela.** Null quando o jogo roda fora de uma sessão verificada.
- `ctx.locale`: o objeto de idioma resolvido: `ctx.locale._lang` mais suas chaves de string traduzidas.
- `ctx.skin`: o objeto de skin resolvido: `ctx.skin._theme` (sempre `light` ou `dark`) mais suas chaves de cor e ativo.
- `ctx.config`: a configuração de jogabilidade resolvida (ou null).

Estes são os **presets resolvidos** para este visitante, produzidos pela Caputchin a partir do seu [esquema e presets](/docs/custom-game-development/dashboard-schema) definidos no painel. Você lê suas próprias chaves deles; você não resolve nada você mesmo.

Se o seu layout precisa de um tamanho explícito, diga ao anfitrião uma vez depois do seu primeiro paint:

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

## 3. Torne o jogo determinístico

Esta é a regra que torna um jogo capaz de proteger: **o jogo precisa ser determinístico dada a semente e as entradas do jogador.** Não chame `Math.random()` nem leia o relógio para nada que afete o resultado; derive cada escolha aleatória de `ctx.seed`. O servidor reexecuta sua lógica sob a mesma semente e o mesmo trace registrado e precisa chegar ao mesmo veredito.

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

Registre as entradas que conduzem o resultado (em quais alvos o jogador acertou, em ordem, com timing se importa) à medida que você joga. Serialize esse registro para uma string: essa string é o seu trace.

## 4. Aprove com o trace

Quando o visitante vence, entregue o trace ao anfitrião. `pass` aceita um objeto com uma única string `trace`:

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

O trace é **opaco para a plataforma**: é qualquer string que só o seu jogo define, de modo que sua lógica, combinada com a semente, reproduza o resultado. O jogo não reporta uma pontuação aqui; a pontuação, se houver, é seu próprio assunto dentro do iframe.

Mais dois fatos sobre a ponte:

- O primeiro `pass` resgata a rodada; uma rodada falha ou abandonada é sinalizada simplesmente **não chamando** `pass` (não há método `fail` nesta ponte).
- `bridge.error({ code, message })` é para uma falha interna do jogo (um ativo falhou em carregar, uma exceção), não uma derrota do jogador. Ele expõe um evento `error` ao anfitrião.

Depois que o anfitrião resgata a aprovação, ele libera o token do widget, e seu backend [verifica esse token](/docs/integration-guides/verify-on-your-backend) como de costume.

## 5. Empacote para a sandbox

O widget carrega exatamente uma URL de script, então tudo precisa estar nesse único arquivo. Configure seu bundler (esbuild, rollup, vite, webpack; qualquer um deles) para uma saída autossuficiente:

- inline os ativos como data URLs (sprites, sons, fontes);
- desabilite o code splitting;
- se você usa WASM, embuta-o como base64 e instancie a partir dos bytes.

Padrões que **não** funcionam dentro da sandbox, porque o iframe é de origem opaca com uma CSP estrita:

- `fetch('./sprite.png')` relativo ao caminho, não há caminho de onde buscar;
- `import('./chunk.js')` dinâmico, a segunda URL é bloqueada;
- `new Worker('./worker.js')`, gere workers de URLs `Blob` inline em vez disso;
- buscas em CDN externo em tempo de execução, `connect-src` as bloqueia.

## 6. Hospede o bundle e aponte o widget para ele

Sirva o arquivo construído do seu próprio host estático por `https` (`http` de loopback é permitido para dev local), depois aponte o widget para ele:

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

O widget carrega seu bundle no seu iframe em sandbox, invoca sua fábrica com o contexto, e espera pelo seu `pass`. Neste ponto o jogo roda e verifica, mas ainda não tem permissão de **proteger** uma chave.

## 7. Torne-o capaz de proteger

Para usar seu jogo auto-hospedado como um portão de verificação, mais duas coisas são necessárias, ambas cobertas a seguir:

1. **Registre-o como um jogo personalizado** no painel (um id que você escolhe) e defina seu [esquema e presets](/docs/custom-game-development/dashboard-schema) de campos para que o contexto carregue locale/skin/config reais.
2. **Envie um artefato de replay**: uma build headless da lógica do seu jogo que o servidor roda para rederivar o veredito da semente e do trace. Veja [replay e proteção](/docs/custom-game-development/replay-and-gating).

Até que o artefato de replay seja enviado e passe na sua autoverificação, o jogo personalizado aparece como **Não reproduzível** e não pode proteger.

## Veja também

- [Replay e proteção](/docs/custom-game-development/replay-and-gating): o artefato que liga o portão.
- [Referência de esquema do painel](/docs/custom-game-development/dashboard-schema): definir os campos que o contexto resolve.
- [Modo manual](/docs/custom-game-development/manual-mode): a alternativa mais leve, que não protege.
- [Verificar no seu backend](/docs/integration-guides/verify-on-your-backend): confirmar o token que uma aprovação produz.
