## 效果引擎宿主件(自 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))