リプレイ契約
リプレイ契約は、リプレイ可能 なゲームがプラットフォームと合意する唯一の必須の面です。それは @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 制限だけに境界づけられます。 |
引数の順は信頼のモデルをエンコードします。seed と config はラウンドのサーバーの用意で、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 の決定を駆動する唯一のフィールドです。score と durationMs は発行されたトークン(そして将来のスコアボード)に運ばれます。パッケージの parseVerdict(value) は信頼されない返り値を検証し、不正な結果を、通ったものではなく、拒否されたラウンドとして扱います。なので、投げるか、ゴミを返す run はフェイルクローズします。
決定論がすべての要件
契約の唯一の厳しいルール:run は 2 つのランタイム、プレイヤーのブラウザとサーバーのアイソレートにわたって、純粋で決定論的 でなければなりません。同一の (seed, config, trace) は、両方で同一の判定を生まなければなりません。これを壊すよくある方法、すべて公開のセルフチェックが run-not-conforming として捕らえます:
Date.now()、performance.now()、Math.random()、ほかの非決定論的なグローバルを読む。- リプレイのアイソレートが提供しない外部の状態(DOM、ネットワーク、ストレージ)を読む。
- ランタイム間で違う浮動小数点の計算に頼る。
どう決定論を達成するか(固定小数点、WASM 仕様の浮動小数点、または IEEE-754 に中和シムを足す)はあなたの選択です。プラットフォームはリプレイをホストするだけです。エンジンキット は、自分で巻きたくないなら、既製の決定論的なプリミティブを提供します。
サーバーがどうそれを使うか
アイソレートが読み込むアーティファクトは、あなたの run バンドル、ライブの entry か、マニフェストの専用の run アーティファクト のどちらかです。
あわせて読む
- engine-kit:素のレデューサーから適合する
runを生み、決定論はあなたのために扱われます。 - caputchin.json マニフェスト:
runアーティファクトとそのモジュールを宣言する。 - マーケットプレイスゲームを作る:この契約がリプレイするトレースを記録する。
- 公開エラーリファレンス:
run-not-conformingのセルフチェックの結果。