@@ -3,7 +3,7 @@
## 效果引擎施加面归 BattleFxHost、奖励授予管线归 RewardGrant(两拆件,经组件出口 fx_host()/
## reward_grant() 取件,禁链式穿透其内部);本类保留宿主职责面=状态机与生命周期、轮 facts 缓冲、
## A2 三事件订阅、五段驱动、attune 族、行发射基建(_emit_row/_assert_row_source/_fatal)、呈现投影、刷新面。
## 零 await;随机只经 rng 封闭流(encounter/behavior_roll/armor_roll/gp_drop)——同种子同输入逐位复现。
## 零 await;随机只经 rng 封闭流(_facts. encounter/behavior_roll/armor_roll/gp_drop)——同种子同输入逐位复现。
## 呈现层读取唯一出口 = presentation_snapshot()/diagnostic_snapshot(),禁链式穿透内部协作对象。
## 伤害只向 DamageController 开单(何时开单归编排、怎么算归控制器);刷新唯一通道 player_request_refresh,禁 RAP 满自动刷。
class_name BattleOrchestrator
@@ -34,6 +34,8 @@ const SOURCE_EXEMPT_TYPES := ["decay", "verdict"]
# fx_grant_gp=专长·GP 入账(钱包面而非棋盘面,行无格子可指)
const SOURCE_EXEMPT_CLASSES := [ " hero_attack " , " enemy_action " , " clear_reward " , " fx_reveal " , " fx_grant_gp " ]
var _facts : BattleFacts # 战斗事实件(battle_facts.gd——事实族 15 成员单点)
var _rounds : RoundCycle # 轮循环驱动件(settlement/round_cycle.gd——五段持有/段序/重入闸/摇号)
var db : ConfigDb
var rng : RngServiceCore
# E1 封装收口:协作对象 enemy_system/status_effects/reward_economy 私有化——跨层读取一律经 presentation_snapshot()/diagnostic_snapshot() 成文出口。
@@ -45,60 +47,43 @@ var action_economy: ActionEconomy
var _enemy_system : UnitContainer
var _status_effects : StatusContainer
var _reward_economy : RewardEconomy
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 _facts_reveals : Array [ Dictionary ] = [ ]
var _facts_seals : Array [ Dictionary ] = [ ]
var _facts_path := " "
var _settling := false
# 结算五段 Segment(段边界=审计五段,段序骨架冻结于
# SettlementTable.segment_order 语义:授予→增益/减益→战斗→衰减→判定。本类做段序编排+
# 行发射/开单原语单点——段体逻辑归 game/core/settlement/ 五类)
var _grant_seg = null # GrantSegment(①授予段——含封印判与 counter/grant 助手单点 )
var _aura_seg = null # AuraSegment(②增益/减益段)
var _combat_seg = null # CombatSegment(③战斗段)
var _decay_seg = null # DecaySegment(④衰减段)
var _verdict_seg = null # VerdictSegment(⑤判定段——返回裁定,终局早退语义归本类)
var _refresh_used_this_round := 0 # 每轮刷新计数(Active 相位合计,兜底上限 = ActionEconomyTable.refresh_cap_per_round)
var _board_seq := 0 # 板序号(本战斗第几张板——快照归因用)
var _board_from_midround_refresh := false # 装板 provenance:轮内刷新换来的图 = true(清图奖励守卫——刷新图不发清图奖励)
var _seal_consumed := { } # 兑付资格以危险格实例为键(整图;换图重置)
# 同调装配运行态(begin_battle 经 enc.attunement 注入;off=空集全部效果零扰动)
var attunement : Array [ Variant ] = [ ] # 装备中同调件 item_id 集
var _mines_hits : Dictionary [ Variant , Variant ] = { " total " : 0 , " last " : 0 , " round " : 0 } # 踩雷计数(触发条件事实面——本场累计/上轮转存/当轮累计三读数合一账)
# 转存时点=_roll_round_actions 轮末(轮内部分结算不更新 last);开战清零归 begin_battle
var _attune_safe_seen := 0 # 积点制件 battle 域安全开格计数(装配集 ap_charge_safe 行扫描;满 charge_n 自动兑付当轮 AP+1,无玩家发起入口)
# 同调装配运行态(begin_battle 经 enc 携带 attunement 键注入 facts; off=空集全部效果零扰动 )
# 转存时点=roll_round_actions 轮末(轮内部分结算不更新 last);开战清零归 begin_battle
var nest_preview_count := - 1 # 巢穴预告读数口(board_installed 命中落值;-1=未装配/未发射;呈现桥消费)
# 特性运行态(relic 持有集经 enc.feats 注入:效果行为=数据行 triggers 经
# 效果引擎分发;魔矢每战首次计数 battle 域。空集=零扰动)
var _feat_first_dig_used := false
# 加算日记读面(begin_battle 经 enc["journal"] 派生——encounter.assign 后全生命期可读;
# 主角攻击基值派生读(enc 携带 hero_atk 键受理后只读——E4 收口语义不变:表值运行期不可变,
# 派生值恒等;「读面即引用、无第二账」同款先例)
func _hero_atk ( ) - > int :
return int ( _facts . encounter . get ( " hero_atk " , int ( db . combat . hero . atk ) ) )
# 加算日记读面(begin_battle 经 enc["journal"] 派生——encounter 受理 assign 后全生命期可读;
# null=调试/单场直构面,账面消费跳过。领域拆分批成员删除后的引用承接,读面即引用、无第二账)
func _journal ( ) - > GainJournal :
return encounter . get ( " journal " , null ) if encounter . get ( " journal " , null ) is GainJournal else null
return _facts . encounter . get ( " journal " , null ) if _facts . encounter . get ( " journal " , null ) is GainJournal else null
# 同调配装件读面(begin_battle 经 enc["loadout"] 派生——充能单源化的 reset_cycle/consume_charge
# 消费面;null=直构零装配,充能面跳过)
func _attune_loadout ( ) - > AttuneLoadout :
return encounter . get ( " loadout " , null )
return _facts . encounter . get ( " loadout " , null )
# 授予件战斗域回调束(bind_battle 注入——键集封闭三键:充能消耗/真视计数/负向踩雷计数;
# 宿主侧各转 _attune_loadout/_journal/_mines_hits 单点,授予件对账本零强引用)
# 宿主侧各转 _attune_loadout/_journal/_rounds. mines_hits 单点,授予件对账本零强引用)
func _battle_hooks ( ) - > Dictionary [ Variant , Variant ] :
return {
" attune_consume " : _hook_attune_consume ,
@@ -108,8 +93,8 @@ func _battle_hooks() -> Dictionary[Variant, Variant]:
func _hook_attune_consume ( item_id : String ) - > void :
if _attune_loadout ( ) != null and treasure_inventory != null :
_attune_loadout ( ) . consume_charge ( item_id , treasure_inventory )
if _attune_loadout ( ) != null and _reward_grant . treasure_inventory != null :
_attune_loadout ( ) . consume_charge ( item_id , _reward_grant . treasure_inventory )
func _hook_vision_add ( ) - > void :
@@ -120,8 +105,8 @@ func _hook_vision_add() -> void:
# 负向 blast(雷)踩雷计数入账(触发条件事实面——正向 spark_aoe/清图奖励不计入;
# 极性判定在授予件 _flat_blast 传点,本口只记账)
func _book_negative_blast ( ) - > void :
_mines_hits [ " round " ] + = 1
_mines_hits [ " total " ] + = 1
_rounds . mines_hits [ " round " ] + = 1
_rounds . mines_hits [ " total " ] + = 1
# 效果引擎宿主侧事实单点(fx_host facts_cb 注入——round_index/battle_index/seals_count/nest_count/hp_ratio 五事实;charge_layers 归 fx_host 自答;未知事实回落 null)
@@ -136,10 +121,10 @@ func _fx_host_fact(fact: String) -> Variant:
" round_index " :
return round_index
" battle_index " :
return int ( encounter . get ( " battle_index " , 1 ) ) # 关卡域战斗序(1 基——level_runner enc 携带;开局姿态窗条件面)
return int ( _facts . encounter . get ( " battle_index " , 1 ) ) # 关卡域战斗序(1 基——level_runner enc 携带;开局姿态窗条件面)
" seals_count " :
var hits_n := 0
for fevt in _facts_seals :
for fevt in _facts . seals :
if bool ( fevt . get ( " hit " , false ) ) :
hits_n + = 1
return hits_n
@@ -151,7 +136,7 @@ func _fx_host_fact(fact: String) -> Variant:
# attune 积点读投影(RewardGrant attune_safe_seen_cb 注入面——use_treasure 拒绝文案读数;经读投影出户防组件回调宿主私有族)
func _attune_safe_seen_read ( ) - > int :
return _attune_safe_seen
return _facts . attune_safe_seen
# 组件出口(拆件后取件面——呈现与测试经此取件,禁链式穿透协作对象内部)
@@ -164,7 +149,7 @@ func reward_grant() -> RewardGrant:
func _attune_has ( item_id : String ) - > bool :
return attunement . has ( item_id )
return _facts . attunement . has ( item_id )
# 装配集只读出口(_attune_has 契约化出口,save v6 只读访问器先例。只读零副作用——
@@ -179,7 +164,7 @@ func attune_has(item_id: String) -> bool:
# kind 分发查询先例=_attune_refresh_x_total bonus_refresh。零装配=0)
func attune_param_sum ( kind : String ) - > int :
var total := 0
for iid_v in attunement :
for iid_v in _facts . attunement :
var arow : Dictionary [ Variant , Variant ] = db . crossrun . treasure_items . row_by_id ( String ( iid_v ) )
if ( arow . get ( " effect_kinds " , [ ] ) as Array ) . has ( kind ) :
total + = int ( arow . get ( " magnitude " , 0 ) )
@@ -189,7 +174,7 @@ func attune_param_sum(kind: String) -> int:
# 装配集内 ap_charge_safe 行读取(取数源=装配集行扫描;
# 首行语义=装配集内至多一件积点制〔试炼镐〕)
func _attune_charge_safe_row ( ) - > Dictionary :
for iid_v in attunement :
for iid_v in _facts . attunement :
var arow : Dictionary [ Variant , Variant ] = db . crossrun . treasure_items . row_by_id ( String ( iid_v ) )
if ( arow . get ( " effect_kinds " , [ ] ) as Array ) . has ( " ap_charge_safe " ) :
return arow
@@ -200,9 +185,9 @@ func _attune_charge_safe_row() -> Dictionary:
# ( used handle 不回收=本轮已耗充能作废,补新 handle=「回满」;差额注入防双份=level_runner
# set_attune_charges 同构)。reset ∈ {battle, level}——adventure 周期归冒险开始种子面)
func _reseed_attune_charges ( reset : String ) - > void :
if _attune_loadout ( ) == null or treasure_inventory == null :
if _attune_loadout ( ) == null or _reward_grant . treasure_inventory == null :
return # 直构零装配/库存未就位——充能回满跳过(空装配同语义)
_attune_loadout ( ) . reset_cycle ( reset , treasure_inventory ) # 写时种行/补行(按周期筛——语义逐字承原循环体)
_attune_loadout ( ) . reset_cycle ( reset , _reward_grant . treasure_inventory ) # 写时种行/补行(按周期筛——语义逐字承原循环体)
# 积点制件进度读数(自动兑付无玩家发起入口,读数口=C 区秘宝槽悬停浮窗进度行:
@@ -213,18 +198,13 @@ func attune_convert_state() -> Dictionary:
if row . is_empty ( ) or String ( row . get ( " form " , " " ) ) != " active_charged " :
return { " assembled " : false , " points " : 0 , " need " : 0 , " display_name " : " " , " item_id " : " " }
var cn := int ( row . get ( " charge_n " , 0 ) )
return { " assembled " : true , " points " : _attune_safe_seen % cn if cn > 0 else 0 , " need " : cn ,
return { " assembled " : true , " points " : _facts . attune_safe_seen % cn if cn > 0 else 0 , " need " : cn ,
" display_name " : String ( row . get ( " display_name " , " " ) ) , " item_id " : String ( row . get ( " item_id " , " " ) ) }
# 特性求值(效果行为=数据行 triggers 订阅,效果引擎 dispatch 分发;
# 流派档位质变=FeatsEngine 纯函数求值
# (档位机制本身系引擎基建非内容效果),两运行态注伤害控制器:档一 rider+1 / 档二 rider 可暴击)
var _seal_miss_seen := { } # 封印未中行去重(每安全格实例一次)
var _perfect_hit_true := 0 # 整图完美封印计数(install 重置)
var _perfect_hit_false := 0
var _perfect_forgiven := 0 # 完美保险豁免累计(单向累加自有账;契约 2 禁改写 _perfect_hit_false)
var _hero_atk := 0 # 主角攻击基值快照(encounter.hero_atk; E4 收口——表值运行期不可变)
func _init ( db_ref : ConfigDb , rng_ref : RngServiceCore ) - > void :
@@ -234,6 +214,7 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
minesweeper = MinesweeperRules . new ( db , action_economy )
_enemy_system = UnitContainer . new ( db )
_status_effects = StatusContainer . new ( db )
_facts = BattleFacts . new ( ) # 战斗事实件(受理事实/轮缓冲/封印账/溯源/条件计数——成员迁移见设计 §4)
_reward_economy = RewardEconomy . new ( db , rng )
_reward_economy . fatal_handler = _fatal # 协作件违约→本类 _fatal 封印(单点接线)
_damage = DamageController . new ( db , rng , _enemy_system , _status_effects )
@@ -252,11 +233,8 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
minesweeper . round_end_board . connect ( _on_round_end )
action_economy . round_end_stopped . connect ( _on_round_end )
# 结算五段接线(授予/战斗两段持三引用=宿主+两协作件;信号连接纪律不涉段〔段零信号连接〕)
_grant_seg = GrantSegment . new ( self , _fx_host , _reward_grant )
_aura_seg = AuraSegment . new ( self )
_combat_seg = CombatSegment . new ( self , _fx_host , _reward_grant )
_decay_seg = DecaySegment . new ( self )
_verdict_seg = VerdictSegment . new ( self )
# 结算五段接线随轮循环驱动件迁驻(round_cycle.gd——段件持有/段序/重入闸/摇号单点;构造序禁环不变)
_rounds = RoundCycle . new ( self , _fx_host , _reward_grant )
# ---- 战斗开始序列(编排专章①:门 → 增益状态 → 敌人系统 → install+roll → 行动经济)----
@@ -269,7 +247,7 @@ func begin_battle(enc: Dictionary) -> bool:
return false
if not _install_and_reset ( ) :
return false
_reset_round_facts ( ) # 首板 install 的初始展开事件不入轮 facts( resource 恒 false 的双保险)
_facts . reset_round ( ) # 首板 install 的初始展开事件不入轮 facts( resource 恒 false 的双保险)
_begin_battle_fx_state ( enc )
_begin_battle_attune_params ( enc )
_begin_battle_preview_economy ( enc )
@@ -281,14 +259,14 @@ func begin_battle(enc: Dictionary) -> bool:
if not action_economy . notify_round_start ( ) :
_fatal ( " 行动经济 首轮 notify_round_start 被拒 " )
return false
_roll_round_actions ( ) # 开轮摇号(首轮同样摇;先于 round_started,挖掘期动画位可读)
_rounds . roll_round_actions ( ) # 开轮摇号(首轮同样摇;先于 round_started,挖掘期动画位可读)
round_started . emit ( round_index )
return true
# 受理段:encounter 装载+缓冲清零+护甲合流+重载闸+开域门+遭遇骰与敌人系统 spawn 受理门。
# 受理段:_facts. encounter 装载+缓冲清零+护甲合流+重载闸+开域门+遭遇骰与敌人系统 spawn 受理门。
func _begin_battle_accept ( enc : Dictionary ) - > bool :
encounter . assign ( enc )
_facts . encounter . assign ( enc )
_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 )
@@ -299,9 +277,9 @@ func _begin_battle_accept(enc: Dictionary) -> bool:
if dice . is_empty ( ) :
_fatal ( " 敌人表 行集解析为空( %d , %d , %s ) " % [ int ( enc . level_index ) , int ( enc . node_depth ) , String ( enc . get ( " node_type " , " battle " ) ) ] )
return false
# E4 收口:加成经 encounter 显式携带(hero_atk/hero_max_hp;缺省 = 表值——冒烟/工具直呼路径)
_hero_atk = int ( enc . get ( " hero_atk " , int ( db . combat . hero . atk ) ) )
if not _enemy_system . spawn ( { " level_index " : int ( enc . level_index ) , " node_depth " : int ( enc . node_depth ) , " hero_hp " : int ( enc . hero_hp ) , " unit_count_overrides " : dice [ 0 ] , " hero_atk_override " : _hero_atk , " hero_max_hp_override " : int ( enc . get ( " hero_max_hp " , - 1 ) ) , " enemy_atk_overrides " : dice [ 1 ] , " hero_attack_type " : String ( enc . get ( " attack_type " , " " ) ) , " hero_armor_type " : String ( enc . get ( " armor_type " , " " ) ) , " row_kind " : String ( enc . get ( " node_type " , " battle " ) ) } ) : # 两根类型轴(空串=锻造配置默认值回退,行为连续)+行类维(battle/boss)
# E4 收口:加成经 _facts. encounter 显式携带(hero_atk/hero_max_hp;缺省 = 表值——冒烟/工具直呼路径;
# _hero_atk 派生读=受理后不变量,无快照成员)
if not _enemy_system . spawn ( { " level_index " : int ( enc . level_index ) , " node_depth " : int ( enc . node_depth ) , " hero_hp " : int ( enc . hero_hp ) , " unit_count_overrides " : dice [ 0 ] , " hero_atk_override " : _hero_atk ( ) , " hero_max_hp_override " : int ( enc . get ( " hero_max_hp " , - 1 ) ) , " enemy_atk_overrides " : dice [ 1 ] , " hero_attack_type " : String ( enc . get ( " attack_type " , " " ) ) , " hero_armor_type " : String ( enc . get ( " armor_type " , " " ) ) , " row_kind " : String ( enc . get ( " node_type " , " battle " ) ) } ) : # 两根类型轴(空串=锻造配置默认值回退,行为连续)+行类维(battle/boss)
_fatal ( " 敌人系统 spawn 被拒 " )
return false
return true
@@ -309,7 +287,7 @@ func _begin_battle_accept(enc: Dictionary) -> bool:
# 遭遇骰(难度曲线与Boss GDD Core Rule 8):行有 unit_count_max 时按 [unit_count, max] 均匀骰;
# 行有 atk_min 时按 [atk_min, atk_max] 骰 spawn 期整战持久值
# encounter 封闭流恰一 scope(数量骰先、atk 骰后——区间宽度为零的行不掷=「定值不骰」既有口径);
# _facts. encounter 封闭流恰一 scope(数量骰先、atk 骰后——区间宽度为零的行不掷=「定值不骰」既有口径);
# 敌人系统零随机纪律保持(收 override 即确定值)。
# 返回 [count_overrides, atk_overrides];行集为空 = 返回 []( fail 判据交受理段 _fatal)。
func _begin_battle_dice ( enc : Dictionary ) - > Array :
@@ -352,12 +330,12 @@ func _begin_battle_dice_roll(rows: Array, estream: RandomNumberGenerator, roll_a
# 引擎分发;流派档位=FeatsEngine 纯函数求值两运行态注伤害控制器。魔矢每战首次开格计数同步归零)
# 扩签运行态就位(归 fx_host)+账本挂接+桶挂引用+揭示/AC 两回调+rider 档位恒零+订阅重建。
func _begin_battle_fx_state ( enc : Dictionary ) - > void :
# 同调装配运行态注入(enc.attunement 携带;off=空集零扰动。淬毒 N 注入
# 同调装配运行态注入(enc._facts. attunement 携带;off=空集零扰动。淬毒 N 注入
# 伤害控制器、偏运 N 注入地图战斗资源——均须先于首板 install)
attunement = ( enc . get ( " attunement " , [ ] ) as Array ) . duplicate ( )
_attune_safe_seen = 0
_facts . attunement = ( enc . get ( " attunement " , [ ] ) as Array ) . duplicate ( )
_facts . attune_safe_seen = 0
_fx_host . reset_battle_state ( int ( enc . get ( " hero_armor_class " , - 1 ) ) )
_mines_hits = { " total " : 0 , " last " : 0 , " round " : 0 } # 踩雷计数开战清零(事实面生命周期同款)
_rounds . mines_hits = { " total " : 0 , " last " : 0 , " round " : 0 } # 踩雷计数开战清零(事实面生命周期同款)
var adv_bucket : Variant = enc . get ( " adv_bucket " , null )
_fx_host . mount_buckets ( adv_bucket if adv_bucket is Dictionary else { } , enc . get ( " level_counters " ) ) # 冒险桶宿主字典(直构兜底自有空字典);关卡桶 LevelRunner 每关 new 下传、非 Dictionary 即不挂
_reward_grant . bind_battle ( _battle_hooks ( ) ) # 授予件战斗域回调束(充能消耗/真视计数/踩雷计数)
@@ -368,13 +346,13 @@ func _begin_battle_fx_state(enc: Dictionary) -> void:
# 不设档位机制」):rider_tier 两注入恒零,散件单体效果=唯一形态
_damage . inject [ " rider_tier_bonus " ] = 0
_damage . inject [ " rider_tier_crit " ] = false
_feat_first_dig_used = false
_facts . feat_first_dig_used = false
# 树 rule 键消费(Fork 激活侧效果=效果引擎合成
# 订阅(rebuild 注入——round_start 首轮 AP+1 / damage_taken 减伤+1)。其余 rule 键
# =stat 面投影不入战斗(T8 表内容 provisional 口径披露))
var tree_rules : Array [ Variant ] = enc . get ( " tree_rules " , [ ] ) as Array
# 效果引擎订阅重建(装载序=feats 表序→treasure 表序→树规则,写死保种子复放——细则见 battle_fx_host.rebuild_subscriptions)
_fx_host . rebuild_subscriptions ( feat_ids , attunement , tree_rules )
_fx_host . rebuild_subscriptions ( feat_ids , _facts . attunement , tree_rules )
# 秘宝常时参数注入(装配集 kind 分发:远程镐挖距放宽/信标网络封印可达豁免;
@@ -415,19 +393,19 @@ func _begin_battle_preview_economy(enc: Dictionary) -> void:
# 板序推进+board_changed 发射+秘宝叠加层退役——时序逐字;复位职责留宿主(成员单点),不随件。
func _install_and_reset ( during_round := false ) - > bool :
var installer := BoardInstaller . new ( { " db " : db , " rng " : rng , " minesweeper " : minesweeper ,
" reward_economy " : _reward_economy , " encounter " : encounter , " fatal_cb " : _fatal } )
" reward_economy " : _reward_economy , " encounter " : _facts . encounter , " fatal_cb " : _fatal } )
if not installer . install ( during_round ) :
return false
_seal_consumed . clear ( ) # 换图新雷 = 新实例,资格自然重置(行动经济 Rule 8)
_seal_miss_seen . clear ( )
_perfect_hit_true = 0 # 整图完美计数随 install 重置
_perfect_hit_false = 0
_perfect_forgiven = 0 # 豁免累计随 install 重置(domain=battle 每战清零)
_board_from_midround_refresh = during_round # 轮内刷新装板 = 刷新图(清图奖励不发);常规换图/开战装板 = false
_board_seq + = 1
_facts . seal_consumed . clear ( ) # 换图新雷 = 新实例,资格自然重置(行动经济 Rule 8)
_facts . seal_miss_seen . clear ( )
_facts . perfect_hit_true = 0 # 整图完美计数随 install 重置
_facts . perfect_hit_false = 0
_facts . perfect_forgiven = 0 # 豁免累计随 install 重置(domain=battle 每战清零)
_facts . board_from_midround = during_round # 轮内刷新装板 = 刷新图(清图奖励不发);常规换图/开战装板 = false
_facts . board_seq + = 1
board_changed . emit ( )
if treasure_inventory != null :
treasure_inventory . retire_overlays ( ) # 叠加层随图消亡(换图/刷新图装板点;已花道具不返还)
if _reward_grant . treasure_inventory != null :
_reward_grant . treasure_inventory . retire_overlays ( ) # 叠加层随图消亡(换图/刷新图装板点;已花道具不返还)
return true
@@ -448,9 +426,9 @@ func _on_cell_revealed(evt: Dictionary) -> void:
if String ( evt . get ( " kind " , " " ) ) == " safe " :
var cs_row : Dictionary [ Variant , Variant ] = _attune_charge_safe_row ( )
if not cs_row . is_empty ( ) :
_attune_safe_seen + = 1
_facts . attune_safe_seen + = 1
var cn_b : int = int ( cs_row . get ( " charge_n " , 999 ) )
if cn_b > 0 and _attune_safe_seen % cn_b == 0 :
if cn_b > 0 and _facts . attune_safe_seen % cn_b == 0 :
action_economy . grant_ap ( int ( cs_row . get ( " magnitude " , 1 ) ) )
if bool ( evt . get ( " resource " , false ) ) :
# 身份快照随缓冲捕获(批次隔离——轮内刷新后 mapping 已换批,授予必须用缓冲期身份,
@@ -459,47 +437,20 @@ func _on_cell_revealed(evt: Dictionary) -> void:
if snap != null :
evt = evt . duplicate ( )
evt [ " reward_snapshot " ] = snap
_facts_reveals . append ( evt )
_facts . reveals . append ( evt )
func _on_seal_judged ( evt : Dictionary ) - > void :
_facts_seals . append ( evt )
_facts . seals . append ( evt )
func _on_round_end ( _payload : Dictionary ) - > void :
_facts_path = String ( _payload . get ( " path_tag " , " " ) )
_maybe_settle ( )
_facts . path = String ( _payload . get ( " path_tag " , " " ) )
_rounds . settle_if_due ( )
func _maybe_settle ( ) - > void :
if _settling or battle_result != " " :
return
if action_economy . phase != action_economy . PHASE_WAITING :
return
_settle ( )
# ---- 每轮结算序列(五段——段序骨架冻结于 SettlementTable.segment_order) ----
func _settle ( ) - > void :
_settling = true
var ri := round_index
# 段序编排(五段——段体归 game/core/settlement/,本类只做段序+行发射/开单原语单点)
_grant_seg . run ( ri ) # ① 授予段(案C 闸随段装载)
_aura_seg . run ( ri ) # ② 增益/减益段(续存实例组合行)
_combat_seg . run ( ri ) # ③ 战斗段(主角先手 → 逐敌还手 → 地图伤害账殿后〔案C〕)
_decay_seg . run ( ri ) # ④ 衰减段(恰一次恒执行含末轮——先兑付后衰减)
var verdict : String = _verdict_seg . run ( ri ) # ⑤ 判定段(防御性优先级 hero 先判)
if verdict != " continue " :
return # 终局:_settling 不回置——_battle_over 已接管
_settling = false
# ---- 授予段 helpers(极性路由)----
# 开格产出乘区(贪婪矿灯/矿脉直觉/赌徒的血签事件收集器注入):
# positive=power × (1+静态 mult%); negative=power × (1+cost%)。
# 全零收集器=原快照原样返回(零扰动)。
func _feat_yield_adjust ( snap : Dictionary , positive : bool , pos_m : int , neg_m : int ) - > Dictionary :
if positive and pos_m < = 0 :
return snap
@@ -556,57 +507,7 @@ func set_treasure_unlocked(ids: Dictionary) -> void:
# 随机消费只在换算层(本方法 + SkillLottery);单位容器收确定值(零随机纪律——armor_roll 先例同构)。
# 统一流:behavior_roll 无子流派生向量(位置交错,单点定位由事件日志承载)。
# 摇定后、兑现前死亡 → 本拍技能作废。
func _roll_round_actions ( ) - > void :
var pity_m : int = int ( db . combat . skill . pity_threshold )
# facts 组包(开轮摇号前一次成型——last_round_end 空字典首轮映射 none 唯一落点;hero 事实不进 facts,
# 容器内直读主角行)
var facts := {
" round_index " : round_index ,
" last_round_end " : " none " if action_economy . last_round_end ( ) . is_empty ( ) else String ( action_economy . last_round_end ( ) . get ( " path_tag " , " none " ) ) ,
" mines_hit_last " : int ( _mines_hits [ " last " ] ) ,
" mines_hit_total " : int ( _mines_hits [ " total " ] ) ,
}
for u in _enemy_system . query_alive ( " enemy " ) :
# Boss 半血解锁闩(迭代14④ T4——开轮摇号前逐敌检查;一次过门单向闩;解锁出一次归因明细行
# 「XX 解锁了 YY」带 skill_unlock 高光键〔type=highlight 既有行型零新增〕,呈现层弹一次性
# 技能名提示〔轻量 toast 非演出——D8 呈现面〕。门=确定性谓词,机制面零随机,摇号随机
# 消费仍在 behavior_roll 既有流——同种子逐位复现不破坏)
for sid in _enemy_system . check_trigger_unlocks ( u , facts ) :
var srow_u : Dictionary [ Variant , Variant ] = db . combat . skill . row_by_id ( sid )
_emit_row ( { " type " : " highlight " , " source_class " : " enemy_action " , " origin_cell " : Vector2i ( - 1 , - 1 ) , " highlight " : " skill_unlock " } ,
round_index , " %s 解锁了 %s " % [ String ( u . display_name ) , String ( srow_u . get ( " display_name " , sid ) ) ] ,
0 , { " skill_id " : sid } )
# enemy_hp_threshold 发射点(内容数据驱动扩展批 T1——血量阈值沿;对所有带该字段敌人逐个发射,
# 非 Boss 专属——Boss 语义留给订阅者 when 判定。pct=本次解锁条目的 trigger 行值)
var pct_v := 0
for pe in ( u . get ( " pool " , [ ] ) as Array ) :
var pe_d : Dictionary [ Variant , Variant ] = pe
if String ( pe_d . get ( " skill_id " , " " ) ) == sid and ( pe_d . get ( " trigger " , { } ) as Dictionary ) . has ( " value " ) :
pct_v = int ( ( pe_d . get ( " trigger " , { } ) as Dictionary ) . get ( " value " , 0 ) )
break
# target_attack_bonus 施加面(处刑台:敌血量阈值沿挂单位字典标记,
# 主角攻击面 _resolve_hero_attack 通用消费——fx_atk_bonus 键加算进 base_value)
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 " :
u [ " fx_atk_bonus " ] = int ( u . get ( " fx_atk_bonus " , 0 ) ) + int ( h . get ( " value " , 0 ) )
var entries : Array [ Variant ] = _enemy_system . rollable_skills ( u , facts )
if _enemy_system . basic_streak ( u ) > = pity_m :
var specials : Array [ Variant ] = _enemy_system . special_skills ( u , facts )
if not specials . is_empty ( ) :
entries = specials # 保底命中——普攻恒可摇 → 非保底路径池恒非空,此为唯一替代集合
var stream : RandomNumberGenerator = rng . open_stream ( " behavior_roll " )
_enemy_system . set_pending_action ( u , SkillLottery . draw ( entries , stream ) )
_enemy_system . tick_cooldowns ( ) # 递减在摇号读值之后——"施放后连续 N 拍不可摇中"的等价实现(单位容器 注记)
_mines_hits [ " last " ] = _mines_hits [ " round " ] # 轮次真正结束转存(轮内部分结算不更新)
_mines_hits [ " round " ] = 0
# ---- 战斗段/算术读数(段体与掷点/折扣/暴击/克制/池乘区/下限/交付管线归 damage_controller.gd
# 与 settlement/combat_segment.gd;本类只保留薄委托——_defender_ac 供结算/呈现快照消费)----
# 防守方护甲等级读数——实现归 伤害控制器 defender_ac(本委托供结算快照/呈现快照)
func _defender_ac ( unit : Dictionary ) - > int :
return _damage . defender_ac ( unit )
@@ -643,12 +544,12 @@ func _emit_settlement_snapshot(verdict: String) -> void:
buffs . append ( { " id " : eff . effect_id , " side " : " enemy " , " layers " : eff . layers , " left " : eff . remaining_settlements } )
var mines_hit := 0
var safe_n := 0
for evt in _facts_reveals :
for evt in _facts . reveals :
if evt . kind == " mine " :
mines_hit + = 1
elif evt . kind == " safe " :
safe_n + = 1
var board_stats := { " seq " : _board_seq }
var board_stats := { " seq " : _facts . board_seq }
if minesweeper . current_board ( ) != null :
board_stats [ " w " ] = minesweeper . current_board ( ) . width
board_stats [ " h " ] = minesweeper . current_board ( ) . height
@@ -656,18 +557,18 @@ func _emit_settlement_snapshot(verdict: String) -> void:
board_stats [ " seals " ] = minesweeper . seal_count ( )
settlement_snapshot . emit ( {
" kind " : " settlement " ,
" level " : int ( encounter . get ( " level_index " , 0 ) ) ,
" depth " : int ( encounter . get ( " node_depth " , 0 ) ) ,
" level " : int ( _facts . encounter . get ( " level_index " , 0 ) ) ,
" depth " : int ( _facts . encounter . get ( " node_depth " , 0 ) ) ,
" round " : round_index ,
" verdict " : verdict ,
" unit_count " : enemies . size ( ) ,
" spawn_count " : _enemy_system . all_enemies ( ) . size ( ) , # 本战斗实例化总数(含死亡残位)——unit_count=0 时区分「首轮全灭」vs「空场」
" hero " : { " hp " : int ( _enemy_system . hero_unit ( ) . get ( " hp " , 0 ) ) , " max_hp " : int ( _enemy_system . hero_unit ( ) . get ( " max_hp " , 0 ) ) , " atk " : int ( _enemy_system . hero_unit ( ) . get ( " atk " , 0 ) ) , " armor_class " : _defender_ac ( _enemy_system . hero_unit ( ) ) } ,
" economy " : { " phase " : String ( action_economy . phase ) , " ap " : int ( action_economy . ap ) , " rap " : int ( action_economy . rap ) , " refresh_used " : _refresh_used_this_round } ,
" economy " : { " phase " : String ( action_economy . phase ) , " ap " : int ( action_economy . ap ) , " rap " : int ( action_economy . rap ) , " refresh_used " : _facts . refresh_used } ,
" board " : board_stats ,
" enemies " : enemies ,
" buffs " : buffs ,
" round_facts " : { " path " : _facts_path , " safe " : safe_n , " mines " : mines_hit , " seal_events " : _facts_seals . size ( ) , } ,
" round_facts " : { " path " : _facts . path , " safe " : safe_n , " mines " : mines_hit , " seal_events " : _facts . seals . size ( ) , } ,
} )
@@ -675,7 +576,7 @@ func _emit_settlement_snapshot(verdict: String) -> void:
func _exit_distribution ( ) - > void :
var mode := " same_board "
if _facts_path == " cleared " :
if _facts . path == " cleared " :
mode = " new_board " # cleared 轮恒走 new_board(契约义务);非清图轮恒同图(等待期刷新通道已移除)
if mode == " new_board " :
_reward_economy . retire_mapping ( )
@@ -689,16 +590,16 @@ func _exit_distribution() -> void:
round_index + = 1
_fx_host . emit_round_start ( )
action_economy . notify_round_start ( )
_reset_round_facts ( )
_refresh_used_this_round = 0
_roll_round_actions ( ) # 开轮摇号——每轮恰摇一次(轮内刷新换图不重摇:refresh 不走本通道)
_facts . reset_round ( )
_facts . refresh_used = 0
_rounds . roll_round_actions ( ) # 开轮摇号——每轮恰摇一次(轮内刷新换图不重摇:refresh 不走本通道)
round_started . emit ( round_index )
# 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道
func can_refresh ( ) - > bool :
return battle_result == " " \
and _refresh_used_this_round < int ( db . run . action_economy . refresh_cap_per_round ) \
and _facts . refresh_used < int ( db . run . action_economy . refresh_cap_per_round ) \
and action_economy . phase == action_economy . PHASE_ACTIVE \
and action_economy . rap > = int ( db . run . action_economy . rap_refresh_cost )
@@ -711,7 +612,7 @@ func player_request_refresh() -> bool:
return false
if not action_economy . request_refresh ( ) :
return false
_refresh_used_this_round + = 1
_facts . refresh_used + = 1
_reward_economy . retire_mapping ( ) # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提)
minesweeper . retire_board ( true ) # 旧板退役(轮内通道)
return _install_and_reset ( true ) # 当场换图——失败即编排违例 fail-fast
@@ -737,11 +638,11 @@ func _battle_over(result: String) -> void:
_reward_economy . retire_mapping ( )
_enemy_system . despawn_all ( )
_status_effects . notify_battle_end ( )
if treasure_inventory != null :
treasure_inventory . retire_overlays ( ) # 叠加层随战斗结束清空(库存不动——关卡域跨战斗)
if _reward_grant . treasure_inventory != null :
_reward_grant . treasure_inventory . retire_overlays ( ) # 叠加层随战斗结束清空(库存不动——关卡域跨战斗)
action_economy . notify_combat_end ( ) # → Idle, RAP 清零
db . set_reload_gate ( false )
_reset_round_facts ( )
_facts . reset_round ( )
battle_finished . emit ( { " result " : result , " hero_hp " : hero_hp_left , " gp_dropped " : gp_dropped } )
@@ -749,7 +650,7 @@ func _battle_over(result: String) -> void:
# gp_dropped = gp_drop 封闭流掷定(tier 行的 roll_min..roll_max 均匀整数——每战斗末恰一 scope)
# + level_gp_bonus(level_index) + escrow。掷定行缺位 = 数据面缺行(fail 报错按 0 兑付——不哑掉)。
func _roll_gp_drop ( ) - > int :
var tier := " boss " if String ( encounter . get ( " node_type " , " " ) ) == " boss " else " normal "
var tier := " boss " if String ( _facts . encounter . get ( " node_type " , " " ) ) == " boss " else " normal "
var drops : Dictionary [ Variant , Variant ] = db . run . shop . gp_drop_row ( tier )
var roll := 0
if drops . is_empty ( ) :
@@ -757,11 +658,11 @@ func _roll_gp_drop() -> int:
else :
var stream : RandomNumberGenerator = rng . open_stream ( " gp_drop " )
roll = stream . randi_range ( int ( drops . get ( " roll_min " , 0 ) ) , int ( drops . get ( " roll_max " , 0 ) ) )
var drop := roll + db . run . shop . level_gp_bonus_at ( int ( encounter . get ( " level_index " , 0 ) ) )
var drop := roll + db . run . shop . level_gp_bonus_at ( int ( _facts . encounter . get ( " level_index " , 0 ) ) )
# 财富之道·聚(战斗掉落 GP 乘算·入账前:只乘战斗掉落两成分〔掷定+关卡加成〕,
# 店内 escrow 不乘〔范围=仅战斗掉落渠道〕;本函数返回值=结算框「GP 增量」行
# 显示源,落在入账前=显示即实得;乘数 provisional 150%)
if ( encounter . get ( " tree_rules " , [ ] ) as Array ) . has ( " economy_hoard " ) :
if ( _facts . encounter . get ( " tree_rules " , [ ] ) as Array ) . has ( " economy_hoard " ) :
drop = int ( round ( float ( drop ) * 1.5 ) )
return drop + _reward_grant . escrow
@@ -812,7 +713,7 @@ func presentation_snapshot() -> Dictionary:
" phase " : String ( action_economy . phase ) ,
" ap " : int ( action_economy . ap ) , " rap " : int ( action_economy . rap ) ,
" can_refresh " : can_refresh ( ) ,
" refresh_used " : _refresh_used_this_round , # 受理计数入呈现成文面(HUD 余量单源读此处)
" refresh_used " : _facts . refresh_used , # 受理计数入呈现成文面(HUD 余量单源读此处)
" hero " : hero ,
" enemies " : enemies ,
" hero_effects " : _status_effects . query_active ( " hero " ) . duplicate ( true ) , # 值拷贝出户(快照消费方直改不渗入状态容器)
@@ -839,12 +740,12 @@ func diagnostic_snapshot() -> Dictionary:
return {
" battle_result " : battle_result ,
" round " : round_index ,
" settling " : _settling ,
" refresh_used_round " : _refresh_used_this_round ,
" board_seq " : _board_seq ,
" facts_path " : _facts_path ,
" facts_reveals " : _facts_reveals . size ( ) ,
" facts_seals " : _facts_seals . size ( ) ,
" settling " : _rounds . is_settling ( ) ,
" refresh_used_round " : _facts . refresh_used ,
" board_seq " : _facts . board_seq ,
" facts_path " : _facts . path ,
" facts_reveals " : _facts . reveals . size ( ) ,
" facts_seals " : _facts . seals . size ( ) ,
" action_economy " : { " phase " : String ( action_economy . phase ) , " ap " : action_economy . ap , " rap " : action_economy . rap } ,
" minesweeper_board_installed " : minesweeper . current_board ( ) != null ,
" minesweeper_revealed " : minesweeper . revealed_cells ( ) . size ( ) if minesweeper . current_board ( ) != null else 0 ,
@@ -855,12 +756,6 @@ func diagnostic_snapshot() -> Dictionary:
}
func _reset_round_facts ( ) - > void :
_facts_reveals . clear ( )
_facts_seals . clear ( )
_facts_path = " "
# ---- 行协议(Core Rule:公共字段 + 行类型特有字段)----
func _emit_row ( head : Dictionary , ri : int , display : String , value : int , extra : Dictionary ) - > void :
@@ -911,6 +806,6 @@ func _fatal(msg: String) -> void:
## 完美封印谓词(两账相减:有效误标=误标总数−豁免数)
## 契约 2: _perfect_hit_false 保持纯净,豁免只累加 _perfect_forgiven,禁回退式改写与拦截式吞事件。
## 契约 2: _facts. perfect_hit_false 保持纯净,豁免只累加 _facts. perfect_forgiven,禁回退式改写与拦截式吞事件。
func perfect_sweep_earned ( ) - > bool :
return _perfect_hit_false - _perfect_forgiven < = 0 and _perfect_hit_true > = 1
return _facts . perfect_hit_false - _facts . perfect_forgiven < = 0 and _facts . perfect_hit_true > = 1