---
type: reference
title: Kontrak putar-ulang
summary: Kontrak run/seed/verdict wajib yang membuat sebuah game bisa-diputar-ulang, apa yang diterima dan dikembalikan fungsi run, bagaimana seed mengikat trace ke satu ronde, dan bagaimana server memutarnya-ulang.
---

Kontrak putar-ulang adalah satu permukaan wajib yang disepakati game **bisa-diputar-ulang** dengan platform. Ia diterbitkan sebagai `@caputchin/replay-contract`: paket yang sengaja mungil, bebas-dependensi (ia dimuat ke isolate putar-ulang tersegel, jadi ia tak menarik apa pun). Semua hal lain tentang game-mu, engine, renderer, format trace, adalah milikmu.

Sebuah game yang mengirim `run` yang sesuai **bisa-diputar-ulang** dan karenanya bisa **menggerbang** sebuah kunci situs; game tanpa satu tetap bisa-disematkan, tapi hanya sebagai UX (ia menunjukkan **Tidak bisa diputar ulang**). Lihat [Kirim game ke marketplace](/docs/marketplace-game-development/overview#replayable-vs-not-replayable) untuk perbedaan itu.

## Fungsi `run`

Sebuah game bisa-diputar-ulang mengirim modul JS atau WASM yang mengekspor satu fungsi bernama `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>
```

| Argumen | Sumber | Makna |
|---|---|---|
| `seed` | Server | [Seed](#the-seed) per-ronde. Penyiapan ronde yang dimiliki-server. |
| `config` | Server | Config gameplay, **nullable dan opaque**: platform tak pernah memeriksanya, dan ia diselesaikan-ulang di sisi-server saat putar-ulang, tak pernah diklaim-klien. `null` berarti "pakai default run sendiri". Di MVP server melewatkan `null`; config per-situs adalah fase yang ditunda. |
| `trace` | Klien | Blob opaque yang dipancarkan klien pemain, yang hanya fungsi ini tafsirkan. Platform tak pernah mengurai atau mengetiknya; ia byte mentah atau string, dibatasi hanya oleh batas ukuran dan batas CPU isolate. |

Urutan argumen mengkodekan model kepercayaan: `seed` dan `config` adalah penyiapan server ronde; `trace` adalah input pemain. **Nilai yang memengaruhi-gerbang (ambang lolos, nyawa) aman dibaca dari `config` tapi akan menjadi bypass jika dibaca dari `trace` klien.**

Modul harus mengekspor fungsi di bawah nama `run` (konstanta `RUN_EXPORT_NAME`). Host putar-ulang memanggilnya dan menunggu hasilnya (instansiasi WASM async pada panggilan pertama).

## Seed [#the-seed]

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

Seed adalah 128 bit rendah dari `SHA-256(sessionId : gameId : roundIndex)`, dibawa sebagai empat kata 32-bit tak-bertanda, kata paling-signifikan dulu. `deriveSeed(sessionId, gameId, roundIndex)` paket menghitungnya; server menurunkannya baik saat menerbitkan ronde maupun lagi saat memutar-ulang, jadi ia tak pernah menumpang kabel sebagai input klien tepercaya.

Krusial, seed **mengikat trace ke satu sesi, game, dan ronde**: memutar-ulang trace asing atau lebih awal di bawah seed berbeda menghasilkan `passed: false`. Pengikatan itu adalah cara injeksi trace dan serangan putar-ulang dipertahankan, jadi kamu harus menyemai semua keacakan darinya agar permainan live dan putar-ulang server sepakat.

## Vonis

```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` adalah satu-satunya bidang yang mengemudikan keputusan captcha; `score` dan `durationMs` dibawa di token yang diterbitkan (dan papan skor masa depan). `parseVerdict(value)` paket memvalidasi nilai kembalian tak-tepercaya dan **memperlakukan hasil yang cacat sebagai ronde ditolak, tak pernah yang lolos**, jadi `run` yang melempar atau mengembalikan sampah gagal tertutup.

## Determinisme adalah seluruh persyaratan [#determinism-is-the-whole-requirement]

Satu aturan keras kontrak: `run` harus **murni dan deterministik lintas dua runtime**, peramban pemain dan isolate server. `(seed, config, trace)` identik harus menghasilkan vonis identik di keduanya. Cara umum merusak ini, semua ditangkap pemeriksaan-diri terbit sebagai `run-not-conforming`:

- membaca `Date.now()`, `performance.now()`, `Math.random()`, atau global non-deterministik lain;
- membaca state eksternal (DOM, jaringan, penyimpanan) yang tak disediakan isolate putar-ulang;
- bergantung pada matematika titik-mengambang yang berbeda antar runtime.

Bagaimana kamu mencapai determinisme (titik-tetap, float spec-WASM, atau IEEE-754 plus shim netralisasi) adalah pilihanmu; platform hanya menghosting putar-ulang. [Kit engine](/docs/marketplace-game-development/engine-kit) menyediakan primitif deterministik siap-pakai jika kamu lebih suka tak menggulung sendiri.

## Bagaimana server memakainya

```mermaid
sequenceDiagram
  participant P as Peramban pemain
  participant S as Server Caputchin
  participant R as Isolate putar-ulang
  S->>S: deriveSeed(sessionId, gameId, round)
  S->>P: terbitkan ronde dengan seed
  P->>P: main, merekam trace
  P->>S: kirim trace saat lolos
  S->>S: turunkan-ulang seed yang sama
  S->>R: run(seed, config, trace)
  R-->>S: vonis { passed, score, durationMs }
  S->>S: parseVerdict, putuskan
```

Artefak yang dimuat isolate adalah bundel `run`-mu, entah `entry` live atau [artefak `run`](/docs/marketplace-game-development/the-caputchin-json-manifest#the-run-artifact) khusus dari manifest.

## Lihat juga

- [engine-kit](/docs/marketplace-game-development/engine-kit): hasilkan `run` yang sesuai dari reducer biasa, dengan determinisme ditangani untukmu.
- [Manifest caputchin.json](/docs/marketplace-game-development/the-caputchin-json-manifest#the-run-artifact): mendeklarasikan artefak `run` dan modulnya.
- [Bangun game marketplace](/docs/marketplace-game-development/build-a-marketplace-game): merekam trace yang diputar-ulang kontrak ini.
- [Referensi galat terbit](/docs/marketplace-game-development/publish-failed-reference): hasil pemeriksaan-diri `run-not-conforming`.
