diff --git a/game/tests/unit/data_config/config_db_fatal_oob_test.gd b/game/tests/unit/data_config/config_db_fatal_oob_test.gd index 4fde9997..b67e2f72 100644 --- a/game/tests/unit/data_config/config_db_fatal_oob_test.gd +++ b/game/tests/unit/data_config/config_db_fatal_oob_test.gd @@ -12,6 +12,7 @@ var _db func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:200 致命真的会死:正式通道加载进程非 0 退出(装载前提组) _db = ConfigDBScript.new() var load_ok: bool = _db.load_tables("res://data/tables", true) if not load_ok: @@ -23,6 +24,7 @@ func _init() -> void: func _trigger_oob() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:204 越域请求在进入解析前致命中止(Fatal 级诊断+进程非 0,无兜底)(越域组) # 共享 .tres 上的 resolver 归最后装载的实例——autoload ConfigDB 的 _ready 在测试 _init 之后 # 二次注入覆盖私有实例,故触发前把解析通道归还被测实例(诊断落 _db,退出码判据才成立) _db.combat.board_gen._resolver = _db._resolver diff --git a/game/tests/unit/data_config/config_db_test.gd b/game/tests/unit/data_config/config_db_test.gd index d40a908a..670dc200 100644 --- a/game/tests/unit/data_config/config_db_test.gd +++ b/game/tests/unit/data_config/config_db_test.gd @@ -9,6 +9,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇 func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 干净检出:表清单内全部表加载成功、就绪态断言(装载组) var db = ConfigDBScript.new() var load_ok: bool = db.load_tables("res://data/tables", true) Kit.check("全部表加载 + 校验通过(含跨表护栏零 error 诊断)〔fv6〕", load_ok and db.diagnostics_count("error") == 0) @@ -30,6 +31,7 @@ func _init() -> void: # ---- 进度坐标 ceil 解析(R1:向上下取整保证偏难;越最大行 clamp 取最大值 + 警告去重)---- func _test_progress_resolution(db) -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:203 向上取整解析:请求无精确行返回不低于请求坐标的最近行(进度查行组) var bg_rows: Array = db.combat.board_gen.rows # 行键全集自表推导(禁写死关卡/深度数值) var level_keys: Array = [] @@ -86,6 +88,7 @@ func _test_progress_resolution(db) -> void: # ---- 变异池门控(unlock_level 闸 + ∅ board_types 通配 + 节点身份第三维)---- func _test_variant_pool_gate(db) -> void: +# oracle: design/gdd/systems/排雷循环/地形变异.md:153 候选池空合法:门控筛空时静默零布置(变异门控组) var rows: Array = db.combat.variation.rows var gated_row := {} var wildcard_row := {} @@ -128,6 +131,7 @@ func _rows_contain(pool: Array, row: Dictionary) -> bool: # ---- ShopTable 消费面(行集解析单点——runner/编排禁自扫表的契约消费侧验证)---- func _test_shop_accessors(db) -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:行结构与引用闭合(商店访问器行结构组) var products: Array = db.run.shop.product_rows() if products.is_empty(): Kit.check("ShopTable products 行族非空(表已已入库前提)", false) @@ -155,6 +159,7 @@ func _test_shop_accessors(db) -> void: # ---- MetaTable 消费面 ---- func _test_meta_accessors(db) -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:行结构与引用闭合(元进度访问器行结构组) # 无 forge_products 行族断言(行族/访问器不在表,V20 清单) var key_rows: Array = db.crossrun.meta.boss_keystone if not key_rows.is_empty(): @@ -174,6 +179,7 @@ func _test_meta_accessors(db) -> void: # ---- TABLE_MANIFEST ↔ data/tables 目录同步(各系统→D2 单点契约的登记面)---- func _test_manifest_dir_sync(db) -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 表目录无清单外文件(清单对账组) var manifest: Dictionary = db.TABLE_MANIFEST var dir := DirAccess.open("res://data/tables") var disk_files: Array = [] @@ -198,6 +204,7 @@ func _test_manifest_dir_sync(db) -> void: # (rowset_rule——行序即契约,R6 加载期兜底)③行集实例总数 ≤4(骰后值运行面,spawn 守卫——冒烟侧验证)。 func _test_rowset_discipline() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别:重复键与缺锚行逐类断言(行集纪律组) # 行集用 rows.assign([...]) 而非直接赋值:rows 是 Array[Dictionary] 定型属性,直接赋 untyped # 数组字面量会触发「Invalid assignment」脚本错误——该错误不改退出码,却会**中断本函数剩余断言** #(本函数五条纪律断言曾因此全部静默不跑,套件仍报 all green,是零覆盖假象)。 diff --git a/game/tests/unit/data_config/counter_prefix_fatal_test.gd b/game/tests/unit/data_config/counter_prefix_fatal_test.gd index 6f0e1951..6b09280e 100644 --- a/game/tests/unit/data_config/counter_prefix_fatal_test.gd +++ b/game/tests/unit/data_config/counter_prefix_fatal_test.gd @@ -20,6 +20,7 @@ const PROBE_REF := ' {\n "feat_id": "ref_probe",\n "display_name": "引用探 func _inject(dir: String, probe: String) -> bool: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:前缀违规夹具注入(前缀夹具组) var path := dir + "/feats.tres" var f := FileAccess.open(path, FileAccess.READ) if f == null: @@ -44,6 +45,7 @@ func _inject(dir: String, probe: String) -> bool: func _assert_rejected(bad_dir: String, probe: String, label: String) -> bool: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:200 致命真的会死:违规表拒绝且进程非 0(拒绝判定组) # 返回 true=正确拒绝;false=放行或夹具失败(调用方据此退出 2) if not Kit.copy_tables(SRC_DIR, bad_dir) or not _inject(bad_dir, probe): push_error("[%s] 夹具目录准备失败" % label) diff --git a/game/tests/unit/data_config/effect_kind_retired_test.gd b/game/tests/unit/data_config/effect_kind_retired_test.gd index a3b46a29..b3476417 100644 --- a/game/tests/unit/data_config/effect_kind_retired_test.gd +++ b/game/tests/unit/data_config/effect_kind_retired_test.gd @@ -39,6 +39,7 @@ const PROBE_TRADE_KEY := ' { func _inject(dir: String, probe: String) -> bool: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:退役词夹具注入(退役词夹具组) var path := dir + "/feats.tres" var f := FileAccess.open(path, FileAccess.READ) if f == null: @@ -63,6 +64,7 @@ func _inject(dir: String, probe: String) -> bool: func _assert_rejected(bad_dir: String, probe: String, label: String) -> bool: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:200 致命真的会死:退役词加载致命中止(拒绝判定组) # 返回 true=正确拒绝;false=放行或夹具失败(调用方据此退出 2) if not Kit.copy_tables(SRC_DIR, bad_dir) or not _inject(bad_dir, probe): push_error("[%s] 夹具目录准备失败" % label) diff --git a/game/tests/unit/data_config/forge_table_test.gd b/game/tests/unit/data_config/forge_table_test.gd index e26e7c61..f4d4bc34 100644 --- a/game/tests/unit/data_config/forge_table_test.gd +++ b/game/tests/unit/data_config/forge_table_test.gd @@ -12,6 +12,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 锻造表装载与就绪态(装载组) var db = ConfigDBScript.new() var load_ok: bool = db.load_tables("res://data/tables", true) Kit.check("生产 forge.tres 随全表加载 + validate 零违例〔锻造工坊批 V1〕", load_ok and db.diagnostics_count("error") == 0) @@ -26,6 +27,7 @@ func _init() -> void: # ---- 生产表六行正向(V1 生产面 + V11 行字段闭集)---- func _test_production_rows(db) -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:行 schema 正反逐类断言(生产行结构组) var rows: Array = db.crossrun.forge.route_rows() Kit.check("六路线行族规模恰 6(三武器+三甲)〔锻造工坊批 V1〕", rows.size() == 6) var ids := [] @@ -89,6 +91,7 @@ func _test_production_rows(db) -> void: # ---- 几何价格梯(V2 价格面单测半边——购买写在 T3 冒烟)---- func _test_price_ladder(db) -> void: +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:220 定价结构:每级价格随已购级数几何递增、基价与公比读表单点(价格梯组) var row: Dictionary = db.crossrun.forge.route("sword") var prices := [] for owned in range(5): @@ -147,6 +150,7 @@ func _mut_spear_initial(t) -> void: func _test_negative_cases() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖(负向 schema 组) # 夹具本身须零违例(否则负向判读失效) var t_ok = ForgeTableScript.new() t_ok.routes = _base_rows() @@ -215,6 +219,7 @@ func _test_negative_cases() -> void: # 32→31:改写族清退批摘 spike(CI run 87 追绿——词表合法收窄的断言随动)。 func _test_effect_kinds_closed() -> void: +# oracle: design/engineering/architecture/architecture.md:89 事件/事实/效果类型/算子四词表封闭(特效闭集组) Kit.check("效果引擎效果类型闭集 31 种(甲案词表退役 overflow/attack_segments+改写族清退摘 spike)〔锻造工坊批 V11〕", EffectEngineScript.EFFECT_KINDS.size() == 31) Kit.check("效果引擎事件词表在册 21 种(charge_full 位摘名——蓄力层删键批)〔锻造工坊批 V11〕", diff --git a/game/tests/unit/data_config/level_gen_params_test.gd b/game/tests/unit/data_config/level_gen_params_test.gd index 116cee43..cd87446a 100644 --- a/game/tests/unit/data_config/level_gen_params_test.gd +++ b/game/tests/unit/data_config/level_gen_params_test.gd @@ -11,6 +11,7 @@ const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 参数档表装载与就绪态(装载组) var db = ConfigDBScript.new() var load_ok: bool = db.load_tables("res://data/tables", true) Kit.check("生产 level_gen_params.tres 随全表加载 + validate 零违例〔fv2〕", @@ -42,6 +43,7 @@ func _table(rows: Array) -> Variant: # ---- 生产三行正向(V2)---- func _test_production(db) -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:127 产出合格:任意参数档产出的图必过产出约束(生产档组) var r1: Dictionary = db.run.level_gen_params.row_by_level_index(1) var r2: Dictionary = db.run.level_gen_params.row_by_level_index(2) var r3: Dictionary = db.run.level_gen_params.row_by_level_index(3) @@ -59,6 +61,7 @@ func _test_production(db) -> void: # ---- 访问器缺行防御 ---- func _test_accessor() -> void: +# oracle: design/gdd/systems/冒险循环/关卡节点图生成.md:127 参数档访问器读回与产出约束(访问器组) var t = _table([_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, 4)]) Kit.check("row_by_level_index 缺行返回空字典〔fv2〕", t.row_by_level_index(9).is_empty()) @@ -66,6 +69,7 @@ func _test_accessor() -> void: # ---- 域校验逐类负向(V2)---- func _test_negative() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:参数档 schema 拒绝(负向组) # 合法基线三行 var base := [_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, 4), _row(2, 9, 0.5, [0.5, 0.2, 0.15, 0.15], 2, 2, 6), diff --git a/game/tests/unit/data_config/reward_dangling_treasure_test.gd b/game/tests/unit/data_config/reward_dangling_treasure_test.gd index 83102d2a..5f7d785a 100644 --- a/game/tests/unit/data_config/reward_dangling_treasure_test.gd +++ b/game/tests/unit/data_config/reward_dangling_treasure_test.gd @@ -14,6 +14,7 @@ const BAD_DIR := "user://treasure_fixture_dangling" func _inject_violation() -> bool: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别:悬空引用夹具(悬空引用组) var path := BAD_DIR + "/reward.tres" var f := FileAccess.open(path, FileAccess.READ) if f == null: @@ -36,6 +37,7 @@ func _inject_violation() -> bool: func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:200 致命真的会死:悬空引用被拒绝、进程非 0(拒绝组) if not Kit.copy_tables(SRC_DIR, BAD_DIR) or not _inject_violation(): push_error("[reward_dangling_treasure_test] 夹具目录准备失败") quit(2) diff --git a/game/tests/unit/data_config/rng_service_test.gd b/game/tests/unit/data_config/rng_service_test.gd index a7ce3bfe..b2c31fd6 100644 --- a/game/tests/unit/data_config/rng_service_test.gd +++ b/game/tests/unit/data_config/rng_service_test.gd @@ -12,6 +12,7 @@ const TEST_SEED := 20260928 # 夹具种子(任意固定值;语义 = 可复 func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:201 黄金向量:登记向量逐个复算(装载组) _test_derive_determinism() _test_counter_ledger() _test_stream_draw_determinism() @@ -27,6 +28,7 @@ func _fresh_service(): # 单盐派生纯函数:两全新实例同种子逐位一致(derive_at 不推计数器——纯函数契约) func _test_derive_determinism() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:201 黄金向量:随机派生纯函数对登记向量逐个复算相等、同输入恒同输出(派生确定性组) var a = _fresh_service() var b = _fresh_service() var same := true @@ -42,6 +44,7 @@ func _test_derive_determinism() -> void: # 全局取数计数器:唯一账本——open_stream 逐次 +1(scope 单调) func _test_counter_ledger() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:202 计数器纪律:连续取号跨模拟关卡切换单调不重置(计数账本组) var rng = _fresh_service() var c0: int = rng.counter() rng.open_stream("armor_roll") @@ -59,6 +62,7 @@ func _test_counter_ledger() -> void: # 同种子两实例同标签流的实际取数序列逐位一致(回放确定性契约——存档快照=主种子+计数器的前提) func _test_stream_draw_determinism() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:201 同输入恒同输出:流取号确定性(流取号组) var faces := 20 # d20 探针面数(夹具值,非玩法表值——只用于产生可比较序列) var a = _fresh_service() var b = _fresh_service() @@ -73,6 +77,7 @@ func _test_stream_draw_determinism() -> void: # 负输入 abs 归一化(警告通道)——|−s| 与 s 同派生 func _test_negative_seed_normalization() -> void: +# oracle: design/engineering/architecture/architecture.md:90 RngService:master_seed 与各流 scope 计数器单点(种子规约组) var pos = _fresh_service() var neg = RngServiceScript.new() neg.set_master_seed(-TEST_SEED) diff --git a/game/tests/unit/data_config/rng_stream_boot_test.gd b/game/tests/unit/data_config/rng_stream_boot_test.gd index 1943c7e9..3c9a4953 100644 --- a/game/tests/unit/data_config/rng_stream_boot_test.gd +++ b/game/tests/unit/data_config/rng_stream_boot_test.gd @@ -22,6 +22,7 @@ func _init() -> void: # 单元宿主改由 `_init` 显式调用,断言逐字未动;源 :70 事件日志落盘条经主会话裁决一 # 改判集成测试——不随本宿主,见迁移账本转办清单)---- func _smoke_initialize() -> void: +# oracle: design/engineering/architecture/architecture.md:90 RngService autoload:open_stream 有状态门面与启动初始化(启动流组) print("=== Dungeon Sweeper 垂直切片冒烟 ===") var db = ConfigDBScript.new() var load_ok: bool = db.load_tables("res://data/tables", true) diff --git a/game/tests/unit/data_config/table_structure_guard_test.gd b/game/tests/unit/data_config/table_structure_guard_test.gd index 7f927de2..e52a6cbf 100644 --- a/game/tests/unit/data_config/table_structure_guard_test.gd +++ b/game/tests/unit/data_config/table_structure_guard_test.gd @@ -29,6 +29,7 @@ var _db func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:198 表结构守卫装载与就绪态(装载组) _db = ConfigDBScript.new() # 表加载失败不中止(沿被迁断言面的源语义:表损坏的红落在具体断言上—— # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) @@ -91,6 +92,7 @@ func _first_encounter_band(level_index: int, enemy_id: String) -> int: # ① 冒险表(单行全局 Rule 4 豁免——建表与接线闸;TP0 令牌池:配额结构/件值对/池总量域检) func _smoke_adventure_table() -> void: +# oracle: design/gdd/systems/冒险循环/冒险结构.md:281 冒险结构表校验(冒险表组) print("--- 冒险表(AdventureTable——令牌池行值单点:回血/配额/件值对/同调位)---") Kit.check("db.adventure 已加载(adventure.tres 入 manifest)", _db.run.adventure != null) if _db.run.adventure == null: @@ -209,6 +211,7 @@ func _smoke_adventure_table() -> void: # ---- 敌人表深度带键结构(迭代 34 建·L1 混编批换代:29 行键序=覆盖表序·(1,3) 混编行集·Boss 行集带键清单)---- func _smoke_enemy_bands() -> void: +# oracle: design/gdd/systems/冒险循环/难度曲线与Boss.md:316 难度与敌人带校验(敌人带组) print("--- 敌人表深度带键结构(29 行键序 · (1,3) 混编 · Boss 带键覆盖表)---") # ① 29 行计数 + 键序 = 覆盖表序(文件行序不变——行序即契约;(1,3) 两行相邻、熊地精在前) # 迭代 35 生成图深度扩面覆盖批随动:战斗带顶扩到各关最大生成深度(L1→6/L2→9/L3→13)、 @@ -297,6 +300,7 @@ func _smoke_enemy_bands() -> void: # ④ 掷点参数域:档位边界升序/档位值降序无零档/骰面与暴击参数在域(加载校验双保险)。 func _smoke_ac_swap_guard() -> void: +# oracle: design/gdd/systems/跨局循环/锻造工坊.md:219 切换语义:一次冒险=一个武器类型+一个甲型,冒险开始后锁死(AC 换装守卫组) print("--- 护甲等级掷点批·换装护栏 ---") # ① 三处无双轨 Kit.check("状态表 defense_up 行已退役(无双轨)", _db.combat.status_effects.row_by_id("defense_up").is_empty()) @@ -415,6 +419,7 @@ func _smoke_ac_swap_guard() -> void: # ---- 地形变异 框架面(四行两轴×两关 + 门控 + 跨字段互锁)---- func _smoke_variation_framework() -> void: +# oracle: design/gdd/systems/排雷循环/地形变异.md:153 候选池门控与静默零布置(变异框架组) print("--- 地形变异 框架面(首批四类型 + 候选池门控 + 跨字段互锁)---") Kit.check("变异清单 6 行(荆棘丛/藤蔓缠绕/岩浆/烬覆 + 落石/矿尘——首批类型定稿批 + 第三关内容批;贫矿脉 2026-10-16 用户令屏蔽删行)", _db.combat.variation.rows.size() == 6) @@ -450,6 +455,7 @@ func _smoke_variation_framework() -> void: # ---- 难度曲线与Boss DifficultyTable 结构位冒烟(AC-10:空表零行为 + 套乘点 = 行值快照前)---- func _smoke_difficulty_table() -> void: +# oracle: design/gdd/systems/冒险循环/难度曲线与Boss.md:316 难度修饰套乘(难度表组) print("--- 难度曲线与Boss·DifficultyTable 结构位 ---") Kit.check("DifficultyTable MVP 空表加载(结构位)", _db.run.difficulty != null and _db.run.difficulty.rows.is_empty()) # ① 空表零行为:敌人系统.spawn 敌值 = EnemyTable 行值原样 @@ -480,6 +486,7 @@ func _smoke_difficulty_table() -> void: # ---- ⑤ 格面资源九型 + gp_drop 流(黄金向量)---- func _smoke_nine_types_gp_stream() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:九型 GP 流校验(九型流组) print("--- 格面资源九型 + gp_drop 流(奖励字典 18 格 + 黄金向量)---") Kit.check("格面资源十型封闭(第九型 gp 九型批 2026-09-20 / 第十型 treasure_item 秘宝内容批 2026-09-29——词表 v1.2)", RewardTable.CELL_TYPES.size() == 10 and RewardTable.CELL_TYPES.has("gp") and RewardTable.CELL_TYPES.has("treasure_item")) @@ -529,6 +536,7 @@ func _smoke_nine_types_gp_stream() -> void: # ---- 元进度·MetaTable 表结构(行值全 provisional 归元进度设计文档配读数; # conversion 覆盖恰三,Boss 两档 first ≥ repeat)---- func _smoke_meta() -> void: +# oracle: design/gdd/systems/跨局循环/元进度.md:148 元进度行族校验(meta 行族组) print("--- 元进度·MetaTable 表结构 ---") var meta_tbl = _db.crossrun.meta Kit.check("MetaTable 非空 + validate 零错(三行族词表封闭/唯一/连续/域界/first≥repeat/ratio 全过)", diff --git a/game/tests/unit/data_config/treasure_target_violation_test.gd b/game/tests/unit/data_config/treasure_target_violation_test.gd index f644ac16..99ba2573 100644 --- a/game/tests/unit/data_config/treasure_target_violation_test.gd +++ b/game/tests/unit/data_config/treasure_target_violation_test.gd @@ -15,6 +15,7 @@ const BAD_DIR := "user://treasure_fixture_target_violation" func _inject_violation() -> bool: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别全覆盖:违规夹具逐类注入(真视窄域夹具组) var path := BAD_DIR + "/treasure_items.tres" var f := FileAccess.open(path, FileAccess.READ) if f == null: @@ -39,6 +40,7 @@ func _inject_violation() -> bool: func _init() -> void: +# oracle: design/gdd/systems/表现与支撑/数据配置.md:200 致命真的会死:违规表被拒绝、进程非 0(违规拒绝组) if not Kit.copy_tables(SRC_DIR, BAD_DIR) or not _inject_violation(): push_error("[treasure_target_violation_test] 夹具目录准备失败") quit(2)