Caputchin
マーケットプレイスゲーム開発

リプレイ契約

リプレイ契約は、リプレイ可能 なゲームがプラットフォームと合意する唯一の必須の面です。それは @caputchin/replay-contract として公開されます。意図的に小さく、依存のないパッケージです(封じられたリプレイのアイソレートに読み込まれるので、何も引き込みません)。あなたのゲームのほかのすべて、エンジン、レンダラー、トレースの形式は、あなたのものです。

適合する run を出荷するゲームは リプレイ可能 で、なのでサイトキーを ゲートできます。それのないゲームはなお埋め込めますが、UX としてのみ(リプレイ不可 と表示)です。その区別は マーケットプレイスにゲームを出荷する を参照してください。

run 関数

リプレイ可能なゲームは、run という名前の関数 1 つをエクスポートする JS か WASM のモジュールを出荷します:

run(seed, config, trace) -> { passed, score, durationMs }
type RunFn<C = unknown> = (
  seed: Seed,
  config: C | null,
  trace: Uint8Array | string,
) => Verdict | Promise<Verdict>
引数ソース意味
seedサーバーラウンドごとの シード。ラウンドのサーバー所有の用意。
configサーバーゲームプレイの構成、null 許容で不透明:プラットフォームは決してそれを検査せず、リプレイ時にサーバー側で再解決され、決してクライアントが主張しません。null は「run 自身の既定を使う」を意味します。MVP ではサーバーは null を渡します。サイトごとの構成は延期されたフェーズです。
traceクライアントプレイヤーのクライアントが発した、この関数だけが解釈する不透明な塊。プラットフォームは決してそれをパースも型づけもしません。それは生のバイトか文字列で、サイズの上限とアイソレートの CPU 制限だけに境界づけられます。

引数の順は信頼のモデルをエンコードします。seedconfig はラウンドのサーバーの用意で、trace はプレイヤーの入力です。ゲートに影響する値(合格のしきい値、ライフ)は config から読むのは安全ですが、クライアントの trace から読むとバイパスになります。

モジュールは、run という名前(定数 RUN_EXPORT_NAME)で関数をエクスポートしなければなりません。リプレイのホストはそれを呼び、結果を待ちます(WASM のインスタンス化は最初の呼び出しで非同期です)。

シード

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

シードは SHA-256(sessionId : gameId : roundIndex) の低位 128 ビットで、4 つの符号なし 32 ビットワードとして、最上位ワードを先頭に運ばれます。パッケージの deriveSeed(sessionId, gameId, roundIndex) がそれを計算します。サーバーは、ラウンドを発行するときと、リプレイするときの両方でそれを導くので、それは決して信頼されたクライアントの入力としてワイヤーに乗りません。

決定的に、シードは トレースを 1 つのセッション、ゲーム、ラウンドに束ねます。異質な、または以前のトレースを別のシードのもとでリプレイすると passed: false を生みます。その束ねが、トレースの注入とリプレイ攻撃を防ぐ方法なので、ライブのプレイとサーバーのリプレイが一致するよう、すべてのランダム性をそれからシードしなければなりません。

判定

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

passed は captcha の決定を駆動する唯一のフィールドです。scoredurationMs は発行されたトークン(そして将来のスコアボード)に運ばれます。パッケージの parseVerdict(value) は信頼されない返り値を検証し、不正な結果を、通ったものではなく、拒否されたラウンドとして扱います。なので、投げるか、ゴミを返す run はフェイルクローズします。

決定論がすべての要件

契約の唯一の厳しいルール:run2 つのランタイム、プレイヤーのブラウザとサーバーのアイソレートにわたって、純粋で決定論的 でなければなりません。同一の (seed, config, trace) は、両方で同一の判定を生まなければなりません。これを壊すよくある方法、すべて公開のセルフチェックが run-not-conforming として捕らえます:

  • Date.now()performance.now()Math.random()、ほかの非決定論的なグローバルを読む。
  • リプレイのアイソレートが提供しない外部の状態(DOM、ネットワーク、ストレージ)を読む。
  • ランタイム間で違う浮動小数点の計算に頼る。

どう決定論を達成するか(固定小数点、WASM 仕様の浮動小数点、または IEEE-754 に中和シムを足す)はあなたの選択です。プラットフォームはリプレイをホストするだけです。エンジンキット は、自分で巻きたくないなら、既製の決定論的なプリミティブを提供します。

サーバーがどうそれを使うか

アイソレートが読み込むアーティファクトは、あなたの run バンドル、ライブの entry か、マニフェストの専用の run アーティファクト のどちらかです。

あわせて読む

このページの内容