回放契约
回放契约是一个 可回放 游戏和平台约定的那一个强制面。它作为 @caputchin/replay-contract 发布:一个刻意极小、零依赖的包(它被加载进一个密封的回放隔离体,所以它什么都不拉)。关于你游戏的其他一切,引擎、渲染器、轨迹格式,都归你。
一个交付了一个合规 run 的游戏是 可回放 的,因而能给一个站点密钥 设关卡;一个没有它的游戏仍可嵌入,但只作为 UX(它显示 不可回放)。那个区分见 向应用市场交付一个游戏。
run 函数
一个可回放游戏交付一个 JS 或 WASM 模块,导出一个名为 run 的函数:
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 意味着“用 run 自己的默认”。在 MVP 阶段服务器传 null;按站点配置是一个延后的阶段。 |
trace | 客户端 | 玩家客户端发出的那团不透明 blob,只由这个函数解读。平台从不解析或为它定类型;它是原始字节或一个字符串,只受一个大小上限和隔离体 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 位,作为四个无符号 32 位字携带,最高有效字在前。这个包的 deriveSeed(sessionId, gameId, roundIndex) 算出它;服务器在签发这个回合时和重新回放时都派生它,所以它绝不作为被信任的客户端输入跑在网线上。
至关重要的是,种子 把一条轨迹绑定到一个会话、游戏和回合:在一个不同的种子下回放一条外来的或更早的轨迹会得到 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 必须 跨两个运行时纯且确定性,即玩家的浏览器和服务器隔离体。相同的 (seed, config, trace) 必须在两者里都得到一个相同的裁定。打破这个的常见方式,全都被发布自检逮成 run-not-conforming:
- 读
Date.now()、performance.now()、Math.random()或其他非确定性的全局量; - 读回放隔离体不提供的外部状态(DOM、网络、存储);
- 倚靠在不同运行时之间有差异的浮点数学。
你如何达成确定性(定点、WASM 规范浮点,或 IEEE-754 加一个中和垫片)由你选;平台只承载这个回放。如果你宁愿不自己造,那个 引擎套件 提供现成的确定性原语。
服务器如何使用它
隔离体加载的那个工件是你的 run 包,要么是实时的 entry,要么是清单里那个专用的 run 工件。
另见
- engine-kit:从一个普通的 reducer 产出一个合规的
run,确定性替你处理好。 - caputchin.json 清单:声明那个
run工件及它的模块。 - 构建一个应用市场游戏:记录这个契约所回放的那条轨迹。
- 发布错误参考:那个
run-not-conforming自检结果。