5.2 可达状态的枚举
逐层 BFS 把 9895423 个可达状态一层层展开:给出每一层的新增与累计、峰值 frontier,以及 4 个好感度变量的实际值域。去重键是状态的规范串,层号就是最短步数。本节贴出 32 层完整输出,并给出预算 12000000 与峰值内存 3132.8 MB 的口径。
tag
逐层 BFS 把 9895423 个可达状态一层层展开:给出每一层的新增与累计、峰值 frontier,以及 4 个好感度变量的实际值域。去重键是状态的规范串,层号就是最短步数。本节贴出 32 层完整输出,并给出预算 12000000 与峰值内存 3132.8 MB 的口径。
一个状态等于节点乘以 4 个好感度、18 个 flag、9 个物品。本节先算出朴素上界 48 × 101 的四次方 × 2 的 18 次方 × 2 的 9 次方约 6.70×10 的 17 次方,再用 BFS 完整枚举跑出真实可达的 9895423 个状态,并把两者相差约 11 个数量级的来源拆成好感度值域、flag 与物品位图、变量相关性三层。
对同一串选择序列,Python 与 TypeScript 各自推进一遍,比较 canonical 串与 FNV-1a 32 位摘要。本节实测 500 条路径全部逐位相同,并给出前 3 条摘要值对照表;FNV-1a 的 Math.imul 与 Python 的 & 0xFFFFFFFF 是两侧一致的关键。
随机路径采样只用自实现的 Lehmer MINSTD,种子一固定,回放就必然复现。本节实测同一 (seed=20261016, n=500) 采样两次 500 条选择序列逐条相同,而换到 seed=20261017 后逐条相同数为 0、结局分布也整体改变。
选择序列是玩家按下的一串 choice_id,路径是引擎走出来的 node_id 序列。本节用同一趟推进给出双表示对照,实测 115 条选项去重后只有 56 条唯一跳转,因此选项数并不等于路径形状数,并用 Kahn 拓扑排序证明 48 个对话节点构成一张无环 DAG。
快照是某一刻的完整状态,差量是相邻两快照间真正改变的字段。本节沿一条 25 步的确定性路径输出逐步骤差量表,并点出 7 个 flag 与 1 个物品被写却从未被任何条件读取——这类「只增不减的信息」按声明未用=报告处理,不判 FAIL,只如实报出。
确定性是同一输入同一输出,幂等是重复应用结果不变,两者常被混为一谈。本节把状态推进拆成三条可断言判据:函数性、set 类算子幂等、add 故意不幂等,并说明全书那句「同输入确定性违例 0」指的是同一状态到同一后继状态,而不是「重复 add 不累加」。
效果算子只有四个,每个只写一个命名空间:set 写 flag、add 写好感度、grant 与 revoke 写物品。本节把全规格 138 次写入摊平数一遍,校验写入域越界 0,并点出声明了却一次未用的 revoke;P 层用同一批选项断言命名空间互斥,并以把 add 放大到百万级仍被钳回证明钳位确实生效。
可满足性问的是:一个条件在有限状态域里到底有没有解,会不会恒假。本节用边界值枚举在「节点 × 4 个好感度 × 18 个 flag × 9 个物品」上判定,57 个条件位点里死条件 0 个;并处理一处真实特征——lin 与 hai 声明了好感度却不在状态域里,任何门限都结构性不可达。
短路是求值器的一条优化纪律:and 左假即停、or 左真即停,但它绝不能改变结果。本节给出短路与不短路两种实现的节点访问步数对照——本规格 57 个位点在 16 个代表状态下 eager 1008 步、短路 992 步——并声明复杂度上界:树内 n 个节点时短路步数恒不超过 n。
条件在规格里是嵌套数组写成的表达式树,求值器就是把它递归地读成真假。本节给出六种形态的求值语义,用 Python 与 TypeScript 两条链把 story_spec.json 里 57 个条件位点全部求值一遍,并把声明域与使用域分开数:声明 6 种形态只用 5 种,声明 4 个比较算子只用 >= 一种。
把同一份规格上的三处消费串成一次联调:穷举给出完整可达状态数与结局分布,执行让确定性策略 first 走到底并算出整数摘要 4162796975,度量给出覆盖率与张力曲线残差。P 层用 TypeScript 内核重放同一条路径,摘要与 S 层逐位相同,验证引擎与规格同源。
装配检查表把本卷十五节交付的零件收成十二项可断言的检查:规格可解析、引用完整、写入域合法、整数不变量残差 0、规模对账、BFS 与基线一致、goto 全部存在、结局全部可达、跨语言锚点一致、W 层零外部依赖、覆盖率达目标、无卡死路径。S 层脚本逐项跑出 PASS/FAIL 表,任一项 FAIL 即非零退出。
把基线摘要、结局分布、整数不变量残差落成回归基线文件,规格每改一次就重跑比对。本节给三类告警:摘要失配、分布漂移、残差非零,各配一个内存内注入负例,实测三组全部触发。告警只打印 WARN 并 exit 0,与 B 层口径一致;而负例测试必须硬失败。
把随机路径当模糊测试的输入:用自实现可播种 PRNG 跑 20000 条路径,断言全部终止于 5 个结局之一、无卡死、无越界。实测分布为离港 10074、沉没 5507、缄默 3223、灯守 1018、真相 178,最稀有的真相仅 178 条。Python 与 TypeScript 两侧对同一批路径的终局状态摘要逐位一致。
属性测试不问「跑一遍对不对」,而问「一条命题是否对所有输入都成立」。本节把第 3 / 6 / 8 章的不变量写成六条属性:好感度恒在 [0,100]、set 类算子幂等、add 故意不幂等、静态死路 0、5 个结局全可达、每状态至少一个可用选项,并给每条属性配一个必须 fail 的注入式负例。
什么必须逐位相同、什么允许不同,本节写成一张容差表:整数状态(好感度、flag、item、节点 id、路径)必须逐位;浮点派生量、对象键序、文本渲染、时间戳允许不同。负例把好感度钳位上限从 100 改成 99,实测 12 / 500 条路径摘要改变,首个分歧点定位到路径 #13 第 20 步 c_fg_1a 的 shen。
同一条确定性策略路径(总是选第一个可用选项,25 个事件)在 Python、Node 内核与浏览器内联三个宿主上推进,终局节点 e_lit、结局 灯守、FNV-1a 摘要 4162796975 三侧逐位相同。本节给出宿主差异清单:整数运算、取模、Math.imul 与大整数、对象键序、Unicode 归一化,逐条说明为何不影响状态摘要。
摘要用 FNV-1a 32 位,只对 canonical 规范串(状态)做,不对台词文本做。本节实测同一批 500 条路径(seed=20261016)在 Python 与 TypeScript 两侧逐位相同 500 / 500,前 3 条摘要为 3976226298、3848604931、1198029437,并与卷一同种子采样逐位相同,证明卷二未另立真源。
四类文本一致性校验一次算齐:漏键、悬空键、变体覆盖、占位符。前两类残差为 0;变体覆盖报出 d_lt_2 欠声明(有 line_when 无 line_else,按规则回落,只报不判失败);占位符残差 0,并给一条负例。本章只做结构一致性,译文质量与字体覆盖需真实业务环境,命令行不可复现。