iter51-卡2
This commit is contained in:
1 parent
1cd6e2b1b0
commit
372b4fdc31
27 files changed
+1231
-356
No files matched your search
@@ -104,7 +104,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
|||||||
_reset_curio_state()
|
_reset_curio_state()
|
||||||
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
|
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
|
||||||
level_index = 1
|
level_index = 1
|
||||||
_bp_store._pending_next_level = false
|
_bp_store.set_pending_next_level(false)
|
||||||
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 缺省 legacy(off 对照零扰动)
|
_shop_mode = "pool" if String(params.get("shop_mode", "legacy")) == "pool" else "legacy" # 缺省 legacy(off 对照零扰动)
|
||||||
_treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {}
|
_treasure_unlocked = params.get("treasure_unlocked", {}) if params.get("treasure_unlocked", {}) is Dictionary else {}
|
||||||
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
|
_attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive
|
||||||
@@ -112,7 +112,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool:
|
|||||||
_refresh_bank.set_refresh_y(maxi(0, int(params.get("attune_refresh_y", 0)))) # additive(Y 冒险周期池)
|
_refresh_bank.set_refresh_y(maxi(0, int(params.get("attune_refresh_y", 0)))) # additive(Y 冒险周期池)
|
||||||
_loadout.set_attunement(_attunement) # 装配集注入(活引用——种行面)
|
_loadout.set_attunement(_attunement) # 装配集注入(活引用——种行面)
|
||||||
adventure_inventory.reset() # 冒险级库存重置(新局空库存)
|
adventure_inventory.reset() # 冒险级库存重置(新局空库存)
|
||||||
_bp_store._loadout_seeded = false # 同调行投行延至首关 bind 时点(库存就位后)
|
_bp_store.set_loadout_seeded(false) # 同调行投行延至首关 bind 时点(库存就位后)
|
||||||
adventure_counters = {} # 冒险桶清零(效果引擎 adv_ 计数器宿主——冒险开始归零)
|
adventure_counters = {} # 冒险桶清零(效果引擎 adv_ 计数器宿主——冒险开始归零)
|
||||||
# 刷新练习开局注入(refresh_stock_grant 装配即得一次性加仓:
|
# 刷新练习开局注入(refresh_stock_grant 装配即得一次性加仓:
|
||||||
# 装配集 kind 分发扫描;只入存量账、临时账不动=V8 契约;resume 不重注入〔存量随断点已在〕)
|
# 装配集 kind 分发扫描;只入存量账、临时账不动=V8 契约;resume 不重注入〔存量随断点已在〕)
|
||||||
@@ -154,7 +154,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
|||||||
summary = {}
|
summary = {}
|
||||||
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
|
levels_total = db.run.level.level_count() # 表访问收口(关数读 level 表行数)
|
||||||
level_index = int(bp.get("level_index", 1))
|
level_index = int(bp.get("level_index", 1))
|
||||||
_bp_store._pending_next_level = false
|
_bp_store.set_pending_next_level(false)
|
||||||
_reset_curio_state() # 续玩三选一态回填前置清理(池与件计数归件、停点态归本类;桶归宿主字典)
|
_reset_curio_state() # 续玩三选一态回填前置清理(池与件计数归件、停点态归本类;桶归宿主字典)
|
||||||
_bp_store.restore_curio_state(bp) # 续玩三选一态回填(池余库/获取计数/特性持有集——件侧;dual_merge/keep_on_null 策略见 BREAKPOINT_FIELDS)
|
_bp_store.restore_curio_state(bp) # 续玩三选一态回填(池余库/获取计数/特性持有集——件侧;dual_merge/keep_on_null 策略见 BREAKPOINT_FIELDS)
|
||||||
_bp_store.apply_resume_params(params) # 续玩参数面——数值账规范化与越界钳制(件侧)
|
_bp_store.apply_resume_params(params) # 续玩参数面——数值账规范化与越界钳制(件侧)
|
||||||
@@ -165,7 +165,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
|||||||
_bp_store.restore_stop_bank_fragments(bp, params) # 停点件/刷新账片段回填(件侧——凭证计数;刷新存量/Y 池缺键回退参数面)
|
_bp_store.restore_stop_bank_fragments(bp, params) # 停点件/刷新账片段回填(件侧——凭证计数;刷新存量/Y 池缺键回退参数面)
|
||||||
_tree_rules = params.get("tree_rules", {}) if params.get("tree_rules", {}) is Dictionary else {} # A/B 面规则键参数字典(续玩参数面同 start_adventure 同式,消费面 set_tree_rules 下传)
|
_tree_rules = params.get("tree_rules", {}) if params.get("tree_rules", {}) is Dictionary else {} # A/B 面规则键参数字典(续玩参数面同 start_adventure 同式,消费面 set_tree_rules 下传)
|
||||||
# 续玩计数器回填(adventure 域计数——缺键 = 旧版断点防御空集;_reset_curio_state 已清零在先——桶归宿主字典)
|
# 续玩计数器回填(adventure 域计数——缺键 = 旧版断点防御空集;_reset_curio_state 已清零在先——桶归宿主字典)
|
||||||
var bp_counters = _bp_store._bp_read("adventure_counters", bp)
|
var bp_counters = _bp_store.bp_read("adventure_counters", bp)
|
||||||
if bp_counters is Dictionary:
|
if bp_counters is Dictionary:
|
||||||
adventure_counters = (bp_counters as Dictionary).duplicate(true)
|
adventure_counters = (bp_counters as Dictionary).duplicate(true)
|
||||||
return _resume_pending_or_start(bp)
|
return _resume_pending_or_start(bp)
|
||||||
@@ -177,18 +177,18 @@ func _resume_pending_or_start(bp: Dictionary) -> bool:
|
|||||||
var bp_pending: Dictionary[Variant, Variant] = bp.get("bonus_pending", {}) as Dictionary
|
var bp_pending: Dictionary[Variant, Variant] = bp.get("bonus_pending", {}) as Dictionary
|
||||||
var bp_result: Dictionary[Variant, Variant] = bp.get("bonus_result", {}) as Dictionary
|
var bp_result: Dictionary[Variant, Variant] = bp.get("bonus_result", {}) as Dictionary
|
||||||
if not bp_pending.is_empty():
|
if not bp_pending.is_empty():
|
||||||
_bp_store._pending_next_level = true
|
_bp_store.set_pending_next_level(true)
|
||||||
_state = State.RUNNING
|
_state = State.RUNNING
|
||||||
_stop.restore_pending(bp_pending) # 选择停点直回(续玩不重跑关卡——信号经停点件中继)
|
_stop.restore_pending(bp_pending) # 选择停点直回(续玩不重跑关卡——信号经停点件中继)
|
||||||
return true
|
return true
|
||||||
if not bp_result.is_empty():
|
if not bp_result.is_empty():
|
||||||
_bp_store._pending_next_level = true
|
_bp_store.set_pending_next_level(true)
|
||||||
_stop.restore_resolved(bp_result) # 已兑付不重兑付只重发(置态+重发收编一步——信号面归停点件)
|
_stop.restore_resolved(bp_result) # 已兑付不重兑付只重发(置态+重发收编一步——信号面归停点件)
|
||||||
_state = State.RUNNING
|
_state = State.RUNNING
|
||||||
return true
|
return true
|
||||||
_bp_store._resume_bp = bp.duplicate(true)
|
_bp_store.set_resume_bp(bp.duplicate(true))
|
||||||
# 充能读侧以行集为源(键值仅校验读数——回填时点在首关 bind 行集就位后,经 _loadout_on_level_inventory)
|
# 充能读侧以行集为源(键值仅校验读数——回填时点在首关 bind 行集就位后,经 _loadout_on_level_inventory)
|
||||||
_bp_store._loadout_seeded = false
|
_bp_store.set_loadout_seeded(false)
|
||||||
_loadout.set_attunement(_attunement)
|
_loadout.set_attunement(_attunement)
|
||||||
adventure_inventory.reset()
|
adventure_inventory.reset()
|
||||||
_state = State.RUNNING
|
_state = State.RUNNING
|
||||||
@@ -198,7 +198,7 @@ func _resume_pending_or_start(bp: Dictionary) -> bool:
|
|||||||
func _reset_curio_state() -> void:
|
func _reset_curio_state() -> void:
|
||||||
_curio.reset() # 池与件获取计数(池半归件;停点态归停点件下行清理)
|
_curio.reset() # 池与件获取计数(池半归件;停点态归停点件下行清理)
|
||||||
_stop.reset_adventure_state()
|
_stop.reset_adventure_state()
|
||||||
_bp_store._loadout_seeded = false
|
_bp_store.set_loadout_seeded(false)
|
||||||
_stats.feats = []
|
_stats.feats = []
|
||||||
adventure_counters = {}
|
adventure_counters = {}
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ func effective_max_hp() -> int:
|
|||||||
|
|
||||||
# 断点承载注入(组合根冒险开始时调用;模拟/冒烟不注入 = 断点面整体静默)
|
# 断点承载注入(组合根冒险开始时调用;模拟/冒烟不注入 = 断点面整体静默)
|
||||||
func set_save_manager(sm: SaveManagerCore) -> void:
|
func set_save_manager(sm: SaveManagerCore) -> void:
|
||||||
_bp_store._save_manager = sm
|
_bp_store.bind_save_manager(sm)
|
||||||
|
|
||||||
|
|
||||||
# 当前关卡行进器读面(呈现层/冒烟用——每关新建实例,调用方须在 level_started 后重取)
|
# 当前关卡行进器读面(呈现层/冒烟用——每关新建实例,调用方须在 level_started 后重取)
|
||||||
@@ -232,13 +232,13 @@ func apply_rest(pct: int, hp: int, max_hp: int) -> int:
|
|||||||
|
|
||||||
# 大奖励确认 → 进下一关(仅兑付后受理——零自动推进的冒险级停点;三选一未兑付拒绝推进)
|
# 大奖励确认 → 进下一关(仅兑付后受理——零自动推进的冒险级停点;三选一未兑付拒绝推进)
|
||||||
func continue_to_next_level() -> bool:
|
func continue_to_next_level() -> bool:
|
||||||
if not _bp_store._pending_next_level:
|
if not _bp_store.pending_next_level():
|
||||||
push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进")
|
push_error("[冒险结构] continue_to_next_level 拒绝——无待确认的关卡推进")
|
||||||
return false
|
return false
|
||||||
if _stop.curio_stop_pending():
|
if _stop.curio_stop_pending():
|
||||||
push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)")
|
push_error("[冒险结构] continue_to_next_level 拒绝——三选一停点未兑付(零自动推进契约)")
|
||||||
return false
|
return false
|
||||||
_bp_store._pending_next_level = false
|
_bp_store.set_pending_next_level(false)
|
||||||
_stop.clear_resolved()
|
_stop.clear_resolved()
|
||||||
level_index += 1
|
level_index += 1
|
||||||
return _start_current_level()
|
return _start_current_level()
|
||||||
@@ -297,9 +297,9 @@ func _loadout_on_level_inventory() -> void:
|
|||||||
var inv = _runner.treasure_inventory
|
var inv = _runner.treasure_inventory
|
||||||
if inv == null:
|
if inv == null:
|
||||||
return
|
return
|
||||||
if not _bp_store._loadout_seeded:
|
if not _bp_store.loadout_seeded():
|
||||||
_bp_store._loadout_seeded = true
|
_bp_store.set_loadout_seeded(true)
|
||||||
var bp_charges: Variant = _bp_store._bp_read("attunement_charges", _bp_store._resume_bp, null) if not _bp_store._resume_bp.is_empty() else null
|
var bp_charges: Variant = _bp_store.bp_read("attunement_charges", _bp_store.resume_bp(), null) if not _bp_store.resume_bp().is_empty() else null
|
||||||
if bp_charges is Dictionary:
|
if bp_charges is Dictionary:
|
||||||
_loadout.restore_fragment({"attunement_charges": bp_charges}, inv) # 旧档读侧:行集为源+键值校验读数
|
_loadout.restore_fragment({"attunement_charges": bp_charges}, inv) # 旧档读侧:行集为源+键值校验读数
|
||||||
else:
|
else:
|
||||||
@@ -322,8 +322,9 @@ func _loadout_on_level_inventory() -> void:
|
|||||||
# apply_adventure_summary 逐关卡完成关次置位〕;末关 Boss 照常入账;明细随摘要 additive 键供结算面归因)
|
# apply_adventure_summary 逐关卡完成关次置位〕;末关 Boss 照常入账;明细随摘要 additive 键供结算面归因)
|
||||||
func _boss_keystone_drop(li: int) -> void:
|
func _boss_keystone_drop(li: int) -> void:
|
||||||
var first := true
|
var first := true
|
||||||
if _bp_store._save_manager != null:
|
var sm_keystone = _bp_store.save_manager()
|
||||||
first = not _bp_store._save_manager.meta_state.career_state().boss_first_kill.has(str(li))
|
if sm_keystone != null:
|
||||||
|
first = not sm_keystone.meta_state.career_state().boss_first_kill.has(str(li))
|
||||||
var row: Dictionary[Variant, Variant] = db.crossrun.meta.boss_keystone_row(li)
|
var row: Dictionary[Variant, Variant] = db.crossrun.meta.boss_keystone_row(li)
|
||||||
var amount: int = int(row.get("first_kill", 0)) if first else int(row.get("repeat_kill", 0))
|
var amount: int = int(row.get("first_kill", 0)) if first else int(row.get("repeat_kill", 0))
|
||||||
_journal.record_keystone_drop({"level": li, "first": first, "amount": amount}) # 逐杀明细+累计单写原语
|
_journal.record_keystone_drop({"level": li, "first": first, "amount": amount}) # 逐杀明细+累计单写原语
|
||||||
@@ -373,9 +374,9 @@ func _start_current_level() -> bool:
|
|||||||
# 断点续玩分支:图由主种子+参数档重生成同图(产图与计数器号位无关,停点零重抽语义随之消解),
|
# 断点续玩分支:图由主种子+参数档重生成同图(产图与计数器号位无关,停点零重抽语义随之消解),
|
||||||
# resume_state 带停点句柄与已到序——重建到停点后经 present_resumed_choice 呈现(信号顺序契约同入口)。
|
# resume_state 带停点句柄与已到序——重建到停点后经 present_resumed_choice 呈现(信号顺序契约同入口)。
|
||||||
var resume_state: Dictionary[Variant, Variant] = {}
|
var resume_state: Dictionary[Variant, Variant] = {}
|
||||||
if not _bp_store._resume_bp.is_empty():
|
if not _bp_store.resume_bp().is_empty():
|
||||||
resume_state = {"node_id": int(_bp_store._bp_read("node_id", _bp_store._resume_bp, -1)),
|
resume_state = {"node_id": int(_bp_store.bp_read("node_id", _bp_store.resume_bp(), -1)),
|
||||||
"visited_ids": _bp_store._bp_read("visited_ids", _bp_store._resume_bp, []) as Array}
|
"visited_ids": _bp_store.bp_read("visited_ids", _bp_store.resume_bp(), []) as Array}
|
||||||
# 接口①开始关卡:生命经主角账共享交接(账面当前值即开局生命——冒险开始置满 / 大奖励回满已写好);
|
# 接口①开始关卡:生命经主角账共享交接(账面当前值即开局生命——冒险开始置满 / 大奖励回满已写好);
|
||||||
# 主角账三件与冒险桶注入(构造注入 ctx——拆件批 T3 收口形态)。
|
# 主角账三件与冒险桶注入(构造注入 ctx——拆件批 T3 收口形态)。
|
||||||
# defer_entry=true 起每关(含首关)——先 emit level_started(呈现层在
|
# defer_entry=true 起每关(含首关)——先 emit level_started(呈现层在
|
||||||
@@ -385,7 +386,7 @@ func _start_current_level() -> bool:
|
|||||||
if ok:
|
if ok:
|
||||||
_emit_level_started()
|
_emit_level_started()
|
||||||
_present_level_entry(resume_state)
|
_present_level_entry(resume_state)
|
||||||
_bp_store._resume_bp = {}
|
_bp_store.clear_resume_bp()
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
|
|
||||||
@@ -409,7 +410,7 @@ func _present_level_entry(resume_state: Dictionary) -> void:
|
|||||||
if resume_state.is_empty() or int(resume_state.get("node_id", -1)) < 0:
|
if resume_state.is_empty() or int(resume_state.get("node_id", -1)) < 0:
|
||||||
_runner.present_entry_choice()
|
_runner.present_entry_choice()
|
||||||
else:
|
else:
|
||||||
_runner.restore_treasure_items(_bp_store._bp_read("aux_items", _bp_store._resume_bp, []) as Array)
|
_runner.restore_treasure_items(_bp_store.bp_read("aux_items", _bp_store.resume_bp(), []) as Array)
|
||||||
_runner.present_resumed_choice()
|
_runner.present_resumed_choice()
|
||||||
|
|
||||||
|
|
||||||
@@ -418,7 +419,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
|||||||
if state != "running":
|
if state != "running":
|
||||||
push_error("[冒险结构] 重复关卡结束宣布拒绝——state=%s(账本与摘要零变化)" % state)
|
push_error("[冒险结构] 重复关卡结束宣布拒绝——state=%s(账本与摘要零变化)" % state)
|
||||||
return
|
return
|
||||||
_bp_store._last_level_payload = payload.duplicate(true)
|
_bp_store.set_last_level_payload(payload.duplicate(true))
|
||||||
var result := String(payload.get("result", ""))
|
var result := String(payload.get("result", ""))
|
||||||
if result == "defeat":
|
if result == "defeat":
|
||||||
_stats.hero_hp = 0 # 倒下记 0(生命账本战后回写口径)
|
_stats.hero_hp = 0 # 倒下记 0(生命账本战后回写口径)
|
||||||
@@ -427,7 +428,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
|||||||
if result != "victory":
|
if result != "victory":
|
||||||
push_error("[冒险结构] 关卡结束结果出域: %s" % result)
|
push_error("[冒险结构] 关卡结束结果出域: %s" % result)
|
||||||
return
|
return
|
||||||
_bp_store._last_treasure_cleared = int(payload.get("aux_cleared", 0)) # additive(末关 summary 取数面)
|
_bp_store.set_last_treasure_cleared(int(payload.get("aux_cleared", 0))) # additive(末关 summary 取数面)
|
||||||
# Boss 钥石逐杀掉落(击杀掉落渠道,末关照常入账;首杀/复杀读 meta.tres boss_keystone)
|
# Boss 钥石逐杀掉落(击杀掉落渠道,末关照常入账;首杀/复杀读 meta.tres boss_keystone)
|
||||||
_boss_keystone_drop(level_index)
|
_boss_keystone_drop(level_index)
|
||||||
if level_index < levels_total:
|
if level_index < levels_total:
|
||||||
@@ -435,7 +436,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
|||||||
#(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位);
|
#(信号同步派发——处理器内立即确认时 _pending_next_level 必须已就位);
|
||||||
# 停点件内部分回血先行 → 加权洗牌掷 3 → 三选一停点(顺序即契约——回血按当刻账本与上限结算,
|
# 停点件内部分回血先行 → 加权洗牌掷 3 → 三选一停点(顺序即契约——回血按当刻账本与上限结算,
|
||||||
# 选择停点零自动推进)。载荷_base 随附钱包读数(停点件零钱包依赖——GP 件入账回调仍走 grant_curio_gp)
|
# 选择停点零自动推进)。载荷_base 随附钱包读数(停点件零钱包依赖——GP 件入账回调仍走 grant_curio_gp)
|
||||||
_bp_store._pending_next_level = true
|
_bp_store.set_pending_next_level(true)
|
||||||
var stop_base: Dictionary[Variant, Variant] = payload.duplicate(true)
|
var stop_base: Dictionary[Variant, Variant] = payload.duplicate(true)
|
||||||
stop_base["gp"] = _wallet.gp
|
stop_base["gp"] = _wallet.gp
|
||||||
_stop.open_level_clear(stop_base)
|
_stop.open_level_clear(stop_base)
|
||||||
@@ -453,7 +454,7 @@ func _settle(result: String) -> void:
|
|||||||
"stopped_at": 0 if result == "completed" else level_index,
|
"stopped_at": 0 if result == "completed" else level_index,
|
||||||
"keystone_total": _journal.keystone_gained(),
|
"keystone_total": _journal.keystone_gained(),
|
||||||
"master_seed": master_seed,
|
"master_seed": master_seed,
|
||||||
"aux_cleared_last": _bp_store._last_treasure_cleared, # additive 扩签(末关清空反馈;非末关走 bonus 载荷)
|
"aux_cleared_last": _bp_store.last_treasure_cleared(), # additive 扩签(末关清空反馈;非末关走 bonus 载荷)
|
||||||
"keystone_drops": _journal.keystone_drop_log(), # additive 扩签(逐杀掉落明细;settle 入账明细/复盘归因数据源)
|
"keystone_drops": _journal.keystone_drop_log(), # additive 扩签(逐杀掉落明细;settle 入账明细/复盘归因数据源)
|
||||||
"vision_used_delta": _journal.vision_used(), # additive 扩签(真视使用本局计数;settle 聚合入档)
|
"vision_used_delta": _journal.vision_used(), # additive 扩签(真视使用本局计数;settle 聚合入档)
|
||||||
"pool_stock": _pool_stock_snapshot(), # additive 扩签(终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面)
|
"pool_stock": _pool_stock_snapshot(), # additive 扩签(终局池读数:清账前快照,商店买走 vs 奖励领走 vs 枯竭的终局面)
|
||||||
@@ -461,19 +462,20 @@ func _settle(result: String) -> void:
|
|||||||
}
|
}
|
||||||
# 败退明细(数值验证工具取数面——tools/balance_verifier.py 止步节点分布数据源;
|
# 败退明细(数值验证工具取数面——tools/balance_verifier.py 止步节点分布数据源;
|
||||||
# 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知)
|
# 仅失败局附加,键缺省 = 未败退,既有五件事消费方零感知)
|
||||||
if result == "failed" and not _bp_store._last_level_payload.is_empty():
|
if result == "failed" and not _bp_store.last_level_payload().is_empty():
|
||||||
summary["defeat"] = {
|
summary["defeat"] = {
|
||||||
"level_index": level_index,
|
"level_index": level_index,
|
||||||
"node_id": int(_bp_store._last_level_payload.get("node_id", -1)),
|
"node_id": int(_bp_store.last_level_payload().get("node_id", -1)),
|
||||||
"node_type": String(_bp_store._last_level_payload.get("node_type", "")),
|
"node_type": String(_bp_store.last_level_payload().get("node_type", "")),
|
||||||
}
|
}
|
||||||
if _runner != null:
|
if _runner != null:
|
||||||
summary["gp_final"] = int(_runner.gp) # gp_final 清账前快照(终局面板取数源=runner 钱包单点)
|
summary["gp_final"] = int(_runner.gp) # gp_final 清账前快照(终局面板取数源=runner 钱包单点)
|
||||||
_runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现)
|
_runner.close_wallet() # GP 冒险收束清零(三结局都清——经转发发 wallet_changed "clear" 供日志/呈现)
|
||||||
_reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化)
|
_reset_curio_state() # 三选一停点态收束清零(结算受理只算一次语义——重复宣布零变化)
|
||||||
if _bp_store._save_manager != null:
|
var sm_end = _bp_store.save_manager()
|
||||||
_bp_store._save_manager.clear_breakpoint() # 三结局清断点(退出冒险=放弃流程+清除断点)
|
if sm_end != null:
|
||||||
_bp_store._save_manager.write_to_disk()
|
sm_end.clear_breakpoint() # 三结局清断点(退出冒险=放弃流程+清除断点)
|
||||||
|
sm_end.write_to_disk()
|
||||||
_state = State.SETTLING
|
_state = State.SETTLING
|
||||||
adventure_finished.emit(summary)
|
adventure_finished.emit(summary)
|
||||||
_state = State.IDLE
|
_state = State.IDLE
|
||||||
|
|||||||
@@ -89,6 +89,22 @@ func counter_add(counter_id: String, value: int, set_form: bool) -> void:
|
|||||||
EffectEngine.counter_add(counters_ctx, counter_id, value, set_form)
|
EffectEngine.counter_add(counters_ctx, counter_id, value, set_form)
|
||||||
|
|
||||||
|
|
||||||
|
# 新资源类型首见登记(授予段 dig_reveal 地质学家判定面——返回 true=首见并置位;空串短路零登记)
|
||||||
|
func mark_new_resource_type(ctype: String) -> bool:
|
||||||
|
if ctype == "":
|
||||||
|
return false
|
||||||
|
if _fx_new_resource_types.has(ctype):
|
||||||
|
return false
|
||||||
|
_fx_new_resource_types[ctype] = true
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
# bonus_delta 轴累积(授予段血税双向施加面——axis 在册四轴才累积,轴外静默)
|
||||||
|
func add_bonus_delta(axis: String, delta: int) -> void:
|
||||||
|
if _fx_bonus_delta.has(axis):
|
||||||
|
_fx_bonus_delta[axis] += delta
|
||||||
|
|
||||||
|
|
||||||
# 业务发射事实面(闭面四键——三 when 事实 {dig_streak 轮内槽/hp_ratio 主角
|
# 业务发射事实面(闭面四键——三 when 事实 {dig_streak 轮内槽/hp_ratio 主角
|
||||||
# 血量比例算式逐字承 damage_controller 原式}∪ 两 rider 运行态读数 {facts_inject 现值};
|
# 血量比例算式逐字承 damage_controller 原式}∪ 两 rider 运行态读数 {facts_inject 现值};
|
||||||
# 其余事实一律 null——battle_index/round_index/seals_count/nest_count 禁进,扩面=行为变化)
|
# 其余事实一律 null——battle_index/round_index/seals_count/nest_count 禁进,扩面=行为变化)
|
||||||
|
|||||||
@@ -65,12 +65,6 @@ var nest_preview_count := -1 # 巢穴预告读数口(board_insta
|
|||||||
# 效果引擎分发;魔矢每战首次计数 battle 域。空集=零扰动)
|
# 效果引擎分发;魔矢每战首次计数 battle 域。空集=零扰动)
|
||||||
|
|
||||||
|
|
||||||
# 主角攻击基值派生读(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 后全生命期可读;
|
# 加算日记读面(begin_battle 经 enc["journal"] 派生——encounter 受理 assign 后全生命期可读;
|
||||||
# null=调试/单场直构面,账面消费跳过。领域拆分批成员删除后的引用承接,读面即引用、无第二账)
|
# null=调试/单场直构面,账面消费跳过。领域拆分批成员删除后的引用承接,读面即引用、无第二账)
|
||||||
func _journal() -> GainJournal:
|
func _journal() -> GainJournal:
|
||||||
@@ -218,7 +212,7 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
|
|||||||
_facts = BattleFacts.new() # 战斗事实件(受理事实/轮缓冲/封印账/溯源/条件计数——成员迁移见设计 §4)
|
_facts = BattleFacts.new() # 战斗事实件(受理事实/轮缓冲/封印账/溯源/条件计数——成员迁移见设计 §4)
|
||||||
_reward_economy = RewardEconomy.new(db, rng)
|
_reward_economy = RewardEconomy.new(db, rng)
|
||||||
_reward_economy.fatal_handler = _fatal # 协作件违约→本类 _fatal 封印(单点接线)
|
_reward_economy.fatal_handler = _fatal # 协作件违约→本类 _fatal 封印(单点接线)
|
||||||
_damage = DamageController.new(db, rng, _enemy_system, _status_effects)
|
_damage = DamageController.new(db, rng, _enemy_system, _status_effects, _facts)
|
||||||
_damage.emit_row_cb = _emit_row # 行协议归属本类(9 类行协议 + 来源断言不外泄)
|
_damage.emit_row_cb = _emit_row # 行协议归属本类(9 类行协议 + 来源断言不外泄)
|
||||||
_fx_host = BattleFxHost.new({"db": db, "rng": rng, "minesweeper": minesweeper, "action_economy": action_economy,
|
_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})
|
"damage": _damage, "emit_cb": _emit_row, "facts_cb": _fx_host_fact})
|
||||||
@@ -232,9 +226,19 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
|
|||||||
minesweeper.seal_judged.connect(_on_seal_judged)
|
minesweeper.seal_judged.connect(_on_seal_judged)
|
||||||
minesweeper.round_end_board.connect(_on_round_end)
|
minesweeper.round_end_board.connect(_on_round_end)
|
||||||
action_economy.round_end_stopped.connect(_on_round_end)
|
action_economy.round_end_stopped.connect(_on_round_end)
|
||||||
# 结算五段接线(授予/战斗两段持三引用=宿主+两协作件;信号连接纪律不涉段〔段零信号连接〕)
|
# 结算五段接线(引用束注入——BoardInstaller 先例 architecture.md:124:六子对象+既有公开协作件
|
||||||
# 结算五段接线随轮循环驱动件迁驻(round_cycle.gd——段件持有/段序/重入闸/摇号单点;构造序禁环不变)
|
# 引用+两公开读数引用+七编排器方法回调+两公开读投影,20 键封闭束;段类各取所需,
|
||||||
_rounds = RoundCycle.new(self, _fx_host, _reward_grant)
|
# 段类对编排器零反向引用——跨件单向接线,注入 Callable 亦属接线面)
|
||||||
|
var seg_bundle: Dictionary[Variant, Variant] = {
|
||||||
|
"facts": _facts, "units": _enemy_system, "damage": _damage, "status_effects": _status_effects,
|
||||||
|
"reward_economy": _reward_economy, "reward_grant": _reward_grant, "fx_host": _fx_host,
|
||||||
|
"minesweeper": minesweeper, "db": db, "action_economy": action_economy, "rng": rng,
|
||||||
|
"emit_row_cb": _emit_row, "fatal_cb": _fatal, "install_cb": _install_and_reset,
|
||||||
|
"battle_over_cb": _battle_over, "exit_distribution_cb": _exit_distribution,
|
||||||
|
"emit_snapshot_cb": _emit_settlement_snapshot, "emit_composition_cb": _emit_composition_effects,
|
||||||
|
"attune_param_sum_cb": attune_param_sum, "perfect_sweep_cb": perfect_sweep_earned,
|
||||||
|
}
|
||||||
|
_rounds = RoundCycle.new(seg_bundle)
|
||||||
|
|
||||||
|
|
||||||
# ---- 战斗开始序列(编排专章①:门 → 增益状态 → 敌人系统 → install+roll → 行动经济)----
|
# ---- 战斗开始序列(编排专章①:门 → 增益状态 → 敌人系统 → install+roll → 行动经济)----
|
||||||
@@ -259,7 +263,7 @@ func begin_battle(enc: Dictionary) -> bool:
|
|||||||
if not action_economy.notify_round_start():
|
if not action_economy.notify_round_start():
|
||||||
_fatal("行动经济 首轮 notify_round_start 被拒")
|
_fatal("行动经济 首轮 notify_round_start 被拒")
|
||||||
return false
|
return false
|
||||||
_rounds.roll_round_actions() # 开轮摇号(首轮同样摇;先于 round_started,挖掘期动画位可读)
|
_rounds.roll_round_actions(round_index) # 开轮摇号(首轮同样摇;先于 round_started,挖掘期动画位可读)
|
||||||
round_started.emit(round_index)
|
round_started.emit(round_index)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -278,8 +282,8 @@ func _begin_battle_accept(enc: Dictionary) -> bool:
|
|||||||
_fatal("敌人表 行集解析为空(%d,%d,%s)" % [int(enc.level_index), int(enc.node_depth), String(enc.get("node_type", "battle"))])
|
_fatal("敌人表 行集解析为空(%d,%d,%s)" % [int(enc.level_index), int(enc.node_depth), String(enc.get("node_type", "battle"))])
|
||||||
return false
|
return false
|
||||||
# E4 收口:加成经 _facts.encounter 显式携带(hero_atk/hero_max_hp;缺省 = 表值——冒烟/工具直呼路径;
|
# E4 收口:加成经 _facts.encounter 显式携带(hero_atk/hero_max_hp;缺省 = 表值——冒烟/工具直呼路径;
|
||||||
# _hero_atk 派生读=受理后不变量,无快照成员)
|
# _damage.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)
|
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": _damage.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 被拒")
|
_fatal("敌人系统 spawn 被拒")
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
@@ -444,54 +448,7 @@ func _on_seal_judged(evt: Dictionary) -> void:
|
|||||||
|
|
||||||
func _on_round_end(_payload: Dictionary) -> void:
|
func _on_round_end(_payload: Dictionary) -> void:
|
||||||
_facts.path = String(_payload.get("path_tag", ""))
|
_facts.path = String(_payload.get("path_tag", ""))
|
||||||
_rounds.settle_if_due()
|
_rounds.settle_if_due(battle_result, round_index)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func _feat_yield_adjust(snap: Dictionary, positive: bool, pos_m: int, neg_m: int) -> Dictionary:
|
|
||||||
if positive and pos_m <= 0:
|
|
||||||
return snap
|
|
||||||
if not positive and neg_m <= 0:
|
|
||||||
return snap
|
|
||||||
var out: Dictionary[Variant, Variant] = snap.duplicate()
|
|
||||||
var power := int(out.get("power", int(_reward_economy.polarity_row(String(out.get("cell_type", "")), "positive" if positive else "negative").power)))
|
|
||||||
var pct := pos_m if positive else neg_m
|
|
||||||
out["power"] = maxi(1, int(round(float(power) * (1.0 + float(pct) / 100.0))))
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
# 伤害型变异过滤(terrain_kind fact 取数面——variation 表 effect=flat_damage 判据;
|
|
||||||
# 🔴 R4 甲永不对冲边界=仅限伤害型,信息型(hide_number 族)永不出现在本集)
|
|
||||||
func _harmful_variants(vids: Array) -> Array:
|
|
||||||
var out: Array[Variant] = []
|
|
||||||
for vid_v in vids:
|
|
||||||
var vid := String(vid_v)
|
|
||||||
var vrow: Dictionary[Variant, Variant] = db.combat.variation.row_by_id(vid)
|
|
||||||
if String(vrow.get("effect", "")) == "flat_damage":
|
|
||||||
out.append(vid)
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
# 邻接伤害型变异收集(terrain_kind fact 取数面——seal_hit 发射点用:封印命中相邻八格的伤害型变异集;
|
|
||||||
# 🔴 R4 甲永不对冲边界=仅限伤害型,信息型〔hide_number 族〕不入集)
|
|
||||||
func _harmful_adjacent(cell: Vector2i) -> Array:
|
|
||||||
var out: Array[Variant] = []
|
|
||||||
var board: BoardData = minesweeper.current_board()
|
|
||||||
if board == null:
|
|
||||||
return out
|
|
||||||
for n in board.neighbors8(cell):
|
|
||||||
out.append_array(_harmful_variants(board.variant_ids(n)))
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
# 效果命中过滤(按效果类型取命中子集——发射点分桶施加面)
|
|
||||||
func _hits_of(hits: Array, kind: String) -> Array:
|
|
||||||
var out: Array[Variant] = []
|
|
||||||
for h in hits:
|
|
||||||
if String((h as Dictionary).get("kind", "")) == kind:
|
|
||||||
out.append(h)
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
# aux 解锁门控转发(LevelRunner 注入;E1/E3 封装:外部经公有面,禁直读 _reward_economy)
|
# aux 解锁门控转发(LevelRunner 注入;E1/E3 封装:外部经公有面,禁直读 _reward_economy)
|
||||||
@@ -586,13 +543,13 @@ func _exit_distribution() -> void:
|
|||||||
action_economy.notify_round_start()
|
action_economy.notify_round_start()
|
||||||
_facts.reset_round()
|
_facts.reset_round()
|
||||||
_facts.refresh_used = 0
|
_facts.refresh_used = 0
|
||||||
_rounds.roll_round_actions() # 开轮摇号——每轮恰摇一次(轮内刷新换图不重摇:refresh 不走本通道)
|
_rounds.roll_round_actions(round_index) # 开轮摇号——每轮恰摇一次(轮内刷新换图不重摇:refresh 不走本通道)
|
||||||
round_started.emit(round_index)
|
round_started.emit(round_index)
|
||||||
|
|
||||||
|
|
||||||
# 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道(折价成本同源:判据归 BattleRefreshFlow)
|
# 呈现层查询面(HUD 刷新按钮可用性)——挖掘期唯一通道(折价成本同源:判据归 BattleRefreshFlow)
|
||||||
func can_refresh() -> bool:
|
func can_refresh() -> bool:
|
||||||
return BattleRefreshFlow.can_refresh(self)
|
return _rounds.refresh_flow().can_refresh(battle_result)
|
||||||
|
|
||||||
|
|
||||||
# 玩家经 HUD框架发起、战斗结算 代理调用(禁止 RAP 满自动刷)。
|
# 玩家经 HUD框架发起、战斗结算 代理调用(禁止 RAP 满自动刷)。
|
||||||
@@ -600,7 +557,7 @@ func can_refresh() -> bool:
|
|||||||
# 非挖掘期请求一律拒绝——非清图轮恒同图。刷新门/折价扣费/换图/成功事实归 BattleRefreshFlow
|
# 非挖掘期请求一律拒绝——非清图轮恒同图。刷新门/折价扣费/换图/成功事实归 BattleRefreshFlow
|
||||||
# (迭代 46 卡A T3 拆出——编排器入口与唯一通道不变,C10 结构预算只降不升)。
|
# (迭代 46 卡A T3 拆出——编排器入口与唯一通道不变,C10 结构预算只降不升)。
|
||||||
func player_request_refresh() -> bool:
|
func player_request_refresh() -> bool:
|
||||||
return BattleRefreshFlow.execute(self)
|
return _rounds.refresh_flow().execute(battle_result, round_index)
|
||||||
|
|
||||||
|
|
||||||
func _battle_over(result: String) -> void:
|
func _battle_over(result: String) -> void:
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ func restore_curio_state(bp: Dictionary) -> void:
|
|||||||
_curio.taken[String(id)] = maxi(0, int(bp_taken.get(id, 0))) # 身份维键(relic:<feat_id>)随快照还原
|
_curio.taken[String(id)] = maxi(0, int(bp_taken.get(id, 0))) # 身份维键(relic:<feat_id>)随快照还原
|
||||||
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
|
# 令牌池余库还原(TP0 v2.2——快照缺键 = 旧版断点,保持满库重建态:旧档只读一版周期的防御口径;
|
||||||
# relic 令牌 feat_id 随还原)
|
# relic 令牌 feat_id 随还原)
|
||||||
var bp_deck = _bp_read("bonus_deck", bp)
|
var bp_deck = bp_read("bonus_deck", bp)
|
||||||
if bp_deck is Array:
|
if bp_deck is Array:
|
||||||
var restored: Array[Variant] = []
|
var restored: Array[Variant] = []
|
||||||
for t in (bp_deck as Array):
|
for t in (bp_deck as Array):
|
||||||
@@ -192,8 +192,9 @@ func restore_stop_bank_fragments(bp: Dictionary, params: Dictionary) -> void:
|
|||||||
|
|
||||||
|
|
||||||
# 读侧单点(BREAKPOINT_FIELDS 策略标签分发;未登记键 fail-fast 不静默按 plain 兜)。
|
# 读侧单点(BREAKPOINT_FIELDS 策略标签分发;未登记键 fail-fast 不静默按 plain 兜)。
|
||||||
# 二次消费面(_start_current_level 经 _resume_bp 取 node_id/visited_ids/aux_items)同走此口。
|
# 二次消费面(_start_current_level 经 _resume_bp 取 node_id/visited_ids/aux_items)同走此口;
|
||||||
func _bp_read(key: String, bp: Dictionary, fallback: Variant = null) -> Variant:
|
# 公开化(5.1 封装修复——宿主读侧同口,_bp_read 更名非并存)
|
||||||
|
func bp_read(key: String, bp: Dictionary, fallback: Variant = null) -> Variant:
|
||||||
if not BREAKPOINT_FIELDS.has(key):
|
if not BREAKPOINT_FIELDS.has(key):
|
||||||
push_error("[冒险结构] 断点字段未登记(读侧): %s" % key)
|
push_error("[冒险结构] 断点字段未登记(读侧): %s" % key)
|
||||||
var v: Variant = bp.get(key, fallback)
|
var v: Variant = bp.get(key, fallback)
|
||||||
@@ -216,3 +217,60 @@ func _bp_read_int(key: String, bp: Dictionary, fallback: int, params: Dictionary
|
|||||||
return maxi(0, int(bp.get(key, int(params.get(key, fallback)))))
|
return maxi(0, int(bp.get(key, int(params.get(key, fallback)))))
|
||||||
_:
|
_:
|
||||||
return int(bp.get(key, fallback))
|
return int(bp.get(key, fallback))
|
||||||
|
|
||||||
|
|
||||||
|
# ---- 断点域六成员公开读写面(5.1 封装修复——宿主私取清零:方法体一律一行转发自有成员,零逻辑增量)----
|
||||||
|
|
||||||
|
# 断点承载注入(宿主构造后接线——save_manager 后绑)
|
||||||
|
func bind_save_manager(sm) -> void:
|
||||||
|
_save_manager = sm
|
||||||
|
|
||||||
|
|
||||||
|
# 断点承载读投影(可 null——模拟/冒烟缺省态)
|
||||||
|
func save_manager() -> Variant:
|
||||||
|
return _save_manager
|
||||||
|
|
||||||
|
|
||||||
|
func set_pending_next_level(v: bool) -> void:
|
||||||
|
_pending_next_level = v
|
||||||
|
|
||||||
|
|
||||||
|
func pending_next_level() -> bool:
|
||||||
|
return _pending_next_level
|
||||||
|
|
||||||
|
|
||||||
|
func set_loadout_seeded(v: bool) -> void:
|
||||||
|
_loadout_seeded = v
|
||||||
|
|
||||||
|
|
||||||
|
func loadout_seeded() -> bool:
|
||||||
|
return _loadout_seeded
|
||||||
|
|
||||||
|
|
||||||
|
func set_resume_bp(bp: Dictionary) -> void:
|
||||||
|
_resume_bp = bp
|
||||||
|
|
||||||
|
|
||||||
|
func clear_resume_bp() -> void:
|
||||||
|
_resume_bp = {}
|
||||||
|
|
||||||
|
|
||||||
|
# 续玩快照读(返引用零拷贝——宿主读侧只读)
|
||||||
|
func resume_bp() -> Dictionary:
|
||||||
|
return _resume_bp
|
||||||
|
|
||||||
|
|
||||||
|
func set_last_level_payload(payload: Dictionary) -> void:
|
||||||
|
_last_level_payload = payload
|
||||||
|
|
||||||
|
|
||||||
|
func last_level_payload() -> Dictionary:
|
||||||
|
return _last_level_payload
|
||||||
|
|
||||||
|
|
||||||
|
func set_last_treasure_cleared(v: int) -> void:
|
||||||
|
_last_treasure_cleared = v
|
||||||
|
|
||||||
|
|
||||||
|
func last_treasure_cleared() -> int:
|
||||||
|
return _last_treasure_cleared
|
||||||
@@ -153,10 +153,10 @@ func _assign_loaded(loaded: Dictionary) -> void:
|
|||||||
presentation.camp_scene_layout = loaded.get("camp_scene_layout")
|
presentation.camp_scene_layout = loaded.get("camp_scene_layout")
|
||||||
for problem: String in presentation.camp_scene_layout.validate():
|
for problem: String in presentation.camp_scene_layout.validate():
|
||||||
_fatal("camp_scene_layout", problem) # 布局几何红线(命中区越画面/相交/侵顶栏)=数据致命
|
_fatal("camp_scene_layout", problem) # 布局几何红线(命中区越画面/相交/侵顶栏)=数据致命
|
||||||
# resolver 协作件注入三表(resolve 委托承载——resolver 文件与键零搬移)
|
# resolver 协作件注入三表(resolve 委托承载——resolver 文件与键零搬移;5.1 封装修复改经 bind_resolver 公开挂载口)
|
||||||
combat.board_gen._resolver = _resolver
|
combat.board_gen.bind_resolver(_resolver)
|
||||||
combat.enemy._resolver = _resolver
|
combat.enemy.bind_resolver(_resolver)
|
||||||
run.difficulty._resolver = _resolver
|
run.difficulty.bind_resolver(_resolver)
|
||||||
|
|
||||||
|
|
||||||
# 跨表校验(悬空引用 / 池护栏——全部加载期致命)本体:
|
# 跨表校验(悬空引用 / 池护栏——全部加载期致命)本体:
|
||||||
|
|||||||
@@ -44,16 +44,33 @@ var _hero_ac_override := -1 # 主角护甲等级合流入参(begin_battle
|
|||||||
# 效果端口件(组装期单点注入——host _init bind_port):发射/计数器/事实/揭示/AC 全经端口路由,
|
# 效果端口件(组装期单点注入——host _init bind_port):发射/计数器/事实/揭示/AC 全经端口路由,
|
||||||
# 缺省 null=未绑(与原 _fx==null 零扰动守卫同语义——单测直构不接端口的夹具零扰动照跑)
|
# 缺省 null=未绑(与原 _fx==null 零扰动守卫同语义——单测直构不接端口的夹具零扰动照跑)
|
||||||
var _port: EffectPort = null
|
var _port: EffectPort = null
|
||||||
|
var facts: BattleFacts # 战斗事实件引用(构造注入——hero_atk 派生读消费面)
|
||||||
|
|
||||||
|
|
||||||
func _init(db_ref, rng_ref, units_ref, effects_ref) -> void:
|
func _init(db_ref, rng_ref, units_ref, effects_ref, facts_ref) -> void:
|
||||||
db = db_ref
|
db = db_ref
|
||||||
rng = rng_ref
|
rng = rng_ref
|
||||||
units = units_ref
|
units = units_ref
|
||||||
effects = effects_ref
|
effects = effects_ref
|
||||||
|
facts = facts_ref
|
||||||
units.unit_died.connect(_on_unit_died)
|
units.unit_died.connect(_on_unit_died)
|
||||||
|
|
||||||
|
|
||||||
|
# 主角攻击基值派生读(enc 携带 hero_atk 键受理后只读——E4 收口语义不变:表值运行期不可变,
|
||||||
|
# 派生值恒等;自编排器归位(5.1 封装修复——伤害域原语),「读面即引用、无第二账」同款先例)
|
||||||
|
func hero_atk() -> int:
|
||||||
|
return int(facts.encounter.get("hero_atk", int(db.combat.hero.atk)))
|
||||||
|
|
||||||
|
|
||||||
|
# 效果命中过滤(按效果类型取命中子集——发射点分桶施加面;自编排器归位)
|
||||||
|
static func hits_of(hits: Array, kind: String) -> Array:
|
||||||
|
var out: Array[Variant] = []
|
||||||
|
for h in hits:
|
||||||
|
if String((h as Dictionary).get("kind", "")) == kind:
|
||||||
|
out.append(h)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
# 主角护甲等级合流入参(战斗编排器 begin_battle 调用——encounter.hero_armor_class;缺参/-1 = 表值兜底)
|
# 主角护甲等级合流入参(战斗编排器 begin_battle 调用——encounter.hero_armor_class;缺参/-1 = 表值兜底)
|
||||||
func set_hero_ac_override(v: int) -> void:
|
func set_hero_ac_override(v: int) -> void:
|
||||||
_hero_ac_override = v
|
_hero_ac_override = v
|
||||||
@@ -423,7 +440,7 @@ func _hero_attack_deliver(t: Dictionary, ctx: Dictionary, redemption: Dictionary
|
|||||||
# 敌方伤害技:掷点 → 折扣/暴击 → 克制点数(attack_type vs 主角护甲类型——
|
# 敌方伤害技:掷点 → 折扣/暴击 → 克制点数(attack_type vs 主角护甲类型——
|
||||||
# 无类型恒中性 delta 0,写法同 hero_attack 的 _counter_delta 消费位)→ hero_defense×enemy_output
|
# 无类型恒中性 delta 0,写法同 hero_attack 的 _counter_delta 消费位)→ hero_defense×enemy_output
|
||||||
# 池乘区 → 下限 → +rider(后置必中附加)→ 交付 → commit_action(作废单不 commit——死亡即不动手)。
|
# 池乘区 → 下限 → +rider(后置必中附加)→ 交付 → commit_action(作废单不 commit——死亡即不动手)。
|
||||||
# 掷点算术归 _enemy_roll;damage_taken 统一发射+消费归 _emit_hero_damage_taken(发射在本函数——
|
# 掷点算术归 _enemy_roll;damage_taken 统一发射+消费归 emit_hero_damage_taken(发射在本函数——
|
||||||
# 与减伤落快照的先后序固定;「掷点后、交付前」消费时点不变)。
|
# 与减伤落快照的先后序固定;「掷点后、交付前」消费时点不变)。
|
||||||
func _resolve_enemy_skill(t: Dictionary) -> void:
|
func _resolve_enemy_skill(t: Dictionary) -> void:
|
||||||
var hero: Dictionary[Variant, Variant] = units.hero_unit()
|
var hero: Dictionary[Variant, Variant] = units.hero_unit()
|
||||||
@@ -432,7 +449,7 @@ func _resolve_enemy_skill(t: Dictionary) -> void:
|
|||||||
# damage_taken 发射(kind=enemy_skill;shield 减伤订阅面:加法族合计 value×per 计数器层值,
|
# damage_taken 发射(kind=enemy_skill;shield 减伤订阅面:加法族合计 value×per 计数器层值,
|
||||||
# 稳健拓荒树规则同面(rebuild 合成订阅);减后过下限保底。damage_reduce〔背水 hp_ratio when〕/
|
# 稳健拓荒树规则同面(rebuild 合成订阅);减后过下限保底。damage_reduce〔背水 hp_ratio when〕/
|
||||||
# counter_add〔保险丝/石肤〕/ac_bonus〔石肤〕同面施加;hp_ratio fact 由 damage_taken 载荷携带)
|
# counter_add〔保险丝/石肤〕/ac_bonus〔石肤〕同面施加;hp_ratio fact 由 damage_taken 载荷携带)
|
||||||
var dt_res := _emit_hero_damage_taken(total, "enemy_skill", String(t.context.get("skill_id", "")), t.origin_cell, int(t.round_index))
|
var dt_res := emit_hero_damage_taken(total, "enemy_skill", String(t.context.get("skill_id", "")), t.origin_cell, int(t.round_index))
|
||||||
total = int(dt_res["total"])
|
total = int(dt_res["total"])
|
||||||
units.apply_damage(hero, total)
|
units.apply_damage(hero, total)
|
||||||
var payload: Dictionary[Variant, Variant] = {"target": "hero", "skill_id": String(t.context.get("skill_id", "")), "baseline": int(t.context.get("baseline", 0)),
|
var payload: Dictionary[Variant, Variant] = {"target": "hero", "skill_id": String(t.context.get("skill_id", "")), "baseline": int(t.context.get("baseline", 0)),
|
||||||
@@ -468,7 +485,7 @@ func _enemy_roll(t: Dictionary) -> Dictionary:
|
|||||||
# 资源直伤(后三者经 board_flat_hero 单据进入)都经此 dispatch 一次 damage_taken 并消费一次;
|
# 资源直伤(后三者经 board_flat_hero 单据进入)都经此 dispatch 一次 damage_taken 并消费一次;
|
||||||
# 同一伤害单据重复发射由 context.hero_damage_consumed 标记闸拦(地形路径在开单点标记)。
|
# 同一伤害单据重复发射由 context.hero_damage_consumed 标记闸拦(地形路径在开单点标记)。
|
||||||
# payload 必含 kind/amount/source/cell/round/hp_ratio;返回 {total, hits} 交调用侧按减后值开单/交付)。
|
# payload 必含 kind/amount/source/cell/round/hp_ratio;返回 {total, hits} 交调用侧按减后值开单/交付)。
|
||||||
func _emit_hero_damage_taken(amount: int, damage_kind: String, source: String, cell: Vector2i, ri: int) -> Dictionary:
|
func emit_hero_damage_taken(amount: int, damage_kind: String, source: String, cell: Vector2i, ri: int) -> Dictionary:
|
||||||
var hu: Dictionary[Variant, Variant] = units.hero_unit()
|
var hu: Dictionary[Variant, Variant] = units.hero_unit()
|
||||||
var payload: Dictionary[Variant, Variant] = {"kind": damage_kind, "amount": amount, "source": source,
|
var payload: Dictionary[Variant, Variant] = {"kind": damage_kind, "amount": amount, "source": source,
|
||||||
"cell": cell, "round": ri, "hp_ratio": int(round(100.0 * float(int(hu.get("hp", 0))) / float(maxi(1, int(hu.get("max_hp", 1))))))}
|
"cell": cell, "round": ri, "hp_ratio": int(round(100.0 * float(int(hu.get("hp", 0))) / float(maxi(1, int(hu.get("max_hp", 1))))))}
|
||||||
@@ -544,7 +561,7 @@ func _resolve_board_blast(t: Dictionary) -> void:
|
|||||||
func _resolve_board_flat_hero(t: Dictionary) -> void:
|
func _resolve_board_flat_hero(t: Dictionary) -> void:
|
||||||
var dealt: int = int(t.base_value)
|
var dealt: int = int(t.base_value)
|
||||||
if not bool((t.get("context", {}) as Dictionary).get("hero_damage_consumed", false)):
|
if not bool((t.get("context", {}) as Dictionary).get("hero_damage_consumed", false)):
|
||||||
var res_flat := _emit_hero_damage_taken(dealt, "board_flat_hero", String(t.get("source_class", "")), t.origin_cell, int(t.round_index))
|
var res_flat := emit_hero_damage_taken(dealt, "board_flat_hero", String(t.get("source_class", "")), t.origin_cell, int(t.round_index))
|
||||||
dealt = int(res_flat["total"])
|
dealt = int(res_flat["total"])
|
||||||
dealt = maxi(int(db.combat.settlement.damage_roll.damage_floor), dealt) # 伤害下限全来源(减后过下限)
|
dealt = maxi(int(db.combat.settlement.damage_roll.damage_floor), dealt) # 伤害下限全来源(减后过下限)
|
||||||
units.apply_damage(units.hero_unit(), dealt)
|
units.apply_damage(units.hero_unit(), dealt)
|
||||||
|
|||||||
@@ -53,6 +53,30 @@ func current_board() -> BoardData:
|
|||||||
return _board
|
return _board
|
||||||
|
|
||||||
|
|
||||||
|
# 伤害型变异过滤(terrain_kind fact 取数面——variation 表 effect=flat_damage 判据;
|
||||||
|
# 🔴 R4 甲永不对冲边界=仅限伤害型,信息型(hide_number 族)永不出现在本集。自编排器归位)
|
||||||
|
func harmful_variants(vids: Array) -> Array:
|
||||||
|
var out: Array[Variant] = []
|
||||||
|
for vid_v in vids:
|
||||||
|
var vid := String(vid_v)
|
||||||
|
var vrow: Dictionary[Variant, Variant] = db.combat.variation.row_by_id(vid)
|
||||||
|
if String(vrow.get("effect", "")) == "flat_damage":
|
||||||
|
out.append(vid)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# 邻接伤害型变异收集(terrain_kind fact 取数面——seal_hit 发射点用:封印命中相邻八格的伤害型变异集;
|
||||||
|
# 🔴 R4 甲永不对冲边界=仅限伤害型,信息型〔hide_number 族〕不入集。自编排器归位)
|
||||||
|
func harmful_adjacent(cell: Vector2i) -> Array:
|
||||||
|
var out: Array[Variant] = []
|
||||||
|
var board: BoardData = current_board()
|
||||||
|
if board == null:
|
||||||
|
return out
|
||||||
|
for n in board.neighbors8(cell):
|
||||||
|
out.append_array(harmful_variants(board.variant_ids(n)))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
# 已开格记录查询面(cell → {kind, digit};未开 = {})——呈现层渲染消费面(board_view 徽章/四格态)
|
# 已开格记录查询面(cell → {kind, digit};未开 = {})——呈现层渲染消费面(board_view 徽章/四格态)
|
||||||
func reveal_record(c: Vector2i) -> Dictionary:
|
func reveal_record(c: Vector2i) -> Dictionary:
|
||||||
return _revealed.get(c, {})
|
return _revealed.get(c, {})
|
||||||
|
|||||||
@@ -67,6 +67,20 @@ func reset_battle() -> void:
|
|||||||
defer.queue = []
|
defer.queue = []
|
||||||
|
|
||||||
|
|
||||||
|
# 奖励乘区调整(yield_mult 命中聚合——自编排器归位〔5.1 封装修复·奖励域原语〕:power 按
|
||||||
|
# pos/neg 乘区百分比放大、下限 1;正负两态由参分;零命中/零乘区原快照直返)
|
||||||
|
func feat_yield_adjust(snap: Dictionary, positive: bool, pos_m: int, neg_m: int) -> Dictionary:
|
||||||
|
if positive and pos_m <= 0:
|
||||||
|
return snap
|
||||||
|
if not positive and neg_m <= 0:
|
||||||
|
return snap
|
||||||
|
var out: Dictionary[Variant, Variant] = snap.duplicate()
|
||||||
|
var power := int(out.get("power", int(reward_economy.polarity_row(String(out.get("cell_type", "")), "positive" if positive else "negative").power)))
|
||||||
|
var pct := pos_m if positive else neg_m
|
||||||
|
out["power"] = maxi(1, int(round(float(power) * (1.0 + float(pct) / 100.0))))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
# 揭示奖励兑付(BattleFxHost.flush_pending_reveals 的兑付回调面——授予面收编:
|
# 揭示奖励兑付(BattleFxHost.flush_pending_reveals 的兑付回调面——授予面收编:
|
||||||
# snapshot 查询与极性路由同在本件,fx_host 对授予面零强引用)
|
# snapshot 查询与极性路由同在本件,fx_host 对授予面零强引用)
|
||||||
func reveal_redeem(cell: Vector2i, ri: int) -> void:
|
func reveal_redeem(cell: Vector2i, ri: int) -> void:
|
||||||
|
|||||||
+33
-13
@@ -48,7 +48,7 @@ func _init(path: String = "") -> void:
|
|||||||
# 无激活态(注入路径空,一切写盘路径拒绝并返回 false);玩家侧经选档界面显式进档/新档后才可写。
|
# 无激活态(注入路径空,一切写盘路径拒绝并返回 false);玩家侧经选档界面显式进档/新档后才可写。
|
||||||
# 旧隐式单档一次性迁入槽 1(Q1 甲,sv12)在本口执行。
|
# 旧隐式单档一次性迁入槽 1(Q1 甲,sv12)在本口执行。
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
slots._migrate_legacy_save(LEGACY_SAVE_PATH, slots.slot_path(1), LEGACY_SAVE_PATH + LEGACY_MIGRATED_SUFFIX)
|
slots.migrate_legacy_save(LEGACY_SAVE_PATH, slots.slot_path(1), LEGACY_SAVE_PATH + LEGACY_MIGRATED_SUFFIX)
|
||||||
|
|
||||||
|
|
||||||
# 迁移注册口:from_version → from_version+1 的升级函数(读档时链式调用)。fn 契约:
|
# 迁移注册口:from_version → from_version+1 的升级函数(读档时链式调用)。fn 契约:
|
||||||
@@ -63,10 +63,10 @@ func register_migration(from_version: int, fn: Callable) -> void:
|
|||||||
# 损坏兜底:原文件改名 <path>.corrupt.bak(已存在则覆盖)+ push_warning + 默认数据继续——不静默丢档。
|
# 损坏兜底:原文件改名 <path>.corrupt.bak(已存在则覆盖)+ push_warning + 默认数据继续——不静默丢档。
|
||||||
func load_from_disk() -> bool:
|
func load_from_disk() -> bool:
|
||||||
_read_only = false
|
_read_only = false
|
||||||
if not FileAccess.file_exists(slots._path):
|
if not FileAccess.file_exists(slots.active_path()):
|
||||||
_data = _default_data() # 新档合法态
|
_data = _default_data() # 新档合法态
|
||||||
return true
|
return true
|
||||||
var f := FileAccess.open(slots._path, FileAccess.READ)
|
var f := FileAccess.open(slots.active_path(), FileAccess.READ)
|
||||||
if f == null:
|
if f == null:
|
||||||
return _corrupt_fallback("读打开失败(%s)" % error_string(FileAccess.get_open_error()))
|
return _corrupt_fallback("读打开失败(%s)" % error_string(FileAccess.get_open_error()))
|
||||||
return _load_parsed(f)
|
return _load_parsed(f)
|
||||||
@@ -102,21 +102,21 @@ func _load_parsed(f: FileAccess) -> bool:
|
|||||||
# 无激活态一律拒绝并返回 false(sv3:启动态不隐式激活,盘面零触碰);
|
# 无激活态一律拒绝并返回 false(sv3:启动态不隐式激活,盘面零触碰);
|
||||||
# 高版本拒读态一律拒绝(契约 4 禁写);写失败返回 false。
|
# 高版本拒读态一律拒绝(契约 4 禁写);写失败返回 false。
|
||||||
func write_to_disk() -> bool:
|
func write_to_disk() -> bool:
|
||||||
if slots._path == "":
|
if slots.active_path() == "":
|
||||||
push_error("[存档序列化] 写盘拒绝——无激活存档位(启动态不隐式激活;经选档进档/新档后落盘)")
|
push_error("[存档序列化] 写盘拒绝——无激活存档位(启动态不隐式激活;经选档进档/新档后落盘)")
|
||||||
return false
|
return false
|
||||||
if _read_only:
|
if _read_only:
|
||||||
push_error("[存档序列化] 写盘拒绝——档案版本高于当前(拒读禁写态)")
|
push_error("[存档序列化] 写盘拒绝——档案版本高于当前(拒读禁写态)")
|
||||||
return false
|
return false
|
||||||
var tmp := slots._path + ".tmp"
|
var tmp := slots.active_path() + ".tmp"
|
||||||
var f := FileAccess.open(tmp, FileAccess.WRITE)
|
var f := FileAccess.open(tmp, FileAccess.WRITE)
|
||||||
if f == null:
|
if f == null:
|
||||||
push_error("[存档序列化] 临时文件打开失败:%s(%s)" % [tmp, error_string(FileAccess.get_open_error())])
|
push_error("[存档序列化] 临时文件打开失败:%s(%s)" % [tmp, error_string(FileAccess.get_open_error())])
|
||||||
return false
|
return false
|
||||||
f.store_string(JSON.stringify(_data, "\t", true))
|
f.store_string(JSON.stringify(_data, "\t", true))
|
||||||
f.close()
|
f.close()
|
||||||
if not _rename_overwrite(tmp, slots._path):
|
if not rename_overwrite(tmp, slots.active_path()):
|
||||||
push_error("[存档序列化] 原子改名失败(tmp 保留待查):%s → %s" % [tmp, slots._path])
|
push_error("[存档序列化] 原子改名失败(tmp 保留待查):%s → %s" % [tmp, slots.active_path()])
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ func _peek_migrated(parsed: Variant, fail: Array) -> Variant:
|
|||||||
|
|
||||||
## 摘要窥视内核(只读推演单点——SlotStore 两 peek 方法经槽路径派生委托至此;
|
## 摘要窥视内核(只读推演单点——SlotStore 两 peek 方法经槽路径派生委托至此;
|
||||||
## 迁移链在此只读推演——生产迁移 Callable 为纯函数,零盘面副作用)
|
## 迁移链在此只读推演——生产迁移 Callable 为纯函数,零盘面副作用)
|
||||||
func _peek_summary_at(path: String) -> Dictionary:
|
func peek_summary_at(path: String) -> Dictionary:
|
||||||
var out := {"readable": false, "has_file": false, "keystone": 0, "cleared": 0, "total": 0}
|
var out := {"readable": false, "has_file": false, "keystone": 0, "cleared": 0, "total": 0}
|
||||||
if not FileAccess.file_exists(path):
|
if not FileAccess.file_exists(path):
|
||||||
out["readable"] = true # 空槽=可读零值合法态
|
out["readable"] = true # 空槽=可读零值合法态
|
||||||
@@ -275,15 +275,35 @@ func _default_data() -> Dictionary[String, Variant]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# 原地重置默认态(SlotStore 槽位原语的协作口——激活/建删/换注回默认数据)
|
# 原地重置默认态(SlotStore 槽位原语的协作口——激活/建删/换注回默认数据;
|
||||||
func _reset_to_default() -> void:
|
# 公开化〔5.1 封装修复——SlotStore 反向私取承接,_reset_to_default 更名非并存〕)
|
||||||
|
func reset_to_default() -> void:
|
||||||
_data = _default_data()
|
_data = _default_data()
|
||||||
|
|
||||||
|
|
||||||
|
# 禁写位公开面(SlotStore 激活/换注三态恢复消费——一行转发自有成员,零逻辑增量)
|
||||||
|
func set_read_only(v: bool) -> void:
|
||||||
|
_read_only = v
|
||||||
|
|
||||||
|
|
||||||
|
func read_only() -> bool:
|
||||||
|
return _read_only
|
||||||
|
|
||||||
|
|
||||||
|
# 数据快照还原公开面(SlotStore 激活/新建失败回退三态恢复消费——零拷贝语义:snapshot 返 _data
|
||||||
|
# 本引用、restore 直还引用,行为与原 _data 直读写逐位等价)
|
||||||
|
func data_snapshot() -> Dictionary[String, Variant]:
|
||||||
|
return _data
|
||||||
|
|
||||||
|
|
||||||
|
func restore_data(data: Dictionary[String, Variant]) -> void:
|
||||||
|
_data = data
|
||||||
|
|
||||||
|
|
||||||
# 损坏兜底:坏档改名 .corrupt.bak(已存在则覆盖)→ 默认数据继续。不静默丢档(警告留痕)、不 crash。
|
# 损坏兜底:坏档改名 .corrupt.bak(已存在则覆盖)→ 默认数据继续。不静默丢档(警告留痕)、不 crash。
|
||||||
func _corrupt_fallback(reason: String) -> bool:
|
func _corrupt_fallback(reason: String) -> bool:
|
||||||
var bak := slots._path + ".corrupt.bak"
|
var bak := slots.active_path() + ".corrupt.bak"
|
||||||
if not _rename_overwrite(slots._path, bak):
|
if not rename_overwrite(slots.active_path(), bak):
|
||||||
push_warning("[存档序列化] 坏档备份改名失败(原文件保留原地):%s——%s" % [bak, reason])
|
push_warning("[存档序列化] 坏档备份改名失败(原文件保留原地):%s——%s" % [bak, reason])
|
||||||
else:
|
else:
|
||||||
push_warning("[存档序列化] 检测到损坏档案,已备份为 %s——%s(用默认新档继续)" % [bak, reason])
|
push_warning("[存档序列化] 检测到损坏档案,已备份为 %s——%s(用默认新档继续)" % [bak, reason])
|
||||||
@@ -314,7 +334,7 @@ func _normalize(data: Dictionary) -> Dictionary[String, Variant]:
|
|||||||
|
|
||||||
# 覆盖式改名(Windows 原生 rename 目标已存在时会失败——先删后改,显式覆盖语义;IO 原语单点——
|
# 覆盖式改名(Windows 原生 rename 目标已存在时会失败——先删后改,显式覆盖语义;IO 原语单点——
|
||||||
# SlotStore 迁移流经本件调用)
|
# SlotStore 迁移流经本件调用)
|
||||||
func _rename_overwrite(from: String, to: String) -> bool:
|
func rename_overwrite(from: String, to: String) -> bool:
|
||||||
var dir := DirAccess.open(from.get_base_dir())
|
var dir := DirAccess.open(from.get_base_dir())
|
||||||
if dir == null:
|
if dir == null:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -2,15 +2,18 @@ class_name AuraSegment
|
|||||||
extends RefCounted
|
extends RefCounted
|
||||||
# ② 增益/减益段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。
|
# ② 增益/减益段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。
|
||||||
# 职责:续存实例组合行(本结算新授予的已在授予段逐 grant 出行——增益状态 义务⑦)。
|
# 职责:续存实例组合行(本结算新授予的已在授予段逐 grant 出行——增益状态 义务⑦)。
|
||||||
# 行发射归宿主编排器(oc)——零算法改动(🔴 零行为变更契约)。
|
# 耦合边界(5.1 封装修复换代):引用束注入——构造收编排器组装的 20 键封闭束,本段断言 1 键
|
||||||
|
# (emit_composition_cb)解包;组合行发射经回调回宿主保留面——对编排器零引用。
|
||||||
|
# 零算法改动(🔴 零行为变更契约)。
|
||||||
|
|
||||||
var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全) 宿主
|
var emit_composition_cb: Callable # 宿主 _emit_composition_effects——续存实例组合行发射(保留面)
|
||||||
|
|
||||||
|
|
||||||
func _init(host) -> void:
|
func _init(bundle: Dictionary) -> void:
|
||||||
oc = host
|
assert(bundle.has("emit_composition_cb"), "[增益减益段] 引用束缺键: emit_composition_cb")
|
||||||
|
emit_composition_cb = bundle.emit_composition_cb
|
||||||
|
|
||||||
|
|
||||||
func run(ri: int) -> void:
|
func run(ri: int) -> void:
|
||||||
oc._emit_composition_effects("hero", ri)
|
emit_composition_cb.call("hero", ri)
|
||||||
oc._emit_composition_effects("enemy", ri)
|
emit_composition_cb.call("enemy", ri)
|
||||||
@@ -2,42 +2,72 @@ class_name BattleRefreshFlow
|
|||||||
extends RefCounted
|
extends RefCounted
|
||||||
## 换图刷新流程件(迭代 46 卡A T3/T4 自编排器拆出):刷新可用性门、折价扣费、换图执行与成功事实登记。
|
## 换图刷新流程件(迭代 46 卡A T3/T4 自编排器拆出):刷新可用性门、折价扣费、换图执行与成功事实登记。
|
||||||
## 拆分边界(需求卡片 C13):本件只负责可用性、扣费、换图与成功事实登记——七件效果的算法留在
|
## 拆分边界(需求卡片 C13):本件只负责可用性、扣费、换图与成功事实登记——七件效果的算法留在
|
||||||
## 效果引擎、授予段与伤害控制器。调用路径=呈现层→编排器单行转发→本件→行动经济/装板壳/授予段。
|
## 效果引擎、授予段与伤害控制器。调用路径=呈现层→编排器单行转发→RoundCycle.refresh_flow()→本件
|
||||||
|
## →行动经济/装板壳/授予段(编排器入口与单行转发形态不变)。
|
||||||
|
## 耦合边界(5.1 封装修复换代):去 static 改实例——构造收 RoundCycle 复制的引用束,断言 8 键
|
||||||
|
## (facts/db/action_economy/fx_host/reward_economy/minesweeper/install_cb/grant_seg)解包同名成员;
|
||||||
|
## 变值标量(battle_result/round_index)走调用参数现值交付;授予段引用直接交换(段间引用,
|
||||||
|
## 不再穿编排器链式取段)——对编排器零引用。
|
||||||
## 反自动刷新(C11):只经玩家入口发起,禁 RAP 满自动刷——编排器公开面单行转发,无自动调用点。
|
## 反自动刷新(C11):只经玩家入口发起,禁 RAP 满自动刷——编排器公开面单行转发,无自动调用点。
|
||||||
## 成功序钉死(T4):折价扣 RAP→refresh_used+1→旧资源退役→旧板退役→装新板→授予段成功事实发射;
|
## 成功序钉死(T4):折价扣 RAP→refresh_used+1→旧资源退役→旧板退役→装新板→授予段成功事实发射;
|
||||||
## 装板失败返回 false 且零效果——成功事实只在新板就位后发射。
|
## 装板失败返回 false 且零效果——成功事实只在新板就位后发射。
|
||||||
|
## 随编排器每战新建同灭(RoundCycle 持有)。
|
||||||
|
|
||||||
|
var facts: BattleFacts # 战斗事实件(refresh_used 受理计数)
|
||||||
|
var db: ConfigDb
|
||||||
|
var action_economy: ActionEconomy
|
||||||
|
var fx_host: BattleFxHost # 效果宿主件(refresh_request 发射面)
|
||||||
|
var reward_economy: RewardEconomy # 地图战斗资源(旧批退役面)
|
||||||
|
var minesweeper: MinesweeperRules # 排雷玩法(旧板退役面)
|
||||||
|
var install_cb: Callable # 宿主 _install_and_reset——装板受理薄壳(保留面)
|
||||||
|
var grant_seg = null # GrantSegment 段间引用(成功事实发射——直接交接零链式)
|
||||||
|
|
||||||
|
|
||||||
|
func _init(bundle: Dictionary) -> void:
|
||||||
|
for k in ["facts", "db", "action_economy", "fx_host", "reward_economy", "minesweeper", "install_cb", "grant_seg"]:
|
||||||
|
assert(bundle.has(k), "[换图刷新流程件] 引用束缺键: %s" % k)
|
||||||
|
facts = bundle.facts
|
||||||
|
db = bundle.db
|
||||||
|
action_economy = bundle.action_economy
|
||||||
|
fx_host = bundle.fx_host
|
||||||
|
reward_economy = bundle.reward_economy
|
||||||
|
minesweeper = bundle.minesweeper
|
||||||
|
install_cb = bundle.install_cb
|
||||||
|
grant_seg = bundle.grant_seg
|
||||||
|
|
||||||
|
|
||||||
## 折价成本读数(refresh_request 命中省力周转 apply_discount——只认 kind==apply_discount 且
|
## 折价成本读数(refresh_request 命中省力周转 apply_discount——只认 kind==apply_discount 且
|
||||||
## mode==refresh,取声明序第一命中,floor(base×cost_mult/100);无命中=表基线逐位不变。
|
## mode==refresh,取声明序第一命中,floor(base×cost_mult/100);无命中=表基线逐位不变。
|
||||||
## can_refresh 与 execute 同源取数〔V3——can 与执行同源判断〕;折扣只改变门槛与扣费,
|
## can_refresh 与 execute 同源取数〔V3——can 与执行同源判断〕;折扣只改变门槛与扣费,
|
||||||
## 不改变拒绝判据〔C2——每轮上限与相位判据照旧〕)
|
## 不改变拒绝判据〔C2——每轮上限与相位判据照旧〕)
|
||||||
static func _effective_refresh_cost(oc: BattleOrchestrator) -> int:
|
func _effective_refresh_cost() -> int:
|
||||||
var base := int(oc.db.run.action_economy.rap_refresh_cost)
|
var base := int(db.run.action_economy.rap_refresh_cost)
|
||||||
for h in oc.fx_host().fx_emit("refresh_request", {"mode": "refresh", "base": base}):
|
for h in fx_host.fx_emit("refresh_request", {"mode": "refresh", "base": base}):
|
||||||
if String(h.get("kind", "")) == "apply_discount" and String(h.get("mode", "")) == "refresh":
|
if String(h.get("kind", "")) == "apply_discount" and String(h.get("mode", "")) == "refresh":
|
||||||
return int(floor(float(base) * float(int(h.get("cost_mult", 100))) / 100.0))
|
return int(floor(float(base) * float(int(h.get("cost_mult", 100))) / 100.0))
|
||||||
return base
|
return base
|
||||||
|
|
||||||
|
|
||||||
## 可用性门(HUD 刷新按钮同源判据):战斗未终局、每轮上限未满、Active 相位、RAP 抵折后价。
|
## 可用性门(HUD 刷新按钮同源判据):战斗未终局、每轮上限未满、Active 相位、RAP 抵折后价。
|
||||||
static func can_refresh(oc: BattleOrchestrator) -> bool:
|
## battle_result=调用参数现值交付。
|
||||||
return oc.battle_result == "" \
|
func can_refresh(battle_result: String) -> bool:
|
||||||
and oc._facts.refresh_used < int(oc.db.run.action_economy.refresh_cap_per_round) \
|
return battle_result == "" \
|
||||||
and oc.action_economy.phase == oc.action_economy.PHASE_ACTIVE \
|
and facts.refresh_used < int(db.run.action_economy.refresh_cap_per_round) \
|
||||||
and oc.action_economy.rap >= _effective_refresh_cost(oc)
|
and action_economy.phase == action_economy.PHASE_ACTIVE \
|
||||||
|
and action_economy.rap >= _effective_refresh_cost()
|
||||||
|
|
||||||
|
|
||||||
## 执行(玩家请求刷新唯一通道体):扣费受理后继续;扣费拒绝/门拒零效果零事实。
|
## 执行(玩家请求刷新唯一通道体):扣费受理后继续;扣费拒绝/门拒零效果零事实。
|
||||||
static func execute(oc: BattleOrchestrator) -> bool:
|
## battle_result/round_index=调用参数现值交付。
|
||||||
if not can_refresh(oc):
|
func execute(battle_result: String, round_index: int) -> bool:
|
||||||
|
if not can_refresh(battle_result):
|
||||||
return false
|
return false
|
||||||
if not oc.action_economy.request_refresh(_effective_refresh_cost(oc)):
|
if not action_economy.request_refresh(_effective_refresh_cost()):
|
||||||
return false
|
return false
|
||||||
oc._facts.refresh_used += 1
|
facts.refresh_used += 1
|
||||||
oc._reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提)
|
reward_economy.retire_mapping() # 旧图批次退役(地图战斗资源 Ready→retired;E6 新批重置前提)
|
||||||
oc.minesweeper.retire_board(true) # 旧板退役(轮内通道)
|
minesweeper.retire_board(true) # 旧板退役(轮内通道)
|
||||||
if not oc._install_and_reset(true): # 当场换图——失败即编排违例 fail-fast(零成功事实)
|
if not bool(install_cb.call(true)): # 当场换图——失败即编排违例 fail-fast(零成功事实)
|
||||||
return false
|
return false
|
||||||
oc._rounds._grant_seg.apply_board_refreshed(oc.round_index) # 成功事实:新板就位后一次发射
|
grant_seg.apply_board_refreshed(round_index) # 成功事实:新板就位后一次发射
|
||||||
return true
|
return true
|
||||||
@@ -4,19 +4,38 @@ extends RefCounted
|
|||||||
# 职责:案C 闸复位 + 本轮扫雷事实推导(执拗 streak 取数面)+ 主角先手
|
# 职责:案C 闸复位 + 本轮扫雷事实推导(执拗 streak 取数面)+ 主角先手
|
||||||
# + 敌方行动兑现 + 地图伤害账殿后(补提交统一结账)。
|
# + 敌方行动兑现 + 地图伤害账殿后(补提交统一结账)。
|
||||||
# 调用序:主角先手 → 逐敌还手 → 地图伤害账殿后〔案C〕;hero hp=0 短路。
|
# 调用序:主角先手 → 逐敌还手 → 地图伤害账殿后〔案C〕;hero hp=0 短路。
|
||||||
# 行发射/开单单点归宿主(oc)——案C 暂存闸归 RewardGrant(段持三引用直调,零转发壳)——
|
# 耦合边界(5.1 封装修复换代):引用束注入——构造收编排器组装的 20 键封闭束,本段断言 10 键
|
||||||
|
# (facts/units/damage/status_effects/fx_host/minesweeper/db/emit_row_cb/fatal_cb/reward_grant)
|
||||||
|
# 解包同名成员;行发射经 emit_row_cb 回宿主、fatal 经 fatal_cb 回宿主——对编排器零引用。
|
||||||
# 本函数体为原编排器 _combat_segment 逐字搬运(🔴 零行为变更契约)。
|
# 本函数体为原编排器 _combat_segment 逐字搬运(🔴 零行为变更契约)。
|
||||||
# run 编排骨架+三私有族(_hero_strike/_enemy_actions/_board_deferred_settle);段序逐字。
|
# run 编排骨架+三私有族(_hero_strike/_enemy_actions/_board_deferred_settle);段序逐字。
|
||||||
|
|
||||||
var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全)
|
var facts: BattleFacts # 战斗事实件(轮缓冲 streak 取数面)
|
||||||
var fx_host: BattleFxHost # 效果引擎宿主件(三引用构造——本轮无消费面,构造形与授予段同形)
|
var units: UnitContainer # 单位容器(目标解析/存活查询)
|
||||||
var reward_grant: RewardGrant # 奖励授予件(案C 暂存闸 defer 持有方)
|
var damage: DamageController # 伤害控制器(开单/提交面)
|
||||||
|
var status_effects: StatusContainer # 状态容器(增益/妨害施加面)
|
||||||
|
var fx_host: BattleFxHost # 效果引擎宿主件(facts_inject 写面)
|
||||||
|
var minesweeper: MinesweeperRules # 排雷玩法(束形统一交付面——卡面 10 键分配)
|
||||||
|
var db: ConfigDb
|
||||||
|
var emit_row_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形)
|
||||||
|
var fatal_cb: Callable # 宿主 _fatal——battle_result 唯一写点留宿主
|
||||||
|
var reward_grant: RewardGrant # 奖励授予件(案C 暂存闸 defer 持有方)
|
||||||
|
|
||||||
|
|
||||||
func _init(host, fx_host_ref: BattleFxHost, reward_grant_ref: RewardGrant) -> void:
|
func _init(bundle: Dictionary) -> void:
|
||||||
oc = host
|
for k in ["facts", "units", "damage", "status_effects", "fx_host", "minesweeper", "db",
|
||||||
fx_host = fx_host_ref
|
"emit_row_cb", "fatal_cb", "reward_grant"]:
|
||||||
reward_grant = reward_grant_ref
|
assert(bundle.has(k), "[战斗段] 引用束缺键: %s" % k)
|
||||||
|
facts = bundle.facts
|
||||||
|
units = bundle.units
|
||||||
|
damage = bundle.damage
|
||||||
|
status_effects = bundle.status_effects
|
||||||
|
fx_host = bundle.fx_host
|
||||||
|
minesweeper = bundle.minesweeper
|
||||||
|
db = bundle.db
|
||||||
|
emit_row_cb = bundle.emit_row_cb
|
||||||
|
fatal_cb = bundle.fatal_cb
|
||||||
|
reward_grant = bundle.reward_grant
|
||||||
|
|
||||||
|
|
||||||
func run(ri: int) -> void:
|
func run(ri: int) -> void:
|
||||||
@@ -26,12 +45,12 @@ func run(ri: int) -> void:
|
|||||||
# 同一次 _settle 内,facts 已定。
|
# 同一次 _settle 内,facts 已定。
|
||||||
var round_safe := 0
|
var round_safe := 0
|
||||||
var round_mine := false
|
var round_mine := false
|
||||||
for evt in oc._facts.reveals:
|
for evt in facts.reveals:
|
||||||
if evt.kind == "safe":
|
if evt.kind == "safe":
|
||||||
round_safe += 1
|
round_safe += 1
|
||||||
elif evt.kind == "mine":
|
elif evt.kind == "mine":
|
||||||
round_mine = true
|
round_mine = true
|
||||||
oc.fx_host().facts_inject["dig_streak"] = 0 if round_mine else round_safe
|
fx_host.facts_inject["dig_streak"] = 0 if round_mine else round_safe
|
||||||
_hero_strike(ri)
|
_hero_strike(ri)
|
||||||
_enemy_actions(ri)
|
_enemy_actions(ri)
|
||||||
_board_deferred_settle(ri)
|
_board_deferred_settle(ri)
|
||||||
@@ -46,26 +65,26 @@ func run(ri: int) -> void:
|
|||||||
# 护甲等级掷点:基础分量吃掷点判定(含暴击);棋盘来源伤害不掷不折;
|
# 护甲等级掷点:基础分量吃掷点判定(含暴击);棋盘来源伤害不掷不折;
|
||||||
# 任何伤害落地 ≥ damage_floor(全局 max(1,·) 适用所有来源,下限在控制器管线单点收口)。
|
# 任何伤害落地 ≥ damage_floor(全局 max(1,·) 适用所有来源,下限在控制器管线单点收口)。
|
||||||
func _hero_strike(ri: int) -> void:
|
func _hero_strike(ri: int) -> void:
|
||||||
var hero_alive: bool = int(oc._enemy_system.hero_unit().get("hp", 0)) > 0
|
var hero_alive: bool = int(units.hero_unit().get("hp", 0)) > 0
|
||||||
var hero_target = oc._enemy_system.default_target()
|
var hero_target = units.default_target()
|
||||||
if hero_alive and hero_target != null:
|
if hero_alive and hero_target != null:
|
||||||
var ht := oc._damage.open_ticket(DamageController.KIND_HERO_ATTACK, "hero_attack", "主角攻击",
|
var ht := damage.open_ticket(DamageController.KIND_HERO_ATTACK, "hero_attack", "主角攻击",
|
||||||
{"channel": "combat", "base_value": oc._hero_atk(), "target_rule": DamageController.TARGET_FRONT_ALIVE,
|
{"channel": "combat", "base_value": damage.hero_atk(), "target_rule": DamageController.TARGET_FRONT_ALIVE,
|
||||||
"ri": ri, "origin_cell": Vector2i(-1, -1),
|
"ri": ri, "origin_cell": Vector2i(-1, -1),
|
||||||
"attack_type": String(oc._enemy_system.hero_unit().get("attack_type", "normal"))})
|
"attack_type": String(units.hero_unit().get("attack_type", "normal"))})
|
||||||
oc._damage.submit(ht)
|
damage.submit(ht)
|
||||||
|
|
||||||
|
|
||||||
# 敌方行动(敌人技能池批——本拍动作开轮已摇定,此处按技能行兑现;行序 = 生成序)。
|
# 敌方行动(敌人技能池批——本拍动作开轮已摇定,此处按技能行兑现;行序 = 生成序)。
|
||||||
# 死亡即不动手:摇定后、兑现前死亡的敌不在存活序中——本拍技能作废,不 commit、不进冷却。
|
# 死亡即不动手:摇定后、兑现前死亡的敌不在存活序中——本拍技能作废,不 commit、不进冷却。
|
||||||
func _enemy_actions(ri: int) -> void:
|
func _enemy_actions(ri: int) -> void:
|
||||||
for u in oc._enemy_system.query_alive("enemy"):
|
for u in units.query_alive("enemy"):
|
||||||
if int(oc._enemy_system.hero_unit().hp) <= 0:
|
if int(units.hero_unit().hp) <= 0:
|
||||||
break # hero hp=0 立即短路(死尸挨打行是归因噪音——用户拍板)
|
break # hero hp=0 立即短路(死尸挨打行是归因噪音——用户拍板)
|
||||||
var skill_id := oc._enemy_system.pending_action(u)
|
var skill_id := units.pending_action(u)
|
||||||
var srow: Dictionary[Variant, Variant] = oc.db.combat.skill.row_by_id(skill_id)
|
var srow: Dictionary[Variant, Variant] = db.combat.skill.row_by_id(skill_id)
|
||||||
if srow.is_empty():
|
if srow.is_empty():
|
||||||
oc._fatal("敌实例待发动作悬空: %s" % skill_id)
|
fatal_cb.call("敌实例待发动作悬空: %s" % skill_id)
|
||||||
continue
|
continue
|
||||||
if String(srow.type) == "buff" or String(srow.type) == "debuff":
|
if String(srow.type) == "buff" or String(srow.type) == "debuff":
|
||||||
# 增益/妨害技 = 0 伤害施放行:不掷不折;状态授予即数值兑现,技能冷却与增益持续两计时器分账。
|
# 增益/妨害技 = 0 伤害施放行:不掷不折;状态授予即数值兑现,技能冷却与增益持续两计时器分账。
|
||||||
@@ -73,9 +92,9 @@ func _enemy_actions(ri: int) -> void:
|
|||||||
# 敌方增益技只强化施放者自己,不再全体受益);debuff → 主角(off_guard/sapped 同义主角侧)。
|
# 敌方增益技只强化施放者自己,不再全体受益);debuff → 主角(off_guard/sapped 同义主角侧)。
|
||||||
var dur: int = int(srow.get("buff_duration", 1))
|
var dur: int = int(srow.get("buff_duration", 1))
|
||||||
var owner_key: String = StatusContainer.owner_key_for(u) if String(srow.type) == "buff" else StatusContainer.OWNER_HERO
|
var owner_key: String = StatusContainer.owner_key_for(u) if String(srow.type) == "buff" else StatusContainer.OWNER_HERO
|
||||||
oc._status_effects.apply_effect(String(srow.buff_ref), owner_key, {"source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "round_index": ri}, dur)
|
status_effects.apply_effect(String(srow.buff_ref), owner_key, {"source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "round_index": ri}, dur)
|
||||||
oc._emit_row({"type": "cast", "source_class": "enemy_action", "origin_cell": Vector2i(-1, -1)}, ri, "%s 施放 %s" % [u.display_name, String(srow.display_name)], 0, {"skill_id": skill_id, "effect_id": String(srow.buff_ref), "duration": dur})
|
emit_row_cb.call({"type": "cast", "source_class": "enemy_action", "origin_cell": Vector2i(-1, -1)}, ri, "%s 施放 %s" % [u.display_name, String(srow.display_name)], 0, {"skill_id": skill_id, "effect_id": String(srow.buff_ref), "duration": dur})
|
||||||
oc._enemy_system.commit_action(u)
|
units.commit_action(u)
|
||||||
continue
|
continue
|
||||||
# 伤害技(含普攻——倍率恒 1)= 敌方伤害技单据:基线 = round(atk×倍率)、rider 预计算归开单侧
|
# 伤害技(含普攻——倍率恒 1)= 敌方伤害技单据:基线 = round(atk×倍率)、rider 预计算归开单侧
|
||||||
# (表值算术零随机),掷点/克制查表(spec 束 attack_type = 敌方攻击类型——迭代 24 N3 轴开列前恒空串不查表)/
|
# (表值算术零随机),掷点/克制查表(spec 束 attack_type = 敌方攻击类型——迭代 24 N3 轴开列前恒空串不查表)/
|
||||||
@@ -84,27 +103,27 @@ func _enemy_actions(ri: int) -> void:
|
|||||||
var baseline: int = int(round(float(u.atk) * float(srow.get("damage_mult", 1.0))))
|
var baseline: int = int(round(float(u.atk) * float(srow.get("damage_mult", 1.0))))
|
||||||
var rider := 0
|
var rider := 0
|
||||||
if srow.has("rider_mult"):
|
if srow.has("rider_mult"):
|
||||||
rider = maxi(int(oc.db.combat.settlement.damage_roll.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult))))
|
rider = maxi(int(db.combat.settlement.damage_roll.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult))))
|
||||||
var et := oc._damage.open_ticket(DamageController.KIND_ENEMY_SKILL, "enemy_action",
|
var et := damage.open_ticket(DamageController.KIND_ENEMY_SKILL, "enemy_action",
|
||||||
"%s %s" % [u.display_name, String(srow.display_name)],
|
"%s %s" % [u.display_name, String(srow.display_name)],
|
||||||
{"channel": "combat", "base_value": baseline, "target_rule": DamageController.TARGET_HERO,
|
{"channel": "combat", "base_value": baseline, "target_rule": DamageController.TARGET_HERO,
|
||||||
"ri": ri, "origin_cell": Vector2i(-1, -1),
|
"ri": ri, "origin_cell": Vector2i(-1, -1),
|
||||||
"attack_type": String(u.get("attack_type", "normal")), "rider": rider})
|
"attack_type": String(u.get("attack_type", "normal")), "rider": rider})
|
||||||
et["source_unit"] = u
|
et["source_unit"] = u
|
||||||
et["context"] = {"skill_id": skill_id, "baseline": baseline}
|
et["context"] = {"skill_id": skill_id, "baseline": baseline}
|
||||||
oc._damage.submit(et)
|
damage.submit(et)
|
||||||
|
|
||||||
|
|
||||||
# 地图伤害兑付(案C:战斗段末——怪物还手后按授予段事件序补提交统一结账;
|
# 地图伤害兑付(案C:战斗段末——怪物还手后按授予段事件序补提交统一结账;
|
||||||
# 主角死于怪物 → 地图账不兑,逐单留「未生效」行不作废静默——没生效也要留痕口径;
|
# 主角死于怪物 → 地图账不兑,逐单留「未生效」行不作废静默——没生效也要留痕口径;
|
||||||
# 补提交后若地图直伤反杀主角,控制器 _halted 作废余单——战斗已败,胜负归判定段)
|
# 补提交后若地图直伤反杀主角,控制器 _halted 作废余单——战斗已败,胜负归判定段)
|
||||||
func _board_deferred_settle(ri: int) -> void:
|
func _board_deferred_settle(ri: int) -> void:
|
||||||
if int(oc._enemy_system.hero_unit().get("hp", 0)) <= 0:
|
if int(units.hero_unit().get("hp", 0)) <= 0:
|
||||||
for t in (reward_grant.defer["queue"] as Array):
|
for t in (reward_grant.defer["queue"] as Array):
|
||||||
oc._emit_row({"type": "reward_ineffective", "source_class": String(t.get("source_class", "")), "origin_cell": t.get("origin_cell", Vector2i(-1, -1))}, ri,
|
emit_row_cb.call({"type": "reward_ineffective", "source_class": String(t.get("source_class", "")), "origin_cell": t.get("origin_cell", Vector2i(-1, -1))}, ri,
|
||||||
"%s 未生效(主角已倒下)" % String(t.get("display", "")), 0, {"forfeit": "hero_down"})
|
"%s 未生效(主角已倒下)" % String(t.get("display", "")), 0, {"forfeit": "hero_down"})
|
||||||
else:
|
else:
|
||||||
for t in (reward_grant.defer["queue"] as Array):
|
for t in (reward_grant.defer["queue"] as Array):
|
||||||
oc._damage.submit(t)
|
damage.submit(t)
|
||||||
(reward_grant.defer["queue"] as Array).clear()
|
(reward_grant.defer["queue"] as Array).clear()
|
||||||
oc._damage.flush() # 战斗段界结账(B 模式——主角单+敌方单+地图单按开单序统一兑现;A 模式地图单补提交时即结)
|
damage.flush() # 战斗段界结账(B 模式——主角单+敌方单+地图单按开单序统一兑现;A 模式地图单补提交时即结)
|
||||||
@@ -1,16 +1,22 @@
|
|||||||
class_name DecaySegment
|
class_name DecaySegment
|
||||||
extends RefCounted
|
extends RefCounted
|
||||||
# ④ 衰减段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。
|
# ④ 衰减段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。
|
||||||
# 职责:状态衰减恰一次恒执行含末轮——先兑付后衰减。行发射归宿主编排器(oc)——
|
# 职责:状态衰减恰一次恒执行含末轮——先兑付后衰减。
|
||||||
|
# 耦合边界(5.1 封装修复换代):引用束注入——构造收编排器组装的 20 键封闭束,本段断言 2 键
|
||||||
|
# (status_effects/emit_row_cb)解包同名成员;行发射经 emit_row_cb 回宿主——对编排器零引用。
|
||||||
# 零算法改动(🔴 零行为变更契约)。
|
# 零算法改动(🔴 零行为变更契约)。
|
||||||
|
|
||||||
var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全) 宿主
|
var status_effects: StatusContainer # 状态容器(衰减推进面)
|
||||||
|
var emit_row_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形)
|
||||||
|
|
||||||
|
|
||||||
func _init(host) -> void:
|
func _init(bundle: Dictionary) -> void:
|
||||||
oc = host
|
for k in ["status_effects", "emit_row_cb"]:
|
||||||
|
assert(bundle.has(k), "[衰减段] 引用束缺键: %s" % k)
|
||||||
|
status_effects = bundle.status_effects
|
||||||
|
emit_row_cb = bundle.emit_row_cb
|
||||||
|
|
||||||
|
|
||||||
func run(ri: int) -> void:
|
func run(ri: int) -> void:
|
||||||
var k: int = oc._status_effects.decay_round()
|
var k: int = status_effects.decay_round()
|
||||||
oc._emit_row({"type": "decay", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "状态衰减", k, {})
|
emit_row_cb.call({"type": "decay", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "状态衰减", k, {})
|
||||||
@@ -2,24 +2,49 @@ class_name GrantSegment
|
|||||||
extends RefCounted
|
extends RefCounted
|
||||||
# ① 授予段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。
|
# ① 授予段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。
|
||||||
# 职责:结算队列(开格/封印判定/清图)→ 极性路由逐批兑付 + 封印判/军规开单 + round_end 施加 +
|
# 职责:结算队列(开格/封印判定/清图)→ 极性路由逐批兑付 + 封印判/军规开单 + round_end 施加 +
|
||||||
# 转存收口 + 揭示兑付 + 段界结账。行发射/开单单点归宿主(oc)——兑付管线归 RewardGrant、
|
# 转存收口 + 揭示兑付 + 段界结账。行发射经 emit_row_cb 回宿主——兑付管线归 RewardGrant、
|
||||||
# 效果施加归 BattleFxHost(段持三引用直调,零转发壳)——
|
# 效果施加归 BattleFxHost(段持注入引用直调,零转发壳)——
|
||||||
# 本段只搬运时序,零算法改动(🔴 零行为变更契约:同种子完整局输出逐位一致)。
|
# 本段只搬运时序,零算法改动(🔴 零行为变更契约:同种子完整局输出逐位一致)。
|
||||||
|
# 耦合边界(5.1 封装修复换代):引用束注入——构造收编排器组装的 20 键封闭束,本段断言 12 键
|
||||||
|
# (facts/units/damage/reward_economy/fx_host/minesweeper/db/action_economy/reward_grant/
|
||||||
|
# emit_row_cb/attune_param_sum_cb/perfect_sweep_cb)解包同名成员;对编排器零引用(跨件单向接线,
|
||||||
|
# 注入 Callable 亦属接线面);变值标量(round_index)走调用参数现值交付。
|
||||||
# run 编排骨架+批私有族(_reveal_batch/_terrain_batch/_seal_batch/_round_end_apply,段内施加面
|
# run 编排骨架+批私有族(_reveal_batch/_terrain_batch/_seal_batch/_round_end_apply,段内施加面
|
||||||
# 再拆 _reveal_safe_apply/_reveal_mine_apply/_seal_hit_effects 等单面函数);地形 damage_taken
|
# 再拆 _reveal_safe_apply/_reveal_mine_apply/_seal_hit_effects 等单面函数);地形 damage_taken
|
||||||
# 消费随迭代 46 卡A T2 并入伤害控制器全局消费族(本段零本地减伤函数);
|
# 消费随迭代 46 卡A T2 并入伤害控制器全局消费族(本段零本地减伤函数);
|
||||||
# kind 分派单点化(P1-1 同批):counter_add 五位三行组 / grant_material 三位兑付块
|
# kind 分派单点化(P1-1 同批):counter_add 五位三行组 / grant_material 三位兑付块
|
||||||
# 收 _apply_counter_hit / _apply_material_hit 两助手(分支本体逐字保留在助手内)。
|
# 收 _apply_counter_hit / _apply_material_hit 两助手(分支本体逐字保留在助手内)。
|
||||||
|
|
||||||
var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全)
|
var facts: BattleFacts # 战斗事实件(轮缓冲/封印账/溯源)
|
||||||
var fx_host: BattleFxHost # 效果引擎宿主件(fx 发射/施加/揭示队列/计数器桶)
|
var units: UnitContainer # 单位容器(hero_unit 读面)
|
||||||
var reward_grant: RewardGrant # 奖励授予件(极性路由/行应用/提交面/清图奖励/escrow)
|
var damage: DamageController # 伤害控制器(开单/承伤发射面)
|
||||||
|
var reward_economy: RewardEconomy # 地图战斗资源(grant/query_full_snapshot 消费面)
|
||||||
|
var fx_host: BattleFxHost # 效果引擎宿主件(fx 发射/施加/揭示队列/计数器桶)
|
||||||
|
var minesweeper: MinesweeperRules # 排雷玩法(板读取/变异过滤/危险格移除面)
|
||||||
|
var db: ConfigDb
|
||||||
|
var action_economy: ActionEconomy
|
||||||
|
var reward_grant: RewardGrant # 奖励授予件(极性路由/行应用/提交面/清图奖励/escrow)
|
||||||
|
var emit_row_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形)
|
||||||
|
var attune_param_sum_cb: Callable # 宿主 attune_param_sum 读投影——转存率合计读数
|
||||||
|
var perfect_sweep_cb: Callable # 宿主 perfect_sweep_earned 读投影——清图奖励第二份判定
|
||||||
|
|
||||||
|
|
||||||
func _init(host, fx_host_ref: BattleFxHost, reward_grant_ref: RewardGrant) -> void:
|
func _init(bundle: Dictionary) -> void:
|
||||||
oc = host
|
for k in ["facts", "units", "damage", "reward_economy", "fx_host", "minesweeper", "db",
|
||||||
fx_host = fx_host_ref
|
"action_economy", "reward_grant", "emit_row_cb", "attune_param_sum_cb", "perfect_sweep_cb"]:
|
||||||
reward_grant = reward_grant_ref
|
assert(bundle.has(k), "[授予段] 引用束缺键: %s" % k)
|
||||||
|
facts = bundle.facts
|
||||||
|
units = bundle.units
|
||||||
|
damage = bundle.damage
|
||||||
|
reward_economy = bundle.reward_economy
|
||||||
|
fx_host = bundle.fx_host
|
||||||
|
minesweeper = bundle.minesweeper
|
||||||
|
db = bundle.db
|
||||||
|
action_economy = bundle.action_economy
|
||||||
|
reward_grant = bundle.reward_grant
|
||||||
|
emit_row_cb = bundle.emit_row_cb
|
||||||
|
attune_param_sum_cb = bundle.attune_param_sum_cb
|
||||||
|
perfect_sweep_cb = bundle.perfect_sweep_cb
|
||||||
|
|
||||||
|
|
||||||
func run(ri: int) -> void:
|
func run(ri: int) -> void:
|
||||||
@@ -45,10 +70,10 @@ func apply_board_refreshed(ri: int) -> void:
|
|||||||
for h in br_hits:
|
for h in br_hits:
|
||||||
if String(h.get("kind", "")) == "grant_gp":
|
if String(h.get("kind", "")) == "grant_gp":
|
||||||
reward_grant.escrow += int(h.get("value", 0))
|
reward_grant.escrow += int(h.get("value", 0))
|
||||||
oc._emit_row({"type": "effect", "source_class": "fx_grant_gp", "origin_cell": 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})
|
emit_row_cb.call({"type": "effect", "source_class": "fx_grant_gp", "origin_cell": 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})
|
||||||
for h in br_hits:
|
for h in br_hits:
|
||||||
if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "blast":
|
if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "blast":
|
||||||
var bb_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_board_refresh", "崩壁冲击",
|
var bb_t := damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_board_refresh", "崩壁冲击",
|
||||||
{"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE,
|
{"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE,
|
||||||
"ri": ri, "origin_cell": Vector2i(-1, -1)})
|
"ri": ri, "origin_cell": Vector2i(-1, -1)})
|
||||||
reward_grant.defer_ticket(bb_t)
|
reward_grant.defer_ticket(bb_t)
|
||||||
@@ -56,7 +81,7 @@ func apply_board_refreshed(ri: int) -> void:
|
|||||||
|
|
||||||
# 开格/踩雷兑付批(轮 facts_reveals 资源事件序:safe 正向乘区兑付 / mine_hit 减半负向兑付)
|
# 开格/踩雷兑付批(轮 facts_reveals 资源事件序:safe 正向乘区兑付 / mine_hit 减半负向兑付)
|
||||||
func _reveal_batch(ri: int) -> void:
|
func _reveal_batch(ri: int) -> void:
|
||||||
for evt in oc._facts.reveals:
|
for evt in facts.reveals:
|
||||||
if not bool(evt.get("resource", false)):
|
if not bool(evt.get("resource", false)):
|
||||||
continue # 封锁格(initial 通道)恒无资源——不入账(战斗结算 义务②)
|
continue # 封锁格(initial 通道)恒无资源——不入账(战斗结算 义务②)
|
||||||
var snap = evt.get("reward_snapshot")
|
var snap = evt.get("reward_snapshot")
|
||||||
@@ -75,16 +100,14 @@ func _reveal_batch(ri: int) -> void:
|
|||||||
# new_resource_type〔地质学家——快照 cell_type 首见判定,见后集登记〕)
|
# new_resource_type〔地质学家——快照 cell_type 首见判定,见后集登记〕)
|
||||||
func _reveal_safe(evt: Dictionary, snap: Dictionary, ri: int) -> void:
|
func _reveal_safe(evt: Dictionary, snap: Dictionary, ri: int) -> void:
|
||||||
if bool(evt.get("dug_under_seal", false)):
|
if bool(evt.get("dug_under_seal", false)):
|
||||||
oc._emit_row({"type": "reward_ineffective", "source_class": "safe_cell", "origin_cell": evt.cell}, ri, "奖励作废(封印格直接挖开——零惩罚零奖励)", 0, {})
|
emit_row_cb.call({"type": "reward_ineffective", "source_class": "safe_cell", "origin_cell": evt.cell}, ri, "奖励作废(封印格直接挖开——零惩罚零奖励)", 0, {})
|
||||||
return
|
return
|
||||||
var board_b: BoardData = oc.minesweeper.current_board()
|
var board_b: BoardData = minesweeper.current_board()
|
||||||
var ctype := String(snap.get("cell_type", ""))
|
var ctype := String(snap.get("cell_type", ""))
|
||||||
var is_new_type := ctype != "" and not fx_host._fx_new_resource_types.has(ctype)
|
var is_new_type := fx_host.mark_new_resource_type(ctype)
|
||||||
if is_new_type:
|
|
||||||
fx_host._fx_new_resource_types[ctype] = true
|
|
||||||
var dr_hits := fx_host.fx_emit("dig_reveal", {"cell": evt.cell, "round": ri,
|
var dr_hits := fx_host.fx_emit("dig_reveal", {"cell": evt.cell, "round": ri,
|
||||||
"is_first_dig_of_battle": not oc._facts.feat_first_dig_used,
|
"is_first_dig_of_battle": not facts.feat_first_dig_used,
|
||||||
"terrain_kind": oc._harmful_variants(evt.get("variant_ids", []) as Array),
|
"terrain_kind": minesweeper.harmful_variants(evt.get("variant_ids", []) as Array),
|
||||||
"is_edge_cell": evt.cell.x == 0 or evt.cell.y == 0
|
"is_edge_cell": evt.cell.x == 0 or evt.cell.y == 0
|
||||||
or evt.cell.x == board_b.width - 1 or evt.cell.y == board_b.height - 1,
|
or evt.cell.x == board_b.width - 1 or evt.cell.y == board_b.height - 1,
|
||||||
"is_blank_zero": board_b.digit_of(evt.cell) == 0,
|
"is_blank_zero": board_b.digit_of(evt.cell) == 0,
|
||||||
@@ -96,7 +119,7 @@ func _reveal_safe(evt: Dictionary, snap: Dictionary, ri: int) -> void:
|
|||||||
continue
|
continue
|
||||||
pos_m += int(h.get("mult", 0))
|
pos_m += int(h.get("mult", 0))
|
||||||
neg_m += int(h.get("cost_mult", 0))
|
neg_m += int(h.get("cost_mult", 0))
|
||||||
reward_grant.grant_routed(oc._feat_yield_adjust(snap, true, pos_m, neg_m), "positive", {"source_class": "safe_cell", "cell": evt.cell, "ri": ri})
|
reward_grant.grant_routed(reward_grant.feat_yield_adjust(snap, true, pos_m, neg_m), "positive", {"source_class": "safe_cell", "cell": evt.cell, "ri": ri})
|
||||||
_reveal_safe_apply(dr_hits, evt, ri)
|
_reveal_safe_apply(dr_hits, evt, ri)
|
||||||
|
|
||||||
|
|
||||||
@@ -107,15 +130,15 @@ func _reveal_safe_apply(dr_hits: Array, evt: Dictionary, ri: int) -> void:
|
|||||||
for h in dr_hits:
|
for h in dr_hits:
|
||||||
if String(h.get("kind", "")) == "counter_add":
|
if String(h.get("kind", "")) == "counter_add":
|
||||||
_apply_counter_hit(h)
|
_apply_counter_hit(h)
|
||||||
if not oc._hits_of(dr_hits, "reveal_tiles").is_empty():
|
if not DamageController.hits_of(dr_hits, "reveal_tiles").is_empty():
|
||||||
fx_host.enqueue_reveal()
|
fx_host.enqueue_reveal()
|
||||||
for h in dr_hits:
|
for h in dr_hits:
|
||||||
if String(h.get("kind", "")) == "grant_material":
|
if String(h.get("kind", "")) == "grant_material":
|
||||||
_apply_material_hit(h, evt.cell, ri)
|
_apply_material_hit(h, evt.cell, ri)
|
||||||
var spell_hits: Array[Variant] = oc._hits_of(dr_hits, "spawn_spell")
|
var spell_hits: Array[Variant] = DamageController.hits_of(dr_hits, "spawn_spell")
|
||||||
if not spell_hits.is_empty() and not oc._facts.feat_first_dig_used:
|
if not spell_hits.is_empty() and not facts.feat_first_dig_used:
|
||||||
oc._facts.feat_first_dig_used = true
|
facts.feat_first_dig_used = true
|
||||||
var spell_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell", "奥术矢",
|
var spell_t := damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_first_dig_spell", "奥术矢",
|
||||||
{"channel": "board", "base_value": int((spell_hits[0] as Dictionary).get("value", 0)),
|
{"channel": "board", "base_value": int((spell_hits[0] as Dictionary).get("value", 0)),
|
||||||
"target_rule": DamageController.TARGET_FRONT_ALIVE, "ri": ri, "origin_cell": evt.cell, "attack_type": "magic"})
|
"target_rule": DamageController.TARGET_FRONT_ALIVE, "ri": ri, "origin_cell": evt.cell, "attack_type": "magic"})
|
||||||
reward_grant.submit(spell_t, false)
|
reward_grant.submit(spell_t, false)
|
||||||
@@ -128,19 +151,19 @@ func _reveal_mine(evt: Dictionary, snap: Dictionary, ri: int) -> void:
|
|||||||
var mine_power := int(snap.get("power", 0))
|
var mine_power := int(snap.get("power", 0))
|
||||||
var mh_hits := fx_host.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)),
|
"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))))))})
|
"hp_ratio": int(round(100.0 * float(int(units.hero_unit().get("hp", 0))) / float(maxi(1, int(units.hero_unit().get("max_hp", 1))))))})
|
||||||
var applied := _reveal_mine_apply(mh_hits, snap, evt.cell, ri)
|
var applied := _reveal_mine_apply(mh_hits, snap, evt.cell, ri)
|
||||||
if int(applied["mine_reduce"]) > 0:
|
if int(applied["mine_reduce"]) > 0:
|
||||||
snap["power"] = maxi(0, mine_power - int(applied["mine_reduce"])) # 减伤落快照(负向兑付按减后值——安全帽每层减 1)
|
snap["power"] = maxi(0, mine_power - int(applied["mine_reduce"])) # 减伤落快照(负向兑付按减后值——安全帽每层减 1)
|
||||||
for h in mh_hits:
|
for h in mh_hits:
|
||||||
if String(h.get("kind", "")) == "counter_add":
|
if String(h.get("kind", "")) == "counter_add":
|
||||||
_apply_counter_hit(h)
|
_apply_counter_hit(h)
|
||||||
if not oc._hits_of(mh_hits, "reveal_tiles").is_empty():
|
if not DamageController.hits_of(mh_hits, "reveal_tiles").is_empty():
|
||||||
fx_host.enqueue_reveal() # 见血开窍——每颗雷一次揭示(授予段末统一兑付)
|
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": int(applied["halve_pct"])})
|
reward_grant.grant_routed(reward_grant.feat_yield_adjust(snap, false, 0, 0), "negative", {"source_class": "mine_hit", "cell": evt.cell, "ri": ri, "halve_pct": int(applied["halve_pct"])})
|
||||||
for h in mh_hits:
|
for h in mh_hits:
|
||||||
if String(h.get("kind", "")) == "open_ticket" and String(h.get("channel", "")) == "reflect":
|
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", "惊巢反制·反弹",
|
var nct := damage.open_ticket(DamageController.KIND_BOARD_BLAST, "attune_nest_counter", "惊巢反制·反弹",
|
||||||
{"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE,
|
{"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE,
|
||||||
"ri": ri, "origin_cell": evt.cell})
|
"ri": ri, "origin_cell": evt.cell})
|
||||||
reward_grant.submit(nct, false)
|
reward_grant.submit(nct, false)
|
||||||
@@ -162,15 +185,14 @@ func _reveal_mine_apply(mh_hits: Array, snap: Dictionary, cell: Vector2i, ri: in
|
|||||||
else:
|
else:
|
||||||
mine_reduce += int(h.get("value", 0)) # 背水——定值减免
|
mine_reduce += int(h.get("value", 0)) # 背水——定值减免
|
||||||
"heal":
|
"heal":
|
||||||
var hu_heal: Dictionary[Variant, Variant] = oc._enemy_system.hero_unit()
|
var hu_heal: Dictionary[Variant, Variant] = units.hero_unit()
|
||||||
hu_heal["hp"] = mini(int(hu_heal.get("hp", 0)) + int(h.get("value", 0)), maxi(1, int(hu_heal.get("max_hp", 1))))
|
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":
|
"bonus_delta":
|
||||||
var axis := String(h.get("axis", ""))
|
var axis := String(h.get("axis", ""))
|
||||||
if fx_host._fx_bonus_delta.has(axis):
|
fx_host.add_bonus_delta(axis, int(h.get("delta", 0)))
|
||||||
fx_host._fx_bonus_delta[axis] += int(h.get("delta", 0))
|
|
||||||
"blast_scaled":
|
"blast_scaled":
|
||||||
var blast_v := maxi(int(oc.db.combat.settlement.damage_roll.damage_floor), int(float(int(snap.get("power", 0))) * float(h.get("mult", 1))))
|
var blast_v := maxi(int(db.combat.settlement.damage_roll.damage_floor), int(float(int(snap.get("power", 0))) * float(h.get("mult", 1))))
|
||||||
var bs_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_blast_scaled", "陷阱爆发",
|
var bs_t := damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_blast_scaled", "陷阱爆发",
|
||||||
{"channel": "board", "base_value": blast_v, "target_rule": DamageController.TARGET_ALL_ALIVE,
|
{"channel": "board", "base_value": blast_v, "target_rule": DamageController.TARGET_ALL_ALIVE,
|
||||||
"ri": ri, "origin_cell": cell})
|
"ri": ri, "origin_cell": cell})
|
||||||
reward_grant.submit(bs_t, false)
|
reward_grant.submit(bs_t, false)
|
||||||
@@ -182,21 +204,21 @@ func _reveal_mine_apply(mh_hits: Array, snap: Dictionary, cell: Vector2i, ri: in
|
|||||||
# 危险格恒无变异——布置段排除,双层叠罚不存在;封印格直挖照吃变异伤害——地形后果同 mine_hit 口径,
|
# 危险格恒无变异——布置段排除,双层叠罚不存在;封印格直挖照吃变异伤害——地形后果同 mine_hit 口径,
|
||||||
# 非奖励不作废;吞噬流资源兑付〔荆棘编织/地脉补偿〕中伤害形态行同样经案C闸暂存,非伤害形态照常当场)
|
# 非奖励不作废;吞噬流资源兑付〔荆棘编织/地脉补偿〕中伤害形态行同样经案C闸暂存,非伤害形态照常当场)
|
||||||
func _terrain_batch(ri: int) -> void:
|
func _terrain_batch(ri: int) -> void:
|
||||||
for evt in oc._facts.reveals:
|
for evt in facts.reveals:
|
||||||
var vids: Array[Variant] = evt.get("variant_ids", [])
|
var vids: Array[Variant] = evt.get("variant_ids", [])
|
||||||
for vid in vids:
|
for vid in vids:
|
||||||
var vrow: Dictionary[Variant, Variant] = oc.db.combat.variation.row_by_id(String(vid))
|
var vrow: Dictionary[Variant, Variant] = db.combat.variation.row_by_id(String(vid))
|
||||||
if String(vrow.get("effect", "")) != "flat_damage":
|
if String(vrow.get("effect", "")) != "flat_damage":
|
||||||
continue # 触发词表按 effect 路由:flat_damage=on_open 兑现;hide_number/on_open 外组合 = 数据行新词表未接线(显式忽略面)
|
continue # 触发词表按 effect 路由:flat_damage=on_open 兑现;hide_number/on_open 外组合 = 数据行新词表未接线(显式忽略面)
|
||||||
# 地形变异伤害单据——无类型必中,下限保底在控制器管线内
|
# 地形变异伤害单据——无类型必中,下限保底在控制器管线内
|
||||||
# damage_taken 统一发射(迭代 46 卡A T2——地形路径并入全局消费族 _emit_hero_damage_taken:
|
# damage_taken 统一发射(迭代 46 卡A T2——地形路径并入全局消费族 emit_hero_damage_taken:
|
||||||
# 发射在单据开立前、减后值开单,组合消费含 shield〔全局受伤语义〕;
|
# 发射在单据开立前、减后值开单,组合消费含 shield〔全局受伤语义〕;
|
||||||
# hero_damage_consumed 标记入单据 context——战斗段末 _resolve_board_flat_hero 跳过重复发射;
|
# hero_damage_consumed 标记入单据 context——战斗段末 _resolve_board_flat_hero 跳过重复发射;
|
||||||
# grant_material〔荆棘/地脉〕移到单据提交后(兑付值序一致——收益不因减伤变化))
|
# grant_material〔荆棘/地脉〕移到单据提交后(兑付值序一致——收益不因减伤变化))
|
||||||
var dt_amount := int(vrow.get("effect_value", 0))
|
var dt_amount := int(vrow.get("effect_value", 0))
|
||||||
var dt_res := oc._damage._emit_hero_damage_taken(dt_amount, "terrain", String(vid), evt.cell, ri)
|
var dt_res := damage.emit_hero_damage_taken(dt_amount, "terrain", String(vid), evt.cell, ri)
|
||||||
dt_amount = int(dt_res["total"])
|
dt_amount = int(dt_res["total"])
|
||||||
var vt := oc._damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, "地形变异", String(vrow.get("display_name", vid)),
|
var vt := damage.open_ticket(DamageController.KIND_BOARD_FLAT_HERO, "地形变异", String(vrow.get("display_name", vid)),
|
||||||
{"channel": "board", "base_value": dt_amount, "target_rule": DamageController.TARGET_HERO,
|
{"channel": "board", "base_value": dt_amount, "target_rule": DamageController.TARGET_HERO,
|
||||||
"ri": ri, "origin_cell": evt.cell, "context": {"variant_id": String(vid), "hero_damage_consumed": true}})
|
"ri": ri, "origin_cell": evt.cell, "context": {"variant_id": String(vid), "hero_damage_consumed": true}})
|
||||||
reward_grant.submit(vt, false)
|
reward_grant.submit(vt, false)
|
||||||
@@ -210,7 +232,7 @@ func _terrain_batch(ri: int) -> void:
|
|||||||
# 台账披露待追认〕;完美布防=本轮封印批全中(hit>0 且 miss==0)时封印伤 ×mult——全对推理奖励)。
|
# 台账披露待追认〕;完美布防=本轮封印批全中(hit>0 且 miss==0)时封印伤 ×mult——全对推理奖励)。
|
||||||
# 伤害单据案C 暂存战斗段末兑付。
|
# 伤害单据案C 暂存战斗段末兑付。
|
||||||
func _seal_batch(ri: int) -> void:
|
func _seal_batch(ri: int) -> void:
|
||||||
for evt in oc._facts.seals:
|
for evt in facts.seals:
|
||||||
_judge_seal(evt, ri)
|
_judge_seal(evt, ri)
|
||||||
_seal_strike_batch(ri)
|
_seal_strike_batch(ri)
|
||||||
|
|
||||||
@@ -220,7 +242,7 @@ func _seal_batch(ri: int) -> void:
|
|||||||
func _seal_strike_batch(ri: int) -> void:
|
func _seal_strike_batch(ri: int) -> void:
|
||||||
var fs_hits := 0
|
var fs_hits := 0
|
||||||
var fs_misses := 0
|
var fs_misses := 0
|
||||||
for fevt in oc._facts.seals:
|
for fevt in facts.seals:
|
||||||
if bool(fevt.get("hit", false)):
|
if bool(fevt.get("hit", false)):
|
||||||
fs_hits += 1
|
fs_hits += 1
|
||||||
else:
|
else:
|
||||||
@@ -230,10 +252,10 @@ func _seal_strike_batch(ri: int) -> void:
|
|||||||
var strike := _seal_strike_value(fs_fx, fs_hits, fs_misses)
|
var strike := _seal_strike_value(fs_fx, fs_hits, fs_misses)
|
||||||
if strike.is_empty():
|
if strike.is_empty():
|
||||||
return
|
return
|
||||||
for fevt in oc._facts.seals:
|
for fevt in facts.seals:
|
||||||
if not bool(fevt.get("hit", false)):
|
if not bool(fevt.get("hit", false)):
|
||||||
continue
|
continue
|
||||||
var st_t := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_seal_strike", "战封印军规",
|
var st_t := damage.open_ticket(DamageController.KIND_BOARD_TYPED, "feat_seal_strike", "战封印军规",
|
||||||
{"channel": "board", "base_value": int(strike["value"]), "target_rule": DamageController.TARGET_FRONT_ALIVE,
|
{"channel": "board", "base_value": int(strike["value"]), "target_rule": DamageController.TARGET_FRONT_ALIVE,
|
||||||
"ri": ri, "origin_cell": fevt.cell, "attack_type": "normal"})
|
"ri": ri, "origin_cell": fevt.cell, "attack_type": "normal"})
|
||||||
reward_grant.submit(st_t, false)
|
reward_grant.submit(st_t, false)
|
||||||
@@ -272,16 +294,16 @@ func _round_end_apply(ri: int) -> void:
|
|||||||
for h in re_fx:
|
for h in re_fx:
|
||||||
if String(h.get("kind", "")) == "grant_gp":
|
if String(h.get("kind", "")) == "grant_gp":
|
||||||
reward_grant.escrow += int(h.get("value", 0))
|
reward_grant.escrow += int(h.get("value", 0))
|
||||||
oc._emit_row({"type": "effect", "source_class": "fx_grant_gp", "origin_cell": 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})
|
emit_row_cb.call({"type": "effect", "source_class": "fx_grant_gp", "origin_cell": 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":
|
elif String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue":
|
||||||
oc.action_economy.queue_ap_delta(int(h.get("value", 0)))
|
action_economy.queue_ap_delta(int(h.get("value", 0)))
|
||||||
# 转存率入参(储备罐 rap_transfer_mult magnitude=目标转存率百分比 125=1.25:1;未装配=100 与 1:1 逐位一致)
|
# 转存率入参(储备罐 rap_transfer_mult magnitude=目标转存率百分比 125=1.25:1;未装配=100 与 1:1 逐位一致)
|
||||||
var rap_pct: int = oc.attune_param_sum("rap_transfer_mult")
|
var rap_pct: int = int(attune_param_sum_cb.call("rap_transfer_mult"))
|
||||||
oc.action_economy.finish_round_stop(rap_pct if rap_pct > 0 else 100)
|
action_economy.finish_round_stop(rap_pct if rap_pct > 0 else 100)
|
||||||
if oc._facts.path == "cleared":
|
if facts.path == "cleared":
|
||||||
reward_grant.grant_clear_reward(ri, oc._facts.board_from_midround, oc.perfect_sweep_earned())
|
reward_grant.grant_clear_reward(ri, facts.board_from_midround, bool(perfect_sweep_cb.call()))
|
||||||
fx_host.flush_pending_reveals(ri) # 揭示效果兑付(授予段末统一掷格,防轮 facts 迭代重入)
|
fx_host.flush_pending_reveals(ri) # 揭示效果兑付(授予段末统一掷格,防轮 facts 迭代重入)
|
||||||
oc._damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界)
|
damage.flush() # 授予段界结账(案C 后本段伤害单据全数暂存,此处对空队 no-op——保留作防御性段界)
|
||||||
|
|
||||||
|
|
||||||
# round_end heal value_counter 兑付(迭代 46 卡A T5——坑道小憩:层值=本轮成功刷新累计恢复点,
|
# round_end heal value_counter 兑付(迭代 46 卡A T5——坑道小憩:层值=本轮成功刷新累计恢复点,
|
||||||
@@ -294,12 +316,12 @@ func _round_end_heal_value_counter(re_fx: Array, ri: int) -> void:
|
|||||||
var layers := EffectEngine.counter(fx_host.counters_ctx, String(h.get("value_counter", "")))
|
var layers := EffectEngine.counter(fx_host.counters_ctx, String(h.get("value_counter", "")))
|
||||||
if layers <= 0:
|
if layers <= 0:
|
||||||
continue
|
continue
|
||||||
var hu: Dictionary[Variant, Variant] = oc._enemy_system.hero_unit()
|
var hu: Dictionary[Variant, Variant] = units.hero_unit()
|
||||||
if int(hu.get("hp", 0)) >= int(hu.get("max_hp", 1)):
|
if int(hu.get("hp", 0)) >= int(hu.get("max_hp", 1)):
|
||||||
oc._emit_row({"type": "reward_ineffective", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 未生效(满血)", 0, {"batch_key": String(h.get("value_counter", ""))})
|
emit_row_cb.call({"type": "reward_ineffective", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 未生效(满血)", 0, {"batch_key": String(h.get("value_counter", ""))})
|
||||||
continue
|
continue
|
||||||
var healed: int = oc._enemy_system.heal(hu, layers)
|
var healed: int = units.heal(hu, layers)
|
||||||
oc._emit_row({"type": "heal", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 回复", healed, {"target": "hero"})
|
emit_row_cb.call({"type": "heal", "source_class": "fx_board_refresh", "origin_cell": Vector2i(-1, -1)}, ri, "坑道小憩 回复", healed, {"target": "hero"})
|
||||||
|
|
||||||
|
|
||||||
# round_end blast 通道反打单(per-counter 积层读数×value=反打总量;source_class 用中性名,
|
# round_end blast 通道反打单(per-counter 积层读数×value=反打总量;source_class 用中性名,
|
||||||
@@ -310,10 +332,10 @@ func _round_end_blast_tickets(re_fx: Array, ri: int) -> void:
|
|||||||
var mg_layers := EffectEngine.counter(fx_host.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
|
var mg_total := int(h.get("value", 0)) * mg_layers
|
||||||
if mg_total > 0:
|
if mg_total > 0:
|
||||||
var mg_t := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_blast_counter",
|
var mg_t := damage.open_ticket(DamageController.KIND_BOARD_BLAST, "fx_blast_counter",
|
||||||
"积层反噬 ×%d" % mg_layers,
|
"积层反噬 ×%d" % mg_layers,
|
||||||
{"channel": "board", "base_value": mg_total, "target_rule": DamageController.TARGET_ALL_ALIVE,
|
{"channel": "board", "base_value": mg_total, "target_rule": DamageController.TARGET_ALL_ALIVE,
|
||||||
"ri": ri, "origin_cell": oc.minesweeper.hero_pos()})
|
"ri": ri, "origin_cell": minesweeper.hero_pos()})
|
||||||
reward_grant.submit(mg_t, false)
|
reward_grant.submit(mg_t, false)
|
||||||
|
|
||||||
|
|
||||||
@@ -329,12 +351,12 @@ func _judge_seal(evt: Dictionary, ri: int) -> void:
|
|||||||
# 殉爆票据 → 施加面 → 雷区图谱额外兑付 → 基线正向兑付)
|
# 殉爆票据 → 施加面 → 雷区图谱额外兑付 → 基线正向兑付)
|
||||||
func _judge_seal_hit(evt: Dictionary, ri: int) -> void:
|
func _judge_seal_hit(evt: Dictionary, ri: int) -> void:
|
||||||
var cell: Vector2i = evt.cell
|
var cell: Vector2i = evt.cell
|
||||||
if oc._facts.seal_consumed.has(cell):
|
if facts.seal_consumed.has(cell):
|
||||||
return # 去重跳过;零明细行
|
return # 去重跳过;零明细行
|
||||||
oc._facts.seal_consumed[cell] = true
|
facts.seal_consumed[cell] = true
|
||||||
oc._facts.mark_perfect_hit()
|
facts.mark_perfect_hit()
|
||||||
var fh_hits := fx_host.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)})
|
"terrain_kind": minesweeper.harmful_adjacent(cell)})
|
||||||
_seal_burst_tickets(fh_hits, cell, ri)
|
_seal_burst_tickets(fh_hits, cell, ri)
|
||||||
_seal_hit_effects(fh_hits, cell, ri)
|
_seal_hit_effects(fh_hits, cell, ri)
|
||||||
# 雷区图谱(封印流——标中雷额外获得一份格面资源:封印开始产出对标开格;
|
# 雷区图谱(封印流——标中雷额外获得一份格面资源:封印开始产出对标开格;
|
||||||
@@ -342,7 +364,7 @@ func _judge_seal_hit(evt: Dictionary, ri: int) -> void:
|
|||||||
for h in fh_hits:
|
for h in fh_hits:
|
||||||
if String(h.get("kind", "")) == "grant_material":
|
if String(h.get("kind", "")) == "grant_material":
|
||||||
_apply_material_hit(h, cell, ri, "seal_hit")
|
_apply_material_hit(h, cell, ri, "seal_hit")
|
||||||
var snap = oc._reward_economy.grant(cell)
|
var snap = reward_economy.grant(cell)
|
||||||
if not (snap is Dictionary):
|
if not (snap is Dictionary):
|
||||||
return
|
return
|
||||||
reward_grant.grant_routed(snap, "positive", {"source_class": "seal_hit", "cell": cell, "ri": ri, "highlight": "seal_hit"})
|
reward_grant.grant_routed(snap, "positive", {"source_class": "seal_hit", "cell": cell, "ri": ri, "highlight": "seal_hit"})
|
||||||
@@ -351,10 +373,10 @@ func _judge_seal_hit(evt: Dictionary, ri: int) -> void:
|
|||||||
# 标中殉爆票据(行 triggers open_ticket channel blast value_from magnitude——每标中一面封印全体存活敌
|
# 标中殉爆票据(行 triggers open_ticket channel blast value_from magnitude——每标中一面封印全体存活敌
|
||||||
# 吃 N 群伤,无类型走 blast 通道;封印爆裂〔专长〕同面消费)
|
# 吃 N 群伤,无类型走 blast 通道;封印爆裂〔专长〕同面消费)
|
||||||
func _seal_burst_tickets(fh_hits: Array, cell: Vector2i, ri: int) -> void:
|
func _seal_burst_tickets(fh_hits: Array, cell: Vector2i, ri: int) -> void:
|
||||||
for h in oc._hits_of(fh_hits, "open_ticket"):
|
for h in DamageController.hits_of(fh_hits, "open_ticket"):
|
||||||
if String(h.get("channel", "")) != "blast":
|
if String(h.get("channel", "")) != "blast":
|
||||||
continue
|
continue
|
||||||
var fbt := oc._damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_seal_burst", "封印爆裂",
|
var fbt := damage.open_ticket(DamageController.KIND_BOARD_BLAST, "feat_seal_burst", "封印爆裂",
|
||||||
{"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE,
|
{"channel": "board", "base_value": int(h.get("value", 0)), "target_rule": DamageController.TARGET_ALL_ALIVE,
|
||||||
"ri": ri, "origin_cell": cell, "highlight": "highlight"})
|
"ri": ri, "origin_cell": cell, "highlight": "highlight"})
|
||||||
reward_grant.submit(fbt, false)
|
reward_grant.submit(fbt, false)
|
||||||
@@ -373,37 +395,36 @@ func _seal_hit_effects(fh_hits: Array, cell: Vector2i, ri: int) -> void:
|
|||||||
fx_host.apply_ac_bonus(h)
|
fx_host.apply_ac_bonus(h)
|
||||||
"bonus_delta":
|
"bonus_delta":
|
||||||
var axis_f := String(h.get("axis", ""))
|
var axis_f := String(h.get("axis", ""))
|
||||||
if fx_host._fx_bonus_delta.has(axis_f):
|
fx_host.add_bonus_delta(axis_f, int(h.get("delta", 0)))
|
||||||
fx_host._fx_bonus_delta[axis_f] += int(h.get("delta", 0))
|
|
||||||
"remove_mines":
|
"remove_mines":
|
||||||
var removed: Array[Variant] = oc.minesweeper.remove_mines_in([cell])
|
var removed: Array[Variant] = minesweeper.remove_mines_in([cell])
|
||||||
if not removed.is_empty():
|
if not removed.is_empty():
|
||||||
oc._emit_row({"type": "extended", "source_class": "fx_seal_smith", "origin_cell": cell}, ri, "封印匠·危险格中立移除", 0, {"effect_id": "mine_neutralized"})
|
emit_row_cb.call({"type": "extended", "source_class": "fx_seal_smith", "origin_cell": cell}, ri, "封印匠·危险格中立移除", 0, {"effect_id": "mine_neutralized"})
|
||||||
"preview_area":
|
"preview_area":
|
||||||
_seal_preview_area(cell, ri)
|
_seal_preview_area(cell, ri)
|
||||||
|
|
||||||
|
|
||||||
# 示警烟火施加面(3×3 邻格资源类型预告——类型去重集入 overlay;信息呈现零回写)
|
# 示警烟火施加面(3×3 邻格资源类型预告——类型去重集入 overlay;信息呈现零回写)
|
||||||
func _seal_preview_area(cell: Vector2i, ri: int) -> void:
|
func _seal_preview_area(cell: Vector2i, ri: int) -> void:
|
||||||
var board_p: BoardData = oc.minesweeper.current_board()
|
var board_p: BoardData = minesweeper.current_board()
|
||||||
var types: Array[Variant] = []
|
var types: Array[Variant] = []
|
||||||
for n in board_p.neighbors8(cell):
|
for n in board_p.neighbors8(cell):
|
||||||
var n_snap = oc._reward_economy.query_full_snapshot(n)
|
var n_snap = reward_economy.query_full_snapshot(n)
|
||||||
if n_snap is Dictionary and not types.has(String((n_snap as Dictionary).get("cell_type", ""))):
|
if n_snap is Dictionary and not types.has(String((n_snap as Dictionary).get("cell_type", ""))):
|
||||||
types.append(String((n_snap as Dictionary).get("cell_type", "")))
|
types.append(String((n_snap as Dictionary).get("cell_type", "")))
|
||||||
if reward_grant.treasure_inventory != null and not types.is_empty():
|
if reward_grant.treasure_inventory != null and not types.is_empty():
|
||||||
reward_grant.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell))
|
reward_grant.treasure_inventory.apply_overlay("reward_preview", board_p.neighbors8(cell))
|
||||||
oc._emit_row({"type": "extended", "source_class": "fx_warning_beacon", "origin_cell": cell}, ri, "示警烟火·周围资源类型", 0, {"effect_id": "fx_preview_area", "types": types})
|
emit_row_cb.call({"type": "extended", "source_class": "fx_warning_beacon", "origin_cell": cell}, ri, "示警烟火·周围资源类型", 0, {"effect_id": "fx_preview_area", "types": types})
|
||||||
|
|
||||||
|
|
||||||
# 封印未中(每格实例一次去重——归因行与负向兑付同键,防逐轮惩罚轰炸;seal_missed 发射序:
|
# 封印未中(每格实例一次去重——归因行与负向兑付同键,防逐轮惩罚轰炸;seal_missed 发射序:
|
||||||
# grant_ap mode:queue 下一轮兑现 / forgive_seal_miss 完美保险豁免 → 归因行 → 负向兑付)
|
# grant_ap mode:queue 下一轮兑现 / forgive_seal_miss 完美保险豁免 → 归因行 → 负向兑付)
|
||||||
func _judge_seal_miss(evt: Dictionary, ri: int) -> void:
|
func _judge_seal_miss(evt: Dictionary, ri: int) -> void:
|
||||||
var cell: Vector2i = evt.cell
|
var cell: Vector2i = evt.cell
|
||||||
oc._facts.mark_perfect_miss()
|
facts.mark_perfect_miss()
|
||||||
if oc._facts.seal_miss_seen.has(cell):
|
if facts.seal_miss_seen.has(cell):
|
||||||
return # 每格实例一次去重
|
return # 每格实例一次去重
|
||||||
oc._facts.seal_miss_seen[cell] = true
|
facts.seal_miss_seen[cell] = true
|
||||||
# 危险直觉·守挂点(seal_missed 发射:契约 12 触发主语=「当回合发生的标错
|
# 危险直觉·守挂点(seal_missed 发射:契约 12 触发主语=「当回合发生的标错
|
||||||
# 动作」——去重闸=新旧标错判别器〔后续轮重判旧封印被闸挡回零触发〕;双条件②「结算时封印仍在
|
# 动作」——去重闸=新旧标错判别器〔后续轮重判旧封印被闸挡回零触发〕;双条件②「结算时封印仍在
|
||||||
# 场」由发射面既有过滤承载〔被挖开的封印已随挖掘擦除零兑付〕;grant_ap mode:queue 下一轮
|
# 场」由发射面既有过滤承载〔被挖开的封印已随挖掘擦除零兑付〕;grant_ap mode:queue 下一轮
|
||||||
@@ -411,17 +432,17 @@ func _judge_seal_miss(evt: Dictionary, ri: int) -> void:
|
|||||||
# adventure 域计数器闸)
|
# adventure 域计数器闸)
|
||||||
for h in fx_host.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":
|
if String(h.get("kind", "")) == "grant_ap" and String(h.get("mode", "")) == "queue":
|
||||||
oc.action_economy.queue_ap_delta(int(h.get("value", 0)))
|
action_economy.queue_ap_delta(int(h.get("value", 0)))
|
||||||
elif String(h.get("kind", "")) == "forgive_seal_miss":
|
elif String(h.get("kind", "")) == "forgive_seal_miss":
|
||||||
# 完美保险(订阅式豁免:本战首次标错计一次豁免,只累加自有账
|
# 完美保险(订阅式豁免:本战首次标错计一次豁免,只累加自有账
|
||||||
# _perfect_forgiven,绝不回写 _perfect_hit_false=契约 2 计数纯净;发射时机与消费点零改动=契约 3)
|
# _perfect_forgiven,绝不回写 _perfect_hit_false=契约 2 计数纯净;发射时机与消费点零改动=契约 3)
|
||||||
oc._facts.mark_perfect_forgiven()
|
facts.mark_perfect_forgiven()
|
||||||
_apply_counter_hit(h) # 豁免同时记账(insurance_used battle 域——下次标错 when lt 1 不再命中)
|
_apply_counter_hit(h) # 豁免同时记账(insurance_used battle 域——下次标错 when lt 1 不再命中)
|
||||||
oc._emit_row({"type": "extended", "source_class": "seal_miss", "origin_cell": cell}, ri, "完美保险·标错豁免", 0,
|
emit_row_cb.call({"type": "extended", "source_class": "seal_miss", "origin_cell": cell}, ri, "完美保险·标错豁免", 0,
|
||||||
{"effect_id": "insurance_used", "forgiven": oc._facts.perfect_forgiven})
|
{"effect_id": "insurance_used", "forgiven": facts.perfect_forgiven})
|
||||||
var hl := "perfect_broken" if oc._facts.perfect_hit_false - oc._facts.perfect_forgiven == 1 else ""
|
var hl := "perfect_broken" if facts.perfect_hit_false - facts.perfect_forgiven == 1 else ""
|
||||||
oc._emit_row({"type": "seal_miss", "source_class": "seal_miss", "origin_cell": cell, "highlight": hl}, ri, "封印未中", 0, {})
|
emit_row_cb.call({"type": "seal_miss", "source_class": "seal_miss", "origin_cell": cell, "highlight": hl}, ri, "封印未中", 0, {})
|
||||||
var snap = oc._reward_economy.grant(cell)
|
var snap = reward_economy.grant(cell)
|
||||||
if not (snap is Dictionary):
|
if not (snap is Dictionary):
|
||||||
return
|
return
|
||||||
reward_grant.grant_routed(snap, "negative", {"source_class": "seal_miss", "cell": cell, "ri": ri})
|
reward_grant.grant_routed(snap, "negative", {"source_class": "seal_miss", "cell": cell, "ri": ri})
|
||||||
@@ -435,6 +456,6 @@ func _apply_counter_hit(h: Dictionary) -> void:
|
|||||||
|
|
||||||
# grant_material 兑付块单点(三位分支本体逐字收拢——source_class 两态 safe_cell/seal_hit 由参承载)
|
# grant_material 兑付块单点(三位分支本体逐字收拢——source_class 两态 safe_cell/seal_hit 由参承载)
|
||||||
func _apply_material_hit(h: Dictionary, cell: Vector2i, ri: int, source_class: String = "safe_cell") -> void:
|
func _apply_material_hit(h: Dictionary, cell: Vector2i, ri: int, source_class: String = "safe_cell") -> void:
|
||||||
var g_snap = oc._reward_economy.grant(cell)
|
var g_snap = reward_economy.grant(cell)
|
||||||
if g_snap is Dictionary:
|
if g_snap is Dictionary:
|
||||||
reward_grant.grant_routed(g_snap, "positive", {"source_class": source_class, "cell": cell, "ri": ri, "label_suffix": 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", ""))})
|
||||||
@@ -1,15 +1,26 @@
|
|||||||
## 轮循环驱动件:每轮结算周期的唯一驱动——结算重入门闸(settling)、五段段序执行
|
## 轮循环驱动件:每轮结算周期的唯一驱动——结算重入门闸(settling)、五段段序执行
|
||||||
## (段序骨架冻结于 SettlementTable.segment_order)、开轮摇号(共识点 B:技能选择前置到轮次开始,
|
## (段序骨架冻结于 SettlementTable.segment_order)、开轮摇号(共识点 B:技能选择前置到轮次开始,
|
||||||
## 摇定即本拍结算的选技)、踩雷计数账(转存时点=摇号轮末)。
|
## 摇定即本拍结算的选技)、踩雷计数账(转存时点=摇号轮末)、换图刷新流程件持有与交接。
|
||||||
## 单一变化原因:轮结算周期的推进结构(段序、重入闸、摇号时序)。
|
## 单一变化原因:轮结算周期的推进结构(段序、重入闸、摇号时序)。
|
||||||
## 耦合边界:oc=宿主引用(五段段类既定同款直调形态)——经 oc 取 facts/协作件/行发射/出口分发;
|
## 耦合边界(5.1 封装修复换代):引用束注入——构造收编排器组装的 20 键封闭束(六子对象+协作件
|
||||||
## 五段构造参数与持有形态不变(grant/combat 三引用、aura/decay/verdict 单引用);判定段零改动
|
## 引用+读数引用+七回调+两读投影),解包自用七键(facts/units/fx_host/db/action_economy/rng/
|
||||||
## (_exit_distribution 留宿主);随机只经 oc.rng 的 behavior_roll 封闭流(流名、开流时点、取号序逐字);
|
## emit_row_cb)后原束分发五段(各段同束自取所需),段类对编排器零引用;变值标量
|
||||||
## 零信号连接、零 await;随编排器每战新建同灭。
|
## (battle_result/round_index)走调用参数现值交付(settle_if_due/roll_round_actions 签名扩展);
|
||||||
|
## 换图刷新流程件=本件持有:五段构造后复制束写入 grant_seg 段间引用再建 BattleRefreshFlow
|
||||||
|
## (授予段引用直接交换——不再穿编排器链式取段)。随机只经注入 rng 的 behavior_roll 封闭流
|
||||||
|
## (流名、开流时点、取号序逐字);零信号连接、零 await;随编排器每战新建同灭。
|
||||||
|
## 类变量封闭面(15≤15 贴顶禁再增):束解包 7(facts/units/fx_host/db/action_economy/rng/emit_row_cb)
|
||||||
|
## +mines_hits+五段+_settling+_refresh_flow。
|
||||||
class_name RoundCycle
|
class_name RoundCycle
|
||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
var oc: BattleOrchestrator # 宿主引用(段类同款直调——facts/协作件/行发射/出口分发取数单点)
|
var facts: BattleFacts # 战斗事实件(受理事实/轮缓冲/封印账/溯源/条件计数)
|
||||||
|
var units: UnitContainer # 单位容器(摇号/解锁/冷却消费面)
|
||||||
|
var fx_host: BattleFxHost # 效果宿主件(enemy_hp_threshold 发射面)
|
||||||
|
var db: ConfigDb
|
||||||
|
var action_economy: ActionEconomy
|
||||||
|
var rng: RngServiceCore
|
||||||
|
var emit_row_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形)
|
||||||
var mines_hits: Dictionary[Variant, Variant] = {"total": 0, "last": 0, "round": 0} # 踩雷计数(本场累计/上轮转存/当轮累计三读数合一账)
|
var mines_hits: Dictionary[Variant, Variant] = {"total": 0, "last": 0, "round": 0} # 踩雷计数(本场累计/上轮转存/当轮累计三读数合一账)
|
||||||
var _grant_seg = null # GrantSegment(①授予段——含封印判与 counter/grant 助手单点)
|
var _grant_seg = null # GrantSegment(①授予段——含封印判与 counter/grant 助手单点)
|
||||||
var _aura_seg = null # AuraSegment(②增益/减益段)
|
var _aura_seg = null # AuraSegment(②增益/减益段)
|
||||||
@@ -17,27 +28,40 @@ var _combat_seg = null # CombatSegment(③战斗段)
|
|||||||
var _decay_seg = null # DecaySegment(④衰减段)
|
var _decay_seg = null # DecaySegment(④衰减段)
|
||||||
var _verdict_seg = null # VerdictSegment(⑤判定段——返回裁定,终局早退语义归宿主)
|
var _verdict_seg = null # VerdictSegment(⑤判定段——返回裁定,终局早退语义归宿主)
|
||||||
var _settling := false
|
var _settling := false
|
||||||
|
var _refresh_flow: BattleRefreshFlow # 换图刷新流程件(本件持有——编排器两公开方法单行转发经此)
|
||||||
|
|
||||||
|
|
||||||
func _init(host: BattleOrchestrator, fx_host_ref: BattleFxHost, reward_grant_ref: RewardGrant) -> void:
|
func _init(bundle: Dictionary) -> void:
|
||||||
oc = host
|
for k in ["facts", "units", "fx_host", "db", "action_economy", "rng", "emit_row_cb"]:
|
||||||
# 结算五段接线(授予/战斗两段持三引用=宿主+两协作件;信号连接纪律不涉段〔段零信号连接〕——五行自宿主 _init 逐字迁入)
|
assert(bundle.has(k), "[轮循环] 引用束缺键: %s" % k)
|
||||||
_grant_seg = GrantSegment.new(oc, fx_host_ref, reward_grant_ref)
|
facts = bundle.facts
|
||||||
_aura_seg = AuraSegment.new(oc)
|
units = bundle.units
|
||||||
_combat_seg = CombatSegment.new(oc, fx_host_ref, reward_grant_ref)
|
fx_host = bundle.fx_host
|
||||||
_decay_seg = DecaySegment.new(oc)
|
db = bundle.db
|
||||||
_verdict_seg = VerdictSegment.new(oc)
|
action_economy = bundle.action_economy
|
||||||
|
rng = bundle.rng
|
||||||
|
emit_row_cb = bundle.emit_row_cb
|
||||||
|
# 结算五段接线(五段同束自取所需;信号连接纪律不涉段〔段零信号连接〕)
|
||||||
|
_grant_seg = GrantSegment.new(bundle)
|
||||||
|
_aura_seg = AuraSegment.new(bundle)
|
||||||
|
_combat_seg = CombatSegment.new(bundle)
|
||||||
|
_decay_seg = DecaySegment.new(bundle)
|
||||||
|
_verdict_seg = VerdictSegment.new(bundle)
|
||||||
|
# 换图刷新流程件(段间引用交付:复制束写入授予段引用——授予段直接交换图流程件,
|
||||||
|
# 编排器 _rounds._grant_seg 跨两层链式取段随之消灭)
|
||||||
|
var flow_bundle: Dictionary[Variant, Variant] = bundle.duplicate()
|
||||||
|
flow_bundle["grant_seg"] = _grant_seg
|
||||||
|
_refresh_flow = BattleRefreshFlow.new(flow_bundle)
|
||||||
|
|
||||||
|
|
||||||
# 结算重入门闸+段序体(原宿主 _maybe_settle 三闸与 _settle 合体——闸序 settling→battle_result→phase
|
# 结算重入门闸+段序体(原宿主 _maybe_settle 三闸与 _settle 合体——闸序 settling→battle_result→phase
|
||||||
# 逐字;终局早退不回置 _settling,_battle_over 接管)
|
# 逐字;终局早退不回置 _settling,_battle_over 接管。battle_result/round_index=调用参数现值交付)
|
||||||
func settle_if_due() -> void:
|
func settle_if_due(battle_result: String, ri: int) -> void:
|
||||||
if _settling or oc.battle_result != "":
|
if _settling or battle_result != "":
|
||||||
return
|
return
|
||||||
if oc.action_economy.phase != oc.action_economy.PHASE_WAITING:
|
if action_economy.phase != action_economy.PHASE_WAITING:
|
||||||
return
|
return
|
||||||
_settling = true
|
_settling = true
|
||||||
var ri := oc.round_index
|
|
||||||
# 段序编排(五段——段体归 game/core/settlement/,宿主只做行发射/开单原语单点)
|
# 段序编排(五段——段体归 game/core/settlement/,宿主只做行发射/开单原语单点)
|
||||||
_grant_seg.run(ri) # ① 授予段(案C 闸随段装载)
|
_grant_seg.run(ri) # ① 授予段(案C 闸随段装载)
|
||||||
_aura_seg.run(ri) # ② 增益/减益段(续存实例组合行)
|
_aura_seg.run(ri) # ② 增益/减益段(续存实例组合行)
|
||||||
@@ -53,31 +77,36 @@ func is_settling() -> bool:
|
|||||||
return _settling
|
return _settling
|
||||||
|
|
||||||
|
|
||||||
|
# 换图刷新流程件读出口(编排器 can_refresh/player_request_refresh 两公开方法单行转发经此)
|
||||||
|
func refresh_flow() -> BattleRefreshFlow:
|
||||||
|
return _refresh_flow
|
||||||
|
|
||||||
|
|
||||||
# ---- 开轮摇号(共识点 B:技能选择前置到轮次开始,数值计算留结算段——原宿主函数逐字迁入)----
|
# ---- 开轮摇号(共识点 B:技能选择前置到轮次开始,数值计算留结算段——原宿主函数逐字迁入)----
|
||||||
# 每实例:保底判定(连续执行普攻 ≥ M → 必出特殊技、特殊技内按权重、不受冷却限制)→ 排除冷却中技能
|
# 每实例:保底判定(连续执行普攻 ≥ M → 必出特殊技、特殊技内按权重、不受冷却限制)→ 排除冷却中技能
|
||||||
# (中签率 = 权重 ÷ 可摇权重合计——份额归一)→ 按行序逐敌从统一流顺序取号摇定本拍动作。
|
# (中签率 = 权重 ÷ 可摇权重合计——份额归一)→ 按行序逐敌从统一流顺序取号摇定本拍动作。
|
||||||
# 随机消费只在换算层(本方法 + SkillLottery);单位容器收确定值(零随机纪律——armor_roll 先例同构)。
|
# 随机消费只在换算层(本方法 + SkillLottery);单位容器收确定值(零随机纪律——armor_roll 先例同构)。
|
||||||
# 统一流:behavior_roll 无子流派生向量(位置交错,单点定位由事件日志承载)。
|
# 统一流:behavior_roll 无子流派生向量(位置交错,单点定位由事件日志承载)。
|
||||||
# 摇定后、兑现前死亡 → 本拍技能作废。
|
# 摇定后、兑现前死亡 → 本拍技能作废。round_index=调用参数现值交付。
|
||||||
func roll_round_actions() -> void:
|
func roll_round_actions(ri: int) -> void:
|
||||||
var pity_m: int = int(oc.db.combat.skill.pity_threshold)
|
var pity_m: int = int(db.combat.skill.pity_threshold)
|
||||||
# facts 组包(开轮摇号前一次成型——last_round_end 空字典首轮映射 none 唯一落点;hero 事实不进 facts,
|
# facts 组包(开轮摇号前一次成型——last_round_end 空字典首轮映射 none 唯一落点;hero 事实不进 facts,
|
||||||
# 容器内直读主角行)
|
# 容器内直读主角行)
|
||||||
var facts := {
|
var facts_pack := {
|
||||||
"round_index": oc.round_index,
|
"round_index": ri,
|
||||||
"last_round_end": "none" if oc.action_economy.last_round_end().is_empty() else String(oc.action_economy.last_round_end().get("path_tag", "none")),
|
"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_last": int(mines_hits["last"]),
|
||||||
"mines_hit_total": int(mines_hits["total"]),
|
"mines_hit_total": int(mines_hits["total"]),
|
||||||
}
|
}
|
||||||
for u in oc._enemy_system.query_alive("enemy"):
|
for u in units.query_alive("enemy"):
|
||||||
# Boss 半血解锁闩(迭代14④ T4——开轮摇号前逐敌检查;一次过门单向闩;解锁出一次归因明细行
|
# Boss 半血解锁闩(迭代14④ T4——开轮摇号前逐敌检查;一次过门单向闩;解锁出一次归因明细行
|
||||||
# 「XX 解锁了 YY」带 skill_unlock 高光键〔type=highlight 既有行型零新增〕,呈现层弹一次性
|
# 「XX 解锁了 YY」带 skill_unlock 高光键〔type=highlight 既有行型零新增〕,呈现层弹一次性
|
||||||
# 技能名提示〔轻量 toast 非演出——D8 呈现面〕。门=确定性谓词,机制面零随机,摇号随机
|
# 技能名提示〔轻量 toast 非演出——D8 呈现面〕。门=确定性谓词,机制面零随机,摇号随机
|
||||||
# 消费仍在 behavior_roll 既有流——同种子逐位复现不破坏)
|
# 消费仍在 behavior_roll 既有流——同种子逐位复现不破坏)
|
||||||
for sid in oc._enemy_system.check_trigger_unlocks(u, facts):
|
for sid in units.check_trigger_unlocks(u, facts_pack):
|
||||||
var srow_u: Dictionary[Variant, Variant] = oc.db.combat.skill.row_by_id(sid)
|
var srow_u: Dictionary[Variant, Variant] = db.combat.skill.row_by_id(sid)
|
||||||
oc._emit_row({"type": "highlight", "source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "highlight": "skill_unlock"},
|
emit_row_cb.call({"type": "highlight", "source_class": "enemy_action", "origin_cell": Vector2i(-1, -1), "highlight": "skill_unlock"},
|
||||||
oc.round_index, "%s 解锁了 %s" % [String(u.display_name), String(srow_u.get("display_name", sid))],
|
ri, "%s 解锁了 %s" % [String(u.display_name), String(srow_u.get("display_name", sid))],
|
||||||
0, {"skill_id": sid})
|
0, {"skill_id": sid})
|
||||||
# enemy_hp_threshold 发射点(内容数据驱动扩展批 T1——血量阈值沿;对所有带该字段敌人逐个发射,
|
# enemy_hp_threshold 发射点(内容数据驱动扩展批 T1——血量阈值沿;对所有带该字段敌人逐个发射,
|
||||||
# 非 Boss 专属——Boss 语义留给订阅者 when 判定。pct=本次解锁条目的 trigger 行值)
|
# 非 Boss 专属——Boss 语义留给订阅者 when 判定。pct=本次解锁条目的 trigger 行值)
|
||||||
@@ -89,18 +118,18 @@ func roll_round_actions() -> void:
|
|||||||
break
|
break
|
||||||
# target_attack_bonus 施加面(处刑台:敌血量阈值沿挂单位字典标记,
|
# target_attack_bonus 施加面(处刑台:敌血量阈值沿挂单位字典标记,
|
||||||
# 主角攻击面 _resolve_hero_attack 通用消费——fx_atk_bonus 键加算进 base_value)
|
# 主角攻击面 _resolve_hero_attack 通用消费——fx_atk_bonus 键加算进 base_value)
|
||||||
var eht_hits := oc._fx_host.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(oc._facts.encounter.get("node_type", "")) == "boss"})
|
"is_boss": String(facts.encounter.get("node_type", "")) == "boss"})
|
||||||
for h in eht_hits:
|
for h in eht_hits:
|
||||||
if String(h.get("kind", "")) == "target_attack_bonus":
|
if String(h.get("kind", "")) == "target_attack_bonus":
|
||||||
u["fx_atk_bonus"] = int(u.get("fx_atk_bonus", 0)) + int(h.get("value", 0))
|
u["fx_atk_bonus"] = int(u.get("fx_atk_bonus", 0)) + int(h.get("value", 0))
|
||||||
var entries: Array[Variant] = oc._enemy_system.rollable_skills(u, facts)
|
var entries: Array[Variant] = units.rollable_skills(u, facts_pack)
|
||||||
if oc._enemy_system.basic_streak(u) >= pity_m:
|
if units.basic_streak(u) >= pity_m:
|
||||||
var specials: Array[Variant] = oc._enemy_system.special_skills(u, facts)
|
var specials: Array[Variant] = units.special_skills(u, facts_pack)
|
||||||
if not specials.is_empty():
|
if not specials.is_empty():
|
||||||
entries = specials # 保底命中——普攻恒可摇 → 非保底路径池恒非空,此为唯一替代集合
|
entries = specials # 保底命中——普攻恒可摇 → 非保底路径池恒非空,此为唯一替代集合
|
||||||
var stream: RandomNumberGenerator = oc.rng.open_stream("behavior_roll")
|
var stream: RandomNumberGenerator = rng.open_stream("behavior_roll")
|
||||||
oc._enemy_system.set_pending_action(u, SkillLottery.draw(entries, stream))
|
units.set_pending_action(u, SkillLottery.draw(entries, stream))
|
||||||
oc._enemy_system.tick_cooldowns() # 递减在摇号读值之后——"施放后连续 N 拍不可摇中"的等价实现(单位容器 注记)
|
units.tick_cooldowns() # 递减在摇号读值之后——"施放后连续 N 拍不可摇中"的等价实现(单位容器 注记)
|
||||||
mines_hits["last"] = mines_hits["round"] # 轮次真正结束转存(轮内部分结算不更新)
|
mines_hits["last"] = mines_hits["round"] # 轮次真正结束转存(轮内部分结算不更新)
|
||||||
mines_hits["round"] = 0
|
mines_hits["round"] = 0
|
||||||
@@ -3,31 +3,45 @@ extends RefCounted
|
|||||||
# ⑤ 判定段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。
|
# ⑤ 判定段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。
|
||||||
# 职责:防御性优先级 hero 先判(双死结构不可达的纵深防御)→ 全量状态快照(despawn 前
|
# 职责:防御性优先级 hero 先判(双死结构不可达的纵深防御)→ 全量状态快照(despawn 前
|
||||||
# 组装——终局含 result)→ 终局行+收束分派 / 下一轮出口分发。
|
# 组装——终局含 result)→ 终局行+收束分派 / 下一轮出口分发。
|
||||||
|
# 耦合边界(5.1 封装修复换代):引用束注入——构造收编排器组装的 20 键封闭束,本段断言 5 键
|
||||||
|
# (units/emit_row_cb/emit_snapshot_cb/battle_over_cb/exit_distribution_cb)解包;行发射/快照/
|
||||||
|
# 终局收束经回调回宿主保留面(exit_distribution 轮推进保留现设计——七段结构冻结)——
|
||||||
|
# 对编排器零引用。
|
||||||
# 返回值 = 裁定("victory"/"defeat"/"continue")——宿主 _settle 据此保持旧早退语义
|
# 返回值 = 裁定("victory"/"defeat"/"continue")——宿主 _settle 据此保持旧早退语义
|
||||||
#(终局不回置 _settling,_battle_over 接管);本段零算法改动(🔴 零行为变更契约)。
|
#(终局不回置 _settling,_battle_over 接管);本段零算法改动(🔴 零行为变更契约)。
|
||||||
|
|
||||||
var oc: BattleOrchestrator # 宿主(类型标注=段内 := 推断前提;循环类名引用 Godot 4 全局类安全) 宿主
|
var units: UnitContainer # 单位容器(hero 存活/敌存活判定)
|
||||||
|
var emit_row_cb: Callable # 宿主 _emit_row——行协议面单点(5 参 head 束形)
|
||||||
|
var emit_snapshot_cb: Callable # 宿主 _emit_settlement_snapshot——结算快照发射(保留面)
|
||||||
|
var battle_over_cb: Callable # 宿主 _battle_over——终局收束(保留面)
|
||||||
|
var exit_distribution_cb: Callable # 宿主 _exit_distribution——下一轮出口分发(保留面)
|
||||||
|
|
||||||
|
|
||||||
func _init(host) -> void:
|
func _init(bundle: Dictionary) -> void:
|
||||||
oc = host
|
for k in ["units", "emit_row_cb", "emit_snapshot_cb", "battle_over_cb", "exit_distribution_cb"]:
|
||||||
|
assert(bundle.has(k), "[判定段] 引用束缺键: %s" % k)
|
||||||
|
units = bundle.units
|
||||||
|
emit_row_cb = bundle.emit_row_cb
|
||||||
|
emit_snapshot_cb = bundle.emit_snapshot_cb
|
||||||
|
battle_over_cb = bundle.battle_over_cb
|
||||||
|
exit_distribution_cb = bundle.exit_distribution_cb
|
||||||
|
|
||||||
|
|
||||||
func run(ri: int) -> String:
|
func run(ri: int) -> String:
|
||||||
var verdict := "continue"
|
var verdict := "continue"
|
||||||
if int(oc._enemy_system.hero_unit().get("hp", 0)) <= 0:
|
if int(units.hero_unit().get("hp", 0)) <= 0:
|
||||||
verdict = "defeat"
|
verdict = "defeat"
|
||||||
elif oc._enemy_system.query_alive("enemy").is_empty():
|
elif units.query_alive("enemy").is_empty():
|
||||||
verdict = "victory"
|
verdict = "victory"
|
||||||
oc._emit_settlement_snapshot(verdict) # 全量状态快照(despawn 前组装——终局含 result)
|
emit_snapshot_cb.call(verdict) # 全量状态快照(despawn 前组装——终局含 result)
|
||||||
if verdict == "defeat":
|
if verdict == "defeat":
|
||||||
oc._emit_row({"type": "verdict", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "战斗失败", 0, {})
|
emit_row_cb.call({"type": "verdict", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "战斗失败", 0, {})
|
||||||
oc._battle_over("defeat")
|
battle_over_cb.call("defeat")
|
||||||
return verdict
|
return verdict
|
||||||
if verdict == "victory":
|
if verdict == "victory":
|
||||||
oc._emit_row({"type": "verdict", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "战斗胜利", 1, {})
|
emit_row_cb.call({"type": "verdict", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "战斗胜利", 1, {})
|
||||||
oc._battle_over("victory")
|
battle_over_cb.call("victory")
|
||||||
return verdict
|
return verdict
|
||||||
oc._emit_row({"type": "verdict", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "下一轮次", 0, {})
|
emit_row_cb.call({"type": "verdict", "source_class": "", "origin_cell": Vector2i(-1, -1)}, ri, "下一轮次", 0, {})
|
||||||
oc._exit_distribution()
|
exit_distribution_cb.call()
|
||||||
return verdict
|
return verdict
|
||||||
+29
-24
@@ -1,6 +1,6 @@
|
|||||||
## 存档槽位文件域件(D3 拆件——自 save_manager 收编槽位族):槽位挂载与模板、激活与建删、
|
## 存档槽位文件域件(D3 拆件——自 save_manager 收编槽位族):槽位挂载与模板、激活与建删、
|
||||||
## 摘要窥视(peek 族)的唯一所有者。槽位态(路径/激活号/模板)随件承载;数据重置与读写盘
|
## 摘要窥视(peek 族)的唯一所有者。槽位态(路径/激活号/模板)随件承载;数据重置与读写盘
|
||||||
## 经注入的 save 内部协作家族(_reset_to_default/_load_parsed/_rename_overwrite)完成——
|
## 经注入的 save 公开协作家族(reset_to_default/data_snapshot/restore_data/rename_overwrite 等)完成——
|
||||||
## 本件零 _data 副本零缓存(load 整体换新不陈旧)。
|
## 本件零 _data 副本零缓存(load 整体换新不陈旧)。
|
||||||
class_name SlotStore
|
class_name SlotStore
|
||||||
extends RefCounted
|
extends RefCounted
|
||||||
@@ -23,9 +23,9 @@ func _init(save_ref, path: String = "") -> void:
|
|||||||
## 注入槽派生名时激活号同步记账,注入任意隔离路径时激活号 0(写盘仍合法——隔离面行为零变化)。
|
## 注入槽派生名时激活号同步记账,注入任意隔离路径时激活号 0(写盘仍合法——隔离面行为零变化)。
|
||||||
func attach_path(path: String) -> void:
|
func attach_path(path: String) -> void:
|
||||||
_path = path
|
_path = path
|
||||||
_save._read_only = false
|
_save.set_read_only(false)
|
||||||
_active_slot = _slot_index_of(path)
|
_active_slot = _slot_index_of(path)
|
||||||
_save._reset_to_default()
|
_save.reset_to_default()
|
||||||
_save.load_from_disk()
|
_save.load_from_disk()
|
||||||
|
|
||||||
|
|
||||||
@@ -35,8 +35,8 @@ func attach_slot_template(template: String) -> void:
|
|||||||
_slot_template = template
|
_slot_template = template
|
||||||
_path = ""
|
_path = ""
|
||||||
_active_slot = 0
|
_active_slot = 0
|
||||||
_save._read_only = false
|
_save.set_read_only(false)
|
||||||
_save._reset_to_default()
|
_save.reset_to_default()
|
||||||
|
|
||||||
|
|
||||||
# 槽文件路径唯一派生口(模板单点——禁第二处拼名)
|
# 槽文件路径唯一派生口(模板单点——禁第二处拼名)
|
||||||
@@ -54,6 +54,11 @@ func has_write_target() -> bool:
|
|||||||
return _path != ""
|
return _path != ""
|
||||||
|
|
||||||
|
|
||||||
|
# 写盘目标路径读投影(has_write_target 同族——save_manager 迁移口/写盘路径单点消费)
|
||||||
|
func active_path() -> String:
|
||||||
|
return _path
|
||||||
|
|
||||||
|
|
||||||
# 进档原语(sv5,契约 6/8):激活槽 N+读盘,复用 load_from_disk 迁移/兜底链按槽成立。
|
# 进档原语(sv5,契约 6/8):激活槽 N+读盘,复用 load_from_disk 迁移/兜底链按槽成立。
|
||||||
# 高版本拒读槽:激活不成立、恢复切换前三态(路径/激活号/禁写位/数据)——防误写他槽;
|
# 高版本拒读槽:激活不成立、恢复切换前三态(路径/激活号/禁写位/数据)——防误写他槽;
|
||||||
# 损坏槽:兜底链接管(.corrupt.bak 改名+空态继续,激活成立)。
|
# 损坏槽:兜底链接管(.corrupt.bak 改名+空态继续,激活成立)。
|
||||||
@@ -62,17 +67,17 @@ func activate_slot(slot: int) -> bool:
|
|||||||
return false
|
return false
|
||||||
var prev_path := _path
|
var prev_path := _path
|
||||||
var prev_slot := _active_slot
|
var prev_slot := _active_slot
|
||||||
var prev_ro: bool = _save._read_only
|
var prev_ro: bool = _save.read_only()
|
||||||
var prev_data = _save._data
|
var prev_data = _save.data_snapshot()
|
||||||
_path = slot_path(slot)
|
_path = slot_path(slot)
|
||||||
_active_slot = slot
|
_active_slot = slot
|
||||||
_save._read_only = false
|
_save.set_read_only(false)
|
||||||
_save._reset_to_default()
|
_save.reset_to_default()
|
||||||
if not _save.load_from_disk():
|
if not _save.load_from_disk():
|
||||||
_path = prev_path
|
_path = prev_path
|
||||||
_active_slot = prev_slot
|
_active_slot = prev_slot
|
||||||
_save._read_only = prev_ro
|
_save.set_read_only(prev_ro)
|
||||||
_save._data = prev_data
|
_save.restore_data(prev_data)
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -84,17 +89,17 @@ func create_slot(slot: int) -> bool:
|
|||||||
return false
|
return false
|
||||||
var prev_path := _path
|
var prev_path := _path
|
||||||
var prev_slot := _active_slot
|
var prev_slot := _active_slot
|
||||||
var prev_ro: bool = _save._read_only
|
var prev_ro: bool = _save.read_only()
|
||||||
var prev_data = _save._data
|
var prev_data = _save.data_snapshot()
|
||||||
_path = slot_path(slot)
|
_path = slot_path(slot)
|
||||||
_active_slot = slot
|
_active_slot = slot
|
||||||
_save._read_only = false
|
_save.set_read_only(false)
|
||||||
_save._reset_to_default()
|
_save.reset_to_default()
|
||||||
if not _save.write_to_disk():
|
if not _save.write_to_disk():
|
||||||
_path = prev_path
|
_path = prev_path
|
||||||
_active_slot = prev_slot
|
_active_slot = prev_slot
|
||||||
_save._read_only = prev_ro
|
_save.set_read_only(prev_ro)
|
||||||
_save._data = prev_data
|
_save.restore_data(prev_data)
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -116,7 +121,7 @@ func delete_slot(slot: int) -> bool:
|
|||||||
if _active_slot == slot:
|
if _active_slot == slot:
|
||||||
_path = ""
|
_path = ""
|
||||||
_active_slot = 0
|
_active_slot = 0
|
||||||
_save._reset_to_default()
|
_save.reset_to_default()
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
@@ -126,12 +131,12 @@ func delete_slot(slot: int) -> bool:
|
|||||||
func peek_slot_summary(slot: int) -> Dictionary:
|
func peek_slot_summary(slot: int) -> Dictionary:
|
||||||
if slot < 1 or slot > _save.SLOT_COUNT:
|
if slot < 1 or slot > _save.SLOT_COUNT:
|
||||||
return {"readable": false, "has_file": false, "keystone": 0, "cleared": 0, "total": 0} # 越界=非槽位,非空槽
|
return {"readable": false, "has_file": false, "keystone": 0, "cleared": 0, "total": 0} # 越界=非槽位,非空槽
|
||||||
return _save._peek_summary_at(slot_path(slot))
|
return _save.peek_summary_at(slot_path(slot))
|
||||||
|
|
||||||
|
|
||||||
# 窥视注入口(契约 7 隔离面同款——测试/探针经路径注入隔离,禁各自拼槽文件名)
|
# 窥视注入口(契约 7 隔离面同款——测试/探针经路径注入隔离,禁各自拼槽文件名)
|
||||||
func peek_summary_at(path: String) -> Dictionary:
|
func peek_summary_at(path: String) -> Dictionary:
|
||||||
return _save._peek_summary_at(path)
|
return _save.peek_summary_at(path)
|
||||||
|
|
||||||
|
|
||||||
# 注入路径的槽号反查(命中槽派生名返回槽号,否则 0——激活号记账用,写盘判定以 _path 非空为准)
|
# 注入路径的槽号反查(命中槽派生名返回槽号,否则 0——激活号记账用,写盘判定以 _path 非空为准)
|
||||||
@@ -146,8 +151,8 @@ func _slot_index_of(path: String) -> int:
|
|||||||
# 旧档字节原样落槽 1,读取时由正式进档路径的兜底/拒读链接管,不静默丢数据);
|
# 旧档字节原样落槽 1,读取时由正式进档路径的兜底/拒读链接管,不静默丢数据);
|
||||||
# dst 已存在不重迁(幂等判据——判别锚④:去判据则重复迁移覆盖槽 1);迁移成功后 src 改名
|
# dst 已存在不重迁(幂等判据——判别锚④:去判据则重复迁移覆盖槽 1);迁移成功后 src 改名
|
||||||
# bak 留痕(不删——处置字节留痕,防再次隐式读写)。任一步失败:源保留原地,下次启动重试。
|
# bak 留痕(不删——处置字节留痕,防再次隐式读写)。任一步失败:源保留原地,下次启动重试。
|
||||||
# tmp+rename 原子形态与 write_to_disk 同纪律(改名原语 _rename_overwrite 归 save IO 域单点)。生产调用=src/dst/bak 为 旧单档/槽 1/留痕名。
|
# tmp+rename 原子形态与 write_to_disk 同纪律(改名原语 rename_overwrite 归 save IO 域单点)。生产调用=src/dst/bak 为 旧单档/槽 1/留痕名。
|
||||||
func _migrate_legacy_save(src: String, dst: String, bak: String) -> void:
|
func migrate_legacy_save(src: String, dst: String, bak: String) -> void:
|
||||||
if not FileAccess.file_exists(src):
|
if not FileAccess.file_exists(src):
|
||||||
return # 执行前置:旧档不存在=无操作
|
return # 执行前置:旧档不存在=无操作
|
||||||
if FileAccess.file_exists(dst):
|
if FileAccess.file_exists(dst):
|
||||||
@@ -156,10 +161,10 @@ func _migrate_legacy_save(src: String, dst: String, bak: String) -> void:
|
|||||||
if not _copy_file(src, tmp):
|
if not _copy_file(src, tmp):
|
||||||
push_warning("[存档序列化] 旧单档迁移失败(源保留,下次启动重试):%s → %s" % [src, tmp])
|
push_warning("[存档序列化] 旧单档迁移失败(源保留,下次启动重试):%s → %s" % [src, tmp])
|
||||||
return
|
return
|
||||||
if not _save._rename_overwrite(tmp, dst):
|
if not _save.rename_overwrite(tmp, dst):
|
||||||
push_warning("[存档序列化] 旧单档迁移改名失败(tmp 保留待查,源保留重试):%s → %s" % [tmp, dst])
|
push_warning("[存档序列化] 旧单档迁移改名失败(tmp 保留待查,源保留重试):%s → %s" % [tmp, dst])
|
||||||
return
|
return
|
||||||
_save._rename_overwrite(src, bak)
|
_save.rename_overwrite(src, bak)
|
||||||
push_warning("[存档序列化] 旧单档已迁入 %s,原件留痕于 %s" % [dst, bak])
|
push_warning("[存档序列化] 旧单档已迁入 %s,原件留痕于 %s" % [dst, bak])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,12 @@ func _validate_shave_params(row: Dictionary) -> Array[Dictionary]:
|
|||||||
return errs
|
return errs
|
||||||
|
|
||||||
|
|
||||||
var _resolver # R1 解析协作件(ConfigDB load_tables 装配段注入——resolver 文件与键零搬移)
|
var _resolver # R1 解析协作件(ConfigDB load_tables 装配段经 bind_resolver 注入——resolver 文件与键零搬移)
|
||||||
|
|
||||||
|
|
||||||
|
# 解析协作件挂载口(ConfigDB 装配段公开面〔5.1 封装修复——直写 _resolver 改经此口〕)
|
||||||
|
func bind_resolver(resolver: ConfigDbResolver) -> void:
|
||||||
|
_resolver = resolver
|
||||||
|
|
||||||
|
|
||||||
## 进度行解析委托(消费面 db.combat.board_gen.resolve_row——R1 ceil/clamp 语义归 ConfigDbResolver)
|
## 进度行解析委托(消费面 db.combat.board_gen.resolve_row——R1 ceil/clamp 语义归 ConfigDbResolver)
|
||||||
|
|||||||
@@ -30,7 +30,12 @@ func validate() -> Array:
|
|||||||
return errs
|
return errs
|
||||||
|
|
||||||
|
|
||||||
var _resolver # R1 解析协作件(ConfigDB load_tables 装配段注入——resolver 文件与键零搬移)
|
var _resolver # R1 解析协作件(ConfigDB load_tables 装配段经 bind_resolver 注入——resolver 文件与键零搬移)
|
||||||
|
|
||||||
|
|
||||||
|
# 解析协作件挂载口(ConfigDB 装配段公开面〔5.1 封装修复——直写 _resolver 改经此口〕)
|
||||||
|
func bind_resolver(resolver: ConfigDbResolver) -> void:
|
||||||
|
_resolver = resolver
|
||||||
|
|
||||||
|
|
||||||
## 难度修饰解析委托(消费面 db.run.difficulty.resolve_modifiers——clamp 语义归 ConfigDbResolver)
|
## 难度修饰解析委托(消费面 db.run.difficulty.resolve_modifiers——clamp 语义归 ConfigDbResolver)
|
||||||
|
|||||||
@@ -209,7 +209,12 @@ static func band_of(band_keys: Array, D: int) -> int:
|
|||||||
# 禁双算法并存〔状态记录 312〕;L 维 ceil/clamp 与越顶 clamp 语义归 resolver)。
|
# 禁双算法并存〔状态记录 312〕;L 维 ceil/clamp 与越顶 clamp 语义归 resolver)。
|
||||||
|
|
||||||
|
|
||||||
var _resolver # R1 解析协作件(ConfigDB load_tables 装配段注入——resolver 文件与键零搬移)
|
var _resolver # R1 解析协作件(ConfigDB load_tables 装配段经 bind_resolver 注入——resolver 文件与键零搬移)
|
||||||
|
|
||||||
|
|
||||||
|
# 解析协作件挂载口(ConfigDB 装配段公开面〔5.1 封装修复——直写 _resolver 改经此口〕)
|
||||||
|
func bind_resolver(resolver: ConfigDbResolver) -> void:
|
||||||
|
_resolver = resolver
|
||||||
|
|
||||||
|
|
||||||
## 带寻址行集解析委托(消费面 db.combat.enemy.resolve_rowset——D 维 band_of 落带语义归 ConfigDbResolver)
|
## 带寻址行集解析委托(消费面 db.combat.enemy.resolve_rowset——D 维 band_of 落带语义归 ConfigDbResolver)
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ func _suites() -> Array:
|
|||||||
{"path": "res://tests/unit/battle_settlement/damage_controller_test.gd", "expect": 0},
|
{"path": "res://tests/unit/battle_settlement/damage_controller_test.gd", "expect": 0},
|
||||||
{"path": "res://tests/unit/battle_settlement/effect_port_test.gd", "expect": 0}, # 效果端口件(效果端口与掷格分件批 V1–V3——端口单实例/业务类零引擎直连源码锚/事实面闭集两对照)
|
{"path": "res://tests/unit/battle_settlement/effect_port_test.gd", "expect": 0}, # 效果端口件(效果端口与掷格分件批 V1–V3——端口单实例/业务类零引擎直连源码锚/事实面闭集两对照)
|
||||||
{"path": "res://tests/unit/battle_settlement/roll_multiplier_test.gd", "expect": 0}, # 掷点与乘区宿主(敌攻击类型轴/护甲掷点行为面/乘区接线/伤害类型克制与标错/fresh 池攻击链——批 B 自 smoke_all 迁入)
|
{"path": "res://tests/unit/battle_settlement/roll_multiplier_test.gd", "expect": 0}, # 掷点与乘区宿主(敌攻击类型轴/护甲掷点行为面/乘区接线/伤害类型克制与标错/fresh 池攻击链——批 B 自 smoke_all 迁入)
|
||||||
|
{"path": "res://tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd", "expect": 0}, # 结算接线同种子逐项 golden 对拍主证〔封装5.1〕(架构审查 5.1 封装修复——段类束注入改造零行为变更基线)
|
||||||
{"path": "res://tests/unit/battle_settlement/seal_polarity_test.gd", "expect": 0}, # 封印与极性宿主(末点 AP 封印/封印不可逆四情况/清图奖励双份与弱形式/刷新 provenance 守卫/极性路由与封印溢价/负向字典/格面资源资格——批 B 自 smoke_all 迁入)
|
{"path": "res://tests/unit/battle_settlement/seal_polarity_test.gd", "expect": 0}, # 封印与极性宿主(末点 AP 封印/封印不可逆四情况/清图奖励双份与弱形式/刷新 provenance 守卫/极性路由与封印溢价/负向字典/格面资源资格——批 B 自 smoke_all 迁入)
|
||||||
{"path": "res://tests/unit/battle_settlement/round_flow_skill_pool_test.gd", "expect": 0}, # 轮内流与技能池宿主(全战斗回放/轮内刷新与 V5 快照单源/遭遇区间骰/结算五段全序/技能池数据面·回放独立·EV 锚·死亡打断·校验拒绝/妨害附加/四桶计数——批 B 自 smoke_all 迁入)
|
{"path": "res://tests/unit/battle_settlement/round_flow_skill_pool_test.gd", "expect": 0}, # 轮内流与技能池宿主(全战斗回放/轮内刷新与 V5 快照单源/遭遇区间骰/结算五段全序/技能池数据面·回放独立·EV 锚·死亡打断·校验拒绝/妨害附加/四桶计数——批 B 自 smoke_all 迁入)
|
||||||
{"path": "res://tests/unit/battle_settlement/refresh_feats_test.gd", "expect": 0}, # 换图系专长七件宿主(迭代 46 卡A——基座产 RAP 与折价同源/成功事实恰一次/全局受伤四路径恰一次/石层缓冲/崩壁延迟/蓄力周转/坑道小憩/矿道红利 GP/七件数据与牌库/无专长基线;原经卡片申报走 02 关联档选跑,卡片退役后补登记由 02 全套件承接)
|
{"path": "res://tests/unit/battle_settlement/refresh_feats_test.gd", "expect": 0}, # 换图系专长七件宿主(迭代 46 卡A——基座产 RAP 与折价同源/成功事实恰一次/全局受伤四路径恰一次/石层缓冲/崩壁延迟/蓄力周转/坑道小憩/矿道红利 GP/七件数据与牌库/无专长基线;原经卡片申报走 02 关联档选跑,卡片退役后补登记由 02 全套件承接)
|
||||||
@@ -225,7 +226,8 @@ func _suites() -> Array:
|
|||||||
{"path": "res://tests/unit/core/tree_requires_level_test.gd", "expect": 0}, # R1 requires_level 等级阈值前置(schema 四拒一过+判定三态+投影两态)
|
{"path": "res://tests/unit/core/tree_requires_level_test.gd", "expect": 0}, # R1 requires_level 等级阈值前置(schema 四拒一过+判定三态+投影两态)
|
||||||
{"path": "res://tests/unit/core/pool_case_d_test.gd", "expect": 0}, # 池结构案D 行为(V6 令牌三态/V7 曝光槽 2+1 回退/V8 概率上限解耦提前退役)
|
{"path": "res://tests/unit/core/pool_case_d_test.gd", "expect": 0}, # 池结构案D 行为(V6 令牌三态/V7 曝光槽 2+1 回退/V8 概率上限解耦提前退役)
|
||||||
{"path": "res://tests/unit/core/feat_vouchers_test.gd", "expect": 0}, # 凭证五件+留任二件(V9 路由逐件/F-1 存量账/V10 留任路由+AP 属性轴)
|
{"path": "res://tests/unit/core/feat_vouchers_test.gd", "expect": 0}, # 凭证五件+留任二件(V9 路由逐件/F-1 存量账/V10 留任路由+AP 属性轴)
|
||||||
{"path": "res://tests/unit/core/settle_segments_test.gd", "expect": 0}, # 结算五段 Segment(V4:直接 new/编排器接线/真实一轮走段序)
|
{"path": "res://tests/unit/core/settle_segments_test.gd", "expect": 0}, # 结算五段 Segment(V4:束构造/编排器接线/真实一轮走段序)
|
||||||
|
{"path": "res://tests/unit/core/encapsulation_sweep_test.gd", "expect": 0}, # 五处结构性零命中扫描〔封装5.1 V2–V7〕(架构审查 5.1 封装修复——七段对编排器零引用/五方法零残留/断点存档公开面/效果累积面/配置挂载面,非空前置+正锚)
|
||||||
{"path": "res://tests/unit/core/save_slot_isolation_test.gd", "expect": 0}, # 卡① 三槽位间隔离/进档失败恢复/宿主链引用同一(sv1/sv5/sv9/sv10)
|
{"path": "res://tests/unit/core/save_slot_isolation_test.gd", "expect": 0}, # 卡① 三槽位间隔离/进档失败恢复/宿主链引用同一(sv1/sv5/sv9/sv10)
|
||||||
{"path": "res://tests/unit/core/save_boot_no_activate_test.gd", "expect": 0}, # 卡① 启动态类级契约:无激活态/拒写盘面零触碰/越界拒绝(sv2/sv3)
|
{"path": "res://tests/unit/core/save_boot_no_activate_test.gd", "expect": 0}, # 卡① 启动态类级契约:无激活态/拒写盘面零触碰/越界拒绝(sv2/sv3)
|
||||||
{"path": "res://tests/unit/core/save_slot_summary_test.gd", "expect": 0}, # 卡① 摘要三项按槽窥视:异值/空槽/坏槽三态/零副作用(sv7/sv8)
|
{"path": "res://tests/unit/core/save_slot_summary_test.gd", "expect": 0}, # 卡① 摘要三项按槽窥视:异值/空槽/坏槽三态/零副作用(sv7/sv8)
|
||||||
|
|||||||
@@ -0,0 +1,501 @@
|
|||||||
|
# tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd — 结算接线同种子逐项 golden 对拍宿主(架构审查 5.1 封装修复批 R1 主证)
|
||||||
|
# 断言面 = 同种子定死驱动序下 step_row 全量行流 + settlement_snapshot 全量载荷 + battle_finished
|
||||||
|
# 载荷 + 收束 presentation_snapshot 关键键(round/battle_result/hero.hp/enemies.size()),
|
||||||
|
# 与 GOLDEN 常量逐位相等〔封装5.1 V1〕——段类束注入改造的零行为变更主证:
|
||||||
|
# 改前基线在未改树上固化(GOLDEN 为空时走转储通道取数),改后全程保持绿。
|
||||||
|
# 驱动序(卡面逐字定死):begin_battle(voucher_rap=20) → 轮1 choose_dig 移动/挖/封邻格/停 →
|
||||||
|
# player_request_refresh(换图刷新链入流)→ 轮2 一挖一停 → 轮3 径直停手。
|
||||||
|
# 协议照单元侧惯例:extends SceneTree+Kit.check 主通道;--script 模式无 autoload,
|
||||||
|
# ConfigDB 显式实例化构造注入+自建 RngService(掷点隔离形态)。
|
||||||
|
# 取数通道:GOLDEN 为空时打印捕获序列 JSON(两格缩进)后按专用码 3 退出——非判定码族(0 绿/1 红/2 空跑)。
|
||||||
|
# 运行:godot --headless --path game --script res://tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd
|
||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||||
|
const ConfigDBScript := preload("res://core/config_db.gd")
|
||||||
|
const AutoPlayer := preload("res://sim/auto_player.gd")
|
||||||
|
|
||||||
|
const TEST_SEED := 4242 # golden 固化种子(卡面定死——改前改后同种子对拍前提)
|
||||||
|
const DUMP_EXIT := 3 # 空跑转储专用退出码(GOLDEN 未固化态取数用)
|
||||||
|
|
||||||
|
# 改前基线(JSON 两格缩进单文件固化——与捕获序列 JSON.stringify 逐字相等即绿;空串=未固化→转储模式)
|
||||||
|
const GOLDEN := r"""
|
||||||
|
{
|
||||||
|
"final": {
|
||||||
|
"battle_result": "",
|
||||||
|
"enemies_n": 2,
|
||||||
|
"hero_hp": 21,
|
||||||
|
"round": 4
|
||||||
|
},
|
||||||
|
"finished": [],
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"display_name": "迟滞(下轮行动点 -1)",
|
||||||
|
"effect_id": "ap_delta",
|
||||||
|
"origin_cell": [
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"pool_value": -1,
|
||||||
|
"round_index": 1,
|
||||||
|
"source_class": "mine_hit",
|
||||||
|
"type": "effect",
|
||||||
|
"value": -1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "行动点(下轮行动点 +1)",
|
||||||
|
"effect_id": "ap_delta",
|
||||||
|
"highlight": "seal_hit",
|
||||||
|
"origin_cell": [
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"pool_value": 0,
|
||||||
|
"round_index": 1,
|
||||||
|
"source_class": "seal_hit",
|
||||||
|
"type": "effect",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ac": 1,
|
||||||
|
"display_name": "主角攻击",
|
||||||
|
"killed": false,
|
||||||
|
"normal": 2,
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"roll": 12,
|
||||||
|
"round_index": 1,
|
||||||
|
"source_class": "hero_attack",
|
||||||
|
"target": "地精一伙",
|
||||||
|
"target_id": "goblin",
|
||||||
|
"tier": 100,
|
||||||
|
"type": "damage",
|
||||||
|
"value": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ac": 10,
|
||||||
|
"attack_type": "normal",
|
||||||
|
"baseline": 1,
|
||||||
|
"crit": true,
|
||||||
|
"display_name": "地精一伙 普通攻击(暴击)",
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"roll": 20,
|
||||||
|
"round_index": 1,
|
||||||
|
"skill_id": "attack",
|
||||||
|
"source_class": "enemy_action",
|
||||||
|
"target": "hero",
|
||||||
|
"tier": 100,
|
||||||
|
"type": "damage",
|
||||||
|
"value": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ac": 10,
|
||||||
|
"attack_type": "normal",
|
||||||
|
"baseline": 1,
|
||||||
|
"display_name": "地精一伙 普通攻击(被压制)",
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"roll": 2,
|
||||||
|
"round_index": 1,
|
||||||
|
"skill_id": "attack",
|
||||||
|
"source_class": "enemy_action",
|
||||||
|
"target": "hero",
|
||||||
|
"tier": 25,
|
||||||
|
"type": "damage",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "状态衰减",
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"round_index": 1,
|
||||||
|
"source_class": "",
|
||||||
|
"type": "decay",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "下一轮次",
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"round_index": 1,
|
||||||
|
"source_class": "",
|
||||||
|
"type": "verdict",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "暴走",
|
||||||
|
"effect_id": "enrage",
|
||||||
|
"origin_cell": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"pool_value": 25,
|
||||||
|
"round_index": 2,
|
||||||
|
"source_class": "mine_hit",
|
||||||
|
"type": "effect",
|
||||||
|
"value": 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ac": 1,
|
||||||
|
"display_name": "主角攻击",
|
||||||
|
"killed": true,
|
||||||
|
"normal": 2,
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"roll": 2,
|
||||||
|
"round_index": 2,
|
||||||
|
"source_class": "hero_attack",
|
||||||
|
"target": "地精一伙",
|
||||||
|
"target_id": "goblin",
|
||||||
|
"tier": 100,
|
||||||
|
"type": "damage",
|
||||||
|
"value": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ac": 10,
|
||||||
|
"attack_type": "normal",
|
||||||
|
"baseline": 1,
|
||||||
|
"display_name": "地精一伙 普通攻击(被压制)",
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"roll": 1,
|
||||||
|
"round_index": 2,
|
||||||
|
"skill_id": "attack",
|
||||||
|
"source_class": "enemy_action",
|
||||||
|
"target": "hero",
|
||||||
|
"tier": 25,
|
||||||
|
"type": "damage",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "状态衰减",
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"round_index": 2,
|
||||||
|
"source_class": "",
|
||||||
|
"type": "decay",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "下一轮次",
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"round_index": 2,
|
||||||
|
"source_class": "",
|
||||||
|
"type": "verdict",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ac": 1,
|
||||||
|
"display_name": "主角攻击",
|
||||||
|
"killed": false,
|
||||||
|
"normal": 2,
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"roll": 8,
|
||||||
|
"round_index": 3,
|
||||||
|
"source_class": "hero_attack",
|
||||||
|
"target": "地精一伙",
|
||||||
|
"target_id": "goblin",
|
||||||
|
"tier": 100,
|
||||||
|
"type": "damage",
|
||||||
|
"value": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ac": 10,
|
||||||
|
"attack_type": "normal",
|
||||||
|
"baseline": 1,
|
||||||
|
"display_name": "地精一伙 普通攻击(被压制)",
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"roll": 6,
|
||||||
|
"round_index": 3,
|
||||||
|
"skill_id": "attack",
|
||||||
|
"source_class": "enemy_action",
|
||||||
|
"target": "hero",
|
||||||
|
"tier": 50,
|
||||||
|
"type": "damage",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "状态衰减",
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"round_index": 3,
|
||||||
|
"source_class": "",
|
||||||
|
"type": "decay",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "下一轮次",
|
||||||
|
"origin_cell": [
|
||||||
|
-1,
|
||||||
|
-1
|
||||||
|
],
|
||||||
|
"round_index": 3,
|
||||||
|
"source_class": "",
|
||||||
|
"type": "verdict",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"snaps": [
|
||||||
|
{
|
||||||
|
"board": {
|
||||||
|
"h": 10,
|
||||||
|
"revealed": 42,
|
||||||
|
"seals": 1,
|
||||||
|
"seq": 1,
|
||||||
|
"w": 10
|
||||||
|
},
|
||||||
|
"buffs": [],
|
||||||
|
"depth": 1,
|
||||||
|
"economy": {
|
||||||
|
"ap": 0,
|
||||||
|
"phase": "Waiting",
|
||||||
|
"rap": 26,
|
||||||
|
"refresh_used": 0
|
||||||
|
},
|
||||||
|
"enemies": [
|
||||||
|
{
|
||||||
|
"armor": "light",
|
||||||
|
"armor_class": 1,
|
||||||
|
"atk": 1,
|
||||||
|
"hp": 2,
|
||||||
|
"id": "goblin",
|
||||||
|
"max_hp": 4,
|
||||||
|
"pending_skill": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"armor": "light",
|
||||||
|
"armor_class": 1,
|
||||||
|
"atk": 1,
|
||||||
|
"hp": 4,
|
||||||
|
"id": "goblin",
|
||||||
|
"max_hp": 4,
|
||||||
|
"pending_skill": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hero": {
|
||||||
|
"armor_class": 10,
|
||||||
|
"atk": 2,
|
||||||
|
"hp": 23,
|
||||||
|
"max_hp": 26
|
||||||
|
},
|
||||||
|
"kind": "settlement",
|
||||||
|
"level": 1,
|
||||||
|
"round": 1,
|
||||||
|
"round_facts": {
|
||||||
|
"mines": 1,
|
||||||
|
"path": "stopped",
|
||||||
|
"safe": 0,
|
||||||
|
"seal_events": 1
|
||||||
|
},
|
||||||
|
"spawn_count": 2,
|
||||||
|
"unit_count": 2,
|
||||||
|
"verdict": "continue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"board": {
|
||||||
|
"h": 10,
|
||||||
|
"revealed": 43,
|
||||||
|
"seals": 0,
|
||||||
|
"seq": 2,
|
||||||
|
"w": 10
|
||||||
|
},
|
||||||
|
"buffs": [],
|
||||||
|
"depth": 1,
|
||||||
|
"economy": {
|
||||||
|
"ap": 0,
|
||||||
|
"phase": "Waiting",
|
||||||
|
"rap": 25,
|
||||||
|
"refresh_used": 1
|
||||||
|
},
|
||||||
|
"enemies": [
|
||||||
|
{
|
||||||
|
"armor": "light",
|
||||||
|
"armor_class": 1,
|
||||||
|
"atk": 1,
|
||||||
|
"hp": 4,
|
||||||
|
"id": "goblin",
|
||||||
|
"max_hp": 4,
|
||||||
|
"pending_skill": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hero": {
|
||||||
|
"armor_class": 10,
|
||||||
|
"atk": 2,
|
||||||
|
"hp": 22,
|
||||||
|
"max_hp": 26
|
||||||
|
},
|
||||||
|
"kind": "settlement",
|
||||||
|
"level": 1,
|
||||||
|
"round": 2,
|
||||||
|
"round_facts": {
|
||||||
|
"mines": 1,
|
||||||
|
"path": "stopped",
|
||||||
|
"safe": 42,
|
||||||
|
"seal_events": 0
|
||||||
|
},
|
||||||
|
"spawn_count": 2,
|
||||||
|
"unit_count": 1,
|
||||||
|
"verdict": "continue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"board": {
|
||||||
|
"h": 10,
|
||||||
|
"revealed": 43,
|
||||||
|
"seals": 0,
|
||||||
|
"seq": 2,
|
||||||
|
"w": 10
|
||||||
|
},
|
||||||
|
"buffs": [],
|
||||||
|
"depth": 1,
|
||||||
|
"economy": {
|
||||||
|
"ap": 0,
|
||||||
|
"phase": "Waiting",
|
||||||
|
"rap": 33,
|
||||||
|
"refresh_used": 0
|
||||||
|
},
|
||||||
|
"enemies": [
|
||||||
|
{
|
||||||
|
"armor": "light",
|
||||||
|
"armor_class": 1,
|
||||||
|
"atk": 1,
|
||||||
|
"hp": 2,
|
||||||
|
"id": "goblin",
|
||||||
|
"max_hp": 4,
|
||||||
|
"pending_skill": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hero": {
|
||||||
|
"armor_class": 10,
|
||||||
|
"atk": 2,
|
||||||
|
"hp": 21,
|
||||||
|
"max_hp": 26
|
||||||
|
},
|
||||||
|
"kind": "settlement",
|
||||||
|
"level": 1,
|
||||||
|
"round": 3,
|
||||||
|
"round_facts": {
|
||||||
|
"mines": 0,
|
||||||
|
"path": "stopped",
|
||||||
|
"safe": 0,
|
||||||
|
"seal_events": 0
|
||||||
|
},
|
||||||
|
"spawn_count": 2,
|
||||||
|
"unit_count": 1,
|
||||||
|
"verdict": "continue"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
var _db
|
||||||
|
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
_db = ConfigDBScript.new()
|
||||||
|
var load_ok: bool = _db.load_tables("res://data/tables", true)
|
||||||
|
Kit.check("前置:表加载零 error", load_ok and _db.diagnostics_count("error") == 0)
|
||||||
|
var orch = load("res://core/battle_orchestrator.gd").new(_db, _make_rng())
|
||||||
|
var rows: Array = []
|
||||||
|
var snaps: Array = []
|
||||||
|
var finished: Array = []
|
||||||
|
orch.step_row.connect(func(row: Dictionary) -> void: rows.append(row))
|
||||||
|
orch.settlement_snapshot.connect(func(s: Dictionary) -> void: snaps.append(s))
|
||||||
|
orch.battle_finished.connect(func(p: Dictionary) -> void: finished.append(p))
|
||||||
|
var accepted: bool = orch.begin_battle({"level_index": 1, "node_depth": 1,
|
||||||
|
"hero_hp": int(_db.combat.hero.max_hp), "voucher_rap": 20})
|
||||||
|
Kit.check("前置:begin_battle 受理", accepted)
|
||||||
|
if accepted:
|
||||||
|
_drive(orch)
|
||||||
|
var final_snap: Dictionary = orch.presentation_snapshot()
|
||||||
|
var captured := {"rows": rows, "snaps": snaps, "finished": finished,
|
||||||
|
"final": {"round": int(final_snap.get("round", -1)),
|
||||||
|
"battle_result": String(final_snap.get("battle_result", "")),
|
||||||
|
"hero_hp": int((final_snap.get("hero", {}) as Dictionary).get("hp", -1)),
|
||||||
|
"enemies_n": (final_snap.get("enemies", []) as Array).size()}}
|
||||||
|
if GOLDEN == "":
|
||||||
|
print("[golden-dump] begin")
|
||||||
|
print(JSON.stringify(captured, " "))
|
||||||
|
print("[golden-dump] end")
|
||||||
|
quit(DUMP_EXIT)
|
||||||
|
return
|
||||||
|
# GOLDEN 三引号字面量首尾各带一个换行(多行字符串语法载体)——对拍两侧 strip 首尾空白后逐位比
|
||||||
|
var captured_json := JSON.stringify(captured, " ").strip_edges()
|
||||||
|
var golden_json := GOLDEN.strip_edges()
|
||||||
|
if captured_json != golden_json:
|
||||||
|
print("[captured-dump] begin")
|
||||||
|
print(JSON.stringify(captured, " "))
|
||||||
|
print("[captured-dump] end")
|
||||||
|
Kit.check("封装5.1 V1 同种子逐项对拍:行流/快照/终局载荷/presentation 逐位同 golden",
|
||||||
|
captured_json == golden_json)
|
||||||
|
quit(Kit.finish("settlement_wiring_snapshot_test"))
|
||||||
|
|
||||||
|
|
||||||
|
func _make_rng():
|
||||||
|
var rng = load("res://core/rng_service.gd").new()
|
||||||
|
rng.set_master_seed(TEST_SEED)
|
||||||
|
return rng
|
||||||
|
|
||||||
|
|
||||||
|
# 驱动序(卡面定死):轮1 挖+封+停 → 刷新 → 轮2 一挖一停 → 轮3 停手
|
||||||
|
func _drive(orch) -> void:
|
||||||
|
var ms = orch.minesweeper
|
||||||
|
# 轮 1:choose_dig 非空则 move→dig→封首个未揭示主角邻格→stop
|
||||||
|
var pick: Dictionary = AutoPlayer.choose_dig(ms)
|
||||||
|
if not pick.is_empty() and pick.get("dig_cell", null) != null:
|
||||||
|
ms.settle_batch([{"action": "move", "cell": pick["hero_cell"]}])
|
||||||
|
ms.settle_batch([{"action": "dig", "cell": pick["dig_cell"]}])
|
||||||
|
var c := _first_unrevealed_adjacent(orch)
|
||||||
|
if c.x >= 0:
|
||||||
|
ms.settle_batch([{"action": "seal_place", "cell": c}])
|
||||||
|
ms.settle_batch([{"action": "stop"}])
|
||||||
|
# 轮 1 结算后刷新(voucher_rap=20 保证 RAP 抵折后价——覆盖换图刷新链)
|
||||||
|
orch.player_request_refresh()
|
||||||
|
# 轮 2:一挖一停
|
||||||
|
var pick2: Dictionary = AutoPlayer.choose_dig(ms)
|
||||||
|
if not pick2.is_empty() and pick2.get("dig_cell", null) != null:
|
||||||
|
ms.settle_batch([{"action": "move", "cell": pick2["hero_cell"]}])
|
||||||
|
ms.settle_batch([{"action": "dig", "cell": pick2["dig_cell"]}])
|
||||||
|
ms.settle_batch([{"action": "stop"}])
|
||||||
|
# 轮 3:径直停手
|
||||||
|
ms.settle_batch([{"action": "stop"}])
|
||||||
|
|
||||||
|
|
||||||
|
# 首个未揭示且与主角 8 邻接的格(卡面定死封印目标选取;无候选=返回 (-1,-1) 跳过封印)
|
||||||
|
func _first_unrevealed_adjacent(orch) -> Vector2i:
|
||||||
|
var ms = orch.minesweeper
|
||||||
|
var hero: Vector2i = ms.hero_pos()
|
||||||
|
var board = ms.current_board()
|
||||||
|
for y in board.height:
|
||||||
|
for x in board.width:
|
||||||
|
var c := Vector2i(x, y)
|
||||||
|
if not ms.is_revealed(c) and board.neighbors8(hero).has(c):
|
||||||
|
return c
|
||||||
|
return Vector2i(-1, -1)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://uys0a2kxc8a6
|
||||||
@@ -668,7 +668,7 @@ func _smoke_feats() -> void:
|
|||||||
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_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
|
var pm_bonus := 0
|
||||||
for h in orch._hits_of(pm_hits, "attack_bonus"):
|
for h in DamageController.hits_of(pm_hits, "attack_bonus"):
|
||||||
pm_bonus = EffectEngine.counter(orch.fx_host().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)
|
Kit.check("疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕", pm_bonus == 2)
|
||||||
fx.rebuild(["fuse_wire"], [], {})
|
fx.rebuild(["fuse_wire"], [], {})
|
||||||
@@ -688,7 +688,7 @@ func _smoke_feats() -> void:
|
|||||||
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 ti_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0})
|
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
|
var ti_val := 0
|
||||||
for h in orch._hits_of(ti_hits, "attack_bonus"):
|
for h in DamageController.hits_of(ti_hits, "attack_bonus"):
|
||||||
ti_val = EffectEngine.counter(orch.fx_host().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)
|
Kit.check("陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕", ti_val == 2)
|
||||||
fx.rebuild(["chain_traps", "pain_memory"], [], {})
|
fx.rebuild(["chain_traps", "pain_memory"], [], {})
|
||||||
@@ -724,7 +724,7 @@ func _smoke_feats() -> void:
|
|||||||
EffectEngine.counter_add(orch.fx_host().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〕", _hits_kind(me_hits, "counter_add").size() == 1)
|
Kit.check("弃图兑换:停手剩余 AP≥3 入账〔专长批 V3〕", _hits_kind(me_hits, "counter_add").size() == 1)
|
||||||
var me_atk := 0
|
var me_atk := 0
|
||||||
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"):
|
for h in DamageController.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", "")))
|
me_atk = EffectEngine.counter(orch.fx_host().counters_ctx, String(h.get("value_counter", "")))
|
||||||
Kit.check("弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕", me_atk == 4)
|
Kit.check("弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕", me_atk == 4)
|
||||||
Kit.check("弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty())
|
Kit.check("弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty())
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# tests/unit/core/encapsulation_sweep_test.gd — 架构审查 5.1 封装修复·五处结构性零命中扫描宿主
|
||||||
|
# 断言面 = 七段断言组(全部带「扫描面非空前置+正锚」——非空防扫描面为空静默绿,正锚证新面在用):
|
||||||
|
# V2 settlement 七文件:非空+class_name 正锚+\boc\b 全文零命中(段类对编排器引用归零,含头注)
|
||||||
|
# V3 battle_orchestrator:非空+搬家五方法定义零命中(宿主原方法删除禁留壳)
|
||||||
|
# V4 adventure_runner:非空+_bp_store._ 零命中+bp_read 正锚(公开面在用)
|
||||||
|
# V5 save_manager:非空+slots._ 零命中+active_path 正锚;slot_store:非空+_save._ 零命中+公开面正锚
|
||||||
|
# V6 grant_segment:非空+两 fx 私键零命中+mark_new_resource_type 正锚
|
||||||
|
# V7 config_db:非空+._resolver 直写零命中+bind_resolver 正锚
|
||||||
|
# 手法沿 player_copy_sweep_test 源 token 扫描先例(不并件——扫描面归属与尾缀语义本批特有)。
|
||||||
|
# 运行:godot --headless --path game --script res://tests/unit/core/encapsulation_sweep_test.gd
|
||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||||
|
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
_sweep_settlement_segments()
|
||||||
|
_sweep_orchestrator_methods()
|
||||||
|
_sweep_breakpoint_pair()
|
||||||
|
_sweep_save_pair()
|
||||||
|
_sweep_fx_host_accumulators()
|
||||||
|
_sweep_config_resolver()
|
||||||
|
quit(Kit.finish("encapsulation_sweep_test"))
|
||||||
|
|
||||||
|
|
||||||
|
# 源文件读取+非空前置(返 "" 即已判红——调用侧跳过后续正则断言)
|
||||||
|
func _src(path: String) -> String:
|
||||||
|
var src := FileAccess.get_file_as_string(path)
|
||||||
|
Kit.check("扫描面非空前置:%s" % path, src.length() > 0)
|
||||||
|
return src
|
||||||
|
|
||||||
|
|
||||||
|
# 正则零命中断言(-pattern 意图写入断言文案——可 grep 定位)
|
||||||
|
func _zero(pattern: String, src: String, label: String, path: String) -> void:
|
||||||
|
var re := RegEx.create_from_string(pattern)
|
||||||
|
Kit.check("%s:%s 零命中〔封装5.1〕" % [label, pattern], re.search(src) == null)
|
||||||
|
|
||||||
|
|
||||||
|
# 正锚在案断言(正则命中 ≥1——新公开面/新形态在用)
|
||||||
|
func _anchor(pattern: String, src: String, label: String, path: String) -> void:
|
||||||
|
var re := RegEx.create_from_string(pattern)
|
||||||
|
Kit.check("%s:%s 正锚在案〔封装5.1〕" % [label, pattern], re.search(src) != null)
|
||||||
|
|
||||||
|
|
||||||
|
# ① V2 settlement 七文件(束注入换代——对编排器引用归零)
|
||||||
|
func _sweep_settlement_segments() -> void:
|
||||||
|
var classes := {
|
||||||
|
"res://core/settlement/grant_segment.gd": "GrantSegment",
|
||||||
|
"res://core/settlement/combat_segment.gd": "CombatSegment",
|
||||||
|
"res://core/settlement/round_cycle.gd": "RoundCycle",
|
||||||
|
"res://core/settlement/verdict_segment.gd": "VerdictSegment",
|
||||||
|
"res://core/settlement/battle_refresh_flow.gd": "BattleRefreshFlow",
|
||||||
|
"res://core/settlement/aura_segment.gd": "AuraSegment",
|
||||||
|
"res://core/settlement/decay_segment.gd": "DecaySegment",
|
||||||
|
}
|
||||||
|
for path in classes:
|
||||||
|
var src := _src(path)
|
||||||
|
if src.length() == 0:
|
||||||
|
continue
|
||||||
|
_anchor("class_name %s" % classes[path], src, "class_name 正锚(%s)" % classes[path], path)
|
||||||
|
_zero("\\boc\\b", src, "对编排器标识符全归零(%s)" % classes[path], path)
|
||||||
|
|
||||||
|
|
||||||
|
# ② V3 编排器搬家五方法零残留
|
||||||
|
func _sweep_orchestrator_methods() -> void:
|
||||||
|
var src := _src("res://core/battle_orchestrator.gd")
|
||||||
|
if src.length() == 0:
|
||||||
|
return
|
||||||
|
_zero("func _hero_atk\\(|func _feat_yield_adjust\\(|func _harmful_variants\\(|func _harmful_adjacent\\(|func _hits_of\\(",
|
||||||
|
src, "编排器搬家五方法定义零残留(宿主删除禁留壳)", "res://core/battle_orchestrator.gd")
|
||||||
|
|
||||||
|
|
||||||
|
# ③ V4 断点域(adventure_runner → BreakpointStore 公开面)
|
||||||
|
func _sweep_breakpoint_pair() -> void:
|
||||||
|
var src := _src("res://core/adventure_runner.gd")
|
||||||
|
if src.length() == 0:
|
||||||
|
return
|
||||||
|
_zero("_bp_store\\._", src, "宿主对断点件私取归零", "res://core/adventure_runner.gd")
|
||||||
|
_anchor("_bp_store\\.bp_read\\(", src, "断点公开读侧在用", "res://core/adventure_runner.gd")
|
||||||
|
|
||||||
|
|
||||||
|
# ④ V5 存档双向公开面(save_manager↔slot_store)
|
||||||
|
func _sweep_save_pair() -> void:
|
||||||
|
var src_m := _src("res://core/save_manager.gd")
|
||||||
|
if src_m.length() > 0:
|
||||||
|
_zero("slots\\._", src_m, "save_manager 对槽位件私取归零", "res://core/save_manager.gd")
|
||||||
|
_anchor("slots\\.active_path\\(\\)", src_m, "槽路径读投影在用", "res://core/save_manager.gd")
|
||||||
|
var src_s := _src("res://core/slot_store.gd")
|
||||||
|
if src_s.length() > 0:
|
||||||
|
_zero("_save\\._", src_s, "slot_store 反向私取归零", "res://core/slot_store.gd")
|
||||||
|
_anchor("_save\\.(set_read_only|read_only|reset_to_default|data_snapshot|restore_data|peek_summary_at|rename_overwrite)",
|
||||||
|
src_s, "存档公开承接面在用", "res://core/slot_store.gd")
|
||||||
|
|
||||||
|
|
||||||
|
# ⑤ V6 效果宿主累积面(grant → BattleFxHost 公开累积方法)
|
||||||
|
func _sweep_fx_host_accumulators() -> void:
|
||||||
|
var src := _src("res://core/settlement/grant_segment.gd")
|
||||||
|
if src.length() == 0:
|
||||||
|
return
|
||||||
|
_zero("_fx_new_resource_types|_fx_bonus_delta", src, "授予段对效果宿主私键直写归零", "res://core/settlement/grant_segment.gd")
|
||||||
|
_anchor("mark_new_resource_type\\(", src, "首见登记公开方法在用", "res://core/settlement/grant_segment.gd")
|
||||||
|
|
||||||
|
|
||||||
|
# ⑥ V7 配置挂载面(config_db → 三 schema bind_resolver)
|
||||||
|
func _sweep_config_resolver() -> void:
|
||||||
|
var src := _src("res://core/config_db.gd")
|
||||||
|
if src.length() == 0:
|
||||||
|
return
|
||||||
|
_zero("\\._resolver\\s*=", src, "解析协作件直写归零", "res://core/config_db.gd")
|
||||||
|
_anchor("bind_resolver\\(_resolver\\)", src, "挂载口在用", "res://core/config_db.gd")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dq1huyomw37m2
|
||||||
@@ -15,23 +15,33 @@ func _init() -> void:
|
|||||||
quit(1)
|
quit(1)
|
||||||
return
|
return
|
||||||
|
|
||||||
# V4-1 段类直接 new()(构造收宿主引用——五类同构)
|
# V4-1 段类直接 new()(引用束注入——编排器 _init 同款 20 键束组装,逐段 new(bundle) 成功断言)
|
||||||
var oc = load("res://core/battle_orchestrator.gd").new(db, load("res://core/rng_service.gd").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, oc.fx_host(), oc.reward_grant()) != null, "V4 GrantSegment 直接 new()〔迭代27 T2〕")
|
var seg_bundle: Dictionary[Variant, Variant] = {
|
||||||
Kit.check_quiet(load("res://core/settlement/aura_segment.gd").new(oc) != null, "V4 AuraSegment 直接 new()〔迭代27 T2〕")
|
"facts": oc._facts, "units": oc._enemy_system, "damage": oc._damage,
|
||||||
Kit.check_quiet(load("res://core/settlement/combat_segment.gd").new(oc, oc.fx_host(), oc.reward_grant()) != null, "V4 CombatSegment 直接 new()〔迭代27 T2〕")
|
"status_effects": oc._status_effects, "reward_economy": oc._reward_economy,
|
||||||
Kit.check_quiet(load("res://core/settlement/decay_segment.gd").new(oc) != null, "V4 DecaySegment 直接 new()〔迭代27 T2〕")
|
"reward_grant": oc.reward_grant(), "fx_host": oc.fx_host(), "minesweeper": oc.minesweeper,
|
||||||
Kit.check_quiet(load("res://core/settlement/verdict_segment.gd").new(oc) != null, "V4 VerdictSegment 直接 new()〔迭代27 T2〕")
|
"db": oc.db, "action_economy": oc.action_economy, "rng": oc.rng,
|
||||||
|
"emit_row_cb": oc._emit_row, "fatal_cb": oc._fatal, "install_cb": oc._install_and_reset,
|
||||||
|
"battle_over_cb": oc._battle_over, "exit_distribution_cb": oc._exit_distribution,
|
||||||
|
"emit_snapshot_cb": oc._emit_settlement_snapshot, "emit_composition_cb": oc._emit_composition_effects,
|
||||||
|
"attune_param_sum_cb": oc.attune_param_sum, "perfect_sweep_cb": oc.perfect_sweep_earned,
|
||||||
|
}
|
||||||
|
Kit.check_quiet(load("res://core/settlement/grant_segment.gd").new(seg_bundle) != null, "V4 GrantSegment 束构造〔迭代27 T2〕")
|
||||||
|
Kit.check_quiet(load("res://core/settlement/aura_segment.gd").new(seg_bundle) != null, "V4 AuraSegment 束构造〔迭代27 T2〕")
|
||||||
|
Kit.check_quiet(load("res://core/settlement/combat_segment.gd").new(seg_bundle) != null, "V4 CombatSegment 束构造〔迭代27 T2〕")
|
||||||
|
Kit.check_quiet(load("res://core/settlement/decay_segment.gd").new(seg_bundle) != null, "V4 DecaySegment 束构造〔迭代27 T2〕")
|
||||||
|
Kit.check_quiet(load("res://core/settlement/verdict_segment.gd").new(seg_bundle) != null, "V4 VerdictSegment 束构造〔迭代27 T2〕")
|
||||||
|
|
||||||
# V4-2 编排器 _init 接线(段持宿主引用;段序编排面在位)——注册面批同代归约 V1:
|
# V4-2 编排器 _init 接线(段持注入引用同一性——宿主回指对象随束注入退役;段序编排面在位):
|
||||||
# 段件宿主直持改经 _rounds(轮循环驱动件)承载,接线断言改指 _rounds 段族
|
# 段件宿主直持改经 _rounds(轮循环驱动件)承载,接线断言改指 _rounds 段族
|
||||||
Kit.check_quiet(oc._rounds != null and oc._rounds.oc == oc, "V1 编排器接线·轮循环驱动件在座且宿主回指〔编排归约 V1〕")
|
Kit.check_quiet(oc._rounds != null and oc._rounds.facts == oc._facts, "V1 编排器接线·轮循环驱动件在座且束注入同一性〔编排归约 V1〕")
|
||||||
Kit.check_quiet(oc._facts != null, "V1 编排器接线·战斗事实件在座〔编排归约 V1〕")
|
Kit.check_quiet(oc._facts != null, "V1 编排器接线·战斗事实件在座〔编排归约 V1〕")
|
||||||
Kit.check_quiet(oc._rounds._grant_seg != null and oc._rounds._grant_seg.oc == oc, "V4 编排器接线·授予段(宿主回指)〔迭代27 T2〕")
|
Kit.check_quiet(oc._rounds._grant_seg != null and oc._rounds._grant_seg.facts == oc._facts, "V4 编排器接线·授予段(束注入同一性·facts)〔迭代27 T2〕")
|
||||||
Kit.check_quiet(oc._rounds._aura_seg != null and oc._rounds._aura_seg.oc == oc, "V4 编排器接线·增益/减益段〔迭代27 T2〕")
|
Kit.check_quiet(oc._rounds._aura_seg != null and oc._rounds._aura_seg.emit_composition_cb.get_object() == oc, "V4 编排器接线·增益/减益段(回调对象同一性)〔迭代27 T2〕")
|
||||||
Kit.check_quiet(oc._rounds._combat_seg != null and oc._rounds._combat_seg.oc == oc, "V4 编排器接线·战斗段〔迭代27 T2〕")
|
Kit.check_quiet(oc._rounds._combat_seg != null and oc._rounds._combat_seg.fx_host == oc.fx_host(), "V4 编排器接线·战斗段(束注入同一性·fx_host)〔迭代27 T2〕")
|
||||||
Kit.check_quiet(oc._rounds._decay_seg != null and oc._rounds._decay_seg.oc == oc, "V4 编排器接线·衰减段〔迭代27 T2〕")
|
Kit.check_quiet(oc._rounds._decay_seg != null and oc._rounds._decay_seg.status_effects == oc._status_effects, "V4 编排器接线·衰减段(束注入同一性·status_effects)〔迭代27 T2〕")
|
||||||
Kit.check_quiet(oc._rounds._verdict_seg != null and oc._rounds._verdict_seg.oc == oc, "V4 编排器接线·判定段〔迭代27 T2〕")
|
Kit.check_quiet(oc._rounds._verdict_seg != null and oc._rounds._verdict_seg.units == oc._enemy_system, "V4 编排器接线·判定段(束注入同一性·units)〔迭代27 T2〕")
|
||||||
# counter_add 写口真实路径(geologist 订阅行承载:公共命令链 begin_battle→move→dig→stop
|
# counter_add 写口真实路径(geologist 订阅行承载:公共命令链 begin_battle→move→dig→stop
|
||||||
# 经授予段 counter_add 单点落 counters_ctx battle 桶——受理载荷+公开读数口 fx_host().counters_ctx)
|
# 经授予段 counter_add 单点落 counters_ctx battle 桶——受理载荷+公开读数口 fx_host().counters_ctx)
|
||||||
var rng_c = load("res://core/rng_service.gd").new()
|
var rng_c = load("res://core/rng_service.gd").new()
|
||||||
|
|||||||
Reference in new issue
Block a user