3.2 资源守恒与不变量检验
机制自己得先自洽,才谈得上好玩。本节对同一份规格在 8 组显式种子上跑同一策略,逐局检验四条不变量:牌数守恒、伤害守恒、能量范围、格挡非负,输出误差表且全部为 0;四条判据都是整数,合格标准是误差恰好为 0 而不是约等于。再给一个「出牌忘进弃牌堆」的人为负例,证明检验器真的会报错。
category
机制自己得先自洽,才谈得上好玩。本节对同一份规格在 8 组显式种子上跑同一策略,逐局检验四条不变量:牌数守恒、伤害守恒、能量范围、格挡非负,输出误差表且全部为 0;四条判据都是整数,合格标准是误差恰好为 0 而不是约等于。再给一个「出牌忘进弃牌堆」的人为负例,证明检验器真的会报错。
快照只留终态,轨迹留每一步的状态序列。本节把 run_spec 的规则拆成「一步」,逐步重放策略并记录每步的能量、双方血量、格挡与累计伤害,导出 out/mech03_trajectory.csv。基线策略实际执行 12 步,未执行的 13 个指令不计入轨迹长度,且轨迹末态与快照逐位一致。
规格会随设计长出新字段,旧规格必须还能被新校验器接受。本节设计 v1→v2 的迁移:v2 只新增 params.critRate(默认 0),迁移写成纯函数,幂等且不动既有字段。迁移前后 runSpec 的 digest 逐字相同,语义等价因此是可断言的事实,六条断言全绿。
规格合法不合法,要能给出具体理由。本节把校验拆成两级:模式校验查必填字段、类型与取值域,语义校验查引用完整性(strategy 的 p<i> 索引必须可达、卡池非空、turnLimit>0、energyMax>=0)。附可跑的 validate.ts 与六个负例,每个都必须被判为不合法,七条断言全绿。
本章把《灰烬牌库》的玩法收敛成一份 play_spec.json:12 个必填字段位、8 个难度参数、4 张卡的卡池、3 条目标与 4 条不变量。本节逐字段给出类型、取值域与作用,附完整 JSON 与一个只读断言测试——卡池总数必须等于 12,params 必须全为整数,跑通七条断言全绿。
卷一的交付物不是一份文档,而是一组冻结的接口:规格版本、digest 键清单、PRNG 常数、params 键集合、基线 digest、四处消费入口。用一个脚本逐条核对它们是否被冻住,并说明卷二《策略、涌现与体验度量》接住什么——策略空间、支配策略、涌现与体验度量。
把 S / P / W / B 四处真的跑一遍:Python 仿真、TS 内核 node --test、浏览器 Playwright 真玩、B 层参数扰动,四处产出同一条 172 字符的摘要,再改一个规则参数(energyMax 3→2)看四处是否同步改变。这是「一份规格,四处消费」主张的可验证形态。
把全书散落的检查项汇成一张可执行的装配检查表:规格字段完整性、模式与语义校验、不变量清单、确定性、三处 digest 一致、B 层扰动可跑、整数与跨语言口径、W 产物自足。每项给一条真命令与一条通过判据,用一个脚本逐项真跑落盘——这是卷一交付前的最后一道闸门。
容差不是随口写的数字,是可执行判据 |left − right| ≤ tol。本节给出三档容差:整数精确(tol=0,必须相等)、千分比(tol=1‰,地板与四舍五入两种口径最多差 1)、浮点表现层(tol=1e-6,且必须显式断言 left !== right)。
同一份源码换宿主,语言原语的差异在三处咬人:Python 的 int 任意精度,JS 的 number 只到 2^53-1;幂与增长量必须走 BigInt;字符串的码点数、UTF-16 码元数、UTF-8 字节数是三个数。本节实测这些边界,并划清哪些量必须逐位一致、哪些只能按声明容差断言。
摘要不是打印用的,它是「三处消费」的契约:ashlib.digest 与 digestSnap 必须逐字相同。本节写出完整的 172 字符 digest 字符串,逐条说明键序、整数、无浮点三者缺一不可,并给出终态、前缀、换 seed 三个场景在 Python、Node、浏览器三处的对照表。
把体验约束写成能失败的断言:闲置率 ≤ 300‰、决策点密度 ≥ 400‰、每回合动作 ≥ 1500‰、回合数落在 [3,8]。设计工作点四条全过,三个故意违反的负例各自让目标断言 fail;B 层软告警指出断言有效域在 enemyHp 75→80 之间断裂。这些断言是代理性的,不保证与真实玩家感受一致。
把体验写成四个全部可算的代理指标:回合数、每回合有效动作率、资源闲置率、决策点密度,全是整数或千分比,落 CSV 并用纯 xml.etree 画图。16 个种子上实测闲置率均值 204‰、决策点密度 585‰、每回合动作 2150‰。它们都是代理指标,只用于相对比较,禁绝对化。
「体验目标」不能直接测量,只能落到机制参数上变成可观测的约束。本节给出映射四元组(体验目标 → 机制参数 → 观测列 → 期望方向 sign),并让同一张表在两个工作点各跑一遍:胜局工作点上 12 条映射有 6 条方向成立,换到败局工作点变成 0 条成立、2 条方向反转——映射的有效性以工作点为条件。
删一个机制之前得先证明它真的没人在用。本节把「退场」写成两级探针加消费层绑定的可判定判据:全域无反应才是死参数,近探针无反应而全域有反应是休眠,同一参数在机制底座里判死、在远征层里却仍然判为活。附六步退场清单、不变量恒真检查与删完后的回归验法。
赛季与 DLC 的差别不在体量,而在叠加层动什么:赛季只覆盖 params(可逆),DLC 追加卡牌实体(不可逆)。本节把五个叠加层压在整条难度阶梯上跑,给出整数差与千分比,并检查相变带有没有被移动、不变量有没有被破坏、哪个叠加层是纯增益膨胀。
机制层增量要判的不是字段齐不齐,而是旧回放还算不算数。本节用三条可判定判据——digest 是否逐字一致、四条不变量是否保持、旧回放是否仍胜——把七条候选增量判成兼容 / 可迁移 / 破坏三档,再用接线判据揭穿「假兼容」:字段加了却没被消费,digest 不变只证明什么都没加。
组合爆炸要靠上界、剪枝与降级判据三条一起管:k 个旋钮各 m 档的全空间是 m^k,先按 12.1 的死旋钮剪掉指数,再设枚举预算,超出即降级为可播种采样。本节给出闭式与实测耗时,并用 B 层软告警量化降级采样把整体难度的估计带偏了多少,以及采样的可信边界在哪。
协同与反协同是可判定的:定义交互项 I = 两机制同时变化的效果 − 各自单独变化的效果之和,先按观测量的好坏方向定向,再归一化成千分比。本节给出整数判据、实跑出的两处协同与两处反协同,并说明为什么「定向」这一步不能省——少了它,反协同会被符号读成协同。
把「机制耦合」写成一个可算的整数:在一个工作点上对每个参数做单参数扰动,看其它量的整数差,落成 7×5 的耦合矩阵与热力格。本节解释为什么 dealt 列恒零、为什么 rewardGold 与 punishHp 是死旋钮,以及为什么耦合是工作点的函数,而不是机制的内禀属性。