---
type: reference
title: El contrato de repetición
summary: El contrato obligatorio de run/seed/verdict que hace un juego reproducible, qué recibe y devuelve la función run, cómo la semilla ata una traza a una ronda, y cómo el servidor la repite.
---

El contrato de repetición es la única superficie obligatoria que un juego **reproducible** acuerda con la plataforma. Se publica como `@caputchin/replay-contract`: un paquete deliberadamente diminuto y sin dependencias (se carga en un isolate de repetición sellado, así que no trae nada). Todo lo demás de tu juego, el engine, el renderer, el formato de la traza, es tuyo.

Un juego que publica un `run` conforme es **reproducible** y por tanto puede **ejercer de gate** de una clave de sitio; un juego sin uno sigue siendo incrustable, pero solo como UX (muestra **No reproducible**). Mira [Publica un juego en el marketplace](/docs/marketplace-game-development/overview#replayable-vs-not-replayable) para esa distinción.

## La función `run`

Un juego reproducible publica un módulo JS o WASM que exporta una función llamada `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>
```

| Argumento | Fuente | Significado |
|---|---|---|
| `seed` | Servidor | La [semilla](#the-seed) por ronda. El montaje propiedad del servidor de la ronda. |
| `config` | Servidor | La config de juego, **nullable y opaca**: la plataforma nunca la inspecciona, y se vuelve a resolver en el servidor en la repetición, nunca la afirma el cliente. `null` significa "usa los valores por defecto del propio run". En el MVP el servidor pasa `null`; la config por sitio es una fase diferida. |
| `trace` | Cliente | El blob opaco que el cliente del jugador emitió, que solo esta función interpreta. La plataforma nunca lo parsea ni lo tipa; son bytes en bruto o una cadena, acotados solo por un tope de tamaño y el límite de CPU del isolate. |

El orden de los argumentos codifica el modelo de confianza: `seed` y `config` son el montaje del servidor de la ronda; `trace` es la entrada del jugador. **Los valores que afectan al gate (un umbral de aprobado, vidas) son seguros de leer de `config` pero serían un bypass si se leyeran de la `trace` del cliente.**

El módulo debe exportar la función bajo el nombre `run` (la constante `RUN_EXPORT_NAME`). El anfitrión de repetición la invoca y espera el resultado (la instanciación de WASM es asíncrona en la primera llamada).

## La semilla [#the-seed]

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

La semilla son los 128 bits bajos de `SHA-256(sessionId : gameId : roundIndex)`, llevados como cuatro palabras de 32 bits sin signo, la palabra más significativa primero. El `deriveSeed(sessionId, gameId, roundIndex)` del paquete la computa; el servidor la deriva tanto al emitir la ronda como de nuevo al repetir, así que nunca viaja por el cable como entrada de cliente confiable.

Crucialmente, la semilla **ata una traza a una sesión, juego y ronda**: repetir una traza ajena o anterior bajo una semilla distinta produce `passed: false`. Esa atadura es cómo se defienden la inyección de trazas y los ataques de repetición, así que debes sembrar toda la aleatoriedad de ella para que el juego en vivo y la repetición del servidor concuerden.

## El veredicto

```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` es el único campo que conduce la decisión del captcha; `score` y `durationMs` se llevan en el token emitido (y un futuro marcador). El `parseVerdict(value)` del paquete valida un valor de retorno no confiable y **trata un resultado malformado como una ronda rechazada, nunca una aprobada**, así que un `run` que lanza o devuelve basura falla cerrado.

## El determinismo es el requisito entero [#determinism-is-the-whole-requirement]

La única regla dura del contrato: `run` debe ser **puro y determinista entre dos runtimes**, el navegador del jugador y el isolate del servidor. Un `(seed, config, trace)` idéntico debe producir un veredicto idéntico en ambos. Las formas comunes de romper esto, todas atrapadas por la autocomprobación de publicación como `run-not-conforming`:

- leer `Date.now()`, `performance.now()`, `Math.random()`, u otros globales no deterministas;
- leer estado externo (DOM, red, almacenamiento) que el isolate de repetición no provee;
- depender de matemática de coma flotante que difiere entre runtimes.

Cómo logres el determinismo (coma fija, floats de la spec de WASM, o IEEE-754 más un shim de neutralización) es tu elección; la plataforma solo aloja la repetición. El [engine kit](/docs/marketplace-game-development/engine-kit) provee primitivas deterministas listas para usar si prefieres no hacer las tuyas.

## Cómo lo usa el servidor

```mermaid
sequenceDiagram
  participant P as Navegador del jugador
  participant S as Servidor de Caputchin
  participant R as Isolate de repetición
  S->>S: deriveSeed(sessionId, gameId, round)
  S->>P: emite la ronda con la semilla
  P->>P: juega, registrando la traza
  P->>S: envía la traza al aprobar
  S->>S: vuelve a derivar la misma semilla
  S->>R: run(seed, config, trace)
  R-->>S: veredicto { passed, score, durationMs }
  S->>S: parseVerdict, decide
```

El artefacto que el isolate carga es tu bundle `run`, o el `entry` en vivo o el [artefacto `run`](/docs/marketplace-game-development/the-caputchin-json-manifest#the-run-artifact) dedicado del manifiesto.

## Véase también

- [El engine-kit](/docs/marketplace-game-development/engine-kit): produce un `run` conforme a partir de un reducer simple, con el determinismo gestionado por ti.
- [El manifiesto caputchin.json](/docs/marketplace-game-development/the-caputchin-json-manifest#the-run-artifact): declarar el artefacto `run` y sus módulos.
- [Construye un juego del marketplace](/docs/marketplace-game-development/build-a-marketplace-game): registrar la traza que este contrato repite.
- [Referencia de errores de publicación](/docs/marketplace-game-development/publish-failed-reference): el resultado de autocomprobación `run-not-conforming`.
