# tests/unit/contracts/system_contracts_test.gd — 系统间接口契约断言(宁缺勿错——只断言已定稿行为) # 运行:godot --headless --path game --script res://tests/unit/contracts/system_contracts_test.gd # 契约线清单来源:design/dev/architecture/architecture.md「API Boundaries」表 + 各系统 AC 前言(规范源)。 # 覆盖面: # 各系统→D2 ConfigDB 类型化单点(TABLE_MANIFEST 词表封闭——伤害单据 kind/目标规则/结账政策常量); # D2↔B1 掷点用途标签 ∈ RngService 封闭词表(EC12——settlement.armor_roll_stream 成员性); # B1→D1 行协议公共字段形态(open_ticket 九栏缺省 + 协议公共键); # B4→B3 增益授予布尔受理口径(域闭 EC1 / 未知 id EC2 / 受理后投影)。 extends SceneTree const ConfigDBScript := preload("res://core/config_db.gd") const RngServiceScript := preload("res://core/rng_service.gd") const StatusContainerScript := preload("res://core/status_container.gd") const DamageControllerScript := preload("res://core/damage_controller.gd") const Kit := preload("res://tests/unit/test_kit.gd") # T6 收口:_check/汇总退出样板单点 func _init() -> void: var db = ConfigDBScript.new() var load_ok: bool = db.load_tables("res://data/tables", true) Kit.check("前置:表加载零 error", load_ok and db.diagnostics_count("error") == 0) if load_ok: _test_damage_controller_vocabularies() _test_rng_tag_closure(db) _test_ticket_nine_columns() _test_status_boolean_acceptance(db) quit(Kit.finish("system_contracts_test")) # ---- B1 兑付配方词表:五配方/三目标规则/双结账政策——常量非空且互异(封闭枚举负空间) ---- func _test_damage_controller_vocabularies() -> void: var kinds: Array = [ DamageControllerScript.KIND_HERO_ATTACK, DamageControllerScript.KIND_ENEMY_SKILL, DamageControllerScript.KIND_BOARD_TYPED, DamageControllerScript.KIND_BOARD_BLAST, DamageControllerScript.KIND_BOARD_FLAT_HERO, ] var nonempty := true for k in kinds: if String(k).is_empty(): nonempty = false Kit.check("五配方常量非空(开单面唯一分派键)", nonempty) var unique_kinds: Dictionary = {} for k in kinds: unique_kinds[String(k)] = true Kit.check("五配方常量互异(词表封闭无别名)", unique_kinds.size() == kinds.size()) var targets: Array = [ DamageControllerScript.TARGET_FRONT_ALIVE, DamageControllerScript.TARGET_HERO, DamageControllerScript.TARGET_ALL_ALIVE, ] var unique_targets: Dictionary = {} for t in targets: unique_targets[String(t)] = true Kit.check("三目标规则常量互异(结账时刻现场解析词表)", unique_targets.size() == targets.size()) Kit.check("双结账政策常量互异(A 即结 / B 攒批回切位)", String(DamageControllerScript.POLICY_IMMEDIATE) != String(DamageControllerScript.POLICY_SEGMENT)) # ---- D2↔B1:掷点用途标签 ∈ RngService 封闭词表(EC12——跨系统字面量契约的成员性断言) ---- func _test_rng_tag_closure(db) -> void: var stream_label: String = String(db.settlement.armor_roll_stream) Kit.check("settlement.armor_roll_stream ∈ RngService 封闭标签词表(EC12)", RngServiceScript.PURPOSE_TAGS.has(stream_label)) Kit.check("RngService 用途标签词表非空且互异(封闭语义由流改标签)", RngServiceScript.PURPOSE_TAGS.size() > 0) # ---- B1→D1 行协议:单据九栏缺省齐备 + 协议公共键形态 ---- func _test_ticket_nine_columns() -> void: var ctrl = _bare_controller() var t: Dictionary = ctrl.open_ticket(DamageControllerScript.KIND_BOARD_FLAT_HERO, "mine_hit", "契约探针", "棋盘兑付", 1, DamageControllerScript.TARGET_HERO, 0) var columns := ["kind", "source_class", "origin_cell", "round_index", "display", "channel", "attack_type", "base_value", "target_rule", "rider", "highlight", "context", "requires", "source_unit"] var all_present := true for c in columns: if not t.has(c): all_present = false Kit.check("单据九栏 + 可选两栏缺省齐备(开单面只写差异栏前提)", all_present) Kit.check("缺省无类型轴(attack_type 空 = 恒不查克制矩阵)", String(t.attack_type) == "") Kit.check("缺省无前置依赖(requires = -1)", int(t.requires) == -1) func _bare_controller(): var db = ConfigDBScript.new() # 只取 open_ticket 纯构造面——容器/依赖传桩(构造期不消费) var units = UnitContainerStub.new() var effects = StatusContainerStub.new() return DamageControllerScript.new(db, RngStub.new(), units, effects) # 桩容器:构造期零消费(DamageController._init 只 connect unit_died 信号——桩需同名信号) class UnitContainerStub: signal unit_died(u: Dictionary) class StatusContainerStub: pass class RngStub: pass # ---- B4→B3:增益授予布尔受理口径(受理/拒绝二元面 + 投影一致性) ---- func _test_status_boolean_acceptance(db) -> void: var effects = StatusContainerScript.new(db) # EC1 域闭:notify_battle_start 前授予 = false(受理面封闭) var any_row: Dictionary = {} if db.status_effects.rows.size() > 0: any_row = db.status_effects.rows[0] Kit.check("前置:状态表非空", not any_row.is_empty()) if any_row.is_empty(): return var probe_id: String = String(any_row.effect_id) var src := {"source_class": "safe_cell", "origin_cell": Vector2i(-1, -1), "round_index": 0} Kit.check("域闭授予拒绝(EC1——战斗域外 apply_effect = false)", effects.apply_effect(probe_id, src) == false) Kit.check("未知 effect_id 拒绝(EC2)", effects.apply_effect("no_such_effect_id__", src) == false) effects.notify_battle_start() Kit.check("开域后受理已知 id(布尔受理口径 true 通道)", effects.apply_effect(probe_id, src) == true) var active: Array = effects.query_active(String(any_row.target_side)) var projected := false for a in active: if String(a.effect_id) == probe_id: projected = int(a.effective_value) == int(a.layers) * int(a.snap_magnitude) Kit.check("受理后投影 effective_value = 层数×授予时快照(抗热重载 EC9 面)", projected)