Files
gplan-2/game/tests/unit/battle_settlement/effect_port_test.gd
T
2026-11-04 05:08:31 +08:00

132 lines
7.8 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
_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:
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:
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:
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)