3.2 浏览器里的真玩与断言
在无头浏览器里真的玩一局:注入的是整段 25 条策略序列,不是逐帧点按;断言的是胜负与状态(won / over / enemyHp / dealt 与不变量),而不只是一条摘要。页面 digest 必须与 Node 侧 digestSnap(runSpec(...)) 逐字相同——同一段源码的两个宿主本就该给出同一个答案。
category
在无头浏览器里真的玩一局:注入的是整段 25 条策略序列,不是逐帧点按;断言的是胜负与状态(won / over / enemyHp / dealt 与不变量),而不只是一条摘要。页面 digest 必须与 Node 侧 digestSnap(runSpec(...)) 逐字相同——同一段源码的两个宿主本就该给出同一个答案。
把 config/play_spec.json 与 core/src/ashdeck/ 的五个确定性内核模块塞进一个自包含 HTML:无构建工具、无网络、无外部 script,拷走一个文件就能玩。本节给出构建脚本与产物级断言——页面里内联的规格必须与磁盘上的规格逐字段相等,任何外部引用都会让「可离线、可分享」失效。
原型最大的风险不是做不出来,而是做不完:范围一旦失控,迭代就变成无限拉锯。本节给「该停了」两条可算的判据——连续 3 轮代理指标无变化即停、总轮数上限兜底——并真跑到触发为止:主循环停在第 5 轮,抽掉唯一让指标动过的那轮后 control 停在第 3 轮。
迭代循环只有三步:改一个参数、跑一次内核、重跑一次原型。一轮的代价是可数的——规格里改 1 行、内核跑几微秒、代理指标重采样几百微秒。本节用 5 轮真实迭代给出这个代价,并用一个负例(energyMax 改成 -1)演示不变量断言如何拦住坏参数。
数字原型的最小内核不是「写得少」,而是「可数的少」:一份 config 进、一条 digest 出,5 个模块、4 条不变量、131 行运行时逻辑、单局 4 微秒。本节把「最小」换成三条可数判据——多少行、几个不变量、跑多久,并用 P 层断言与 S 层实测把它钉死。
三卷本收官清点:150 节正文加一条跨语言工具链(config / sim / core / web / out 七个目录),三卷各留一份交接产物;说明三条使用入口——照着读、照着复现、换成自己的规格当模板;划清边界——机制侧可算,主观体验只能用代理指标做相对比较,美术 / 性能 / 网络 / 商业化需真实业务环境(X 档);最后交代三个后续方向,并让三卷的三个问题在此闭合。
把 16.1 检查表里最硬的一条展开成全链路回放:从 config/play_spec.json 出发,在 S 层 Python、P 层 TypeScript、W 层浏览器与第 13 章六个品类变体上各取一次 digest,证明逐字相同;再给出 B 层稳健性证明——只改一个规则参数 energyMax,链上三处宿主同步改变且仍逐字一致。附四个节点的 digest 对照表与六个变体的结果摘要。
把三卷 150 节的产物汇成一张可勾选的检查表:三卷各回答一个问题,每项给出证据路径、真命令、通过判据与档位(S/P/W/D/X/B)。检查表本体是数据文件,执行器逐项核对产物是否存在、档位是否合法;X 档一律显式标注「需真实业务环境,命令行不可复现」。19 项全部 ok、fail=0,本节也说明检查表为什么不能写成许愿池。
把 13.3 如实记下的三处保留逐条分类并给出处置:平台跳跃种子轴惰性是设计使然、不是缺陷;塔防种子轴只有 10 桶是真实区分度不足、可改进但不是 bug;「胜率不得为 0‰ 或 1000‰」这条直觉判据被实测证伪、是判据本身错。同时划清闸门的诚实边界:它只覆盖机制侧,玩家体验、美术、性能、网络、商业化需真实业务环境。
正式判定六个品类变体 6/6 通过闸门,并给出 6 变体 × 5 判据 = 30 格的通过矩阵。权威输入是 13.3 的 out/gd_proto_13_gate.json,但本节不照抄——重跑六个内核重新判五条,再断言与权威输入逐格一致;另加一层扰动幅度稳健性扫描,报告通过状态保持的幅度区间,只软告警不硬失败。
把 13.3 的闸门报告当基线、把 14 章的距离口径并进来,合成一套回归套件,对六个品类变体重跑一遍并断言结果不变——回归 = 稳定性,不是正确性。三层:结构回归(六变体规格骨架同构)、数值回归(六条 digest 逐字相同)、判据回归(五条闸门判据的通过状态不变),并附一个负例证明回归不是恒真。
把 6×6 距离矩阵读出来,并老实交代三条边界:距离依赖维度选择(留一维度敏感性实测 Δ 在 33~63‰,最近一对在 4/10 次留一下改判)、整数地板除的量化误差上界(归一化传播到 L1 每对 ≤20、千分比每格 <1),以及「距离只是代理指标、只做相对比较、禁绝对化」,并给出与风格距离矩阵的分工。
先逐维 min-max 归一化到 0..1000,再用整数 L1 定义距离、用 floor(1000×d/maxd) 归一化成千分比,落成 6×6 对称矩阵,并验证对称性、三角不等式与「最小非零距离 > 0」的可区分性。实测塔防↔自走棋最近(268‰)、自走棋↔放置最远(1000‰),全部整数、无浮点。
给六个品类各造一个十维整数特征向量:机制计数、状态字段数、策略空间对数与熵、非支配率、状态空间对数、决策深度、随机结构计数、相变宽度、平均步数。说明每维的选择理由与「机制无关 vs 品类特有」的边界,从规格文本数出六维、从内核真跑出四维,落成可复现的 6×10 矩阵。
给六个品类变体定一套统一的五条判据——确定性、不变量、终态可达、契约同构、扰动非退化——逐个判通过或不通过,落成机器可读的 out/gd_proto_13_gate.json 并在正文逐字贴出。平台跳跃的种子轴惰性(16 个种子只出 1 个结果桶)如实记录、不掩盖;胜率与主桶占比都显式声明为代理指标。
把六份规格各自真跑一遍——卡牌构筑(本体)/ 塔防 / 三消 / 平台跳跃 / 自走棋 / 放置——并列成一张表:规格 id、规则段条目数、状态段字段数、参数字段数、不变量条数、确定性 digest。表同时证明结构同构(快照都投影进同一套 12 个契约槽位)与内容不同(四组计数与 digest 两两不同)。
把六份品类规格投影到同一份六段 schema(id / naming / state / rules / params / invariant):本体要一层 view 投影,五个品类变体本来就在这六段上。划出「不变量」——骨架一致、naming 的 8 个槽位、无浮点、invariant 非空——与「自由度」——规则条目 11~52、参数字段 2~8、内容指纹两两不同。
把「手感」操作化为三条可断言的帧级不变量——反馈延迟上界、缓冲窗口内输入不丢失、土狼窗口单调——再用真断言跑一遍。另给一个代理指标 responsePermille(响应率千分比),并显式声明它只是 proxy:只做相对比较、禁绝对化,粒度粗到 baseline 与 noCoyote 同为 1000,必须靠帧级不变量才分得开。
把手感参数可计算化:八个参数全是整数,帧数类与单位类都能断言逐位相等;只有帧到毫秒的感知派生量要按声明容差(60 fps 下一帧约 17 ms)断言。给出土狼时间与输入缓冲的整数边界、击退衰减的精确整数递推,并用一张敏感度表划清改时序与只改表现的参数。
把手感从渲染层剥出来,写成整数帧的确定性状态机:输入采样 → 缓冲 → 状态推进 → 反馈触发。同一段输入脚本、同一段美术表现,只改土狼时间与输入缓冲两组窗口参数,就得到四条互不相同的帧序列——手感差异全部落在可断言的整数帧上,并由 7 条 P 层断言固定住。