# contracts/play-loop/ —— 游戏真玩与可信证据闭环契约 这一目录同时保存多代真玩契约。`play-spec` 与 `verdict-feedback` 是旧九门驱动闭环的 C5/C6 契约,仍服务 tier2、历史复跑和在途兼容;`playtest/2` 已冻结只读,`playtest/3` 是便宜档生成验收的新签发真相层,把 Actor 操作、runner 取证、独立 Judge、单掷 guard、二掷合并和最终 postguard 分开。v3 不再让测试模型一句 pass 直接放行,也不恢复 `_forensicsView`、play-spec 或 tap-targets 作为便宜档玩法权威。 | 契约文件 | 设计代号 | 是什么 | 消费方 | 状态 | |---|---|---|---|---| | `play-spec.schema.json` | C5 | PlaySpec 考卷:游戏声明起局仪式、驱动器族、输入指令、赢/输可观测量、与源工程的派生绑定 | W-S1 修复三单(接 startRitual / derivedFrom 进 harness 与 ensure_play_spec)· tier2 F-1 对齐 | schema 已立、校验器与负样本齐;**生产接线在途** | | `verdict-feedback.schema.json` | C6 | VerdictFeedback 判卷反馈:每次未过门回喂续修 agent 的结构化载荷(哪道门 / 卡在哪个 phase / 判卷驱动器 / 证据指针 / 疑似失败面 / 修复方向) | W-S1 修复三单(gate_judge 产结构化反馈、middleware 注入续修)· tier2 F-1 对齐 | schema 已立、校验器与负样本齐;**生产接线在途** | | `game-event.schema.json` | game-event/1 | runtime 可信边界封装的版本化业务事件;native 与 historical legacy-adapted 来源严格分开;Match-3 swap/match payload 为严格结构 | runtime `ctx.event`、历史适配器、runner | schema、payload hash、revision、run 连续性和 matched union/count 正负样本齐 | | `playtest-evidence.schema.json` | playtest/2 | 上一代单局证据契约,仅用于历史回放和回归验证 | 历史证据读取器 | **冻结只读,不再签发、不允许适配升级** | | `playtest-evidence-v3.schema.json` | playtest/3 | 新签发单局权威证据:目标解析、双 Judge 独立槽、成本预留、rollGuard/merge/finalPostguard 与兼容投影 | CLI、Service create、A11 modify 共用的 `run_acceptance_v3` | schema、语义校验与样本齐;生产接线在途 | | `proof-obligation-registry.schema.json` | proof-obligations/2 | 按五个可信 `proofProfileId` 解析精确事件、payload 谓词、有序 sequence、`sameValue` 与 heritage `orderedSeries` | runner obligation resolver、JudgePackage 构建器、postguard | schema 与正负样本齐 | | `proof-obligations.v2.json` | 2026-07-15.v3 | 五 profile canonical 义务数据;默认义务与 brief 可选提升的唯一事实源 | 与上一行相同 | canonical 数据已立,`--suite` 强制校验 | | `legacy-game-log-mapping.schema.json` / `legacy-game-log-mapping.v1.json` | legacy-game-log-mapping/1 | historical_replay 专用精确 allowlist,键为 profile+tag+payload paths,禁止读取 message | 历史适配器 | schema、canonical 与负样本齐 | | `single-roll-fact.schema.json` | SingleRollFact | Node runner 交给 Python rollGuard 的单掷事实,封存动作时间推进、帧、事件、义务与 Judge 包 hash | Node runner、Python rollGuard | schema、真实形状样本与负样本齐 | | `acceptance-request.schema.json` | acceptance-request/1 | 一次验收的不可变输入身份;初始运行无父链,唯一修回同时绑定原产物与父请求 | Python 编排、Node runner | 精确 11 字段、canonical profile 与当前任务绑定对账、正负样本齐 | | `acceptance-provenance.schema.json` | acceptance-provenance/1 | 请求身份与修回链的逐字段镜像,并绑定 request 原始字节 hash 和被测 artifactHash | Python 编排、Node runner | 精确 13 字段,不接受 `requestFile` 等可写替代字段 | | `acceptance-request-v2.schema.json` / `acceptance-provenance-v2.schema.json` | acceptance-request/2 · acceptance-provenance/2 | 新验收身份链;交互身份只能是完整嵌套 `InteractionBinding/1` 或显式 null,完整 request 进入 canonical hash | Python 编排、Node runner、bundle validator | absent/verified/repair 正链与 flat/task drift 负样本齐;`/1` 继续冻结只读 | | `proof-obligation-resolution.schema.json` | ProofObligationResolution/1 | 按 proof 默认、原始 brief 匹配和可信 interaction 匹配确定性解析 required 义务 | runner resolver、SingleRollProof/2 | sidecar 语义 hash、注册表顺序和 binding 原始文件 hash 均可复算 | | `single-roll-proof-v1.schema.json` | SingleRollProof/1 frozen | 冻结 legacy 单掷包;保持完整 v3 actionRecord,不含 Resolution sidecar | 历史 bundle validator | 历史动作形状冻结,不受 ProofAction 收窄影响 | | `single-roll-proof-v2.schema.json` / `proof-action.schema.json` | SingleRollProof/2 · ProofAction/1 | 新 `/2` 请求的单掷封存包;动作只保留 normalized、帧、事件游标和受控时间推进,不含 Actor request/selection/TargetSet/runner group | Node runner、bundle validator、JudgePackage 构建器 | 窄动作正链与 request 泄漏负例齐;完整审计留在 runner 侧 | | `judge-package.schema.json` | JudgePackage/1 | Judge 唯一可读事实投影;不含 interaction、Resolution 或 solver 身份 | Judge A/B | runner-owned 结构严格隔离;业务 `gameEvents[].payload` 不做字段名黑名单 | | `proof-resolution-bundle.schema.json` | proof-resolution-bundle/1 | 测试清单;跨文件核对 request→provenance→binding→Resolution→SingleRollProof→JudgePackage→evidence 闭包 | `validate.py --proof-package-dir` | absent/verified/legacy/repair 四条正链与全链重签负例齐;不是生产证据 | | `actor-selection.schema.json` | ActorSelection/1 | Actor 的唯一输出结构;只允许目标 ID、棋盘单元、安全按键或有限等待 | new-api Anthropic `output_config`、生产 selection parser | canonical schema、生产接线与正负样本齐 | | `actor-view-v3.schema.json` / `actor-view-v4.schema.json` | ActorView/3 frozen · ActorView/4 | 冻结真实 View/3 投影;View/4 普通路径字段不变,标准 Match-3 只增加脱敏 pair 候选 | 后续 View/4 actor adapter | contract 与兼容矩阵齐;**未接生产 Actor** | | `actor-selection-v2.schema.json` / `actor-protocol-bundle.schema.json` | ActorSelection/2 · ActorProtocolBundle/1 | 普通四动作保持原义;Match-3 增加唯一 `swap_pair`,联合锁定 View/Selection/prompt/parser 代际 | 后续 View/4 parser、runner | 普通四动作、swap_pair、stale/unknown/endpoint/version drift 样本齐;**未接生产** | | `judge-result.schema.json` | JudgeResult/1 | Judge A/B 的唯一输出结构;固定顶层裁决、逐义务状态与候选步骤引用形状 | new-api Anthropic `output_config`、生产 Judge parser | canonical schema、生产接线与正负样本齐 | | `visual-target-set.schema.json` | VisualTargetSet/1 | `opencv-visual-targets@1.0.1` 从 clean frame 生成的 region/lattice/永久 g01;guide 与 manifest 保持独立制品 | target proposer、resolver、Match-3 shadow 感知器 | schema、canonical targetSetHash、真实生产 fixture 与 grid-only 回归齐 | | `interaction-profile-registry.schema.json` / `interaction-profiles.v1.json` | InteractionProfileRegistry/1 | Writer 前由可信编排器选择的交互规则;首版登记标准 Match-3 正交交换,并与 proof profile 的品类和模板路由对账 | 编排器、runner、builder provenance | canonical 数据、跨表校验与负样本齐;**尚未接生产入口** | | `interaction-profile-registry-v2.schema.json` / `interaction-profiles.v2.json` | InteractionProfileRegistry/2 | View/4 专用注册表;prompt 绑定仓内 contract-only 工件,producer/solver/parser/resolver/normalizer/equivalence 都携带 strict config 并复算 configHash | D-F 共用 runner | canonical registry、producer capability 与 drift 负例齐;校准路径已接,`runtimeEligible=false`,不改变 live View/3 prompt | | `match3-producer-preflight.schema.json` | Match3ProducerPreflight/1 | 在 Actor 与成本预留前封存 producer probe、隔离浏览器、首击帧/事件、lattice/SwapSet 与 normalizer 事实;`executionStage` 让早期环境失败以 null 表达未产生证据 | Match-3 专项 runner、上层编排器 | canonical factHash、三类结果、canonical geometry 与早期退出负例齐;校准入口已接,生产仍冻结 | | `interaction-binding.schema.json` | InteractionBinding/1 | 把 profile、注册表版本和当前 taskBindingHash 封成不可替换的 interactionBindingHash | 编排器、runner | schema、哈希复算与篡改负样本齐;**尚未接生产入口** | | `match3-board-projection.schema.json` | Match3BoardProjection/1 | frozen clean frame 上的匿名棋格类别投影;逐格 cropHash 始终绑定真实逻辑像素 crop | Match-3 感知器、规则 solver | schema、Lab+直方图+空间形状确定性感知、保守退出与 shadow 审计齐;**未接 Actor** | | `match3-swap-set.schema.json` | Match3SwapSet/1 | 从匿名棋盘确定性复算的合法无序交换对、交换前连续段和新连续段证明 | Match-3 规则 solver、后续 ActorView/4 | schema、纯规则 solver、sealed pair oracle 与正负样本齐;**未接 Actor/CDP** | | `actor-selection-attempt.schema.json` / `match3-pair-resolution.schema.json` / `grouped-action-ref.schema.json` | Attempt/1 · PairResolution/1 · GroupedActionRef/1 | 一个 Actor 选择确定一个 pair;runner 展开为两个原生 tap,不伪造第二次 Actor request | 后续 D-F runner audit | ref/hash、pair 另一端、first/second/reset 角色样本齐 | | `board-identity-check.schema.json` / `match3-action-group.schema.json` | BoardIdentityCheck/1 · Match3ActionGroup/1 | 首 tap 后从 TargetSet+Projection 重算匿名分区;原始 perception audit 的 ref/hash、两帧、首击格、配置、classifier、双跑结果与 support mask 同时进入闭包;只有 equivalent 才允许第二 tap | D-F 共用 runner audit | audit 漂移、伪造 mask、classifier 漂移、changed/重复第二端/缺动作负例齐 | | `collection-proof.schema.json` / `match3-action-bundle.schema.json` / `match3-roll-interaction-audit.schema.json` | CollectionProof · Match3ActionBundle/1 · Match3RollInteractionAudit/1 | CollectionProof 保存完整 runner 事实;两个测试清单回读 producer preflight、双 tap、Proof/Judge、真实帧字节、post-first perception audit 及最多两组一次 reset 的全部审计工件 | `validate.py --match3-action-bundle` / `--match3-roll-audit` | preflight→roll 身份闭包、完整正链与重签负例齐;测试清单本身不是生产证据 | | `match3-audio-audit.schema.json` | Match3AudioAudit/1 | limiter 后窗口账;绑定 host 已验证的 BGM 原始字节、runtime 节点、视觉 transition、cue receipt 和逐 cue onset 终态 | host、专项 runner | 完成事务的每个应发声 transition 必须恰有一张成功 receipt,至少一个 onset detected,终局 cue 必须 detected;runner 在每个 completed group 后立即无输入 drain,并在该 group 目录独立封存 | | `build-manifest.schema.json` / `match3-visual-transaction.schema.json` / `match3-visual-audit.schema.json` | BuildManifest/1 · Match3VisualTransaction/1 · Match3VisualAudit/1 | staged 制品、视觉事务最小可信投影及稳定终盘审计 | 后续 specialty sidecar 生产者 | contract-first,exact schema 与关键负例齐;**未接生产 runner** | | `match3-effect-render-receipt.schema.json` / `match3-effect-audit.schema.json` | Match3EffectRenderReceipt/1 · Match3EffectAudit/1 | 可信主画布特效回执和 build/filmstrip/palette 闭包 | 后续 specialty sidecar 生产者 | contract-first;**未接生产 runner** | | `input-lock-probe.schema.json` / `match3-filmstrip-analyzer.schema.json` | InputLockProbe/1 · Match3FilmstripAnalyzer/1 | 独立输入锁 control/probe 与 swap→clear→fall/refill→settle 胶片顺序 | 后续 specialty sidecar 生产者 | contract-first;**未接生产 runner** | | `match3-roll-interaction-audit-v2.schema.json` / `single-roll-proof-v3.schema.json` | Match3RollInteractionAudit/2 · SingleRollProof/3 | `/2` 单向引用冻结动作 Audit/1 和全部 specialty sidecar;Proof/3 再单向引用 Audit/2 | specialty gate、`validate.py --match3-specialty-proof` | 跨文件正链和缺字段/hash 漂移/旧版冒充负例齐;**当前 runner 禁止签发** | AudioAudit 的 group 输出目录是单次 run 身份边界,不可复用。若目录已有正式侧车,sealer 在写候选前报 `artifact_ref_collision`;旧文件只属于旧 run,消费方只能使用本次调用返回的 `ref/hash`,不得扫描目录捡取历史 formal。 ## v3 可信证据边界 `playtest/3` 把流程阶段与终裁结果拆成两个字段: ```text phase = PRECHECK | COLLECTING | JUDGING | DECIDED outcome = accept | reject | inconclusive | tester_error ``` Actor 只能输出 tap/key/wait/drag 动作。下一轮 `ActorView` 只回灌 runner 记录的客观动作结果,不回灌 Actor 的 why、summary 或结论。Judge 使用独立 API client、session、system prompt、上下文投影和证据目录,只读取 hash 封存后的 JudgePackage。每掷 `rollGuard` 只产 `rollOutcome`,merge 只产 `mergeCandidate`;只有顶层 `finalPostguard` 能写 `decision` 与 compatibility。 终裁和发布是两个不同问题,固定公式如下: ```text decision.accepted = (outcome == accept) publishFrozen = (acceptanceMode == v3_shadow or outcome != accept) compatibility.accepted = compatibility.ok = (acceptanceMode == v3 and outcome == accept) ``` 因此 shadow 样本即使硬证完整,`finalPostguard.pass=true`、`decision.accepted=true` 也只说明验收裁决为接受;`decision.publishFrozen` 与 `compatibility.publishFrozen` 仍为 true,`compatibility.accepted/ok` 必须为 false,不能进入自动交付。只有 active `v3` 的 accept 可以解冻发布。`decision.shadowAccepted` 在 shadow 下镜像终裁 accepted,在 active v3 下为 null。 `compatibility` 是 run-summary 唯一平铺投影,固定包含 `accepted / ok / acceptanceVersion / publishFrozen / playtest / judge / failureLayer / failureReason / trace`。其中 `failureLayer` 是 `{layer, reason, failedGates}` 对象;`trace.playtest` 与 `playtest` 逐字一致,`trace.gameplayJudge` 与 `judge` 逐字一致。契约不再保留 `tracePlaytest`、`traceGameplayJudge` 等第二套别名,避免 CLI、Service create、A11 modify 和 Java 消费层各读一份。 `decision` 同时封存冻结位、失败对象、repair 权限与成本:`repairCountAcrossParentChain / repairEligible / repairFeedback / costRmb / parentChainCostRmb`。顶层 `repair` 镜像 count/eligible/attempted/reason;eligible 时 reason 必须逐字等于 repairFeedback,parent 链已发生过修复时 attempted=true。两处不一致属于协议错误,不得靠任一侧猜测修正。请求只提交本轮有限非负的 writer 成本增量;repair 的历史基数必须从 sealed parent decision 读取,调用方不能自报或重置。单掷成本以明细原值计账,顶层与 ledger 的 `costRmb` 必须严格等于十进制 `ROUND_HALF_UP(sum(entries[].rmb), 5)`,不能拿语言默认 round 或未舍入浮点和五位汇总直接比较。 每个 required proof obligation 以 `sequenceRefs[]` 为权威,每一步同时引用动作、动作后截图和 `game-event/1` 业务事件;flat refs 只能从它确定性投影。runner 先按精确 type、payload 谓词、动作白名单和事件顺序选候选,Judge 再把事件与截图交叉核对。真实输入只在同一个浏览器原生 Event 的宿主同步分发栈内产生 action provenance;Promise、timer、RAF、保存事件二次派发、直接 `_emit` 以及外部或真实输入栈内嵌套的 `host.stepFrames/tap/do/state` 都不能继承 actionId。`wait` 只表示在受控时间窗口内观察到注册表允许的定时业务结果,不声称该事件由 wait 内部触发,也永远不能写入首个输入反馈;多步骤义务中的 wait 必须通过 `sameValue` 与其他步骤关联,单步骤 wait 必须有 payload 谓词。官方延迟里程碑均晚于 600ms 输入量子,由显式 wait 取证,不恢复宽时间窗。 验收入口先校验 `acceptance-request/1` 与 `acceptance-provenance/1` 的原始文件字节 hash,再逐字段核对 gameId、briefHash、genre、templateRoute、proofProfileId、registryVersion、taskBindingHash 与修回链。`taskBindingHash` 是当前生成任务 traceId 的 SHA-256,同一任务的唯一 repair 全 parent 链保持不变;发布边界按当前任务 traceId 复算,不允许把内容相同的旧任务证据重放给新任务。两份文件和 playtest evidence 必须位于 gameDir 外;artifactHash 覆盖 staged gameDir 的全部普通文件,名为 `evidence` 的目录也不例外,任何文件或目录 symlink 直接拒绝。runner 启动前调用共用的 Python dirfd helper 一次性捕获普通文件字节并以该快照计算 artifactHash:`artifact-snapshot/1` 先写域标签,文件按 UTF-8 路径字节序排列,每项用 uint64be 分隔 path 与内容长度;根和每级目录、最终文件都经 `openat + O_NOFOLLOW` 打开并核对 device/inode,读取前核普通文件、大小和 128MiB 总帽,读取后复核 inode、大小及纳秒时间戳。Node 对 helper 的长度清晰输出再次复算 hash,浏览器只从本机内存快照服务读取这些字节。活目录的末尾复算不再参与生产裁决,因为它既挡不住“改写、加载、恢复”,又会在快照未变时制造无关假阴。发布边界另行要求当前待发布目录 hash 与 accepted artifactHash 一致。 浏览器业务事件只从导航前注入的固定 bridge 读取。bridge 在产物脚本运行前捕获原生描述符、冻结、String/RegExp、数组判定和受控 RAF 的 Map 方法;capture listener 以闭包 listenerType 对账,不读取 `event.type`,原生 isTrusted getter 也独立捕获。因此页面后续 monkeypatch `Object.*`、`Event.prototype.type/isTrusted`、`String`、`RegExp.prototype.test`、`Map.prototype`、`Array.isArray` 或 `JSON.*` 不会改变可信读口和虚拟帧账。native 模式要求不可替换且整体冻结的 `__genHost`/`__gameHost`,双 reader 也必须不可替换;公开 `window.__gameLog` 只服务 historical_replay,不能注入 native 业务事件或契约错误。每条事件同时携带 Node `stableStringify(payload)` 的 `payloadCanonical` 与其 SHA-256;Python 校验原文字节 hash,并要求 `json.loads(payloadCanonical)` 与 payload 类型严格、有限数值语义等值,不再用自身浮点格式复算。 heritage 使用受限 `orderedSeries`:同一 `workId` 的完整 3–12 步必须按 1..N 出现,每步 action/post frame 两两独立,summary 与 terminal 再逐项核对总数、stepId 顺序、完成位、成品数量和有限评分。TRPG 的天赋返回闭环使用 `sameValue(/upgradeId)` 绑定 level→talent→floor;翻层、下一关、营业结算分别使用 `sourceFloorVisitId`、`sourceLevelVisitId`、`dayId` 绑定前后业务事件,禁止跨周期或独立 timer 拼证据。 注册表的 `briefOptionalRules` 只做一件事:当 brief 明确出现“升级/天赋”“提示”“自动设备”等关键词时,把已登记的可选义务提升为 required。Actor 和 Judge 都不能临时发明或删除义务。运行结果必须记录 `proofRegistryVersion` 与命中的 `briefRuleMatches`,以便复现当时用的是哪一版验收义务。 新签发链使用 `acceptance-request/2`。请求把 `interactionBinding` 固定为完整 `InteractionBinding/1` 或 null,禁止再用平铺字段拼接身份;`acceptance-provenance/2` 必须逐字段镜像请求,并以 `artifactHash` 指向本次被测产物。初始请求的两个 parent 字段和 `sourceArtifactHash` 都为 null;唯一 repair 必须同时引用父 request 与父 provenance,继承 gameId、briefHash、genre、templateRoute、proof profile、注册表版本、task binding 与 interaction binding,且子请求的 `sourceArtifactHash` 必须等于父 provenance 的 `artifactHash`。 repair 校验不会只信父 provenance 的 `acceptanceRequestHash`。父 provenance 还必须逐字段镜像父 request 的 gameId、briefHash、genre、templateRoute、proof profile、注册表版本、task binding、interaction binding、source、parent 和 repairOrdinal;即使 request hash 正确,任一镜像字段单独漂移也会被拦。 Resolution 不是 Judge 输入。可信 resolver 读取原始 brief UTF-8 字节、冻结 proof 注册表和已验证 interaction binding,按注册表原顺序生成 `briefRuleMatches`、`interactionRuleMatches` 与 `requiredObligationIds`,再封存语义 hash。SingleRollProof/2 只通过 ref/hash 绑定该 sidecar;JudgePackage 只含 Judge 判定所需的事实候选、动作、帧、业务事件和文字引用范围。隔离检查只作用于 runner-owned 投影,不能因为游戏业务 payload 恰好出现 `candidateId`、`newRuns` 或 `solver` 就误拦。 `proof-resolution-bundle/1` 只用于测试跨文件闭包。validator 会复算完整 canonical request hash、brief 原始字节 hash、binding 文件 hash、Resolution hash、JudgePackage hash 和 SingleRollProof 封存 hash,并对账 request/provenance/proof/Judge 的 artifact 与身份字段。负样本带 `expectedErrorPattern`,套件必须命中指定根因,不能靠任意旧 hash 或无关结构错误“假绿”。 legacy `/1` bundle 按 request 版本进入冻结分支:request/provenance 使用旧 schema 与旧 proof registry 快照,单掷包使用 `single-roll-proof-v1.schema.json` 和 v3 事实语义校验。旧协议没有定义可复算的 canonical request hash,因此 validator 不新造算法,而是把历史 `acceptanceRequestHash` 当作 opaque 身份:request 与 provenance 逐字段镜像,proof 的 gameId、brief、品类、路由、proof profile、冻结注册表版本和 task binding 必须镜像 request,proof 的 opaque request hash 与 artifactHash 分别绑定 provenance。冻结 proof schema 把 `proofRegistryVersion` 固定为 `2026-07-14.v2`;它不加载当前 interaction registry,也不能仅凭 `packageType` 和外层 hash 被视为合法。 ## Match-3 离线规则与初始帧 shadow 边界 `interaction-profiles.v1.json` 的 `match3.orthogonal-swap-v1` 必须逐字对账 `proof-obligations.v2.json` 中 `puzzle.match-board` 的 `genre=puzzle` 与 `templateRoute=_template-puzzle`。`interactionBindingHash` 固定为: ```text SHA256("interaction-binding/1\n" + stableJson({ interactionProfileId, interactionRegistryVersion, taskBindingHash })) ``` v1 `Match3BoardProjection/1` 的 classId 按 classified 格的行优先首次出现次序编号为 `vc01...`;v2 D-F 审计不依赖 classId 字面值,而是从完整 classified cells 重算两两同类关系矩阵,因此首 tap 后即使类别重编号,只要 dimensions、lattice geometry 和匿名分区相同,BoardIdentity 仍可判 equivalent。alternatives 的 classId 不得重复,并按置信度降序、classId 升序保存。不保存输入颜色名或图标名。`projectionHash`、单候选 `candidateHash` 和 `candidateSetHash` 都采用“域标签 + 换行 + stableJson”口径,并排除自身 hash 字段。规则 solver 只枚举正交相邻、交换前异类、交换后形成此前不存在且包含交换端点的最大连续段;A↔B 与 B↔A 只能留下一个规范 pair。 `validate.py` 对单独的 `Match3SwapSet/1` 只能证明字段、排序、状态和两层 hash 的封存结构自洽;虽然 SwapSet 保存 source frame、TargetSet 与 Projection 引用,单文件校验仍不能证明这些文件真实存在或候选确实由那份棋盘计算。生产可信入口必须调用 `verifyMatch3SwapSetAgainstProjection(projection, swapSet)`:函数先严格校验 Projection,再从它重新运行 solver,并逐字比较完整 SwapSet。即使调用方伪造 `newRuns/classId` 或删除候选后重新签发 `candidateHash/candidateSetHash`,联合校验仍固定报 `swap_set_projection_mismatch`,不得进入 Actor 或动作派发。 第一切片建立离线规则层,第二切片增加初始 frozen clean frame 的只读感知 shadow;D-F contract slice 进一步定义 View/4、Selection/2、pair 双 tap、BoardIdentity、严格双事件和 roll recovery 的审计闭包。一个 `swap_pair` 只产生一次 `ActorSelectionAttempt/1`,PairResolution 从 sealed candidate 确定另一端,再展开为两个相邻原生 action。`eventsRef` 必须是覆盖双 tap 事件游标区间的完整 slice;validator 从第二 action 区间确定性选出相邻的 native/profile-proof `swap-committed→match-formed`,不再接受 manifest 自报 `proofProjection`。Resolution 必须提升 Match-3 义务,CollectionProof 保存完整动作、帧和事件,SingleRollProof 只保留允许的窄投影,JudgePackage 必须是 CollectionProof 的严格只读投影;三张实际 PNG 的原始字节 hash 同时绑定 action、TargetSet、Projection、SwapSet 和 proof/Judge frame。恢复最多两组、一次 reset;预算由 v2 recovery policy 推导,reset 必须重复首组第一端、预算 600ms、完整事件切片不含 swap/match,并从 reset action post frame 贯通 TargetSet→Projection→SwapSet 的新 candidateSet。以上仍是 contract-first,尚未接 Actor/CDP/runtime;`no_candidate` 也只表示当前可靠投影中没有可执行交换,不能直接形成游戏 reject。 标准生产者能力由 v2 registry 唯一给出:390×844 视口、8×8 棋盘、40px pitch、`(35,120)` 原点、20px 深色圆形 cell shell、40×40 冻结白色选中环像素 mask、472 像素支持集和 12px 主题安全内缩。选中环不能用 canvas `arc/stroke` 近似;真 Chrome 已证明同一参数在不同格位会产生 475/468 个变化像素。`Match3ProducerPreflight/1` 的 expected capability 必须逐字来自 registry,probe 自身 configHash 也必须复算;probe 身份漂移只能形成 `tester_error/profile_contract_error`,不能伪装成游戏不兼容。compatible 还必须从 producer grid 复算格心数组与 canonical `geometryHash`,再同时满足首击无业务事件、normalizer=`normalized`、changedPixelCount=472、supportMaskHash 精确命中冻结 mask、格外变化为 0 且重放残差不超过 1。像素或交互形态不满足该固定能力时签发 `incompatible/profile_contract_incompatible`。 Chromium、CDP、截图或本地视觉链未执行完时仍要签发 `tester_error/environment_error`。事实用 `executionStage` 记录最后完成边界;尚未产生的 probe、frame、event、lattice、SwapSet 必须为 null,normalizer 固定为 `not_run`,其 ref/hash 与像素数也全部为 null,禁止用占位 hash 冒充证据。正式 `Match3RollInteractionAudit/1` 必须引用原始 preflight 字节并复算 hash,只接受 `compatible/none`,同时逐字段核对 game、artifact、request、task binding、interaction binding 与 seed。所有结果都带 canonical `factHash`;校准入口已接线,`runtimeEligible=false` 仍保持生产冻结。 ## C5/C6 为什么存在 **考卷(C5)**至今是隐形的。驱动器怎么选,靠的是 `cheap_run.py:241-242` 那句「state 里有没有 `targets` 键」——有就用点目标的 tap-targets 族、没有就用循环按键的 key-cycle 族。这条约定只活在一个函数里,读代码才知道。更要命的是考卷和源工程之间**没有任何绑定**:`cheap_run.py:278` 用「play-spec.json 已存在就不覆盖」来兜底,可源工程一旦改过,旧考卷还在,就会拿上一版的考卷去判新工程——要么假绿、要么错判。C5 把驱动器族、起局仪式、赢/输可观测量都升为显式字段,并加一段 `derivedFrom.sourceHash` 把考卷绑到源工程内容上:hash 不一致即算陈旧、必须重生。陈旧从此可判定,而不是靠「不覆盖」听天由命。 **反馈(C6)**至今是一整段拼出来的中文字符串(`gate_judge.py:109` 的 `_cheap_verdict_feedback`、`run.py:1070` 的 `verdict_feedback`)。字符串拼装里丢字段是无声的,最典型的一处就是 `gate_judge.py:100-102`:它拼 H 门的 latch 细节时只读了 `latch.after`,没读 `latch.phaseNow`。可 latch 有两条失败支——驻留校验支带 `after`(`play.cdp.cjs:1089`),而 advisory 失败支只带 `phaseNow`、不带 `after`(`play.cdp.cjs:1083`)。于是当游戏卡在 `playing` 玩不到终局、走 advisory 支失败时,回喂里就成了 `after=None`,续修 agent 根本不知道游戏卡在哪个阶段,只能盲修。C6 把「卡在哪个阶段」升为一等字段 `phaseNow`,并规定「只要报 latch 失败就必须带 phaseNow」——这类丢字段从结构上不再可能发生。 ## 不编码 tier,用能力字段与扩展段表达档位差异(Δ9③) 两份 schema 都要同时承载便宜档与 tier2 富游戏档,但**任何字段里都不会出现 `tier: cheap | tier2` 这样的枚举**。做法沿用 `contracts/trace/` 已立的「接口对称、内容不对称」纪律:核心字段两档同名同义(考卷的 driver/observables/derivedFrom,反馈的 gate/driverType/failedGates),档位差异一律靠两种方式表达—— - **能力字段的「在不在」**:考卷里出现 `economy` 段=这局有经济胜负语义,出现 `controlCheck`=这局有可跟手的控制体,出现 `firstPlay`=要判首玩核心闭环时限。它们表达的是「这局具备什么能力」,而不是「这局属于哪档」。便宜档轻游戏不写这些段,tier2 富游戏档写,同一份 schema 都校验得过。 - **`ext` 扩展段**:某一档独有、不进对称核心的东西塞 `ext`,各写各的、没有的字段绝不编造。tier2 的经济三个数、findings 严重度就落在反馈的 `ext` 里;便宜档一般不写 `ext`。 样本 `play-spec/valid/03-tier2-paddle-economy.json` 与 `verdict-feedback/valid/03-tier2-economy-ext.json` 就是用来证明这一点的:tier2 的富能力字段和 `ext` 富证据全部通过同一份 schema,全程没有 tier 枚举。 ## 和另外两处容易混的东西划清界限 - **C6 反馈 ≠ `contracts/trace/` 的 trace 事件。** trace 记录是生成轨迹里一步的观测事件,写进台账供成本对账和排障回看,是旁路的、给人和监控看的。C6 反馈是判分没过时**喂回 agent 让它接着修**的载荷,是闭环里的一环、给模型读的。两者都带 `traceId`(反馈的 `evidence.traceId` 正是用来 join 到 trace 台账),但用途和生命周期完全不同:trace 每步都产,反馈只在未过门时产。 - **这两份 ≠ `contracts/agent-loop/` 里的 v1 verdict。** `contracts/agent-loop/verdict.schema.json` 是 Tier0/1 clicker 世代的终判记录(designId / playReport / decision 三值),是**终判本身**,且是上一代模板闭环的产物。C5/C6 属于当前引擎无关九门这一代,且 C6 是终判**之后**产的反馈、不是终判。别把两代、也别把「终判」和「终判后的反馈」混为一谈。 ## 校验器与样本 校验器 `validate.py` 零依赖(只用 Python 标准库),自带一个 JSON-Schema Draft-2020-12 子集实现——本仓没装 jsonschema、也没 vendor ajv,而红线要求零新依赖。JSON Schema 负责字段形状;标准 schema 无法表达的跨引用不变量由同文件的语义校验补齐,包括 Actor/Judge 隔离、seed 握手、动作事件区间、proof 三证引用、注册表义务解析、rollGuard/merge/finalPostguard 写权、shadow 发布冻结、decision/repair/compatibility 镜像、成本合计,以及 accepted 不得与 problems/矛盾共存。 ```bash # 套件模式:正样本应全过、负样本应全拦,打印每契约小计与总计 python3 validate.py --suite # 单点模式:拿任意样本对某 schema 校验(可多文件) python3 validate.py play-spec.schema.json samples/play-spec/valid/01-t2048-key-cycle.json # v3 单点模式;冻结历史契约仍可按对应旧 schema 单独校验 python3 validate.py playtest-evidence-v3.schema.json samples/playtest-evidence-v3/valid/01-judging-single-roll.json python3 validate.py proof-obligation-registry.schema.json proof-obligations.v2.json python3 validate.py single-roll-fact-v3.schema.json samples/single-roll-fact-v3/valid/01-targeted-dual-judge.json python3 validate.py acceptance-request.schema.json samples/acceptance-request/valid/01-initial-narrative.json python3 validate.py acceptance-provenance.schema.json samples/acceptance-provenance/valid/01-initial-narrative.json python3 validate.py acceptance-request-v2.schema.json samples/acceptance-request-v2/valid/02-verified-match3.json python3 validate.py acceptance-provenance-v2.schema.json samples/acceptance-provenance-v2/valid/02-verified-match3.json python3 validate.py proof-obligation-resolution.schema.json samples/proof-obligation-resolution/valid/02-verified-match3.json python3 validate.py single-roll-proof-v1.schema.json samples/proof-resolution-bundle/fixtures/legacy-single-roll-proof.json python3 validate.py single-roll-proof-v2.schema.json samples/single-roll-proof-v2/valid/05-complete-match3-proof.json # 结构化审计日志:成功和失败都记录阶段、引用、仓内相对路径及声明/复算 hash;不记录 brief、payload 或模型内容 python3 validate.py --trace-jsonl artifacts/validation-logs/match3-action-positive.jsonl \ --match3-action-bundle samples/match3-action-bundle/valid/01-complete-double-event.json python3 validate.py judge-package.schema.json samples/judge-package/valid/01-business-payload-name-collision.json python3 validate.py interaction-binding.schema.json samples/interaction-binding/valid/01-match3-task.json python3 validate.py interaction-profile-registry-v2.schema.json interaction-profiles.v2.json python3 validate.py actor-protocol-bundle.schema.json samples/actor-protocol-bundle/valid/02-view4-selection2.json python3 validate.py match3-board-projection.schema.json samples/match3-board-projection/valid/01-resolved-3x3.json python3 validate.py match3-swap-set.schema.json samples/match3-swap-set/valid/01-single-candidate.json python3 validate.py match3-producer-preflight.schema.json samples/match3-producer-preflight/valid/04-tester-environment-error.json # 跨文件闭包:也可传 absent、legacy 或 repair 的 manifest python3 validate.py --proof-package-dir samples/proof-resolution-bundle/valid/02-verified-v2.json python3 validate.py --match3-action-bundle samples/match3-action-bundle/valid/01-complete-double-event.json python3 validate.py --match3-roll-audit samples/match3-roll-interaction-audit/valid/01-reset-then-complete.json python3 validate.py --match3-specialty-proof samples/match3-specialty-proof/valid/01-complete-visual-audio-closure.json ``` 样本目录 `samples/<契约>/{valid,invalid}/`,目录名对应同名 schema。较大的 v3 衍生样本使用 `_base + _set/_delete` 声明最小单点变异:校验器会先加载完整正样本,再应用 JSON Pointer 变异,因此样本仍是完整契约实例,不会因为复制多份几百行 JSON 而随协议漂移。一般样本用 `_note` 写预期语义;request/provenance 正样本为了证明 exact keys 不带任何测试字段: - **C5 负样本**:①无 sourceHash 绑定(复现 `cheap_run.py:278` 缺绑定的陈旧兜底)②无任何驱动方式(driver/inputs 皆缺)③驱动器族缺必填参数(key-cycle 无 keys)④进展断言用了 `checkAssertion` 不认的算子(会无声判否)。 - **C6 负样本**:①报 latch 失败却丢 phaseNow(复现 `gate_judge.py:100-102`)②逐门反馈缺 driverType ③声称未过门却给空 items(复现 `gate_judge.py:125-126` 的盲修缺口)④门全绿却仍产反馈(passed=true 矛盾态)。 - **playtest/2 正样本**:active v3 accept、shadow accept 冻结、mechanical reject、inconclusive、tester_error;四态齐且发布态单独覆盖。 - **playtest/2 负样本**:提前写 outcome、accept+blocking problem、proof 引用断裂、Actor/Judge 共 session、插件日志冒充业务事件、mergeCandidate 被越权升级、rollGuard 越权写 decision、requested/actual seed 不一致、brief 义务漏解析、shadow compatibility 假放行、publishFrozen 矛盾、repair 镜像矛盾、decision.accepted 矛盾。 - **proof registry 负样本**:缺 profile、缺动作白名单、直接输入错误允许 wait、brief 孤儿义务、重复义务/步骤、坏谓词与 profile id 漂移。 - **因果证据负样本**:timeAdvance 缺失或帧数不闭合、sequence 倒序、wait 冒充输入、payload 谓词失败、TRPG upgradeId 不同、heritage 缺步骤/复用动作/summary 顺序矛盾。 - **请求与来源负样本**:额外字段、初始运行伪造父链、修回缺 source/parent、profile 与 genre/route 不一致、registryVersion 漂移和坏 request hash。 - **闭包负样本**:brief/interaction 规则漏解析、Resolution/Proof 身份漂移、Judge runner-owned 数据泄漏、provenance 与 proof artifact 分叉、repair 父 binding/brief/source artifact 与父 provenance 镜像漂移、legacy proof 畸形/gameId 替换/冻结注册表升级;全部按目标错误正则验收。 - **Match-3 负样本**:v2 config/prompt/registry/recovery policy 漂移;producer geometry 漂移、`not_run` 伪造 audit、preflight 字节/seed/result 与正式 roll 分叉;stale/unknown candidate、第一端不在 pair、第二 tap 重复、BoardIdentity changed 后仍继续;legacy/cross-profile proof 事件、完整事件流不相邻、event cursor 断裂;swap revision 不递增、payload 额外字段、run 不连续/乱序、matched union/count 漂移;未授权 reset、预算不足、reset 产生 swap、candidateSet 自报和第三组。 ## 取样来源 正样本的 gameplay/verdict 字段取自现网真实产物,`derivedFrom` 等 C5 新增绑定段按契约目标形态补齐(现网 play-spec.json 尚无此段,接线在 W-S1 在途),每个样本 `_note` 如实记录: - `play-spec/valid/01` ← `game-runtime/games/_wg1-gen/t2048/play-spec.json`(key-cycle) - `play-spec/valid/02` ← `cheap-worker/fixtures/golden-specs/shop-serve.play-spec.json`(tap-targets) - `play-spec/valid/03` ← `game-cloud/.../saa-golden/play-spec.json`(paddle-intercept + controlCheck)+ `run.py:1017-1024` 的 economy 形态 - `play-spec/valid/04` ← `game-runtime/games/_wg1-gen/_goldenpath-ref/play-spec.json`(inputs 固定序列) - `verdict-feedback/valid/01` ← `game-runtime/games/_wg1-gen/t2048/evidence/verdict.json`(H_progress latch.phaseNow='playing' 真实失败) - `verdict-feedback/valid/02` ← `game-runtime/games/_wg1-gen/_goldenpath-ref/evidence/verdict.json`(F_wiring callCount=0 真实失败) - `verdict-feedback/valid/03` ← `run.py:1000-1070` verdict_feedback/_economy_evidence + `gate_judge.py:42-53` 前缀口径 + `tier2-verdict.schema.json` richGameGates/findings 形态(tier2 富证据示例值) - `playtest-evidence/valid/01..05` ← [`2026-07-13-生成线验收v3-可信证据闭环-设计`](../../docs/agent-specs/2026-07-13-生成线验收v3-可信证据闭环-设计.md) 的 playtest/2、四态 outcome、active 发布条件与 shadow 冻结语义。 - `proof-obligations.v2.json` ← 同设计 §3.5 的五 profile 闭环、精确业务事件、动态 heritage 工序和 TRPG 升级周期约束。