---
type: reference
title: Le contrat de rejeu
summary: Le contrat run/seed/verdict obligatoire qui rend un jeu rejouable, ce que la fonction run reçoit et renvoie, comment la graine lie une trace à une manche, et comment le serveur la rejoue.
---

Le contrat de rejeu est l'unique surface obligatoire sur laquelle un jeu **rejouable** s'accorde avec la plateforme. Il est publié sous `@caputchin/replay-contract` : un paquet délibérément minuscule et sans dépendance (il est chargé dans un isolat de rejeu scellé, donc il n'entraîne rien). Tout le reste de ton jeu, le moteur, le renderer, le format de trace, est à toi.

Un jeu qui livre un `run` conforme est **rejouable** et peut donc **gater** une clé de site ; un jeu sans cela est toujours intégrable, mais seulement comme UX (il s'affiche **Non rejouable**). Vois [Livre un jeu au marketplace](/docs/marketplace-game-development/overview#replayable-vs-not-replayable) pour cette distinction.

## La fonction `run`

Un jeu rejouable livre un module JS ou WASM exportant une fonction nommée `run` :

```ts
run(seed, config, trace) -> { passed, score, durationMs }
```

```ts
type RunFn<C = unknown> = (
  seed: Seed,
  config: C | null,
  trace: Uint8Array | string,
) => Verdict | Promise<Verdict>
```

| Argument | Source | Signification |
|---|---|---|
| `seed` | Serveur | La [graine](#the-seed) par manche. La mise en place de la manche, propriété du serveur. |
| `config` | Serveur | La config de gameplay, **nullable et opaque** : la plateforme ne l'inspecte jamais, et elle est re-résolue côté serveur au rejeu, jamais affirmée par le client. `null` signifie « utilise les propres défauts du run ». Au MVP, le serveur passe `null` ; la config par site est une phase différée. |
| `trace` | Client | Le blob opaque que le client du joueur a émis, que cette fonction seule interprète. La plateforme ne le parse ni ne le type jamais ; ce sont des octets bruts ou une chaîne, bornés seulement par un plafond de taille et la limite CPU de l'isolat. |

L'ordre des arguments encode le modèle de confiance : `seed` et `config` sont la mise en place serveur de la manche ; `trace` est l'entrée du joueur. **Les valeurs qui affectent le gate (un seuil de réussite, des vies) sont sûres à lire depuis `config` mais seraient un contournement si lues depuis le `trace` du client.**

Le module doit exporter la fonction sous le nom `run` (la constante `RUN_EXPORT_NAME`). L'hôte de rejeu l'invoque et attend le résultat (l'instanciation WASM est asynchrone au premier appel).

## La graine [#the-seed]

```ts
type Seed = readonly [number, number, number, number]   // 128 bits, 4 u32 words, MSW first
```

La graine est les 128 bits de poids faible de `SHA-256(sessionId : gameId : roundIndex)`, portés comme quatre mots non signés de 32 bits, mot le plus significatif en premier. Le `deriveSeed(sessionId, gameId, roundIndex)` du paquet la calcule ; le serveur la dérive à la fois en émettant la manche et de nouveau en rejouant, donc elle ne voyage jamais sur le fil comme entrée client de confiance.

De façon cruciale, la graine **lie une trace à une session, un jeu et une manche** : rejouer une trace étrangère ou antérieure sous une graine différente donne `passed: false`. Cette liaison est la façon dont l'injection de trace et les attaques par rejeu sont défendues, donc tu dois ensemencer tout l'aléatoire à partir d'elle pour que le jeu en direct et le rejeu serveur s'accordent.

## Le verdict

```ts
interface Verdict {
  readonly passed: boolean      // drives the verification decision
  readonly score: number        // game-defined, any finite number
  readonly durationMs: number   // finite, non-negative
}
```

`passed` est le seul champ qui pilote la décision du captcha ; `score` et `durationMs` sont portés dans le token émis (et un futur tableau des scores). Le `parseVerdict(value)` du paquet valide une valeur de retour non fiable et **traite un résultat malformé comme une manche rejetée, jamais réussie**, donc un `run` qui lève ou renvoie n'importe quoi échoue en mode fermé.

## Le déterminisme est toute l'exigence [#determinism-is-the-whole-requirement]

L'unique règle stricte du contrat : `run` doit être **pur et déterministe sur deux runtimes**, le navigateur du joueur et l'isolat du serveur. Un `(seed, config, trace)` identique doit donner un verdict identique dans les deux. Les façons courantes de casser ça, toutes attrapées par l'auto-vérification de publication comme `run-not-conforming` :

- lire `Date.now()`, `performance.now()`, `Math.random()`, ou d'autres globaux non déterministes ;
- lire un état externe (DOM, réseau, stockage) que l'isolat de rejeu ne fournit pas ;
- s'appuyer sur des maths à virgule flottante qui diffèrent entre runtimes.

Comment tu atteins le déterminisme (virgule fixe, flottants spec-WASM, ou IEEE-754 plus un shim de neutralisation) est ton choix ; la plateforme n'héberge que le rejeu. Le [kit moteur](/docs/marketplace-game-development/engine-kit) fournit des primitives déterministes prêtes à l'emploi si tu préfères ne pas rouler les tiennes.

## Comment le serveur l'utilise

```mermaid
sequenceDiagram
  participant P as Navigateur du joueur
  participant S as Serveur Caputchin
  participant R as Isolat de rejeu
  S->>S: deriveSeed(sessionId, gameId, round)
  S->>P: issue round with seed
  P->>P: play, recording the trace
  P->>S: submit trace on pass
  S->>S: re-derive the same seed
  S->>R: run(seed, config, trace)
  R-->>S: verdict { passed, score, durationMs }
  S->>S: parseVerdict, decide
```

L'artefact que l'isolat charge est ton bundle `run`, soit l'`entry` en direct, soit l'[artefact `run`](/docs/marketplace-game-development/the-caputchin-json-manifest#the-run-artifact) dédié du manifeste.

## Voir aussi

- [L'engine-kit](/docs/marketplace-game-development/engine-kit) : produire un `run` conforme à partir d'un simple reducer, avec le déterminisme géré pour toi.
- [Le manifeste caputchin.json](/docs/marketplace-game-development/the-caputchin-json-manifest#the-run-artifact) : déclarer l'artefact `run` et ses modules.
- [Bâtir un jeu pour le marketplace](/docs/marketplace-game-development/build-a-marketplace-game) : enregistrer la trace que ce contrat rejoue.
- [Référence des erreurs de publication](/docs/marketplace-game-development/publish-failed-reference) : le résultat d'auto-vérification `run-not-conforming`.
