主卡T3(拆件批wr4/wr5): 应用层收薄+构造注入+信号直连——adventure_runner六转发信号族与_forward_*方法删除(选路快照双职改核心侧受理_on_level_choices_presented直订level信号;stop信号中继删);level_runner构造注入一次给齐(_init收ctx十二键:hero/ledger瘦壳/adv_bucket/attunement/tree_rules活引用/shop_mode/treasure_unlocked/类型轴/凭证出箱/pool_wiring四参——后置setter群八件与set_hero/set_run_ledger退役,_bind_shop全仓仅构造一处,头注补关卡开始信号前零发射契约,八白盒属性内部改直指_shop);adv _start_current_level组包ctx(_inject_level_params退役改_emit_level_started只广播);呈现/测量信号直连(main_adventure_flow _connect_runner_once六信号level_started重取点is_connected守卫重连+三选一订stop();autoplay增订level_started同关守卫重连三信号;curio_interact_controller 14处adventure.stop()改址);测量面残留换代(bp_baseline_dump直连改level_started重连/ui_tune_probe与main_debug_flow keystone读hero());测试直连面换代(breakpoint_resume P5订stop()/adventure_feats信号序观测level_started回调内重连——bind句柄每回调新建不以Callable相等判重);sc3源码断言落hero_ledger_test(_bind_shop全仓仅构造一处/_forward_族零命中)〔拆件批 V5〕。验收:bp1对拍292dumps×27键逐位零漂移(改前/T1/T2/T3四时点一致)+十套件零红+csf集成绿+冒烟17项0失败〔拆件批 V4〕〔拆件批 V5〕
This commit is contained in:
1 parent
4a9e4300ad
commit
4da7f3e2f2
13 files changed
+188276
-173
No files matched your search
@@ -10,14 +10,6 @@ signal level_started(level_index: int, display_name: String) ## 每关开始
|
||||
signal curio_choice_presented(payload: Dictionary) ## 专长三选一停点(非末关的关卡完成恰一次;通道白名单后候选恒专长+回血在载荷)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared}
|
||||
signal curio_resolved(result: Dictionary) ## 三选一兑付完成(pick_curio/替换受理后恰一次)——{picked, value, hp_before, hp_after, level_index}
|
||||
signal adventure_finished(summary: Dictionary) ## 冒险结算(completed/failed/abandoned)——summary 五件事:打穿没有/完成几关/止步第几关/钥石合计/主种子(+additive keystone_drops)
|
||||
# ---- 转发族(签名与 LevelRunner 同款——呈现层对接透明;S6 经 _forward_* 方法)----
|
||||
signal node_entered(node: Dictionary, info: Dictionary)
|
||||
signal choices_presented(candidates: Array)
|
||||
signal step_row(row: Dictionary)
|
||||
signal round_started(round_index: int)
|
||||
signal board_changed()
|
||||
signal wallet_changed(event: String, gp: int, extra: Dictionary)
|
||||
|
||||
var db: ConfigDb
|
||||
var rng: RngServiceCore
|
||||
enum State { IDLE, RUNNING, SETTLING } # 会话状态机(内部 enum,外部 String 契约经 getter 词表)
|
||||
@@ -59,8 +51,6 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
|
||||
_hero = HeroLedger.new(db, {})
|
||||
_curio = CurioDeck.new(db, rng)
|
||||
_stop = CurioStop.new(db, _curio, _hero, _snapshot_breakpoint)
|
||||
_stop.curio_choice_presented.connect(_relay_stop_choice_presented)
|
||||
_stop.curio_resolved.connect(_relay_stop_resolved)
|
||||
|
||||
|
||||
# 件出口(组件交出非委托壳——呈现/测量面取主角账单点;拆件批接口块)
|
||||
@@ -73,15 +63,6 @@ func stop() -> CurioStop:
|
||||
return _stop
|
||||
|
||||
|
||||
# 停点件信号中继(呈现层直订面在 T3 信号直连前由本类承转——方法转发非 lambda)
|
||||
func _relay_stop_choice_presented(payload: Dictionary) -> void:
|
||||
curio_choice_presented.emit(payload)
|
||||
|
||||
|
||||
func _relay_stop_resolved(result: Dictionary) -> void:
|
||||
curio_resolved.emit(result)
|
||||
|
||||
|
||||
# ---- 冒险生命周期 ----
|
||||
|
||||
# 冒险开始(主种子注入恰一次——全冒险唯一 rng.new_adventure 调用点;重开 = 全新一轮:
|
||||
@@ -160,7 +141,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool:
|
||||
_pending_next_level = true
|
||||
_stop.restore_resolved(bp_result)
|
||||
_state = State.RUNNING
|
||||
curio_resolved.emit(_stop.resolved_payload())
|
||||
_stop.reemit_resolved() # 已兑付不重兑付只重发(信号面归停点件)
|
||||
return true
|
||||
_resume_bp = bp.duplicate(true)
|
||||
# 充能账本随断点回局(缺键 = 旧档/无断点,保持 _seed_attune_charges 种子态)
|
||||
@@ -419,7 +400,26 @@ func abandon() -> bool:
|
||||
# ---- 关卡推进(私有——序列编排单一出口)----
|
||||
|
||||
func _start_current_level() -> bool:
|
||||
_runner = LevelRunner.new(db, rng)
|
||||
# 关卡对象构造注入一次给齐(拆件批 T3——ctx 键集见 LevelRunner._init 头注;
|
||||
# 凭证出箱在组包时点取走〔节点级一次性——未用即过期语义不变〕)
|
||||
var outbox: Dictionary[Variant, Variant] = _stop.voucher_outbox()
|
||||
var pool_wiring: Dictionary[Variant, Variant] = {"deck": _curio_deck, "consume": _stop.consume_pool_purchase,
|
||||
"ticket": _feat_ticket_consider, "cap": _curio_cap_ok} if _shop_mode == "pool" else {}
|
||||
var ctx: Dictionary[Variant, Variant] = {
|
||||
"hero": _hero,
|
||||
"ledger": run_ledger,
|
||||
"adv_bucket": adventure_counters,
|
||||
"attunement": _attunement,
|
||||
"tree_rules": _tree_rules,
|
||||
"shop_mode": _shop_mode,
|
||||
"treasure_unlocked": _treasure_unlocked,
|
||||
"hero_attack_type": _hero.attack_type,
|
||||
"hero_armor_type": _hero.armor_type,
|
||||
"voucher_rap": int(outbox.get("rap", 0)),
|
||||
"voucher_time_sand": int(outbox.get("time_sand", 0)),
|
||||
"pool_wiring": pool_wiring,
|
||||
}
|
||||
_runner = LevelRunner.new(db, rng, ctx)
|
||||
_bind_level_runner()
|
||||
_stop.rebind_level_runner(_runner, level_index) # 停点件每关注入时点(GP 件入账回调换代——防跨关调死对象)
|
||||
# 断点续玩分支:图由主种子+参数档重生成同图(产图与计数器号位无关,停点零重抽语义随之消解),
|
||||
@@ -435,43 +435,24 @@ func _start_current_level() -> bool:
|
||||
# 呈现零 RNG 消耗、pick 走同一 _enter_node——测量面逐位零漂移。
|
||||
var ok: bool = _runner.start_level_external(level_index, apply_rest, true, resume_state)
|
||||
if ok:
|
||||
_inject_level_params()
|
||||
_emit_level_started()
|
||||
_present_level_entry(resume_state)
|
||||
_resume_bp = {}
|
||||
return ok
|
||||
|
||||
|
||||
# 关卡行进器接线(每关新实例:信号转发 + 共享账下传)
|
||||
# 关卡行进器接线(每关新实例:结算受理连接 + 能力件骑库存注入——信号转发族已退役,
|
||||
# 呈现/模拟器在 level_started 重取点直连 runner 信号〔is_connected 守卫恰好一次〕)
|
||||
func _bind_level_runner() -> void:
|
||||
# 树限次计数按关清零现由 LevelRunner.level_counters 随每关 new 天然承载(旧硬编码清除名单函数已删)
|
||||
# core↔core 接线只许方法 Callable(S6——lambda 捕获 self 会延长 RefCounted 生命周期乃至成环)
|
||||
_runner.node_entered.connect(_forward_node_entered)
|
||||
_runner.choices_presented.connect(_forward_choices_presented)
|
||||
_runner.step_row.connect(_forward_step_row)
|
||||
_runner.round_started.connect(_forward_round_started)
|
||||
_runner.board_changed.connect(_forward_board_changed)
|
||||
_runner.wallet_changed.connect(_forward_wallet_changed)
|
||||
_runner.level_finished.connect(_on_level_finished)
|
||||
_runner.set_hero(_hero, adventure_counters) # 主角账与冒险桶注入(账面单点共享——T1 过渡面)
|
||||
_runner.set_run_ledger(run_ledger) # 充能账瘦壳下传
|
||||
_runner.sync_attune_inventory() # 能力件骑库存面注入(随账下传同拍)
|
||||
_runner.choices_presented.connect(_on_level_choices_presented) # 选路停点快照触发(原转发兼快照双职的核心侧受理)
|
||||
_runner.sync_attune_inventory() # 能力件骑库存面注入(构造注入同拍)
|
||||
|
||||
|
||||
# 关卡参数注入(装配集/特性/rule 键/类型轴/商店池/凭证件 + level_started 广播)
|
||||
func _inject_level_params() -> void:
|
||||
_runner.set_attunement(_attunement) # 同调装配下传(enc 携带进战斗结算)
|
||||
_runner.set_feats(_hero.feats) # 特性持有集下传(活引用——关内兑付 relic 当关生效;begin_battle enc.feats 求值面)
|
||||
_runner.set_tree_rules(_tree_rules) # 树 rule 键集下传(A/B 面消费)
|
||||
_runner.set_hero_axes(_hero.attack_type, _hero.armor_type) # 两根类型轴快照下传(enc 携带进 spawn)
|
||||
if _shop_mode == "pool":
|
||||
# 商店共享池注入(库存账本引用+消费 handler 双下传——消费经注入 Callable 直达停点件兑付单点;
|
||||
# legacy 模式零注入零扰动。cap 检查回调四参注入)
|
||||
_runner.set_shop_pool(_curio_deck, _stop.consume_pool_purchase, _feat_ticket_consider, _curio_cap_ok)
|
||||
# 凭证件出箱注入(节点级/回合级一次性:注入即清零,未用即过期=寿命档语义)
|
||||
var outbox: Dictionary[Variant, Variant] = _stop.voucher_outbox()
|
||||
_runner.set_voucher_grants(int(outbox.get("rap", 0)), int(outbox.get("time_sand", 0)))
|
||||
# 三选一道具件跨关携带注入面不存在(aux 卡不在池种内→ carry 恒空集;
|
||||
# 容器字段保留=替换子交互惰性承载,零投放零行为)
|
||||
# 关卡开始广播(装配已随构造注入进 ctx——本函数只剩信号广播;信号顺序契约:
|
||||
# 关卡开始信号先于入口候选呈现〔defer_entry 两步式以本发射为界〕)
|
||||
func _emit_level_started() -> void:
|
||||
level_started.emit(level_index, String(_runner.level_row.get("display_name", "")))
|
||||
|
||||
|
||||
@@ -548,15 +529,9 @@ func _settle(result: String) -> void:
|
||||
_state = State.IDLE
|
||||
|
||||
|
||||
# ---- 转发族(签名与 LevelRunner 同款——呈现层对接透明)----
|
||||
|
||||
func _forward_node_entered(node: Dictionary, info: Dictionary) -> void:
|
||||
node_entered.emit(node, info)
|
||||
|
||||
|
||||
func _forward_choices_presented(candidates: Array) -> void:
|
||||
choices_presented.emit(candidates)
|
||||
_snapshot_breakpoint() # 停点快照(选路停点=唯一可恢复停点形态;战斗中不存)
|
||||
# 选路停点快照受理(level.choices_presented 直订——选路停点=唯一可恢复停点形态;战斗中不存)
|
||||
func _on_level_choices_presented(_candidates: Array) -> void:
|
||||
_snapshot_breakpoint()
|
||||
|
||||
|
||||
# ---- 断点快照(字段登记单源:写侧拼装与读侧回退策略同登记一表)----
|
||||
@@ -674,17 +649,3 @@ func _bp_read_int(key: String, bp: Dictionary, fallback: int, params: Dictionary
|
||||
return int(bp.get(key, fallback))
|
||||
|
||||
|
||||
func _forward_step_row(row: Dictionary) -> void:
|
||||
step_row.emit(row)
|
||||
|
||||
|
||||
func _forward_round_started(ri: int) -> void:
|
||||
round_started.emit(ri)
|
||||
|
||||
|
||||
func _forward_board_changed() -> void:
|
||||
board_changed.emit()
|
||||
|
||||
|
||||
func _forward_wallet_changed(event: String, gp_val: int, extra: Dictionary) -> void:
|
||||
wallet_changed.emit(event, gp_val, extra)
|
||||
@@ -459,6 +459,11 @@ func voucher_outbox() -> Dictionary:
|
||||
return out
|
||||
|
||||
|
||||
# 已兑付态重发(宿主 resume 已兑付分支调用——信号面归停点件,续玩不重兑付只重发载荷)
|
||||
func reemit_resolved() -> void:
|
||||
curio_resolved.emit(_resolved.duplicate(true))
|
||||
|
||||
|
||||
# 已兑付态清除(宿主 continue_to_next_level 推进受理时调用——重兑付防御复位面)
|
||||
func clear_resolved() -> void:
|
||||
_resolved = {}
|
||||
|
||||
+37
-60
@@ -4,6 +4,8 @@
|
||||
## 种子域:单关模式 start_level 自播种;冒险模式 start_level_external 不重播种(主种子恰一次归 AdventureRunner)。
|
||||
## 调试浮窗百分比通道单点归主角账(set_hero_override 转调——公式消费面同源)。
|
||||
## 信号顺序契约:呈现层须在 level_started 回调重取 runner 实例,choices_presented 才不会被空防御丢弃。
|
||||
## 关卡开始信号前零发射:本件全部信号在 level_started 发射前保持静默(构造与 start 收参零信号——
|
||||
## defer_entry 两步式以 level_started 为界,先广播后呈现入口候选;配断言面见冒烟源码断言〔拆件批 V5〕)。
|
||||
class_name LevelRunner
|
||||
extends RefCounted
|
||||
|
||||
@@ -74,11 +76,38 @@ var visited: Array[Dictionary] = [] # 已兑付/到达节点
|
||||
var _generator # 参数化长图生成器(惰性建——NodeMapGenerator 持注入的 RngService)
|
||||
|
||||
|
||||
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore) -> void:
|
||||
# 构造注入一次给齐(拆件批 T3——后置 setter 群与商店重绑机制退役;_bind_shop 全仓仅本处构造调用)。
|
||||
# ctx 键(冒险模式由 AdventureRunner._start_current_level 组包;缺省空字典 = 单关测试模式:
|
||||
# 私有主角账自建〔空 override〕、充能账瘦壳自建、冒险桶本实例自有、pool 模式零注入):
|
||||
# hero:HeroLedger(共享主角账——活引用)/ledger:RunLedger(充能账瘦壳)/adv_bucket:Dictionary(冒险桶宿主字典)
|
||||
# attunement:Array(同调装配集——活引用)/tree_rules:Array(树 rule 键集——活引用)/shop_mode:String("pool" 才注入池面)
|
||||
# treasure_unlocked:Dictionary/hero_attack_type:String/hero_armor_type:String
|
||||
# voucher_rap:int/voucher_time_sand:int(凭证出箱注入面)
|
||||
# pool_wiring:Dictionary(池模式装配 4 参:{deck:Array, consume:Callable, ticket:Callable, cap:Callable}——缺省空字典 = legacy 零注入)
|
||||
func _init(db_ref: ConfigDb, rng_ref: RngServiceCore, ctx: Dictionary = {}) -> void:
|
||||
db = db_ref
|
||||
rng = rng_ref
|
||||
_hero.db = db # 直构私有账补表引用(构造参形态 T3 收口)
|
||||
if ctx.get("hero", null) is HeroLedger:
|
||||
_hero = ctx["hero"]
|
||||
_feats = _hero.feats # 特性集活引用注入(关内兑付 relic 当关生效——契约 6)
|
||||
else:
|
||||
_hero.db = db # 直构私有账补表引用(单关模式——实例不重建,值重置走 start_level)
|
||||
if ctx.get("ledger", null) is RunLedger:
|
||||
run_ledger = ctx["ledger"]
|
||||
if ctx.get("adv_bucket", null) is Dictionary:
|
||||
_adv_bucket = ctx["adv_bucket"]
|
||||
_treasure_unlocked = ctx.get("treasure_unlocked", {}) if ctx.get("treasure_unlocked", {}) is Dictionary else {}
|
||||
_attunement = ctx.get("attunement", []) if ctx.get("attunement", []) is Array else [] # 活引用注入(契约 6——禁快照)
|
||||
_tree_rules = ctx.get("tree_rules", []) if ctx.get("tree_rules", []) is Array else [] # 活引用注入
|
||||
_hero_attack_type = String(ctx.get("hero_attack_type", ""))
|
||||
_hero_armor_type = String(ctx.get("hero_armor_type", ""))
|
||||
_voucher_rap = maxi(0, int(ctx.get("voucher_rap", 0)))
|
||||
_voucher_time_sand = maxi(0, int(ctx.get("voucher_time_sand", 0)))
|
||||
_shop = ShopSession.new(db, rng)
|
||||
var pool: Dictionary[Variant, Variant] = ctx.get("pool_wiring", {}) as Dictionary
|
||||
if String(ctx.get("shop_mode", "")) == "pool" and not pool.is_empty():
|
||||
_shop.set_shop_pool(pool.get("deck", []) as Array, pool.get("consume", Callable()) as Callable,
|
||||
pool.get("ticket", Callable()) as Callable, pool.get("cap", Callable()) as Callable)
|
||||
_bind_shop()
|
||||
|
||||
|
||||
@@ -602,27 +631,14 @@ func pick_next_node(node_id: int) -> bool:
|
||||
func current_battle() -> Variant:
|
||||
return _settlement
|
||||
|
||||
# aux 解锁门控集注入(AdventureRunner 自 params 传递)
|
||||
func set_treasure_unlocked(ids: Dictionary) -> void:
|
||||
_treasure_unlocked = ids
|
||||
if _settlement != null:
|
||||
_settlement.set_treasure_unlocked(ids)
|
||||
# 共享充能账瘦壳(RunLedger 瘦身后只承载 attune_charges;构造注入)
|
||||
var run_ledger: RunLedger = RunLedger.new()
|
||||
|
||||
|
||||
# 同调装配集注入(冒险开始参数面;AdventureRunner 唯一调用方)
|
||||
func set_attunement(ids: Array) -> void:
|
||||
_attunement = ids.duplicate()
|
||||
_bind_shop() # 装配集共享引用换代重绑(刷新执照读数面)
|
||||
|
||||
|
||||
# 特性持有集注入(AdventureRunner._start_current_level 唯一调用方;begin_battle
|
||||
# enc.feats 携带进战斗求值;**共享引用不复制**——关内奖励节点兑付的 relic 须当关后续战斗生效
|
||||
# (attune_charges 共享账本同款);空集=零扰动)
|
||||
# 特性持有集(begin_battle enc.feats 求值面;**共享引用不复制**——关内奖励节点兑付的 relic 须当关生效)
|
||||
var _feats: Array[Variant] = []
|
||||
|
||||
|
||||
func set_feats(ids: Array) -> void:
|
||||
_feats = ids
|
||||
# 树 rule 键集(begin_battle enc.tree_rules 携带;Fork 激活侧 effects——构造注入活引用)
|
||||
var _tree_rules: Array[Variant] = []
|
||||
|
||||
|
||||
# 特性持有集读面(HUD C 区特性段数据源)
|
||||
@@ -630,55 +646,16 @@ func feats_held() -> Array:
|
||||
return _feats.duplicate()
|
||||
|
||||
|
||||
# 树 rule 键集注入(begin_battle enc.tree_rules 携带;Fork 激活侧 effects)
|
||||
var _tree_rules: Array[Variant] = []
|
||||
|
||||
|
||||
func set_tree_rules(rules: Array) -> void:
|
||||
_tree_rules = rules.duplicate()
|
||||
_bind_shop() # rule 键集共享引用换代重绑(散折价判定面)
|
||||
|
||||
|
||||
# 两根类型轴注入(adventure 参数面快照;空串=未注入→锻造配置默认值回退。
|
||||
# 消费面:effective_ac 甲型基础 AC 层 + begin_battle enc 携带进 unit_container.spawn 英雄快照)
|
||||
func set_hero_axes(attack_type: String, armor_type: String) -> void:
|
||||
_hero_attack_type = attack_type
|
||||
_hero_armor_type = armor_type
|
||||
|
||||
|
||||
# 共享充能账瘦壳注入(RunLedger 瘦身后只承载 attune_charges;战斗侧只走账类原语)
|
||||
var run_ledger: RunLedger = RunLedger.new()
|
||||
|
||||
|
||||
# 主角账只读出口(加算区生产读面承接—— AdventureRunner.hero() 同款组件交出)
|
||||
func hero() -> HeroLedger:
|
||||
return _hero
|
||||
|
||||
|
||||
# 主角实体账注入(冒险模式共享 AdventureRunner 建件——生命/钱包/加算区/类型轴/特性集单点;
|
||||
# 随行注入冒险桶引用。T1 过渡 setter——T3 构造注入收口后本组 setter 退役)
|
||||
func set_hero(h: HeroLedger, adv_bucket: Dictionary) -> void:
|
||||
_hero = h
|
||||
_adv_bucket = adv_bucket
|
||||
_bind_shop() # 账面换代重绑(钱包/购买加算落点面)
|
||||
|
||||
|
||||
func set_run_ledger(l: RunLedger) -> void:
|
||||
run_ledger = l
|
||||
_bind_shop() # 账类换代重绑(充能账读数面)
|
||||
|
||||
|
||||
# 凭证件三账注入(储备金袋 RAP/时沙 首轮 AP/完美保险 标错豁免;节点级一次性,
|
||||
# battle begin 消费后归零;缺省 0=零扰动)
|
||||
# 凭证出箱注入面(储备金袋 RAP/时沙 首轮 AP;节点级一次性,battle begin 消费后归零;缺省 0=零扰动)
|
||||
var _voucher_rap := 0
|
||||
var _voucher_time_sand := 0
|
||||
|
||||
|
||||
func set_voucher_grants(rap: int, time_sand: int) -> void:
|
||||
_voucher_rap = maxi(0, rap)
|
||||
_voucher_time_sand = maxi(0, time_sand)
|
||||
|
||||
|
||||
# 同调主动件充能账骑库存面注入(账本住
|
||||
# RunLedger;差额注入防续玩双份——resume 路径 restore_inventory
|
||||
# 已含断点内 attune 记录)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 三选一交互控制器:三选一停点编排(presented→pick→confirm/refresh→resolved→continue)+ TP0 令牌池卡片文案装配 + 秘宝显示名投影。
|
||||
## 信号连接只归组合根(curio_overlay/adventure 信号接至本类 on_* 入口);模态栈/演播后排窗/呈现刷新/fatal 经组合根回调注入,adventure/runner 现值注入。
|
||||
## 兑付写调用唯一经 adventure.pick_curio/apply_node_curio——本类零直写。
|
||||
## 兑付写调用唯一经 adventure.stop().pick_curio/apply_node_curio——本类零直写。
|
||||
## 同调装配不可变(冒险中零更换途径)——无替换子交互。
|
||||
extends RefCounted
|
||||
|
||||
@@ -69,7 +69,7 @@ func _skip_empty_stop() -> bool:
|
||||
toast.show_toast("可用战利品为空——本次三选一跳过", "info")
|
||||
var adventure = _adventure_get.call()
|
||||
if adventure != null:
|
||||
adventure.skip_empty_curio() # core 停点消化(其守卫=仅空候选放行)
|
||||
adventure.stop().skip_empty_curio() # core 停点消化(其守卫=仅空候选放行)
|
||||
if _curio_context == "reward_node":
|
||||
var runner = _runner_get.call()
|
||||
if runner != null:
|
||||
@@ -116,7 +116,7 @@ func on_presented(payload: Dictionary) -> void:
|
||||
func present_node_curio() -> void:
|
||||
var adventure = _adventure_get.call()
|
||||
_curio_context = "reward_node"
|
||||
var node_draw: Dictionary[Variant, Variant] = adventure.node_curio_candidates()
|
||||
var node_draw: Dictionary[Variant, Variant] = adventure.stop().node_curio_candidates()
|
||||
var node_cands: Array[Variant] = node_draw.get("candidates", []) as Array
|
||||
if node_cands.is_empty():
|
||||
# 空池降级——不开面、不挂节点停点(present_node_curio 未调即未挂),
|
||||
@@ -128,7 +128,7 @@ func present_node_curio() -> void:
|
||||
return
|
||||
_open_modal.call(curio_overlay)
|
||||
curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(node_cands))
|
||||
adventure.present_node_curio(node_cands)
|
||||
adventure.stop().present_node_curio(node_cands)
|
||||
_feed_refresh_display(adventure) # 刷新面投喂单点
|
||||
|
||||
|
||||
@@ -147,8 +147,8 @@ func show_pick() -> void:
|
||||
func _feed_refresh_display(adventure: AdventureRunner) -> void:
|
||||
var runner = _runner_get.call()
|
||||
var gp := int(runner.gp) if runner != null else 0
|
||||
var cost: int = adventure.curio_refresh_gp_cost()
|
||||
curio_overlay.set_refresh_display(adventure.attune_refresh_left(), cost, gp >= cost)
|
||||
var cost: int = adventure.stop().curio_refresh_gp_cost()
|
||||
curio_overlay.set_refresh_display(adventure.stop().attune_refresh_left(), cost, gp >= cost)
|
||||
|
||||
|
||||
func _curio_card_texts(candidates: Array) -> Array:
|
||||
@@ -181,22 +181,22 @@ func _curio_card_texts(candidates: Array) -> Array:
|
||||
# 选择面,节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途)
|
||||
func on_refresh() -> void:
|
||||
var adventure = _adventure_get.call()
|
||||
if adventure == null or not adventure.curio_stop_pending():
|
||||
if adventure == null or not adventure.stop().curio_stop_pending():
|
||||
return
|
||||
var runner = _runner_get.call()
|
||||
var refreshed: bool = adventure.refresh_curio()
|
||||
var refreshed: bool = adventure.stop().refresh_curio()
|
||||
if not refreshed:
|
||||
# 免费位(X/Y)已穷——GP 付费档(先扣钱包后重抽,余额不足拒绝零副作用)
|
||||
var cost: int = adventure.curio_refresh_gp_cost()
|
||||
var cost: int = adventure.stop().curio_refresh_gp_cost()
|
||||
if runner == null or not runner.spend_gp(cost):
|
||||
toast.show_toast("GP不足——刷新失败", "warn")
|
||||
return
|
||||
refreshed = adventure.refresh_curio_paid()
|
||||
refreshed = adventure.stop().refresh_curio_paid()
|
||||
_refresh_presentations.call() # 钱包变化上屏(对齐商店购买后刷新面)
|
||||
if not refreshed:
|
||||
return
|
||||
if _curio_context == "reward_node":
|
||||
var st: Dictionary[Variant, Variant] = adventure.node_curio_pending_state()
|
||||
var st: Dictionary[Variant, Variant] = adventure.stop().node_curio_pending_state()
|
||||
curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(st.get("candidates", []) as Array))
|
||||
_feed_refresh_display(adventure)
|
||||
|
||||
@@ -210,14 +210,14 @@ func on_pick_confirmed(item_id: String) -> void:
|
||||
var kind := parts[0]
|
||||
var tier := parts[1] if parts.size() > 1 else ""
|
||||
var rid := parts[2] if parts.size() > 2 else ""
|
||||
var ok: bool = adventure.apply_node_curio(kind, tier, rid) if _curio_context == "reward_node" else adventure.pick_curio(kind, tier, rid)
|
||||
var ok: bool = adventure.stop().apply_node_curio(kind, tier, rid) if _curio_context == "reward_node" else adventure.stop().pick_curio(kind, tier, rid)
|
||||
if not ok:
|
||||
push_error("[bonus_interact] 三选一兑付被拒——见输出日志")
|
||||
return
|
||||
# T5——节点渠道专长凭证拿到即开:候选已被替换为专长面,本地读在途态重绘(零 emit——context 保持 reward_node,
|
||||
# 后续兑付/确认仍走节点路径回图选路)
|
||||
if _curio_context == "reward_node" and kind == "feat_ticket":
|
||||
var st2: Dictionary[Variant, Variant] = adventure.node_curio_pending_state()
|
||||
var st2: Dictionary[Variant, Variant] = adventure.stop().node_curio_pending_state()
|
||||
curio_overlay.open_pick(Narrator.node_curio_header(), _curio_card_texts(st2.get("candidates", []) as Array))
|
||||
|
||||
|
||||
|
||||
@@ -4,20 +4,15 @@
|
||||
## 局态仍全局住在 run_scope 节点:本件零成员态、零信号连接新增(连接语句随原段搬运)。
|
||||
extends RefCounted
|
||||
|
||||
# 冒险编排构建+接线(开新局/续玩共用——信号连接只在组合根;断点注入同口)
|
||||
# 冒险编排构建+接线(开新局/续玩共用——信号连接只在组合根;断点注入同口)。
|
||||
# 信号直连(拆件批 T3):runner 六信号在 level_started 重取点重连(旧实例连接随引用释放消亡——
|
||||
# is_connected 守卫恰好一次);三选一信号面归停点件(每冒险一次——接线恰一次)。
|
||||
static func _wire_adventure(root: Variant) -> void:
|
||||
root.run_scope.adventure = AdventureRunner.new(root.db, root.get_node(root.AUTOLOAD_PATH_RNG))
|
||||
root.run_scope.adventure.step_row.connect(root._on_step_row)
|
||||
root.run_scope.adventure.round_started.connect(root._on_round_started)
|
||||
root.run_scope.adventure.board_changed.connect(root._on_board_changed)
|
||||
root.run_scope.adventure.node_entered.connect(root._on_runner_node)
|
||||
root.run_scope.adventure.choices_presented.connect(root._on_choices_presented)
|
||||
root.run_scope.adventure.wallet_changed.connect(root._on_wallet_changed)
|
||||
# 三选一信号族接线
|
||||
root.run_scope.adventure.curio_choice_presented.connect(root._bonus.on_presented)
|
||||
root.run_scope.adventure.curio_resolved.connect(root._bonus.on_resolved)
|
||||
root.run_scope.adventure.adventure_finished.connect(root._on_adventure_finished)
|
||||
root.run_scope.adventure.level_started.connect(root._on_adventure_level_started)
|
||||
root.run_scope.adventure.stop().curio_choice_presented.connect(root._bonus.on_presented)
|
||||
root.run_scope.adventure.stop().curio_resolved.connect(root._bonus.on_resolved)
|
||||
root.run_scope.adventure.set_save_manager(root.save_manager) # 断点承载注入(停点快照写盘;调试/模拟不经过此路径)
|
||||
|
||||
|
||||
@@ -26,6 +21,8 @@ static func _wire_adventure(root: Variant) -> void:
|
||||
# 本回调返回后才有 choices_presented 到达——入口选路层随即弹出)
|
||||
static func _on_adventure_level_started(root: Variant, level_index: int, display_name: String) -> void:
|
||||
root.run_scope.runner = root.run_scope.adventure.runner()
|
||||
# runner 信号直连(拆件批 T3——六转发族退役后的重取点重连;恰好一次断言 sc1 守卫承载)
|
||||
_connect_runner_once(root)
|
||||
# 开场行传当前生效值(runner.effective_atk/effective_max_hp 与战斗面板同源:
|
||||
# 表值+永久强化 flat+加算区 flat 后乘调试浮窗 pct,本回调时点 runner 已带全部加成);「含面板加成」
|
||||
# 后缀判断认任一加成源——永久强化 atk/max_hp flat 或调试浮窗 pct 或局内加算区 atk/max_hp
|
||||
@@ -39,6 +36,22 @@ static func _on_adventure_level_started(root: Variant, level_index: int, display
|
||||
root.log_view.append_bb(root.Narrator.level_open_line(display_name, int(root.run_scope.adventure.master_seed),
|
||||
root.run_scope.runner.effective_atk(), root.run_scope.runner.effective_max_hp(), boosted))
|
||||
|
||||
# runner 六信号重连(is_connected 守卫——未连即接;守卫失败 push_error=重连恰好一次契约承载)
|
||||
static func _connect_runner_once(root: Variant) -> void:
|
||||
var r = root.run_scope.runner
|
||||
if r == null:
|
||||
push_error("[冒险流程] runner 信号重连失败——level_started 后 runner 为空")
|
||||
return
|
||||
var pairs := [
|
||||
[r.step_row, root._on_step_row], [r.round_started, root._on_round_started],
|
||||
[r.board_changed, root._on_board_changed], [r.node_entered, root._on_runner_node],
|
||||
[r.choices_presented, root._on_choices_presented], [r.wallet_changed, root._on_wallet_changed],
|
||||
]
|
||||
for pair in pairs:
|
||||
if not (pair[0] as Signal).is_connected(pair[1]):
|
||||
(pair[0] as Signal).connect(pair[1])
|
||||
|
||||
|
||||
static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -> void:
|
||||
# 冒险模式:runner 逐关更替——每次节点事件重取(单关模式幂等);须先于 board_changed 回退读面
|
||||
if root.run_scope.is_adventure and root.run_scope.adventure != null:
|
||||
|
||||
@@ -58,7 +58,7 @@ static func _debug_run_data(root: Variant) -> Dictionary:
|
||||
if r == null:
|
||||
return {"seed": seed_txt, "level": "—", "node": "—", "hp": "—", "gp": "—", "keystone": "—"}
|
||||
return {"seed": seed_txt, "level": int(root.run_scope.adventure.level_index), "node": int(r.node_index),
|
||||
"hp": "%d/%d" % [int(r.hero_hp), int(r.effective_max_hp())], "gp": int(r.gp), "keystone": int(root.run_scope.adventure.keystone)}
|
||||
"hp": "%d/%d" % [int(r.hero_hp), int(r.effective_max_hp())], "gp": int(r.gp), "keystone": root.run_scope.adventure.hero().keystone_gained()}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -144,8 +144,8 @@ func _ready() -> void:
|
||||
problems.append("当局数据 HP 失对(%s)" % str(rd.get("hp")))
|
||||
if int(rd.get("gp", -1)) != int(r.gp):
|
||||
problems.append("当局数据 GP 失对(%s ≠ %s)" % [str(rd.get("gp")), str(r.gp)])
|
||||
if int(rd.get("keystone", -1)) != int(main.run_scope.adventure.keystone):
|
||||
problems.append("当局数据钥石失对(%s ≠ %s)" % [str(rd.get("keystone")), str(main.run_scope.adventure.keystone)])
|
||||
if int(rd.get("keystone", -1)) != main.run_scope.adventure.hero().keystone_gained():
|
||||
problems.append("当局数据钥石失对(%s ≠ %s)" % [str(rd.get("keystone")), str(main.run_scope.adventure.hero().keystone_gained())])
|
||||
var close_btn: Button = ProbeUiUtil.find_button(main.debug_overlay, "关闭", "prefix")
|
||||
if close_btn == null:
|
||||
problems.append("浮窗关闭钮缺失(真实收窗路径中断)")
|
||||
|
||||
@@ -140,13 +140,14 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_
|
||||
|
||||
"level_maps": [], # 逐关节点图标识(生成图 = 关卡序号+本局主种子——MC 分层出数)
|
||||
}
|
||||
adr.step_row.connect(_on_step_row.bind(adr, st, collect_rows))
|
||||
adr.node_entered.connect(_on_adventure_node_entered.bind(adr, st, logger))
|
||||
adr.wallet_changed.connect(_on_adventure_wallet.bind(st, logger))
|
||||
# 信号直连(拆件批 T3):runner 信号在 level_started 重取点重连(转发族退役——重取点前移到本回调)
|
||||
adr.level_started.connect(_on_autoplay_level_started.bind(adr, st, collect_rows, logger))
|
||||
# 首关信号在 start_adventure 内同步发射(驱动循环起步前)——入口候选订阅须在本行前就位:
|
||||
# _on_autoplay_level_started 于本行同步执行(start_adventure 返回前),runner 三信号先于驱动循环就绪
|
||||
# 三选一停点驱动(确定性策略候选取首件、
|
||||
# 兑付后推进下一关——零随机流自开,同种子逐位可复放;无满位替换策略)
|
||||
adr.curio_choice_presented.connect(_on_adventure_bonus.bind(st, adr))
|
||||
adr.curio_resolved.connect(_on_adventure_curio_resolved.bind(st, adr))
|
||||
# 兑付后推进下一关——零随机流自开,同种子逐位可复放;无满位替换策略;信号面归停点件)
|
||||
adr.stop().curio_choice_presented.connect(_on_adventure_bonus.bind(st, adr))
|
||||
adr.stop().curio_resolved.connect(_on_adventure_curio_resolved.bind(st, adr))
|
||||
adr.adventure_finished.connect(_on_adventure_finished.bind(st, logger))
|
||||
adr.set_hero_override(atk_pct, hp_pct)
|
||||
# 同调分层注入(缺省 {} 与既有调用方逐位一致零行为变更)
|
||||
@@ -190,6 +191,20 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_
|
||||
return st
|
||||
|
||||
|
||||
# 每关开始(autoplay 直连面):重取 runner 并重连三信号(恰好一次——同关守卫:
|
||||
# level_started 每关恰一次发射,bind Callable 每次新建故以关卡号守卫防重入)
|
||||
static func _on_autoplay_level_started(level_index: int, _display_name: String, adr, st: Dictionary, collect_rows: bool, logger) -> void:
|
||||
if int(st.get("wired_level", -1)) == int(level_index):
|
||||
return # 同关重入防御(恰好一次——bind 句柄每回调新建,不以 Callable 相等判重)
|
||||
st["wired_level"] = int(level_index)
|
||||
var r = adr.runner()
|
||||
if r == null:
|
||||
return
|
||||
r.step_row.connect(_on_step_row.bind(adr, st, collect_rows))
|
||||
r.node_entered.connect(_on_adventure_node_entered.bind(adr, st, logger))
|
||||
r.wallet_changed.connect(_on_adventure_wallet.bind(st, logger))
|
||||
|
||||
|
||||
# 冒险模式节点采集(_on_node_entered 的 AdventureRunner 门面版——runner 逐关更替,经 adr.runner() 重取;
|
||||
# encounter_max_hp_seq 抓 effective_max_hp:node_entered 先于 begin_battle,encounter 尚为空字典,
|
||||
# 该值 = begin_battle 将收到 hero_max_hp 同一表达式〔规划书抓取点与代码事实冲突,按代码为准已登记〕)
|
||||
|
||||
@@ -54,8 +54,10 @@ func _drive_dump_run(db, seed_val: int) -> Dictionary:
|
||||
rng.set_master_seed(seed_val)
|
||||
var adr = AdventureRunnerScript.new(db, rng)
|
||||
var ctx := {"result": "", "dumps": [] as Array, "seq": 0, "adr": adr, "db": db}
|
||||
adr.node_entered.connect(_on_node_entered.bind(ctx))
|
||||
adr.curio_resolved.connect(_on_curio_resolved.bind(ctx))
|
||||
# 信号直连(拆件批 T3——转发族退役):runner 信号在 level_started 重取点重连(同关守卫恰一次);
|
||||
# 停点件信号面订 stop()(每冒险一次)
|
||||
adr.level_started.connect(_on_dump_level_started.bind(ctx))
|
||||
adr.stop().curio_resolved.connect(_on_curio_resolved.bind(ctx))
|
||||
if not adr.start_adventure(seed_val, PARAMS):
|
||||
ctx["result"] = "start_failed"
|
||||
return ctx
|
||||
@@ -134,6 +136,18 @@ static func _dump(ctx: Dictionary, tag: String) -> void:
|
||||
})
|
||||
|
||||
|
||||
# 每关开始(dump 直连面):重取 runner 重连 node_entered(同关守卫——level_started 每关恰一次发射)
|
||||
static func _on_dump_level_started(_li: int, _dn: String, ctx: Dictionary) -> void:
|
||||
var adr = ctx["adr"]
|
||||
if String(ctx.get("wired_level", "")) == "%s|%d" % [str(adr.master_seed), int(adr.level_index)]:
|
||||
return
|
||||
ctx["wired_level"] = "%s|%d" % [str(adr.master_seed), int(adr.level_index)]
|
||||
var r = adr.runner()
|
||||
if r == null:
|
||||
return
|
||||
r.node_entered.connect(_on_node_entered.bind(ctx))
|
||||
|
||||
|
||||
# 节点入口驱动(信号时点):入口 dump 覆盖节点域字段形态;rest/shop 直通继续,reward 呈现三选一
|
||||
static func _on_node_entered(node: Dictionary, _info: Dictionary, ctx: Dictionary) -> void:
|
||||
var adr = ctx["adr"]
|
||||
|
||||
@@ -55,9 +55,9 @@ func _smoke_adventure_api() -> void:
|
||||
# 入口停点观测(信号序 + 入口候选快照——容器写回规避 lambda 按值捕获)
|
||||
var sig_seq: Array = []
|
||||
var obs := {"entry_cands": []}
|
||||
adr.level_started.connect(_api_mark_started.bind(sig_seq))
|
||||
adr.choices_presented.connect(_api_mark_choices.bind(sig_seq, obs))
|
||||
adr.node_entered.connect(_api_mark_node.bind(sig_seq))
|
||||
# 直连面(转发信号退役):runner 信号在 level_started 回调内重连(构造期 runner 未建——
|
||||
# 信号序观测=先广播后连接的同步链承载)
|
||||
adr.level_started.connect(_api_mark_started.bind(sig_seq, obs, adr))
|
||||
Kit.check("冒险启动受理(主种子注入恰一次——state=running;种子 42=首局固定种子生成图——精确路径断言需要确定图(卡② 代差))", adr.start_adventure(42))
|
||||
Kit.check("进行中重复 start_adventure 拒绝(重开须待结算完毕)", not adr.start_adventure(43))
|
||||
Kit.check("停点前移次序契约:level_started 先于入口候选呈现(反序选路口会被呈现层 runner 空防御丢弃)", sig_seq.size() == 2 and String(sig_seq[0]) == "level_started" and String(sig_seq[1]) == "choices_presented")
|
||||
@@ -86,7 +86,12 @@ func _smoke_adventure_api() -> void:
|
||||
|
||||
|
||||
# 观测桩(信号序采集——bind 容器写回,签名按各信号实参定制)
|
||||
func _api_mark_started(_li: int, _dn: String, seq: Array) -> void:
|
||||
func _api_mark_started(_li: int, _dn: String, seq: Array, obs: Dictionary, adr) -> void:
|
||||
# 重取点重连(拆件批 T3 直连——回调时点 runner 已建;后续 choices_presented/node_entered 观测就位)
|
||||
if not adr.runner().choices_presented.is_connected(_api_mark_choices):
|
||||
adr.runner().choices_presented.connect(_api_mark_choices.bind(seq, obs))
|
||||
if not adr.runner().node_entered.is_connected(_api_mark_node):
|
||||
adr.runner().node_entered.connect(_api_mark_node.bind(seq))
|
||||
seq.append("level_started")
|
||||
|
||||
|
||||
@@ -410,7 +415,7 @@ func _smoke_adventure() -> void:
|
||||
var a12 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(12))
|
||||
a12.start_adventure(12)
|
||||
var cap := {"payload": {}}
|
||||
a12.curio_choice_presented.connect(func(p): cap["payload"] = p)
|
||||
a12.stop().curio_choice_presented.connect(func(p): cap["payload"] = p) # 停点件信号面(拆件批 T3 直连)
|
||||
var max_full: int = int(_db.hero.max_hp) + int(a12.hero().base_override_flat().get("max_hp", 0))
|
||||
a12.hero().hero_hp = max_full
|
||||
a12._on_level_finished({"result": "victory", "hp": max_full, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||
|
||||
@@ -106,7 +106,7 @@ func _init() -> void:
|
||||
var got_cands := {"cands": [] as Array}
|
||||
var adr_f = AdventureRunnerScript.new(db, load("res://core/rng_service.gd").new())
|
||||
adr_f.set_save_manager(sm_f) # 兑付后停点快照落盘承载(与真机组合根同构)
|
||||
adr_f.curio_choice_presented.connect(func(p): got_cands["cands"] = (p.get("candidates", []) as Array).duplicate())
|
||||
adr_f.stop().curio_choice_presented.connect(func(p): got_cands["cands"] = (p.get("candidates", []) as Array).duplicate()) # 停点件信号面(拆件批 T3 直连)
|
||||
Kit.check_quiet(adr_f.resume_adventure(bp_f, params_e), "P5 续玩受理(选择态分支)")
|
||||
Kit.check_quiet(adr_f.runner() == null, "P5 续玩不重跑关卡(三选一停点直回,_start_current_level 未走)")
|
||||
Kit.check_quiet(got_cands["cands"] == cands_e, "P5 续玩候选不重抽(逐位一致)")
|
||||
@@ -123,7 +123,7 @@ func _init() -> void:
|
||||
var adr_g = AdventureRunnerScript.new(db, load("res://core/rng_service.gd").new())
|
||||
adr_g.set_save_manager(sm_f) # 已兑付态续玩的后续快照同承载
|
||||
var got_result := {"r": {}}
|
||||
adr_g.curio_resolved.connect(func(r): got_result["r"] = r.duplicate())
|
||||
adr_g.stop().curio_resolved.connect(func(r): got_result["r"] = r.duplicate()) # 停点件信号面(拆件批 T3 直连)
|
||||
Kit.check_quiet(adr_g.resume_adventure(bp_g, params_e), "P5 已兑付态续玩受理")
|
||||
Kit.check_quiet(String(got_result["r"].get("picked", "")) == picked_e, "P5 已兑付不重付(resolved 重发同件)")
|
||||
Kit.check_quiet(adr_g.continue_to_next_level(), "P5 已兑付续玩后推进受理(下一关开始)")
|
||||
|
||||
@@ -121,4 +121,22 @@ func _init() -> void:
|
||||
Kit.check_quiet(h5.bonus_total("atk") == 0,
|
||||
"hr2 汇总与日记推导不符→拒载(汇总不回填——日记为正主)")
|
||||
|
||||
quit(Kit.finish("hero_ledger_test", "PASS — 主角账公式/三通道日记/断点片段往返与旧档回退 全绿"))
|
||||
# ---- sc3 源码断言(拆件批 T3——构造注入收口与转发族退役的静态面)----
|
||||
var adv_src := FileAccess.get_file_as_string("res://core/adventure_runner.gd")
|
||||
var lvl_src := FileAccess.get_file_as_string("res://core/level_runner.gd")
|
||||
var bind_shop_adv: int = adv_src.count("func _bind_shop")
|
||||
var bind_shop_lvl: int = lvl_src.count("func _bind_shop")
|
||||
var bind_calls := 0 # 调用行计数(排除 func 定义行——行首剥缩进后以 _bind_shop() 起始)
|
||||
for line in (adv_src + lvl_src).split("
|
||||
"):
|
||||
var t := line.strip_edges()
|
||||
if t.begins_with("_bind_shop()"):
|
||||
bind_calls += 1
|
||||
Kit.check_quiet(bind_shop_adv == 0 and bind_shop_lvl == 1,
|
||||
"sc3 _bind_shop 定义全仓仅 level_runner 构造一处(商店重绑机制退役)〔拆件批 V5〕")
|
||||
Kit.check_quiet(bind_calls == 1,
|
||||
"sc3 _bind_shop 调用全仓仅构造一处(bind_calls=%d)〔拆件批 V5〕" % bind_calls)
|
||||
Kit.check_quiet(not adv_src.contains("_forward_") and not adv_src.contains("signal node_entered"),
|
||||
"sc3 六转发信号族与 _forward_ 转发方法全仓零命中(信号直连收口)〔拆件批 V5〕")
|
||||
|
||||
quit(Kit.finish("hero_ledger_test", "PASS — 主角账公式/三通道日记/断点片段往返与旧档回退/源码断言 全绿"))
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user