能验,但要换一种验法:把分支表从剧本文字导成带 ID 的路径清单,再用脚本把从开场到每个终点的走向全部枚举出来,然后对着结果看断头路、走不到的节点和重复终点。人工点几遍只能覆盖主线和顺手点到的支线,嵌套分支和回头选项很容易漏。脚本枚举不判断台词写得好不好,它只保证结构上每条路径都落得到终点。
判断标准可以落到三条:分支表能导出“节点ID + 选项文本 + 去向ID + 是否终点”,遍历脚本能枚举出全部路径,并且每条路径都以标记过的终点结束。适用场景是分支超过一屏、有嵌套或回头选项的剧本。做法是先导表、再遍历、最后拿路径时长做一次粗筛。边界也要说清:脚本只验结构,不验选项是否吸引人、情绪节奏是否成立,这些仍然要人工看。
把分支表整理成带 ID 的路径清单
剧本里的分支通常写成缩进段落或大纲,人看得懂,脚本读不了。第一步是把每条选项拆成一行记录,只保留四个字段,不要在这一步混入演出描述。
| 节点ID | 选项文本 | 去向ID | 是否终点 |
|---|---|---|---|
| start | 推门进去 | hall | 否 |
| hall | 往左走 | left_room | 否 |
| hall | 往右走 | side_path | 否 |
| left_room | 回头 | hall | 否 |
| side_path | (无选项) | — | 是 |
字段含义:节点ID 是这一行选项所在的节点,同一节点可以有多行;选项文本是观众看到的按钮文案,留空表示该节点没有可选项;去向ID 是点下去之后到达的节点;是否终点标在“去向ID”这个节点上,表示走到它就是一次正常结束。
终点节点一定要单独留一行、选项文本留空、是否终点写“是”。否则脚本无法区分“设计好的结尾”和“忘了写后续”,这两种情况在数据里长得一模一样。整理完导成 CSV 或 JSON 都行,只要列名固定,后面脚本不用改。
写一段通用遍历骨架,枚举从开场到每个终点的全部路径
下面这段不依赖任何平台接口,只吃上面那张表。用栈或递归都行,这里用递归,把当前走过的节点集合带上,遇到回环就停,避免无限深入。
import json
rows = json.load(open('branches.json', encoding='utf-8'))
START = 'start'
ENDS = {r['targetId'] for r in rows if r['isEnd']} # 被标成终点的节点
out = {}
for r in rows:
if r['optionText']:
out.setdefault(r['nodeId'], []).append(r)
paths = []
def walk(node, path, seen):
if node in ENDS:
paths.append(path + [node + ' (终点)'])
return
nexts = out.get(node, [])
if not nexts:
paths.append(path + [node + ' (断头)'])
return
for r in nexts:
step = node + ' -[' + r['optionText'] + ']-> ' + r['targetId']
if r['targetId'] not in out and r['targetId'] not in ENDS:
paths.append(path + [step + ' (去向缺失)'])
continue
if r['targetId'] in seen:
paths.append(path + [step + ' (回环)'])
continue
walk(r['targetId'], path + [step], seen | {node})
walk(START, [], set())
for i, p in enumerate(paths, 1):
print(i, ' | '.join(p))
print('路径总数:', len(paths))
把 branches.json 放在同一目录直接跑,输出大概是这种形态:
1 start -[推门进去]-> hall | hall -[往左走]-> left_room | left_room -[回头]-> hall (回环)
2 start -[推门进去]-> hall | hall -[往左走]-> left_room
3 start -[推门进去]-> hall | hall -[往右走]-> side_path (终点)
路径总数: 3
注意两处替换项:START 改成自己剧本的开场节点 ID;回环的判断按“当前路径”算,不要用全局已访问集合,否则第二条从同一节点出发的合法路径会被误判成回环而漏掉。
检查三类问题:断头路、不可达节点、重复终点
- 断头路:节点有选项但去向ID为空或表里查不到;或者节点没有任何选项,却没被标成终点。前者是数据缺了,后者是剧本忘写结尾。
- 不可达节点:节点ID出现在表里,但在遍历输出的所有路径中一次都没经过。常见原因是某个选项被改过去向,旧节点留在了表里。
- 重复终点:多个终点节点的选项文本、去向和结尾描述完全一致,本质是同一个结尾占了两份,改一处容易漏另一处。
输出时给每类固定前缀,方便直接搜索和贴回工单:
[断头] node=hall 去向ID为空 出现在路径: start -[推门进去]-> hall
[不可达] node=old_room 被 1 条选项指向,0 条路径经过
[重复终点] end_a / end_b 选项文本与去向完全一致
断头路按条数看,不可达按“被指向次数 / 实际经过次数”看,重复终点按组看。三类问题不多的时候不要急着改,先把清单发给写剧本的人确认哪条是设计意图——有些看似断头的节点其实是留待续写的占位。
把枚举结果和时长对一遍
结构没错不代表体验没错。在表里补一列可选字段 durationSec(节点停留时长),没有就先估一个区间中值,标明是粗估,别拿它当验收数据。
汇总方式是按遍历输出的每条路径,把沿途节点的 durationSec 加起来,同时记节点数。输出一张小表:路径编号、节点数、总时长、选项链。然后看两头:节点数和总时长明显偏短的路径,往往是一路点到终点、中间没有可停留的内容;明显偏长且节点数多的,要确认观众是否有耐心走完。
偏短偏长的界限不要硬套统一阈值,按自己剧本的正常区间定一个占位值就行,比如单次体验期望 40 到 90 秒,那低于 40 秒的路径先标出来人工看。这一步只做粗筛,不做结论。
改完剧本再跑一次脚本,比对前后差异
改完不重跑,等于没验。复跑方式就是原封不动再执行一次遍历脚本,把输出重定向到新文件,和上一版并排比。要比对的字段建议固定成这几项:
- 路径总数、终点节点数、断头条数、不可达节点数;
- 每条路径的节点数和选项链,按选项链做文本比对,找出被删掉或被合并的路径;
- 新增的终点是否在旧清单里出现过,避免改一处多出一个重复结尾。
验证标准有两条:一是枚举出的路径条数与分支表里选项的展开关系一致,也就是每个节点的每个选项都在某条路径里出现过一次;二是每条路径都能落到被标成终点的节点上,输出里不出现“断头”和“去向缺失”。
还有一处边界要提前认下:带回头选项的剧本,路径条数会随回环截断规则变化,脚本截断了不代表这条回环不该存在。这类路径要单独列出来人工确认是不是设计意图,不要直接当成缺陷删掉。