# tests/unit/schema_vocab_violation_test.gd — schema 词表违约词的拒绝路径集中断言面(迭代 55「QA覆盖余量收口批」T1/V1) # 覆盖:十七违约词族七检查——①empty_table 八表空行集注入(board_gen/enemy/level/meta/reward/shop/skill/status_effect) # ②settlement missing_segment/pperfect_oob/segorder_violation 三注入 ③feat+treasure zero_flat 白名单违约注入 # ④meta attune_refresh_upgrade keystone_cost 越域注入 ⑤treasure use_phase 出域注入(USE_PHASES=active_dig 枚值面) # ⑥board_gen solver_rules="single" frozen 拒(同钉 single_subset 冻结语义) ⑦meta_tree 彩蛋 4 行(>3)load_bearing 拒 # 注入形态沿在役先例(meta_progression_forge_test.gd:581 同款):load(schema).new() 新实例+真行深拷贝注入—— # 禁改共享 Resource(共享 Resource 池污染 Gotcha)。违约词经 validate() 诊断面直调取 errs 断言(非致命通道, # 不进 manifest negative 名单——本面非运行期致命路径,不触 test-conventions §5.6)。 # 协议照单元侧惯例:extends SceneTree+Kit.check 主通道;--script 模式无 autoload,ConfigDB 显式实例化构造注入。 # 运行:godot --headless --path game --script res://tests/unit/schema_vocab_violation_test.gd(expect 0) extends SceneTree const Kit := preload("res://tests/unit/test_kit.gd") const ConfigDBScript := preload("res://core/config_db.gd") var _db func _init() -> void: # 判据出处: design/engineering/test-conventions.md:67 (违约词拒绝路径——validate 诊断面直调断言宿主) _db = ConfigDBScript.new() # 表加载失败不中止(collect_only=true——诊断在此展开,红由具体断言落,沿 guard 套件同款) var load_ok: bool = _db.load_tables("res://data/tables", true) if not load_ok: push_error("[schema_vocab_violation_test] 表加载返回失败——诊断如下(红由具体断言落)") for d in _db.diagnostics(): if d.severity == "error" or d.severity == "fatal": push_error(" [%s] %s: %s" % [d.severity, d.category, d.message]) _check_empty_tables() _check_settlement_vocab() _check_zero_flat_guard() _check_keystone_cost_domain() _check_use_phase_domain() _check_solver_frozen() _check_meta_tree_load_bearing() quit(Kit.finish("schema_vocab_violation_test")) # errs 数组按 category(可选 message 子串)命中判定 func _has(errs: Array, cat: String, needle: String = "") -> bool: for e in errs: if String(e["category"]) != cat: continue if needle == "" or String(e["message"]).contains(needle): return true return false # ---- ① empty_table 八表空行集注入(fresh 新实例 rows/行族全空)---- func _check_empty_tables() -> void: # 判据出处: design/engineering/test-conventions.md:67 (空表违约词拒绝路径——validate 诊断面直调取 errs) var schemas := { "board_gen": "res://data/schemas/board_gen_table.gd", "enemy": "res://data/schemas/enemy_table.gd", "level": "res://data/schemas/level_table.gd", "meta": "res://data/schemas/meta_table.gd", "reward": "res://data/schemas/reward_table.gd", "shop": "res://data/schemas/shop_table.gd", "skill": "res://data/schemas/skill_table.gd", "status_effect": "res://data/schemas/status_effect_table.gd", } for table_name in schemas.keys(): var t = load(String(schemas[table_name])).new() Kit.check("G1-① 空表注入被拒:%s validate() errs 含 empty_table〔QA余 V1〕" % table_name, _has(t.validate(), "empty_table")) # ---- ② settlement 三注入(missing_segment/pperfect_oob/segorder_violation)---- func _check_settlement_vocab() -> void: # 判据出处: design/engineering/test-conventions.md:67 (settlement 三违约词拒绝路径——validate 诊断面直调取 errs) # missing_segment:fresh 新实例 core/damage_roll 段缺失 var seg_missing = load("res://data/schemas/settlement_table.gd").new() Kit.check("G1-② settlement 段缺失注入被拒:errs 含 missing_segment〔QA余 V1〕", _has(seg_missing.validate(), "missing_segment")) # pperfect_oob:真表深拷贝改 p_perfect=150(载体域 {100,200} 外——禁改共享 Resource,deep duplicate 隔离) var st = (_db.combat.settlement as Resource).duplicate(true) st.core.p_perfect = 150 Kit.check("G1-② p_perfect=150(域 {100,200} 外)注入被拒:errs 含 pperfect_oob〔QA余 V1〕", _has(st.validate(), "pperfect_oob")) # segorder_violation:真表深拷贝改段序(FROZEN_SEGMENT_ORDER 恒等面外) var st2 = (_db.combat.settlement as Resource).duplicate(true) st2.segment_order = PackedStringArray(["verdict", "buff_debuff", "combat", "decay", "grant"]) Kit.check("G1-② segment_order 违例注入被拒:errs 含 segorder_violation〔QA余 V1〕", _has(st2.validate(), "segorder_violation")) # ---- ③ zero_flat 白名单违约注入(feat+treasure 两表)---- func _check_zero_flat_guard() -> void: # 判据出处: design/engineering/test-conventions.md:67 (零裸 flat 白名单违约词拒绝路径——validate 诊断面直调取 errs) # feat:真行深拷贝+白名单外键(ROW_KEYS 外) var feat_row: Dictionary = (_db.crossrun.feats.rows[0] as Dictionary).duplicate(true) feat_row["ghost_flat"] = 1 var ft = load("res://data/schemas/feat_table.gd").new() ft.rows = [feat_row] Kit.check("G1-③ feat 行键出白名单注入被拒:errs 含 zero_flat〔QA余 V1〕", _has(ft.validate(), "zero_flat")) # treasure:真行深拷贝+白名单外键(KEY_WHITELIST 外) var tr_row: Dictionary = (_db.crossrun.treasure_items.rows[0] as Dictionary).duplicate(true) tr_row["ghost_flat"] = 1 var tt = load("res://data/schemas/treasure_item_table.gd").new() tt.rows = [tr_row] Kit.check("G1-③ treasure 行键出白名单注入被拒:errs 含 zero_flat〔QA余 V1〕", _has(tt.validate(), "zero_flat")) # ---- ④ meta attune_refresh_upgrade keystone_cost 越域注入 ---- func _check_keystone_cost_domain() -> void: # 判据出处: design/engineering/test-conventions.md:67 (keystone_cost 越域拒绝路径——validate 诊断面直调取 errs) var bad: Dictionary = (_db.crossrun.meta.attune_refresh_upgrade[0] as Dictionary).duplicate(true) bad["keystone_cost"] = 1000 # 域 [1,999] 外 var mt = load("res://data/schemas/meta_table.gd").new() var bad_family: Array[Dictionary] = [bad] # Array[Dictionary] 严格型——未类型化字面量赋值会 SCRIPT ERROR 中断 mt.attune_refresh_upgrade = bad_family Kit.check("G1-④ meta attune_refresh_upgrade keystone_cost=1000 越域注入被拒:errs 含 domain(keystone_cost 越域)〔QA余 V1〕", _has(mt.validate(), "domain", "keystone_cost 越域")) # ---- ⑤ treasure use_phase 出域注入(覆盖 active_dig 枚值面)---- func _check_use_phase_domain() -> void: # 判据出处: design/engineering/test-conventions.md:67 (use_phase 出域拒绝路径——validate 诊断面直调取 errs) var TT = load("res://data/schemas/treasure_item_table.gd") # 枚值面钉:USE_PHASES 单元素 = active_dig(出域注入覆盖的正是该枚值面) Kit.check("G1-⑤ use_phase 枚值面钉:USE_PHASES=[active_dig]〔QA余 V1〕", TT.USE_PHASES == ["active_dig"]) var bad: Dictionary = (_db.crossrun.treasure_items.rows[0] as Dictionary).duplicate(true) bad["use_phase"] = "post_clear_x" # 出域 var tt = TT.new() tt.rows = [bad] Kit.check("G1-⑤ treasure use_phase 出域注入被拒:errs 含 enum(use_phase 出域)〔QA余 V1〕", _has(tt.validate(), "enum", "use_phase 出域")) # ---- ⑥ board_gen solver_rules="single" frozen 拒(同钉 single_subset 冻结语义)---- func _check_solver_frozen() -> void: # 判据出处: design/engineering/test-conventions.md:67 (solver_rules 冻结语义拒绝路径——validate 诊断面直调取 errs) # 冻结语义正证:真行(single_subset)零 frozen 违例——frozen 只拒 non-single_subset var good: Dictionary = (_db.combat.board_gen.rows[0] as Dictionary).duplicate(true) var bt = load("res://data/schemas/board_gen_table.gd").new() var good_rows: Array[Dictionary] = [good] # Array[Dictionary] 严格型——未类型化字面量赋值会 SCRIPT ERROR 中断 bt.rows = good_rows Kit.check("G1-⑥ 冻结语义正证:solver_rules=single_subset 真行零 frozen 违例〔QA余 V1〕", not _has(bt.validate(), "frozen")) # 冻结拒:single 在 SOLVER_RULES 词表内(enum 合法)但被冻结面拒——注入仅命中 frozen 违约词 var bad: Dictionary = (_db.combat.board_gen.rows[0] as Dictionary).duplicate(true) bad["solver_rules"] = "single" var bt2 = load("res://data/schemas/board_gen_table.gd").new() var bad_rows: Array[Dictionary] = [bad] bt2.rows = bad_rows Kit.check("G1-⑥ solver_rules=single(enum 合法、冻结面外)注入被拒:errs 含 frozen〔QA余 V1〕", _has(bt2.validate(), "frozen")) # ---- ⑦ meta_tree 彩蛋 4 行(>3)load_bearing 注入 ---- func _check_meta_tree_load_bearing() -> void: # 判据出处: design/engineering/test-conventions.md:67 (彩蛋上限违约词拒绝路径——validate 诊断面直调取 errs) var easter: Dictionary = (_db.crossrun.meta_tree.row_by_id("easter_ley") as Dictionary).duplicate(true) var mt = load("res://data/schemas/meta_tree_table.gd").new() var bad_rows: Array = [] for i in range(4): var r: Dictionary = easter.duplicate(true) r["node_id"] = "easter_x%d" % i bad_rows.append(r) mt.rows = bad_rows Kit.check("G1-⑦ meta_tree 彩蛋 4 行(>3)注入被拒:errs 含 load_bearing(彩蛋节点 4 > 3)〔QA余 V1〕", _has(mt.validate(), "load_bearing", "彩蛋节点"))