12.3 从测试到决策
假设被证伪之后必须二选一:改规格还是改假设。用样本量扫描 N=24/100/500/2000/8000 诊断 H4(结局分布熵)——熵在 N=100 起越过 1500 毫比特、N≥500 稳定在 1718–1747,判定为测试设计缺陷、改假设而非改规格,story_spec.json 一行不改;真人测试标 X。
category
假设被证伪之后必须二选一:改规格还是改假设。用样本量扫描 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 个,全程整数、无浮点、不依赖任何第三方绘图库。
同一条选择序列推进两次,走出的状态必须逐位一致。本节用三条证据钉死这件事:确定性序列回放两次摘要相同、每个前缀从零重放等于全程记录、500 条采样序列各自回放两次逐条一致,并说明「回放无隐藏状态」到底在保证什么、为什么它是本卷所有度量的前提。
同一条路径可以写成三种编码:人可读的规范串、机器可比的紧凑整数元组、以及只对状态算的 FNV-1a 摘要。本节给出三种编码的构造规则,并用 Python 与 TypeScript 两侧对同一条路径算出逐位一致的结果,说明摘要绑定的是编码串而不是文本。
一次通关可以被拆成三种表示:玩家按下的选择序列、引擎走出的节点序列、以及每一步之后的完整状态轨迹。本节用《雾港夜航》的首选项确定性策略走完一条 25 步路径,逐步骤打印好感度、置位 flag、持有物品与 FNV-1a 状态摘要,并给出节点数等于选择数加一的整数不变量。
《叙事策划与叙事工艺》卷三《叙事体验与路径度量》目录:16 章 48 节,把玩家路径变成可度量的对象——路径的表示与采样、分支覆盖率与缺口、结局分布熵、张力曲线与 MAE、后果可见性、节奏密度、好感度曲线形状、一致性残差,以及反推与演化回归。本页给出分工说明、六档可运行性口径与全书 16 章 48 节完整清单。
规格演化后(新增 flag、新增物品、新增好感度变量、新增节点),旧存档必须仍能读。本节实跑兼容性矩阵:四种新规格下旧存档全部读入成功并给出补默认值规则;迁移函数三条判据——缺失字段补默认、多余字段丢弃并报告、类型不符拒绝——逐条在本机实跑验证。
《叙事策划与叙事工艺》卷二《叙事引擎与分支系统》附录 A:把全卷 16 章 48 节散落的对话树节点与选项字段、嵌套数组条件文法与四类效果算子汇成索引表,给出本卷 S/P/W/D 四层共 43 个仿真脚本、31 个内核模块与 37 份产物的实盘清点,并附一段可跑的 TypeScript 对话树格式检查器——它连同三个注入负例证明格式约定不是纸面条文。
浏览器里走完同一条确定性策略路径(总是选第一个可用选项,共 25 个事件),读出终局状态,与 Python 侧产物比对 canonical 串与 FNV-1a 摘要。三侧锚点一致:摘要 4162796975、节点 e_lit、结局 灯守。只对整数状态写逐位一致。
给可玩原型挂一套稳定的 data-* 选择器契约:当前节点、每个选择按钮、四个好感度、已置位 flag、持有物品各有一个可寻址的属性。再用 Playwright 无头打开这页单文件 HTML,断言 DOM 已绘制、按钮数等于当前可用选项数、点击后节点推进。
把 config/story_spec.json 与 core/src/inkwell/engine.ts 一起内联进一个单文件 HTML,产出 web/dist/ne09.html。本节实测产物 41641 字节、script src 0、外部 http(s) 链接 0,给出零外部依赖的自检口径与双击即玩的最小交付形态。
四条必须恒为 0 的残差:钳位(可达状态与转移后都在 [0,100] 内)、初值(每个声明角色的 init 落在自身 range 内)、累积口径(新值恒等于 clamp(旧值 + add),set 不写好感度)、门限可达性(15 条门限均可跨过)。四条残差合计 0;lin 与 hai 有 affinity 声明却不在 state.vars,按「声明未用=报告,用了未声明=FAIL」报告。
好感度的分段在规格里就是一串 [aff, 名, >=, 整数] 阈值门,长在选项 when、节点 line_when、结局 when 三处。本节把 15 条门限全抽出来,再逐条扰动 ±5,在同一采样口径下重算覆盖率与可达结局集合,找出 4 条临界门限;并如实说明为什么放宽 5 点不起作用而收紧 5 点才起作用——基线覆盖率已饱和在 1000‰。B 层只打印 WARN,退出码 0。