卡①T1+T2(拆件批wr1/wr2/wr3): BattleFxHost+RewardGrant两新件建件+fx/grant族迁移+use_treasure拆分+三触点分家——battle_fx_host.gd(14类变量:bundle7+grant_cb+engine+counters_ctx+ac/reveal两束;fx_emit/emit_round_start/apply_ac_bonus/enqueue_reveal/flush_pending_reveals取号序逐字/rebuild_subscriptions/mount_buckets/reset_battle_state/bonus_delta_drain/reveal_busy)+reward_grant.gd(15类变量:bundle11+escrow+defer+treasure_inventory+battle_ledger;grant_routed随att归因束改形/submit案C闸/use_treasure四步骨架+_charge_gate+_apply_use_kinds+_safe_dig_open+_cell_removal_open/grant_clear_reward三参形宿主态经参传入/reveal_redeem兑付回调面);宿主删fx族11成员+grant族3成员,组件出口fx_host()/reward_grant(),_fx_host_fact五事实单点+_attune_safe_seen_read读投影,treasure_inventory带setter转发,begin_battle七接线点改指,_battle_over改bonus_delta_drain,_roll_gp_drop读_reward_grant.escrow;两段三引用构造(与T4接口块同形先行)读写面改指(oc._submit实为7处G5清单漏1,oc._fx_bonus_delta axis/axis_f两形);treasure_interact_controller改ctx束8键+三现值同查+inventory_projection拆_projection_row/_auto_row;main_ui_build构造改束(grant_get=run_scope.settlement.reward_grant()现值);S7/S8登记两新件7→9;测试改址:adventure_feats 75+round_flow 8+treasure_effects 7(含G11漏载的ctrl4构造点)+settle_segments 3+seal_polarity 10(含G11漏载的_grant_clear_identity直调)+level_shop 4+forge 2,另修reward_grant内部_submit/_outcome_of旧名三处与adventure_feats orch._fx/orch0._fx.rebuild两残漏(脚本错误静默吞断言假绿形态,修后feats 138PASS/seal 73PASS恢复全额);代拍板:①RewardGrant bundle 12→11键(minesweeper改经fx_host同源引用)腾位battle_ledger独立成员(卡面15≤15算术缺口,16键=基线外NEW停卡不可取)②bind_grant落_reward_grant.reveal_redeem(query_full_snapshot唯授予件持有,D1「RewardGrant授予面」兑付回调口径)③续拆四私有(迁入形逐键过阈值义务)④grant_clear_reward测试改址传宿主态读(s._board_from_midround_refresh/perfect_sweep_earned)。验收:七套件直跑绿+零SCRIPT ERROR+gdstyle基线外新违规0(battle_orchestrator行数恶化键随拆件归绿989<1302;level_map_screen 22>21=iter38存量漏登记非本批触碰面)〔拆件批 V8〕
This commit is contained in:
1 parent
8452162665
commit
cc02bb9315
18 files changed
+903
-614
No files matched your search
@@ -0,0 +1,168 @@
|
||||
## 效果引擎宿主件(自 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——行协议面单点(束形改形后随束形)
|
||||
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/reward_economy 三 set_fx)
|
||||
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)
|
||||
|
||||
|
||||
# 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_reveal_request 回调〕/地质学家/跳格采掘共用)
|
||||
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("extended", "fx_reveal", 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("extended", "fx_reveal", Vector2i(-1, -1), ri, "揭示落空(格子不可揭)", 0, {"effect_id": "fx_reveal"}, "")
|
||||
continue
|
||||
grant_cb.call(cell, ri)
|
||||
emit_cb.call("extended", "fx_reveal", cell, ri, "专长揭示", 0, {"effect_id": "fx_reveal"}, "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))
|
||||
@@ -0,0 +1 @@
|
||||
uid://wto2l1qxitki
|
||||
@@ -1,5 +1,9 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 战斗结算编排中枢:轮次生命周期唯一 owner,五段结算(授予→增益/减益→战斗→衰减→判定)与明细行协议发射(垂直切片验证:一场完整战斗逐行归因)。
|
||||
## 效果引擎施加面归 BattleFxHost(core/battle_fx_host——fx 族持有与兑付)、奖励授予管线归 RewardGrant
|
||||
## (core/reward_grant——极性路由/行应用/秘宝使用/escrow);本类保留宿主职责面=状态机与生命周期、
|
||||
## 轮 facts 缓冲、A2 三事件订阅、五段驱动、attune 族、行发射基建(_emit_row/_assert_row_source/_fatal)、
|
||||
## 呈现投影、刷新面;两协作件经组件出口 fx_host()/reward_grant() 取件,禁链式穿透其内部。
|
||||
## 零 await;随机只经 rng 封闭流(encounter/behavior_roll/armor_roll/gp_drop)——同种子同输入逐位复现。
|
||||
## 呈现层读取唯一出口 = presentation_snapshot()/diagnostic_snapshot(),禁链式穿透内部协作对象。
|
||||
## 伤害只向 DamageController 开单(何时开单归编排、怎么算归控制器);刷新唯一通道 player_request_refresh,禁 RAP 满自动刷。
|
||||
@@ -19,7 +23,7 @@ signal settlement_snapshot(snap: Dictionary) ## 每结算末尾的全量状态
|
||||
# 判定行 verdict · 高光行 highlight
|
||||
# 扩展行:行型值=extended(单一扩展行型),效果身份由 effect_id 载荷携带(_emit_row 强制必载,
|
||||
# 缺载即断言失败零发射)。现役效果身份与发射点:
|
||||
# fx_reveal(专长揭示掷格——battle_orchestrator _flush_pending_reveals 三发射点:兑付行+落空行×2)
|
||||
# fx_reveal(专长揭示掷格——battle_fx_host flush_pending_reveals 三发射点:兑付行+落空行×2)
|
||||
# mine_neutralized(封印匠·危险格中立移除——grant_segment 封印命中 remove_mines 面)
|
||||
# fx_preview_area(示警烟火·周围资源类型——grant_segment 封印命中 preview_area 面)
|
||||
# insurance_used(完美保险·标错豁免——grant_segment 标错 forgive_seal_miss 面)
|
||||
@@ -43,21 +47,23 @@ var action_economy: ActionEconomy
|
||||
var _enemy_system: UnitContainer
|
||||
var _status_effects: StatusContainer
|
||||
var _reward_economy: RewardEconomy
|
||||
var treasure_inventory: TreasureItems # LevelRunner 注入的关卡域库存引用;
|
||||
# 每战新建本类,库存不归本类所有——注入面
|
||||
var treasure_inventory: TreasureItems = null:
|
||||
set(v):
|
||||
treasure_inventory = v
|
||||
if _reward_grant != null:
|
||||
_reward_grant.treasure_inventory = v # 库存注入转发(注入时序在宿主构造之后——外部赋值路径零改址)
|
||||
var _damage: DamageController # 伤害控制器(单据唯一兑付管线)
|
||||
var _fx_host: BattleFxHost # 效果引擎宿主件(fx 族持有与兑付——拆件定稿轴件一)
|
||||
var _reward_grant: RewardGrant # 奖励授予件(授予管线与秘宝使用——拆件定稿轴件二)
|
||||
|
||||
var encounter: Dictionary[String, Variant] = {}
|
||||
var round_index := 0
|
||||
var battle_result := "" # "" / "victory" / "defeat" / "fatal_error"
|
||||
var escrow := 0 # 战斗内 GP 缓冲(战斗域中间态——begin_battle 清零;gp_gain/gp_loss 兑付累记;随 gp_dropped 上抛即弃,不入钱包)
|
||||
|
||||
var _facts_reveals: Array[Dictionary] = []
|
||||
var _facts_seals: Array[Dictionary] = []
|
||||
var _facts_path := ""
|
||||
var _settling := false
|
||||
var _defer_board_damage := false # 案C 暂存闸(地图奖励伤害兑付在战斗段末——怪物还手后)
|
||||
var _deferred_board: Array[Dictionary] = [] # 授予段暂存伤害单据(保授予段事件序;战斗段怪物还手后按序补提交统一结账)
|
||||
# 结算五段 Segment(段边界=审计五段,段序骨架冻结于
|
||||
# SettlementTable.segment_order 语义:授予→增益/减益→战斗→衰减→判定。本类做段序编排+
|
||||
# 行发射/开单原语单点——段体逻辑归 game/core/settlement/ 五类)
|
||||
@@ -74,106 +80,16 @@ var _seal_consumed := {} # 兑付资格以危险格实例为
|
||||
var attunement: Array[Variant] = [] # 装备中同调件 item_id 集
|
||||
var _ledger: RunLedger = null # 共享四本账(begin_battle 经 enc["ledger"] 持引用;缺省新账=调试面)
|
||||
var _attune_safe_seen := 0 # 积点制件 battle 域安全开格计数(装配集 ap_charge_safe 行扫描;满 charge_n 自动兑付当轮 AP+1,无玩家发起入口)
|
||||
# ---- 效果引擎扩签运行态(新效果类型施加面;空订阅零扰动)----
|
||||
var _fx_hero_ac_base := -1 # 主角 AC 基线快照(enc.hero_armor_class;-1=表值兜底未解析)
|
||||
var _fx_ac_round := 0 # ac_bonus 本轮累积器(封印桩形态——round_start 清零)
|
||||
var nest_preview_count := -1 # 巢穴预告读数口(board_installed 命中落值;-1=未装配/未发射;呈现桥消费)
|
||||
var _fx_ac_battle := 0 # ac_bonus 本战累积器(石肤形态——cap 封顶,begin_battle 清零)
|
||||
var _fx_bonus_delta := {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0} # 本战 bonus_delta 累积(战末经 _ledger.bonus_add_axis 汇入后清零)
|
||||
var _fx_new_resource_types := {} # 本战已见资源类型集(地质学家 fact new_resource_type 判定——battle 域)
|
||||
var _fx_pending_reveals: Array[Dictionary] = [] # 揭示效果待兑队列(见血开窍/斩首学者/地质学家/跳格采掘——授予段末统一兑付,防轮 facts 迭代中追加重入)
|
||||
var _fx_revealing := false # fx 揭示兑付中标记(_on_cell_revealed 缓冲旁路——fx 揭示的奖励由兑付点直接入账,防二次缓冲二次兑付)
|
||||
# 特性运行态(relic 持有集经 enc.feats 注入:效果行为=数据行 triggers 经
|
||||
# 效果引擎分发;魔矢每战首次计数 battle 域。空集=零扰动)
|
||||
var _feat_first_dig_used := false
|
||||
# 效果引擎(发布订阅解释器;begin_battle 从持有集/装备集重建订阅。
|
||||
# 施加面:发射点 dispatch 返回命中,发射点按组合规则施加)
|
||||
var _fx: EffectEngine
|
||||
var fx_counters_round: Dictionary[Variant, Variant] = {} # round 桶(轮始整桶清空;随编排器生灭)
|
||||
var fx_counters_battle: Dictionary[Variant, Variant] = {} # battle 桶(无前缀 id 缺省归此;随编排器每战 new 天然空)
|
||||
var counters_ctx: EffectContext = null # 四桶引用束(_init 建、begin_battle 挂 adventure/level 引用)
|
||||
|
||||
|
||||
# 事件发射薄面(18 发射点统一入口——订阅空=零循环零扰动;确定性同步分发零 RNG)
|
||||
func _fx_emit(event: String, payload: Dictionary) -> Array:
|
||||
if _fx == null:
|
||||
return []
|
||||
return _fx.dispatch(counters_ctx, event, payload, _fx_fact)
|
||||
|
||||
|
||||
# round_start 发射+施加(内容数据驱动扩展批 T3——grant_ap mode:queue=当轮入队,行动经济轮切换
|
||||
# 即兑现;计数器 round_start 重置点在 dispatch 内生效。调用点两处:begin_battle 首轮 / 轮始段续轮。
|
||||
# payload 扩 ap_exhausted_last〔一滴不剩 when 面〕+ac_bonus 本轮累积器清零)
|
||||
func _emit_round_start() -> void:
|
||||
_fx_ac_round = 0
|
||||
_sync_hero_ac()
|
||||
for h in _fx_emit("round_start", {"round": 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 累积后同步主角 AC(石肤/封印桩消费面——defender_ac 经 set_hero_ac_override 单点;
|
||||
# 基线 = enc 口径,-1(表值兜底态)时解析为表值后叠加,守恒口径与 begin_battle 注入一致)
|
||||
func _sync_hero_ac() -> void:
|
||||
if _fx_hero_ac_base < 0:
|
||||
_fx_hero_ac_base = int(db.hero.armor_class)
|
||||
_damage.set_hero_ac_override(_fx_hero_ac_base + _fx_ac_round + _fx_ac_battle)
|
||||
|
||||
|
||||
# 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":
|
||||
_fx_ac_round += int(h.get("value", 0))
|
||||
else:
|
||||
_fx_ac_battle = _fx_ac_battle + int(h.get("value", 0)) if cap <= 0 else mini(cap, _fx_ac_battle + int(h.get("value", 0)))
|
||||
_sync_hero_ac()
|
||||
|
||||
|
||||
# 揭示效果入队(fx_reveal 掷格兑付延后到授予段末——防轮 facts 迭代中追加重入;见血开窍/
|
||||
# 斩首学者〔经伤害控制器 fx_reveal_request 回调〕/地质学家/跳格采掘共用)
|
||||
func _fx_enqueue_reveal() -> void:
|
||||
_fx_pending_reveals.append({"round": round_index})
|
||||
|
||||
|
||||
# 揭示效果兑付(授予段末统一——fx_reveal 封闭流从未揭示安全格掷 1 格全揭示;
|
||||
# 信息红线=只揭真格〔危险格不入候选〕;空候选=零兑付留归因行;揭示奖励经 _grant_routed
|
||||
# 正向路由直接入账——_fx_revealing 旁路 _on_cell_revealed 缓冲防二次兑付)
|
||||
func _flush_pending_reveals(ri: int) -> void:
|
||||
if _fx_pending_reveals.is_empty():
|
||||
return
|
||||
var board: BoardData = minesweeper.current_board()
|
||||
while not _fx_pending_reveals.is_empty():
|
||||
_fx_pending_reveals.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_row("extended", "fx_reveal", 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)]
|
||||
_fx_revealing = true
|
||||
var ok: bool = minesweeper.fx_reveal(cell)
|
||||
_fx_revealing = false
|
||||
if not ok:
|
||||
_emit_row("extended", "fx_reveal", Vector2i(-1, -1), ri, "揭示落空(格子不可揭)", 0, {"effect_id": "fx_reveal"}, "")
|
||||
continue
|
||||
var snap = _reward_economy.query_full_snapshot(cell)
|
||||
if snap is Dictionary:
|
||||
_grant_routed(snap, "positive", "fx_reveal", cell, ri, "fx_reveal")
|
||||
_emit_row("extended", "fx_reveal", cell, ri, "专长揭示", 0, {"effect_id": "fx_reveal"}, "fx_reveal")
|
||||
|
||||
|
||||
# 补充事实面(fact 取值第二优先源——payload 未携带的运行态读数)
|
||||
func _fx_fact(fact: String) -> Variant:
|
||||
# 效果引擎宿主侧事实单点(fx_host facts_cb 注入——round_index/battle_index/seals_count/nest_count/
|
||||
# hp_ratio 五事实;charge_layers 归 fx_host 自答不经此;未知事实回落 null)
|
||||
func _fx_host_fact(fact: String) -> Variant:
|
||||
match fact:
|
||||
"charge_layers":
|
||||
return action_economy.charge_layers
|
||||
"nest_count":
|
||||
var n := 0
|
||||
for eff in _status_effects.query_active("enemy"):
|
||||
@@ -196,6 +112,21 @@ func _fx_fact(fact: String) -> Variant:
|
||||
return null
|
||||
|
||||
|
||||
# attune 积点读投影(RewardGrant attune_safe_seen_cb 注入面——use_treasure 拒绝文案读数;
|
||||
# attune 族原位保留,经读投影出户防组件回调宿主私有族)
|
||||
func _attune_safe_seen_read() -> int:
|
||||
return _attune_safe_seen
|
||||
|
||||
|
||||
# 组件出口(拆件后取件面——呈现与测试经此取件,禁链式穿透协作对象内部)
|
||||
func fx_host() -> BattleFxHost:
|
||||
return _fx_host
|
||||
|
||||
|
||||
func reward_grant() -> RewardGrant:
|
||||
return _reward_grant
|
||||
|
||||
|
||||
func _attune_has(item_id: String) -> bool:
|
||||
return attunement.has(item_id)
|
||||
|
||||
@@ -278,10 +209,6 @@ var _hero_atk := 0 # 主角攻击基值快照(encounte
|
||||
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
|
||||
db = db_ref
|
||||
rng = rng_ref
|
||||
_fx = EffectEngine.new(db_ref)
|
||||
counters_ctx = EffectContext.new()
|
||||
counters_ctx.round_bucket = fx_counters_round # 挂自有两桶引用(与编排器成员字段同物异名同字典)
|
||||
counters_ctx.battle_bucket = fx_counters_battle
|
||||
_board_generator = BoardGenerator.new()
|
||||
action_economy = ActionEconomy.new(db)
|
||||
minesweeper = MinesweeperRules.new(db, action_economy)
|
||||
@@ -290,17 +217,24 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
|
||||
_reward_economy = RewardEconomy.new(db, rng)
|
||||
_damage = DamageController.new(db, rng, _enemy_system, _status_effects)
|
||||
_damage.emit_row_cb = _emit_row # 行协议归属本类(9 类行协议 + 来源断言不外泄)
|
||||
_damage.set_fx(_fx, counters_ctx) # 效果引擎注入(attack_commit/clamp_check/armor_roll/kill/damage_taken 发射面)
|
||||
action_economy.set_fx(_fx, counters_ctx) # dig_paid/charge_full 发射面
|
||||
_reward_economy.set_fx(_fx, counters_ctx) # reward_roll 发射面
|
||||
_fx_host = BattleFxHost.new({"db": db, "rng": rng, "minesweeper": minesweeper,
|
||||
"action_economy": action_economy, "damage": _damage, "emit_cb": _emit_row, "facts_cb": _fx_host_fact})
|
||||
_reward_grant = RewardGrant.new({"db": db, "reward_economy": _reward_economy, "damage": _damage,
|
||||
"fx_host": _fx_host, "action_economy": action_economy, "status_effects": _status_effects,
|
||||
"units": _enemy_system, "emit_cb": _emit_row, "fatal_cb": _fatal,
|
||||
"attune_safe_seen_cb": _attune_safe_seen_read, "pool_effective_cb": _pool_effective})
|
||||
_fx_host.bind_grant(_reward_grant.reveal_redeem) # 构造序禁环:两件建齐后接线(fx 件对授予面走注入回调)
|
||||
_damage.set_fx(_fx_host.engine, _fx_host.counters_ctx) # 效果引擎注入(attack_commit/clamp_check/armor_roll/kill/damage_taken 发射面)
|
||||
action_economy.set_fx(_fx_host.engine, _fx_host.counters_ctx) # dig_paid/charge_full 发射面
|
||||
_reward_economy.set_fx(_fx_host.engine, _fx_host.counters_ctx) # reward_roll 发射面
|
||||
minesweeper.cell_revealed.connect(_on_cell_revealed)
|
||||
minesweeper.seal_judged.connect(_on_seal_judged)
|
||||
minesweeper.round_end_board.connect(_on_round_end)
|
||||
action_economy.round_end_stopped.connect(_on_round_end)
|
||||
# 结算五段接线(段持宿主引用;信号连接纪律不涉段〔段零信号连接〕)
|
||||
_grant_seg = GrantSegment.new(self)
|
||||
# 结算五段接线(授予/战斗两段持三引用=宿主+两协作件;信号连接纪律不涉段〔段零信号连接〕)
|
||||
_grant_seg = GrantSegment.new(self, _fx_host, _reward_grant)
|
||||
_aura_seg = AuraSegment.new(self)
|
||||
_combat_seg = CombatSegment.new(self)
|
||||
_combat_seg = CombatSegment.new(self, _fx_host, _reward_grant)
|
||||
_decay_seg = DecaySegment.new(self)
|
||||
_verdict_seg = VerdictSegment.new(self)
|
||||
|
||||
@@ -312,7 +246,7 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
push_warning("[战斗结算] begin_battle 重复受理")
|
||||
return false
|
||||
encounter.assign(enc)
|
||||
escrow = 0 # 战斗域零 GP 持久(每场战斗缓冲从零起算)
|
||||
_reward_grant.reset_battle() # 战斗域零 GP 持久(每场战斗缓冲从零起算;案C 闸复位)
|
||||
_damage.set_hero_ac_override(int(enc.get("hero_armor_class", -1))) # 主角护甲合流入参(伤害控制器 defender_ac 消费)
|
||||
db.set_reload_gate(true)
|
||||
if not _status_effects.notify_battle_start():
|
||||
@@ -377,21 +311,14 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
# modify 窗口、偏运=reward_roll bias_rewards;value 行字段 value_from 定影,begin_battle 不注入)
|
||||
# 特性运行态(enc.feats 携带持有集:效果行为=数据行 triggers 经效果
|
||||
# 引擎分发;流派档位=FeatsEngine 纯函数求值两运行态注伤害控制器。魔矢每战首次开格计数同步归零)
|
||||
# 扩签运行态就位:AC 基线快照、累积器清零、adventure 计数器/加算区共享引用接收
|
||||
_fx_hero_ac_base = int(enc.get("hero_armor_class", -1))
|
||||
_fx_ac_round = 0
|
||||
_fx_ac_battle = 0
|
||||
_fx_new_resource_types = {}
|
||||
_fx_pending_reveals = []
|
||||
_fx_bonus_delta = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
|
||||
# 扩签运行态就位(归 fx_host:AC 基线快照、累积器清零、揭示队列+再入闸清零、bonus_delta 四轴清零)
|
||||
_fx_host.reset_battle_state(int(enc.get("hero_armor_class", -1)))
|
||||
var ledger_ref = enc.get("ledger", null)
|
||||
_ledger = ledger_ref if ledger_ref is RunLedger else RunLedger.new() # 缺省新账=调试/单场零扰动面
|
||||
counters_ctx.adventure_bucket = _ledger.adventure_counters # 挂冒险桶引用(跨战持久=同一字典引用,非副本搬运)
|
||||
var level_ref: Variant = enc.get("level_counters")
|
||||
if level_ref is Dictionary:
|
||||
counters_ctx.level_bucket = level_ref # 挂关卡桶引用(LevelRunner 每关 new 下传)
|
||||
_damage.fx_reveal_request = _fx_enqueue_reveal
|
||||
_damage.fx_ac_request = _apply_ac_bonus
|
||||
_fx_host.mount_buckets(_ledger.adventure_counters, enc.get("level_counters")) # 挂冒险桶引用(跨战持久=同一字典引用,非副本搬运);关卡桶 LevelRunner 每关 new 下传、非 Dictionary 即不挂
|
||||
_reward_grant.bind_battle(_ledger)
|
||||
_damage.fx_reveal_request = _fx_host.enqueue_reveal
|
||||
_damage.fx_ac_request = _fx_host.apply_ac_bonus
|
||||
var feat_ids: Array[Variant] = enc.get("feats", []) as Array
|
||||
# 流派档位质变不设(奇物.md v1「不设流派标签、
|
||||
# 不设档位机制」):rider_tier 两注入恒零,散件单体效果=唯一形态
|
||||
@@ -405,7 +332,7 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
# 效果引擎订阅重建(每战从持有集/装备集重建,中途获得下一战生效;
|
||||
# 树规则合成订阅同面装载。装载序=feats 表序→treasure 表序→树规则,写死保种子复放。
|
||||
# 计数器四桶经 counters_ctx 引用宿主容器,无回灌时点(旧战首回灌时点删除))
|
||||
_fx.rebuild(feat_ids, attunement, tree_rules)
|
||||
_fx_host.rebuild_subscriptions(feat_ids, attunement, tree_rules)
|
||||
# 秘宝常时参数注入(装配集 kind 分发:远程镐挖距放宽/信标网络封印可达豁免;
|
||||
# 缺省 0/false=零装配零扰动。注入在首板 install 之后〔板在早段〕——板生成不读两参数,
|
||||
# 参数只影响挖掘/插棋动作判据,时序安全)
|
||||
@@ -420,7 +347,7 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
# 只报数量不标位置=信息红线,不碰分布=棋盘事实不可变契约 3。命中=读数口落值,呈现归
|
||||
# 呈现桥消费(无格来源不进结算明细行——行来源纪律);板已就绪〔早段 install〕、订阅行已在 rebuild 装载)
|
||||
nest_preview_count = -1
|
||||
var bi_hits := _fx_emit("board_installed", {})
|
||||
var bi_hits := _fx_host.fx_emit("board_installed", {})
|
||||
for h in bi_hits:
|
||||
if String(h.get("kind", "")) == "mine_count_preview":
|
||||
nest_preview_count = minesweeper.mine_total()
|
||||
@@ -440,7 +367,7 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
# mode:queue 当轮入队、notify_round_start 即兑现(先手压制=首轮 AP+1 语义锚);
|
||||
# 计数器 round_start 重置点同点生效)
|
||||
round_index = 1
|
||||
_emit_round_start()
|
||||
_fx_host.emit_round_start()
|
||||
if not action_economy.notify_round_start():
|
||||
_fatal("行动经济 首轮 notify_round_start 被拒")
|
||||
return false
|
||||
@@ -587,9 +514,9 @@ func _on_cell_revealed(evt: Dictionary) -> void:
|
||||
# 轮 facts 缓冲不设相位守卫——轮末最后一挖的揭示事件在 request_dig 翻相之后才到(事件序:
|
||||
# cell_revealed 批 → seal_judged 批 → round_end),phase==Active 守卫会丢最后一挖的事实;
|
||||
# initial 通道事件(install 初始展开——封锁格)resource=false,进入授予段被资格检查自然跳过。
|
||||
# fx_reveal 通道事件旁路缓冲(专长揭示的奖励由 _flush_pending_reveals 兑付点
|
||||
# fx_reveal 通道事件旁路缓冲(专长揭示的奖励由 fx_host flush_pending_reveals 兑付点
|
||||
# 直接入账,此处缓冲会导致二次兑付)。
|
||||
if battle_result == "" and not _fx_revealing:
|
||||
if battle_result == "" and not _fx_host.reveal_busy():
|
||||
# 积点自动兑付:
|
||||
# 满 charge_n 直接当轮 grant_ap,无玩家发起入口、无积点账中转〔账即时清空,battle 域重置
|
||||
# 自然无感〕。落位=揭示缓冲点——事件序 cell_revealed 批先于本格扣费,AP 归零恰逢积点满时
|
||||
@@ -695,267 +622,12 @@ func _hits_of(hits: Array, kind: String) -> Array:
|
||||
out.append(h)
|
||||
return out
|
||||
|
||||
# 「动作 × 格型」矩阵的执行面:snap = 缓冲期冻结身份快照(cell_type = 该格装板期摇定的类型身份),
|
||||
# polarity 由动作决定(开安全格/封印命中 = positive,开危险格/标错封印 = negative),
|
||||
# 兑付行 = 奖励字典(类型, 极性)行。返回 true = percent 行实际生效(flat 行恒 false——兑付结果由行协议承载)。
|
||||
# ---- 秘宝(use_treasure 四步受理编排,调用方=同调主动件;能力件注入归
|
||||
# LevelRunner.set_attune_charges 骑库存面)----
|
||||
|
||||
|
||||
# use_treasure 四步受理(registry use_orchestration 规范序——唯一调用权归本类;序即原子:
|
||||
# ① 相位 gate(Active——行动经济 phase getter)→ ② validate_use(目标校验读 排雷玩法 只读面,零消耗)→
|
||||
# ③ 逐 kind 应用(顺序钉死:空间先、ap_grant 后——effect_kinds 列表序非运行时语义)→ ④ consume。
|
||||
# 拒绝全部发生在 ①②(后两步合法输入下无失败路径);使用零 AP 成本(不经 try_spend)。
|
||||
# 结算明细无道具行(GDD——效果即时可见:overlay 呈现+AP 条刷新;AP 过账本 grant_ap 走 _reject 面)。
|
||||
func use_treasure(handle: int, target: Vector2i) -> Dictionary:
|
||||
if treasure_inventory == null:
|
||||
return {"ok": false, "reason": "库存未接线"}
|
||||
if action_economy.phase != action_economy.PHASE_ACTIVE:
|
||||
return {"ok": false, "reason": "非挖掘期(使用窗口=Active)"}
|
||||
var board: MinesweeperRules = minesweeper # 直引公有成员(无包装层)
|
||||
# 板维度读数(Rules 无 width 代理,读 BoardData 本体)
|
||||
var board_b: BoardData = board.current_board()
|
||||
var v: Dictionary[Variant, Variant] = treasure_inventory.validate_use(handle, target,
|
||||
Vector2(board_b.width, board_b.height),
|
||||
func(c: Vector2i): return not board.is_revealed(c))
|
||||
if not bool(v.ok):
|
||||
return v # ② 拒绝零消耗
|
||||
# treasure_used 发射+施加(使用主动秘宝受理面:counter_spend 订阅=受理闸
|
||||
# (掘进蓄能积点账本),消耗成功才受理;积点不足拒绝零消耗。零 item_id 特判)
|
||||
var tu_hits := _fx_emit("treasure_used", {"item_id": String(v.item_id), "target": target})
|
||||
for h in tu_hits:
|
||||
if String(h.get("kind", "")) == "counter_spend":
|
||||
if not EffectEngine.counter_spend(counters_ctx, String(h.get("counter_id", "")), int(h.get("value", 1))):
|
||||
var vrow: Dictionary[Variant, Variant] = db.treasure_item_row(String(v.item_id))
|
||||
var cn_info: int = int(vrow.get("charge_n", 0)) # 读受理行自身
|
||||
return {"ok": false, "reason": "%s:安全格积点未达(%d/%d)" % [String(vrow.get("display_name", "")), _attune_safe_seen % cn_info if cn_info > 0 else 0, cn_info]}
|
||||
var kinds: Array[Variant] = v.kinds
|
||||
if kinds.has("mine_reveal") or kinds.has("reveal_tiles"):
|
||||
# 真视写入(reveal_tiles 这类真视 kind 与 mine_reveal 同叠加层通道:
|
||||
# 危险格显示=信息呈现零回写,棋盘事实不可变契约 3)
|
||||
treasure_inventory.apply_overlay("mine_reveal", v.resolved)
|
||||
if kinds.has("reward_preview"):
|
||||
treasure_inventory.apply_overlay("reward_preview", v.resolved)
|
||||
if kinds.has("ap_grant"):
|
||||
if not action_economy.grant_ap(int(v.magnitude)):
|
||||
return {"ok": false, "reason": "行动点加量被拒(相位防御)"}
|
||||
if kinds.has("safe_dig"):
|
||||
# 天眼安全格直开(C5 全额兑付:fx_reveal 通道复用=免 AP 零相位、
|
||||
# 连锁与格面资源与普通挖格同链;危险格仅真视显示不移除=契约 3;有封印格跳过——封印=玩家承诺不代开)
|
||||
var opened_any := false
|
||||
for c_v in v.resolved:
|
||||
var c := Vector2i(c_v)
|
||||
if board.has_seal(c):
|
||||
continue
|
||||
if board.fx_reveal(c):
|
||||
opened_any = true
|
||||
if not opened_any:
|
||||
return {"ok": false, "reason": "天眼:窗口内安全格均已开(无可开格)"}
|
||||
if kinds.has("cell_removal"):
|
||||
# 爆破工兵包兑付(中立移除危险格:未揭示/无封印/是雷过滤归排雷玩法;
|
||||
# 邻接数字冻结零重算、清图分母零涉(危险格不入分母);空集拒绝零消耗——
|
||||
# consume 之前 return(拒绝面注记扩「③内零消耗分支」),拒绝 reason 可呈现)
|
||||
var blasted: Array[Variant] = board.remove_mines_in(v.resolved)
|
||||
if blasted.is_empty():
|
||||
return {"ok": false, "reason": "爆破工兵包:3×3 区域内无可移除危险格(须未揭示且未封印)"}
|
||||
var rec: Dictionary[Variant, Variant] = treasure_inventory.consume(handle)
|
||||
# 同调主动件充能扣减(共享账本直改:adventure/level/settlement 同一字典引用)
|
||||
if not rec.is_empty() and String(rec.get("source_class", "")) == "attunement":
|
||||
_ledger.attune_charge_consume(String(rec.get("item_id", "")))
|
||||
# 真视类工具使用计数(信标网络解锁行为条件:受理成功恰计一次;
|
||||
# 共享 dict 引用下传〔attune_charges 同款〕,冒险收束聚合入档 save.add_vision_used)
|
||||
if kinds.has("reveal_tiles") or kinds.has("safe_dig") or kinds.has("mine_reveal"):
|
||||
if not _ledger.vision_counter.is_empty():
|
||||
_ledger.vision_count_add(1) # 受理成功恰计一次(缺省空账=调试不计)
|
||||
return {"ok": true, "item_id": v.item_id, "resolved": v.resolved, "record": rec}
|
||||
|
||||
|
||||
# aux 解锁门控转发(LevelRunner 注入;E1/E3 封装:外部经公有面,禁直读 _reward_economy)
|
||||
func set_treasure_unlocked(ids: Dictionary) -> void:
|
||||
_reward_economy.set_treasure_unlocked(ids)
|
||||
|
||||
|
||||
func _grant_routed(snap: Dictionary, polarity: String, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "", halve_pct: int = 0) -> bool:
|
||||
# treasure_item 型零投放(无 aux 行入池、CURIO_KINDS 词表无此型),本口不处理;
|
||||
# 剩余类型走极性字典路由。
|
||||
var row: Dictionary[Variant, Variant] = _reward_economy.polarity_row(String(snap.get("cell_type", "")), polarity)
|
||||
if row.is_empty():
|
||||
_fatal("极性字典缺行: %s %s" % [String(snap.get("cell_type", "")), polarity])
|
||||
return false
|
||||
if polarity == "positive":
|
||||
row["power"] = int(snap.get("power", int(row.power))) # 区间行快照 power(身份批 roll 值)覆盖表值——授予期零随机口径(E14)
|
||||
# 标中兑付溢价(条款3 条件二封印溢价形态 a):
|
||||
# δ 读 settlement.seal_hit_premium(percent 域 125=×1.25 provisional);100=无溢价直通零扰动。
|
||||
# 兑付公式一处=本块:只乘封印命中正向(flat power 乘后入单据/治疗/排队;percent 幅度经
|
||||
# premium_pct 贯穿 _apply_percent_row 应用与呈现同乘)——标空/挖格/清图不乘,雷安同池冻结不动。
|
||||
# 取整=四舍五入(与伤害管线 round_direction=floor 分域——registry clause3_inequality 登记;
|
||||
# 1 点量纲行 ac_up/action 取整后不变=整数粒度地板)。
|
||||
if source_class == "seal_hit" and int(db.settlement.seal_hit_premium) != 100:
|
||||
var prem_pct: int = int(db.settlement.seal_hit_premium)
|
||||
row = row.duplicate()
|
||||
row["power"] = int(round(int(row.power) * prem_pct / 100.0))
|
||||
row["premium_pct"] = prem_pct
|
||||
# 惊巢反制(减半系数=mine_hit
|
||||
# damage_mult 订阅命中(行 value 50=减 50%,取整 ceil);反弹单=mine_hit
|
||||
# open_ticket channel reflect 订阅施加(编排器 mine_hit 施加段);duplicate 隔离——减半不回写极性字典)
|
||||
if halve_pct > 0 and polarity == "negative" and source_class == "mine_hit":
|
||||
row = row.duplicate()
|
||||
var half: int = int(ceil(float(int(row.power)) * float(100 - halve_pct) / 100.0))
|
||||
_emit_row("effect", source_class, cell, ri, "惊巢反制——危险格挖掘惩罚减半(%d→%d)" % [int(row.power), half], 0, {"effect_id": "nest_counter"}, highlight)
|
||||
row.power = half
|
||||
return _dispatch_row(row, source_class, cell, ri, highlight, label_suffix)
|
||||
|
||||
|
||||
# 兑付形态分派:effect_id 非空 = percent 行(进增益状态容器);空 = flat 行(当场兑付)
|
||||
func _dispatch_row(row: Dictionary, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> bool:
|
||||
var eff := String(row.get("effect_id", ""))
|
||||
if eff != "":
|
||||
return _apply_percent_row(row, source_class, cell, ri, highlight, label_suffix)
|
||||
_apply_flat_row(row, source_class, cell, ri, highlight, label_suffix)
|
||||
return false
|
||||
|
||||
|
||||
# percent 行兑付:GP 型行走战斗内缓冲记账;其余 apply_effect 入容器;
|
||||
# 满层拒收 = "奖励未生效"行(零静默)
|
||||
func _apply_percent_row(row: Dictionary, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> bool:
|
||||
var eff := String(row.effect_id)
|
||||
if eff == "gp_gain" or eff == "gp_loss":
|
||||
return _apply_gp_row(row, source_class, cell, ri, highlight, label_suffix)
|
||||
var srow: Dictionary[Variant, Variant] = db.status_effect_row(eff)
|
||||
# 封印溢价贯穿(迭代14② T4——仅封印命中正向行携带 premium_pct;其余授予路径缺省 100 零扰动):
|
||||
# 应用幅度与呈现行值同乘,容器经 magnitude_override 拿同一快照(抗表重载口径保持)。
|
||||
var prem_pct: int = int(row.get("premium_pct", 100))
|
||||
var eff_mag: int = int(srow.magnitude)
|
||||
var src := {"source_class": source_class, "origin_cell": cell, "round_index": ri}
|
||||
if prem_pct != 100:
|
||||
eff_mag = int(round(eff_mag * prem_pct / 100.0))
|
||||
src["magnitude_override"] = eff_mag
|
||||
var ok: bool = _status_effects.apply_effect(eff, src)
|
||||
if ok:
|
||||
_emit_row("effect", source_class, cell, ri, String(srow.display_name) + label_suffix, eff_mag, {"effect_id": eff, "pool_value": _pool_effective(eff)}, highlight)
|
||||
return true
|
||||
_emit_row("reward_ineffective", source_class, cell, ri, "奖励未生效(满层)%s" % label_suffix, 0, {"batch_key": eff}, highlight)
|
||||
return false
|
||||
|
||||
|
||||
# GP 型行兑付:战斗内缓冲 escrow 记账——不入钱包不入增益容器。
|
||||
# 正向(gp_gain)= 挖开GP安全格/封印命中;负向(gp_loss)= 挖危险格/标错封印。兑付行经既有 effect 行
|
||||
# 通道外发(escrow 随行——归因可读),战斗结束随 gp_dropped 一次上抛后即弃。
|
||||
func _apply_gp_row(row: Dictionary, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> bool:
|
||||
var eff := String(row.effect_id)
|
||||
var delta: int = int(row.power) if eff == "gp_gain" else -int(row.power)
|
||||
escrow += delta
|
||||
_emit_row("effect", source_class, cell, ri, "%s %+d%s" % [String(row.display_name), delta, label_suffix], delta, {"effect_id": eff, "escrow": escrow}, highlight)
|
||||
return true
|
||||
|
||||
|
||||
# flat 行兑付(负向恒即时,无库存语义):伤害型分支走开单据(
|
||||
# 管线/下限/目标解析归 伤害控制器,本函数只保留非伤害分支与单据装配):
|
||||
# action = 行动点排队(非伤害);blast 正向 = 群伤单据(无类型恒定表值打全体存活敌;负向 = 警觉 percent 行走 _apply_percent_row);
|
||||
# heal 正向 = 主角回复 clamp(满血 → 奖励未生效行);heal 负向 = 主角直伤单据(不经增益状态);
|
||||
# pierce/magic 正向 = 单目标克制直伤单据(attack_type 查克制矩阵,必中全额不掷点);负向 = 主角直伤单据。
|
||||
# 返回 "redeemed" = 实际有效兑付 / "zero" = 零兑付(行已自记)——读数 = 控制器结账 outcome(A 模式提交即结)。
|
||||
# sync = 同步开单(清图批——流程依赖完美第二份/高光份数读数,无视结账政策当场结账)。
|
||||
func _apply_flat_row(row: Dictionary, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "", sync := false) -> String:
|
||||
var ctype := String(row.cell_type)
|
||||
var power := int(row.power)
|
||||
var display := String(row.display_name) + label_suffix
|
||||
if ctype == "action":
|
||||
var delta: int = power if String(row.get("polarity", "positive")) == "positive" else -power
|
||||
action_economy.queue_ap_delta(delta)
|
||||
_emit_row("effect", source_class, cell, ri, "%s(下轮行动点 %+d)" % [display, delta], delta, {"effect_id": "ap_delta", "pool_value": action_economy.pending_ap_delta()}, highlight)
|
||||
return "redeemed"
|
||||
if ctype == "blast":
|
||||
var t := _damage.open_ticket(DamageController.KIND_BOARD_BLAST, source_class, display, "board", power, \
|
||||
DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, highlight, {"batch_key": String(row.reward_id)})
|
||||
return _outcome_of(_submit(t, sync))
|
||||
if ctype == "pierce" or ctype == "magic":
|
||||
# 正向 = 单目标克制直伤(必中全额不掷点——棋盘来源伤害既有口径);
|
||||
# 负向 = 主角直伤(复用反噬通道)。
|
||||
if String(row.get("polarity", "positive")) == "negative":
|
||||
return _submit_flat_hero(source_class, display, power, cell, ri, highlight, sync)
|
||||
var atk_type: String = String(row.get("attack_type", ctype))
|
||||
var t2 := _damage.open_ticket(DamageController.KIND_BOARD_TYPED, source_class, display, "board", power, \
|
||||
DamageController.TARGET_FRONT_ALIVE, ri, cell, atk_type, 0, highlight, {"batch_key": String(row.reward_id)})
|
||||
return _outcome_of(_submit(t2, sync))
|
||||
if ctype == "heal":
|
||||
if String(row.get("polarity", "positive")) == "negative":
|
||||
return _submit_flat_hero(source_class, display, power, cell, ri, highlight, sync)
|
||||
if int(_enemy_system.hero_unit().get("hp", 0)) <= 0:
|
||||
# 主角已倒下(同轮更早的负向直伤致死)——治疗不再合法作用,按"奖励未生效"留痕
|
||||
_emit_row("reward_ineffective", source_class, cell, ri, "%s 未生效(主角已倒下)" % display, 0, {"batch_key": String(row.reward_id)}, highlight)
|
||||
return "zero"
|
||||
if int(_enemy_system.hero_unit().hp) >= int(_enemy_system.hero_unit().max_hp):
|
||||
_emit_row("reward_ineffective", source_class, cell, ri, "%s 未生效(满血)" % display, 0, {"batch_key": String(row.reward_id)}, highlight)
|
||||
return "zero"
|
||||
var healed: int = _enemy_system.heal(_enemy_system.hero_unit(), power) # clamp 归 战斗结算
|
||||
_emit_row("heal", source_class, cell, ri, "%s 回复" % display, healed, {"target": "hero"}, highlight)
|
||||
return "redeemed"
|
||||
_fatal("flat 行未知类型: %s" % ctype)
|
||||
return "zero"
|
||||
|
||||
|
||||
# 开单分流:sync(清图批)走控制器同步开单——先按序结清段内排队单据、再当场兑现本单;
|
||||
# 常规开单按结账时机政策(A 即结 / B 入队)。
|
||||
func _submit(t: Dictionary, sync: bool) -> int:
|
||||
if _defer_board_damage:
|
||||
_deferred_board.append(t) # 案C:授予段伤害单据暂存——战斗段怪物还手后按事件序补提交(sync 同闸,清图批一致延后)
|
||||
return -1
|
||||
return _damage.flush_submit(t) if sync else _damage.submit(t)
|
||||
|
||||
|
||||
# 案C 结账读数:暂存单(_submit 返回 -1)以「将兑付」回报——授予段兑付决策(清图完美第二份等)只能
|
||||
# 依据开单时点事实(该时点敌人存活有保证);战斗段末实际结账若目标已灭,控制器自记未生效行。
|
||||
func _outcome_of(ticket_id: int) -> String:
|
||||
return "redeemed" if ticket_id < 0 else _damage.outcome(ticket_id)
|
||||
|
||||
|
||||
# 主角直伤单据(反噬/治疗包负向——无类型必中,下限保底在控制器管线内)
|
||||
func _submit_flat_hero(source_class: String, display: String, power: int, cell: Vector2i, ri: int, highlight: String, sync := false) -> String:
|
||||
var t := _damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, source_class, display, "board", power, \
|
||||
DamageController.TARGET_HERO, ri, cell, "", 0, highlight, {})
|
||||
return _outcome_of(_submit(t, sync))
|
||||
|
||||
|
||||
# 封印判定批:标中 = 该危险格资源的正向兑付(每雷实例一次;高光 = 强制检查点);
|
||||
# 标空 = "封印未中"归因行 + 该安全格资源的负向兑付(同键去重——每格实例一次)。
|
||||
# 清图批:清图奖励对象恰好发一次;完美封印追加第二份同身份兑付(grant_clear 本身恰一次)。
|
||||
# 高光行 value = 实际有效兑付份数(禁记名义值;首份零兑付 → 降级不发)。
|
||||
func _grant_clear_reward(ri: int) -> void:
|
||||
if _board_from_midround_refresh:
|
||||
return # 轮内刷新换来的新图不发清图奖励(行动经济/战斗结算 明文——刷新图不是清图挣来的)
|
||||
var snap = _reward_economy.grant_clear()
|
||||
if not (snap is Dictionary):
|
||||
return
|
||||
var perfect: bool = perfect_sweep_earned()
|
||||
var first_ok := _grant_clear_identity(snap, ri, "(清图奖励)")
|
||||
var second_ok := false
|
||||
if perfect and first_ok:
|
||||
second_ok = _grant_clear_identity(snap, ri, "(完美第二份)")
|
||||
if perfect and first_ok:
|
||||
_emit_row("highlight", "clear_reward", Vector2i(-1, -1), ri, "完美封印!清图奖励 ×2", (2 if second_ok else 1), {}, "perfect_sweep")
|
||||
|
||||
|
||||
# 清图奖励 = 整图独立对象(不属于任何一格):兑付形态按其 cell_type 的正向字典形态走,
|
||||
# 行值全部取快照(身份批 roll 值)。返回实际有效兑付。
|
||||
# 同步开单(sync)——完美第二份与高光份数的流程依赖需要同步读数(开单权归流程);
|
||||
# 同步路径内先按序结清段内排队单据(B 模式保序),随后本批无视结账政策当场兑现。
|
||||
func _grant_clear_identity(snap: Dictionary, ri: int, suffix: String) -> bool:
|
||||
var row := {
|
||||
"reward_id": String(snap.get("reward_id", "")),
|
||||
"cell_type": String(snap.get("cell_type", "")),
|
||||
"polarity": "positive",
|
||||
"display_name": String(snap.get("display_name", "")),
|
||||
"power": int(snap.get("power", 0)),
|
||||
"effect_id": String(snap.get("effect_id", "")),
|
||||
"stacks": int(snap.get("stacks", 1)),
|
||||
}
|
||||
var eff := String(row.effect_id)
|
||||
if eff != "":
|
||||
return _apply_percent_row(row, "clear_reward", Vector2i(-1, -1), ri, "clear_reward_h2", suffix)
|
||||
return _apply_flat_row(row, "clear_reward", Vector2i(-1, -1), ri, "clear_reward_h2", suffix, true) == "redeemed"
|
||||
|
||||
|
||||
# ---- 开轮摇号(共识点 B:技能选择前置到轮次开始,数值计算留结算段)----
|
||||
# 每实例:保底判定(连续执行普攻 ≥ M → 必出特殊技、特殊技内按权重、不受冷却限制)→ 排除冷却中技能
|
||||
# (中签率 = 权重 ÷ 可摇权重合计——份额归一)→ 按行序逐敌从统一流顺序取号摇定本拍动作。
|
||||
@@ -983,7 +655,7 @@ func _roll_round_actions() -> void:
|
||||
break
|
||||
# target_attack_bonus 施加面(处刑台:敌血量阈值沿挂单位字典标记,
|
||||
# 主角攻击面 _resolve_hero_attack 通用消费——fx_atk_bonus 键加算进 base_value)
|
||||
var eht_hits := _fx_emit("enemy_hp_threshold", {"unit": String(u.enemy_id), "pct": pct_v,
|
||||
var eht_hits := _fx_host.fx_emit("enemy_hp_threshold", {"unit": String(u.enemy_id), "pct": pct_v,
|
||||
"is_boss": String(encounter.get("node_type", "")) == "boss"})
|
||||
for h in eht_hits:
|
||||
if String(h.get("kind", "")) == "target_attack_bonus":
|
||||
@@ -1088,7 +760,7 @@ func _exit_distribution() -> void:
|
||||
action_economy.notify_settlement_done(mode)
|
||||
# round_start 发射先于行动经济轮切换(queue 型 grant_ap 当轮入队当轮兑现——与首轮同口径)
|
||||
round_index += 1
|
||||
_emit_round_start()
|
||||
_fx_host.emit_round_start()
|
||||
action_economy.notify_round_start()
|
||||
_reset_round_facts()
|
||||
_refresh_used_this_round = 0
|
||||
@@ -1123,16 +795,16 @@ func _battle_over(result: String) -> void:
|
||||
# battle_won 发射+施加(战地包扎 heal 订阅面;败北不发射。heal=英雄单位回血
|
||||
# 〔封顶单位上限〕,战后生命经 hero_hp_left 回写冒险账本——时点在捕获前=回血计入带入下一层)
|
||||
if result == "victory":
|
||||
for h in _fx_emit("battle_won", {"round": round_index}):
|
||||
for h in _fx_host.fx_emit("battle_won", {"round": round_index}):
|
||||
if String(h.get("kind", "")) == "heal":
|
||||
var hu_w: Dictionary[Variant, Variant] = _enemy_system.hero_unit()
|
||||
hu_w["hp"] = mini(int(hu_w.get("hp", 0)) + int(h.get("value", 0)), maxi(1, int(hu_w.get("max_hp", 1))))
|
||||
# bonus_delta 战末汇入(血税双向账:本战累积并入冒险加算区共享引用后清零)+
|
||||
# adventure 域计数器导出回写(T6——共享引用覆写,attune_charges 先例)
|
||||
var drained := _fx_host.bonus_delta_drain()
|
||||
if not _ledger.bonus.is_empty():
|
||||
for axis in _fx_bonus_delta.keys():
|
||||
_ledger.bonus_add_axis(axis, int(_fx_bonus_delta[axis]))
|
||||
_fx_bonus_delta = {"atk": 0, "max_hp": 0, "armor_class": 0, "ap": 0}
|
||||
for axis in drained.keys():
|
||||
_ledger.bonus_add_axis(axis, int(drained[axis]))
|
||||
# 搬运链删除:adventure 桶经 ctx 引用直写 RunLedger,无战末导出时点(旧导出/汇入搬运链删除)
|
||||
# 映射随战斗结束退役(地图战斗资源 义务④——下一场战斗 = 新板新 scope 批,roll_board 相位守卫的前提)
|
||||
var hero_hp_left := int(_enemy_system.hero_unit().get("hp", 0)) # despawn 前捕获(信号载荷供呈现层"HP 带入下一层")
|
||||
@@ -1166,7 +838,7 @@ func _roll_gp_drop() -> int:
|
||||
# 显示源,落在入账前=显示即实得;乘数 provisional 150%)
|
||||
if (encounter.get("tree_rules", []) as Array).has("economy_hoard"):
|
||||
drop = int(round(float(drop) * 1.5))
|
||||
return drop + escrow
|
||||
return drop + _reward_grant.escrow
|
||||
|
||||
|
||||
# ---- 呈现状态面(E1 封装收口):呈现层 / headless 自动玩家的唯一读取出口——
|
||||
|
||||
@@ -347,7 +347,7 @@ func _board_face_dig(c: Vector2i) -> String:
|
||||
# fx_reveal 效果通道揭示(订阅类「揭示 1 格」效果的棋盘面执行口:
|
||||
# 不经行动经济〔零 AP 消耗零相位要求〕、不要求邻接〔任意揭示〕、只受理安全格〔信息红线=只揭真格,
|
||||
# 危险格/已开/出界/移除格=拒绝零副作用〕。连锁与揭示事件与 _do_dig 同构(chain 通道复用),资源兑付归
|
||||
# 战斗结算 _flush_pending_reveals 直接入账——channel=fx_reveal 供归因)
|
||||
# battle_fx_host flush_pending_reveals 直接入账——channel=fx_reveal 供归因)
|
||||
func fx_reveal(c: Vector2i) -> bool:
|
||||
if _board == null or not _board.in_bounds(c) or is_revealed(c) or _board.is_mine(c) or _removed.has(c):
|
||||
return false
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
## 奖励授予件(自 battle_orchestrator 收编 grant 族):极性字典路由兑付(grant_routed——含封印溢价
|
||||
## 与惊巢反制减半)、三种行应用(flat 四分支/percent/gp)、伤害单据提交面(submit——案C 暂存闸承载)、
|
||||
## 清图奖励与身份授予(完美封印第二份)、战斗内 GP 缓冲 escrow、秘宝使用四步编排(use_treasure——
|
||||
## 相位闸→校验→受理闸→逐 kind 应用→消耗)、揭示奖励兑付(reveal_redeem——fx_host flush 的兑付回调面)。
|
||||
## 生命周期=随编排器(编排器 _init 同建同灭,每战新建)。
|
||||
## 跨件单向接线(🔴 契约红线):持 BattleFxHost 引用(treasure_used 发射与 counters_ctx 消费;
|
||||
## minesweeper 同源经 fx_host 引用——两引用束同源零行为差);行发射经 emit_cb 回宿主(协议面单点
|
||||
## 留宿主);fatal 经 fatal_cb 回宿主 _fatal(battle_result 状态机唯一写点留宿主);attune 积点读数经
|
||||
## attune_safe_seen_cb、percent 行池值读经 pool_effective_cb——禁回调宿主私有族、禁转发壳。
|
||||
## 随机:零随机零 await——授予期零随机口径(roll 值授予前已定)。
|
||||
## att 归因束(全 grant 族共用,键集封闭):source_class:String/cell:Vector2i/ri:int/
|
||||
## highlight:String(缺省"")/label_suffix:String(缺省"")/halve_pct:int(缺省 0)。
|
||||
## 类变量封闭面(15≤15):bundle 11 项(db/reward_economy/damage/fx_host/action_economy/status_effects/
|
||||
## units/emit_cb/fatal_cb/attune_safe_seen_cb/pool_effective_cb,缺一键 assert)+escrow+defer
|
||||
## (键集封闭 on/queue——原 _defer_board_damage/_deferred_board 二合一)+treasure_inventory
|
||||
## (经宿主 setter 转发注入——库存注入时序在宿主构造之后)+battle_ledger(bind_battle 挂接——
|
||||
## use_treasure 充能扣减与真视计数消费)。禁实现期增删键。
|
||||
class_name RewardGrant
|
||||
extends RefCounted
|
||||
|
||||
var db: ConfigDb
|
||||
var reward_economy: RewardEconomy
|
||||
var damage: DamageController
|
||||
var fx_host: BattleFxHost
|
||||
var action_economy: ActionEconomy
|
||||
var status_effects: StatusContainer
|
||||
var units: UnitContainer
|
||||
var emit_cb: Callable # 宿主 _emit_row——行协议面单点(束形改形后随束形)
|
||||
var fatal_cb: Callable # 宿主 _fatal——battle_result 唯一写点留宿主
|
||||
var attune_safe_seen_cb: Callable # 宿主 attune 读投影——use_treasure 拒绝文案读数(_attune_safe_seen)
|
||||
var pool_effective_cb: Callable # 宿主 _pool_effective——percent 行池值读
|
||||
var escrow := 0 # 战斗内 GP 缓冲(战斗域中间态——reset_battle 清零;gp_gain/gp_loss 兑付累记;随 gp_dropped 上抛即弃,不入钱包)
|
||||
var defer := {"on": false, "queue": []} # 案C 暂存闸(授予段伤害单据只暂存不结账——战斗段怪物还手后按事件序补提交统一结账)
|
||||
var treasure_inventory: TreasureItems = null # 经宿主 setter 转发注入(库存注入时序在宿主构造之后)
|
||||
var battle_ledger: RunLedger = null # begin_battle 账本引用挂接(bind_battle——缺省新账面由宿主传入)
|
||||
|
||||
|
||||
func _init(bundle: Dictionary) -> void:
|
||||
for k in ["db", "reward_economy", "damage", "fx_host", "action_economy", "status_effects", "units",
|
||||
"emit_cb", "fatal_cb", "attune_safe_seen_cb", "pool_effective_cb"]:
|
||||
assert(bundle.has(k), "[奖励授予] 引用束缺键: %s" % k)
|
||||
db = bundle.db
|
||||
reward_economy = bundle.reward_economy
|
||||
damage = bundle.damage
|
||||
fx_host = bundle.fx_host
|
||||
action_economy = bundle.action_economy
|
||||
status_effects = bundle.status_effects
|
||||
units = bundle.units
|
||||
emit_cb = bundle.emit_cb
|
||||
fatal_cb = bundle.fatal_cb
|
||||
attune_safe_seen_cb = bundle.attune_safe_seen_cb
|
||||
pool_effective_cb = bundle.pool_effective_cb
|
||||
|
||||
|
||||
# begin_battle 账本引用挂接(编排器 begin_battle 调用——attune_charge_consume/vision_count_add 消费)
|
||||
func bind_battle(ledger_ref: RunLedger) -> void:
|
||||
battle_ledger = ledger_ref
|
||||
|
||||
|
||||
# begin_battle 运行态复位(escrow 清零+案C 闸复位——每战编排器新建本件天然全新,复位为防御性收口)
|
||||
func reset_battle() -> void:
|
||||
escrow = 0
|
||||
defer.on = false
|
||||
defer.queue = []
|
||||
|
||||
|
||||
# 揭示奖励兑付(BattleFxHost.flush_pending_reveals 的兑付回调面——授予面收编:
|
||||
# snapshot 查询与极性路由同在本件,fx_host 对授予面零强引用)
|
||||
func reveal_redeem(cell: Vector2i, ri: int) -> void:
|
||||
var snap = reward_economy.query_full_snapshot(cell)
|
||||
if snap is Dictionary:
|
||||
grant_routed(snap, "positive", {"source_class": "fx_reveal", "cell": cell, "ri": ri, "highlight": "fx_reveal"})
|
||||
|
||||
|
||||
# use_treasure 四步受理(registry use_orchestration 规范序——序即原子:
|
||||
# ① 相位 gate(Active——行动经济 phase getter)→ ② validate_use(目标校验读排雷玩法只读面,零消耗)→
|
||||
# ②半 treasure_used 受理闸(counter_spend 订阅——消耗成功才受理)→ ③ 逐 kind 应用(顺序钉死:空间先、
|
||||
# ap_grant 后——effect_kinds 列表序非运行时语义)→ ④ consume。
|
||||
# 拒绝全部发生在 ①②③(④合法输入下无失败路径);使用零 AP 成本(不经 try_spend)。
|
||||
# 结算明细无道具行(GDD——效果即时可见:overlay 呈现+AP 条刷新;AP 过账本 grant_ap 走 _reject 面)。
|
||||
func use_treasure(handle: int, target: Vector2i) -> Dictionary:
|
||||
if treasure_inventory == null:
|
||||
return {"ok": false, "reason": "库存未接线"}
|
||||
if action_economy.phase != action_economy.PHASE_ACTIVE:
|
||||
return {"ok": false, "reason": "非挖掘期(使用窗口=Active)"}
|
||||
var board: MinesweeperRules = fx_host.minesweeper # 直引宿主件同源引用(无包装层)
|
||||
# 板维度读数(Rules 无 width 代理,读 BoardData 本体)
|
||||
var board_b: BoardData = board.current_board()
|
||||
var v: Dictionary[Variant, Variant] = treasure_inventory.validate_use(handle, target,
|
||||
Vector2(board_b.width, board_b.height),
|
||||
func(c: Vector2i): return not board.is_revealed(c))
|
||||
if not bool(v.ok):
|
||||
return v # ② 拒绝零消耗
|
||||
var gate_reject: Dictionary = _charge_gate(v, target)
|
||||
if not gate_reject.is_empty():
|
||||
return gate_reject # ②半 受理闸拒绝零消耗
|
||||
var reject: Dictionary = _apply_use_kinds(v, target)
|
||||
if not reject.is_empty():
|
||||
return reject # ③ 拒绝零消耗(④ consume 之前 return——reason 可呈现)
|
||||
var rec: Dictionary[Variant, Variant] = treasure_inventory.consume(handle)
|
||||
# 同调主动件充能扣减(共享账本直改:adventure/level/settlement 同一字典引用)
|
||||
if not rec.is_empty() and String(rec.get("source_class", "")) == "attunement":
|
||||
battle_ledger.attune_charge_consume(String(rec.get("item_id", "")))
|
||||
# 真视类工具使用计数(信标网络解锁行为条件:受理成功恰计一次;
|
||||
# 共享 dict 引用下传〔attune_charges 同款〕,冒险收束聚合入档 save.add_vision_used)
|
||||
var kinds: Array[Variant] = v.kinds
|
||||
if kinds.has("reveal_tiles") or kinds.has("safe_dig") or kinds.has("mine_reveal"):
|
||||
if not battle_ledger.vision_counter.is_empty():
|
||||
battle_ledger.vision_count_add(1) # 受理成功恰计一次(缺省空账=调试不计)
|
||||
return {"ok": true, "item_id": v.item_id, "resolved": v.resolved, "record": rec}
|
||||
|
||||
|
||||
# 受理闸(treasure_used 发射+施加:counter_spend 订阅=掘进蓄能积点账本,积点不足拒绝零消耗。
|
||||
# 零 item_id 特判;拒绝文案读装配集积点进度——宿主 attune 读投影)
|
||||
func _charge_gate(v: Dictionary, target: Vector2i) -> Dictionary:
|
||||
var tu_hits := fx_host.fx_emit("treasure_used", {"item_id": String(v.item_id), "target": target})
|
||||
for h in tu_hits:
|
||||
if String(h.get("kind", "")) == "counter_spend":
|
||||
if not EffectEngine.counter_spend(fx_host.counters_ctx, String(h.get("counter_id", "")), int(h.get("value", 1))):
|
||||
var vrow: Dictionary[Variant, Variant] = db.treasure_item_row(String(v.item_id))
|
||||
var cn_info: int = int(vrow.get("charge_n", 0)) # 读受理行自身
|
||||
var seen := int(attune_safe_seen_cb.call())
|
||||
return {"ok": false, "reason": "%s:安全格积点未达(%d/%d)" % [String(vrow.get("display_name", "")), seen % cn_info if cn_info > 0 else 0, cn_info]}
|
||||
return {}
|
||||
|
||||
|
||||
# ③ 逐 kind 应用(顺序钉死:空间先、ap_grant 后;拒收字典/空字典=受理——④ consume 前零消耗分支)
|
||||
func _apply_use_kinds(v: Dictionary, _target: Vector2i) -> Dictionary:
|
||||
var kinds: Array[Variant] = v.kinds
|
||||
if kinds.has("mine_reveal") or kinds.has("reveal_tiles"):
|
||||
# 真视写入(reveal_tiles 这类真视 kind 与 mine_reveal 同叠加层通道:
|
||||
# 危险格显示=信息呈现零回写,棋盘事实不可变契约)
|
||||
treasure_inventory.apply_overlay("mine_reveal", v.resolved)
|
||||
if kinds.has("reward_preview"):
|
||||
treasure_inventory.apply_overlay("reward_preview", v.resolved)
|
||||
if kinds.has("ap_grant"):
|
||||
if not action_economy.grant_ap(int(v.magnitude)):
|
||||
return {"ok": false, "reason": "行动点加量被拒(相位防御)"}
|
||||
if kinds.has("safe_dig"):
|
||||
var dig_reject := _safe_dig_open(v)
|
||||
if not dig_reject.is_empty():
|
||||
return dig_reject
|
||||
if kinds.has("cell_removal"):
|
||||
var blast_reject := _cell_removal_open(v)
|
||||
if not blast_reject.is_empty():
|
||||
return blast_reject
|
||||
return {} # 空字典=受理
|
||||
|
||||
|
||||
# 天眼安全格直开(C5 全额兑付:fx_reveal 通道复用=免 AP 零相位、
|
||||
# 连锁与格面资源与普通挖格同链;危险格仅真视显示不移除=棋盘事实契约;有封印格跳过——封印=玩家承诺不代开;
|
||||
# 拒收字典/空字典=有开格)
|
||||
func _safe_dig_open(v: Dictionary) -> Dictionary:
|
||||
var opened_any := false
|
||||
for c_v in v.resolved:
|
||||
var c := Vector2i(c_v)
|
||||
if fx_host.minesweeper.has_seal(c):
|
||||
continue
|
||||
if fx_host.minesweeper.fx_reveal(c):
|
||||
opened_any = true
|
||||
if not opened_any:
|
||||
return {"ok": false, "reason": "天眼:窗口内安全格均已开(无可开格)"}
|
||||
return {}
|
||||
|
||||
|
||||
# 爆破工兵包兑付(中立移除危险格:未揭示/无封印/是雷过滤归排雷玩法;
|
||||
# 邻接数字冻结零重算、清图分母零涉(危险格不入分母);空集拒绝零消耗——
|
||||
# consume 之前 return(拒绝面注记扩「③内零消耗分支」),拒绝 reason 可呈现)
|
||||
func _cell_removal_open(v: Dictionary) -> Dictionary:
|
||||
var blasted: Array[Variant] = fx_host.minesweeper.remove_mines_in(v.resolved)
|
||||
if blasted.is_empty():
|
||||
return {"ok": false, "reason": "爆破工兵包:3×3 区域内无可移除危险格(须未揭示且未封印)"}
|
||||
return {}
|
||||
|
||||
|
||||
# 极性路由兑付(「动作 × 格型」矩阵的执行面:snap = 缓冲期冻结身份快照〔cell_type = 该格装板期
|
||||
# 摇定的类型身份〕,polarity 由动作决定(开安全格/封印命中 = positive,开危险格/标错封印 = negative),
|
||||
# 兑付行 = 奖励字典(类型, 极性)行。返回 true = percent 行实际生效(flat 行恒 false——兑付结果由行协议承载)。
|
||||
# treasure_item 型零投放(无 aux 行入池、CURIO_KINDS 词表无此型),本口不处理;剩余类型走极性字典路由。
|
||||
func grant_routed(snap: Dictionary, polarity: String, att: Dictionary) -> bool:
|
||||
var source_class := String(att.get("source_class", ""))
|
||||
var cell: Vector2i = att.get("cell", Vector2i(-1, -1))
|
||||
var ri := int(att.get("ri", 0))
|
||||
var highlight := String(att.get("highlight", ""))
|
||||
var label_suffix := String(att.get("label_suffix", ""))
|
||||
var halve_pct := int(att.get("halve_pct", 0))
|
||||
var row: Dictionary[Variant, Variant] = reward_economy.polarity_row(String(snap.get("cell_type", "")), polarity)
|
||||
if row.is_empty():
|
||||
fatal_cb.call("极性字典缺行: %s %s" % [String(snap.get("cell_type", "")), polarity])
|
||||
return false
|
||||
if polarity == "positive":
|
||||
row["power"] = int(snap.get("power", int(row.power))) # 区间行快照 power(身份批 roll 值)覆盖表值——授予期零随机口径
|
||||
# 标中兑付溢价(条款3 条件二封印溢价形态 a):
|
||||
# δ 读 settlement.seal_hit_premium(percent 域 125=×1.25 provisional);100=无溢价直通零扰动。
|
||||
# 兑付公式一处=本块:只乘封印命中正向(flat power 乘后入单据/治疗/排队;percent 幅度经
|
||||
# premium_pct 贯穿 _apply_percent_row 应用与呈现同乘)——标空/挖格/清图不乘,雷安同池冻结不动。
|
||||
# 取整=四舍五入(与伤害管线 round_direction=floor 分域——registry clause3_inequality 登记;
|
||||
# 1 点量纲行 ac_up/action 取整后不变=整数粒度地板)。
|
||||
if source_class == "seal_hit" and int(db.settlement.seal_hit_premium) != 100:
|
||||
var prem_pct: int = int(db.settlement.seal_hit_premium)
|
||||
row = row.duplicate()
|
||||
row["power"] = int(round(int(row.power) * prem_pct / 100.0))
|
||||
row["premium_pct"] = prem_pct
|
||||
# 惊巢反制(减半系数=mine_hit damage_mult 订阅命中〔行 value 50=减 50%,取整 ceil〕;
|
||||
# duplicate 隔离——减半不回写极性字典)
|
||||
if halve_pct > 0 and polarity == "negative" and source_class == "mine_hit":
|
||||
row = row.duplicate()
|
||||
var half: int = int(ceil(float(int(row.power)) * float(100 - halve_pct) / 100.0))
|
||||
emit_cb.call("effect", source_class, cell, ri, "惊巢反制——危险格挖掘惩罚减半(%d→%d)" % [int(row.power), half], 0, {"effect_id": "nest_counter"}, highlight)
|
||||
row.power = half
|
||||
return _dispatch_row(row, att)
|
||||
|
||||
|
||||
# 兑付形态分派:effect_id 非空 = percent 行(进增益状态容器);空 = flat 行(当场兑付)
|
||||
func _dispatch_row(row: Dictionary, att: Dictionary) -> bool:
|
||||
var eff := String(row.get("effect_id", ""))
|
||||
if eff != "":
|
||||
return _apply_percent_row(row, att)
|
||||
_apply_flat_row(row, att, false)
|
||||
return false
|
||||
|
||||
|
||||
# percent 行兑付:GP 型行走战斗内缓冲记账;其余 apply_effect 入容器;
|
||||
# 满层拒收 = "奖励未生效"行(零静默)
|
||||
func _apply_percent_row(row: Dictionary, att: Dictionary) -> bool:
|
||||
var eff := String(row.effect_id)
|
||||
if eff == "gp_gain" or eff == "gp_loss":
|
||||
return _apply_gp_row(row, att)
|
||||
var srow: Dictionary[Variant, Variant] = db.status_effect_row(eff)
|
||||
# 封印溢价贯穿(仅封印命中正向行携带 premium_pct;其余授予路径缺省 100 零扰动):
|
||||
# 应用幅度与呈现行值同乘,容器经 magnitude_override 拿同一快照(抗表重载口径保持)。
|
||||
var prem_pct: int = int(row.get("premium_pct", 100))
|
||||
var eff_mag: int = int(srow.magnitude)
|
||||
if prem_pct != 100:
|
||||
eff_mag = int(round(eff_mag * prem_pct / 100.0))
|
||||
var ok: bool = status_effects.apply_effect(eff, _grant_src(att, prem_pct, eff_mag))
|
||||
if ok:
|
||||
emit_cb.call("effect", String(att.get("source_class", "")), att.get("cell", Vector2i(-1, -1)), int(att.get("ri", 0)),
|
||||
String(srow.display_name) + String(att.get("label_suffix", "")), eff_mag,
|
||||
{"effect_id": eff, "pool_value": int(pool_effective_cb.call(eff))}, String(att.get("highlight", "")))
|
||||
return true
|
||||
emit_cb.call("reward_ineffective", String(att.get("source_class", "")), att.get("cell", Vector2i(-1, -1)), int(att.get("ri", 0)),
|
||||
"奖励未生效(满层)%s" % String(att.get("label_suffix", "")), 0, {"batch_key": eff}, String(att.get("highlight", "")))
|
||||
return false
|
||||
|
||||
|
||||
# percent 行施加源束(source_class/origin_cell/round_index 三键;溢价行补 magnitude_override——
|
||||
# 容器拿同一快照,与呈现行值同乘口径一致)
|
||||
func _grant_src(att: Dictionary, prem_pct: int, eff_mag: int) -> Dictionary:
|
||||
var src := {"source_class": String(att.get("source_class", "")), "origin_cell": att.get("cell", Vector2i(-1, -1)), "round_index": int(att.get("ri", 0))}
|
||||
if prem_pct != 100:
|
||||
src["magnitude_override"] = eff_mag
|
||||
return src
|
||||
|
||||
|
||||
# GP 型行兑付:战斗内缓冲 escrow 记账——不入钱包不入增益容器。
|
||||
# 正向(gp_gain)= 挖开GP安全格/封印命中;负向(gp_loss)= 挖危险格/标错封印。兑付行经既有 effect 行
|
||||
# 通道外发(escrow 随行——归因可读),战斗结束随 gp_dropped 一次上抛后即弃。
|
||||
func _apply_gp_row(row: Dictionary, att: Dictionary) -> bool:
|
||||
var eff := String(row.effect_id)
|
||||
var delta: int = int(row.power) if eff == "gp_gain" else -int(row.power)
|
||||
escrow += delta
|
||||
emit_cb.call("effect", String(att.get("source_class", "")), att.get("cell", Vector2i(-1, -1)), int(att.get("ri", 0)),
|
||||
"%s %+d%s" % [String(row.display_name), delta, String(att.get("label_suffix", ""))], delta,
|
||||
{"effect_id": eff, "escrow": escrow}, String(att.get("highlight", "")))
|
||||
return true
|
||||
|
||||
|
||||
# flat 行兑付分派(负向恒即时,无库存语义):伤害型分支走开单据(
|
||||
# 管线/下限/目标解析归 伤害控制器,本件只保留非伤害分支与单据装配):
|
||||
# action = 行动点排队(非伤害);blast 正向 = 群伤单据(无类型恒定表值打全体存活敌;负向 = 警觉 percent 行走 _apply_percent_row);
|
||||
# heal 正向 = 主角回复 clamp(满血 → 奖励未生效行);heal 负向 = 主角直伤单据(不经增益状态);
|
||||
# pierce/magic 正向 = 单目标克制直伤单据(attack_type 查克制矩阵,必中全额不掷点);负向 = 主角直伤单据。
|
||||
# 返回 "redeemed" = 实际有效兑付 / "zero" = 零兑付(行已自记)——读数 = 控制器结账 outcome(A 模式提交即结)。
|
||||
# sync = 同步开单(清图批——流程依赖完美第二份/高光份数读数,无视结账政策当场结账)。
|
||||
func _apply_flat_row(row: Dictionary, att: Dictionary, sync := false) -> String:
|
||||
var ctype := String(row.cell_type)
|
||||
var display := String(row.display_name) + String(att.get("label_suffix", ""))
|
||||
if ctype == "action":
|
||||
return _flat_action(row, display, att)
|
||||
if ctype == "blast":
|
||||
return _flat_blast(row, display, att, sync)
|
||||
if ctype == "pierce" or ctype == "magic":
|
||||
return _flat_typed(row, display, att, sync)
|
||||
if ctype == "heal":
|
||||
return _flat_heal(row, display, att, sync)
|
||||
fatal_cb.call("flat 行未知类型: %s" % ctype)
|
||||
return "zero"
|
||||
|
||||
|
||||
# 行动点排队行(非伤害——下轮行动点带极性入队)
|
||||
func _flat_action(row: Dictionary, display: String, att: Dictionary) -> String:
|
||||
var power := int(row.power)
|
||||
var delta: int = power if String(row.get("polarity", "positive")) == "positive" else -power
|
||||
action_economy.queue_ap_delta(delta)
|
||||
emit_cb.call("effect", String(att.get("source_class", "")), att.get("cell", Vector2i(-1, -1)), int(att.get("ri", 0)),
|
||||
"%s(下轮行动点 %+d)" % [display, delta], delta,
|
||||
{"effect_id": "ap_delta", "pool_value": action_economy.pending_ap_delta()}, String(att.get("highlight", "")))
|
||||
return "redeemed"
|
||||
|
||||
|
||||
# 群伤单据(无类型恒定表值——打全体存活敌)
|
||||
func _flat_blast(row: Dictionary, display: String, att: Dictionary, sync: bool) -> String:
|
||||
var t := damage.open_ticket(DamageController.KIND_BOARD_BLAST, String(att.get("source_class", "")), display, "board", int(row.power),
|
||||
DamageController.TARGET_ALL_ALIVE, int(att.get("ri", 0)), att.get("cell", Vector2i(-1, -1)), "", 0,
|
||||
String(att.get("highlight", "")), {"batch_key": String(row.reward_id)})
|
||||
return outcome_of(submit(t, sync))
|
||||
|
||||
|
||||
# 单目标克制直伤单据(正向 = pierce/magic 必中全额不掷点;负向 = 主角直伤〔复用反噬通道〕)
|
||||
func _flat_typed(row: Dictionary, display: String, att: Dictionary, sync: bool) -> String:
|
||||
if String(row.get("polarity", "positive")) == "negative":
|
||||
return _submit_flat_hero(display, int(row.power), att, sync)
|
||||
var atk_type: String = String(row.get("attack_type", String(row.cell_type)))
|
||||
var t2 := damage.open_ticket(DamageController.KIND_BOARD_TYPED, String(att.get("source_class", "")), display, "board", int(row.power),
|
||||
DamageController.TARGET_FRONT_ALIVE, int(att.get("ri", 0)), att.get("cell", Vector2i(-1, -1)), atk_type, 0,
|
||||
String(att.get("highlight", "")), {"batch_key": String(row.reward_id)})
|
||||
return outcome_of(submit(t2, sync))
|
||||
|
||||
|
||||
# 主角回复行(正向 = clamp 回复,满血/已倒下 → 奖励未生效行;负向 = 主角直伤单据)
|
||||
func _flat_heal(row: Dictionary, display: String, att: Dictionary, sync: bool) -> String:
|
||||
var power := int(row.power)
|
||||
if String(row.get("polarity", "positive")) == "negative":
|
||||
return _submit_flat_hero(display, power, att, sync)
|
||||
if int(units.hero_unit().get("hp", 0)) <= 0:
|
||||
# 主角已倒下(同轮更早的负向直伤致死)——治疗不再合法作用,按"奖励未生效"留痕
|
||||
emit_cb.call("reward_ineffective", String(att.get("source_class", "")), att.get("cell", Vector2i(-1, -1)), int(att.get("ri", 0)),
|
||||
"%s 未生效(主角已倒下)" % display, 0, {"batch_key": String(row.reward_id)}, String(att.get("highlight", "")))
|
||||
return "zero"
|
||||
if int(units.hero_unit().hp) >= int(units.hero_unit().max_hp):
|
||||
emit_cb.call("reward_ineffective", String(att.get("source_class", "")), att.get("cell", Vector2i(-1, -1)), int(att.get("ri", 0)),
|
||||
"%s 未生效(满血)" % display, 0, {"batch_key": String(row.reward_id)}, String(att.get("highlight", "")))
|
||||
return "zero"
|
||||
var healed: int = units.heal(units.hero_unit(), power) # clamp 归 战斗结算
|
||||
emit_cb.call("heal", String(att.get("source_class", "")), att.get("cell", Vector2i(-1, -1)), int(att.get("ri", 0)),
|
||||
"%s 回复" % display, healed, {"target": "hero"}, String(att.get("highlight", "")))
|
||||
return "redeemed"
|
||||
|
||||
|
||||
# 开单分流:sync(清图批)走控制器同步开单——先按序结清段内排队单据、再当场兑现本单;
|
||||
# 常规开单按结账时机政策(A 即结 / B 入队)。案C 闸开启期间一律暂存(战斗段怪物还手后按事件序补提交)。
|
||||
func submit(t: Dictionary, sync: bool) -> int:
|
||||
if bool(defer.on):
|
||||
(defer.queue as Array).append(t) # 案C:授予段伤害单据暂存(sync 同闸,清图批一致延后)
|
||||
return -1
|
||||
return damage.flush_submit(t) if sync else damage.submit(t)
|
||||
|
||||
|
||||
# 案C 结账读数:暂存单(submit 返回 -1)以「将兑付」回报——授予段兑付决策(清图完美第二份等)只能
|
||||
# 依据开单时点事实(该时点敌人存活有保证);战斗段末实际结账若目标已灭,控制器自记未生效行。
|
||||
func outcome_of(ticket_id: int) -> String:
|
||||
return "redeemed" if ticket_id < 0 else damage.outcome(ticket_id)
|
||||
|
||||
|
||||
# 主角直伤单据(反噬/治疗包负向——无类型必中,下限保底在控制器管线内)
|
||||
func _submit_flat_hero(display: String, power: int, att: Dictionary, sync: bool) -> String:
|
||||
var t := damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, String(att.get("source_class", "")), display, "board", power,
|
||||
DamageController.TARGET_HERO, int(att.get("ri", 0)), att.get("cell", Vector2i(-1, -1)), "", 0,
|
||||
String(att.get("highlight", "")), {})
|
||||
return outcome_of(submit(t, sync))
|
||||
|
||||
|
||||
# 封印判定批清图奖励(清图奖励对象恰好发一次;完美封印追加第二份同身份兑付——grant_clear 本身恰一次)。
|
||||
# 高光行 value = 实际有效兑付份数(禁记名义值;首份零兑付 → 降级不发)。
|
||||
# skip_on_refresh/perfect 为宿主态读经参传入(轮内刷新 provenance 与完美封印谓词留宿主)。
|
||||
func grant_clear_reward(ri: int, skip_on_refresh: bool, perfect: bool) -> void:
|
||||
if skip_on_refresh:
|
||||
return # 轮内刷新换来的新图不发清图奖励(行动经济/战斗结算 明文——刷新图不是清图挣来的)
|
||||
var snap = reward_economy.grant_clear()
|
||||
if not (snap is Dictionary):
|
||||
return
|
||||
var first_ok := _grant_clear_identity(snap, ri, "(清图奖励)")
|
||||
var second_ok := false
|
||||
if perfect and first_ok:
|
||||
second_ok = _grant_clear_identity(snap, ri, "(完美第二份)")
|
||||
if perfect and first_ok:
|
||||
emit_cb.call("highlight", "clear_reward", Vector2i(-1, -1), ri, "完美封印!清图奖励 ×2", (2 if second_ok else 1), {}, "perfect_sweep")
|
||||
|
||||
|
||||
# 清图奖励 = 整图独立对象(不属于任何一格):兑付形态按其 cell_type 的正向字典形态走,
|
||||
# 行值全部取快照(身份批 roll 值)。返回实际有效兑付。
|
||||
# 同步开单(sync)——完美第二份与高光份数的流程依赖需要同步读数(开单权归流程);
|
||||
# 同步路径内先按序结清段内排队单据(B 模式保序),随后本批无视结账政策当场兑现。
|
||||
func _grant_clear_identity(snap: Dictionary, ri: int, suffix: String) -> bool:
|
||||
var row := {
|
||||
"reward_id": String(snap.get("reward_id", "")),
|
||||
"cell_type": String(snap.get("cell_type", "")),
|
||||
"polarity": "positive",
|
||||
"display_name": String(snap.get("display_name", "")),
|
||||
"power": int(snap.get("power", 0)),
|
||||
"effect_id": String(snap.get("effect_id", "")),
|
||||
"stacks": int(snap.get("stacks", 1)),
|
||||
}
|
||||
var att := {"source_class": "clear_reward", "cell": Vector2i(-1, -1), "ri": ri,
|
||||
"highlight": "clear_reward_h2", "label_suffix": suffix}
|
||||
if String(row.effect_id) != "":
|
||||
return _apply_percent_row(row, att)
|
||||
return _apply_flat_row(row, att, true) == "redeemed"
|
||||
@@ -0,0 +1 @@
|
||||
uid://bfxpcev60bxe2
|
||||
@@ -4,18 +4,22 @@ extends RefCounted
|
||||
# 职责:案C 闸复位 + 本轮扫雷事实推导(执拗 streak/连珠矢段数)+ 蓄力层注入 + 主角先手
|
||||
# (含连珠矢分段开单)+ 敌方行动兑现 + 地图伤害账殿后(补提交统一结账)。
|
||||
# 调用序:主角先手 → 逐敌还手 → 地图伤害账殿后〔案C〕;hero hp=0 短路。
|
||||
# 行发射/开单/兑付原语仍归宿主编排器(oc)——本函数体为原编排器 _combat_segment 逐字搬运
|
||||
# (🔴 零行为变更契约)。
|
||||
# 行发射/开单单点归宿主(oc)——案C 暂存闸归 RewardGrant(段持三引用直调,零转发壳)——
|
||||
# 本函数体为原编排器 _combat_segment 逐字搬运(🔴 零行为变更契约)。
|
||||
|
||||
var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全) 宿主
|
||||
var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全)
|
||||
var fx_host: BattleFxHost # 效果引擎宿主件(三引用构造——本轮无消费面,构造形与授予段同形)
|
||||
var reward_grant: RewardGrant # 奖励授予件(案C 暂存闸 defer 持有方)
|
||||
|
||||
|
||||
func _init(host) -> void:
|
||||
func _init(host, fx_host_ref: BattleFxHost, reward_grant_ref: RewardGrant) -> void:
|
||||
oc = host
|
||||
fx_host = fx_host_ref
|
||||
reward_grant = reward_grant_ref
|
||||
|
||||
|
||||
func run(ri: int) -> void:
|
||||
oc._defer_board_damage = false # 案C 闸复位——战斗段起开单即入队(主角/敌方/补提交的地图单按开单序统一结账)
|
||||
reward_grant.defer["on"] = false # 案C 闸复位——战斗段起开单即入队(主角/敌方/补提交的地图单按开单序统一结账)
|
||||
# 本轮扫雷事实推导(执拗 streak/连珠矢段数共用取数面):本轮 safe 事件数=
|
||||
# streak;踩雷即清零(mine_hit 监听同口——执拗「踩雷清零」承载)。攻击在战斗段=授予段后
|
||||
# 同一次 _settle 内,facts 已定。
|
||||
@@ -90,11 +94,11 @@ func run(ri: int) -> void:
|
||||
# 主角死于怪物 → 地图账不兑,逐单留「未生效」行不作废静默——没生效也要留痕口径;
|
||||
# 补提交后若地图直伤反杀主角,控制器 _halted 作废余单——战斗已败,胜负归判定段)
|
||||
if int(oc._enemy_system.hero_unit().get("hp", 0)) <= 0:
|
||||
for t in oc._deferred_board:
|
||||
for t in (reward_grant.defer["queue"] as Array):
|
||||
oc._emit_row("reward_ineffective", String(t.get("source_class", "")), t.get("origin_cell", Vector2i(-1, -1)), ri,
|
||||
"%s 未生效(主角已倒下)" % String(t.get("display", "")), 0, {"forfeit": "hero_down"}, "")
|
||||
else:
|
||||
for t in oc._deferred_board:
|
||||
for t in (reward_grant.defer["queue"] as Array):
|
||||
oc._damage.submit(t)
|
||||
oc._deferred_board.clear()
|
||||
(reward_grant.defer["queue"] as Array).clear()
|
||||
oc._damage.flush() # 战斗段界结账(B 模式——主角单+敌方单+地图单按开单序统一兑现;A 模式地图单补提交时即结)
|
||||
@@ -2,24 +2,29 @@ class_name GrantSegment
|
||||
extends RefCounted
|
||||
# ① 授予段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。
|
||||
# 职责:结算队列(开格/封印判定/清图)→ 极性路由逐批兑付 + 封印判/军规开单 + round_end 施加 +
|
||||
# 转存收口 + 揭示兑付 + 段界结账。行发射/开单/兑付原语仍归宿主编排器(oc)——
|
||||
# 转存收口 + 揭示兑付 + 段界结账。行发射/开单单点归宿主(oc)——兑付管线归 RewardGrant、
|
||||
# 效果施加归 BattleFxHost(段持三引用直调,零转发壳)——
|
||||
# 本段只搬运时序,零算法改动(🔴 零行为变更契约:同种子完整局输出逐位一致)。
|
||||
# kind 分派单点化(P1-1 同批):counter_add 五位三行组 / grant_material 三位兑付块
|
||||
# 收 _apply_counter_hit / _apply_material_hit 两助手(原 :683/:742/:777/:839/:1193 与
|
||||
# :690/:782/:1217 八处——分支本体逐字保留在助手内)。
|
||||
|
||||
var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全) 宿主(状态/行发射/开单单点仍归其身——段只搬运时序)
|
||||
var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全)
|
||||
var fx_host: BattleFxHost # 效果引擎宿主件(fx 发射/施加/揭示队列/计数器桶)
|
||||
var reward_grant: RewardGrant # 奖励授予件(极性路由/行应用/提交面/清图奖励/escrow)
|
||||
|
||||
|
||||
func _init(host) -> void:
|
||||
func _init(host, fx_host_ref: BattleFxHost, reward_grant_ref: RewardGrant) -> void:
|
||||
oc = host
|
||||
fx_host = fx_host_ref
|
||||
reward_grant = reward_grant_ref
|
||||
|
||||
|
||||
func run(ri: int) -> void:
|
||||
# ① 授予段:结算队列(开格/封印判定/清图)→ 极性路由逐批兑付(伤害形态单据暂存——见 _submit 案C 闸)。
|
||||
# ① 授予段:结算队列(开格/封印判定/清图)→ 极性路由逐批兑付(伤害形态单据暂存——见 submit 案C 闸)。
|
||||
# 判定基线 = 轮末在场封印(发射面已过滤——被挖开的封印已随挖掘擦除零兑付;
|
||||
# 开格事实不可撤)。
|
||||
oc._defer_board_damage = true # 案C:授予段伤害单据只暂存不结账(兑付时点=战斗段末)
|
||||
reward_grant.defer["on"] = true # 案C:授予段伤害单据只暂存不结账(兑付时点=战斗段末)
|
||||
for evt in oc._facts_reveals:
|
||||
if not bool(evt.get("resource", false)):
|
||||
continue # 封锁格(initial 通道)恒无资源——不入账(战斗结算 义务②)
|
||||
@@ -39,10 +44,10 @@ func run(ri: int) -> void:
|
||||
var board_b: BoardData = oc.minesweeper.current_board()
|
||||
var snap_pre = evt.get("reward_snapshot")
|
||||
var ctype := String((snap_pre as Dictionary).get("cell_type", "")) if snap_pre is Dictionary else ""
|
||||
var is_new_type := ctype != "" and not oc._fx_new_resource_types.has(ctype)
|
||||
var is_new_type := ctype != "" and not fx_host._fx_new_resource_types.has(ctype)
|
||||
if is_new_type:
|
||||
oc._fx_new_resource_types[ctype] = true
|
||||
var dr_hits := oc._fx_emit("dig_reveal", {"cell": evt.cell, "round": ri,
|
||||
fx_host._fx_new_resource_types[ctype] = true
|
||||
var dr_hits := fx_host.fx_emit("dig_reveal", {"cell": evt.cell, "round": ri,
|
||||
"is_first_dig_of_battle": not oc._feat_first_dig_used,
|
||||
"terrain_kind": oc._harmful_variants(evt.get("variant_ids", []) as Array),
|
||||
"is_edge_cell": evt.cell.x == 0 or evt.cell.y == 0
|
||||
@@ -56,7 +61,7 @@ func run(ri: int) -> void:
|
||||
continue
|
||||
pos_m += int(h.get("mult", 0))
|
||||
neg_m += int(h.get("cost_mult", 0))
|
||||
oc._grant_routed(oc._feat_yield_adjust(snap, true, pos_m, neg_m), "positive", "safe_cell", evt.cell, ri, "")
|
||||
reward_grant.grant_routed(oc._feat_yield_adjust(snap, true, pos_m, neg_m), "positive", {"source_class": "safe_cell", "cell": evt.cell, "ri": ri})
|
||||
# 计数器执行(连击计数——加法族,乘区收集后执行=本格兑付用旧值,与迁移前逐位一致)
|
||||
for h in dr_hits:
|
||||
if String(h.get("kind", "")) == "counter_add":
|
||||
@@ -64,7 +69,7 @@ func run(ri: int) -> void:
|
||||
# 揭示效果入队(地质学家/跳格采掘 reveal_tiles 订阅面;授予段末
|
||||
# _flush_pending_reveals 统一掷格兑付)
|
||||
if not oc._hits_of(dr_hits, "reveal_tiles").is_empty():
|
||||
oc._fx_enqueue_reveal()
|
||||
fx_host.enqueue_reveal()
|
||||
# 追加产出(深挖钻头 v2——本格双份制:grant_material 命中=配额内追加第二份;标签随行)
|
||||
for h in dr_hits:
|
||||
if String(h.get("kind", "")) == "grant_material":
|
||||
@@ -76,7 +81,7 @@ func run(ri: int) -> void:
|
||||
var spell_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell",
|
||||
"奥术矢", "board", int((spell_hits[0] as Dictionary).get("value", 0)),
|
||||
DamageController.TARGET_FRONT_ALIVE, ri, evt.cell, "magic")
|
||||
oc._submit(spell_t, false)
|
||||
reward_grant.submit(spell_t, false)
|
||||
elif evt.kind == "mine":
|
||||
# 封印不可逆四情况②:插危险格再挖开 = 只受挖雷一层惩罚(mine_hit 照常兑付——惊动巢穴),
|
||||
# 标中不发(封印已随挖掘擦除、不入轮末判定基线)。
|
||||
@@ -86,7 +91,7 @@ func run(ri: int) -> void:
|
||||
# damage_reduce〔安全帽耗层减伤——value_counter 层值,一次清零〕/ heal〔血蜜〕/
|
||||
# blast_scaled〔陷阱爆发——雷伤×倍率全体敌〕/ bonus_delta〔血税〕/ reveal_tiles〔见血开窍〕)
|
||||
var mine_power := int((snap as Dictionary).get("power", 0))
|
||||
var mh_hits := oc._fx_emit("mine_hit", {"cell": evt.cell, "round": ri,
|
||||
var mh_hits := fx_host.fx_emit("mine_hit", {"cell": evt.cell, "round": ri,
|
||||
"damage": mine_power, "dug_under_seal": bool(evt.get("dug_under_seal", false)),
|
||||
"hp_ratio": int(round(100.0 * float(int(oc._enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(oc._enemy_system.hero_unit().get("max_hp", 1))))))})
|
||||
var halve_pct := 0
|
||||
@@ -97,7 +102,7 @@ func run(ri: int) -> void:
|
||||
halve_pct += int(h.get("value", 0))
|
||||
"damage_reduce":
|
||||
if h.has("value_counter") and String(h.get("value_counter", "")) != "":
|
||||
mine_reduce += EffectEngine.counter(oc.counters_ctx, String(h.get("value_counter", ""))) # 安全帽——层值减免
|
||||
mine_reduce += EffectEngine.counter(fx_host.counters_ctx, String(h.get("value_counter", ""))) # 安全帽——层值减免
|
||||
else:
|
||||
mine_reduce += int(h.get("value", 0)) # 背水——定值减免
|
||||
"heal":
|
||||
@@ -105,26 +110,26 @@ func run(ri: int) -> void:
|
||||
hu_heal["hp"] = mini(int(hu_heal.get("hp", 0)) + int(h.get("value", 0)), maxi(1, int(hu_heal.get("max_hp", 1))))
|
||||
"bonus_delta":
|
||||
var axis := String(h.get("axis", ""))
|
||||
if oc._fx_bonus_delta.has(axis):
|
||||
oc._fx_bonus_delta[axis] += int(h.get("delta", 0))
|
||||
if fx_host._fx_bonus_delta.has(axis):
|
||||
fx_host._fx_bonus_delta[axis] += int(h.get("delta", 0))
|
||||
"blast_scaled":
|
||||
var blast_v := maxi(int(oc.db.settlement.damage_floor), int(float(mine_power) * float(h.get("mult", 1))))
|
||||
var bs_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_blast_scaled", "陷阱爆发", "board",
|
||||
blast_v, DamageController.TARGET_ALL_ALIVE, ri, evt.cell, "", 0, "", {})
|
||||
oc._submit(bs_t, false)
|
||||
reward_grant.submit(bs_t, false)
|
||||
if mine_reduce > 0:
|
||||
snap["power"] = maxi(0, mine_power - mine_reduce) # 减伤落快照(负向兑付按减后值——安全帽每层减 1)
|
||||
for h in mh_hits:
|
||||
if String(h.get("kind", "")) == "counter_add":
|
||||
_apply_counter_hit(h)
|
||||
if not oc._hits_of(mh_hits, "reveal_tiles").is_empty():
|
||||
oc._fx_enqueue_reveal() # 见血开窍——每颗雷一次揭示(授予段末统一兑付)
|
||||
oc._grant_routed(oc._feat_yield_adjust(snap, false, 0, 0), "negative", "mine_hit", evt.cell, ri, "", "", halve_pct)
|
||||
fx_host.enqueue_reveal() # 见血开窍——每颗雷一次揭示(授予段末统一兑付)
|
||||
reward_grant.grant_routed(oc._feat_yield_adjust(snap, false, 0, 0), "negative", {"source_class": "mine_hit", "cell": evt.cell, "ri": ri, "halve_pct": halve_pct})
|
||||
for h in mh_hits:
|
||||
if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "reflect":
|
||||
var nct := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_counter", "惊巢反制·反弹", "board",
|
||||
int(h.get("value", 0)), DamageController.TARGET_ALL_ALIVE, ri, evt.cell, "", 0, "", {})
|
||||
oc._submit(nct, false)
|
||||
reward_grant.submit(nct, false)
|
||||
# 地形变异 兑付段(授予段尾——触发枚举 on_open:挖开即伤、chain 通道同口径;伤害单据案C 暂存到
|
||||
# 战斗段末兑付〔案C〕;信息型 hide_number 不进结算——呈现层读取,见 board_view 数字遮蔽;
|
||||
# 危险格恒无变异——布置段排除,双层叠罚不存在;封印格直挖照吃变异伤害——地形后果同 mine_hit 口径,
|
||||
@@ -140,7 +145,7 @@ func run(ri: int) -> void:
|
||||
# 面〕先收集再按减后值开单;counter_add〔保险丝/石肤〕+ ac_bonus〔石肤〕照常施加;
|
||||
# grant_material〔荆棘/地脉〕移到单据提交后(与迁移前兑付值序一致——收益不因减伤变化)
|
||||
var dt_amount := int(vrow.get("effect_value", 0))
|
||||
var dt_hits := oc._fx_emit("damage_taken", {"kind": "terrain", "amount": dt_amount,
|
||||
var dt_hits := fx_host.fx_emit("damage_taken", {"kind": "terrain", "amount": dt_amount,
|
||||
"source": String(vid), "cell": evt.cell, "round": ri, "terrain_kind": String(vid),
|
||||
"hp_ratio": int(round(100.0 * float(int(oc._enemy_system.hero_unit().get("hp", 0))) / float(maxi(1, int(oc._enemy_system.hero_unit().get("max_hp", 1))))))})
|
||||
for h in dt_hits:
|
||||
@@ -148,12 +153,12 @@ func run(ri: int) -> void:
|
||||
"damage_reduce":
|
||||
dt_amount = maxi(0, dt_amount - int(h.get("value", 0)))
|
||||
"ac_bonus":
|
||||
oc._apply_ac_bonus(h)
|
||||
fx_host.apply_ac_bonus(h)
|
||||
"counter_add":
|
||||
_apply_counter_hit(h)
|
||||
var vt := oc._damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, "地形变异", String(vrow.get("display_name", vid)), "board", \
|
||||
dt_amount, DamageController.TARGET_HERO, ri, evt.cell, "", 0, "", {"variant_id": String(vid)})
|
||||
oc._submit(vt, false)
|
||||
reward_grant.submit(vt, false)
|
||||
for h in dt_hits:
|
||||
if String(h.get("kind", "")) == "grant_material":
|
||||
_apply_material_hit(h, evt.cell, ri)
|
||||
@@ -172,7 +177,7 @@ func run(ri: int) -> void:
|
||||
fs_hits += 1
|
||||
else:
|
||||
fs_misses += 1
|
||||
var fs_fx := oc._fx_emit("seal_settle", {"seals_hit_all": fs_hits > 0 and fs_misses == 0,
|
||||
var fs_fx := fx_host.fx_emit("seal_settle", {"seals_hit_all": fs_hits > 0 and fs_misses == 0,
|
||||
"seals_count": fs_hits, "round": ri})
|
||||
var banner_hits: Array[Variant] = []
|
||||
for h in fs_fx:
|
||||
@@ -192,21 +197,21 @@ func run(ri: int) -> void:
|
||||
var st_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_seal_strike",
|
||||
"战封印军规", "board", strike_value, DamageController.TARGET_FRONT_ALIVE, ri,
|
||||
fevt.cell, "normal")
|
||||
oc._submit(st_t, false)
|
||||
reward_grant.submit(st_t, false)
|
||||
# round_end 发射+施加(内容数据驱动扩展批 T3——每轮结算收口:熔火之躯反打单开立〔open_ticket
|
||||
# 即时执行〕+积层清零〔counter_add set〕;整备转蓄力由 finish_round_stop 承载——停手侧 AP 已在
|
||||
# request_stop 记账,转存决策=grant_ap(mode:transfer) 订阅定夺,无订阅走 RAP 转存缺省)
|
||||
var re_fx := oc._fx_emit("round_end", {"round": ri})
|
||||
var re_fx := fx_host.fx_emit("round_end", {"round": ri})
|
||||
for h in re_fx:
|
||||
if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "blast":
|
||||
var mg_layers := EffectEngine.counter(oc.counters_ctx, String(h.get("per", "")))
|
||||
var mg_layers := EffectEngine.counter(fx_host.counters_ctx, String(h.get("per", "")))
|
||||
var mg_total := int(h.get("value", 0)) * mg_layers
|
||||
if mg_total > 0:
|
||||
# source_class 用中性名;per-counter 反打通道保留泛用面
|
||||
var mg_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_blast_counter",
|
||||
"积层反噬 ×%d" % mg_layers, "board", mg_total,
|
||||
DamageController.TARGET_ALL_ALIVE, ri, oc.minesweeper.hero_pos(), "", 0, "", {})
|
||||
oc._submit(mg_t, false)
|
||||
reward_grant.submit(mg_t, false)
|
||||
for h in re_fx:
|
||||
if String(h.get("kind", "")) == "counter_add":
|
||||
_apply_counter_hit(h)
|
||||
@@ -214,18 +219,18 @@ func run(ri: int) -> void:
|
||||
# grant_ap mode:queue〔封印回响——返 1 AP 下轮,queue_ap_delta 通道与轮始加成同面〕
|
||||
for h in re_fx:
|
||||
if String(h.get("kind", "")) == "grant_gp":
|
||||
oc.escrow += int(h.get("value", 0))
|
||||
oc._emit_row("effect", "fx_grant_gp", Vector2i(-1, -1), ri, "专长·GP 入账 +%d" % int(h.get("value", 0)), int(h.get("value", 0)), {"effect_id": "fx_grant_gp", "escrow": oc.escrow}, "")
|
||||
reward_grant.escrow += int(h.get("value", 0))
|
||||
oc._emit_row("effect", "fx_grant_gp", Vector2i(-1, -1), ri, "专长·GP 入账 +%d" % int(h.get("value", 0)), int(h.get("value", 0)), {"effect_id": "fx_grant_gp", "escrow": reward_grant.escrow}, "")
|
||||
elif String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue":
|
||||
oc.action_economy.queue_ap_delta(int(h.get("value", 0)))
|
||||
# 转存率入参(迭代 22 秘宝批 T3——储备罐 rap_transfer_mult magnitude=目标转存率百分比 125=1.25:1;
|
||||
# 未装配=100 与旧 1:1 逐位一致)
|
||||
var rap_pct: int = oc.attune_param_sum("rap_transfer_mult")
|
||||
oc.action_economy.finish_round_stop(oc._fx != null and oc._fx.has_effect_on("round_end", "grant_ap", "transfer"),
|
||||
oc.action_economy.finish_round_stop(fx_host.engine.has_effect_on("round_end", "grant_ap", "transfer"),
|
||||
rap_pct if rap_pct > 0 else 100)
|
||||
if oc._facts_path == "cleared":
|
||||
oc._grant_clear_reward(ri)
|
||||
oc._flush_pending_reveals(ri) # 揭示效果兑付(授予段末统一掷格,防轮 facts 迭代重入)
|
||||
reward_grant.grant_clear_reward(ri, oc._board_from_midround_refresh, oc.perfect_sweep_earned())
|
||||
fx_host.flush_pending_reveals(ri) # 揭示效果兑付(授予段末统一掷格,防轮 facts 迭代重入)
|
||||
oc._damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界)
|
||||
|
||||
|
||||
@@ -240,7 +245,7 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
|
||||
oc._perfect_hit_true += 1
|
||||
# seal_hit 发射+施加(内容数据驱动扩展批 T3——桶序固定承载等价:殉爆〔T4 秘宝面〕→
|
||||
# 净化改写 → 图谱额外资源 → 基线兑付)
|
||||
var fh_hits := oc._fx_emit("seal_hit", {"cell": cell, "round": ri,
|
||||
var fh_hits := fx_host.fx_emit("seal_hit", {"cell": cell, "round": ri,
|
||||
"terrain_kind": oc._harmful_adjacent(cell)})
|
||||
# 标中殉爆(迭代 11 同调批 T5 → 内容数据驱动扩展批 T4:订阅施加——行 triggers open_ticket
|
||||
# channel blast value_from magnitude;每标中一面封印全体存活敌吃 N 群伤,无类型走 blast 通道。
|
||||
@@ -250,7 +255,7 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
|
||||
continue
|
||||
var fbt := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_seal_burst", "封印爆裂", "board",
|
||||
int(h.get("value", 0)), DamageController.TARGET_ALL_ALIVE, ri, cell, "", 0, "highlight", {})
|
||||
oc._submit(fbt, false)
|
||||
reward_grant.submit(fbt, false)
|
||||
# 标中施加面:counter_add〔血税 标中+1 攻——bonus_delta atk 轴〕/
|
||||
# ac_bonus〔封印桩 本轮 AC+N〕/ preview_area〔示警烟火 3×3 资源类型预告〕/
|
||||
# remove_mines〔封印匠 每第 3 次该危险格中立移除——mod_eq 闸在行 when〕。
|
||||
@@ -260,11 +265,11 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
|
||||
"counter_add":
|
||||
_apply_counter_hit(h)
|
||||
"ac_bonus":
|
||||
oc._apply_ac_bonus(h)
|
||||
fx_host.apply_ac_bonus(h)
|
||||
"bonus_delta":
|
||||
var axis_f := String(h.get("axis", ""))
|
||||
if oc._fx_bonus_delta.has(axis_f):
|
||||
oc._fx_bonus_delta[axis_f] += int(h.get("delta", 0))
|
||||
if fx_host._fx_bonus_delta.has(axis_f):
|
||||
fx_host._fx_bonus_delta[axis_f] += int(h.get("delta", 0))
|
||||
"remove_mines":
|
||||
var removed: Array[Variant] = oc.minesweeper.remove_mines_in([cell])
|
||||
if not removed.is_empty():
|
||||
@@ -287,7 +292,7 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
|
||||
var snap = oc._reward_economy.grant(cell)
|
||||
if not (snap is Dictionary):
|
||||
return
|
||||
oc._grant_routed(snap, "positive", "seal_hit", cell, ri, "seal_hit")
|
||||
reward_grant.grant_routed(snap, "positive", {"source_class": "seal_hit", "cell": cell, "ri": ri, "highlight": "seal_hit"})
|
||||
else:
|
||||
oc._perfect_hit_false += 1
|
||||
if oc._seal_miss_seen.has(cell):
|
||||
@@ -298,7 +303,7 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
|
||||
# 场」由发射面既有过滤承载〔被挖开的封印已随挖掘擦除零兑付〕;grant_ap mode:queue 下一轮
|
||||
# 开轮兑现——末轮判出无下一轮=合法作废自动成立〔编排器每战新建,队列不跨战〕;限次=合成行
|
||||
# adventure 域计数器闸)
|
||||
for h in oc._fx_emit("seal_missed", {"cell": cell, "round": ri}):
|
||||
for h in fx_host.fx_emit("seal_missed", {"cell": cell, "round": ri}):
|
||||
if String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue":
|
||||
oc.action_economy.queue_ap_delta(int(h.get("value", 0)))
|
||||
elif String(h.get("kind", "")) == "forgive_seal_miss":
|
||||
@@ -313,17 +318,17 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
|
||||
var snap = oc._reward_economy.grant(cell)
|
||||
if not (snap is Dictionary):
|
||||
return
|
||||
oc._grant_routed(snap, "negative", "seal_miss", cell, ri, "")
|
||||
reward_grant.grant_routed(snap, "negative", {"source_class": "seal_miss", "cell": cell, "ri": ri})
|
||||
|
||||
|
||||
# counter_add 三行组单点(原五位分支本体逐字收拢——kind 分派随段归位 P1-1)
|
||||
func _apply_counter_hit(h: Dictionary) -> void:
|
||||
var set_v = h.get("set", null)
|
||||
EffectEngine.counter_add(oc.counters_ctx, String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null)
|
||||
EffectEngine.counter_add(fx_host.counters_ctx, String(h.get("counter_id", "")), int(h.get("value", 0)) if set_v == null else int(set_v), set_v != null)
|
||||
|
||||
|
||||
# grant_material 兑付块单点(原三位分支本体逐字收拢——source_class 两态 safe_cell/seal_hit 由参承载)
|
||||
func _apply_material_hit(h: Dictionary, cell: Vector2i, ri: int, source_class: String = "safe_cell") -> void:
|
||||
var g_snap = oc._reward_economy.grant(cell)
|
||||
if g_snap is Dictionary:
|
||||
oc._grant_routed(g_snap, "positive", source_class, cell, ri, "", String(h.get("label", "")))
|
||||
reward_grant.grant_routed(g_snap, "positive", {"source_class": source_class, "cell": cell, "ri": ri, "label_suffix": String(h.get("label", ""))})
|
||||
@@ -61,10 +61,15 @@ static func _build_battle_wiring(root: Variant) -> void:
|
||||
root._row_pacer.refresh_request = root._refresh_presentations
|
||||
root._row_pacer.pump_ended = root._on_pump_ended # 演播完边沿统一派发(挂起 modal 优先,其次锁期点击重放)
|
||||
root.add_child(root._row_pacer)
|
||||
# 秘宝两步交互(本体在 treasure_interact_controller.gd;settlement/runner 随关卡更替,
|
||||
# 经现值回调注入——仿 identity_resolver 只读回调先例)
|
||||
root._treasure_ui = TreasureInteractController.new(root.db, root.meta, root.info_panel, root.board_zone.toast, root.board_view,
|
||||
func() -> Variant: return root.run_scope.settlement, func() -> Variant: return root.run_scope.runner)
|
||||
# 秘宝两步交互(本体在 treasure_interact_controller.gd;settlement/runner/grant 随关卡/战斗更替,
|
||||
# 经现值回调注入——仿 identity_resolver 只读回调先例;grant 每战新建编排器,现值取件防悬挂)
|
||||
root._treasure_ui = TreasureInteractController.new({
|
||||
"db": root.db, "meta": root.meta, "info_panel": root.info_panel, "toast": root.board_zone.toast,
|
||||
"board_view": root.board_view,
|
||||
"settlement_get": func() -> Variant: return root.run_scope.settlement,
|
||||
"runner_get": func() -> Variant: return root.run_scope.runner,
|
||||
"grant_get": func() -> Variant: return root.run_scope.settlement.reward_grant() if root.run_scope.settlement != null else null,
|
||||
})
|
||||
|
||||
root.board_zone.action_requested.connect(root._on_board_action)
|
||||
root.board_zone.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(战斗屏 HUD 角标)
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 秘宝两步交互控制器:C 区槽位选中/取消(toggle)→ 棋盘点格使用目标拾取的状态机 + 库存/叠加层只读投影装配。
|
||||
## 写调用唯一经 settlement.use_treasure;信号连接只归组合根(info_panel.treasure_slot_clicked 接至 on_slot_clicked)。
|
||||
## settlement/runner 随关卡逐关更替——经组合根现值回调注入。
|
||||
## AP 类(target=none)调用面一律 attempt_use(handle)(cell 缺省=null=显式无目标)——哨兵坐标收口本类,不出控制器。
|
||||
## 写调用分家(三触点):使用受理走 grant(RewardGrant.use_treasure——授予件面)、排雷玩法只读读与
|
||||
## 积点进度读走 settlement(编排器面);写调用唯一经现值回调注入的协作件,控制器零核心写调用本体。
|
||||
## 信号连接只归组合根(info_panel.treasure_slot_clicked 接至 on_slot_clicked)。
|
||||
## settlement/runner/grant 随关卡/战斗逐次更替——经组合根现值回调注入(grant 每战新建编排器,
|
||||
## 现值取件防悬挂)。AP 类(target=none)调用面一律 attempt_use(handle)(cell 缺省=null=显式无目标)
|
||||
## ——哨兵坐标收口本类,不出控制器。
|
||||
## ctx 引用束封闭键集(8 项,缺一键 assert):db/meta/info_panel:HeroInfoPanelView/toast:ToastStack/
|
||||
## board_view:BoardView/settlement_get:Callable(编排器现值——minesweeper 读与 attune_convert_state
|
||||
## 双取值面之一)/runner_get:Callable/grant_get:Callable(授予件现值——use_treasure 直注)。
|
||||
## 禁实现期增删键。
|
||||
extends RefCounted
|
||||
|
||||
const NO_TARGET := Vector2i(-9, -9) # core use_treasure 形参占位(仅 target=none 行到达——validate_use 忽略该值)
|
||||
@@ -12,20 +19,23 @@ var meta # 解锁门控数据源(工坊解锁×活行交
|
||||
var info_panel: HeroInfoPanelView
|
||||
var toast: ToastStack # 反馈面(board_zone.toast 注入)
|
||||
var board_view: BoardView
|
||||
var _settlement_get: Callable # () → 当前战斗结算现值(组合根 lambda)
|
||||
var _settlement_get: Callable # () → 当前战斗结算现值(组合根 lambda——双取值面之一)
|
||||
var _runner_get: Callable # () → 当前关卡推进器现值(组合根 lambda)
|
||||
var _grant_get: Callable # () → 当前奖励授予件现值(组合根 lambda——每战新建,防悬挂)
|
||||
var _use_selection := 0 # 选中句柄(0=未选中;选中期棋盘点击=使用目标拾取)
|
||||
|
||||
|
||||
func _init(db_ref, meta_ref, info_panel_ref: HeroInfoPanelView, toast_ref: ToastStack,
|
||||
board_view_ref: BoardView, settlement_get: Callable, runner_get: Callable) -> void:
|
||||
db = db_ref
|
||||
meta = meta_ref
|
||||
info_panel = info_panel_ref
|
||||
toast = toast_ref
|
||||
board_view = board_view_ref
|
||||
_settlement_get = settlement_get
|
||||
_runner_get = runner_get
|
||||
func _init(ctx: Dictionary) -> void:
|
||||
for k in ["db", "meta", "info_panel", "toast", "board_view", "settlement_get", "runner_get", "grant_get"]:
|
||||
assert(ctx.has(k), "[秘宝交互] 引用束缺键: %s" % k)
|
||||
db = ctx.db
|
||||
meta = ctx.meta
|
||||
info_panel = ctx.info_panel
|
||||
toast = ctx.toast
|
||||
board_view = ctx.board_view
|
||||
_settlement_get = ctx.settlement_get
|
||||
_runner_get = ctx.runner_get
|
||||
_grant_get = ctx.grant_get
|
||||
|
||||
|
||||
# aux 解锁门控集组装:工坊解锁 ∩ 过渡活行词表(treasure_transitional_rows 口径);
|
||||
@@ -51,7 +61,9 @@ func active_selection() -> int:
|
||||
func on_slot_clicked(slot_handle: int) -> void:
|
||||
var settlement = _settlement_get.call()
|
||||
var runner = _runner_get.call()
|
||||
if settlement == null or runner == null or runner.treasure_inventory == null:
|
||||
var grant = _grant_get.call()
|
||||
# 三现值同查守卫(settlement/runner/grant 任一未就绪即静默 return——现值回调随更替时序)
|
||||
if settlement == null or runner == null or grant == null or runner.treasure_inventory == null:
|
||||
return
|
||||
if slot_handle == 0:
|
||||
_use_selection = 0
|
||||
@@ -82,11 +94,11 @@ func on_slot_clicked(slot_handle: int) -> void:
|
||||
|
||||
# 使用受理(第二步;cell=空间类目标格,null=无目标;core 侧 use_treasure 四步编排——拒绝零消耗 toast 可辨认)
|
||||
func attempt_use(handle: int, cell: Variant = null) -> void:
|
||||
var settlement = _settlement_get.call()
|
||||
if settlement == null:
|
||||
var grant = _grant_get.call()
|
||||
if grant == null:
|
||||
return
|
||||
var target: Vector2i = cell if cell != null else NO_TARGET
|
||||
var v: Dictionary[Variant, Variant] = settlement.use_treasure(handle, target)
|
||||
var v: Dictionary[Variant, Variant] = grant.use_treasure(handle, target)
|
||||
if not bool(v.ok):
|
||||
toast.show_toast(String(v.get("reason", "无法使用")), "warn")
|
||||
return
|
||||
@@ -98,7 +110,7 @@ func attempt_use(handle: int, cell: Variant = null) -> void:
|
||||
toast.show_toast("已使用 %s" % String(arow.get("display_name", "")), "info")
|
||||
|
||||
|
||||
# 库存投影(C 区四槽取数面——字形=名首字;usable=整图零信息预判回调接排雷玩法只读面)
|
||||
# 库存投影(C 区四槽取数面——逐库存行装配 + 末尾自动兑付件合成行)
|
||||
func inventory_projection() -> Array:
|
||||
var runner = _runner_get.call()
|
||||
if runner == null or runner.treasure_inventory == null:
|
||||
@@ -111,41 +123,61 @@ func inventory_projection() -> Array:
|
||||
return ms.has_unrevealed() # 只读查询面(排雷玩法 has_unrevealed——手搓全板双循环收口)
|
||||
var out: Array[Variant] = []
|
||||
var cs: Dictionary[Variant, Variant] = settlement.attune_convert_state() if settlement != null else {}
|
||||
var auto_seen := false # 自动兑付件是否已随骑库存出现(恒否——charges=0 不入骑库存;守合成行重复)
|
||||
for rec in runner.treasure_inventory.query_inventory(cb):
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(rec.get("item_id", "")))
|
||||
var dn := String(arow.get("display_name", ""))
|
||||
# 积点进度浮窗行(自动兑付件 C 区悬停 tooltip 补 N/M 进度;件 id 锁定=装配行唯一)
|
||||
var note := ""
|
||||
if bool(cs.get("assembled", false)) and String(cs.get("item_id", "")) == String(rec.get("item_id", "")):
|
||||
note = "积点 %d/%d" % [int(cs.get("points", 0)), int(cs.get("need", 0))]
|
||||
auto_seen = true
|
||||
out.append({
|
||||
"handle": int(rec.get("handle", 0)),
|
||||
"item_id": String(rec.get("item_id", "")),
|
||||
"glyph": dn.left(1) if dn != "" else "?",
|
||||
"name": dn,
|
||||
"used": bool(rec.get("used", false)),
|
||||
"usable": bool(rec.get("usable", true)),
|
||||
"note": note,
|
||||
})
|
||||
out.append(_projection_row(rec, cs))
|
||||
# 自动兑付件展示行(charges=0 不入骑库存〔_reseed_attune_charges 跳过注入〕,
|
||||
# C 区四槽末尾补合成行承载悬停进度;handle=-1 非库存句柄:槽点击走说明 toast 分支不进两步交互)
|
||||
# C 区四槽末尾补合成行承载悬停进度;handle=-1 非库存句柄:槽点击走说明 toast 分支不进两步交互)。
|
||||
# 库存内已见同 id 行(恒否——charges=0 不入骑库存)则不重复补行,守合成行重复
|
||||
var auto_seen := false
|
||||
if bool(cs.get("assembled", false)):
|
||||
for row_v in out:
|
||||
if String(cs.get("item_id", "")) == String((row_v as Dictionary).get("item_id", "")):
|
||||
auto_seen = true
|
||||
break
|
||||
if bool(cs.get("assembled", false)) and not auto_seen:
|
||||
var adn := String(cs.get("display_name", ""))
|
||||
if adn != "":
|
||||
out.append({
|
||||
"handle": -1,
|
||||
"item_id": String(cs.get("item_id", "")),
|
||||
"glyph": adn.left(1),
|
||||
"name": adn,
|
||||
"used": false,
|
||||
"usable": true,
|
||||
"note": "积点 %d/%d" % [int(cs.get("points", 0)), int(cs.get("need", 0))],
|
||||
})
|
||||
var auto := _auto_row(cs)
|
||||
if not auto.is_empty():
|
||||
out.append(auto)
|
||||
return out
|
||||
|
||||
|
||||
# 库存投影行(字形=名首字;usable=整图零信息预判回调接排雷玩法只读面)+
|
||||
# 积点进度浮窗行(自动兑付件 C 区悬停 tooltip 补 N/M 进度;件 id 锁定=装配行唯一)
|
||||
func _projection_row(rec: Dictionary, cs: Dictionary) -> Dictionary:
|
||||
var arow: Dictionary[Variant, Variant] = db.treasure_item_row(String(rec.get("item_id", "")))
|
||||
var dn := String(arow.get("display_name", ""))
|
||||
var note := ""
|
||||
if bool(cs.get("assembled", false)) and String(cs.get("item_id", "")) == String(rec.get("item_id", "")):
|
||||
note = "积点 %d/%d" % [int(cs.get("points", 0)), int(cs.get("need", 0))]
|
||||
return {
|
||||
"handle": int(rec.get("handle", 0)),
|
||||
"item_id": String(rec.get("item_id", "")),
|
||||
"glyph": dn.left(1) if dn != "" else "?",
|
||||
"name": dn,
|
||||
"used": bool(rec.get("used", false)),
|
||||
"usable": bool(rec.get("usable", true)),
|
||||
"note": note,
|
||||
}
|
||||
|
||||
|
||||
# 自动兑付件合成展示行(未装配/无名行=空字典不补行)
|
||||
func _auto_row(cs: Dictionary) -> Dictionary:
|
||||
if not bool(cs.get("assembled", false)):
|
||||
return {}
|
||||
var adn := String(cs.get("display_name", ""))
|
||||
if adn == "":
|
||||
return {}
|
||||
return {
|
||||
"handle": -1,
|
||||
"item_id": String(cs.get("item_id", "")),
|
||||
"glyph": adn.left(1),
|
||||
"name": adn,
|
||||
"used": false,
|
||||
"usable": true,
|
||||
"note": "积点 %d/%d" % [int(cs.get("points", 0)), int(cs.get("need", 0))],
|
||||
}
|
||||
|
||||
|
||||
func refresh_slots() -> void:
|
||||
info_panel.set_treasure_inventory(inventory_projection(), _use_selection)
|
||||
|
||||
|
||||
@@ -306,13 +306,13 @@ fi
|
||||
# 可回放纯逻辑步骤流(同种子同输入逐位复现),await 会打断确定性——「模块归属」B1 行已写明
|
||||
# 「禁 await——切片 88 冒烟实证」。本闸为防回归防线(2026-10-24 人工 grep 盘面干净)。
|
||||
# 扫描面:B1 域文件集(与 architecture.md「模块归属」B1 行同口径)=battle_orchestrator.gd
|
||||
# + settlement/ 五段段类 + damage_controller.gd,共 7 文件,显式清单见 S8_FILES。
|
||||
# + battle_fx_host.gd + reward_grant.gd 两拆件 + settlement/ 五段段类 + damage_controller.gd,共 9 文件,显式清单见 S8_FILES。
|
||||
# 执行侧字面值豁免:脚本必须带字面值才能工作;文件集与架构文档冲突时以 B1 域文件集为准。
|
||||
# 判据:文件集内出现 await 关键字即红(词边界 \bawait\b,任何形态——await 信号/协程/
|
||||
# get_tree());排除注释行(行首 #/##)与字符串字面量(双引号包裹段内的 await 非代码),
|
||||
# 口径仿 S1/S2 的 grep 排除手法。
|
||||
# 自检:清单内文件缺位(路径写错/被改名)即判红——防扫面为空静默绿。
|
||||
S8_FILES="game/core/battle_orchestrator.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/damage_controller.gd"
|
||||
S8_FILES="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.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/damage_controller.gd"
|
||||
S8_MISS=0
|
||||
for f in $S8_FILES; do
|
||||
if [ ! -f "$f" ]; then
|
||||
@@ -354,7 +354,7 @@ fi
|
||||
# 自检:扫描面为空即判红(require_scan_face);B1 文件集与三族自家文件缺位即判红
|
||||
# (文件被改名=豁免名单失效,宁可红不可静默放行)。
|
||||
if require_scan_face "S7" "$DIR_CORE"; then
|
||||
S7_B1_SET="game/core/battle_orchestrator.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/damage_controller.gd"
|
||||
S7_B1_SET="game/core/battle_orchestrator.gd game/core/battle_fx_host.gd game/core/reward_grant.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/damage_controller.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
|
||||
|
||||
@@ -984,7 +984,7 @@ func _smoke_counter_buckets() -> void:
|
||||
enc2["battle_index"] = 2
|
||||
var o2 = RigS.new(_db, _fresh_rng(779302))
|
||||
o2.begin_battle(enc2)
|
||||
Kit.check("V1 第2战 begin_battle 经 ctx 读到上一战持久值=1(读路径走宿主字典引用,非副本种子回灌)〔无状态化 V1〕", EffectEngine.counter(o2.counters_ctx, "adv_twin_digs") == 1)
|
||||
Kit.check("V1 第2战 begin_battle 经 ctx 读到上一战持久值=1(读路径走宿主字典引用,非副本种子回灌)〔无状态化 V1〕", EffectEngine.counter(o2.fx_host().counters_ctx, "adv_twin_digs") == 1)
|
||||
o2.action_economy.request_dig(0, false)
|
||||
Kit.check("V1 第2战挖后 adventure 桶跨战累积=2(run_ledger:42 判空早退缺陷消解的回归钉面——旧副本态恒 1)〔无状态化 V1〕", int(led.adventure_counters.get("adv_twin_digs", 0)) == 2)
|
||||
# V6——battle 桶随宿主销毁:第1战标错落 insurance_used(battle 桶无前缀),次战新编排器读 0。
|
||||
@@ -1003,7 +1003,7 @@ func _smoke_counter_buckets() -> void:
|
||||
safe_a = ca
|
||||
break
|
||||
oA._grant_seg._judge_seal({"cell": safe_a, "hit": false}, 1)
|
||||
Kit.check("V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕", EffectEngine.counter(oA.counters_ctx, "insurance_used") == 1)
|
||||
Kit.check("V6 第1战标错落 battle 桶 insurance_used=1(_apply_counter_hit 静态写口经 ctx)〔无状态化 V6〕", EffectEngine.counter(oA.fx_host().counters_ctx, "insurance_used") == 1)
|
||||
# 卡④ wr2①——完美保险标错行=单一扩展行型(原 insurance_used 实名行型收敛)
|
||||
var v6_ins := {}
|
||||
for r in v6_rows:
|
||||
@@ -1016,12 +1016,12 @@ func _smoke_counter_buckets() -> void:
|
||||
encB["battle_index"] = 2
|
||||
var oB = RigS.new(_db, _fresh_rng(779402))
|
||||
oB.begin_battle(encB)
|
||||
Kit.check("V6 battle 桶随宿主销毁:次战新编排器 fx_counters_battle 天然空,读 insurance_used=0〔无状态化 V6〕", EffectEngine.counter(oB.counters_ctx, "insurance_used") == 0)
|
||||
Kit.check("V6 battle 桶随宿主销毁:次战新编排器 fx_counters_battle 天然空,读 insurance_used=0〔无状态化 V6〕", EffectEngine.counter(oB.fx_host().counters_ctx, "insurance_used") == 0)
|
||||
|
||||
|
||||
# ---- 扩展行收敛(迭代 38 卡④)——fx_reveal 兑付行与落空行经真实揭示管线发射 ----
|
||||
# 驱动形态:装配揭示类专长(geologist)的编排器上走生产同一入队/兑付管线
|
||||
# (_fx_enqueue_reveal → _flush_pending_reveals——grant/damage 揭示订阅面的共用出口)。
|
||||
# (fx_host enqueue_reveal → flush_pending_reveals——grant/damage 揭示订阅面的共用出口)。
|
||||
# V6 形态先例同源:直入真实管线缝(_judge_seal 直调同款口径)。
|
||||
|
||||
func _smoke_extended_rows() -> void:
|
||||
@@ -1037,8 +1037,8 @@ func _smoke_extended_rows() -> void:
|
||||
o_fx.begin_battle(enc_fx)
|
||||
var fx_rows: Array = []
|
||||
o_fx.step_row.connect(func(row): fx_rows.append(row))
|
||||
o_fx._fx_enqueue_reveal()
|
||||
o_fx._flush_pending_reveals(1)
|
||||
o_fx.fx_host().enqueue_reveal()
|
||||
o_fx.fx_host().flush_pending_reveals(1)
|
||||
var paid := {}
|
||||
for r in fx_rows:
|
||||
if String(r.get("effect_id", "")) == "fx_reveal":
|
||||
@@ -1054,8 +1054,8 @@ func _smoke_extended_rows() -> void:
|
||||
for cv in o_void.minesweeper.current_board().all_cells():
|
||||
if not o_void.minesweeper.current_board().is_mine(cv):
|
||||
o_void.minesweeper.fx_reveal(cv)
|
||||
o_void._fx_enqueue_reveal()
|
||||
o_void._flush_pending_reveals(1)
|
||||
o_void.fx_host().enqueue_reveal()
|
||||
o_void.fx_host().flush_pending_reveals(1)
|
||||
var missed := {}
|
||||
for r in void_rows:
|
||||
if String(r.get("effect_id", "")) == "fx_reveal" and String(r.get("display_name", "")).contains("落空"):
|
||||
|
||||
@@ -317,7 +317,7 @@ func _rows_at(rows: Array, source_class: String, cell) -> Array:
|
||||
return out
|
||||
|
||||
|
||||
# 负向字典逐型直验(类型化兑付行协议):找真实板上各型非封锁格,直调 _grant_routed 负向路由,
|
||||
# 负向字典逐型直验(类型化兑付行协议):找真实板上各型非封锁格,直调 grant_routed 负向路由(att 归因束形态),
|
||||
# 断言每型的兑付形态(enrage/off_guard/sapped 效果行、heal 直伤行、blast 警觉行、action 行动点排队)。
|
||||
|
||||
|
||||
@@ -362,7 +362,7 @@ func _smoke_clear_reward_zero_paths() -> void:
|
||||
var snap = s._reward_economy.grant_clear()
|
||||
Kit.check("清图槽身份 = blast 型(clear_default 唯一活行 clear_blast——第二份同身份兑付)", snap != null and String(snap.cell_type) == "blast")
|
||||
rows.clear()
|
||||
var first_ok: bool = s._grant_clear_identity(snap, 1, "(清图奖励)")
|
||||
var first_ok: bool = s.reward_grant()._grant_clear_identity(snap, 1, "(清图奖励)")
|
||||
Kit.check("② 即时类首份零兑付(无存活敌)→ first_ok=false", not first_ok)
|
||||
Kit.check("② 零兑付产出 reward_ineffective 行(batch_key=clear_blast)", _has_source_row(rows, "reward_ineffective", "clear_reward", "clear_blast"))
|
||||
# ②-b 高光守卫:perfect 谓词成立但首份零兑付 → 高光行不发(不记名义值)
|
||||
@@ -377,8 +377,8 @@ func _smoke_clear_reward_zero_paths() -> void:
|
||||
s_b._perfect_hit_true = 1 # 测试通道:构造完美谓词成立(真值在正常流由封印判定累加)
|
||||
s_b._perfect_hit_false = 0
|
||||
rows_b.clear()
|
||||
s_b._grant_clear_reward(1)
|
||||
Kit.check("② _grant_clear_reward 守卫路径重走首份 → 未生效行仍在案", _has_source_row(rows_b, "reward_ineffective", "clear_reward", "clear_blast"))
|
||||
s_b.reward_grant().grant_clear_reward(1, s_b._board_from_midround_refresh, s_b.perfect_sweep_earned())
|
||||
Kit.check("② grant_clear_reward 守卫路径重走首份 → 未生效行仍在案", _has_source_row(rows_b, "reward_ineffective", "clear_reward", "clear_blast"))
|
||||
Kit.check("② 首份零兑付 → 高光行不发(不记名义值 2)", _rows_of_type(rows_b, "highlight").is_empty())
|
||||
# ③ 第二份被拒:唯一存活敌 HP ≤ 首份 aoe 量 → 首份击杀 → 第二份无存活敌(zero + 未生效行)
|
||||
var rng2 = load("res://core/rng_service.gd").new()
|
||||
@@ -392,7 +392,7 @@ func _smoke_clear_reward_zero_paths() -> void:
|
||||
s2._perfect_hit_true = 1
|
||||
s2._perfect_hit_false = 0
|
||||
rows2.clear()
|
||||
s2._grant_clear_reward(1)
|
||||
s2.reward_grant().grant_clear_reward(1, s2._board_from_midround_refresh, s2.perfect_sweep_earned())
|
||||
Kit.check("③ 首份 aoe 真实兑付(击杀唯一存活敌)", _has_source_row(rows2, "damage", "clear_reward"))
|
||||
Kit.check("③ 第二份被拒产出 reward_ineffective 行(零兑付)", _has_source_row(rows2, "reward_ineffective", "clear_reward", "clear_blast"))
|
||||
var hl2 := _rows_of_type(rows2, "highlight")
|
||||
@@ -666,7 +666,7 @@ func _smoke_seal_irreversible_quadrants() -> void:
|
||||
# ---- 试玩日志器冒烟(结算快照 JSONL + 诊断转储)----
|
||||
|
||||
|
||||
# 负向字典逐型直验(类型化兑付行协议):找真实板上各型非封锁格,直调 _grant_routed 负向路由,
|
||||
# 负向字典逐型直验(类型化兑付行协议):找真实板上各型非封锁格,直调 grant_routed 负向路由(att 归因束形态),
|
||||
# 断言每型的兑付形态(enrage/off_guard/sapped 效果行、heal 直伤行、blast 警觉行、action 行动点排队)。
|
||||
func _smoke_negative_dictionary() -> void:
|
||||
print("--- 负向字典逐型直验(sapped 在位 / heal 直伤 / blast 警觉 / action 行动点)---")
|
||||
@@ -721,7 +721,7 @@ func _smoke_negative_dictionary() -> void:
|
||||
continue
|
||||
rows.clear()
|
||||
var snap: Dictionary = settlement._reward_economy.query_full_snapshot(by_type[ctype])
|
||||
settlement._grant_routed(snap, "negative", "mine_hit", by_type[ctype], 1)
|
||||
settlement.reward_grant().grant_routed(snap, "negative", {"source_class": "mine_hit", "cell": by_type[ctype], "ri": 1})
|
||||
var eff: String = percent_map[ctype]
|
||||
Kit.check("%s 型负向 → %s 效果行(mine_hit 来源指回该格)" % [ctype, eff], _has_source_row(rows, "effect", "mine_hit") and _has_effect_id(rows, eff))
|
||||
# heal:负向 = 主角直伤(表值;不经增益状态)
|
||||
@@ -729,7 +729,7 @@ func _smoke_negative_dictionary() -> void:
|
||||
rows.clear()
|
||||
var hp_before: int = int(settlement.presentation_snapshot().hero.hp)
|
||||
var snap_h: Dictionary = settlement._reward_economy.query_full_snapshot(by_type["heal"])
|
||||
settlement._grant_routed(snap_h, "negative", "mine_hit", by_type["heal"], 1)
|
||||
settlement.reward_grant().grant_routed(snap_h, "negative", {"source_class": "mine_hit", "cell": by_type["heal"], "ri": 1})
|
||||
var hp_after: int = int(settlement.presentation_snapshot().hero.hp)
|
||||
var backlash_power: int = 0
|
||||
for row in _db.reward.rows:
|
||||
@@ -740,7 +740,7 @@ func _smoke_negative_dictionary() -> void:
|
||||
if by_type.has("blast"):
|
||||
rows.clear()
|
||||
var snap_b: Dictionary = settlement._reward_economy.query_full_snapshot(by_type["blast"])
|
||||
settlement._grant_routed(snap_b, "negative", "mine_hit", by_type["blast"], 1)
|
||||
settlement.reward_grant().grant_routed(snap_b, "negative", {"source_class": "mine_hit", "cell": by_type["blast"], "ri": 1})
|
||||
var alert_ok: bool = false
|
||||
for eff_a in settlement._status_effects.query_active("enemy"):
|
||||
if String(eff_a.effect_id) == "alert":
|
||||
@@ -749,9 +749,9 @@ func _smoke_negative_dictionary() -> void:
|
||||
# action:负向 = 下轮行动点 −1 / 正向 = +1(排队,轮开始兑现;确定性直验不依赖板面摇号)
|
||||
rows.clear()
|
||||
var d0: int = settlement.action_economy.pending_ap_delta()
|
||||
settlement._grant_routed({"reward_id": "ap_drain", "cell_type": "action", "polarity": "negative", "display_name": "迟滞", "power": 1}, "negative", "mine_hit", Vector2i(0, 0), 1)
|
||||
settlement.reward_grant().grant_routed({"reward_id": "ap_drain", "cell_type": "action", "polarity": "negative", "display_name": "迟滞", "power": 1}, "negative", {"source_class": "mine_hit", "cell": Vector2i(0, 0), "ri": 1})
|
||||
Kit.check("action 型负向 → 下轮行动点 −1(排队)", settlement.action_economy.pending_ap_delta() == d0 - 1)
|
||||
settlement._grant_routed({"reward_id": "ap_up", "cell_type": "action", "polarity": "positive", "display_name": "行动点", "power": 1}, "positive", "safe_open", Vector2i(0, 0), 1)
|
||||
settlement.reward_grant().grant_routed({"reward_id": "ap_up", "cell_type": "action", "polarity": "positive", "display_name": "行动点", "power": 1}, "positive", {"source_class": "safe_open", "cell": Vector2i(0, 0), "ri": 1})
|
||||
Kit.check("action 型正负抵消 → 排队净值归零", settlement.action_economy.pending_ap_delta() == d0)
|
||||
|
||||
|
||||
|
||||
@@ -565,76 +565,76 @@ func _smoke_feats() -> void:
|
||||
# ⑤ V3 订阅面逐件(附表 A 三列——命中面=EffectEngine dispatch 直测;账本面=counter 直读;
|
||||
# 落地行为=发射点施加面〔T1 施加段承载——本段按声明逐件钉命中与边界/反例〕)
|
||||
var orch = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(73))
|
||||
var fx = orch._fx
|
||||
var fx = orch.fx_host().engine
|
||||
# counter 落账 helper(dispatch 只返回命中不落账——施加面同构:counter_add 逐条落账本 helper 承载)
|
||||
var apply_fx := func(hits: Array) -> void:
|
||||
for h in hits:
|
||||
if String(h.get("kind", "")) == "counter_add":
|
||||
var sv_c = h.get("set", 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)
|
||||
EffectEngine.counter_add(orch.fx_host().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(orch.counters_ctx, "dig_paid", {"ap_cost": 1})
|
||||
var hits_dp: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_paid", {"ap_cost": 1})
|
||||
var refund := false
|
||||
for h in hits_dp:
|
||||
if String(h.get("kind", "")) == "grant_ap":
|
||||
refund = true
|
||||
EffectEngine.counter_add(orch.counters_ctx, "adv_twin_digs", 1)
|
||||
EffectEngine.counter_add(orch.fx_host().counters_ctx, "adv_twin_digs", 1)
|
||||
if i == 1 or i == 3:
|
||||
Kit.check("双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" % (i + 1), refund)
|
||||
else:
|
||||
Kit.check("双管镐反例:第 %d 次不返(未满 2 次)" % (i + 1), not refund)
|
||||
fx.rebuild(["geologist"], [], [])
|
||||
var geo_hits: Array = fx.dispatch(orch.counters_ctx, "dig_reveal", {"new_resource_type": true})
|
||||
var geo_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"new_resource_type": true})
|
||||
apply_fx.call(geo_hits)
|
||||
Kit.check("地质学家:首开新类型集 1 点〔专长批 V3〕", orch._hits_of(geo_hits, "counter_add").size() == 1)
|
||||
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})
|
||||
EffectEngine.counter_add(orch.fx_host().counters_ctx, "geo_types", 2) # 手动基数补齐(row1 已落账 1+2=3——满 4 种的预评估现值)
|
||||
var geo_hits2: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"new_resource_type": true})
|
||||
apply_fx.call(geo_hits2)
|
||||
Kit.check("地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕", orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.counters_ctx, "geo_types") == 0)
|
||||
var geo_hits3: Array = fx.dispatch(orch.counters_ctx, "dig_reveal", {"new_resource_type": false})
|
||||
Kit.check("地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕", orch._hits_of(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.fx_host().counters_ctx, "geo_types") == 0)
|
||||
var geo_hits3: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"new_resource_type": false})
|
||||
Kit.check("地质学家反例:同类型重复首开不叠点〔专长批 V3〕", orch._hits_of(geo_hits3, "counter_add").is_empty())
|
||||
fx.rebuild(["hard_hat"], [], [])
|
||||
apply_fx.call(fx.dispatch(orch.counters_ctx, "dig_reveal", {}))
|
||||
apply_fx.call(fx.dispatch(orch.counters_ctx, "dig_reveal", {}))
|
||||
Kit.check("安全帽:开安全格攒层 ×2〔专长批 V3〕", EffectEngine.counter(orch.counters_ctx, "helm_layers") == 2)
|
||||
var hh_hits: Array = fx.dispatch(orch.counters_ctx, "mine_hit", {"damage": 6})
|
||||
apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {}))
|
||||
apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {}))
|
||||
Kit.check("安全帽:开安全格攒层 ×2〔专长批 V3〕", EffectEngine.counter(orch.fx_host().counters_ctx, "helm_layers") == 2)
|
||||
var hh_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {"damage": 6})
|
||||
var hh_reduce := 0
|
||||
for h in hh_hits:
|
||||
if String(h.get("kind", "")) == "damage_reduce":
|
||||
hh_reduce = EffectEngine.counter(orch.counters_ctx, String(h.get("value_counter", "")))
|
||||
hh_reduce = EffectEngine.counter(orch.fx_host().counters_ctx, String(h.get("value_counter", "")))
|
||||
Kit.check("安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕", hh_reduce == 2)
|
||||
fx.rebuild(["skip_mining"], [], [])
|
||||
var sk_zero: Array = fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": true})
|
||||
var sk_zero: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"is_blank_zero": true})
|
||||
apply_fx.call(sk_zero)
|
||||
Kit.check("跳格采掘:空白 0 格攒 1 点〔专长批 V3〕", orch._hits_of(sk_zero, "counter_add").size() == 1)
|
||||
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})
|
||||
EffectEngine.counter_add(orch.fx_host().counters_ctx, "skip_pts", 1) # 手动基数补齐(已落账 1+1=2——满 3 点的预评估现值)
|
||||
var sk_full: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"is_blank_zero": true})
|
||||
apply_fx.call(sk_full)
|
||||
Kit.check("跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕", orch._hits_of(sk_full, "reveal_tiles").size() == 1)
|
||||
Kit.check("跳格采掘反例:普通开格不计点〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty())
|
||||
Kit.check("跳格采掘反例:普通开格不计点〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty())
|
||||
fx.rebuild(["blast_accounting"], [], [])
|
||||
for i in 5:
|
||||
var ba_hits: Array = fx.dispatch(orch.counters_ctx, "dig_paid", {"ap_cost": 1})
|
||||
var ba_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_paid", {"ap_cost": 1})
|
||||
for h in ba_hits:
|
||||
if String(h.get("kind", "")) == "counter_add":
|
||||
EffectEngine.counter_add(orch.counters_ctx, String(h.get("counter_id", "")), 1)
|
||||
Kit.check("爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1)
|
||||
EffectEngine.counter_add(orch.fx_host().counters_ctx, String(h.get("counter_id", "")), 1)
|
||||
Kit.check("爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "round_end", {}), "grant_gp").size() == 1)
|
||||
fx.rebuild(["frontier_miner"], [], [])
|
||||
Kit.check("边陲矿工:边缘格产出 +1·中心格反例零产出〔专长批 V3〕",
|
||||
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())
|
||||
orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"is_edge_cell": true}), "grant_material").size() == 1
|
||||
and orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"is_edge_cell": false}), "grant_material").is_empty())
|
||||
# 封印系
|
||||
fx.rebuild(["seal_echo"], [], [])
|
||||
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", {})
|
||||
apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}))
|
||||
apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}))
|
||||
var se_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "round_end", {})
|
||||
Kit.check("封印回响:标中计数≥2→轮末返 1 AP(下轮 queue)〔专长批 V3〕", orch._hits_of(se_hits, "grant_ap").size() == 1 and String((orch._hits_of(se_hits, "grant_ap")[0] as Dictionary).get("mode", "")) == "queue")
|
||||
fx.rebuild(["seal_smith"], [], [])
|
||||
var sm_remove := 0
|
||||
for i in 3:
|
||||
var sm_dispatch: Array = fx.dispatch(orch.counters_ctx, "seal_hit", {})
|
||||
var sm_dispatch: Array = fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {})
|
||||
apply_fx.call(sm_dispatch)
|
||||
for h in sm_dispatch:
|
||||
if String(h.get("kind", "")) == "remove_mines":
|
||||
@@ -642,116 +642,116 @@ func _smoke_feats() -> void:
|
||||
Kit.check("封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕", sm_remove == 1)
|
||||
fx.rebuild(["seal_stake"], [], [])
|
||||
Kit.check("封印桩:标中本轮 AC +N(round 域——round_start 清零)〔专长批 V3〕",
|
||||
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")
|
||||
orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}), "ac_bonus").size() == 1
|
||||
and String((orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}), "ac_bonus")[0] as Dictionary).get("mode", "")) == "round")
|
||||
fx.rebuild(["warning_beacon"], [], [])
|
||||
Kit.check("示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "preview_area").size() == 1)
|
||||
Kit.check("示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}), "preview_area").size() == 1)
|
||||
# 踩雷/受伤系
|
||||
fx.rebuild(["pain_memory"], [], [])
|
||||
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})
|
||||
apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {}))
|
||||
apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {}))
|
||||
var pm_hits: Array = fx.dispatch(orch.fx_host().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 = EffectEngine.counter(orch.counters_ctx, String(h.get("value_counter", "")))
|
||||
pm_bonus = EffectEngine.counter(orch.fx_host().counters_ctx, String(h.get("value_counter", "")))
|
||||
Kit.check("疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕", pm_bonus == 2)
|
||||
fx.rebuild(["fuse_wire"], [], [])
|
||||
var fw_hits: Array = fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 6})
|
||||
var fw_hits: Array = fx.dispatch(orch.fx_host().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))
|
||||
Kit.check("保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty())
|
||||
for h in fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "terrain", "amount": 6}):
|
||||
Kit.check("保险丝反例:单次 <5 不攒点不触发〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "damage_taken", {"kind": "terrain", "amount": 3}), "shield").is_empty())
|
||||
for h in fx.dispatch(orch.fx_host().counters_ctx, "damage_taken", {"kind": "terrain", "amount": 6}):
|
||||
if String(h.get("kind", "")) == "counter_add":
|
||||
EffectEngine.counter_add(orch.counters_ctx, String(h.get("counter_id", "")), int(h.get("value", 0)))
|
||||
EffectEngine.counter_add(orch.fx_host().counters_ctx, String(h.get("counter_id", "")), int(h.get("value", 0)))
|
||||
Kit.check("保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕", fw_shield == 0)
|
||||
fx.rebuild(["blood_insight"], [], [])
|
||||
Kit.check("见血开窍:踩雷揭示 1 格〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1)
|
||||
Kit.check("见血开窍:踩雷揭示 1 格〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1)
|
||||
fx.rebuild(["trap_instinct"], [], [])
|
||||
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})
|
||||
apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {}))
|
||||
var ti_hits: Array = fx.dispatch(orch.fx_host().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 = EffectEngine.counter(orch.counters_ctx, String(h.get("value_counter", "")))
|
||||
ti_val = EffectEngine.counter(orch.fx_host().counters_ctx, String(h.get("value_counter", "")))
|
||||
Kit.check("陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕", ti_val == 2)
|
||||
fx.rebuild(["chain_traps", "pain_memory"], [], [])
|
||||
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", {}))
|
||||
Kit.check("连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕", EffectEngine.counter(orch.counters_ctx, "pain_layers") == ct_before + 2)
|
||||
apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {}))
|
||||
var ct_before: int = EffectEngine.counter(orch.fx_host().counters_ctx, "pain_layers")
|
||||
apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {}))
|
||||
Kit.check("连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕", EffectEngine.counter(orch.fx_host().counters_ctx, "pain_layers") == ct_before + 2)
|
||||
fx.rebuild(["trap_burst"], [], [])
|
||||
var tb_hits: Array = fx.dispatch(orch.counters_ctx, "mine_hit", {"damage": 8})
|
||||
var tb_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {"damage": 8})
|
||||
Kit.check("陷阱爆发:全体敌雷伤×倍率直伤(blast_scaled——施加面值=damage×mult)〔专长批 V3〕",
|
||||
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)
|
||||
fx.rebuild(["blood_honey"], [], [])
|
||||
Kit.check("血蜜:踩雷回复 N 生命〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.counters_ctx, "mine_hit", {}), "heal").size() == 1)
|
||||
Kit.check("血蜜:踩雷回复 N 生命〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {}), "heal").size() == 1)
|
||||
fx.rebuild(["stone_skin"], [], [])
|
||||
var ss_hits: Array = fx.dispatch(orch.counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1})
|
||||
var ss_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1})
|
||||
Kit.check("石肤:受伤 AC+1(本场域 cap 3——达上限不再涨由累积器封顶)〔专长批 V3〕",
|
||||
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)
|
||||
fx.rebuild(["last_stand"], [], [])
|
||||
Kit.check("背水:生命≤50% 减伤·过半血反例零减伤〔专长批 V3〕",
|
||||
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())
|
||||
orch._hits_of(fx.dispatch(orch.fx_host().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.fx_host().counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 2, "hp_ratio": 80}), "damage_reduce").is_empty())
|
||||
# 跨族桥
|
||||
fx.rebuild(["breaker_strike"], [], [])
|
||||
Kit.check("破封一击:挖开带封印危险格 +1 层·无封印反例零触发〔专长批 V3〕",
|
||||
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())
|
||||
orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {"dug_under_seal": true}), "counter_add").size() == 1
|
||||
and orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {"dug_under_seal": false}), "counter_add").is_empty())
|
||||
# 节奏系
|
||||
fx.rebuild(["map_exchange"], [], [])
|
||||
var me_hits: Array = fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 4})
|
||||
var me_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_stop", {"ap_left": 4})
|
||||
apply_fx.call(me_hits)
|
||||
EffectEngine.counter_add(orch.counters_ctx, "stop_swap", 4) # value_event="ap_left" 解析归施加面(action_economy 载荷取值)——冒烟手动落账
|
||||
EffectEngine.counter_add(orch.fx_host().counters_ctx, "stop_swap", 4) # value_event="ap_left" 解析归施加面(action_economy 载荷取值)——冒烟手动落账
|
||||
Kit.check("弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕", orch._hits_of(me_hits, "counter_add").size() == 1)
|
||||
var me_atk := 0
|
||||
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", "")))
|
||||
for h in orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}), "attack_bonus"):
|
||||
me_atk = EffectEngine.counter(orch.fx_host().counters_ctx, String(h.get("value_counter", "")))
|
||||
Kit.check("弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕", me_atk == 4)
|
||||
Kit.check("弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty())
|
||||
Kit.check("弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty())
|
||||
fx.rebuild(["last_drop"], [], [])
|
||||
var ld_hits: Array = fx.dispatch(orch.counters_ctx, "round_start", {"round": 2, "ap_exhausted_last": true})
|
||||
var ld_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "round_start", {"round": 2, "ap_exhausted_last": true})
|
||||
Kit.check("一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕", orch._hits_of(ld_hits, "grant_ap").size() == 1)
|
||||
Kit.check("一滴不剩反例:上轮有余不发〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty())
|
||||
Kit.check("一滴不剩反例:上轮有余不发〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty())
|
||||
fx.rebuild(["market_close"], [], [])
|
||||
for i in 8:
|
||||
for h in fx.dispatch(orch.counters_ctx, "dig_reveal", {}):
|
||||
for h in fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {}):
|
||||
if String(h.get("kind", "")) == "counter_add":
|
||||
EffectEngine.counter_add(orch.counters_ctx, String(h.get("counter_id", "")), 1)
|
||||
Kit.check("收盘:本轮开格≥8→+1 GP〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.counters_ctx, "round_end", {}), "grant_gp").size() == 1)
|
||||
EffectEngine.counter_add(orch.fx_host().counters_ctx, String(h.get("counter_id", "")), 1)
|
||||
Kit.check("收盘:本轮开格≥8→+1 GP〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "round_end", {}), "grant_gp").size() == 1)
|
||||
# 战斗系
|
||||
fx.rebuild(["execution_scholar"], [], [])
|
||||
Kit.check("斩首学者:击杀揭示 1 格〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1)
|
||||
Kit.check("斩首学者:击杀揭示 1 格〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1)
|
||||
fx.rebuild(["executioner"], [], [])
|
||||
var ex_hits: Array = fx.dispatch(orch.counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 30, "is_boss": false})
|
||||
var ex_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 30, "is_boss": false})
|
||||
Kit.check("处刑台:敌血量≤30% 挂单位标记加伤·阈值外反例零命中〔专长批 V3〕",
|
||||
orch._hits_of(ex_hits, "target_attack_bonus").size() == 1
|
||||
and orch._hits_of(fx.dispatch(orch.counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 60, "is_boss": false}), "target_attack_bonus").is_empty())
|
||||
and orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 60, "is_boss": false}), "target_attack_bonus").is_empty())
|
||||
fx.rebuild(["field_dressing"], [], [])
|
||||
Kit.check("战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1)
|
||||
Kit.check("战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1)
|
||||
# 契约尖点
|
||||
fx.rebuild(["blood_tax"], [], [])
|
||||
Kit.check("血税:双向同局账本(踩陷阱−2max_hp/封印命中+1atk——bonus_delta 战末汇入)〔专长批 V3〕",
|
||||
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")
|
||||
orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {}), "bonus_delta").size() == 1
|
||||
and String((orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}), "bonus_delta")[0] as Dictionary).get("axis", "")) == "atk")
|
||||
# 迁入 2 件
|
||||
fx.rebuild(["venom_edge"], [], [])
|
||||
Kit.check("淬毒刃纹(迁入):克制正面(magic_vs_heavy 矩阵值>0)追加·中性面反例零触发〔专长批 V3〕",
|
||||
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())
|
||||
orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "clamp_check", {"matchup": "magic_vs_heavy"}), "modify_ticket").size() == 1
|
||||
and orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "clamp_check", {"matchup": "normal_vs_light"}), "modify_ticket").is_empty())
|
||||
fx.rebuild(["seal_burst"], [], [])
|
||||
Kit.check("封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.counters_ctx, "seal_hit", {}), "open_ticket").size() == 1)
|
||||
Kit.check("封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕", orch._hits_of(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}), "open_ticket").size() == 1)
|
||||
# 零持有
|
||||
var orch0 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(74))
|
||||
orch0._fx.rebuild([], [], [])
|
||||
orch0.fx_host().rebuild_subscriptions([], [], [])
|
||||
Kit.check("零持有订阅=零命中(relic 关着零扰动——伤害管线既有行为逐位不变)",
|
||||
orch0._fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}).is_empty()
|
||||
and orch0._fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}).is_empty()
|
||||
and orch0._fx_emit("mine_hit", {"damage": 5}).is_empty()
|
||||
and orch0._fx_emit("battle_won", {"round": 1}).is_empty())
|
||||
orch0.fx_host().fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}).is_empty()
|
||||
and orch0.fx_host().fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}).is_empty()
|
||||
and orch0.fx_host().fx_emit("mine_hit", {"damage": 5}).is_empty()
|
||||
and orch0.fx_host().fx_emit("battle_won", {"round": 1}).is_empty())
|
||||
# ⑦c 特性持有呈现段(C 区「特性」节;呈现面在 06 在册)
|
||||
var panel = load("res://presentation/views/hero_info_panel_view.gd").new()
|
||||
panel.set_feat_holdings([])
|
||||
|
||||
@@ -889,9 +889,9 @@ func _smoke_gp_escrow() -> void:
|
||||
if String(row.get("effect_id", "")) == "gp_gain" and row.get("origin_cell", []) == dig_arr and int(row.get("value", 0)) == 3:
|
||||
gain_ok = true
|
||||
Kit.check("GP 袋兑付行在案(GP 袋 +3 → 战斗内缓冲,origin 指回来源格)", gain_ok)
|
||||
Kit.check("escrow 累计缓冲 ≥3(实测 %d——战斗域中间态,不入钱包不入增益容器)" % int(settlement2.escrow), int(settlement2.escrow) >= 3)
|
||||
Kit.check("escrow 累计缓冲 ≥3(实测 %d——战斗域中间态,不入钱包不入增益容器)" % int(settlement2.reward_grant().escrow), int(settlement2.reward_grant().escrow) >= 3)
|
||||
var gp_d: int = int(scan["fin"].get("p", {}).get("gp_dropped", -999))
|
||||
Kit.check("格面兑付计入 gp_dropped(%d − escrow %d ∈ [2,4]——战斗末一次到账,随上抛即弃)" % [gp_d, int(settlement2.escrow)], gp_d - int(settlement2.escrow) >= 2 and gp_d - int(settlement2.escrow) <= 4)
|
||||
Kit.check("格面兑付计入 gp_dropped(%d − escrow %d ∈ [2,4]——战斗末一次到账,随上抛即弃)" % [gp_d, int(settlement2.reward_grant().escrow)], gp_d - int(settlement2.reward_grant().escrow) >= 2 and gp_d - int(settlement2.reward_grant().escrow) <= 4)
|
||||
# ② 破财(gp_loss)直验:兑付入口直调(weight 0 不入池——字典负向行单测通道,负向字典冒烟前例)
|
||||
var rng_l = _fresh_rng(20260921)
|
||||
var settle_l = load("res://core/battle_orchestrator.gd").new(_db, rng_l)
|
||||
@@ -899,9 +899,9 @@ func _smoke_gp_escrow() -> void:
|
||||
settle_l.battle_finished.connect(func(p): fin_l["p"] = p)
|
||||
Kit.check("破财直验战斗启动", settle_l.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999, "node_type": "boss"}))
|
||||
for i in range(3):
|
||||
settle_l._grant_routed({"reward_id": "gp_lose", "cell_type": "gp", "polarity": "negative", "display_name": "破财", "power": 2, "effect_id": "gp_loss", "stacks": 1},
|
||||
"negative", "mine_hit", Vector2i(0, 0), 1)
|
||||
Kit.check("破财 ×3 → escrow −6(负向缓冲记账,零钱包触达)", int(settle_l.escrow) == -6)
|
||||
settle_l.reward_grant().grant_routed({"reward_id": "gp_lose", "cell_type": "gp", "polarity": "negative", "display_name": "破财", "power": 2, "effect_id": "gp_loss", "stacks": 1},
|
||||
"negative", {"source_class": "mine_hit", "cell": Vector2i(0, 0), "ri": 1})
|
||||
Kit.check("破财 ×3 → escrow −6(负向缓冲记账,零钱包触达)", int(settle_l.reward_grant().escrow) == -6)
|
||||
settle_l.minesweeper.settle_batch([{"action": "stop"}])
|
||||
var gp_l: int = int(fin_l.get("p", {}).get("gp_dropped", 999))
|
||||
var boss_drop: Dictionary = _db.shop_gp_drop_row("boss")
|
||||
@@ -921,8 +921,8 @@ func _smoke_gp_escrow() -> void:
|
||||
var fin3 := {}
|
||||
b3.battle_finished.connect(func(p): fin3["p"] = p)
|
||||
for i in range(3):
|
||||
b3._grant_routed({"reward_id": "gp_lose", "cell_type": "gp", "polarity": "negative", "display_name": "破财", "power": 2, "effect_id": "gp_loss", "stacks": 1},
|
||||
"negative", "mine_hit", Vector2i(0, 0), 1)
|
||||
b3.reward_grant().grant_routed({"reward_id": "gp_lose", "cell_type": "gp", "polarity": "negative", "display_name": "破财", "power": 2, "effect_id": "gp_loss", "stacks": 1},
|
||||
"negative", {"source_class": "mine_hit", "cell": Vector2i(0, 0), "ri": 1})
|
||||
var guard3 := 12
|
||||
while runner.current_battle() == b3 and not fin3.has("p") and guard3 > 0:
|
||||
guard3 -= 1
|
||||
|
||||
@@ -673,9 +673,9 @@ func _smoke_tree_rules() -> void:
|
||||
var nc_rows: Array = [] # step_row 采集盒(lambda 捕引用容器——捕获变量赋值不出闭包)
|
||||
orch_nc.step_row.connect(func(r): nc_rows.append(r))
|
||||
# heal 负向行表值 power=6(极性字典行值——负向 power 走表值不走快照)→ halve 50% ceil=3
|
||||
orch_nc._grant_routed({"cell_type": "heal", "power": 10}, "negative",
|
||||
"mine_hit", Vector2i.ZERO, 0, "", "", 50)
|
||||
# 受理判据=惊巢反制效果行落 step_row(halve 分支先于 dispatch 发行——flat 行 _dispatch_row
|
||||
orch_nc.reward_grant().grant_routed({"cell_type": "heal", "power": 10}, "negative",
|
||||
{"source_class": "mine_hit", "cell": Vector2i.ZERO, "ri": 0, "halve_pct": 50})
|
||||
# 受理判据=惊巢反制效果行落 step_row(halve 分支先于 dispatch 发行——flat 行 _apply_flat_row 分派
|
||||
# 恒返 false,返回值不承载受理语义;行发行即分支受理)
|
||||
var nc_hit := ""
|
||||
for r in nc_rows:
|
||||
|
||||
@@ -17,9 +17,9 @@ func _init() -> void:
|
||||
|
||||
# V4-1 段类直接 new()(构造收宿主引用——五类同构)
|
||||
var oc = load("res://core/battle_orchestrator.gd").new(db, load("res://core/rng_service.gd").new())
|
||||
Kit.check_quiet(load("res://core/settlement/grant_segment.gd").new(oc) != null, "V4 GrantSegment 直接 new()〔迭代27 T2〕")
|
||||
Kit.check_quiet(load("res://core/settlement/grant_segment.gd").new(oc, oc.fx_host(), oc.reward_grant()) != null, "V4 GrantSegment 直接 new()〔迭代27 T2〕")
|
||||
Kit.check_quiet(load("res://core/settlement/aura_segment.gd").new(oc) != null, "V4 AuraSegment 直接 new()〔迭代27 T2〕")
|
||||
Kit.check_quiet(load("res://core/settlement/combat_segment.gd").new(oc) != null, "V4 CombatSegment 直接 new()〔迭代27 T2〕")
|
||||
Kit.check_quiet(load("res://core/settlement/combat_segment.gd").new(oc, oc.fx_host(), oc.reward_grant()) != null, "V4 CombatSegment 直接 new()〔迭代27 T2〕")
|
||||
Kit.check_quiet(load("res://core/settlement/decay_segment.gd").new(oc) != null, "V4 DecaySegment 直接 new()〔迭代27 T2〕")
|
||||
Kit.check_quiet(load("res://core/settlement/verdict_segment.gd").new(oc) != null, "V4 VerdictSegment 直接 new()〔迭代27 T2〕")
|
||||
|
||||
@@ -30,7 +30,7 @@ func _init() -> void:
|
||||
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": "geo_types", "value": 1}) == null
|
||||
and EffectEngine.counter(oc.counters_ctx, "geo_types") == 1, "V4 counter_add 助手单点(原五位三行组收拢后行为等价——写口经 ctx 落 battle 桶)〔迭代27 T2〕")
|
||||
and EffectEngine.counter(oc.fx_host().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()
|
||||
|
||||
@@ -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(ob1.counters_ctx, "round_start", {"round_index": 2})
|
||||
var hits_r2 = ob1.fx_host().engine.dispatch(ob1.fx_host().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(ob1b.counters_ctx, "round_start", {"round_index": 1})
|
||||
var hits_none = ob1b.fx_host().engine.dispatch(ob1b.fx_host().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(ob2.counters_ctx, "round_start", {"round_index": 1})) == 1
|
||||
and _ap_of(ob2._fx.dispatch(ob2.counters_ctx, "round_start", {"round_index": 2})) == 1,
|
||||
Kit.check_quiet(_ap_of(ob2.fx_host().engine.dispatch(ob2.fx_host().counters_ctx, "round_start", {"round_index": 1})) == 1
|
||||
and _ap_of(ob2.fx_host().engine.dispatch(ob2.fx_host().counters_ctx, "round_start", {"round_index": 2})) == 1,
|
||||
"双管装填:第 1/2 轮各命中 +1(lte 2 边界内)")
|
||||
Kit.check_quiet(_ap_of(ob2._fx.dispatch(ob2.counters_ctx, "round_start", {"round_index": 3})) == 0,
|
||||
Kit.check_quiet(_ap_of(ob2.fx_host().engine.dispatch(ob2.fx_host().counters_ctx, "round_start", {"round_index": 3})) == 0,
|
||||
"双管装填反例:第 3 轮零命中;战斗外零发放(round_start 事件域外无触发面)")
|
||||
|
||||
# ③ 阔视野带(参数读数=4 / 未装配=0——注入面消费在呈现桥 T4)
|
||||
@@ -99,8 +99,10 @@ func _init() -> void:
|
||||
var rng4c = load("res://core/rng_service.gd").new(); rng4c.set_master_seed(253)
|
||||
var ob4c = orch_s.new(db, rng4c)
|
||||
ob4c.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(db.hero.max_hp), "attunement": ["trial_pick"]})
|
||||
var ctrl4 = load("res://presentation/treasure_interact_controller.gd").new(db, null, null, null, null,
|
||||
func(): return ob4c, func(): return StubAutoInvRunner.new())
|
||||
var ctrl4 = load("res://presentation/treasure_interact_controller.gd").new({
|
||||
"db": db, "meta": null, "info_panel": null, "toast": null, "board_view": null,
|
||||
"settlement_get": func(): return ob4c, "runner_get": func(): return StubAutoInvRunner.new(),
|
||||
"grant_get": func(): return ob4c.reward_grant()})
|
||||
var proj4: Array = ctrl4.inventory_projection()
|
||||
Kit.check_quiet(proj4.size() == 1 and int(proj4[0].get("handle", 0)) == -1
|
||||
and String(proj4[0].get("item_id", "")) == "trial_pick"
|
||||
@@ -164,7 +166,7 @@ func _init() -> void:
|
||||
var h8: int = int(rec8.get("handle", -1)) # handle 取 add_item 返回(snapshot 投影无 handle 键)
|
||||
var tgt8: Vector2i = _first_closed_safe(ob8)
|
||||
Kit.check_quiet(tgt8 != Vector2i(-9, -9), "线香:夹具存在未开安全格")
|
||||
var v8: Dictionary = ob8.use_treasure(h8, tgt8)
|
||||
var v8: Dictionary = ob8.reward_grant().use_treasure(h8, tgt8)
|
||||
Kit.check_quiet(bool(v8.get("ok", false)), "线香正常:单格真视受理(cell 目标解析)")
|
||||
Kit.check_quiet(bool(ob8.treasure_inventory.query_overlays().get("mine_reveal", []).has(tgt8))
|
||||
and ob8.minesweeper.is_revealed(tgt8) == false,
|
||||
@@ -233,7 +235,7 @@ func _init() -> void:
|
||||
ob11.treasure_inventory = inv11
|
||||
var h11: int = int(rec11.get("handle", -1))
|
||||
var tgt11: Vector2i = _first_closed_safe(ob11)
|
||||
var v11: Dictionary = ob11.use_treasure(h11, tgt11)
|
||||
var v11: Dictionary = ob11.reward_grant().use_treasure(h11, tgt11)
|
||||
Kit.check_quiet(bool(v11.get("ok", false)), "天眼正常:3×3 受理(真视+safe_dig 复合)")
|
||||
var opened_safe := true
|
||||
var mines_shown := 0
|
||||
@@ -282,7 +284,7 @@ func _init() -> void:
|
||||
var h13: int = int(rec13.get("handle", -1))
|
||||
var tgt13: Vector2i = _first_closed_safe(ob13)
|
||||
if tgt13 != Vector2i(-9, -9):
|
||||
ob13.use_treasure(h13, tgt13)
|
||||
ob13.reward_grant().use_treasure(h13, tgt13)
|
||||
Kit.check_quiet(int(lg13.vision_counter.get("used", 0)) == 1,
|
||||
"计数链正常:受理成功恰 +1(信标网络解锁行为条件数据源)")
|
||||
|
||||
|
||||
Reference in new issue
Block a user