两文件是流水线流程件而非游戏描述件,此前住在 design/gdd/ 造成三处不适配: gdd_lint 必须为它们逐条关掉体裁规则、格式权威却写在 doc-conventions §12、 与同族的 roadmap(在仓库根)分居两处。迁到根后按 roadmap.md 的无编号风格改名。 - 迁出:design/gdd/06-待办清单.md → 待办清单.md;07-体验验证清单.md → 体验验证清单.md - 指向改写 53 个文件:GDD 各系统文档指路存根、四份规约、02 根索引、README/roadmap/AGENTS - 两份清单自身 74 处出站链接按新基址重指(../art → design/art、systems → design/gdd/systems) - gdd_lint:待办/体验验证两文件的 R1/R3/R5/R7 豁免行随迁删除(已脱离扫描面),后门表清零 - pipeline_gate 默认路径、log_analysis/render_gate 称谓、repo_structure_audit 根文件白名单同步 - 保护面补写:两份清单列入实现批禁写清单(AGENTS 两类批条 / process-conventions §12 / 架构文档域表), 条目状态只由流水线各段推进 - 范围外未动(用户令「全不碰」):reports/ 与 design/research/ 的 22 处历史指向、game/ 两处注释
21 KiB
关卡节点图生成
一句话:单关节点图怎么从固定配置变成逐局随机——图模板池、抽签与实例化 状态:v0.5 台阶一已实施(迭代 5 T5,2026-09-28 落 master 前挂 worktree m3-iter5-20260928——15 张骨架+node_map 扩签+首次冒险固定脚本+池内全模板校验+MC 分层出数,冒烟 576/0;文档本节规则与实现一致性以本批落地为准,台阶二/三仍后置) 旧版参考:无(旧体系未单独成文;事项自 2026-09-20 立项,池重生成已落地〔2026-09-29 返工批,二轮目检 15/15 过〕,原待办清单条目已完成移除)
设计目的
现行每关的节点图是关卡表里写死的一行行集——玩家每一局走的是完全相同的图。对照参照游戏:杀戮尖塔每层抽一张新地图,哈迪斯的房间编排逐局变化——布局变化本身就是"再来一局"的驱动力的一部分,而本作这块目前是零。
本文立项把单关节点图从固定配置升级为逐局随机。边界先说死:本文只管图从哪里来;图怎么走、走到每个节点发生什么、图合法性怎么校验,全部仍是关卡节点地图的现行规则,本设计对它们零改动——生成侧的职责止于"生产一张合法的图",生产出来的图进的是既有校验、既有行进、既有兑付。
系统范围
边界这样划:图模板与模板池是内容资产(与敌人表行、棋盘参数同级的配置内容),抽签是玩法随机(消费主种子派生流),实例化的产物就是关卡表行集——行集内容的责任方仍是冒险结构,本文管的是行集怎么被生产出来。
负责:
| 负责 | 说明 |
|---|---|
| 图模板的结构定义 | 一张模板有哪些组成部分、满足什么条件才算一张合法模板 |
| 模板池与关卡表接线 | 每关从哪个池抽、池怎么随关卡表配置 |
| 抽签时机与随机流 | 什么时候抽、消费哪条封闭流、可复现域怎么覆盖 |
| 实例化与校验交接 | 模板落成行集,交给既有的图合法性校验 |
| 台阶路线与测量对账 | 三级台阶的推进条件;模拟读数不被随机污染的口径 |
不负责:
| 不负责 | 归谁 |
|---|---|
| 节点图怎么走、选路、兑付触发、收束点 | 关卡节点地图(现行规则零改动) |
| 图合法性校验的检查项与算法 | 关卡节点地图(LevelTable.validate 现行——生成侧只是它的上游供给方) |
| 池里放几张模板、每张模板里放什么遭遇数值 | 冒险结构(行集内容责任方)与难度曲线与Boss(数值所有权) |
| 战斗节点内部的敌人配置与初始化 | 战斗循环(节点编号寻址口径零改动) |
| 节点图的画面呈现 | HUD框架(节点态照旧读图,零新呈现语义) |
在核心循环中的位置
冒险结构 ──开始关卡──▶ 关卡节点图生成(抽模板 → 实例化行集)──行集──▶ 关卡节点地图(校验 → 选路行进)
核心循环里"关卡:节点链逐节点推进"这一段之前,多了一步"这一关的图是哪张"的抽签——本系统就站在这一步。它向上接冒险结构的开始关卡请求,向下把实例化好的行集交给关卡节点地图;生成之后的一切(校验、行进、兑付、收束)与本系统无关。
核心规则
概念三件套:图模板、模板池、图模板抽签
人话阶段:本系统只有三个概念,弄清它们谁包含谁,整个系统就读懂了。
图模板(本文含义:一张手工编写的合法节点图骨架——一个节点集合〔每个节点带类型标注与关卡内唯一编号〕加一个连线集合)是内容资产,不是运行期生成物。实例化(本文含义:把模板原样照抄落成该关的行集——节点集合与连线进关卡表行,零变换)之后,它就是关卡节点地图要加载的那张图。
| 概念 | 一句话定义 | 归谁 |
|---|---|---|
| 图模板 | 手工编写的合法节点图骨架;实例化即行集 | 本文(结构规则);内容归冒险结构 |
| 模板池 | 关卡表逐关行配置的同关可抽模板集合,池大小 N(N≥1) | 本文(规则);配置承载归数据配置 |
| 图模板抽签 | 关卡开始时从池中均匀抽一张 | 本文(时机与流);流基础设施归数据配置 |
模板池挂在关卡表逐关行上:行集从"单一节点集合"扩为"模板池"。N=1 的池里只有现行那张图,逐局抽签退化回固定图——这是回滚档(本文含义:任何一关把池缩回一张模板,该关行为就与今日固定配置逐位等价的保险形态)。整个设计因此可增量落地:先全关 N=1,再逐关扩池。
抽签消费主种子派生的统一随机流,走扩签通道新立用途标签 node_map(统一流口径已定——2026-09-27 两轮复裁落地;标签不参与派生、只进事件日志)。
出度上限与层宽(2026-10-01 用户拍板 T17 目检):单节点出边数(出度)最大为 2——加载校验第七检(fanout_violation 加载致命)。规则连带(数学约束):出度 ≤2 + 入口唯一下,BFS 层宽上限 = 4(层宽序列上限 1,2,4;2026-09-29 旧规则「层宽上限 5」与出度约束不相容,随本拍板废止)。现行池 = tools/t17_fanout2_rebuild.py 按两规则重建(五骨架:宽廊双干〔现行〕/三干中廊/宽廊斜岔/三干宽门/窄廊短支——层宽序列 1-2-4-1-1 / 1-2-3-2-1 / 1-2-2-2-1,最大层宽 4 应用足)。
内容纪律:每张新模板等于一个小内容批——模板里每个战斗与 Boss 节点都必须在敌人与棋盘生成的数值表里有精确行,义务从冒险结构加关纪律的"每关一行集"细化到"每模板一行集";冒烟覆盖义务同批扩为遍历池内全部模板校验,不只抽中的那张。
抽签时机与可复现
人话阶段:什么时候抽、抽完怎么保证复盘对得上,是本系统接入现有随机纪律的两件事。抽签时点 = 开始关卡处理内、加载节点图之前,逐关各抽一次(不是冒险开始一次抽齐三关)——抽签时机贴着"这一关的图要用"的时点,主种子的复现域天然覆盖。
冒险结构(开始关卡:关卡序号、当前生命、生效上限)
→ 关卡节点图生成:按关卡序号取模板池 → 抽一张(图模板抽签流,恰一 scope)
→ 实例化为行集 → 交图合法性校验(关卡节点地图现行,零改动)
→ 关卡节点地图加载行集 → 呈现选路停点(现行流程原样续跑)
可复现域沿用既有口径:主种子 + 全局取数计数器(2026-09-27 统一流;旧表述系并流前口径)(语义权威归冒险结构「失败重开与主种子」节,本文不复述)。抽签每次消费恰一个带编号的 scope,计数器随既有快照契约入档——存档序列化零新增域。同主种子同操作序列的两局,逐位同一张图。
教学位随骨架走:各骨架的"第一场战斗节点"沿用教学位纪律(首遇位固定数量不加骰)——教学位属性是骨架的内容标注,不跟节点编号走(拍板点①裁决后第一关也参与抽签,例外见下节)。
首次冒险的固定脚本(拍板点①裁决写入文档)
人话阶段:教学关不该第一局就随机——玩家第一次玩,地图必须是设计好的那一张。拍板点①裁决(2026-09-27):全部关卡都参与抽签,唯独玩家的第一次冒险全程走固定脚本。这也是参照游戏的通用做法(首次冒险特殊脚本)。
| 事项 | 规则 |
|---|---|
| 判定标志 | 存档域新增布尔位「首次冒险已进行」(owner=存档序列化,实施批落 schema) |
| 判定时机 | 开始新冒险时读标志:false = 首次脚本模式;true = 正常抽签 |
| 脚本模式内容 | 全关零抽签——每关直接取池内的现行固定模板(即回滚档成员),逐位等价今日行为,无需新做脚本内容 |
| 置位时机 | 冒险收束点置位(完成/失败/放弃三结局同写)——存档级属性冒险期只读、写入只在收束挂点(既有纪律) |
两条边界:首次冒险中途退出游戏(未到任何收束点)则标志未写,下次新冒险仍走脚本——教学重放,可接受;首次冒险内死亡或放弃即结算置位,第二次冒险起抽签。
测量口径:模拟试玩与数值验证夹具恒置标志为 true(永远走抽签路径)——首次脚本分支不进任何读数。
三级台阶路线
人话阶段:2026-09-21 呈报的三级台阶(图模板抽签 → 骨架固定+属性随机 → 全程序生成)随本批立项成文为正式路线。每一级都比上一级多一点随机,每一级都必须过同样两条不变量:①产物过图合法性校验(检查项口径归关卡节点地图,本文不复述);②商店房"每关恰 1、关底 Boss 前一位必经位"的编排约束在生成侧成立——冒烟断言照旧把关,校验零新增。
| 台阶 | 内容 | 前置条件 |
|---|---|---|
| 一:图模板抽签 | 每关模板池 N≥1,逐局从池中抽一张手工模板实例化 | 测量对账条款先行(见下节);每张新模板按内容纪律配齐数值行 |
| 二:骨架固定+属性随机 | 模板拓扑定型,节点类型在候选槽位(本文含义:模板给某个可变位列出的允许节点类型集合,实例化时从集合内均匀掷一个——例:某槽位允许{奖励,恢复},掷中哪个就是哪个)内掷定 | 台阶一完成;商店房所在位排除出候选槽位(保必经位) |
| 三:全程序生成 | 读参数(深度、分支密度、节点类型配比)直接构造图,构造失败重掷(重掷预算同棋盘生成 max_gen_attempts 纪律) | 台阶二完成;图合法性校验作最终守门 |
台阶二的随机量入账:候选槽位掷定消费同一条图模板抽签流的后续 scope,计数器域照旧覆盖,可复现性零损失。台阶三的参数取值域届时随本文修订登记,本草案不预设。
台阶一用一句话说:给每关手工做几张不同的地图骨架,每局开始时随机抽一张用(用户裁决路线确认:骨架不完全固定、多份随机调取)。玩家看到的是同一关每局长得不一样——这局岔路多、下局一条窄道——但每张都是人工画好、审过合法性的图,不是程序乱长。台阶一不做两件事:房间类型不掷骰(某房间是奖励房还是恢复房仍由骨架定死——那是台阶二),程序不算图(那是台阶三)。首版池规模 = 每关 5 张(拍板点④),其中 1 张 = 现行固定图。
测量对账:别让随机污染读数
人话阶段:2026-09-21 呈报暂缓的三理由之一是"污染 MC 读数"——模拟试玩与数值验证的通关率是按固定图测的,图一随机,读数波动就混进了布局差异。本节是消解条款,三条:
- 分层出数:模拟试玩与数值验证工具的读数按「关卡 × 图模板」分层记录;混合池的整体通关率 = 各模板条件通关率的加权平均(权重 = 抽中概率,均匀池即算术平均)。
- 基线可比:基线批(与基线数据逐项比对、重投)跑固定模板口径——把池临时缩到 N=1(回滚档)或固定抽签 scope,保证新旧基线在同一张图上对话。
- 实施门:随机量(扩池)上线排在难度曲线与Boss曲线调参批完成之后——现行行值 provisional 收带前叠加布局随机,归因会失真;抽签机制本身可先行落地(池保持 N=1 行为零变化,见拍板点③选项 C)。
另两条暂缓理由顺带消解:类型集未稳——五类节点封闭枚举已随商店房批定稿,扩类走文档修订的既有通道对模板同样适用(模板 schema 跟随枚举走);内容量不足——台阶一的模板就是内容批,按内容批节奏逐张走,不设批量生产目标。
拍板点(登记 待办清单,顺带更新条目)
| # | 拍板点 | 状态 |
|---|---|---|
| ① | 第一关(教学关)是否参与抽签 | 已裁决(2026-09-27):参与——仅首次冒险走固定脚本,见「首次冒险的固定脚本」节 |
| ② | node_map 随机流增流 |
已裁决(2026-09-27 两轮复裁):统一用一个流——全游戏 rng 架构批先行(评估书已交付,2026-09-27),本特性实施排队其后 |
| ③ | 台阶一实施时点 | 已裁决(2026-09-27):机制先行骨架随后——特性内先落抽签机制(池暂 N=1 行为零变化)、骨架内容批随后扩池;整体仍排在架构批落地之后 |
| ④ | 首版池规模 | 已裁决(2026-09-27):每关 5 张起步(调参表已投值) |
| ⑤ | 抽签时点 = 关卡开始逐关抽 | 已裁决(2026-09-27):确认 |
证据面 A:随机流与「增流」是什么(拍板点②的背景)
人话:本项目的随机数只有一个总入口(随机服务,代码名 rng_service),硬性红线禁止任何代码绕开它自己掷骰——"同一个种子+同样操作序列 = 逐位相同的一局"靠这条保证,调试复盘和模拟对账都依赖它。总入口内部按用途分账:每种用途占一栏,叫一条流(本文含义:随机服务内部按用途分开记账的一栏独立随机序列,各有各的编号,互不串账)。现行清单七条:雷图生成、奖励抽取、敌人数量掷骰、命中掷点、出招摇号、变异布置、GP 掉落——清单是封闭的,新用途必须在清单里开新栏。
图模板抽签是一笔新用途的随机账,要在清单里新开一栏(拟名 node_map)——「增流」就是这个动作:给封闭流清单加一条新流。它之所以要拍板:加流有固定成本(改代码 + 补一组校验用黄金向量 + 加冒烟断言,纪律规定的三件套),不是随手配置。
| 选项 | 内容 | 代价 |
|---|---|---|
| A 增流(原荐) | 清单加 node_map 栏,抽签独立记账 |
三件套成本;图的抽签可独立复现,与其它系统零耦合 |
| B 借现有流 | 从某条现有流取号 | 省三件套;但这笔账和那个系统的账搅在一起——重放对方系统的随机序列会被抽签挤位,复现口径变含糊,不推荐 |
裁决注记(2026-09-27):用户两轮复裁后终选「统一用一个流」(全面执行),执行路径 = 全游戏 rng 架构批先行、本特性排队其后。评估书已交付(2026-09-27,git 历史可查)——核心发现:字面目标「单一随机源、逐位可复现」现行架构已满足(七条流系同一主种子的七个盐值分栏而非七个随机源);待裁决项 = 是否进一步并掉分栏计数器(方案甲维持分栏 / 乙物理并流 / 丙并流+全事件日志)。
证据面 B:台阶一详解与实施时点(拍板点③的背景)
台阶一(图模板抽签)落地后的样子:
- 玩家看到什么:同一关,每局地图的房间连接方式不一样(这局岔路多、下局一条窄道),但每张都是人工画好、审过合法性的图——不是程序乱长。
- 做三件事:①每关 5 张骨架(拍板点④,其中 1 张 = 现行固定图);②开始关卡时抽一张实例化;③首次冒险走固定脚本(拍板点①)。
- 不做两件事:房间类型不掷骰(那是台阶二);程序不生成图(那是台阶三)。
- 内容形态:一个内容批 = 15 张骨架(3 关 × 5)+ 池内全模板加载校验 + 冒烟断言。骨架里只有房间和连线,不新增敌人数值——遭遇表按节点编号共用:两张骨架若都在 3 号位开战,3 号位打的敌人是同一套(表键不含骨架维);骨架差异体现在拓扑与房型排布,不体现在"同编号打不同敌人"。要打破这条需给遭遇表键加骨架维,归实施批 schema 拍板。
实施时点三个选项:
| 选项 | 内容 | 后果 |
|---|---|---|
| A 机制+骨架立即全上 | 现在做完整内容批(含扩池到 5 张) | 曲线调参批未完成,数值基线在动;地图一随机,模拟读数的波动归因变难(数值动了还是图动了分不清) |
| B 全部等调参批完成(v0.1 原荐) | 机制与内容都排在曲线调参批后 | 读数对账最干净;机制落地要等 |
| C 机制先行、骨架随后(原荐) | 先落抽签机制但每关池暂放 1 张现行图(行为零变化),骨架内容批画好后一次扩到 5 张——扩池时点守测量对账实施门 | 机制与内容解耦:扩池前读数零污染,扩池后分层出数条款接管;不用等调参批 |
依赖与输出
这张表就是删除任何一方时的影响面清单。
| 方向 | 系统 / 文档 | 接口要点 |
|---|---|---|
| 上游 | 冒险结构 | 开始关卡流程是抽签时点的宿主;主种子与派生流的语义权威;行集内容责任方(模板内容归它) |
| 上游 | 数据配置 | 关卡表模板池列的承载与统一读取;node_map 统一流用途标签(扩签已落地 v0.5)与流种子派生公式的基础设施 |
| 上游 | 难度曲线与Boss | 模板内遭遇数值的所有权;曲线调参批完成是台阶一的实施门 |
| 下游 | 关卡节点地图 | 实例化行集的供给方;其图合法性校验与全部行进规则零改动 |
| 下游 | 存档序列化 | 抽签 scope 计数器入既有快照契约(主种子 + 流计数器),零新增域;「首次冒险已进行」布尔位为唯一新增存档字段(实施批落 schema) |
| 下游 | HUD框架 | 零新语义——节点态照旧读图,模板差异对呈现层透明 |
P0 最小闭环(台阶一实施批)——✅ 已落地(v0.5,2026-09-28)
实施前置已解除:统一随机流批已落地(2026-09-27 两轮复裁——统一流+全事件日志);抽签接入形态 = 统一流扩签新用途标签
node_map(扩签通道 = 改代码 + 评审)。
- 关卡表逐关行扩模板池结构(承载归数据配置,schema 细节随实施批定)。
- 池内全部模板加载期预校验(违规致命,fail-fast 不变)。
- 存档域加「首次冒险已进行」布尔位:开始新冒险读标志(false = 首次脚本模式,全关零抽签取现行固定模板),冒险收束点置位(三结局同写,写入只在收束挂点)。
- 开始关卡处理内抽签实例化(
node_map流恰一 scope——拍板点②)。 - 既有图合法性校验照跑零改动;关卡节点地图与战斗侧全链零改动。
- 冒烟断言:抽签确定性(同种子逐位同图)、池内全模板校验、N=1 池与现行固定图逐位等价、首次脚本分支(标志 false = 固定图 / true = 抽签)。
P1 / P2 后置
| 优先级 | 内容 | 后置原因 |
|---|---|---|
| P1 | 台阶二:骨架固定+属性随机(候选槽位掷定) | 台阶一完成后另批立项;商店必经位的槽位排除规则顺带成文 |
| P2 | 台阶三:全程序生成(参数化构造) | 台阶二完成后另批立项;参数取值域与重掷预算顺带修订本文 |
调参表
本系统无已锁数值。台阶一实施后进表:
| 参数 | 域 | 现行值 | 说明 |
|---|---|---|---|
| 模板池大小 N | int [1,+∞),关卡表逐关行 | 每关 5(2026-09-27 拍板点④;2026-09-28 台阶一实施批落地,3 关 × 5) | N=1 = 回滚档;扩池 = 内容批 |
| 重掷预算(台阶三) | 未立项不设值 | — | 台阶三立项时随本文修订登记 |
验收标准(草案——随拍板点裁决完成,实施批落地时按本节验收)
- 池内全模板加载期通过图合法性校验,违规致命(校验面不只抽中的那张)。
- 抽签确定性:同主种子同操作序列逐位同一张图;抽签 scope 可独立回放。
- 回滚档成立:N=1 池的关卡与现行固定图逐位等价。
- 首次冒险脚本:存档标志 false 时全程固定图(逐位等价现行行集),冒险收束点置位标志恰一次;标志 true 后每局抽签。
- 商店房编排约束在每张模板上成立:每关恰 1 个商店房、关底 Boss 前一位必经位。
- 零新面:关卡节点地图行进/兑付/收束断言与战斗侧寻址断言全绿,零改动。
- 测量分层:模拟读数含「关卡 × 图模板」维度;基线批可固定模板复现。
- 内容纪律:每张新模板的战斗与 Boss 节点在敌人与棋盘生成的数值表中有精确行(冒烟遍历池内全模板)。
待办
本系统无未决待办(结构随机生成池已落地);后续新增待办统一登记待办清单,本节不再逐条登记。