iter49-卡1:效果端口与掷格分件批(轴戊两件建件):EffectPort六Callable路由+RewardRoller掷格面迁出+两业务类零引擎直连+effect_port_test V1-V3

This commit is contained in:
DKChan committed 2026-11-02 07:30:33 +08:00
1 parent 926b05a61c
commit 11beb4fc59
31 files changed
+697 -586

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

+1
View File
@@ -196,6 +196,7 @@ func _suites() -> Array:
{"path": "res://tests/unit/presentation/stage_background_test.gd", "expect": 0}, # B 区舞台背景三关变体与连续地面层(V1–V5/V8——ground_h 表单源/board_type 选图/缺图回退/地面几何/光柱退役)
{"path": "res://tests/unit/battle_settlement/damage_math_test.gd", "expect": 0},
{"path": "res://tests/unit/battle_settlement/damage_controller_test.gd", "expect": 0},
{"path": "res://tests/unit/battle_settlement/effect_port_test.gd", "expect": 0}, # 效果端口件(效果端口与掷格分件批 V1–V3——端口单实例/业务类零引擎直连源码锚/事实面闭集两对照)
{"path": "res://tests/unit/battle_settlement/roll_multiplier_test.gd", "expect": 0}, # 掷点与乘区宿主(敌攻击类型轴/护甲掷点行为面/乘区接线/伤害类型克制与标错/fresh 池攻击链——批 B 自 smoke_all 迁入)
{"path": "res://tests/unit/battle_settlement/seal_polarity_test.gd", "expect": 0}, # 封印与极性宿主(末点 AP 封印/封印不可逆四情况/清图奖励双份与弱形式/刷新 provenance 守卫/极性路由与封印溢价/负向字典/格面资源资格——批 B 自 smoke_all 迁入)
{"path": "res://tests/unit/battle_settlement/round_flow_skill_pool_test.gd", "expect": 0}, # 轮内流与技能池宿主(全战斗回放/轮内刷新与 V5 快照单源/遭遇区间骰/结算五段全序/技能池数据面·回放独立·EV 锚·死亡打断·校验拒绝/妨害附加/四桶计数——批 B 自 smoke_all 迁入)
+3 -3
View File
@@ -314,7 +314,7 @@ fi
# get_tree());排除注释行(行首 #/##)与字符串字面量(双引号包裹段内的 await 非代码),
# 口径仿 S1/S2 的 grep 排除手法。
# 自检:清单内文件缺位(路径写错/被改名)即判红——防扫面为空静默绿。
S8_FILES="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/settlement/round_cycle.gd game/core/damage_controller.gd game/core/settlement/battle_refresh_flow.gd"
S8_FILES="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/settlement/round_cycle.gd game/core/damage_controller.gd game/core/settlement/battle_refresh_flow.gd game/core/effect_port.gd game/core/reward_roller.gd"
S8_MISS=0
for f in $S8_FILES; do
if [ ! -f "$f" ]; then
@@ -358,7 +358,7 @@ fi
if require_scan_face "S7" "$DIR_CORE"; then
# B1 域文件集(迭代 46 卡A 增补 battle_refresh_flow——换图刷新流程件=C13 拆分边界的 B1 域拆件,
# 承载旧资源退役/装板编排调用;architecture.md「模块归属」BattleRefreshFlow 行随反哺点落文)
S7_B1_SET="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/settlement/round_cycle.gd game/core/damage_controller.gd game/core/settlement/battle_refresh_flow.gd"
S7_B1_SET="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.gd game/core/board_installer.gd game/core/settlement/aura_segment.gd game/core/settlement/combat_segment.gd game/core/settlement/decay_segment.gd game/core/settlement/grant_segment.gd game/core/settlement/verdict_segment.gd game/core/settlement/round_cycle.gd game/core/damage_controller.gd game/core/settlement/battle_refresh_flow.gd game/core/effect_port.gd game/core/reward_roller.gd"
S7_MISS=0
for f in $S7_B1_SET game/core/unit_container.gd game/core/status_container.gd game/core/reward_economy.gd; do
if [ ! -f "$f" ]; then
@@ -390,7 +390,7 @@ else
s7_scan B3 game/core/status_container.gd \
'notify_battle_start|apply_effect|query_active|decay_round|notify_battle_end'
s7_scan B4 game/core/reward_economy.gd \
'roll_board|query_identity|query_full_snapshot|grant|grant_clear|retire_mapping|polarity_row|phase|weighted_pick'
'roll_board|query_identity|query_full_snapshot|grant|grant_clear|retire_mapping|polarity_row|phase'
if [ -n "$S7_HITS" ]; then
echo "[static_scan] S7 FAIL: B2/B3/B4 API 符号越出 B1 文件集命中 $(printf '%s' "$S7_HITS" | grep -c .) 处(编排唯一性,B1 AC-43)"
printf '%s' "$S7_HITS"
@@ -0,0 +1,129 @@
# 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 charge_rotation 行)
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("--- 效果端口:装配链单实例+真实发射路由 ---")
# 充能跑(整备军备在册——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)
@@ -0,0 +1 @@
uid://b11nytp21w58b
@@ -221,27 +221,27 @@ func _smoke_global_variant() -> void:
Kit.check("确定性:同种子 Boss 图两遍 board_variants/变异格/取号逐位相同(region 布置路径)", _arr_str_eq(bb2.board_variant_ids(), boss1_ids) and bb2.variants == boss1_variants and crng2.variant_calls == boss1_calls)
# ④ 资源面偏斜投影(RewardEconomy 纯函数面——守恒/白名单/构造例/合成序)
var eco = load("res://core/reward_economy.gd").new(_db, load("res://core/rng_service.gd").new())
var base_rows: Array = eco._rollable_rows()
var base_sum: int = eco._pool_weight(base_rows)
var base_rows: Array = eco.roller._rollable_rows()
var base_sum: int = eco.roller._pool_weight(base_rows)
Kit.check("偏斜基线:正向池现行合计 77(挖掘风险批口径)", base_sum == 77)
eco.board_skew = 0
var off_rows: Array = eco._biased_cell_rows(0) # 内容数据驱动扩展批 T4:偏运权重改 reward_roll 订阅命中入参(luck_bias 注入位退役)
eco.roller.board_skew = 0
var off_rows: Array = eco.roller._biased_cell_rows(0) # 内容数据驱动扩展批 T4:偏运权重改 reward_roll 订阅命中入参(luck_bias 注入位退役)
Kit.check("off 态零扰动:board_skew=0 行集与基线逐位相同(不进投影域)", off_rows.size() == base_rows.size() and _rows_weights_equal(off_rows, base_rows))
eco.board_skew = 30
var on_rows: Array = eco._biased_cell_rows(0)
Kit.check("合计守恒:偏斜后正向池合计 = 77(整数最大余数重归一)", eco._pool_weight(on_rows) == 77)
eco.roller.board_skew = 30
var on_rows: Array = eco.roller._biased_cell_rows(0)
Kit.check("合计守恒:偏斜后正向池合计 = 77(整数最大余数重归一)", eco.roller._pool_weight(on_rows) == 77)
var wmap := _rows_weight_map(on_rows)
Kit.check("白名单豁免:weaken/gp/action 三行偏斜后逐位不动", wmap.get("weaken", -1) == 8 and wmap.get("ap_up", -1) == 6 and wmap.get("gp_coin", -1) == 2)
Kit.check("生存族下调:heal 4→3 / defense 11→7(v=30 构造例)", wmap.get("heal_minor", -1) == 3 and wmap.get("ac_up", -1) == 7)
Kit.check("进攻族上浮:attack 14→15 / blast 16→18 / pierce 8→9 / magic 8→9(v=30 构造例,等比上浮守恒)", wmap.get("attack_up", -1) == 15 and wmap.get("spark_aoe", -1) == 18 and wmap.get("pierce_strike", -1) == 9 and wmap.get("magic_bolt", -1) == 9)
eco.board_skew = 30
var combo_rows: Array = eco._biased_cell_rows(2) # 件级加算 2=偏运硬币行 value(reward_roll bias_rewards 命中面同值)
eco.roller.board_skew = 30
var combo_rows: Array = eco.roller._biased_cell_rows(2) # 件级加算 2=偏运硬币行 value(reward_roll bias_rewards 命中面同值)
var cw := _rows_weight_map(combo_rows)
Kit.check("合成序:图级偏斜先(乘法面)件级 luck_bias 后(加法 +2×四稀有类)——合计 77+8", eco._pool_weight(combo_rows) == 77 + 4 * 2)
Kit.check("合成序:图级偏斜先(乘法面)件级 luck_bias 后(加法 +2×四稀有类)——合计 77+8", eco.roller._pool_weight(combo_rows) == 77 + 4 * 2)
Kit.check("合成序构造例:skew(heal 3)+2=5 / gp 2+2=4 / pierce 9+2=11 / magic 9+2=11(图级定型后玩家构筑叠加)", cw.get("heal_minor", -1) == 5 and cw.get("gp_coin", -1) == 4 and cw.get("pierce_strike", -1) == 11 and cw.get("magic_bolt", -1) == 11)
eco.board_skew = 50
var v50: Dictionary = _rows_weight_map(eco._biased_cell_rows(0))
Kit.check("域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)", eco._pool_weight(eco._biased_cell_rows(0)) == 77 and int(v50.get("heal_minor", -1)) > 0)
eco.roller.board_skew = 50
var v50: Dictionary = _rows_weight_map(eco.roller._biased_cell_rows(0))
Kit.check("域上界 v=50:守恒成立且逐行权重恒正(域 [0,50] 安全)", eco.roller._pool_weight(eco.roller._biased_cell_rows(0)) == 77 and int(v50.get("heal_minor", -1)) > 0)
# wr10 偏运判定收口+注入接线(p5 缝拆)——判定面夹具白盒装载(现网 game/data/tables/ 无产出
# bias_rewards 的数据行,先例=tree_rules_v11_test 读 _triggers);命中数前置写在消息里防空扫描面假绿。
var wrng = load("res://core/rng_service.gd").new()
@@ -256,13 +256,13 @@ func _smoke_global_variant() -> void:
Kit.check("wr10①判定面:空订阅=%d / 装载后命中 %d 行 → luck_bias_for(4)=%d(期望 0/1/2;命中 0 即判据空转)" % [empty_bias, hit_rows, loaded_bias],
empty_bias == 0 and hit_rows == 1 and loaded_bias == 2)
Kit.check("wr10②接线面:组装点注入的查询口有效且与宿主查询口同源",
woc._reward_economy.luck_bias_query.is_valid() and woc._reward_economy.luck_bias_query == wfx.luck_bias_for)
woc._reward_economy.board_skew = 0
woc._reward_economy.roller.luck_bias_query.is_valid() and woc._reward_economy.roller.luck_bias_query == wfx.luck_bias_for)
woc._reward_economy.roller.board_skew = 0
# 取值面防御:未接线时按「取不到偏运」计红而非崩在空回调上(撤销证明⑤态下本套件其余断言仍须跑完)
var qb: Callable = woc._reward_economy.luck_bias_query
var qb: Callable = woc._reward_economy.roller.luck_bias_query
var wbias: int = int(qb.call(4)) if qb.is_valid() else -1
Kit.check("wr10③权重生效面:注入回调取回 %d → 偏运池合计 77+4*2(实测 %d)" % [wbias, woc._reward_economy._pool_weight(woc._reward_economy._biased_cell_rows(wbias))],
wbias == 2 and woc._reward_economy._pool_weight(woc._reward_economy._biased_cell_rows(wbias)) == 77 + 4 * 2)
Kit.check("wr10③权重生效面:注入回调取回 %d → 偏运池合计 77+4*2(实测 %d)" % [wbias, woc._reward_economy.roller._pool_weight(woc._reward_economy.roller._biased_cell_rows(wbias))],
wbias == 2 and woc._reward_economy.roller._pool_weight(woc._reward_economy.roller._biased_cell_rows(wbias)) == 77 + 4 * 2)
# 普通图摇号全链路确定性(off 路径)——同种子普通图资源映射逐位相同(golden 对拍另有批前快照)
var nrng = load("res://core/rng_service.gd").new()
nrng.set_master_seed(42)