# 致命类用例(独立进程,期望退出码 1) # reward_dangling_treasure——RewardTable aux 投放行双表校验:aux 行 effect_id 引用 retired # 道具(夹具用 loot_compass)时,config_db 跨表校验必须报 reward_dangling_treasure、 # load_tables 整体拒绝(退役件/失义件不得出现在掉落投放面)。 # 运行:godot --headless --path game --script res://tests/unit/data_config/reward_dangling_treasure_test.gd # 退出码协议:1 = 校验正确拒绝(预期);2 = 违规表被放行(回归信号)。 extends SceneTree const ConfigDBScript := preload("res://core/config_db.gd") const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:BAD 表目录夹具共享(copy_tables) const SRC_DIR := "res://data/tables" const BAD_DIR := "user://treasure_fixture_dangling" func _inject_violation() -> bool: # 判据出处: design/gdd/systems/表现与支撑/数据配置.md:199 校验类别:悬空引用夹具(悬空引用组) var path := BAD_DIR + "/reward.tres" var f := FileAccess.open(path, FileAccess.READ) if f == null: return false var s := f.get_as_text() f.close() # 追加式注入(现行表已无 aux 行可作锚):向 rows 尾追加 # 一块引用已退役 loot_compass 的 aux 投放行,双表校验须拦(retired 不入投放面)。 var inject := ', {\n\n"reward_id": "treasure_dangling_probe",\n"cell_type": "treasure_item",\n"polarity": "positive",\n"effect_id": "loot_compass",\n"display_name": "悬空探针",\n"power": 0,\n"weight": 1,\n"pool_tag": "cell",\n"tier": 1\n}' var idx := s.rfind("\n}]") if idx < 0: return false s = s.substr(0, idx) + inject + s.substr(idx) var w := FileAccess.open(path, FileAccess.WRITE) if w == null: return false w.store_string(s) w.close() return true func _init() -> void: # 判据出处: 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) return var db = ConfigDBScript.new() var ok: bool = db.load_tables(BAD_DIR, false) if ok: push_error("[reward_dangling_treasure_test] 违规表被放行——aux 双表校验失守(回归)") quit(2) return if db.diagnostics_count("error") >= 1: print("[reward_dangling_treasure_test] 双表校验触发(reward_dangling_treasure 诊断在案,加载被拒)") quit(1) return push_error("[reward_dangling_treasure_test] 加载被拒但无 reward_dangling_treasure 诊断——校验通道静默化(回归)") quit(2)