# tests/unit/data_config/level_gen_params_test.gd — 关卡节点图参数档单测(节点图生成器核心批 卡① T1/V2) # 覆盖:生产表三行加载+validate 零违例、域校验逐类负向(出口 L1 恒 1/配比合计 1/层深下限/增幅变大/ # 岔口去路数锁定/键集闭集/键重复)、ConfigDB 访问器按 level_index 读回与缺行空字典。 # 运行:godot --headless --path game --script res://tests/unit/data_config/level_gen_params_test.gd(expect 0) # 协议(tests/README):extends SceneTree + quit(code);负向用例资源直构不碰生产表。 extends SceneTree const ConfigDBScript := preload("res://core/config_db.gd") const LgpScript := preload("res://data/schemas/level_gen_params.gd") const Kit := preload("res://tests/unit/test_kit.gd") func _init() -> void: var db = ConfigDBScript.new() var load_ok: bool = db.load_tables("res://data/tables", true) Kit.check("生产 level_gen_params.tres 随全表加载 + validate 零违例〔fv2〕", load_ok and db.diagnostics_count("error") == 0) if load_ok: _test_production(db) _test_accessor() _test_negative() quit(Kit.finish("level_gen_params_test")) func _row(L: int, layers: int, bd: float, ratios: Array, entry: int, exit_c: int, floor_n: int) -> Dictionary: return {"level_index": L, "layers": layers, "branch_density": bd, "battle_ratio": ratios[0], "reward_ratio": ratios[1], "rest_ratio": ratios[2], "shop_ratio": ratios[3], "entry_count": entry, "exit_count": exit_c, "battle_floor": floor_n, "fork_ways_min": 2, "fork_ways_max": 3} func _table(rows: Array) -> Variant: var t = LgpScript.new() var typed: Array[Dictionary] = [] for r in rows: typed.append(r) t.rows = typed return t # ---- 生产三行正向(V2)---- func _test_production(db) -> void: var r1: Dictionary = db.level_gen_params_row(1) var r2: Dictionary = db.level_gen_params_row(2) var r3: Dictionary = db.level_gen_params_row(3) Kit.check("访问器读回 L1 行五旋钮齐备〔fv2〕", int(r1.get("layers", 0)) == 6 and is_equal_approx(float(r1.get("branch_density", 0.0)), 0.3) and int(r1.get("exit_count", 0)) == 1 and int(r1.get("entry_count", 0)) == 1) Kit.check("访问器读回 L2/L3 行层深 9/13 递增〔fv2〕", int(r2.get("layers", 0)) == 9 and int(r3.get("layers", 0)) == 13) Kit.check("三行出口 = 1/2/3、战斗保底 = 4/6/8 与 §8.1 表对上〔fv2〕", int(r1.get("battle_floor", 0)) == 4 and int(r2.get("exit_count", 0)) == 2 and int(r3.get("exit_count", 0)) == 3 and int(r3.get("battle_floor", 0)) == 8) Kit.check("生产表 validate 零错误〔fv2〕", db.level_gen_params.validate().is_empty()) # ---- 访问器缺行防御 ---- func _test_accessor() -> void: 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()) # ---- 域校验逐类负向(V2)---- func _test_negative() -> void: # 合法基线三行 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), _row(3, 13, 0.7, [0.55, 0.15, 0.15, 0.15], 2, 3, 8)] Kit.check("合法三行域校验零错误(夹具自证)〔fv2〕", _table(base).validate().is_empty()) # ① L1 出口改 2 → 出口恒 1 断言红(变异步同款) var bad_l1_exit := [_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 2, 4), base[1], base[2]] Kit.check("L1 出口=2 被拒(出口 L1 恒 1 已锁)〔fv2〕", not _table(bad_l1_exit).validate().is_empty()) # ② 配比合计 0.5 → 合计 1 断言红(变异步同款) var bad_ratio := [_row(1, 6, 0.3, [0.2, 0.1, 0.1, 0.1], 1, 1, 4), base[1], base[2]] Kit.check("配比合计 0.5 被拒(四类合计 1 已锁)〔fv2〕", not _table(bad_ratio).validate().is_empty()) # ③ 层深低于下限 var bad_layers := [_row(1, 5, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, 4), base[1], base[2]] Kit.check("层深 5<6 被拒(层深下限已锁)〔fv2〕", not _table(bad_layers).validate().is_empty()) # ④ 增幅未变大(6/9/11:Δ12=3、Δ23=2) var bad_growth := [base[0], _row(2, 9, 0.5, [0.5, 0.2, 0.15, 0.15], 2, 2, 6), _row(3, 11, 0.7, [0.55, 0.15, 0.15, 0.15], 2, 3, 8)] Kit.check("层深增幅倒挂被拒(递增且增幅变大)〔fv2〕", not _table(bad_growth).validate().is_empty()) # ⑤ 出口越上界(L3 出口 4) var bad_exit := [base[0], base[1], _row(3, 13, 0.7, [0.55, 0.15, 0.15, 0.15], 2, 4, 8)] Kit.check("出口 4 越域 [1,3] 被拒〔fv2〕", not _table(bad_exit).validate().is_empty()) # ⑥ 岔口去路数改 (2,4) var bad_fork_row := _row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, 4) bad_fork_row["fork_ways_max"] = 4 Kit.check("岔口去路数 (2,4) 被拒(恒 2/3 已锁)〔fv2〕", not _table([bad_fork_row, base[1], base[2]]).validate().is_empty()) # ⑦ 键集非闭集(多塞未知键) var bad_keys_row: Dictionary = _row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, 4) bad_keys_row["mystery"] = 1 Kit.check("字段键集非 ROW_KEYS 闭集被拒〔fv2〕", not _table([bad_keys_row, base[1], base[2]]).validate().is_empty()) # ⑧ level_index 重复 Kit.check("键重复行被拒〔fv2〕", not _table([base[0], base[0]]).validate().is_empty()) # ⑨ 战斗保底负值 Kit.check("战斗保底 -1 被拒(≥0)〔fv2〕", not _table([_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, -1), base[1], base[2]]).validate().is_empty()) # ⑩ 空表=合法态(沿 difficulty_table 先例) Kit.check("空表域校验零错误(合法态)〔fv2〕", _table([]).validate().is_empty())