Files
gplan-2/game/core/battle_fx_host.gd
T
DKChan ea322599f7 卡②(结构小件批p1-p7+V15): 七点定点修落地+运行期违约通道负面套件+wr10 偏运缝拆验收——①p1 状态角标判据去重(main_overlay_flow._sync_status_entry 一组重复赋值语句删除,battle_live 局部量随之退役=wr1 载体);②p2 确认窗开合归模态栈单口(main 两处 _on_confirm_* 改 _close_modal;存档删除入口改 root._open_modal(root.confirm_dialog) 后再 open,遮挡记账/挂起兜底/开侧同步同口);③p3 棋盘动作与 stop 两入口显式 settlement 空防护(锁谓词异常时不崩于空引用);④p4 会话更替即清队:_begin_session 首行 _row_pacer.flush()(待播行不住局态=机制保证非时序约定);⑤p5 缝拆:RewardEconomy 删跨域直持(_fx/_ctx/set_fx)改注入 luck_bias_query: Callable,reward_roll 发射与 bias_rewards 求和收回 BattleFxHost.luck_bias_for(cell_count)(三参形 dispatch,不带 facts——禁复用 fx_emit 的第四实参会打开 when 事实条件面),组装点 set_fx 一行换赋值一行 1 换 1;类变量 15−2+2 净 15,gdstyle 无第 16 枚;⑥p6/p7 RngService 未知用途标签与未播种取数两分支加 violation_handler 记录位+release 下 return null 空引用即刻崩(fail-fast 传导,合法路径不可达)——debug 下 assert 先中断函数本体两态不可判别,故记录位是唯一可观测面;⑦board_generator/reward_economy 各加 fatal_handler 注入位并由组装点接 orchestrator._fatal(release 剥 assert 后仍经 battle_result 封印真中止)。新件 tests/unit/core/run_time_fatal_test.gd(manifest 登记 expect=1 negative=true):wr5 协作件 _fatal 经组装点传导 p5 封印+缺省形态不外溢/wr6 未知标签通道/wr7 未播种通道(各用新建 RngService 实例,播种断言 _counter==0)。wr10 三条入 variant_placement_test=判定面夹具白盒装载(现网数据无 bias_rewards 行,沿 tree_rules_v11_test 先例)+接线同源+权重生效 77+4*2。验收:variant_placement 49 checks 全绿(wr10 ①②③ PASS:空订阅 0/命中 1 行/装载后 2;查询口有效且与宿主同源;偏运池实测 85);run_time_fatal_test 退出码 1 三点绿;pacing_queue 绿(wr4 前置夹具真入队 2 行→更替后清零);ui_click_probe 问题 0(wr2 遮挡账开/关两侧断言);round_flow_skill_pool 130 checks 绿;wr11 dump_battle 六组(L{1,2}×S{42,779401,779501})与改前 before_* 逐字节 cmp 全等;gdstyle 基线外新违规 0、恶化 1(level_map_screen 类变量 22>21=iter38 存量非本批触碰面)、基线文件零改动〔结构小件批 V15〕
2026-10-27 18:39:53 +08:00

180 lines
11 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.
## 效果引擎宿主件(自 battle_orchestrator 收编 fx 族):事件发射面(fx_emit——18 发射点统一入口)、
## 补充事实判定(charge_layers 自答,余五事实转宿主)、主角 AC 三值累积与同步(本轮/本战累积器+
## 基线快照)、bonus_delta 四轴累积(战末宿主 drain 汇入账本)、揭示待兑队列与兑付(含再入闸——
## 同步信号回环防二次缓冲二次兑付)、round/battle 桶计数器持有(桶随编排器生灭语义不变)、
## round_start 发射与施加、订阅重建、桶挂引用。生命周期=随编排器(编排器 _init 同建同灭,每战新建)。
## 跨件单向接线(🔴 契约红线):行发射与五事实经构造注入的 emit_cb/facts_cb 回调宿主——协议面单点
## 留宿主,禁回调宿主私有族;揭示奖励兑付走 bind_grant 注入的兑付回调(RewardGrant 授予面——
## snapshot 查询与 grant_routed 同面);对本件零反向强引用(禁 RefCounted 双向环——两件均随每战新建)。
## 随机只经注入 rng 的 fx_reveal 封闭流(除该流外零随机;零 await——效果引擎全同步解释器)。
## 类变量封闭面(14≤15):bundle 7 项(db/rng/minesweeper/action_economy/damage/emit_cb/facts_cb,
## 缺一键 assert——引用束注入防 _init max-parameters 新键)+grant_cb+engine+counters_ctx+
## ac 束(键集封闭 base/round/battle)+_fx_bonus_delta+_fx_new_resource_types+reveal 束
## (键集封闭 queue/busy)。禁实现期增删键;禁为机检文本保留无效成员(原编排器 fx_counters_round/
## battle 两零读者成员迁入即删——同物异名同字典,桶引用经 counters_ctx 承载)。
class_name BattleFxHost
extends RefCounted
var db: ConfigDb
var rng: RngServiceCore
var minesweeper: MinesweeperRules
var action_economy: ActionEconomy
var damage: DamageController
var emit_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形:head/ri/display/value/extra)
var facts_cb: Callable # 宿主 _fx_host_fact——round_index/battle_index/seals_count/nest_count/hp_ratio 五事实宿主侧单点
var grant_cb: Callable = Callable() # 揭示兑付回调=RewardGrant 授予面(bind_grant 注入;构造序解法——本件对授予面零强引用)
var engine: EffectEngine # 效果引擎(原编排器 _fx——宿主 _init 经此转注 damage/action_economy 两 set_fx;reward_economy 经 luck_bias_for 查询口取回偏运合计)
var counters_ctx: EffectContext # 四桶引用束(round/battle 两桶本件自有;adventure/level 桶 begin_battle mount_buckets 挂引用)
var ac := {"base": -1, "round": 0, "battle": 0} # ac 束(base=主角 AC 基线快照 enc.hero_armor_class,-1=表值兜底未解析;round=ac_bonus 本轮累积器〔封印桩,round_start 清零〕;battle=本战累积器〔石肤,cap 封顶,begin_battle 清零〕)
var _fx_bonus_delta := {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0} # 本战 bonus_delta 累积(战末宿主 bonus_delta_drain 读后清零)
var _fx_new_resource_types := {} # 本战已见资源类型集(地质学家 fact new_resource_type 判定——battle 域)
var reveal := {"queue": [], "busy": false} # reveal 束(queue=揭示效果待兑队列〔见血开窍/斩首学者/地质学家/跳格采掘共用——授予段末统一兑付,防轮 facts 迭代中追加重入〕;busy=兑付中标记——宿主 _on_cell_revealed 缓冲旁路判定,防二次缓冲二次兑付)
func _init(bundle: Dictionary) -> void:
for k in ["db", "rng", "minesweeper", "action_economy", "damage", "emit_cb", "facts_cb"]:
assert(bundle.has(k), "[效果引擎宿主] 引用束缺键: %s" % k)
db = bundle.db
rng = bundle.rng
minesweeper = bundle.minesweeper
action_economy = bundle.action_economy
damage = bundle.damage
emit_cb = bundle.emit_cb
facts_cb = bundle.facts_cb
engine = EffectEngine.new(db)
counters_ctx = EffectContext.new() # round/battle 两桶即 ctx 自带字典——编排器旧 fx_counters 两成员同物异名零读者,不再另立
# 揭示兑付回调挂接(宿主 _init 建两件后接线——构造序禁环)
func bind_grant(cb: Callable) -> void:
grant_cb = cb
# 事件发射薄面(18 发射点统一入口——订阅空=零循环零扰动;确定性同步分发零 RNG)
func fx_emit(event: String, payload: Dictionary) -> Array:
return engine.dispatch(counters_ctx, event, payload, _fx_fact)
# 偏运权重查询(reward_roll 发射面——bias_rewards 词汇在本层收口,掷格域只见整数)。
# 三参形 dispatch(不带 facts:本事件订阅行 when 事实面不参与,命中集与缝拆前逐字同);
# 发射时点由掷格件在开流之后、权重装配之前调用;零命中=0=原行集零扰动。
func luck_bias_for(cell_count: int) -> int:
var total := 0
for h in engine.dispatch(counters_ctx, "reward_roll", {"polarity": "positive", "cell_count": cell_count}):
if String(h.get("kind", "")) == "bias_rewards":
total += int(h.get("value", 0))
return total
# round_start 发射+施加(grant_ap mode:queue=当轮入队,行动经济轮切换即兑现;计数器 round_start
# 重置点在 dispatch 内生效。调用点两处:begin_battle 首轮 / 轮始段续轮。payload 携 round〔宿主侧
# 事实单点〕+ap_exhausted_last〔一滴不剩 when 面〕;发射前 ac_bonus 本轮累积器清零并同步主角 AC)
func emit_round_start() -> void:
ac.round = 0
_sync_hero_ac()
for h in fx_emit("round_start", {"round": int(facts_cb.call("round_index")),
"ap_exhausted_last": action_economy.ap_exhausted_last_round()}):
if String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue":
action_economy.queue_ap_delta(int(h.get("value", 0)))
# ac_bonus 施加(mode=round 本轮累积器〔封印桩,round_start 清零〕/
# mode=battle 本战累积器〔石肤,cap 封顶;缺省 battle〕;cap=0/缺省=无上限)
func apply_ac_bonus(h: Dictionary) -> void:
var mode := String(h.get("mode", "battle"))
var cap := int(h.get("cap", 0))
if mode == "round":
ac.round = int(ac.round) + int(h.get("value", 0))
else:
ac.battle = int(ac.battle) + int(h.get("value", 0)) if cap <= 0 else mini(cap, int(ac.battle) + int(h.get("value", 0)))
_sync_hero_ac()
# 揭示效果入队(fx_reveal 掷格兑付延后到授予段末——防轮 facts 迭代中追加重入;见血开窍/
# 斩首学者〔经伤害控制器 fx_calls["reveal"] 回调〕/地质学家/跳格采掘共用)
func enqueue_reveal() -> void:
reveal.queue.append({"round": int(facts_cb.call("round_index"))})
# 揭示效果兑付(授予段末统一——fx_reveal 封闭流从未揭示安全格掷 1 格全揭示;
# 信息红线=只揭真格〔危险格不入候选〕;空候选=零兑付留归因行且不取号;
# 揭示奖励经兑付回调正向路由直接入账——reveal.busy 旁路宿主 _on_cell_revealed 缓冲防二次兑付)
func flush_pending_reveals(ri: int) -> void:
if (reveal.queue as Array).is_empty():
return
var board: BoardData = minesweeper.current_board()
while not (reveal.queue as Array).is_empty():
(reveal.queue as Array).pop_back()
var cands: Array[Variant] = []
for c in board.all_cells():
if not minesweeper.is_revealed(c) and not board.is_mine(c):
cands.append(c)
if cands.is_empty():
emit_cb.call({"type": "extended", "source_class": "fx_reveal", "origin_cell": Vector2i(-1, -1)}, ri, "揭示落空(无未揭示安全格)", 0, {"effect_id": "fx_reveal"})
continue
var rg: RandomNumberGenerator = rng.open_stream("fx_reveal")
var cell: Vector2i = cands[rg.randi_range(0, cands.size() - 1)]
reveal.busy = true
var ok: bool = minesweeper.fx_reveal(cell)
reveal.busy = false
if not ok:
emit_cb.call({"type": "extended", "source_class": "fx_reveal", "origin_cell": Vector2i(-1, -1)}, ri, "揭示落空(格子不可揭)", 0, {"effect_id": "fx_reveal"})
continue
grant_cb.call(cell, ri)
emit_cb.call({"type": "extended", "source_class": "fx_reveal", "origin_cell": cell, "highlight": "fx_reveal"}, ri, "专长揭示", 0, {"effect_id": "fx_reveal"})
# 效果引擎订阅重建(每战从持有集/装备集重建,中途获得下一战生效;树规则合成订阅同面装载。
# 装载序=feats 表序→treasure 表序→树规则,写死保种子复放。计数器四桶经 counters_ctx 引用宿主容器,无回灌时点)
func rebuild_subscriptions(feats: Array, attunement: Array, tree_rules: Array) -> void:
engine.rebuild(feats, attunement, tree_rules)
# begin_battle 桶挂引用(adventure 桶=账本共享引用跨战持久=同一字典引用,非副本搬运;
# level 非 Dictionary 即不挂——LevelRunner 每关 new 下传)
func mount_buckets(adv_bucket: Dictionary, level_bucket: Variant) -> void:
counters_ctx.adventure_bucket = adv_bucket
if level_bucket is Dictionary:
counters_ctx.level_bucket = level_bucket
# begin_battle 运行态清零(ac 三值/bonus_delta 四轴/new_resource_types/揭示队列+再入闸——
# 原编排器 begin_battle 扩签运行态就位段原序)
func reset_battle_state(ac_base: int) -> void:
ac.base = ac_base
ac.round = 0
ac.battle = 0
_fx_new_resource_types = {}
reveal.queue = []
reveal.busy = false
_fx_bonus_delta = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
# 战末 drain(读后清零;宿主 _battle_over 单消费——读改清三拍合一,时序等价)
func bonus_delta_drain() -> Dictionary:
var out := _fx_bonus_delta
_fx_bonus_delta = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
return out
# 揭示兑付中读投影(宿主 _on_cell_revealed 旁路判定——fx 揭示的奖励由兑付点直接入账,
# 此处缓冲会导致二次兑付)
func reveal_busy() -> bool:
return bool(reveal.busy)
# 补充事实面(fact 取值第二优先源——payload 未携带的运行态读数;charge_layers 本件自答
# 〔action_economy 直读〕,余五事实转宿主 facts_cb——宿主侧单点,未知事实回落 null)
func _fx_fact(fact: String) -> Variant:
if fact == "charge_layers":
return action_economy.charge_layers
return facts_cb.call(fact)
# ac_bonus 累积后同步主角 AC(石肤/封印桩消费面——defender_ac 经 set_hero_ac_override 单点;
# 基线 = enc 口径,-1(表值兜底态)时解析为表值后叠加,守恒口径与 begin_battle 注入一致)
func _sync_hero_ac() -> void:
if int(ac.base) < 0:
ac.base = int(db.hero.armor_class)
damage.set_hero_ac_override(int(ac.base) + int(ac.round) + int(ac.battle))