Files
gplan-2/design/gdd/d2-data-config.md
T
2026-09-14 18:18:49 +08:00

612 lines
61 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# D2 数据配置与数值表(Data Config & Balance Tables)
> **状态**:Approved · **版本**:v2 · 2026-09-14
> **承载支柱**:P1、P2(基础设施层,间接支撑全部四支柱) · **层级**:Layer 1 Foundation · Milestone MVP · 设计顺序第 1 位
> **一句话**:D2 把全部影响手感与平衡的数字收进可配置、可校验的数值表,经 ConfigDB 单点发放、RngService 统一种子化——战斗可复现、平衡可热调、headless 可测的数据底座。
> **推演件**:`design/gdd/analysis/d2-data-config.md`(本文件只收结论,理由在那里)
## 设计目的
D2 是全部玩法数值的单一事实源:data/tables 的 .tres + autoload ConfigDB + autoload RngService——schema 先行、数值回填。没有 D2,参数散落硬编码:调一次难度改五处代码、战斗无法用固定种子复现、headless 自动化测试无从谈起。它是玩家永远不该注意到的地基:不制造情绪,但失职会直接摧毁 P1/P2 的信任底线。
1. 设计师"改数字不改代码"——全部影响手感的数字(AP、旗、折算比例、奖励值)集中一处可调,热重载即改即验,"这轮玩起来不对劲"能快速修正且不引入新毛刺。
2. 开发与测试"同决策必同结果"——RNG 统一种子化,相同决策序列必然相同战斗结果,回放 / 审计 / 自动化测试可复现。
3. 难度"越下越险由结构保证"——数值表天生带(关卡序号, 节点深度)两维,难度曲线可审计、可回溯,而非散落各处的魔法数字。
4. CI 与无窗口环境照样跑完整战斗——数据层零界面依赖。
## 系统范围
负责:
- 全部表 schema 类(data/schemas)与数据文件(data/tables)
- autoload ConfigDB / RngService · SCHEMA_VERSION 常量
- 加载 / 校验 / 热重载状态机 · 校验双通道与诊断收集器
- debug override 链 · 难度行解析 resolve
- 十张已定稿表 + 两张占位表的结构与键(数值由 owner 系统按设计顺序回填)
不负责:
- 平衡数值定值与玩法公式——伤害、掉落权重、成长曲线(owner 系统 GDD 与 registry:B1 / B4 / C3 等)
- 玩法随机预置——任何系统若需玩法随机(如结算浮动),自带方案走增流通道并通过 P1 评审(规则 5)
- 存档(D3)· 任何 UI 表现面
- 精英 / Boss 修饰语义(C1 / C3——DifficultyTable 只预留 elite_ / boss_ 命名空间)· MetaTable 键结构(C4)
硬约束:玩法数值零代码字面量 · 零 UI 依赖 · fail-fast 无软回退 · RNG 流集合封闭 · 表路径固定(debug 注入根除外)· release 无 override 源。
## 核心循环
### 数据加载与校验链路
```
进程启动(Unloaded)
│ ConfigDB._ready() / load_tables(root, collect_only)
▼
Loading ──── 按表清单逐一 load() ── 文件缺失 → Failed
▼
Validating ── 单个资源加载成功 → schema validate(),错误入诊断收集器
▼
┌ 校验双通道 ──────────────────────────────────────────┐
│ 游戏通道 collect_only=false(默认): │
│ 错误 → push_error 清单 → 致命错误路径 │
│ editor / CI / headless / release 一律致命 │
│ 测试通道 collect_only=true(仅 debug/测试构建): │
│ 收集不升级 → 冒烟脚本逐夹具断言类别 → 一次性退出 │
└──────────────────────────────────────────────────────┘
▼
Ready ────── 全部通过 → 类型化属性可读
```
### 热重载链路(仅 debug)
```
编辑器热重载 / 调试键 → Reloading
│
├─ 战斗中(set_reload_gate 已置位)→ 拒绝返回 false,停留 Ready
│ (战斗结束后手动再触发,不自动重放)
▼
重走 Loading → Validating
│
├─ 校验失败 → 保留旧表停留 Ready + debug 错误清单(唯一软着陆,不杀调试会话)
▼
校验通过 → Ready:新值经类型化属性可读 + config_reloaded 恰一次(仅 debug,D1 调试用)
※ RNG 状态线独立:Reloading / Failed 不触碰 RngService——计数器与 master_seed 不变(AC-4)
```
### RNG 取数链路
```
进程启动 → 流待命(未播种取数 = 致命)
▼
冒险开始 → C2 set_master_seed()(仅全流计数器全零时受理)
▼
scope 事件:board_gen = 每次生成新扫雷图(A1 换图时)
reward_roll = 每次 board install 批(格奖励批 + 清图奖励批)
encounter = 每场战斗实例化(B1 begin_battle——unit_count 区间骰)
│ open_stream(stream):计数器 +1 → R2 派生 → 返回已播种实例(同一 scope 事件内取数共用)
▼
冒险结束 → 流保留供复盘;快照 = master_seed + 各流 scope 计数器
(须在无在飞 open_stream 实例时切出——scope 事件边界之外)
```
### 出口分发
| 出口 | 触发 | 动作 | 约束 |
|---|---|---|---|
| 启动致命中止 | 启动加载任一致命错误 | 输出错误清单并中止构建;headless 独立进程非 0 退出 | 全构建一致,无软回退 |
| 热重载软着陆 | debug 热重载校验失败 | 保留旧表停留 Ready + debug 错误清单 | 唯一例外 |
| 运行期致命 | 请求越域 / 未播种流取数 | 游戏内致命错误路径;headless quit 非 0 | 不分构建 |
| 热重载成功 | debug 且门未置位且校验通过 | 新值可读 + config_reloaded 恰一次 | 战斗中被门拒绝 |
| 存档拒读 | SCHEMA_VERSION 高于当前 | 拒绝读取 | 比较归 D3,常量归 D2 |
## 表结构与回填(schema 先行)
规则:
1. 规则 3(结构先行、数值回填):全部数值表按"结构 + 数据"两件套交付——结构 = data/schemas/ 下一个自定义 Resource 类(每字段带类型、范围、默认值和注释),数据 = data/tables/ 下数据文件;D2 先交付全部 schema 与占位表(含键结构),具体数值由 owner 系统 GDD 回填,先有格子、后填数字。
举证:无计算。十张已回填表 schema 目录见本节,逐表字段行见调参表节。
2. 规则 4(进度坐标查行):跟着冒险进度成长的数值表(难度、生成参数、敌人配置)每行带(关卡序号, 节点深度)两维坐标——系统按坐标取数,无精确命中时取不超过请求坐标的最近已有行;敌人等实体键在命中行内按名字精确匹配,那一行没有这个名字就是悬空引用、直接致命中止,不做身份近似。
举证:解析规则(floor 取行、warning 四元组去重、越域前置守卫)见「难度行解析」节;悬空引用 = 加载期致命(规则 6)。
| 表的形状 | 行键怎么定 |
|---|---|
| 进度成长表 | 行键 =(关卡序号 level_index, 节点深度 node_depth) |
| 实体复合键表 | 进度两键之后再复合实体主键列(如 EnemyTable 的 enemy_id;合成规则写入 schema 注释) |
| 单行全局常量表 | 无成长语义的表允许只放一行 |
| 元进度表 | 存档/天赋/代币归 MetaTable,键结构归 C4 定 |
| 两维坐标之外的数值轴 | 归宿 |
|---|---|
| 战斗内轴(胜场/轮次等) | 由 owner 公式承载或作为表列;归属登记进 schema 注释(C3 定稿时声明) |
| 难度第三维"精英/Boss 修饰" | game-concept 定稿:难度 = f(关卡序号, 节点深度, 精英/Boss 修饰);DifficultyTable 预留 elite_ / boss_ 前缀扩展字段命名空间,语义由 C3 定稿——本 GDD 不定义任何修饰数值、不预写修饰枚举 |
| 两键定不了精英性的节点形态 | 如分支地图同深度并存精英与普通节点——允许改键,按接口漂移修订规则回写(与 MetaTable 同等豁免待遇) |
设计意图:进度坐标可以就近近似,"是哪种敌人"不能近似——身份近似会静默改变遭遇构成、损害 P2 归因。
### 十张已回填表 schema 目录(逐表字段行见调参表节)
| 表资源 | 键结构 | Rule 4 豁免 | 跨字段 / 结构校验(加载期致命) | 悬空引用政策 | Owner |
|---|---|---|---|---|---|
| ActionEconomyTable | 单行全局 | 单行全局定义表(先例) | 任一动作成本 ≤ ap_per_round(schema validate() 跨字段类别承载,条件性夹具登记) | — | A3 ✓ |
| VariationTable | 按 variant_id(含 unlock_level 列) | 定义表非进度成长表——进度语义承载于 unlock_level 列(schema 注释) | info_mask=hide_numbers ⇒ cell_constraint=safe_nonzero(夹具 variation_mask_constraint) | counter_item_ids MVP 不校验——A5 首个道具 id 回填时补校验 + 条件性夹具 | A4 ✓ |
| StatusEffectTable | 按 effect_id | 定义表非进度成长表(schema 注释承载) | magnitude:percent ∈ [-100,100] ∧ ≠0 / flat ≥ 1 跨字段校验 | effect_id 校验已激活(B4 RewardTable 首引 defense_up——夹具 b4_dangling_effect) | B3 ✓ |
| BoardGenTable | (level_index, node_depth)——首个进度键表(depth-0 锚行内建;第三轴落点 = Boss:Dmax 行约定 + elite 命名空间预留 + A1 OQ6 重审;改键退路 = 规则 4 接口漂移通道) | 无(进度成长表) | fog_domain_ok(F2)+ min_safe_feasible(F2b)+ cost_feasible(F2c:attempts×W×H ≤ 16384 provisional,运行期禁墙钟逃生) | — | A1 ✓ |
| EnemyTable | (level_index, node_depth, enemy_id)——每行集恰一敌种 | 无(进度成长表,A1 同型) | 每行集恰一敌种(行集内 enemy_id 唯一,违规加载期致命) | — | B2 ✓ |
| HeroTable | 单行全局 | 单行全局定义表(ActionEconomyTable 先例) | atk = 0 加载拒绝("0 基线 × 百分比增益恒零"死锁自 schema 层杜绝) | — | B2 ✓ |
| RewardTable | 按 reward_id(多行;pool_tag 分池不参与键) | 定义表非进度成长表(schema 注释承载) | 池空(pool_tag × 雾/非雾两态各 ≥1 活行)· clear_default 恒正当量 · buff⇒effect_id 必填 · safe_cell 活 buff 行集非空 · floor_supply_missing / no_damage_row(跨 RewardConfigTable) | effect_id 精确引用 StatusEffectTable(已激活) | B4 ✓ |
| RewardConfigTable | 单行全局 | 单行全局常量表(HeroTable / ActionEconomyTable 先例) | k_dmg=0 = F5 断言关闭(现行默认——刀保底语义由供给控制器战斗首件保证取代;no_damage_row 后闸托底) | — | B4 ✓ |
| SettlementTable | 单行全局 | 单行全局常量/结构表(HeroTable / ActionEconomyTable 先例) | segment_order 恒等拍板段序校验违例致命 · effect_pool_map 键 ⊆ StatusEffectTable 活行集 · p_perfect 载体域 {100,200} 域外加载致命——三行致命 manifest:segorder_violation / poolmap_dangling / pperfect_oob | effect_pool_map 悬空加载期致命 | B1 ✓ |
| AuxItemTable | 按 item_id | 定义表非进度成长表(schema 注释承载) | target ∈ 各空间 effect_kind 支持域交集(A5 承载——真视窄域护栏校验,夹具 a5_target_violation) | counter_item_ids(VariationTable → item_id)首引已定稿〔probe_dust〕、悬空校验激活随 D2×② 对侧批 | A5 ✓(原 HelperItemTable 占位更名转正——A5 定稿登记) |
### 未回填占位表
| 表资源 | 占位内容 | 键结构 | Owner |
|---|---|---|---|
| DifficultyTable | 成长参数 + 精英/Boss 修饰预留命名空间(乘数域 [0.1,10] registry 预登记;越域 clamp+warning = registry 登记的显式例外——组合事故非配置事故,非 fail-fast 场景) | (level_index, node_depth) | C3(第 12 位) |
| MetaTable | 代币/天赋字段名 | 单行全局(元进度轴豁免,C4 可改键) | C4(第 16 位) |
### MVP 内容行(事实登记归 registry)
| 表 | MVP 行集 | 值状态 |
|---|---|---|
| ActionEconomyTable | 单行:ap_per_round=8 · dig/插旗/拔旗=1 · rap_refresh_cost=8 · move/chain=0(冻结) | 定稿值,登记归 registry `action_economy` |
| VariationTable | 恰含 fog 行(unlock_level=2 · hide_numbers · safe_nonzero) | 岩浆/冰冻无 MVP 行(垂直切片建行) |
| StatusEffectTable | 四行 attack_up / defense_up / ambush / weaken(全 stack) | 值 provisional,锚验收归 B4/B2/B1;weaken = −65(值域 [60,85]——B1 F0 裁决,条款 2 锚闭合);defense_up = −30(减伤墙批 C3 投值,叠满两层 −60 帽——B3/registry 同批) |
| BoardGenTable | 参考行 L1 / L2 | provisional(C3 调参所有权) |
| EnemyTable | 三敌种行 goblin / bugbear / ogre_chief(C3 修订批转正——registry `enemy.mvp_content` 为准) | 值 provisional——溯源义务见登记与对账义务表 |
| HeroTable | 单行(max_hp / atk=6 原型锚) | max_hp 出处待查;atk provisional 归 C3 |
| RewardTable | 默认根行集八行:defense_up_basic / attack_up_basic / spark_aoe / heal_minor / knife_t1 / knife_adv / heal_pack / clear_blast | 值 provisional 归 C3;格子池四行即时类 + 注入池三行可选类(active_inject——供给控制器承载,原"即时 75% / 可选 25%"权重占比由注入机制替代)+ 清图槽单行 |
| RewardConfigTable | 单行 t_fog_min=2 · k_dmg=0 · n_draft=32 · p_min=8 · active_p0=0.4 · active_decay=0.5 | provisional 归 C3 |
| SettlementTable | 单行默认(D_ref=12 · p_perfect=200 · 拍板段序 · clamp −100/+100 · floor · +4 字段:damage_a_per_cell=1 / damage_a_cap=8 / damage_b_per_flag=2 / armor_effectiveness 克制矩阵——护甲克制批,C3 修订批接口漂移) | provisional 归 C3 |
## 加载·校验·热重载
规则:
1. 规则 6(出错当场中止):D2 在加载与运行期发现数据出错一律致命中止,不做软回退——错误数值上线比崩溃更伤(P1/P2 信任底线);错误分两类处置(下表)。唯一例外:debug 热重载校验失败时保留旧表停留 Ready、给出错误清单,不杀运行中的调试会话。
举证:未播种流取数见边界表 EC7;请求越域见难度行解析前置守卫。两类构建分流见本节「校验双通道」。
2. 规则 7(零界面依赖):D2 自己的代码与测试一律不 import 任何 Control/场景类——纯数据层不碰表现;没有界面依赖,CI 与无窗口环境才能跑完整战斗。
举证:无计算。验收 = 静态扫描 + headless 实跑两项合成判定(AC-10)。
3. 规则 8(版本拒读):版本常量 `SCHEMA_VERSION` 归 D2;表文件与存档(D3)携带版本号,存档带来的版本号高于当前支持时直接拒绝读取——旧程序不硬猜新格式的数据。
举证:无计算。拒读行为见边界表 EC9(比较逻辑归 D3,常量归 D2)。
| 错误类别 | 什么情况 | 处置 |
|---|---|---|
| 加载期校验错误 | 启动加载/热重载时查出:字段越界、类型错、NaN、表为空或某 level 无行、缺 depth-0 锚行、重复键、悬空引用、权重和 ≤0、段序违例、池映射悬空、p_perfect 越域 | 经校验双通道处理;启动路径在 editor、CI、headless、release 一律致命 |
| 运行期致命 | 运行中越界请求:关卡/深度坐标越域(解析入口前置守卫拦截)、未设种子就取随机数 | 不分构建一律致命:游戏内走致命错误路径,headless 冒烟进程 quit 非 0 |
设计意图:MVP 有意不做软回退——带病数值上线比崩溃更伤信任底线。
### 校验双通道(规则 6 实现参照:ConfigDB.load_tables(root, collect_only))
| 通道 / 机制 | 要点 |
|---|---|
| 游戏通道(collect_only=false,默认) | 校验错误 → push_error 错误清单 → 走致命错误路径(游戏内中止构建;headless 下独立进程非 0 退出——`--script` 协议,设计顺序 #1 时无主场景可依赖);release 构建的致命表现(引擎默认错误路径的实际行为验证)显式 deferred→首次 release 导出 checklist(与 AC-18 同待遇) |
| 测试通道(collect_only=true,仅 debug/测试构建) | 收集错误不升级,供冒烟脚本逐夹具断言错误类别后一次性退出 |
| 夹具注入 | debug/test 构建允许经命令行 `--data-root=<路径>` 让 ConfigDB 从注入根加载(夹具放 tests/fixtures/);禁止把夹具拷进 data/tables/(污染 AC-1 的干净检出前提) |
| 诊断收集器 | resolve / 校验 / override 的 warning 与错误共用同一类目体系,统一经 ConfigDB 内部诊断收集器投递,同时镜像 push_warning 供开发期控制台(Godot 无进程内拦截 push_warning 的 API——冒烟断言必须走收集器);测试通道暴露只读诊断清单与 `reset_diagnostics()`;跨调用去重状态(R1 四元组注册表)随 `load_tables()` 或 `reset_diagnostics()` 清空(用例间无顺序依赖);游戏通道 warning 仅镜像控制台,不承诺收集 |
### 状态机
| 状态 | 进入条件 | 行为/出口 |
|---|---|---|
| Unloaded | 进程启动前 | 磁盘 .tres 未加载 |
| Loading | `ConfigDB._ready()` 或 `load_tables()` | 按表清单逐一 `load()`;文件缺失即入 Failed |
| Validating | 单个资源加载成功 | 调 schema 的 `validate()`,错误入收集器 |
| Ready | 全部通过 | 类型化属性可读 |
| Failed(启动路径) | 启动加载任一致命错误 | 全构建致命中止并输出错误清单 |
| Failed(reload 路径) | 热重载校验失败 | 保留旧表停留 Ready + debug 错误清单(不杀运行中的调试会话) |
| Reloading(仅 debug) | 编辑器热重载 / 调试键 | 战斗中拒绝(返回 false,战斗结束后手动再触发);否则重走 Loading → Ready |
不变式:RNG 与 ConfigDB 状态线相互独立——Reloading / Failed(任一路径)不触碰 RngService,计数器与 master_seed 不变(派生只依赖盐/master_seed/scope,与表数据无关;若按状态机对称性在 reload 时重置 RNG 将击穿 AC-4)。
### 边界情况
| # | 条件 | 行为 | 依据 |
|---|---|---|---|
| EC1 | 表文件缺失或 load() 失败 | 致命中止,错误清单含路径;无软回退 | 规则 6 |
| EC2 | 字段越界 / NaN / 类型不符 | `validate()` 收集后致命 | 规则 6 |
| EC3 | 表为空或某 level 行集为空 | 校验阶段致命(R1 前置条件),不会进入运行期 | 规则 6 |
| EC4 | 请求 (level_index, node_depth) 无精确行 | 按 R1 floor 解析 + warning(按四元组去重)——预期行为,不是错误 | R1 |
| EC5 | level_index 超过表最大 level | 命中最大 level 行(floor 规则的自然结果,无独立 clamp 分支) | R1 |
| EC6 | master_seed 为负 | abs() 归一化 + warning,保证 ∈ [0, 2^63);恰为 INT64_MIN → 致命(abs 恒等回绕,与 0 静默别名——机理是输出掩码抹掉 bit 63) | R2 |
| EC7 | 未设 master_seed 就取随机数 | `push_error` 后显式中止(GDScript 的 push_error 本身不终止进程——游戏内走致命错误路径,冒烟脚本 `quit(1)`),取到的值不可用——C2 未接线的显式暴露,禁止 seed=0 软回退 | 规则 6 |
| EC8 | 战斗进行中请求热重载 | 拒绝并返回 false;战斗结束后手动再触发(不自动重放) | 状态机表 |
| EC9 | 存档携带的 SCHEMA_VERSION 高于当前 | 拒绝读取(比较逻辑归 D3,常量归 D2) | 规则 8 |
| EC10 | 奖励权重表权重和 ≤ 0 | validate 致命(B4 回填后必须 > 0) | 规则 6 |
| EC11 | debug override 类型或范围与 schema 字段不符 | warning + 忽略该条(仅 debug;release 无 override 源) | R3 |
| EC12 | 同一流名被重复注册或请求未知名流 | debug 断言中止(流名封闭) | 规则 5 |
| EC13 | 两个系统同帧请求同一流 | 允许——`open_stream` 各自取号(计数器各 +1)按调用顺序串行生效;单线程假设成立(Godot 主线程玩法逻辑),不引入锁 | 规则 5 |
| EC14 | 请求越域(level_index < 1 或 node_depth < 0) | 规则 6 致命(R1 前置守卫) | R1 |
| EC15 | 某 level 行集缺 depth-0 锚行 | validate 致命(防"入口天灾":入口请求静默 floor 到深行造成难度突刺);写死开、无运行期开关,C3 如需重议走本 GDD 显式修订流程(非 knob) | 规则 6 |
| EC16 | 热重载校验失败 | 保留旧表停留 Ready + debug 错误清单 | 状态机 reload 路径 |
## 统一取数与调试覆盖
规则:
1. 规则 1(数字只写在表里):凡是设计师要调的数字(行动点、插旗成本、奖励值、敌人数值……)只存在于 data/tables/ 下的 .tres 数值表里,一处定义、处处引用;代码文件里禁止出现玩法数值字面量(引擎/工程常量除外),评审发现写死的玩法数值即评审阻塞项。
举证:无计算。违规检测 = 启发式扫描 + 人工复核(AC-14,随各 owner 回填定稿与里程碑 gate-check 执行)。
2. 规则 2(取数只找 ConfigDB):全部 19 个系统读数值只有一条路——经 autoload ConfigDB 的类型化属性读取(例 `ConfigDB.action_economy.ap_per_round`);禁止绕过它自己 `load()` 表文件或长期缓存副本(副本绕过校验,热重载换新值时它也不知情)。唯一例外:debug/测试构建可从命令行注入的根目录加载。
举证:无计算。注入根机制见「校验双通道」夹具注入行。
设计意图:单点发放 + 单点读取,热调与校验才有意义。
### 调试覆盖链(R3 debug_override,仅 debug 构建)
**结论**:`effective = override[key] if _has_override(key) else table_value`——仅 debug 构建加载 override 源;v1 仅支持单行全局表字段;类型/范围不符 → warning + 忽略;release 结构性不存在;永不回写 .tres。
| # | 边界 |
|---|---|
| ① | 覆盖判定与存储内聚于 ConfigDB(规则 2 单点),gameplay 不可见 override 来源 |
| ② | key = schema 字段点路径(与规则 2 属性路径同构,例 `action_economy.ap_per_round`);未知 key → debug warning + 忽略该条;v1 仅支持单行全局表字段——进度键表的行寻址语法(如 `difficulty.(2,0).hp`)登记为后续扩展,当前 override 对进度表无 key 空间 |
| ③ | override 值类型与范围必须与 schema 字段一致,不一致 → debug warning + 忽略该条(复用 schema 校验逻辑,防止 debug 会话注入越界值污染平衡基线) |
| ④ | release 构建不加载、不注册任何 override 源(恒等是结构保证而非"碰巧不生效");debug 谓词实现为可注入形式(`OS.is_debug_build()` 之外引入 feature 标签闸,例 `sim_release`)——AC-8 的模拟以此为基础 |
| ⑤ | override 仅为运行时状态,永不回写 .tres |
## 随机数服务(RngService)
规则:
1. 规则 5(随机只从 RngService 按流取):全项目玩法代码的随机数只此一家——autoload RngService 持有封闭的三条具名流(下表),流集合封闭、只含具名消费者;要新增随机流(含 B1 若需结算随机)必须改代码走增流通道并通过 P1 支柱评审(随机须有界、可见或可推理);玩法代码禁止 `randomize()` 或自建 RandomNumberGenerator。master seed 由 C2 在冒险开始时设定——同一冒险内相同决策序列必然相同战斗结果。
举证:种子派生公式与黄金向量见本节「R2 流种子派生」;依据 = B4 契约源〔AC-1〕· R2 黄金向量。
### 封闭流与取数入口
| 流 | 消费者 | scope 取号事件 |
|---|---|---|
| board_gen | A1 布雷 | 每次生成新扫雷图(A1 换图时) |
| reward_roll | B4 掉落 | 每次 board install 批(格奖励批 + 清图奖励批)——B4 AC-1 为契约源 |
| encounter | B1 战斗实例化 | 每场战斗实例化恰一 scope(begin_battle 实例化期,扫雷开始前——unit_count 区间均匀骰,挖掘决策域之外 P1 合规;C3 遭遇随机化批增流,registry 已登记) |
新增流 = 增流通道:改代码 + P1 支柱评审(随机须有界、可见或可推理)。
| 取数入口 | 给谁用 | 行为 |
|---|---|---|
| `open_stream(stream) -> RandomNumberGenerator` | 玩法代码(有状态流门面) | 每调用一次 = 一次 scope 取号:该流计数器 +1 → 派生 → 返回已播种实例;一个 scope 事件内所有取数共用该实例 |
| `derive_at(stream, scope)` | 测试/预览(纯函数) | 不推计数器——预览取数不污染正式序列(杜绝预览类取数的平衡 Heisenbug) |
### R2 流种子派生(stream_seed_derivation)
**结论**:纯派生函数——同一冒险内(master_seed 固定)同流各 scope 种子互不碰撞;同输入恒同输出;跨 Godot 版本恒定(不用运行时 `hash()`——其跨版本稳定性无承诺);输出 ∈ [0, 2^63)。跨 master_seed 的全局单射不做声明(数学上由鸽笼原理排除)。Output Range:[0, 2^63)。
```
derive_at(stream, scope) = ( SALT_s XOR wrap64(master_seed + scope × GOLDEN_RATIO) ) AND 0x7FFFFFFFFFFFFFFF
wrap64 = GDScript int(64 位二补数)自然回绕 = MOD 2^64
```
#### 实现级规约(必须逐条遵守,否则黄金向量失配)
| 规约 | 要点 |
|---|---|
| wrap64 | GDScript int(64 位二补数)的自然回绕 = MOD 2^64;禁止 float 中间量(53 位尾数必丢精度)、禁止对中间量使用 `%`(带符号取余语义易误用)、禁止"主动修复溢出"(中间积本就越界——乘数 GOLDEN_RATIO = 0x9E3779B97F4A7C15 > 2^63,scope=1 即回绕,这是设计依赖的语义,不是缺陷) |
| 常量书写 | 三个常量(盐×2 + 乘数)均 ≥2^63,GDScript 十六进制字面量按二补数解析为负——统一用分段表达式书写(例 `(0x9E3779B9 << 32) \| 0x7F4A7C15`,显式括号、不依赖优先级记忆),禁止直接书写 ≥2^63 的完整字面量;注意 GDScript 的 `^` 是按位 XOR |
| 输出掩码 | 经 AND 掩码保证非负(∈ [0, 2^63))后再赋 `RandomNumberGenerator.seed` |
**Variables:**
| 变量 | 类型 | 范围 | 说明 |
|---|---|---|---|
| SALT_s | int64 | 编译期常量 | board_gen=0xA0761D6478BD642F · reward_roll=0xE7037ED1A0B428DB · encounter=0x8EBC6AF09C88C6E3(互异奇常数,均 ≠ 乘数) |
| master_seed | int64 | [0, 2^63) | 冒险主种子;`set_master_seed()` 对负输入 abs() 归一化并 warning;恰为 INT64_MIN → 致命(abs 在 int64 对 INT64_MIN 恒等回绕,实测与 0 在全流种子逐一相等——别名机理是输出掩码抹掉 bit 63,掩码本身是规约的一部分);仅许全流计数器全零时调用(冒险中途重播种会静默清零计数器,击穿同冒险无碰撞前提与 D3 快照语义 → debug 断言) |
| scope | int64 | ≥0 | 派生入参;有状态门面的取号值(每流独立计数器:set_master_seed() 时归零,每次 `open_stream` +1,永不重置、不随关卡/图序重置——否则不同关卡的同序号图会得到相同种子);负 scope → debug 断言中止(`derive_at` 是预览工具入口,静默负值会与 scope+2^63 经输出掩码别名) |
**黄金向量(AC-3 的常量依据,经独立复算验证):**
| 流 | master_seed | scope | stream_seed |
|---|---|---|---|
| board_gen | 42 | 0 | 2339089373825164293 |
| board_gen | 42 | 1 | 4485977604321908752 |
| board_gen | 42 | 2 | 2024629813010603131 |
| board_gen | 42 | 3 | 8849696722413293638 |
| reward_roll | 42 | 0 | 7422916049646135537 |
| encounter | 42 | 0 | 1061841193803499209 |
- Example:同流同 scope 重复调用 `derive_at` 恒等(纯函数);同帧对同一流连续 `open_stream` 两次 → 计数器各 +1,得两个互异且顺序确定的种子。
### RNG 生命周期与快照
- RNG 生命周期:进程启动 → 流待命(未播种时取数即致命)→ 冒险开始设定 master_seed → 每次具名 scope 事件派生种子 → 冒险结束保留供复盘。
- 快照 = master_seed + 各流 scope 计数器(仅记录计数器、缺 master_seed 不足以恢复序列——进程重启会从种子序列起点重放;见 D3 接口)。
- 快照切出须发生在无在飞 `open_stream` 实例时(scope 事件边界之外):门面实例作用域限定于单次 scope 事件、消费者不得跨事件持有——在飞实例的内部取数进度不入快照,违反则续玩/复盘与实况分叉(AC-15 失效),该前提由 D3 消费侧遵守。
## 难度行解析(R1 difficulty_row_resolution)
**结论**:resolve 恒返回已有行——进度键 floor 解析、实体键精确匹配;不存在 default 行兜底、不存在 clamp 外推;非精确命中记 warning(四元组去重);请求越域在解析前置守卫致命。Output Range:行资源必有值(前置条件由校验保证);极端输入(超表大关卡号)命中最大已有行。
```
row = resolve(table, level_index, node_depth) (ConfigDB 公开解析方法,table 经类型化属性取得)
前置守卫:level_index < 1 ∨ node_depth < 0 → 规则 6 致命(请求越域,不进解析)
L* = max{ lv ∈ LevelKeys(table) | lv ≤ level_index };空集(请求低于表最小键,向上解析)→ L* = min LevelKeys(table)
D* = max{ dp ∈ DepthKeys(L*) | dp ≤ node_depth };空集 → D* = min DepthKeys(L*)
```
**Variables:**
| 变量 | 类型 | 范围 | 说明 |
|---|---|---|---|
| level_index | int | ≥1 | 当前关卡序号(1 起);越域 → 致命 |
| node_depth | int | ≥0 | 节点深度(0=关卡入口);越域 → 致命 |
| L* / D* | int | 表内键 | 命中行键 |
- 无兜底 / 无外推的前置:表非空、每个 level 至少 1 行、每个 level 含 depth-0 锚行是规则 6 校验前置;超表成长外推是 C3 owner 公式的职责,D2 只解析到最近已有行。
- warning 定位为开发期异常信号(防刷屏去重是刻意的),不是难度审计工具——离线审计由曲线导出工具承担(headless 脚本导出 resolve(level 1..N × depth 0..M) 矩阵供 diff,登记为 C3 期交付物)。
- Example:表含行 (1,0)(1,4)(2,0)(2,6)——`resolve(3,3)` → L*=2、D*=0 → 返回 (2,0) 行,warning 一条(missing_type=both);`resolve(1,4)` → 精确命中零 warning;`resolve(99,99)` → 命中 (2,6),missing_type=both(与前者去重键不同,各自一条)。
- 去重键 = (table_id, missing_type, L*, D*) 四元组,内容含请求 (level_index, node_depth)、解析结果 (L*, D*)、缺失类型;去重注册表随每次 `load_tables()` 或 `reset_diagnostics()` 清空。
### missing_type(R1 warning 分类枚举)
| 值 | 判定 |
|---|---|
| base_ceil | level 分量向上解析(请求低于表最小键)——恒记 base_ceil(depth 分量缺失与否只写入 warning 内容,不改类型) |
| level_floor / depth_floor | 单维向下解析 |
| both | level_floor 与 depth_floor 同时发生 |
- depth 分量只有精确命中或向下 floor 两种情形(EC15 锚行保证每个 level 含 depth-0,永不 ceil);AC-5 各用例的 missing_type 均由本规则推导。
设计意图:解析永不发明数据——floor 到已有行并留 warning,外推归 C3 owner 公式。
## 依赖与输出
**上游(hard)**:无——Foundation 层,D2 不依赖任何系统;master_seed 由 C2 冒险开始时设定。
| 方向 | 系统 | 性质 | 接口要点 |
|---|---|---|---|
| 下游 | 全部其他 19 系统 | hard(规则 1) | ConfigDB 类型化读取;禁止其他取数路径(A2 经 A3 表间接承载参数) |
| 下游 | A1 | hard | BoardGenTable 回填(首个进度键表)+ VariationTable schema(biome/tag 字段)+ `board_gen` 流(换图时取号) |
| 下游 | A3 | hard | ActionEconomyTable schema 回填 |
| 下游 | A4 | hard | VariationTable 回填 |
| 下游 | B2 | hard | EnemyTable 回填(十字段 + 三敌种行 + 每行集恰一敌种校验)+ HeroTable 新增(接口漂移回写) |
| 下游 | B3 | hard | StatusEffectTable 回填 |
| 下游 | B4 | hard | RewardTable 回填(九字段 + 跨字段校验族 + 默认根行集,use_timing 承重 MVP 单值)+ RewardConfigTable 新增(单行全局)+ `reward_roll` 流(scope = 每次 board install 批——格奖励批 + 清图奖励批,B4 规则 2 接口漂移) |
| 下游 | B1 | hard | SettlementTable 回填(可回放步骤流的数据前提)+ debug 热重载门标志 `set_reload_gate()`(战斗开始置位 / 结束复位,仅 debug 构建)+ `encounter` 流(战斗实例化期取号恰一 scope——unit_count 区间骰,C3 遭遇随机化批增流) |
| 下游 | C2 / C3 | hard | `RngService.set_master_seed()` + DifficultyTable 行解析 + 曲线导出工具交付物 |
| 下游 | D3 | hard | `SCHEMA_VERSION` + 存档内 master_seed + 各流 scope 计数器快照(续玩/复盘;含只读计数器接口,快照须在无在飞实例时切出) |
| 下游 | A5 / C4 | hard(垂直切片 / Alpha) | AuxItemTable 回填(A5 定稿登记——原 HelperItemTable 占位更名转正;counter_item_ids 表值回填与悬空校验激活随 D2×② 对侧批)/ MetaTable 回填 |
| 下游 | D1 / D4 / D5 / D6 | soft | 如需参数表再增 schema(现无占位);display_name 本地化归 D6 |
| 外发 | —— | —— | 不订阅任何玩法事件;仅 debug 构建发 `config_reloaded`(D1 调试用) |
- 数据读取类事实依赖不逐条列边(完整清单以本表为准);A2 无直接依赖(只外发事件,参数经 A3 表承载),与 systems-index 的 A2 回边规则一致。
- 接口漂移修订规则:owner GDD 定稿时可修改占位 schema 类(schema 与表同仓),但必须回写本 GDD 表清单与 `design/registry/entities.yaml`,并在本文件版本历史登记修订;回执登记粒度 = 受影响字段/枚举/流——逐表 diff schema 目录(schema 目录 + 逐表字段行 + MVP 内容行 + 依赖表),禁按"对侧 GDD"粒度整批登记;占位参考值定稿时必须删除本 GDD 调参表清单中的对应条目(值的事实登记归 owner GDD 与 registry)。
- 双向一致性已与 systems-index 依赖分层表核对一致(核对明细见 analysis 附录);已知待定:C3 是否消费 B1 输出——见索引门禁增补记录,归 C3 GDD 定稿,不影响 D2。
### 登记与对账义务(在案项——已闭环项见 analysis 附录)
| 编号 | 内容 | 对侧 | 状态 |
|---|---|---|---|
| 4 | counter_item_ids 悬空引用(D2 首个引用型字段)——MVP 不校验,首个道具 id 回填时补校验 + 条件性夹具 | A5 | 条件性(未激活) |
| 5 | EnemyTable MVP 行值(goblin 18HP / 4ATK 等)溯源补登(原型 REPORT,出处待查) | B2 / C3 | 待补 |
| 6 | HeroTable max_hp MVP 值溯源补登(出处待查) | B2 / C3 | 待补 |
| 7 | 胜场成长机制去留(+25% HP / +1 ATK——反雪球问题) | C3 | 待裁决 |
| 8 | 曲线导出工具(headless 导出 resolve 矩阵供 diff)——R1 审计的离线载体 | C3 | C3 期交付物(排期倒挂注意见 analysis 开放问题 #5) |
| 9 | 规则 5 对全项目玩法代码的随机扫描 | Technical Setup | 项目级 CI 项 |
| 10 | release 构建致命表现验证(引擎默认错误路径实际行为) | 首次 release 导出 | deferred(AC-18 checklist) |
| 11 | 战斗级可回放集成测试(同 master_seed 同动作序列 → 步骤流逐行相同) | B1 | deferred(AC-15) |
## P0 最小闭环
1. D2 交付全部 schema 类与占位表(含键结构)——先有格子。
2. owner 系统 GDD 按设计顺序回填数值(十张已定稿 + 两张占位)。
3. 进程启动:`ConfigDB.load_tables()` 逐一加载 + schema `validate()`,经校验双通道。
4. 全部通过 → Ready:19 系统经类型化属性读数,无第二取数路径。
5. C2 冒险开始 `set_master_seed()`;三条封闭流按各自 scope 事件取号。
6. debug 构建热重载即改即验(战斗中被门拒绝,结束后手动再触发)。
7. `godot --headless --script` 冒烟 `quit(0)`——加载、校验、取数、派生全链路绿。
## P1 / P2 后置
| 优先级 | 内容 | 后置原因 |
|---|---|---|
| P1 | 战斗级可回放集成测试(同 master_seed 同动作序列 → 步骤流逐行相同,AC-15) | D2 阶段无战斗载体,deferred→B1 |
| P1 | 难度曲线导出工具(headless 导出 resolve 矩阵供 diff——R1 审计的离线载体) | C3 期交付物;排期倒挂见 analysis 开放问题 #5 |
| P2 | release 构建致命表现 + override 结构不存在验证(AC-18) | deferred→首次 release 导出 checklist |
| P2 | 进度键表 override 行寻址语法(如 `difficulty.(2,0).hp`) | v1 仅支持单行全局表字段,登记为后续扩展 |
| P2 | 编辑器内表文件热重载自动化 | 低优先、未指派(analysis 开放问题 #2) |
| P2 | gdUnit4 引入与冒烟用例迁移 | Technical Setup 阶段裁决(analysis 开放问题 #1) |
## 呈现与 UI 面
| 项 | 要点 |
|---|---|
| 玩家 UI | 无——MVP 无面向玩家的 UI;debug override 与夹具注入均经 Godot 编辑器 / 命令行参数设置(`--data-root=`,仅 debug),不做自定义面板 |
| 校验失败呈现 | editor / CI 输出控制台(错误清单格式见 AC-1);release 构建走引擎默认错误路径(实际表现验证 deferred→首次导出 checklist) |
| 开发侧可视化 | Godot 编辑器 Resource inspector(自带)+ 校验错误控制台输出 |
| 音频 | 无需求(纯数据层,无直接表现面) |
## 调参表
### D2 自身 knobs(元参数)
| Knob | 默认 | 调过高 | 调过低 |
|---|---|---|---|
| floor 解析策略 | floor + warning(无外推) | —— | 引入外推/静默会掩盖 C3 难度断档 |
| rng_stream_names | 封闭 3 流(board_gen / reward_roll / encounter) | 增流需改代码 + P1 评审(有意设卡) | 流复用破坏可复现隔离 |
| debug_override_enabled | false(仅 debug 构建) | 污染测试基线 | —— |
| 表路径清单 | data/tables/ 固定(debug 注入根除外) | —— | 散装路径破坏单点访问 |
| depth-0 锚行校验 | 写死开(非 knob,规则 6) | —— | ——(重议须走本 GDD 显式修订,EC15) |
- Knob 交互:floor 策略与 C3 的难度语义共享,改动需 C3 GDD 知情;校验严格度不设 knob(fatal 写死——放行带病数值会使 CI 冒烟失去意义)。
- 未决占位与溯源义务(provisional——owner 定稿前不视为平衡事实;定稿后删除对应条目,值的事实登记归 owner GDD 与 registry):EnemyTable 行值溯源(义务 #5)· HeroTable max_hp 溯源(义务 #6)· 胜场成长机制去留(义务 #7)——见登记与对账义务表;已定稿占位参考值(标中旗 / 清图奖励 / 状态四行 / 奖励池等)已随各 owner 定稿清退出本清单。
### 表 schema 逐表字段行(D2 核心资产——字段全集以各 owner GDD Schema 节与 registry 为准)
#### ActionEconomyTable(单行全局 · A3 回填 ✓)
| 字段 | knob | 说明 |
|---|---|---|
| ap_per_round | ✓ | 每轮行动点 |
| dig_ap_cost | ✓ | 挖掘成本 |
| flag_place_ap_cost | ✓ | 插旗成本 |
| flag_pull_ap_cost | ✓ | 拔旗成本 |
| rap_refresh_cost | ✓ | RAP 刷新门槛 |
| move_ap_cost | ✗ 冻结 | 移动成本 |
| chain_ap_cost | ✗ 冻结 | 连锁展开成本 |
| 跨字段校验 | — | 任一动作成本 ≤ ap_per_round(违规加载期致命,条件性夹具登记) |
#### VariationTable(按 variant_id · A4 回填 ✓)
| 字段 | 类型 / 域 | knob | 说明 |
|---|---|---|---|
| variant_id | String 主键唯一 | — | 行键 |
| display_name | String | — | 本地化归 D6 |
| unlock_level | int [1,999] | ✓ | 进度语义承载列(规则 4 豁免载体) |
| scope | region / global | — | global 预留 C3 |
| info_mask | none / hide_numbers | — | 雾行 = hide_numbers |
| cell_constraint | any / safe_nonzero | — | 雾行 = safe_nonzero |
| biome_tag | StringList | ✓ | ∅=通配 |
| counter_item_ids | StringList | ✗ | A5 预留;悬空引用政策见目录表 |
| 承重规则 | — | — | 生成期布置约束:雾区仅非 0 安全格(区内无雷无 0 格,A1 生成期强制)+ 零特判原则 |
#### StatusEffectTable(按 effect_id · B3 回填 ✓)
| 字段 | 类型 / 域 | 说明 |
|---|---|---|
| effect_id | String 主键唯一 | 行键;引用校验已激活 |
| display_name | String | 本地化归 D6 |
| target_side | hero / enemy | 作用侧 |
| magnitude | percent ∈ [-100,100] ∧ ≠0 / flat ≥ 1 | 跨字段校验 |
| unit | percent / flat | 量纲 |
| duration_settlements | [1,99] | 持续结算次数 |
| stack_policy | stack / refresh / independent | 叠层策略 |
| max_stack | [1,16] | 仅 stack 消费 |
#### BoardGenTable((level_index, node_depth) 首个进度键表 · A1 回填 ✓)
| 字段 | 类型 / 域 | knob | 说明 |
|---|---|---|---|
| board_width / board_height | int [8,64] | ✓ | 板尺寸 |
| mine_density | float (0,0.5] | ✓ | 密度浮点(尺寸解耦);布雷数下限 round(d×(W×H−9)) ≥ 1 |
| inferability_target | float [0.85,1] | ✓ | P1 门槛承重参数 + 绝对锚 |
| solver_rules | single / single_subset | ✗ 冻结 single_subset | 推理定义域非调参 |
| min_safe_count | int [1,999] | ✓ | 退化板对冲 |
| max_gen_attempts | int [1,32] | ✓ | 生成-验证循环上限 |
| fog_region_count | int [0,32] | ✓ | 雾区数 |
| fog_target_ratio | float [0,1) | ✓ | ≤ min(8d, 0.25) 绝对上限 |
| board_biome | StringSet 非空 | — | 域校验致命(词表 owner 未定稿前只校验非空) |
| elite_ / boss_ 前缀 | 命名空间预留 | ✗ | 第三轴登记约定——语义归 C1/C3,本 GDD 不定义 |
| 跨字段谓词 | — | — | fog_domain_ok(F2)+ min_safe_feasible(F2b)+ cost_feasible(F2c:attempts×W×H ≤ 16384 provisional,运行期禁墙钟逃生)——加载期致命 |
#### EnemyTable((level_index, node_depth, enemy_id) · B2 回填 ✓)
| 字段 | 类型 / 域 | knob | 说明 |
|---|---|---|---|
| enemy_id | String(行集内主键) | — | 行集内唯一(每行集恰一敌种) |
| display_name | String | — | 本地化归 D6 |
| unit_count | int [1,3] | ✓ | 遭遇区间下界(C3 修订批——区间 [unit_count, unit_count_max] 经 B1 encounter 骰定本战实际数量;新敌种首遇节点固定不加骰) |
| unit_count_max | int [unit_count,3],可省略 | ✓ | 遭遇区间上界(缺省 = 定值,旧行零迁移——C3 修订批新增字段) |
| armor_type | enum {none, a, b},缺省 none | ✓ | 护甲类型(克制矩阵 = SettlementTable.armor_effectiveness;MVP 映射 goblin=none / bugbear=a / ogre_chief=b——护甲克制批) |
| hp | int [1,999] | ✓ | 难度修饰前基线 |
| atk | int [0,999] | ✓ | attack 行为伤害基值;0 = 纯伏击伤敌种合法 |
| heavy_multiplier | int [1,16] | ✓ | 无 heavy 拍敌种不消费(非校验项先例) |
| pattern | StringList,环长 [1,8] | — | MVP 校验域 {attack, charge, heavy}——guard/boost/hinder/summon 预留值校验拒绝 |
| advantage_per_round_cap | int [0,16] | ✓ | 默认 4 定稿(反灾难护栏);0 = 免疫伏击合法极端 |
#### HeroTable(单行全局 · B2 回填 ✓ · 接口漂移新增)
| 字段 | 类型 / 域 | knob | 说明 |
|---|---|---|---|
| max_hp | int [1,999] | ✓ | MVP 值 provisional(出处待查) |
| atk | int [1,999] | — | 0 = 加载拒绝(基础攻击恒存在,"0 基线 × 百分比增益恒零"死锁自 schema 层杜绝);MVP = 6 原型锚 provisional 归 C3;消费面 = 主角战斗段先手攻击 |
#### RewardTable(按 reward_id · B4 回填 ✓)
| 字段 | 类型 / 域 | knob | 说明 |
|---|---|---|---|
| reward_id | String 主键 | — | 行键(多行;pool_tag 分池不参与键) |
| category | buff / damage_item / heal_pack / instant_aoe / instant_heal | — | 两类交付轴:即时类 {buff, instant_aoe, instant_heal} 当场兑付不进库存 / 可选类 {damage_item, heal_pack} 入关卡域库存;预留值校验拒绝 |
| effect_id | String 可空 | — | buff 类必填(StatusEffectTable 精确引用);非 buff 恒空 |
| display_name | String 非空 | — | D1 呈现数据源,本地化归 D6(B2/B3 同库惯例) |
| power | int [0,999] | ✓ | item 类加载校验 ≥1;buff 类不消费非校验 |
| weight | int [0,999] | ✓ | 0 = 不入池 |
| pool_tag | safe_cell / active_inject / clear_default | — | clear_\<biome\> 命名空间预留拒绝;active_inject = 供给控制器注入池(可选类三行——install 期注入,非逐格 roll) |
| tier | int [1,8] | ✓ | A4 雾档约束载体 |
| use_timing | pre_settlement(MVP 单值) | — | 承重——道具使用窗口 MVP 激活,扩枚举走修订 |
#### RewardConfigTable(单行全局 · B4 回填 ✓ · 接口漂移新增)
| 字段 | 类型 / 域 | 默认 | 说明 |
|---|---|---|---|
| t_fog_min | int [1,8] | 2 | F2 雾档过滤门限 |
| k_dmg | int [0,999] | 0 | 0 = F5 断言关闭(现行默认——刀保底语义由供给控制器战斗首件保证取代;no_damage_row 后闸托底) |
| active_p0 | float [0,1] | 0.4 | 供给控制器注入概率初值(首件之后每张新板)——E[战斗获取] ≈ 1 标定目标,归 C3 |
| active_decay | float (0,1] | 0.5 | 注入概率指数递减 0.4→0.2→0.1→…(加难杠杆候选 0.6 随 C3 OQ7 扫描) |
| n_draft | int [1,999] | 32 | F5 草稿重 roll 上限 |
| p_min | int [1,999] | 8 | 默认根最低刀 power——K≥1 供给闸刀力下界(floor_supply_missing) |
#### SettlementTable(单行全局 · B1 回填 ✓ · 共十二字段)
| 字段 | 类型 / 域 | 默认 | knob | 说明 |
|---|---|---|---|---|
| equivalence_ref_damage(D_ref) | int [1,999] | 12 | ✓ | 当量参考敞口;变更触发 B1 全对照表 + 条款③⑤对账重跑(禁独立调参) |
| p_perfect | percent {100,200} | 200 | ✓ | 完美排雷清图奖励第二份兑付触发——非整数倍数乘数无运行时载体,域外加载致命 |
| segment_order | StringList | 拍板段序 | ✗ 结构冻结 | 恒等拍板段序校验,违例致命(防实现乱序静默漂移) |
| source_label_map | Dictionary source_class→文案键 | 封闭枚举全覆盖 | ✗ 结构冻结 | P2 归因呈现映射;键集 = B3 source_class ∪ {道具/判定扩展, enemy_action, flag_miss, hero_attack};新增键 = 接口漂移 |
| effect_pool_map | Dictionary effect_id→{hero_offense, hero_defense, enemy_output, flat_attach} | 四键定稿映射 | ✗ 结构冻结 | 效果语义映射;键 ⊆ StatusEffectTable 活行集,悬空加载期致命;新 effect_id 上线须随批登记 |
| pool_clamp_lo | int pp | −100 | ✗ 结构冻结 | percent 池地板域下界(乘数域随之 [0,2]) |
| pool_clamp_hi | int pp | +100 | ✗ 结构冻结 | percent 池地板域上界 |
| round_direction | enum {floor} | floor | ✗ 结构冻结 | 单一取整规则 |
| damage_a_per_cell | int ≥ 0 | 1 | ✓ | 伤害A 开格充能/格(护甲克制批——充能与结算归 B1 战斗段〔F6〕,C3 投值) |
| damage_a_cap | int ≥ 0 | 8 | ✓ | 伤害A 单轮充能封顶(0 = 不封顶——防连锁轮失控) |
| damage_b_per_flag | int ≥ 0 | 2 | ✓ | 伤害B 标雷充能/首次标中雷(每雷实例一次去重) |
| armor_effectiveness | Dictionary {base, dmg_a, dmg_b} × {none, a, b} | 特化 ×150 / 无甲 ×100 / 对位 ×50 | 键集 ✗ 结构冻结 / 数值 ✓ | 伤害类型 × 护甲克制矩阵——主角攻击三分量(基础/伤害A/伤害B)各自查表后 floor 相加;敌种甲型映射归 EnemyTable.armor_type |
## 验收标准
### 测试口径
| 口径 | 要点 |
|---|---|
| 测试载体 | `--headless --script` 冒烟框架(脚本 extends SceneTree 以获得 quit(code);该模式 autoload 须由脚本显式实例化——AC-1 前提声明;设计顺序 #1 时无主场景可依赖) |
| 覆盖 | 8/8 Core Rules;规则 6 加载期校验错误类别全部有夹具(悬空引用为条件性夹具,已随 B4 首引激活);运行期致命(请求越域 EC14、未播种 EC7)由 AC-5b / AC-17 行为级覆盖;R1 / R2 全部边界 |
| 致命类断言协议 | 致命用例(AC-2b、AC-5b 游戏通道半、AC-6 后半、AC-11、AC-17)各为独立进程调用,冒烟 runner 清单逐条登记"调用 → 期望退出码";单进程内只做测试通道收集断言 |
| warning 断言 | 一律经诊断收集器(见校验双通道),每用例先 `reset_diagnostics()` |
| 人工与延迟 | AC-14 人工 · AC-15 deferred→B1 · AC-18 deferred→首次 release 导出 |
### AC 清单(Given-When-Then,编号不变)
| AC | Given | When | Then | 覆盖 |
|---|---|---|---|---|
| AC-1 | 干净检出 | `godot --headless --script tests/smoke_data.gd` | 核心规则 #3 表清单的全部表(以清单为准,不写死数量)加载且 data/tables/ 无清单外 .tres;脚本成功 `quit(0)`,任一失败 `quit(1)` 并输出错误清单(表路径 + 字段 + 错误类别) | 规则 3/6 |
| AC-2 | tests/fixtures/ 夹具集(越界字段 / 文件缺失 / 空表 / 某 level 无行 / 缺 depth-0 锚行 / 重复键 / 权重和 ≤0 各一;悬空引用为条件性夹具——已随 B4 首引激活) | 冒烟脚本经 `--data-root` 注入逐夹具运行 `load_tables(root, collect_only=true)`(测试通道,单进程逐个断言) | 每个夹具的错误清单含对应类别(EC1 / EC2 / EC3 / EC15 / EC10 及重复键类别) | 规则 6 / EC |
| AC-2b | 任一致命夹具经注入根加载 | 冒烟 runner 以独立 headless 进程执行游戏通道 `load_tables(root, collect_only=false)`(`--script` 协议,脚本显式实例化 autoload;不依赖主场景) | 进程非 0 退出且错误清单完整——验证"游戏内真的会死"而非仅"校验器能发现" | 规则 6 游戏通道 |
| AC-3 | R2 黄金向量表(6 常量) | 纯函数 `derive_at` 复算(board_gen master=42 scope 0–3、reward_roll master=42 scope=0、encounter master=42 scope=0);同流同 scope 重复调用 | 逐个等于向量表常量;重复调用恒等(纯函数无副作用) | R2 |
| AC-4 | master_seed=42 | `open_stream("board_gen")` 连续取号 ≥100 次(跨模拟"关卡切换");同期调用任意 `reload()` | 公开只读计数器单调不重置(各关同序号图种子互异);计数器不变(状态线独立) | R2 / 规则 5 / 状态机表 |
| AC-5 | 真实 DifficultyTable schema 构造的表含行 (1,0)(1,4)(2,0)(2,6)(每用例先 `reset_diagnostics()`) | 依次 `resolve(3,3)` → `resolve(3,4)` → `resolve(1,4)` → `resolve(99,99)`;单行表 {(2,0)} 上 `resolve(1,5)` | (3,3):返回 (2,0) 行且诊断收集器 warning 恰一条(missing_type=both,含请求/结果/类型);(3,4):warning 不新增(四元组去重键相同);(1,4):精确命中且零 warning;(99,99):命中 (2,6) 且新增恰一条(去重键含 D*,与首条不同);单行表:命中 (2,0) 且 missing_type=base_ceil(由 R1 分类规则推导:level 分量向上解析恒记 base_ceil) | R1 |
| AC-5b | 真实 DifficultyTable schema 构造的合法表 | `resolve(0, 3)`(level_index < 1)与 `resolve(2, -1)`(node_depth < 0);同用例经游戏通道独立进程复跑 | 测试通道诊断清单各含"请求越域"致命类别一条且不进入解析(EC14 / R1 前置守卫);独立进程非 0 退出 | R1 / EC14 |
| AC-6 | master_seed=-7;master_seed=INT64_MIN;`derive_at` 传入负 scope | `set_master_seed()`;`set_master_seed()`;`derive_at` | 归一化为 7 + 诊断收集器 warning 一条,后续种子 ∈ [0, 2^63)(EC6);INT64_MIN 致命(非 warning;独立进程断言退出码非 0);负 scope debug 断言中止(负值经输出掩码与 scope+2^63 别名) | R2 / EC6 |
| AC-7 | debug 构建 override{"action_economy.ap_per_round"=12}(域内值——A3 schema 域 [1,32];越域 override 会被 R3③ 忽略,出域示例会使本 AC 假失败) | 经 ConfigDB 读取;override key 为未知路径、类型或范围与 schema 不符;`reload()` 后 | effective=12;诊断收集器 warning + 忽略该条;.tres 文件内容不变(R3 边界①③⑤) | R3 |
| AC-8 | 非 debug 谓词模拟(feature 标签) | `_has_override(key)` | 恒 false——本条仅证明门存在性;"release 不加载 override 源"的结构保证验证 deferred→首次 release 导出 checklist | R3④ |
| AC-9 | D2 文件集(data/schemas、ConfigDB、RngService、tests) | 静态扫描(模式覆盖 `load(`、`preload(`、`ResourceLoader.load`、`"res://data/" +` 动态拼接;`res://tests/fixtures/` 前缀豁免——不在 `res://data/` 扫描域) | 无 `randomize()` 调用、无 ConfigDB 之外的 data 表加载(规则 2/5);规则 5 对全项目玩法代码的扫描登记为项目级 CI 项(义务 #9) | 规则 2/5 |
| AC-10 | D2 文件集 | 静态扫描 + AC-1 headless 实跑 | 无 `extends Control/Node2D`、无 `.tscn` preload,且 AC-1 headless 实跑通过——两项合成判定规则 7(扫描是启发式,硬证据是 headless 实跑) | 规则 7 |
| AC-11 | RngService(debug 构建) | 请求未知名流或重复注册流名 | debug 断言中止(EC12;独立进程退出码非 0) | 规则 5 / EC12 |
| AC-12 | 同帧对同一流 | 连续两次 `open_stream` | 两个互异且顺序确定的种子(EC13) | 规则 5 / EC13 |
| AC-13 | 冒烟脚本运行 | 反射断言 | `SCHEMA_VERSION` 常量存在且 ConfigDB 可读;全部 schema 携带版本字段;进度类表(BoardGen/Enemy/Difficulty)暴露 (level_index, node_depth[, enemy_id]) 键字段;DifficultyTable schema 暴露 `elite_` / `boss_` 前缀预留字段("高版本拒读"路径 deferred→D3) | 规则 8 + 规则 4 键契约 + 第三维命名空间 |
| AC-14(人工) | 每次回填定稿 | 验收人复核 | 规则 1 数值字面量禁令成立:启发式扫描(warning 级,误报难免)+ 人工复核三要素——验收人 = owner GDD 回填定稿评审 + 里程碑 gate-check;时机 = 每次回填定稿;证据 = 评审记录 / gate-check 登记 | 规则 1 |
| AC-15(deferred→B1) | 同 master_seed 同动作序列 | B1 集成测试重放 | 结算步骤流逐行相同(D2 阶段无战斗载体) | 规则 5 可回放 |
| AC-16 | debug 构建且 B1 侧经 `set_reload_gate(true)` 置位(模拟战斗中);门未置位;reload 后新表校验失败 | `reload()` | 置位:返回 false、ConfigDB 值不变、不发 `config_reloaded`;未置位:返回 true 且新值经类型化属性可读且 `config_reloaded` 发出恰一次(仅 debug 构建);校验失败:旧表保留、状态停留 Ready(EC16) | 状态机表 / EC16 / 规则 6 |
| AC-17 | 未设 master_seed | `open_stream` 或 `derive_at` | push_error + 冒烟通道断言中止路径(quit 非 0)(EC7) | 规则 6 / EC7 |
| AC-18(deferred→首次 release 导出) | release 模板构建 | 致命校验错误发生;查找 override 源 | 表现符合"引擎默认错误路径"预期;override 结构性不存在(R3 边界④、校验双通道 release 侧) | 规则 6 / R3④ |
## 版本历史
| 版本 | 日期 | 改动 |
|---|---|---|
| v2 | 2026-09-14 | 两件套格式迁移:按模板 v2 骨架重排——头部收敛为状态/版本/一句话/推演件指针;设计推演(公式推导论证、数值锚定论证、候选取舍、体验锚点论证、已裁决争议)移至 `design/gdd/analysis/d2-data-config.md`;评审元数据与已闭环对账义务(#1/#2/#3/#12)入 analysis 附录;规则、数值、枚举、EC、AC、OQ 编号与内容零变更 |
| 跨评审 D2 漂移批 | 2026-09-14 | /review-all-gdds 🔴-2 五处补漂移修订批(X-8 遗漏项补登,registry 同批):**RNG 流封闭清单 2→3 流**(encounter——消费者 B1,scope = 每场战斗实例化恰一 scope;Core Rule 5 / 取数链路 / 盐值 0x8EBC6AF09C88C6E3 / 黄金向量 +1 常量〔AC-3 五→六〕/ set_master_seed 全流零计数前提)· **pool_tag 枚举增 active_inject** · **RewardConfigTable +2 字段**(active_p0=0.4 / active_decay=0.5)+ k_dmg 默认 1→0 且"C3 禁区默认值"条款解除 · **默认根行集 clear_knife→clear_blast**(注入池占比注记替代 75/25)· **HelperItemTable 更名 AuxItemTable**(A5 定稿转正,schema 目录九→十张)· X-8 补登:SettlementTable 逐表字段行补 4 字段(共十二字段)、EnemyTable MVP 行三敌种登记 · 接口漂移修订规则增"回执按受影响字段/枚举粒度逐表 diff schema 目录"流程纪律;核心规则表增依据列(lint R8 引用列迁移) |
| C3 修订批回写 | 2026-09-14 | /design-review C3 修订批(X-8 回执,registry 同批):**EnemyTable schema +2 字段**(unit_count_max——遭遇区间上界 / armor_type——护甲类型枚举 {none,a,b},克制矩阵挂 SettlementTable)· **SettlementTable +4 字段**(damage_a_per_cell=1 / damage_a_cap=8 / damage_b_per_flag=2 / armor_effectiveness 克制矩阵——护甲克制批转正,机制与结算归 B1、C3 投值)· DifficultyTable 表清单行补乘数域与 clamp 例外注记(registry 登记的组合事故例外,非 fail-fast 场景)· 敌种键更名 goblin/bugbear/ogre_chief(DND 批对齐) |
| v1.0-lean | 2026-09-13 | 格式迁移:legacy 原稿按 lean 样例重排——修订注记出正文收敛至本表;核心规则 / 枚举 / 边界 / AC 转表格;加载校验与热重载链路转字符画;九张表 schema 逐表字段行入 Tuning Knobs;义务与夹具登记收敛为登记与对账义务表。正文残留旧称按 registry 与各 owner 成稿写现行值:SettlementTable 八字段口径(pool_clamp 拆 lo/hi)、p_perfect 载体域 {100,200}、source_label_map 键集含 hero_attack、RewardTable category 五值与默认根八行集——语义零变更 |
| 两级奖励批 + 基础攻击裁决 | 2026-09-11 | RewardTable category 三值扩为五值(即时 / 可选两类交付轴)+ 默认根行集重登记八行(原五行为集退役);RewardConfigTable p_min 默认 4→8;HeroTable atk 改域 [1,999] 且 0 = 加载拒绝(基础攻击恒存在),消费面 = 主角战斗段先手攻击 |
| B1 回填 + 评审批收编 | 2026-09-10 | SettlementTable 定稿回填(registry 原记七字段);同日评审批统一八字段口径——pool_clamp_lo_hi 拆 lo/hi 两字段、p_perfect 域 [100,999] 改载体域 {100,200}、source_label_map 键集补 enemy_action / flag_miss(hero_attack 随 B1 成稿) |
| B4 full 评审 delta | 2026-09-09 | RewardTable 增 display_name(九字段)+ 默认根行集登记 + RewardConfigTable 新增(单行全局四字段,接口漂移——B4 标量 knob 的 Rule 1 载体)+ Core Rule 5 reward_roll 语义正文补同步(每次 board install 批) |
| B4 回填定稿 | 2026-09-09 | RewardTable 八字段 + 跨字段校验族 + use_timing 占位转承重 + 首个 effect_id 引用回填激活 StatusEffectTable 悬空校验 + reward_roll scope 语义改写 + Tuning Knobs 奖励池占位条目清退 |
| B2 回填定稿 | 2026-09-09 | EnemyTable 八字段 + 双敌种行 + 每行集恰一敌种校验;HeroTable 新增(接口漂移回写) |
| A1 full 评审 delta | 2026-09-09 | BoardGenTable inferability_target 收窄 [0.85,1](P1 绝对锚)+ fog_target_ratio 绝对上限 min(8d, 0.25) + 布雷数下限 round(d×(W×H−9)) ≥ 1 + F2c 成本谓词(运行期禁墙钟逃生)+ elite_/boss_ 命名空间预留(第三轴登记约定,Boss=Dmax 行 + A1 OQ6 重审门) |
| A1 回填定稿 | 2026-09-09 | BoardGenTable 九字段 + F2/F2b 跨字段谓词 + solver_rules 冻结 + MVP 参考行(provisional,C3 调参所有权)——首个进度键表 |
| B3 回填定稿 | 2026-09-08 | StatusEffectTable schema 八字段 + 四行 MVP 内容(键 = effect_id,Rule 4 豁免注明;effect_id 悬空校验待首引激活) |
| A4 re-review delta | 2026-09-08 | VariationTable 增跨字段校验 hide_numbers⇒safe_nonzero(加载期致命,夹具 variation_mask_constraint)+ counter_item_ids knob 误标修正(非 knob) |
| A4 回填定稿 | 2026-09-08 | VariationTable 八字段 + fog 行 + Rule 4 豁免注明(registry 新条目 variation_table / variant_pool_gate) |
| A3 三轮评审 delta | 2026-09-07 | ActionEconomyTable 增跨字段 validate 规则(任一动作成本 ≤ ap_per_round,schema 类承载、违规致命、条件性夹具登记) |
| A3 RAP 结构修订 | 2026-09-07 | flags_per_round / flag_unit_cost / stop_multiplier / clear_multiplier 四字段删除(旗无上限、停手 / 清图折算乘数退役)、rap_refresh_cost 新增;AC-7 示例值域内修正(99 出域会使本 AC 假失败) |
| A3 回填定稿 | 2026-09-07 | ActionEconomyTable 字段清单交付 + 成本三字段入表 + 冻结字段(ap_per_round=8 等定稿值登记 registry) |
| 收口 | 2026-09-07 | 两轮评审 + delta 复核 5/5 PASS 后 Approved;CD-GDD-ALIGN 终裁 NEEDS REVISION → 修订落实 → 独立 delta 核对通过 |
| 原型占位值清退 | 2026-09-07 起随各 owner 定稿 | 标中旗 +5 / 面旗(量纲与语义经 A3 OQ5 重锚 → weaken 行)、清图 +10(→ B4 清图奖励插槽)、增伤 1.5× / 减伤 0.5× / 持续 2 次结算(→ StatusEffectTable 四行)、奖励池 5 类(→ RewardTable 五值)——机制与数值事实登记归 owner GDD 与 registry |