16.2 三种消费的总联调
让穷举、执行、度量三种消费跑同一份规格并断言彼此不矛盾:BFS 穷举的可达集(9895423 个状态)包含全部采样路径,同一条选择序列在 Python / TS / 浏览器三处推进出逐位相同的状态摘要 4162796975。含 Playwright 无头真玩。
tag
让穷举、执行、度量三种消费跑同一份规格并断言彼此不矛盾:BFS 穷举的可达集(9895423 个状态)包含全部采样路径,同一条选择序列在 Python / TS / 浏览器三处推进出逐位相同的状态摘要 4162796975。含 Playwright 无头真玩。
三卷共用一份真源。本节把「装起来能不能用」拆成四组共 17 条断言:真源字段齐、卷一结构过、卷二引擎三处消费一致(跨语言锚点摘要 4162796975 逐位相等)、卷三六项代理指标量得出。每条都给出本机实跑值与可重跑命令,任一 FAIL 即非零退出。
命令行量不出玩家情绪,本卷七条指标全是 proxy。本节把「不能声称什么」写成可执行的 lint:八类禁声称(更感人、更好玩、玩家满意度、更优秀、玩家实际、读起来、绝对达标、提升 N 倍)、五条正面清单,以及 12 条候选结论的逐条判定与改写。指标能声称的只有相对变化与结构量。
同一张指标表里的数不能随便比:量纲不同的指标不能相加,七条指标合成不出一个总分;同一份规格加删一条选项,覆盖率就从 446 漂到 438 / 454;熵对样本量敏感,n=50 缺最稀有结局反而算出更高的熵。本节用三个可复现实验把可比性的边界钉死。
全卷七条代理指标第一次被收成一张总表:分支覆盖率、结局分布熵、张力曲线 MAE、好感度曲线 MAE、一致性残差、节奏密度偏差、整数不变量残差,每条给全定义、量纲、真源字段、实测值与首见小节。脚本从规格重算六条并与各章产物逐条对账,落 out/nm_15_1_metric_catalog.json。
指标变了不等于指标变坏了。本节把 14.2 的增量按三级分类:OK(没变)、DRIFT(变了但未越声明阈值)、CROSS(越过阈值才判得动方向)。再以 B 层把两处规格参数连续扫一遍,扫出相变点,全程只发软告警、始终 exit 0——扰动不该让构建硬失败。
上一节算出了影响面,这一节把「改动前 / 改动后」做成一张回归对照表:分支覆盖率、结局分布熵、张力 MAE、一致性残差各一列,增量各一行,并对着写在 metrics 里的声明阈值逐格判越阈。五处改动全是「变了但未越阈」,表同时落成 JSON 与 CSV,供后续每次演化重跑比对。
改一处规格,哪些路径与指标会变?本节用集合差回答:拿卷二 4.3 的 500 条采样序列当基线,在改动后的规格上重放,逐项量被阻断路径数、终局状态集差、结局集差与边/节点/场景/节拍集差。五处小改动各自给出实测数字,并说明为什么影响面不能靠重跑穷举来算。
解集有多大是一回事,稳不稳是另一回事。本节用两条轴量反推的稳健性:把目标曲线的 b10 从 5 挪到 4,在可枚举的预算上精确求新旧解集的交集与 Jaccard 千分;再把 MAE 阈值从 0 毫扫到 2000 毫看解集规模的相变。结论是解集近稳但规模巨大,曲线达标推不出唯一标注。
给定一条目标张力曲线,反过来能求出哪些节拍标注?本节把「MAE 不超阈值」化成 sum 绝对值不超过 16 的整数约束,用逐位 DP 精确计数:达标解集有 6072047140 个,占全空间 5 的 16 次方的 39.8 千分。随机搜索 200000 次的经验命中率与精确比例吻合,而收紧到 62 毫时随机搜索一次都命不中。
曲线偏离目标时,怎么把它落到具体的节拍下标与场景?本节用残差向量做定位,造三个漂移场景:在路径上的单点、两个异常点,以及离路径的隐藏点。实测发现路径口径看不见没经过的节拍——b10 从 5 降到 4 时路径 MAE 仍为 0 毫,全曲线 MAE 才抬到 62 毫。可观测性决定了可识别性。
假设被证伪之后必须二选一:改规格还是改假设。用样本量扫描 N=24/100/500/2000/8000 诊断 H4(结局分布熵)——熵在 N=100 起越过 1500 毫比特、N≥500 稳定在 1718–1747,判定为测试设计缺陷、改假设而非改规格,story_spec.json 一行不改;真人测试标 X。
把规格与内核内联进单文件 HTML,用 Playwright 无头注入 24 条选择序列真玩:逐条断言终局节点、结局与状态摘要与 Python 侧一致,两遍确定性 24/24,覆盖率 982‰,截图与批跑 JSON 落盘。真人玩家测试标 X。
一次可玩性测试如果没有假设,就只是点着玩。本节把《雾港夜航》这次测试要验的东西落成四条可证伪的命题——覆盖率、结局覆盖、跨宿主一致、分布熵,每条都给出可测整数、判据与证伪条件,并算出零假设基线 446‰。真人玩家测试需真实业务环境,命令行不可复现,标 X。
本章的「模拟玩家」是采样器,不是玩家模型:它只做路径选择策略(随机 / 贪心 / 覆盖导向),不建模动因、不做玩家分型。本节把这条分工写成一份可 lint 的 D 层声明表,再用 S 层脚本做硬断言,并与 11.1、11.2 真跑产物交叉核对,证明声明与脚本做的是同一件事。
同一份规格、同一个种子,只换「怎么选」这一条规则:随机、贪心、覆盖导向各跑 5000 条路径,量结局分布、结局分布熵与路径长度分布。实测贪心 5000 条里一次都没走到「真相」,而随机与覆盖导向都到达全部 5 个结局;再把三种策略按 7 档混合比扫描,只有纯贪心丢结局。全部指标都是 proxy。
给采样装上「策略」这个旋钮:随机(可播种 PRNG 选可用选项)、贪心(总是最大化好感度增量)、覆盖导向(优先走尚未覆盖的边)三种模拟玩家策略,在同一份规格下各走 10 与 200 条路径,对照覆盖率、结局数与路径长度区间。全部指标都是 proxy。
把 96 条采样路径在各场景上的经过次数画成 14×96 的矩阵热力图,cell 为整数、颜色按整数档位查表(档位定义在 config/nm_10_3_heatmap.json,全程无浮点调色)。矩阵元素之和 2544 恰等于所有路径的节点数之和;按结局排序后同结局的路径聚成一块。
把 48 个对话节点 / 56 条去重对话边按场景分层画成一张 SVG 分支图,边按是否被一条确定性路径走过着色:走过 25 条(446‰),未走过 31 条;随机采样 200 条即可铺满 56/56。布局坐标由场景序号与场景内序号直接算出,纯 xml.etree 建树、回读校验。
把前几章算出的张力曲线用纯 xml.etree 手写成 SVG:ET.Element / ET.SubElement 建树、ET.tostring 落盘,再用 ET.fromstring 回读校验合法性并断言元素计数。16 个节拍里一条确定性路径命中 11 个、跳过 5 个,全程整数、无浮点、不依赖任何第三方绘图库。