# tests/unit/battle_settlement/effect_port_test.gd — 效果端口件验收宿主(效果端口与掷格分件批) # 断言面 = 三套(需求卡片 §9 对照表行 1/2/3 载体): # V1 端口单实例与真实发射路由(装配链同一实例+attack_commit 经真实战斗流命中可观测) # V2 业务类零引擎直连(源码契约锚——两业务文件全文零 EffectEngine/EffectContext token) # V3 伤害域事实面闭集(合成行阳性/阴性对照——载荷携带事实可达、宿主链事实不可达) # 协议照单元侧惯例:extends SceneTree+Kit.check 主通道;--script 模式无 autoload, # ConfigDB 显式实例化构造注入(各套自建 RngService——源套掷点隔离形态)。 # 运行:godot --headless --path game --script res://tests/unit/battle_settlement/effect_port_test.gd extends SceneTree const Kit := preload("res://tests/unit/test_kit.gd") const ConfigDBScript := preload("res://core/config_db.gd") const EffectEngineScript := preload("res://core/effect_engine.gd") const TEST_SEED := 42 # 夹具种子(基建值非玩法数值——同种子两跑对比的隔离前提) const LAYER_COUNTER := "swap_charge_layers" # 蓄力周转计数器(feats.tres swap_charge_layers 行) var _db func _init() -> void: # 判据出处: design/engineering/architecture/architecture.md「模块归属·功能层」battle_orchestrator 行(域文件集含 EffectPort) (效果端口宿主——装载组) _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: _smoke_effect_port_wiring() _test_business_classes_engine_free() _test_biz_fact_face_closed() quit(Kit.finish("effect_port_test")) # ---- 夹具 ---- func _enc(feats: Array) -> Dictionary: return {"level_index": 1, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp), "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "feats": feats, "tree_rules": {}, "battle_index": 1, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "boss", "adv_bucket": {}} func _orch(seed_v: int): var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(seed_v) return load("res://core/battle_orchestrator.gd").new(_db, rng) func _hero_attack_value(rows: Array) -> int: for r in rows: if String(r.get("type", "")) == "damage" and String(r.get("source_class", "")) == "hero_attack": return int(r.get("value", 0)) return -1 # ---- V1 端口单实例与真实发射路由 ---- func _smoke_effect_port_wiring() -> void: # 判据出处: design/engineering/architecture/architecture.md「模块归属·功能层」battle_orchestrator 行(域文件集含 EffectPort) (端口单实例与六 Callable 路由组) print("--- 效果端口:装配链单实例+真实发射路由 ---") # 充能跑(蓄力周转 charge_rotation 在册——attack_commit 订阅行 attack_bonus per 蓄力周转计数器) var orch_c = _orch(TEST_SEED) var rows_c: Array = [] orch_c.step_row.connect(func(row): rows_c.append(row)) Kit.check("V1 begin_battle 受理", orch_c.begin_battle(_enc(["charge_rotation"]))) # 端口同一性(内部字段直查=受控例外:端口同一性无公开查询面,对照表行 1 注明理由) var port_d = orch_c.fx_host().damage._port var port_a = orch_c.action_economy._port Kit.check("V1 端口在座(damage/action_economy 双绑)", port_d != null and port_a != null) Kit.check("V1 端口单实例(两业务类持同一 EffectPort)", port_d == port_a) # 蓄力周转预置 3(调试注入两层·数据面粒度——计数器账面直写,与 refresh_feats_test V8 同法) EffectEngineScript.counter_add(orch_c.fx_host().counters_ctx, LAYER_COUNTER, 3) # 最小回合:停手即轮末结算(主角先手攻击在战斗段同步兑付——零 await 同步链) orch_c.minesweeper.settle_batch([{"action": "stop"}]) var v_c := _hero_attack_value(rows_c) Kit.check("V1 attack_commit 命中批可观测:主角攻击行在流", v_c >= 0) Kit.check("V1 命中批经端口路由全处理(attack_bonus 消费行+counter_add set-0 行——层账 3→0)", EffectEngineScript.counter(orch_c.fx_host().counters_ctx, LAYER_COUNTER) == 0) # 对照跑(同种子同 enc 零预置——attack_commit 订阅行 when 不通过=零命中零扰动) var orch_0 = _orch(TEST_SEED) var rows_0: Array = [] orch_0.step_row.connect(func(row): rows_0.append(row)) orch_0.begin_battle(_enc(["charge_rotation"])) orch_0.minesweeper.settle_batch([{"action": "stop"}]) var v_0 := _hero_attack_value(rows_0) Kit.check("V1 对照跑攻击行在流", v_0 >= 0) Kit.check("V1 delta 读数非零(蓄力周转加算进入主角攻击基值)", v_c - v_0 >= 1) # ---- V2 业务类零引擎直连(源码契约锚——theme_projection_test 源码锚惯例)---- func _test_business_classes_engine_free() -> void: # 判据出处: design/engineering/architecture/architecture.md「模块归属·功能层」battle_orchestrator 行(域文件集含 EffectPort) (业务类零引擎直连组) print("--- 效果端口:业务类零引擎直连源码锚 ---") for path in ["res://core/damage_controller.gd", "res://core/action_economy.gd"]: var src := FileAccess.get_file_as_string(path) Kit.check("V2 扫描面非空前置:%s" % path, src.length() > 0) Kit.check("V2 全文零引擎直连 token(EffectEngine/EffectContext):%s" % path, not src.contains("EffectEngine") and not src.contains("EffectContext")) # ---- V3 伤害域事实面闭集(合成行两对照——damage_taken 真实单据流)---- func _test_biz_fact_face_closed() -> void: # 判据出处: design/engineering/architecture/architecture.md「模块归属·功能层」battle_orchestrator 行(域文件集含 EffectPort) (闭面四键 biz_fact 组) print("--- 效果端口:业务发射事实面闭集 ---") var orch = _orch(TEST_SEED) Kit.check("V3 begin_battle 受理", orch.begin_battle(_enc([]))) # 合成行直改数据面(调试注入两层·数据面粒度):effect 段逐字抄现役 shield 表行(kind/value 零推表); # rebuild 走持有 id 过滤真实表行——合成行经直改表行进入装载域(订阅面换代为合成行独占,确定性) var pos_id := "probe_shield_pos" var neg_id := "probe_shield_neg" _db.crossrun.feats.rows.append({"feat_id": pos_id, "display_name": "探针盾甲甲", "rarity": "common", "desc": "V3 阳性对照(hp_ratio 载荷携带)", "triggers": [{"on": "damage_taken", "when": [{"fact": "hp_ratio", "op": "lte", "value": 100}], "do": {"kind": "shield", "value": 1}}]}) _db.crossrun.feats.rows.append({"feat_id": neg_id, "display_name": "探针盾甲乙", "rarity": "common", "desc": "V3 阴性对照(battle_index 闭面外)", "triggers": [{"on": "damage_taken", "when": [{"fact": "battle_index", "op": "eq", "value": 1}], "do": {"kind": "shield", "value": 1}}]}) var ctrl = orch.fx_host().damage # 阳性对照(载荷携带事实——harness 能见命中):合成行独占订阅面(rebuild 持有 id 过滤—— # 只挂阳性行),board_flat_hero 真实单据流,base_value 足量避开伤害下限保底 orch.fx_host().rebuild_subscriptions([pos_id], [], {}) var hp0 := int(ctrl.units.hero_unit().get("hp", 0)) var t_a: Dictionary = ctrl.open_ticket(ctrl.KIND_BOARD_FLAT_HERO, "probe_port", "闭面探测甲", {"channel": "board", "base_value": 10, "target_rule": "hero", "ri": 1, "origin_cell": orch.minesweeper.hero_pos()}) ctrl.flush_submit(t_a) var hp1 := int(ctrl.units.hero_unit().get("hp", 0)) Kit.check("V3 阳性对照:hp_ratio 载荷 when 通过——承伤减量恰 1(10→9)", hp0 - hp1 == 9) # 阴性对照(闭面外事实——宿主链答而业务面不答):订阅面换代为阴性合成行独占,同路径零减量=F10 现状形态守卫 orch.fx_host().rebuild_subscriptions([neg_id], [], {}) var t_b: Dictionary = ctrl.open_ticket(ctrl.KIND_BOARD_FLAT_HERO, "probe_port", "闭面探测乙", {"channel": "board", "base_value": 10, "target_rule": "hero", "ri": 1, "origin_cell": orch.minesweeper.hero_pos()}) ctrl.flush_submit(t_b) var hp2 := int(ctrl.units.hero_unit().get("hp", 0)) Kit.check("V3 阴性对照:battle_index 不答——零减量(10→10,事实面未扩)", hp1 - hp2 == 10) # 闭面收窄 V4(键退役验收——经 begin_battle 真实装配链取宿主应答;键在→返 0,键退役→null) Kit.check("V4 闭面收窄:charge_layers 键退役——宿主事实应答 null", orch.fx_host().biz_fact("charge_layers") == null)