主卡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:
DKChan committed 2026-10-27 03:51:35 +08:00
1 parent 4a9e4300ad
commit 4da7f3e2f2
13 files changed
+188276 -173

No files matched your search

+32 -71
View File
@@ -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)
+5
View File
@@ -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
View File
@@ -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 记录)