---
type: tutorial
title: Bâtir un jeu auto-hébergé
summary: Bâtis un bundle de jeu en bac à sable avec le SDK de jeu, héberge-le toi-même, pointe le widget vers lui avec game-src, et produis la trace déterministe que le serveur rejoue.
---

À la fin de ce tutoriel, tu auras un vrai jeu Caputchin : un bundle JavaScript qui tourne dans l'iframe en bac à sable du widget, parle à l'hôte via le SDK de jeu, et rapporte une **trace** que le serveur peut rejouer. Contrairement au [mode manuel](/docs/custom-game-development/manual-mode), un jeu auto-hébergé peut [gater une clé de site](/docs/site-keys/games), parce que sa manche est rejouable. Ce tutoriel couvre la construction et l'hébergement du jeu ; [rejeu et gating](/docs/custom-game-development/replay-and-gating) couvre l'artefact qui active réellement le gate.

Lis [exécute ton propre jeu](/docs/custom-game-development/overview) pour savoir où ça s'insère. Il te faut un endroit où héberger un fichier JavaScript statique (n'importe quel CDN ou hôte statique) et le widget sur ta page.

## La forme d'un jeu auto-hébergé

Un jeu auto-hébergé est un seul bundle JavaScript autonome qui :

1. Importe la fonction `register` de `@caputchin/game-sdk` et enregistre une seule **fabrique de jeu**.
2. Se rend dans le conteneur que le widget donne à la fabrique, en utilisant la langue, le skin et la configuration résolus passés à ses côtés.
3. Se déroule de façon déterministe sous la graine par manche, enregistrant le jeu comme une **trace**.
4. À la victoire, appelle `bridge.pass({ trace })` avec cet enregistrement.

Le widget charge ton bundle dans son iframe et invoque ta fabrique ; le fait que la fabrique soit appelée *est* le signal de départ (il n'y a pas d'événement de départ distinct à attendre).

## 1. Enregistre une fabrique de jeu

Installe le SDK et appelle `register` une fois avec ta fabrique. Tu ne passes **pas** le manifeste ; le serveur résout les préréglages et les remet à la fabrique.

```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. Lis le contexte par manche

Le troisième argument de la fabrique, `ctx`, porte tout ce dont ton jeu a besoin pour ce visiteur :

- `ctx.seed` : la graine de rejeu par manche. **Dérive tout l'aléatoire de celle-ci.** Null quand le jeu tourne hors d'une session vérifiée.
- `ctx.locale` : l'objet de langue résolu, `ctx.locale._lang` plus tes clés de chaînes traduites.
- `ctx.skin` : l'objet de skin résolu, `ctx.skin._theme` (toujours `light` ou `dark`) plus tes clés de couleur et de ressource.
- `ctx.config` : la configuration de gameplay résolue (ou null).

Ce sont les **préréglages résolus** pour ce visiteur, produits par Caputchin à partir de ton [schéma et tes préréglages](/docs/custom-game-development/dashboard-schema) définis au tableau de bord. Tu lis tes propres clés dessus ; tu ne résous rien toi-même.

Si ta mise en page a besoin d'une taille explicite, dis-le à l'hôte une fois après ton premier rendu :

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

## 3. Rends le jeu déterministe

C'est la règle qui rend un jeu gatable : **le jeu doit être déterministe étant donné la graine et les entrées du joueur.** N'appelle pas `Math.random()` ni ne lis l'horloge murale pour quoi que ce soit qui affecte le résultat ; dérive chaque choix aléatoire de `ctx.seed`. Le serveur rejoue ta logique sous la même graine et la même trace enregistrée et doit atteindre le même verdict.

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

Enregistre les entrées qui pilotent le résultat (quelles cibles le joueur a touchées, dans l'ordre, avec le timing si ça compte) à mesure que tu joues. Sérialise cet enregistrement en une chaîne : cette chaîne est ta trace.

## 4. Passe avec la trace

Quand le visiteur gagne, remets la trace à l'hôte. `pass` prend un objet avec une seule chaîne `trace` :

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

La trace est **opaque pour la plateforme** : c'est n'importe quelle chaîne que ton jeu seul définit, telle que ta logique, combinée à la graine, reproduise le résultat. Le jeu ne rapporte pas de score ici ; le score, s'il y en a, est ta propre affaire dans l'iframe.

Deux faits de plus sur le pont :

- Le premier `pass` échange la manche ; une manche échouée ou abandonnée est signalée en simplement **n'appelant pas** `pass` (il n'y a pas de méthode `fail` sur ce pont).
- `bridge.error({ code, message })` est pour une défaillance interne au jeu (une ressource a échoué à charger, une exception), pas une perte du joueur. Il fait surgir un événement `error` vers l'hôte.

Après que l'hôte a échangé le pass, il libère le token du widget, et ton backend [vérifie ce token](/docs/integration-guides/verify-on-your-backend) comme d'habitude.

## 5. Bundle pour le bac à sable

Le widget charge exactement une URL de script, donc tout doit être dans ce seul fichier. Configure ton bundler (esbuild, rollup, vite, webpack ; n'importe lequel) pour une seule sortie autonome :

- intègre les ressources comme URL de données (sprites, sons, polices) ;
- désactive le découpage de code ;
- si tu utilises WASM, intègre-le en base64 et instancie à partir des octets.

Les schémas qui **ne** fonctionnent **pas** dans le bac à sable, parce que l'iframe est à origine opaque avec une CSP stricte :

- un `fetch('./sprite.png')` relatif au chemin : il n'y a pas de chemin d'où récupérer ;
- un `import('./chunk.js')` dynamique : la deuxième URL est bloquée ;
- `new Worker('./worker.js')` : lance plutôt les workers à partir d'URL `Blob` en ligne ;
- les récupérations de CDN externe à l'exécution : `connect-src` les bloque.

## 6. Héberge le bundle et pointe le widget vers lui

Sers le fichier construit depuis ton propre hôte statique en `https` (le `http` de bouclage est autorisé pour le dev local), puis pointe le widget vers lui :

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

Le widget charge ton bundle dans son iframe en bac à sable, invoque ta fabrique avec le contexte, et attend ton `pass`. À ce stade le jeu tourne et vérifie, mais il n'est pas encore autorisé à **gater** une clé.

## 7. Rends-le gatable

Pour utiliser ton jeu auto-hébergé comme gate de vérification, deux choses de plus sont nécessaires, toutes deux couvertes ensuite :

1. **Enregistre-le comme jeu personnalisé** sur le tableau de bord (un id que tu choisis) et définis son [schéma et ses préréglages](/docs/custom-game-development/dashboard-schema) de champs pour que le contexte porte de vrais locale/skin/config.
2. **Téléverse un artefact de rejeu** : un build headless de ta logique de jeu que le serveur exécute pour re-dériver le verdict à partir de la graine et de la trace. Vois [rejeu et gating](/docs/custom-game-development/replay-and-gating).

Tant que l'artefact de rejeu n'est pas téléversé et ne réussit pas son auto-vérification, le jeu personnalisé s'affiche **Non rejouable** et ne peut pas gater.

## Voir aussi

- [Rejeu et gating](/docs/custom-game-development/replay-and-gating) : l'artefact qui active le gate.
- [Référence du schéma du tableau de bord](/docs/custom-game-development/dashboard-schema) : définir les champs que le contexte résout.
- [Mode manuel](/docs/custom-game-development/manual-mode) : l'alternative plus légère, sans gating.
- [Vérifier sur ton backend](/docs/integration-guides/verify-on-your-backend) : confirmer le token qu'un pass produit.
