# 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.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) 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.run.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)] # ① 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]] # ② 配比合计 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]] # ③ 层深低于下限 var bad_layers := [_row(1, 5, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, 4), base[1], base[2]] # ④ 增幅未变大(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)] # ⑤ 出口越上界(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)] # ⑥ 岔口去路数改 (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 # ⑦ 键集非闭集(多塞未知键) 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 # 逐类负向表:[个案标签, 行集, 期望零错误]——每行仍逐案断言,消息带期望/实际与违例明细 var cases: Array = [ ["合法三行域校验零错误(夹具自证)", base, true], ["L1 出口=2 被拒(出口 L1 恒 1 已锁)", bad_l1_exit, false], ["配比合计 0.5 被拒(四类合计 1 已锁)", bad_ratio, false], ["层深 5<6 被拒(层深下限已锁)", bad_layers, false], ["层深增幅倒挂被拒(递增且增幅变大)", bad_growth, false], ["出口 4 越域 [1,3] 被拒", bad_exit, false], ["岔口去路数 (2,4) 被拒(恒 2/3 已锁)", [bad_fork_row, base[1], base[2]], false], ["字段键集非 ROW_KEYS 闭集被拒", [bad_keys_row, base[1], base[2]], false], ["键重复行被拒", [base[0], base[0]], false], ["战斗保底 -1 被拒(≥0)", [_row(1, 6, 0.3, [0.5, 0.2, 0.2, 0.1], 1, 1, -1), base[1], base[2]], false], ["空表域校验零错误(合法态)", [], true], ] for c in cases: var errs: Array = _table(c[1]).validate() var expect_label := "零错误" if c[2] else "有违例" var actual_label := "零错误" if errs.is_empty() else "违例 %d 条 %s" % [errs.size(), str(errs)] Kit.check("域校验:%s(期望%s,实际%s)〔fv2〕" % [c[0], expect_label, actual_label], errs.is_empty() == c[2])