diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index ce2aba07..011a90e8 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -1422,188 +1422,188 @@ func _smoke_feats() -> void: for h in hits: if String(h.get("kind", "")) == "counter_add": var sv_c = h.get("set", null) - fx.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0)) if sv_c == null else int(sv_c), sv_c != null) + EffectEngine.counter_add(orch.counters_ctx, String(h.get("counter_id", "")), int(h.get("value", 0)) if sv_c == null else int(sv_c), sv_c != null) # 开格系 fx.rebuild(["twin_pick"], [], []) for i in 4: - var hits_dp: Array = fx.dispatch("dig_paid", {"ap_cost": 1}) + var hits_dp: Array = fx.dispatch(orch.counters_ctx, "dig_paid", {"ap_cost": 1}) var refund := false for h in hits_dp: if String(h.get("kind", "")) == "grant_ap": refund = true - fx.counter_add("twin_digs", 1) + EffectEngine.counter_add(orch.counters_ctx, "adv_twin_digs", 1) if i == 1 or i == 3: check(refund, "双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" % (i + 1)) else: check(not refund, "双管镐反例:第 %d 次不返(未满 2 次)" % (i + 1)) fx.rebuild(["geologist"], [], []) - var geo_hits: Array = fx.dispatch("dig_reveal", {"new_resource_type": true}) + var geo_hits: Array = fx.dispatch(orch.counters_ctx, "dig_reveal", {"new_resource_type": true}) apply_fx.call(geo_hits) check(orch._hits_of(geo_hits, "counter_add").size() == 1, "地质学家:首开新类型集 1 点〔专长批 V3〕") - fx.counter_add("geo_types", 2) # 手动基数补齐(row1 已落账 1+2=3——满 4 种的预评估现值) - var geo_hits2: Array = fx.dispatch("dig_reveal", {"new_resource_type": true}) + EffectEngine.counter_add(orch.counters_ctx, "geo_types", 2) # 手动基数补齐(row1 已落账 1+2=3——满 4 种的预评估现值) + var geo_hits2: Array = fx.dispatch(orch.counters_ctx, "dig_reveal", {"new_resource_type": true}) apply_fx.call(geo_hits2) - check(orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and fx.counter("geo_types") == 0, + check(orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.counters_ctx, "geo_types") == 0, "地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕") - var geo_hits3: Array = fx.dispatch("dig_reveal", {"new_resource_type": false}) + var geo_hits3: Array = fx.dispatch(orch.counters_ctx, "dig_reveal", {"new_resource_type": false}) check(orch._hits_of(geo_hits3, "counter_add").is_empty(), "地质学家反例:同类型重复首开不叠点〔专长批 V3〕") fx.rebuild(["hard_hat"], [], []) - apply_fx.call(fx.dispatch("dig_reveal", {})) - apply_fx.call(fx.dispatch("dig_reveal", {})) - check(fx.counter("helm_layers") == 2, "安全帽:开安全格攒层 ×2〔专长批 V3〕") - var hh_hits: Array = fx.dispatch("mine_hit", {"damage": 6}) + apply_fx.call(fx.dispatch(orch.counters_ctx, "dig_reveal", {})) + apply_fx.call(fx.dispatch(orch.counters_ctx, "dig_reveal", {})) + check(EffectEngine.counter(orch.counters_ctx, "helm_layers") == 2, "安全帽:开安全格攒层 ×2〔专长批 V3〕") + var hh_hits: Array = fx.dispatch(orch.counters_ctx, "mine_hit", {"damage": 6}) var hh_reduce := 0 for h in hh_hits: if String(h.get("kind", "")) == "damage_reduce": - hh_reduce = fx.counter(String(h.get("value_counter", ""))) + hh_reduce = EffectEngine.counter(orch.counters_ctx, String(h.get("value_counter", ""))) check(hh_reduce == 2, "安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕") fx.rebuild(["skip_mining"], [], []) - var sk_zero: Array = fx.dispatch("dig_reveal", {"is_blank_zero": true}) + var sk_zero: Array = fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": true}) apply_fx.call(sk_zero) check(orch._hits_of(sk_zero, "counter_add").size() == 1, "跳格采掘:空白 0 格攒 1 点〔专长批 V3〕") - fx.counter_add("skip_pts", 1) # 手动基数补齐(已落账 1+1=2——满 3 点的预评估现值) - var sk_full: Array = fx.dispatch("dig_reveal", {"is_blank_zero": true}) + EffectEngine.counter_add(orch.counters_ctx, "skip_pts", 1) # 手动基数补齐(已落账 1+1=2——满 3 点的预评估现值) + var sk_full: Array = fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": true}) apply_fx.call(sk_full) check(orch._hits_of(sk_full, "reveal_tiles").size() == 1, "跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕") - check(orch._hits_of(fx.dispatch("dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty(), + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty(), "跳格采掘反例:普通开格不计点〔专长批 V3〕") fx.rebuild(["blast_accounting"], [], []) for i in 5: - var ba_hits: Array = fx.dispatch("dig_paid", {"ap_cost": 1}) + var ba_hits: Array = fx.dispatch(orch.counters_ctx, "dig_paid", {"ap_cost": 1}) for h in ba_hits: if String(h.get("kind", "")) == "counter_add": - fx.counter_add(String(h.get("counter_id", "")), 1) - check(orch._hits_of(fx.dispatch("round_end", {}), "grant_gp").size() == 1, "爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕") + EffectEngine.counter_add(orch.counters_ctx, String(h.get("counter_id", "")), 1) + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1, "爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕") fx.rebuild(["frontier_miner"], [], []) - check(orch._hits_of(fx.dispatch("dig_reveal", {"is_edge_cell": true}), "grant_material").size() == 1 - and orch._hits_of(fx.dispatch("dig_reveal", {"is_edge_cell": false}), "grant_material").is_empty(), + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_edge_cell": true}), "grant_material").size() == 1 + and orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_edge_cell": false}), "grant_material").is_empty(), "边陲矿工:边缘格产出 +1·中心格反例零产出〔专长批 V3〕") # 封印系 fx.rebuild(["seal_echo"], [], []) - apply_fx.call(fx.dispatch("seal_hit", {})) - apply_fx.call(fx.dispatch("seal_hit", {})) - var se_hits: Array = fx.dispatch("round_end", {}) + apply_fx.call(fx.dispatch(orch.counters_ctx, "seal_hit", {})) + apply_fx.call(fx.dispatch(orch.counters_ctx, "seal_hit", {})) + var se_hits: Array = fx.dispatch(orch.counters_ctx, "round_end", {}) check(orch._hits_of(se_hits, "grant_ap").size() == 1 and String((orch._hits_of(se_hits, "grant_ap")[0] as Dictionary).get("mode", "")) == "queue", "封印回响:标中计数≥2→轮末返 1 AP(下轮 queue)〔专长批 V3〕") fx.rebuild(["seal_smith"], [], []) var sm_remove := 0 for i in 3: - var sm_dispatch: Array = fx.dispatch("seal_hit", {}) + var sm_dispatch: Array = fx.dispatch(orch.counters_ctx, "seal_hit", {}) apply_fx.call(sm_dispatch) for h in sm_dispatch: if String(h.get("kind", "")) == "remove_mines": sm_remove += 1 check(sm_remove == 1, "封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕") fx.rebuild(["seal_stake"], [], []) - check(orch._hits_of(fx.dispatch("seal_hit", {}), "ac_bonus").size() == 1 - and String((orch._hits_of(fx.dispatch("seal_hit", {}), "ac_bonus")[0] as Dictionary).get("mode", "")) == "round", + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "ac_bonus").size() == 1 + and String((orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "ac_bonus")[0] as Dictionary).get("mode", "")) == "round", "封印桩:标中本轮 AC +N(round 域——round_start 清零)〔专长批 V3〕") fx.rebuild(["warning_beacon"], [], []) - check(orch._hits_of(fx.dispatch("seal_hit", {}), "preview_area").size() == 1, "示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕") + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "preview_area").size() == 1, "示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕") # 踩雷/受伤系 fx.rebuild(["pain_memory"], [], []) - apply_fx.call(fx.dispatch("mine_hit", {})) - apply_fx.call(fx.dispatch("mine_hit", {})) - var pm_hits: Array = fx.dispatch("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}) + apply_fx.call(fx.dispatch(orch.counters_ctx, "mine_hit", {})) + apply_fx.call(fx.dispatch(orch.counters_ctx, "mine_hit", {})) + var pm_hits: Array = fx.dispatch(orch.counters_ctx, "attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}) var pm_bonus := 0 for h in orch._hits_of(pm_hits, "attack_bonus"): - pm_bonus = fx.counter(String(h.get("value_counter", ""))) + pm_bonus = EffectEngine.counter(orch.counters_ctx, String(h.get("value_counter", ""))) check(pm_bonus == 2, "疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕") fx.rebuild(["fuse_wire"], [], []) - var fw_hits: Array = fx.dispatch("damage_taken", {"kind": "terrain", "amount": 6}) + var fw_hits: Array = fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 6}) var fw_shield := 0 for h in fw_hits: if String(h.get("kind", "")) == "shield": fw_shield += int(h.get("value", 0)) - check(orch._hits_of(fx.dispatch("damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty(), + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty(), "保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕") - for h in fx.dispatch("damage_taken", {"kind": "terrain", "amount": 6}): + for h in fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 6}): if String(h.get("kind", "")) == "counter_add": - fx.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0))) + EffectEngine.counter_add(orch.counters_ctx, String(h.get("counter_id", "")), int(h.get("value", 0))) check(fw_shield == 0, "保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕") fx.rebuild(["blood_insight"], [], []) - check(orch._hits_of(fx.dispatch("mine_hit", {}), "reveal_tiles").size() == 1, "见血开窍:踩雷揭示 1 格〔专长批 V3〕") + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1, "见血开窍:踩雷揭示 1 格〔专长批 V3〕") fx.rebuild(["trap_instinct"], [], []) - apply_fx.call(fx.dispatch("mine_hit", {})) - var ti_hits: Array = fx.dispatch("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}) + apply_fx.call(fx.dispatch(orch.counters_ctx, "mine_hit", {})) + var ti_hits: Array = fx.dispatch(orch.counters_ctx, "attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}) var ti_val := 0 for h in orch._hits_of(ti_hits, "attack_bonus"): - ti_val = fx.counter(String(h.get("value_counter", ""))) + ti_val = EffectEngine.counter(orch.counters_ctx, String(h.get("value_counter", ""))) check(ti_val == 2, "陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕") fx.rebuild(["chain_traps", "pain_memory"], [], []) - apply_fx.call(fx.dispatch("mine_hit", {})) - var ct_before: int = fx.counter("pain_layers") - apply_fx.call(fx.dispatch("mine_hit", {})) - check(fx.counter("pain_layers") == ct_before + 2, "连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕") + apply_fx.call(fx.dispatch(orch.counters_ctx, "mine_hit", {})) + var ct_before: int = EffectEngine.counter(orch.counters_ctx, "pain_layers") + apply_fx.call(fx.dispatch(orch.counters_ctx, "mine_hit", {})) + check(EffectEngine.counter(orch.counters_ctx, "pain_layers") == ct_before + 2, "连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕") fx.rebuild(["trap_burst"], [], []) - var tb_hits: Array = fx.dispatch("mine_hit", {"damage": 8}) + var tb_hits: Array = fx.dispatch(orch.counters_ctx, "mine_hit", {"damage": 8}) check(orch._hits_of(tb_hits, "blast_scaled").size() == 1 and float((orch._hits_of(tb_hits, "blast_scaled")[0] as Dictionary).get("mult", 0)) > 1.0, "陷阱爆发:全体敌雷伤×倍率直伤(blast_scaled——施加面值=damage×mult)〔专长批 V3〕") fx.rebuild(["blood_honey"], [], []) - check(orch._hits_of(fx.dispatch("mine_hit", {}), "heal").size() == 1, "血蜜:踩雷回复 N 生命〔专长批 V3〕") + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "heal").size() == 1, "血蜜:踩雷回复 N 生命〔专长批 V3〕") fx.rebuild(["stone_skin"], [], []) - var ss_hits: Array = fx.dispatch("damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1}) + var ss_hits: Array = fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1}) check(orch._hits_of(ss_hits, "ac_bonus").size() == 1 and int((orch._hits_of(ss_hits, "ac_bonus")[0] as Dictionary).get("cap", 0)) == 3, "石肤:受伤 AC+1(本场域 cap 3——达上限不再涨由累积器封顶)〔专长批 V3〕") fx.rebuild(["last_stand"], [], []) - check(orch._hits_of(fx.dispatch("damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1, "hp_ratio": 40}), "damage_reduce").size() == 1 - and orch._hits_of(fx.dispatch("damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 2, "hp_ratio": 80}), "damage_reduce").is_empty(), + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1, "hp_ratio": 40}), "damage_reduce").size() == 1 + and orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 2, "hp_ratio": 80}), "damage_reduce").is_empty(), "背水:生命≤50% 减伤·过半血反例零减伤〔专长批 V3〕") # 跨族桥 fx.rebuild(["breaker_strike"], [], []) - check(orch._hits_of(fx.dispatch("mine_hit", {"dug_under_seal": true}), "counter_add").size() == 1 - and orch._hits_of(fx.dispatch("mine_hit", {"dug_under_seal": false}), "counter_add").is_empty(), + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {"dug_under_seal": true}), "counter_add").size() == 1 + and orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {"dug_under_seal": false}), "counter_add").is_empty(), "破封一击:挖开带封印危险格 +1 层·无封印反例零触发〔专长批 V3〕") # 节奏系 fx.rebuild(["map_exchange"], [], []) - var me_hits: Array = fx.dispatch("dig_stop", {"ap_left": 4}) + var me_hits: Array = fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 4}) apply_fx.call(me_hits) - fx.counter_add("stop_swap", 4) # value_event="ap_left" 解析归施加面(action_economy 载荷取值)——冒烟手动落账 + EffectEngine.counter_add(orch.counters_ctx, "stop_swap", 4) # value_event="ap_left" 解析归施加面(action_economy 载荷取值)——冒烟手动落账 check(orch._hits_of(me_hits, "counter_add").size() == 1, "弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕") var me_atk := 0 - for h in orch._hits_of(fx.dispatch("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}), "attack_bonus"): - me_atk = fx.counter(String(h.get("value_counter", ""))) + for h in orch._hits_of(fx.dispatch(orch.counters_ctx, "attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}), "attack_bonus"): + me_atk = EffectEngine.counter(orch.counters_ctx, String(h.get("value_counter", ""))) check(me_atk == 4, "弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕") - check(orch._hits_of(fx.dispatch("dig_stop", {"ap_left": 2}), "counter_add").is_empty(), + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty(), "弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕") fx.rebuild(["last_drop"], [], []) - var ld_hits: Array = fx.dispatch("round_start", {"round": 2, "ap_exhausted_last": true}) + var ld_hits: Array = fx.dispatch(orch.counters_ctx, "round_start", {"round": 2, "ap_exhausted_last": true}) check(orch._hits_of(ld_hits, "grant_ap").size() == 1, "一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕") - check(orch._hits_of(fx.dispatch("round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty(), + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty(), "一滴不剩反例:上轮有余不发〔专长批 V3〕") fx.rebuild(["market_close"], [], []) for i in 8: - for h in fx.dispatch("dig_reveal", {}): + for h in fx.dispatch(orch.counters_ctx, "dig_reveal", {}): if String(h.get("kind", "")) == "counter_add": - fx.counter_add(String(h.get("counter_id", "")), 1) - check(orch._hits_of(fx.dispatch("round_end", {}), "grant_gp").size() == 1, "收盘:本轮开格≥8→+1 GP〔专长批 V3〕") + EffectEngine.counter_add(orch.counters_ctx, String(h.get("counter_id", "")), 1) + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1, "收盘:本轮开格≥8→+1 GP〔专长批 V3〕") # 战斗系 fx.rebuild(["execution_scholar"], [], []) - check(orch._hits_of(fx.dispatch("kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1, + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1, "斩首学者:击杀揭示 1 格〔专长批 V3〕") fx.rebuild(["executioner"], [], []) - var ex_hits: Array = fx.dispatch("enemy_hp_threshold", {"unit": "e1", "pct": 30, "is_boss": false}) + var ex_hits: Array = fx.dispatch(orch.counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 30, "is_boss": false}) check(orch._hits_of(ex_hits, "target_attack_bonus").size() == 1 - and orch._hits_of(fx.dispatch("enemy_hp_threshold", {"unit": "e1", "pct": 60, "is_boss": false}), "target_attack_bonus").is_empty(), + and orch._hits_of(fx.dispatch(orch.counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 60, "is_boss": false}), "target_attack_bonus").is_empty(), "处刑台:敌血量≤30% 挂单位标记加伤·阈值外反例零命中〔专长批 V3〕") fx.rebuild(["field_dressing"], [], []) - check(orch._hits_of(fx.dispatch("battle_won", {"round": 2}), "heal").size() == 1, + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1, "战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕") # 契约尖点 fx.rebuild(["blood_tax"], [], []) - check(orch._hits_of(fx.dispatch("mine_hit", {}), "bonus_delta").size() == 1 - and String((orch._hits_of(fx.dispatch("seal_hit", {}), "bonus_delta")[0] as Dictionary).get("axis", "")) == "atk", + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "bonus_delta").size() == 1 + and String((orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "bonus_delta")[0] as Dictionary).get("axis", "")) == "atk", "血税:双向同局账本(踩陷阱−2max_hp/封印命中+1atk——bonus_delta 战末汇入)〔专长批 V3〕") # 迁入 2 件 fx.rebuild(["venom_edge"], [], []) - check(orch._hits_of(fx.dispatch("clamp_check", {"matchup": "magic_vs_heavy"}), "modify_ticket").size() == 1 - and orch._hits_of(fx.dispatch("clamp_check", {"matchup": "normal_vs_light"}), "modify_ticket").is_empty(), + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "clamp_check", {"matchup": "magic_vs_heavy"}), "modify_ticket").size() == 1 + and orch._hits_of(fx.dispatch(orch.counters_ctx, "clamp_check", {"matchup": "normal_vs_light"}), "modify_ticket").is_empty(), "淬毒刃纹(迁入):克制正面(magic_vs_heavy 矩阵值>0)追加·中性面反例零触发〔专长批 V3〕") fx.rebuild(["seal_burst"], [], []) - check(orch._hits_of(fx.dispatch("seal_hit", {}), "open_ticket").size() == 1, + check(orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "open_ticket").size() == 1, "封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕") # 零持有 var orch0 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(74)) @@ -6051,36 +6051,40 @@ func _smoke_tree() -> void: # 引擎直构 + 编排授予段直注(_grant_seg._judge_seal=结算授予段封印判定单口))---- func _smoke_tree_rules() -> void: print("--- 天赋树六键行为(V10:窗/限次/聚散/守侧)---") - # 开局姿态组:每关首轮窗(battle_index 事实——引擎直构) + # 开局姿态组:每关首轮窗(battle_index 事实——引擎直构 + 自建 ctx) var fx = load("res://core/effect_engine.gd").new(null) fx.rebuild([], [], ["posture_first_strike", "posture_bulwark"]) - check((fx.dispatch("round_start", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1, + var ctx_p := EffectContext.new() + check((fx.dispatch(ctx_p, "round_start", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1, "先手压制:每关首战首轮窗命中(battle_index=1 且 round=1 → grant_ap queue)〔天赋树批 V10〕") - check((fx.dispatch("round_start", {"battle_index": 1, "round_index": 2}, Callable()) as Array).is_empty() - and (fx.dispatch("round_start", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty(), + check((fx.dispatch(ctx_p, "round_start", {"battle_index": 1, "round_index": 2}, Callable()) as Array).is_empty() + and (fx.dispatch(ctx_p, "round_start", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty(), "先手压制:首轮窗外地不触发(第二轮/第二关首战首轮——「每战首轮」旧语义换代件)") - check((fx.dispatch("damage_taken", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1 - and (fx.dispatch("damage_taken", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty(), + check((fx.dispatch(ctx_p, "damage_taken", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1 + and (fx.dispatch(ctx_p, "damage_taken", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty(), "稳健拓荒:首轮窗内受击出盾、窗外不出(shield damage_taken 同窗条件)") - # 危险直觉·锋:限次闸(引擎直构——mine_hit 挂点现成,编排收集 halve_pct 系既有 damage_mult 路径) + # 危险直觉·锋:限次闸(引擎直构——mine_hit 挂点现成;限次计数住 level 桶,跨战持久=共享同一字典引用) var fz = load("res://core/effect_engine.gd").new(null) fz.rebuild([], [], ["sense_sharp"]) + var lvl_shared := {} # 同一关的 level 桶(两编排器共享引用 = 跨战持久,替代旧 counters_export/seed 副本搬运) + var ctx_fz := EffectContext.new() + ctx_fz.level_bucket = lvl_shared var got_mult := false var got_add := false - for h in (fz.dispatch("mine_hit", {}, Callable()) as Array): + for h in (fz.dispatch(ctx_fz, "mine_hit", {}, Callable()) as Array): if String(h.get("kind", "")) == "damage_mult": got_mult = true if String(h.get("kind", "")) == "counter_add": got_add = true check(got_mult and got_add, "锋:踩雷减半命中+限次记账(counter 0<2 闸开——damage_mult+counter_add 双行同预评估态)") - fz.counter_add("tree_sense_sharp_used", 2) # 直调置数(V10 注入口径——置关卡域计数后走事件路径) - check((fz.dispatch("mine_hit", {}, Callable()) as Array).is_empty(), "锋:限次耗尽全额(counter 2 不<2 闸关——每关限次 provisional 2)") - var exported: Dictionary = fz.counters_export() + EffectEngine.counter_add(ctx_fz, "lvl_tree_sense_sharp_used", 2) # 直调置数(V10 注入口径——置 level 桶计数后走事件路径) + check((fz.dispatch(ctx_fz, "mine_hit", {}, Callable()) as Array).is_empty(), "锋:限次耗尽全额(counter 2 不<2 闸关——每关限次 provisional 2)") var fz2 = load("res://core/effect_engine.gd").new(null) fz2.rebuild([], [], ["sense_sharp"]) - fz2.counters_seed(exported) - check((fz2.dispatch("mine_hit", {}, Callable()) as Array).is_empty(), - "锋:限次跨战持久(adventure 域 counters_export/seed——断点保留同源,不新增存档域不升 v7)") + var ctx_fz2 := EffectContext.new() + ctx_fz2.level_bucket = lvl_shared # 挂同一 level 桶引用(新形态跨战持久:无 export/seed,同字典即持久) + check((fz2.dispatch(ctx_fz2, "mine_hit", {}, Callable()) as Array).is_empty(), + "锋:限次跨战持久(level 桶共享引用——替代旧 adventure 域 counters_export/seed 副本搬运)") # 守侧五场景(契约 12):编排 rig——begin_battle 携 sense_guard,授予段 _grant_seg._judge_seal 直注 var orch = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777001)) check(orch.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, diff --git a/game/tests/unit/core/effect_words_t1_test.gd b/game/tests/unit/core/effect_words_t1_test.gd index a40634ad..bd2af281 100644 --- a/game/tests/unit/core/effect_words_t1_test.gd +++ b/game/tests/unit/core/effect_words_t1_test.gd @@ -26,12 +26,12 @@ func _init() -> void: "新效果类型在册:heal/ac_bonus/damage_reduce/bonus_delta/blast_scaled/preview_area/target_attack_bonus〔专长批 V1〕") Kit.check_quiet(EffectEngineScript.OPS.has("mod_eq"), "新算子在册:mod_eq〔专长批 V1〕") - # ② 合法新词行零违例(validate_trigger_row 正例) + # ② 合法新词行零违例(validate_trigger_row 正例——前缀归属替代旧 domain 键) var errs: Array = [] var ok_row := {"on": "dig_stop", "when": [{"fact": "ap_left", "op": "gte", "value": 3}], - "do": {"kind": "counter_add", "counter_id": "stop_swap", "value_event": "ap_left", "domain": "adventure"}} + "do": {"kind": "counter_add", "counter_id": "stop_swap", "value_event": "ap_left"}} var mine_covered: bool = EffectEngineScript.validate_trigger_row("V1正例", {}, [ok_row], errs) - Kit.check_quiet(errs.is_empty() and not mine_covered, "合法新词行 validate 零违例(dig_stop/ap_left/counter_add+domain)〔专长批 V1〕") + Kit.check_quiet(errs.is_empty() and not mine_covered, "合法新词行 validate 零违例(dig_stop/ap_left/counter_add——battle 桶无前缀)〔专长批 V1〕") errs = [] EffectEngineScript.validate_trigger_row("V1正例2", {}, [{"on": "battle_won", "when": [{"fact": "hp_ratio", "op": "lte", "value": 1}], @@ -46,81 +46,85 @@ func _init() -> void: EffectEngineScript.validate_trigger_row("出域kind", {}, [{"on": "dig_stop", "do": {"kind": "nope_kind", "value": 1}}], errs) Kit.check_quiet(errs.size() == 4, "出域 on/fact/op/kind 各一行全部被拒(4 违例)〔专长批 V1〕") - # ④ 运行期命中序(合法新事件;dispatch 出域 fail-fast=debug assert〔effect_engine dispatch 首行 - # assert(EVENTS.has)——release 剥离系引擎语义,自动化覆盖走 ③ 加载期 validate 致命通道〕) + # ④ 运行期命中序(合法新事件;dispatch 出域 fail-fast=debug assert;引擎无状态——计数器经 ctx) var fx = EffectEngineScript.new(null) + var ctx_f := EffectContext.new() var hits: Array = [] fx.rebuild([], [], []) fx._push_trigger("test", "v1_row", {"on": "dig_stop", "when": [{"fact": "ap_left", "op": "gte", "value": 3}], "do": {"kind": "counter_add", "counter_id": "stop_swap", "value_event": "ap_left"}}, {}) - hits = fx.dispatch("dig_stop", {"ap_left": 5}) + hits = fx.dispatch(ctx_f, "dig_stop", {"ap_left": 5}) Kit.check_quiet(hits.size() == 1, "dig_stop 合法命中(ap_left 5≥3)〔专长批 V1〕") - hits = fx.dispatch("dig_stop", {"ap_left": 1}) + hits = fx.dispatch(ctx_f, "dig_stop", {"ap_left": 1}) Kit.check_quiet(hits.is_empty(), "dig_stop 反例不命中(ap_left 1<3)〔专长批 V1〕") Kit.check_quiet(not EffectEngineScript.EVENTS.has("dig_stop_x"), "词表封闭:出域事件词不在 EVENTS(dispatch 首行 assert 消费同一词表)〔专长批 V1〕") - # ⑤ mod_eq 算子(双管镐落法——预评估口径每第 2 次判定) + # ⑤ mod_eq 算子(双管镐落法——预评估口径每第 2 次判定;battle 桶无前缀) var fx2 = EffectEngineScript.new(null) + var ctx2 := EffectContext.new() fx2.rebuild([], [], []) fx2._push_trigger("test", "mod_row", {"on": "dig_paid", "when": [{"fact": "counter", "id": "dbl_digs", "op": "mod_eq", "value": 2, "ref": 1}], "do": {"kind": "grant_ap", "value": 1}}, {}) var refunds := 0 for i in 4: - if not fx2.dispatch("dig_paid", {"ap_cost": 1}).is_empty(): + if not fx2.dispatch(ctx2, "dig_paid", {"ap_cost": 1}).is_empty(): refunds += 1 - fx2.counter_add("dbl_digs", 1) + EffectEngine.counter_add(ctx2, "dbl_digs", 1) Kit.check_quiet(refunds == 2, "mod_eq 预评估口径:4 次付费挖格恰返 2 次(第 2/4 次)〔专长批 V1〕") - # ⑥ adventure 域计数器 export/seed 往返(T6) + # ⑥ adventure 桶共享引用跨引擎持久(T6 换代——替代旧 export/seed 副本搬运) + var adv_bucket := {} # 同一冒险账本字典引用跨两引擎=跨战持久(无副本无搬运时点) var fx3 = EffectEngineScript.new(null) fx3._push_trigger("test", "adv_row", {"on": "mine_hit", - "do": {"kind": "counter_add", "counter_id": "pain_layers", "value": 1, "domain": "adventure"}}, {}) - fx3._push_trigger("test", "adv_row2", {"on": "mine_hit", - "do": {"kind": "counter_add", "counter_id": "round_mines", "value": 1, "reset_at": "round_start"}}, {}) + "do": {"kind": "counter_add", "counter_id": "adv_persist", "value": 1}}, {}) + var ctx3 := EffectContext.new() + ctx3.adventure_bucket = adv_bucket for _i in 2: - for h in fx3.dispatch("mine_hit", {}): # 施加面同构(编排侧 counter_add 逐条落账) + for h in fx3.dispatch(ctx3, "mine_hit", {}): if String(h.get("kind", "")) == "counter_add": - fx3.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0))) - var exported: Dictionary = fx3.counters_export() - Kit.check_quiet(exported.get("pain_layers", -1) == 2 and not exported.has("round_mines"), - "export 只含 adventure 域(pain_layers=2、round_mines battle 域不入)〔专长批 V1〕") + EffectEngine.counter_add(ctx3, String(h.get("counter_id", "")), int(h.get("value", 0))) + Kit.check_quiet(EffectEngine.counter(ctx3, "adv_persist") == 2 and int(adv_bucket.get("adv_persist", -1)) == 2, + "adventure 桶就地直写共享字典累积=2(adv_ 前缀归 adventure 桶——替代旧 counters_export)〔专长批 V1〕") var fx4 = EffectEngineScript.new(null) - fx4._push_trigger("test", "adv_row_seed", {"on": "mine_hit", - "do": {"kind": "counter_add", "counter_id": "pain_layers", "value": 1, "domain": "adventure"}}, {}) - fx4.counters_seed({"pain_layers": 7}) - Kit.check_quiet(fx4.counter("pain_layers") == 7, "seed 回灌:counters_seed 后 adventure 域计数器=7〔专长批 V1〕") + fx4._push_trigger("test", "adv_row2", {"on": "mine_hit", + "do": {"kind": "counter_add", "counter_id": "adv_persist", "value": 1}}, {}) + var ctx4 := EffectContext.new() + ctx4.adventure_bucket = adv_bucket # 挂同一字典引用=下一战可读上一战持久值(替代旧 counters_seed 回灌) + Kit.check_quiet(EffectEngine.counter(ctx4, "adv_persist") == 2, + "adventure 桶共享引用跨引擎可读(无 export/seed,同字典即跨战持久)〔专长批 V1〕") - # ⑦ V4——攒层/计数类件跨事件计数(置计数→事件路径→清零点:round_start 重置/用后清零) + # ⑦ V4——前缀归桶清零/消耗语义(round_ 桶随 round_start 整桶清空;battle 无前缀计数跨 round_start 存活) var fx5 = EffectEngineScript.new(null) + var ctx5 := EffectContext.new() fx5.rebuild([], [], []) fx5._push_trigger("test", "round_row", {"on": "mine_hit", - "do": {"kind": "counter_add", "counter_id": "round_mines", "value": 1}}, {}) # 未声明 reset_at——round_start 不重置(声明制对照面) - fx5._push_trigger("test", "spend_row", {"on": "treasure_used", - "do": {"kind": "counter_spend", "counter_id": "round_mines", "value": 1}}, {}) - for h in fx5.dispatch("mine_hit", {}): + "do": {"kind": "counter_add", "counter_id": "round_mines", "value": 1}}, {}) + for h in fx5.dispatch(ctx5, "mine_hit", {}): if String(h.get("kind", "")) == "counter_add": - fx5.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0))) - for h in fx5.dispatch("mine_hit", {}): + EffectEngine.counter_add(ctx5, String(h.get("counter_id", "")), int(h.get("value", 0))) + for h in fx5.dispatch(ctx5, "mine_hit", {}): if String(h.get("kind", "")) == "counter_add": - fx5.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0))) - Kit.check_quiet(fx5.counter("round_mines") == 2, "V4:跨事件计数累积(两雷两计)〔专长批 V4〕") - Kit.check_quiet(fx5.counter_spend("round_mines", 1) and fx5.counter("round_mines") == 1, - "V4:counter_spend 写口消耗(用后清零语义承载面)〔专长批 V4〕") - fx5.dispatch("round_start", {"round": 2}) - Kit.check_quiet(fx5.counter("round_mines") == 1, - "V4:非 reset 点计数器不受 round_start 影响(reset_at 声明制——round_mines 未声明重置)〔专长批 V4〕") + EffectEngine.counter_add(ctx5, String(h.get("counter_id", "")), int(h.get("value", 0))) + Kit.check_quiet(EffectEngine.counter(ctx5, "round_mines") == 2, "V4:跨事件计数累积(两雷两计——round_ 前缀落 round 桶)〔专长批 V4〕") + Kit.check_quiet(EffectEngine.counter_spend(ctx5, "round_mines", 1) and EffectEngine.counter(ctx5, "round_mines") == 1, + "V4:counter_spend 静态写口消耗(用后清零语义承载面)〔专长批 V4〕") + fx5.dispatch(ctx5, "round_start", {"round": 2}) + Kit.check_quiet(EffectEngine.counter(ctx5, "round_mines") == 0, + "V4:round_ 前缀计数器随 round_start 整桶清空(旧 reset_at 名单声明制换代为前缀归桶)〔专长批 V4〕") + # battle 桶对照:无前缀 helm_layers 不受 round_start 影响(跨 round_start 存活) var fx6 = EffectEngineScript.new(null) + var ctx6 := EffectContext.new() fx6.rebuild([], [], []) - fx6._push_trigger("test", "reset_row", {"on": "mine_hit", - "do": {"kind": "counter_add", "counter_id": "helm_layers", "value": 1, "reset_at": "round_start"}}, {}) - for h in fx6.dispatch("mine_hit", {}): + fx6._push_trigger("test", "battle_row", {"on": "mine_hit", + "do": {"kind": "counter_add", "counter_id": "helm_layers", "value": 1}}, {}) + for h in fx6.dispatch(ctx6, "mine_hit", {}): if String(h.get("kind", "")) == "counter_add": - fx6.counter_add(String(h.get("counter_id", "")), int(h.get("value", 0))) - fx6.dispatch("round_start", {"round": 2}) - Kit.check_quiet(fx6.counter("helm_layers") == 0, - "V4:round_start 重置点清零(订阅行声明 reset_at——安全帽攒层清零语义)〔专长批 V4〕") + EffectEngine.counter_add(ctx6, String(h.get("counter_id", "")), int(h.get("value", 0))) + fx6.dispatch(ctx6, "round_start", {"round": 2}) + Kit.check_quiet(EffectEngine.counter(ctx6, "helm_layers") == 1, + "V4:battle 桶(无前缀)计数器不受 round_start 清空(round 桶整桶清空仅触及 round_ 前缀键)〔专长批 V4〕") quit(Kit.finish("effect_words_t1")) diff --git a/game/tests/unit/core/settle_segments_test.gd b/game/tests/unit/core/settle_segments_test.gd index 53c419db..68da98a1 100644 --- a/game/tests/unit/core/settle_segments_test.gd +++ b/game/tests/unit/core/settle_segments_test.gd @@ -29,8 +29,8 @@ func _init() -> void: Kit.check_quiet(oc._combat_seg != null and oc._combat_seg.oc == oc, "V4 编排器接线·战斗段〔迭代27 T2〕") Kit.check_quiet(oc._decay_seg != null and oc._decay_seg.oc == oc, "V4 编排器接线·衰减段〔迭代27 T2〕") Kit.check_quiet(oc._verdict_seg != null and oc._verdict_seg.oc == oc, "V4 编排器接线·判定段〔迭代27 T2〕") - Kit.check_quiet(oc._grant_seg._apply_counter_hit({"counter_id": "dig_combo", "value": 1}) == null - and oc._fx.counter("dig_combo") == 1, "V4 counter_add 助手单点(原五位三行组收拢后行为等价)〔迭代27 T2〕") + Kit.check_quiet(oc._grant_seg._apply_counter_hit({"counter_id": "geo_types", "value": 1}) == null + and EffectEngine.counter(oc.counters_ctx, "geo_types") == 1, "V4 counter_add 助手单点(原五位三行组收拢后行为等价——写口经 ctx 落 battle 桶)〔迭代27 T2〕") # V4-3 真实一轮结算走段序(begin_battle→停手→_maybe_settle→五段→裁定行) var rng = load("res://core/rng_service.gd").new() diff --git a/game/tests/unit/core/treasure_effects_test.gd b/game/tests/unit/core/treasure_effects_test.gd index e98f59e6..95fbd150 100644 --- a/game/tests/unit/core/treasure_effects_test.gd +++ b/game/tests/unit/core/treasure_effects_test.gd @@ -31,7 +31,7 @@ func _init() -> void: var ap_with := int(ob1.action_economy._ap) # 测试夹具直读(呈现读数走 presentation_snapshot——本断言取账本真值) Kit.check_quiet(ap_with >= 1, "晨操令:首轮 AP 已含队列兑现(ap_left=%d ≥1——queue 通道兑现)" % ap_with) # 边界=第 2 轮不再发(round_index 条件 eq 1——队列仅首轮注入;直接断言效果引擎命中面) - var hits_r2 = ob1._fx.dispatch("round_start", {"round_index": 2}) + var hits_r2 = ob1._fx.dispatch(ob1.counters_ctx, "round_start", {"round_index": 2}) var r2_ap := 0 for h in hits_r2: if String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue": @@ -42,7 +42,7 @@ func _init() -> void: var ob1b = orch_s.new(db, rng1b) Kit.check_quiet(ob1b.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(db.hero.max_hp)}) and ob1b.attune_param_sum("light_radius_bonus") == 0, "空装配基线复用:未装配件参数读数 0") - var hits_none = ob1b._fx.dispatch("round_start", {"round_index": 1}) + var hits_none = ob1b._fx.dispatch(ob1b.counters_ctx, "round_start", {"round_index": 1}) Kit.check_quiet(_ap_of(hits_none) == 0, "晨操令反例:未装配首轮零命中(订阅行随装配集装载)") # ② 双管装填(前 2 轮各 +1 / 第 3 轮零) @@ -50,10 +50,10 @@ func _init() -> void: var ob2 = orch_s.new(db, rng2) Kit.check_quiet(ob2.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(db.hero.max_hp), "attunement": ["twin_loader"]}), "双管装填:装配起战") - Kit.check_quiet(_ap_of(ob2._fx.dispatch("round_start", {"round_index": 1})) == 1 - and _ap_of(ob2._fx.dispatch("round_start", {"round_index": 2})) == 1, + Kit.check_quiet(_ap_of(ob2._fx.dispatch(ob2.counters_ctx, "round_start", {"round_index": 1})) == 1 + and _ap_of(ob2._fx.dispatch(ob2.counters_ctx, "round_start", {"round_index": 2})) == 1, "双管装填:第 1/2 轮各命中 +1(lte 2 边界内)") - Kit.check_quiet(_ap_of(ob2._fx.dispatch("round_start", {"round_index": 3})) == 0, + Kit.check_quiet(_ap_of(ob2._fx.dispatch(ob2.counters_ctx, "round_start", {"round_index": 3})) == 0, "双管装填反例:第 3 轮零命中;战斗外零发放(round_start 事件域外无触发面)") # ③ 阔视野带(参数读数=4 / 未装配=0——注入面消费在呈现桥 T4) diff --git a/game/tests/unit/core/tree_rules_v11_test.gd b/game/tests/unit/core/tree_rules_v11_test.gd index 3e88d9e6..cdda06cf 100644 --- a/game/tests/unit/core/tree_rules_v11_test.gd +++ b/game/tests/unit/core/tree_rules_v11_test.gd @@ -26,17 +26,17 @@ func _init() -> void: {"fact": "round_index", "op": "eq", "value": 1}], "do": {"kind": "shield", "value": 1}}], "sense_sharp": [{"on": "mine_hit", - "when": [{"fact": "counter", "id": "tree_sense_sharp_used", "op": "lt", "value": 2}], + "when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": 2}], "do": {"kind": "damage_mult", "value": 50}}, {"on": "mine_hit", - "when": [{"fact": "counter", "id": "tree_sense_sharp_used", "op": "lt", "value": 2}], - "do": {"kind": "counter_add", "counter_id": "tree_sense_sharp_used", "value": 1, "domain": "adventure"}}], + "when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": 2}], + "do": {"kind": "counter_add", "counter_id": "lvl_tree_sense_sharp_used", "value": 1}}], "sense_guard": [{"on": "seal_missed", - "when": [{"fact": "counter", "id": "tree_sense_guard_used", "op": "lt", "value": 2}], + "when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": 2}], "do": {"kind": "grant_ap", "mode": "queue", "value": 1}}, {"on": "seal_missed", - "when": [{"fact": "counter", "id": "tree_sense_guard_used", "op": "lt", "value": 2}], - "do": {"kind": "counter_add", "counter_id": "tree_sense_guard_used", "value": 1, "domain": "adventure"}}], + "when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": 2}], + "do": {"kind": "counter_add", "counter_id": "lvl_tree_sense_guard_used", "value": 1}}], } var six := ["posture_first_strike", "posture_bulwark", "sense_sharp", "sense_guard", "economy_hoard", "economy_spend"] # rebuild 实装面(db=null→feat/attune 两段空转,_triggers 只含树规则合成行)——行数对照载体