iter49-卡2:模态句柄分域批(句柄册三域化):UiModals 14→3纯字段容器+run/menu两新子容器+ModalFlow升格注册面直驻+全仓寻址换代+V1结构断言

This commit is contained in:
DKChan committed 2026-11-02 07:30:33 +08:00
1 parent 11beb4fc59
commit 240f1a1769
28 files changed
+552 -513

No files matched your search

+21 -21
View File
@@ -1,7 +1,7 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 组合根:唯一的模式流转与 core↔view 接线处(垂直切片验证:线性第一关承载元进度循环)。
## db.hero 运行期只读,加成经 runner.set_hero_override / debug_session 生效值入战斗。
## 禁链式穿透 settlement.*(相位读取只走 presentation_snapshot);模态开合只走 ui.modals.flow(注册面编排口)。
## 禁链式穿透 settlement.*(相位读取只走 presentation_snapshot);模态开合只走 ui.flow(注册面编排口)。
## 界面句柄与层清单在 ui 注册面(ui_registry.gd)——屏壳/战区/模态三域句柄与 LAYER_CONTRACT 单点宿主。
## 局态全在 run_scope.gd 节点,禁在屏/视图侧持有局态引用;autoload 节点路径常量单点在 AUTOLOAD_PATH_*。
extends Control
@@ -104,7 +104,7 @@ func _ready() -> void:
func _build_ui() -> void:
MainUiBuild.build(self)
ui.modals.debug_overlay.diagnose_requested.connect(_on_diagnose_pressed) # 诊断转储入口
ui.modals.menu.debug_overlay.diagnose_requested.connect(_on_diagnose_pressed) # 诊断转储入口
func _fatal_screen(msg: String) -> void:
@@ -126,11 +126,11 @@ func _start_adventure_from_menu() -> void:
#(确认才删、取消只关窗——裁决 3)。"gate"/"gate_new"=卡④ 大门两分支消费方(有/无断点),
# "campfire_leave"=篝火二次确认(卡④ T4 接入——分派机制扩展第三/第四消费方)。
func _on_confirm_accepted() -> void:
ui.modals.flow.close(ui.modals.confirm_dialog) # 模态栈单口
ui.flow.close(ui.modals.menu.confirm_dialog) # 模态栈单口
match _confirm_consumer:
"delete_slot":
save_manager.slots.delete_slot(_confirm_consumer_slot)
ui.modals.save_select_overlay.open_save_select(HudPresenter.save_select_data(save_manager)) # 就地刷新(该槽回空态)
ui.modals.menu.save_select_overlay.open_save_select(HudPresenter.save_select_data(save_manager)) # 就地刷新(该槽回空态)
"gate":
_camp_pres.on_camp_closed() # 冒险发起后营地场景屏隐藏(选档层同收——卡④ T4 关层收闭)
_resume_from_menu() # 继续上一段冒险(现役续玩编排同源——卡④ 契约 4)
@@ -144,7 +144,7 @@ func _on_confirm_accepted() -> void:
# 次确认分派(三钮形态中钮——大门「开始新冒险」出口;卡④ T4)
func _on_confirm_secondary() -> void:
ui.modals.flow.close(ui.modals.confirm_dialog) # 模态栈单口
ui.flow.close(ui.modals.menu.confirm_dialog) # 模态栈单口
match _confirm_consumer:
"gate":
_camp_pres.on_camp_closed()
@@ -153,7 +153,7 @@ func _on_confirm_secondary() -> void:
func _on_confirm_declined() -> void:
ui.modals.flow.close(ui.modals.confirm_dialog) # 只关窗(取消无状态变更——裁决 3:取消不删/卡④:退回营地);关侧恢复归 ModalFlow
ui.flow.close(ui.modals.menu.confirm_dialog) # 只关窗(取消无状态变更——裁决 3:取消不删/卡④:退回营地);关侧恢复归 ModalFlow
_confirm_consumer = ""
@@ -169,16 +169,16 @@ func _resume_from_menu() -> void:
if not save_manager.has_breakpoint():
return
var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot()
ui.modals.flow.close(ui.modals.debug_overlay) # 开冒险路径显式关浮窗(续玩同口径)
ui.flow.close(ui.modals.menu.debug_overlay) # 开冒险路径显式关浮窗(续玩同口径)
ui.screens.start_overlay.visible = false
_begin_session(true)
_wire_adventure()
run_scope.level_active = true
_sync_status_entry() # 续玩恢复点亮状态面板入口角标
ui.modals.level_screen.set_abandon_available(true)
ui.modals.run.level_screen.set_abandon_available(true)
ui.battle.log_view.clear()
logger.start_session(int(bp.get("master_seed", 0)))
run_scope.adventure.set_hero_override(ui.modals.debug_overlay.atk_pct(), ui.modals.debug_overlay.hp_pct())
run_scope.adventure.set_hero_override(ui.modals.menu.debug_overlay.atk_pct(), ui.modals.menu.debug_overlay.hp_pct())
# 同调三键随续玩下传(build_adventure_params 统一构造,与开新局同源同口径)
if not run_scope.adventure.resume_adventure(bp, build_adventure_params(save_manager, meta,
{"treasure_unlocked": _treasure_ui.unlocked_set()})):
@@ -214,16 +214,16 @@ static func build_adventure_params(save_manager_ref: SaveManagerCore, meta_ref:
# 冒险模式(三关连探——AdventureRunner 编排;转发信号签名与 runner 一致,接线照原样对接;
# 信号连接只在组合根)
func _start_adventure() -> void:
ui.modals.flow.close(ui.modals.debug_overlay) # 开冒险路径显式关浮窗(调试浮窗默认开态下开局不带窗;对局中经角标再开)
ui.flow.close(ui.modals.menu.debug_overlay) # 开冒险路径显式关浮窗(调试浮窗默认开态下开局不带窗;对局中经角标再开)
_begin_session(true)
_wire_adventure()
run_scope.level_active = true
_sync_status_entry() # 进对局点亮状态面板入口角标
ui.modals.level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期)
ui.modals.run.level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期)
ui.battle.log_view.clear()
var seed_val := _seed_value()
logger.start_session(seed_val)
run_scope.adventure.set_hero_override(ui.modals.debug_overlay.atk_pct(), ui.modals.debug_overlay.hp_pct())
run_scope.adventure.set_hero_override(ui.modals.menu.debug_overlay.atk_pct(), ui.modals.menu.debug_overlay.hp_pct())
# 永久强化注入:冒险开始参数面——meta 现算 override 显式传参(全零 = 白板)
# 开局参数面(同调三键同源构造——确定性承载=固定种子+参数档,首局教学图由种子域三分判定给 42)
if not run_scope.adventure.start_adventure(seed_val, build_adventure_params(save_manager, meta,
@@ -287,16 +287,16 @@ func _on_battle_finished(payload: Dictionary) -> void:
_refresh_presentations()
return
"level_end":
ui.modals.end_overlay.open_victory(Narrator.battle_end_text("victory", true),
ui.modals.run.end_overlay.open_victory(Narrator.battle_end_text("victory", true),
"返回", ui.battle.board_zone.board_view.get_global_rect(), _end_victory_opts(payload))
"standalone_end":
if result == "victory":
ui.modals.end_overlay.open_victory(Narrator.battle_end_text("victory", false),
ui.modals.run.end_overlay.open_victory(Narrator.battle_end_text("victory", false),
"回到遭遇选择", ui.battle.board_zone.board_view.get_global_rect(), _end_victory_opts(payload))
else:
ui.modals.end_overlay.open_defeat(Narrator.battle_end_text("defeat", false),
ui.modals.run.end_overlay.open_defeat(Narrator.battle_end_text("defeat", false),
"返回", ui.battle.board_zone.board_view.get_global_rect())
ui.modals.flow.open_after_playback(ui.modals.end_overlay)
ui.flow.open_after_playback(ui.modals.run.end_overlay)
_refresh_presentations()
@@ -562,7 +562,7 @@ func _on_wallet_changed(event: String, gp: int, extra: Dictionary)-> void:
func _on_board_action(cell: Vector2i, kind: String) -> void:
# 玩家动作 → 核心写调用的唯一通道(模态/无战斗拒绝在此收口)
if ui.modals.flow.locks_input():
if ui.flow.locks_input():
return
# 结算演播期棋盘冻结(v3 口径——不可点/不可移/不可挖):
# 锁面=排播队列活跃(纯队列判据;排播泵尾迹窗口不可作演播中标志——见 row_pacer._process 注)。
@@ -605,7 +605,7 @@ func _on_board_action(cell: Vector2i, kind: String) -> void:
func _on_stop_pressed() -> void:
if ui.modals.flow.locks_input():
if ui.flow.locks_input():
return
# stop 经批次提交(排雷玩法 D1×6①——不直调 行动经济)
if run_scope.settlement == null:
@@ -622,7 +622,7 @@ func _on_stop_pressed() -> void:
func _on_refresh_confirmed() -> void:
# 弃图刷新(挖掘期 Active 当场换图继续挖——D4;RAP 按门槛扣费 · 旧板封印作废,每轮上限读表)
if ui.modals.flow.locks_input():
if ui.flow.locks_input():
return
var ok: bool = false
if run_scope.level_active and run_scope.runner != null:
@@ -764,8 +764,8 @@ func _seed_value() -> int:
# 重放模板 0 即在此种子下标定〕;置位 true=教学局已收束的非首局走 ③ 随机);
# ③非首局空/非法框=引擎全局熵 randi()(Godot 4 启动自播种子,headless 同;不走 rng_service
# 封闭流、禁 randomize()/自建 RNG——组合根建种子属引擎熵豁免面,不进玩法 RNG 消费)
if ui.modals.debug_overlay.seed_edit.text.is_valid_int():
return int(ui.modals.debug_overlay.seed_edit.text)
if ui.modals.menu.debug_overlay.seed_edit.text.is_valid_int():
return int(ui.modals.menu.debug_overlay.seed_edit.text)
if save_manager.meta_state.career_state().first_adventure_done:
return randi() # rng-seed-exempt(非首局空框引擎熵——组合根建种子不进玩法 RNG 消费,static_scan S1/S2 标记豁免)
return 42
+29 -29
View File
@@ -30,7 +30,7 @@ static func _on_adventure_level_started(root: Variant, level_index: int, display
# 开场行只显示 atk/max_hp 两值,注记指向不存在的显示轴反成失真),白板局不带
var ov: Dictionary[Variant, Variant] = root.meta.hero_override()
var boosted: bool = int(ov.get("atk", 0)) > 0 or int(ov.get("max_hp", 0)) > 0 \
or root.ui.modals.debug_overlay.atk_pct() > 0 or root.ui.modals.debug_overlay.hp_pct() > 0 \
or root.ui.modals.menu.debug_overlay.atk_pct() > 0 or root.ui.modals.menu.debug_overlay.hp_pct() > 0 \
or root.run_scope.runner.journal().bonus_total("atk") > 0 \
or root.run_scope.runner.journal().bonus_total("max_hp") > 0
root.ui.battle.log_view.append_bb(root.Narrator.level_open_line(display_name, int(root.run_scope.adventure.master_seed),
@@ -70,21 +70,21 @@ static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -
root.ui.battle.log_view.append_bb(root.Narrator.reward_line())
elif t == "rest":
var healed: int = int(info.get("healed", 0))
root.ui.modals.flow.open(root.ui.modals.node_overlay)
root.ui.modals.node_overlay.show_rest(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0)))
root.ui.flow.open(root.ui.modals.run.node_overlay)
root.ui.modals.run.node_overlay.show_rest(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0)))
root.ui.battle.log_view.append_bb(root.Narrator.rest_line(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0))))
elif t == "shop":
# 商店房兑付:到达即开店——商品/货架快照与余额经 runner 只读投影喂入;
# 购买/离开归玩家意图(buy_requested/leave_pressed 接线),零自动推进。
# pool 面 = 共享池货架三选一同构卡;legacy 面 = 固定三件(off 对照承载)
root.ui.modals.flow.open(root.ui.modals.shop_overlay)
root.ui.flow.open(root.ui.modals.menu.shop_overlay)
if root.run_scope.runner.shop.is_shop_pool():
root.ui.modals.shop_overlay.open_shelf(root.run_scope.runner.shop.shop_shelf(), int(root.run_scope.runner.gp))
root.ui.modals.shop_overlay.set_visit_info(int(root.run_scope.runner.shop.shop_bp_visit_buys()), float(root.db.run.shop.price_a)) # 爬价规则行
root.ui.modals.shop_overlay.set_refresh_display(root.run_scope.runner.shop.shop_refresh_free_left(), root.run_scope.runner.shop.shop_refresh_cost(), int(root.run_scope.runner.gp) >= root.run_scope.runner.shop.shop_refresh_cost()) # MC-1
root.ui.modals.menu.shop_overlay.open_shelf(root.run_scope.runner.shop.shop_shelf(), int(root.run_scope.runner.gp))
root.ui.modals.menu.shop_overlay.set_visit_info(int(root.run_scope.runner.shop.shop_bp_visit_buys()), float(root.db.run.shop.price_a)) # 爬价规则行
root.ui.modals.menu.shop_overlay.set_refresh_display(root.run_scope.runner.shop.shop_refresh_free_left(), root.run_scope.runner.shop.shop_refresh_cost(), int(root.run_scope.runner.gp) >= root.run_scope.runner.shop.shop_refresh_cost()) # MC-1
else:
root.ui.modals.shop_overlay.open_shop(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
root.ui.modals.shop_overlay.set_visit_info(-1, 1.0) # legacy 面无爬价行(off 对照承载)
root.ui.modals.menu.shop_overlay.open_shop(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
root.ui.modals.menu.shop_overlay.set_visit_info(-1, 1.0) # legacy 面无爬价行(off 对照承载)
root.ui.battle.log_view.append_bb(root.Narrator.shop_line(int(root.run_scope.runner.gp)))
root.logger.log_node(node, info, root.run_scope.runner.node_index)
root._refresh_presentations()
@@ -110,20 +110,20 @@ static func _on_adventure_finished(root: Variant, summary: Dictionary) -> void:
if not detail.is_empty():
text += "\n" + root.Narrator.keystone_settle_text(detail, root._bonus.item_name_lookup())
if result == "completed":
root.ui.modals.end_overlay.open_victory(text, "返回冒险", root.ui.battle.board_zone.board_view.get_global_rect(),
root.ui.modals.run.end_overlay.open_victory(text, "返回冒险", root.ui.battle.board_zone.board_view.get_global_rect(),
{"gp_summary": "本局 GP 入账 · 合计 %d" % int(summary.get("gp_final", 0))}) # GP 行(取数=summary gp_final 清账前快照);钥石明细已在 text(keystone_settle_text)——三行账
else:
root.ui.modals.end_overlay.open_defeat(text, "返回", root.ui.battle.board_zone.board_view.get_global_rect())
root.ui.modals.run.end_overlay.open_defeat(text, "返回", root.ui.battle.board_zone.board_view.get_global_rect())
root.logger.log_level_end(summary) # 试玩时间线:冒险终局(摘要随载荷)
if root.run_scope.runner != null:
root.run_scope.runner.restore_hero_base()
root.ui.modals.flow.open_after_playback(root.ui.modals.end_overlay)
root.ui.flow.open_after_playback(root.ui.modals.run.end_overlay)
root._refresh_presentations()
static func _on_battle_end_continue(root: Variant) -> void:
# 终局出口:返回开始界面(冒险摘要留档试玩日志;钥石在收束时入存档)
root.ui.modals.flow.close(root.ui.modals.end_overlay)
root.ui.modals.end_overlay.close()
root.ui.flow.close(root.ui.modals.run.end_overlay)
root.ui.modals.run.end_overlay.close()
# 三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面
if root._bonus.resume_if_pending(): # 三选一停点在途恢复入口(curio_interact_controller)
return
@@ -131,9 +131,9 @@ static func _on_battle_end_continue(root: Variant) -> void:
if root.run_scope.settlement_stop_timer.is_armed():
root.run_scope.settlement_stop_timer.disarm()
_enter_level_screen(root)
root.ui.modals.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held())))
root.ui.modals.level_screen.open_choices(root.run_scope.pending_candidates, root.run_scope.runner)
root.ui.modals.level_screen.apply_status(level_map_status_payload(root.run_scope.runner))
root.ui.modals.run.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held())))
root.ui.modals.run.level_screen.open_choices(root.run_scope.pending_candidates, root.run_scope.runner)
root.ui.modals.run.level_screen.apply_status(level_map_status_payload(root.run_scope.runner))
root.run_scope.pending_candidates = []
root._refresh_presentations()
return
@@ -141,7 +141,7 @@ static func _on_battle_end_continue(root: Variant) -> void:
# 局态封印/账本/模态挂起全复位(局态字段清单见 run_scope.gd)
root._debug.reset()
root._begin_session(false)
root.ui.modals.debug_overlay.reset_sliders()
root.ui.modals.menu.debug_overlay.reset_sliders()
root.ui.screens.start_overlay.visible = true
root._refresh_presentations()
@@ -158,7 +158,7 @@ static func _on_inter_next_pressed(root: Variant) -> void:
static func _on_node_continue(root: Variant) -> void:
root.ui.modals.flow.close(root.ui.modals.node_overlay)
root.ui.flow.close(root.ui.modals.run.node_overlay)
if root.run_scope.runner != null:
# 确认 ≠ 推进——兑付确认后经 choices_presented 弹出选路口
root.run_scope.runner.continue_after_node()
@@ -176,24 +176,24 @@ static func _on_choices_presented(root: Variant, candidates: Array) -> void:
and String(root.run_scope.runner.current_node().get("type", "")) == "battle" and not candidates.is_empty():
root.run_scope.settlement_stop_timer.arm()
root.run_scope.pending_candidates = candidates.duplicate()
root.ui.modals.end_overlay.open_victory(root.Narrator.battle_end_text("victory", false), "下一节点",
root.ui.modals.run.end_overlay.open_victory(root.Narrator.battle_end_text("victory", false), "下一节点",
root.ui.battle.board_zone.board_view.get_global_rect(),
{"gp_summary": "战利品 GP +%d · 合计 %d" % [root.run_scope.last_gain_amount, int(root.run_scope.runner.gp)]})
root.ui.modals.flow.open_after_playback(root.ui.modals.end_overlay)
root.ui.flow.open_after_playback(root.ui.modals.run.end_overlay)
root._refresh_presentations()
return
_enter_level_screen(root)
root.ui.modals.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held())))
root.ui.modals.level_screen.open_choices(candidates, root.run_scope.runner)
root.ui.modals.level_screen.apply_status(level_map_status_payload(root.run_scope.runner))
root.ui.modals.run.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held())))
root.ui.modals.run.level_screen.open_choices(candidates, root.run_scope.runner)
root.ui.modals.run.level_screen.apply_status(level_map_status_payload(root.run_scope.runner))
root._refresh_presentations()
static func _on_route_chosen(root: Variant, node_id: int) -> void:
# 视图已按节点类型定去留——战斗/Boss 出屏(visible=false,此处收栈
# 记录一致性+回战斗屏);奖励/恢复/商店本屏保持激活(兑付覆盖层由节点事件域内弹出,模态栈
# 打开低层时自动隐藏本屏、关闭自动恢复)
if not root.ui.modals.level_screen.visible:
root.ui.modals.flow.close(root.ui.modals.level_screen)
if not root.ui.modals.run.level_screen.visible:
root.ui.flow.close(root.ui.modals.run.level_screen)
_exit_level_screen(root)
if root.run_scope.runner != null and not root.run_scope.runner.pick_next_node(node_id):
push_error("[组合根] 选路被拒(编号 %d 不在候选)" % node_id)
@@ -203,14 +203,14 @@ static func _on_route_chosen(root: Variant, node_id: int) -> void:
static func _enter_level_screen(root: Variant) -> void:
root.run_scope.level_screen_active = true
root.ui.screens.battle_root.visible = false
root.ui.modals.flow.open(root.ui.modals.level_screen)
root.ui.flow.open(root.ui.modals.run.level_screen)
root._sync_status_entry() # 屏级互斥切换同步(_open_modal 单口不识屏级状态)
static func _exit_level_screen(root: Variant) -> void:
root.run_scope.level_screen_active = false
root.ui.screens.battle_root.visible = true
if root.ui.modals.level_screen.visible:
root.ui.modals.flow.close(root.ui.modals.level_screen)
if root.ui.modals.run.level_screen.visible:
root.ui.flow.close(root.ui.modals.run.level_screen)
root._sync_status_entry() # 同上
+10 -10
View File
@@ -21,17 +21,17 @@ static func _show_debug_overlay(root: Variant) -> void:
# 门禁:对局中可开;本函数仍是唯一开窗入口,门禁逻辑收函数内:
# 棋盘输入锁/状态角标判据排除浮窗,
# 遮挡语义=ModalStack 登记制(他模态开时暂隐、关后自动恢复),此处零场景限制
if root.ui.modals.debug_overlay == null:
if root.ui.modals.menu.debug_overlay == null:
return
root.ui.modals.flow.open(root.ui.modals.debug_overlay)
root.ui.flow.open(root.ui.modals.menu.debug_overlay)
_sync_debug_overlay(root)
# 调试浮窗角标点击(两屏同款处理器——与状态角标同形制守卫;仍收口 _show_debug_overlay 单口)
static func _on_debug_badge_pressed(root: Variant) -> void:
var map_on: bool = root.ui.modals.level_screen != null and root.ui.modals.level_screen.visible
if (map_on and root.ui.modals.flow.badge_greyed_map()) or (not map_on and root.ui.modals.flow.badge_greyed_battle()):
var map_on: bool = root.ui.modals.run.level_screen != null and root.ui.modals.run.level_screen.visible
if (map_on and root.ui.flow.badge_greyed_map()) or (not map_on and root.ui.flow.badge_greyed_battle()):
return
_show_debug_overlay(root)
@@ -40,10 +40,10 @@ static func _on_debug_badge_pressed(root: Variant) -> void:
# 浮窗上下文同步(当局数据刷新+对局中按钮禁用单点):调用面=开窗口与模态开合
# 单口(凡影响开态/对局态的路径必经其一——与 _sync_status_entry 同款纪律)
static func _sync_debug_overlay(root: Variant) -> void:
if root.ui.modals.debug_overlay == null:
if root.ui.modals.menu.debug_overlay == null:
return
root.ui.modals.debug_overlay.set_context_locked(root.run_scope.settlement != null or root.run_scope.is_adventure) # 对局中批量模拟/两层调试入口禁用(长程异步模拟与活跃结算并存态不开放)
root.ui.modals.debug_overlay.update_run_data(_debug_run_data(root)) # 当局数据只读区块喂入(开窗+模态开合信号驱动)
root.ui.modals.menu.debug_overlay.set_context_locked(root.run_scope.settlement != null or root.run_scope.is_adventure) # 对局中批量模拟/两层调试入口禁用(长程异步模拟与活跃结算并存态不开放)
root.ui.modals.menu.debug_overlay.update_run_data(_debug_run_data(root)) # 当局数据只读区块喂入(开窗+模态开合信号驱动)
@@ -63,7 +63,7 @@ static func _debug_run_data(root: Variant) -> Dictionary:
static func _close_debug_overlay(root: Variant) -> void:
root.ui.modals.flow.close(root.ui.modals.debug_overlay) # 模态栈单口——开始层全程未隐藏,无需恢复
root.ui.flow.close(root.ui.modals.menu.debug_overlay) # 模态栈单口——开始层全程未隐藏,无需恢复
static func _on_tune_apply(root: Variant, atk_pct: int, hp_pct: int) -> void:
# 加成无持久态可"落"——各战斗开局经 board_resolvers.debug_hero_override/runner 现算生效(应用=关闭浮窗)
@@ -83,8 +83,8 @@ static func _start_debug_levels(root: Variant) -> void:
# settlement/编排态=状态机破坏;模拟在跑时 reset_sliders 会作废测量读数(d5 置灰之外的信号绕过闸)
if root.run_scope.is_adventure or root.run_scope.settlement != null or root._tune_sim.is_running():
return
root.ui.modals.debug_overlay.reset_sliders()
root.ui.modals.flow.close(root.ui.modals.debug_overlay) # 浮窗内入口发起——随开随收(对局期不可再开:门禁在 _show_debug_overlay)
root.ui.modals.menu.debug_overlay.reset_sliders()
root.ui.flow.close(root.ui.modals.menu.debug_overlay) # 浮窗内入口发起——随开随收(对局期不可再开:门禁在 _show_debug_overlay)
root.ui.screens.start_overlay.visible = false
root.ui.battle.log_view.clear()
root.ui.battle.log_view.append_bb(root.Narrator.debug_open_line(root._seed_value(), 1, true))
+7 -7
View File
@@ -18,22 +18,22 @@ static func on_esc_input(root: Variant) -> void:
var esc: Control = root.ui.modals.camp.esc_overlay
var window := _topmost_camp_window(root)
if window != null:
root.ui.modals.flow.close(window) # ① 先关最上层功能窗(v2.3 定稿)
root.ui.flow.close(window) # ① 先关最上层功能窗(v2.3 定稿)
return
if root.ui.modals.settings_overlay.visible:
if root.ui.modals.menu.settings_overlay.visible:
return # ①' 设置开在浮窗之上 → 无反馈(浮窗不隔层关闭——契约 3)
if esc.visible:
root.ui.modals.flow.close(esc) # ② 再按 Esc 关窗
root.ui.flow.close(esc) # ② 再按 Esc 关窗
return
if is_camp_context(root):
root.ui.modals.flow.open(esc) # ③ 营地语境唤起
root.ui.flow.open(esc) # ③ 营地语境唤起
# ④ 非营地语境 → 无反馈
# 五功能窗现开查询(camp.md v2.3 五窗互斥单开——模态栈深恒 1,至多一窗在开;生涯页入窗集)
static func _topmost_camp_window(root: Variant) -> Control:
for w: Control in [root.ui.modals.camp.forge, root.ui.modals.camp.treasure, root.ui.modals.camp.tree,
root.ui.modals.camp.codex, root.ui.modals.career_overlay]:
root.ui.modals.camp.codex, root.ui.modals.menu.career_overlay]:
if w != null and w.visible:
return w
return null
@@ -42,13 +42,13 @@ static func _topmost_camp_window(root: Variant) -> Control:
## 返回主菜单动线(契约 4):首步 close 浮窗(缺此步浮窗残留主菜单上方)→营地与选档层同收+开始层恢复
static func _on_return_to_menu(root: Variant) -> void:
root.ui.modals.flow.close(root.ui.modals.camp.esc_overlay)
root.ui.flow.close(root.ui.modals.camp.esc_overlay)
root._camp_pres.on_camp_closed() # 关层收闭单口(卡④ T3——营地层与选档层同收+开始层恢复)
## 设置出口(浮窗登记在设置层之下——open 只隐上方层,浮窗由树序遮挡非属性隐藏;关后重现=栈语义)
static func _on_settings(root: Variant) -> void:
root.ui.modals.flow.open(root.ui.modals.settings_overlay)
root.ui.flow.open(root.ui.modals.menu.settings_overlay)
## 退出出口(_quit_game 原样复用——settings_store.save_settings+quit,零语义副作用)
+30 -30
View File
@@ -1,15 +1,15 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 组合根覆盖层流程承接件:设置/状态面板/生涯页/选档界面/图例/钱包事件的开合与数据投喂。
## 本件是 main.gd 同名函数的逐字搬运(成员访问改 root 前缀)——main 侧留同名薄委托,探针 main.X 路径零改动。
## 全部开合走组合根模态编排口(root.ui.modals.flow.open/close);本件零成员态。
## 全部开合走组合根模态编排口(root.ui.flow.open/close);本件零成员态。
extends RefCounted
static func _open_settings(root: Variant) -> void:
root.ui.screens.start_overlay.visible = false
root.ui.modals.flow.open(root.ui.modals.settings_overlay) # 模态栈单口
root.ui.flow.open(root.ui.modals.menu.settings_overlay) # 模态栈单口
static func _on_settings_closed(root: Variant) -> void:
root.ui.modals.flow.close(root.ui.modals.settings_overlay) # 模态栈单口
root.ui.flow.close(root.ui.modals.menu.settings_overlay) # 模态栈单口
# 语境分支(卡⑤ 契约 3——谓词=camp_esc_overlay.visible,谁开的设置谁收):
# 营地浮窗路径关设置回浮窗不回主菜单(栈语义:浮窗登记在下、由树序遮挡非属性隐藏);
# 菜单路径保留开始层恢复硬副作用。
@@ -29,21 +29,21 @@ static func _status_data(root: Variant) -> Dictionary:
return HudPresenter.status_data(root.db, root.meta, root.run_scope.runner, root._treasure_ui)
static func _open_status_panel(root: Variant) -> void:
root.ui.modals.flow.open(root.ui.modals.status_panel) # 模态栈单口(开层前排空排播队列——既有层同款行为,目检确认项)
root.ui.modals.status_panel.open_status(_status_data(root))
root.ui.flow.open(root.ui.modals.run.status_panel) # 模态栈单口(开层前排空排播队列——既有层同款行为,目检确认项)
root.ui.modals.run.status_panel.open_status(_status_data(root))
static func _on_status_closed(root: Variant) -> void:
root.ui.modals.flow.close(root.ui.modals.status_panel) # 模态栈单口
root.ui.flow.close(root.ui.modals.run.status_panel) # 模态栈单口
static func _open_career(root: Variant) -> void:
root.ui.screens.start_overlay.visible = false
root.ui.modals.flow.open(root.ui.modals.career_overlay) # 模态栈单口
root.ui.modals.career_overlay.open_career(HudPresenter.career_data(root.db, root.save_manager, root.meta))
root.ui.flow.open(root.ui.modals.menu.career_overlay) # 模态栈单口
root.ui.modals.menu.career_overlay.open_career(HudPresenter.career_data(root.db, root.save_manager, root.meta))
static func _on_career_closed(root: Variant) -> void:
root.ui.modals.flow.close(root.ui.modals.career_overlay) # 模态栈单口
root.ui.flow.close(root.ui.modals.menu.career_overlay) # 模态栈单口
if not root.ui.modals.camp.scene.visible: # 营地语境关生涯不回开始层(笔记本入口——卡④ T3;栈已复现场景根)
root.ui.screens.start_overlay.visible = true
@@ -70,17 +70,17 @@ static func _on_camp_object_pressed(root: Variant, key: StringName) -> void:
_open_gate_confirm(root)
&"campfire":
root._confirm_consumer = "campfire_leave"
root.ui.modals.confirm_dialog.open("确定离开营地回到主菜单?", "离开", "留下")
root.ui.modals.menu.confirm_dialog.open("确定离开营地回到主菜单?", "离开", "留下")
static func _open_gate_confirm(root: Variant) -> void:
if root.save_manager.has_breakpoint():
root._confirm_consumer = "gate"
root.ui.modals.confirm_dialog.open_three("有未完成的冒险——继续上一段冒险,还是开始新冒险?",
root.ui.modals.menu.confirm_dialog.open_three("有未完成的冒险——继续上一段冒险,还是开始新冒险?",
"继续上一段冒险", "开始新冒险", "取消")
return
root._confirm_consumer = "gate_new"
root.ui.modals.confirm_dialog.open("是否开始新冒险?", "是", "否")
root.ui.modals.menu.confirm_dialog.open("是否开始新冒险?", "是", "否")
# 生涯页快照投影 —— 承接体见 HudPresenter.career_data(开层调用点直接改引)
@@ -93,11 +93,11 @@ static func _open_gate_confirm(root: Variant) -> void:
# 该钮的唯一落点(卡③ 批内探针函数直调=中间态,实现报告「已知中间态」登记)。
static func _open_save_select(root: Variant) -> void:
root.ui.screens.start_overlay.visible = false
root.ui.modals.flow.open(root.ui.modals.save_select_overlay) # 模态栈单口
root.ui.modals.save_select_overlay.open_save_select(HudPresenter.save_select_data(root.save_manager))
root.ui.flow.open(root.ui.modals.menu.save_select_overlay) # 模态栈单口
root.ui.modals.menu.save_select_overlay.open_save_select(HudPresenter.save_select_data(root.save_manager))
static func _on_save_select_closed(root: Variant) -> void:
root.ui.modals.flow.close(root.ui.modals.save_select_overlay) # 模态栈单口
root.ui.flow.close(root.ui.modals.menu.save_select_overlay) # 模态栈单口
root.ui.screens.start_overlay.visible = true
@@ -125,18 +125,18 @@ static func _on_save_select_load(root: Variant, slot: int) -> void:
static func _on_save_select_delete(root: Variant, slot: int) -> void:
root._confirm_consumer = "delete_slot"
root._confirm_consumer_slot = slot
root.ui.modals.flow.open(root.ui.modals.confirm_dialog) # 模态栈单口(显示编排/挂起兜底/遮挡记账——与其余覆盖层同口)
root.ui.modals.confirm_dialog.open("删除存档位 %d?删除后该档不可恢复。" % slot, "删除", "取消")
root.ui.flow.open(root.ui.modals.menu.confirm_dialog) # 模态栈单口(显示编排/挂起兜底/遮挡记账——与其余覆盖层同口)
root.ui.modals.menu.confirm_dialog.open("删除存档位 %d?删除后该档不可恢复。" % slot, "删除", "取消")
# 入口角标点击(两屏同款处理器):P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰,
# 探针 pressed.emit 绕过 disabled 时由此兜住;地图屏自身的登记开态不计——has_open_excluding)
static func _on_status_badge_pressed(root: Variant) -> void:
var map_on: bool = root.ui.modals.level_screen != null and root.ui.modals.level_screen.visible
var map_on: bool = root.ui.modals.run.level_screen != null and root.ui.modals.run.level_screen.visible
# P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰,探针 pressed.emit 绕过 disabled 时由此兜住;
# 地图屏自身的登记开态不计;判据排除调试浮窗:对局中浮窗开着时状态角标仍可点)
if (map_on and root.ui.modals.flow.badge_greyed_map()) or (not map_on and root.ui.modals.flow.badge_greyed_battle()):
if (map_on and root.ui.flow.badge_greyed_map()) or (not map_on and root.ui.flow.badge_greyed_battle()):
return
_open_status_panel(root)
@@ -146,14 +146,14 @@ static func _on_status_badge_pressed(root: Variant) -> void:
# 地图屏角标=随屏;置灰=登记册上除地图屏自身外任一层开着(防「看得见点不着」态)。
# 调用面=模态开合单口与屏级互斥口(凡影响开态/屏态的路径必经其一)。
static func _sync_status_entry(root: Variant) -> void:
if root.ui.modals.status_panel == null or root.ui.modals.flow == null or root.ui.battle.board_zone == null or root.ui.modals.level_screen == null:
if root.ui.modals.run.status_panel == null or root.ui.flow == null or root.ui.battle.board_zone == null or root.ui.modals.run.level_screen == null:
return
var map_on: bool = root.ui.modals.level_screen.visible
var map_on: bool = root.ui.modals.run.level_screen.visible
root.ui.battle.board_zone.status_badge.visible = root.run_scope.level_active and not map_on
root.ui.modals.level_screen.status_badge.visible = map_on
root.ui.modals.run.level_screen.status_badge.visible = map_on
# 置灰判据为排除式(调试浮窗开态不计入:对局中浮窗开着时角标仍可点)
root.ui.battle.board_zone.status_badge.disabled = root.ui.modals.flow.badge_greyed_battle() # 互斥域谓词
root.ui.modals.level_screen.status_badge.disabled = root.ui.modals.flow.badge_greyed_map()
root.ui.battle.board_zone.status_badge.disabled = root.ui.flow.badge_greyed_battle() # 互斥域谓词
root.ui.modals.run.level_screen.status_badge.disabled = root.ui.flow.badge_greyed_map()
# 调试浮层角标(全局浮层任何 UI 不遮挡——可见性不随场景变,只抬 offset
# 避让:地图屏底栏 85px 常驻,角标抬至底栏上方 16px;其余场景右下角 16px 贴底)
root.ui.screens.debug_badge.offset_bottom = -101.0 if map_on else -16.0
@@ -171,19 +171,19 @@ static func _on_wallet_changed(root: Variant, event: String, gp: int, extra: Dic
root.logger.log_wallet(event, gp, extra)
if String(event) == "gain":
root.run_scope.last_gain_amount = int(extra.get("amount", 0)) # GP 增量取数(每战恰一笔 gain)
if root.ui.modals.shop_overlay.visible and root.run_scope.runner != null:
if root.ui.modals.menu.shop_overlay.visible and root.run_scope.runner != null:
if root.run_scope.runner.shop.is_shop_pool():
root.ui.modals.shop_overlay.refresh_shelf_state(int(root.run_scope.runner.gp)) # pool 面余额联动(货架内容仅买后重抽变)
root.ui.modals.menu.shop_overlay.refresh_shelf_state(int(root.run_scope.runner.gp)) # pool 面余额联动(货架内容仅买后重抽变)
else:
root.ui.modals.shop_overlay.refresh_state(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
root.ui.modals.menu.shop_overlay.refresh_state(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
root._refresh_presentations()
static func _on_legend_toggled(root: Variant) -> void:
if root.ui.modals.legend_overlay.is_open():
root.ui.modals.flow.close(root.ui.modals.legend_overlay)
if root.ui.modals.run.legend_overlay.is_open():
root.ui.flow.close(root.ui.modals.run.legend_overlay)
else:
root.ui.modals.flow.open(root.ui.modals.legend_overlay)
root.ui.flow.open(root.ui.modals.run.legend_overlay)
+62 -62
View File
@@ -46,7 +46,7 @@ static func _build_screens(root: Variant) -> void:
root.ui.battle.info_panel = root.ui.screens.adventure_screen.info_panel
root.ui.battle.board_zone = root.ui.screens.adventure_screen.board_zone
root.ui.battle.settlement_fx = root.ui.screens.adventure_screen.settlement_fx
root.ui.modals.level_screen = root.ui.screens.adventure_screen.level_screen
root.ui.modals.run.level_screen = root.ui.screens.adventure_screen.level_screen
static func _build_battle_wiring(root: Variant) -> void:
# 结算行排播泵(本体在 row_pacer.gd;本类注入视图句柄与呈现刷新回调,零信号连接)
@@ -57,7 +57,7 @@ static func _build_battle_wiring(root: Variant) -> void:
root.ui.screens.row_pacer.settlement_fx = root.ui.battle.settlement_fx
root.ui.screens.row_pacer.board_view = root.ui.battle.board_zone.board_view
root.ui.screens.row_pacer.refresh_request = root._refresh_presentations
root.ui.screens.row_pacer.pump_ended = root.ui.modals.flow.pump_ended # 演播完边沿统一派发(挂起 modal 优先,其次锁期点击重放)
root.ui.screens.row_pacer.pump_ended = root.ui.flow.pump_ended # 演播完边沿统一派发(挂起 modal 优先,其次锁期点击重放)
root.add_child(root.ui.screens.row_pacer)
# 秘宝两步交互(本体在 treasure_interact_controller.gd;settlement/runner/grant 随关卡/战斗更替,
# 经现值回调注入——仿 identity_resolver 只读回调先例;grant 每战新建编排器,现值取件防悬挂)
@@ -86,10 +86,10 @@ static func _build_overlays(root: Variant) -> void:
# < 商店 < 营地场景根 < 四子界面 < 设置 < 状态面板(只读) < 生涯页(只读) < 确认弹窗 < 调试浮窗;
# 关卡画面住冒险屏壳(树序在模态之下)——登记序仍夹在节点层与三选一层之间,遮挡语义零改动)
var overlays: Dictionary[Variant, Variant] = UiAssembler.build_overlays(root, root.db)
root.ui.modals.legend_overlay = overlays.legend_overlay
root.ui.modals.node_overlay = overlays.node_overlay
root.ui.modals.end_overlay = overlays.end_overlay
root.ui.modals.shop_overlay = overlays.shop_overlay
root.ui.modals.run.legend_overlay = overlays.legend_overlay
root.ui.modals.run.node_overlay = overlays.node_overlay
root.ui.modals.run.end_overlay = overlays.end_overlay
root.ui.modals.menu.shop_overlay = overlays.shop_overlay
root.ui.screens.camp_screen = CampScreenScene.instantiate()
root.add_child(root.ui.screens.camp_screen)
# 营地场景根装配(布局表经 ConfigDB 单点注入——视图禁自载表路径;CampOverlay 随四柜台拆解退役)
@@ -120,58 +120,58 @@ static func _build_overlays(root: Variant) -> void:
var hud_band := CampHudBand.new()
root.ui.modals.camp.scene.set_hud_band(hud_band)
# 屏壳/子界面插位由 LAYER_CONTRACT 槽统一落位——禁散落 move_child
root.ui.modals.legend_overlay.closed.connect(root.ui.modals.flow.close.bind(root.ui.modals.legend_overlay))
root.ui.modals.node_overlay.continue_pressed.connect(root._on_node_continue)
root.ui.modals.level_screen.route_chosen.connect(root._on_route_chosen)
root.ui.modals.level_screen.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(地图屏顶栏角标)
root.ui.modals.level_screen.abandon_requested.connect(root._on_abandon_requested) # 放弃入口
root.ui.modals.end_overlay.continue_pressed.connect(root._on_battle_end_continue)
root.ui.modals.run.legend_overlay.closed.connect(root.ui.flow.close.bind(root.ui.modals.run.legend_overlay))
root.ui.modals.run.node_overlay.continue_pressed.connect(root._on_node_continue)
root.ui.modals.run.level_screen.route_chosen.connect(root._on_route_chosen)
root.ui.modals.run.level_screen.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(地图屏顶栏角标)
root.ui.modals.run.level_screen.abandon_requested.connect(root._on_abandon_requested) # 放弃入口
root.ui.modals.run.end_overlay.continue_pressed.connect(root._on_battle_end_continue)
static func _build_aux_panels(root: Variant) -> void:
root.ui.modals.settings_overlay = SettingsOverlay.new(root.settings_store) # 最小设置面(音量/全屏/减动效)
root.add_child(root.ui.modals.settings_overlay)
root.ui.modals.settings_overlay.closed.connect(root._on_settings_closed)
root.ui.modals.menu.settings_overlay = SettingsOverlay.new(root.settings_store) # 最小设置面(音量/全屏/减动效)
root.add_child(root.ui.modals.menu.settings_overlay)
root.ui.modals.menu.settings_overlay.closed.connect(root._on_settings_closed)
# 局内状态面板·只读(登记设置层同档带 dim 遮罩,确认弹窗之前;
# 数据=组合根开层实时读 MetaProgression 访问器喂入,本层零写通路)
root.ui.modals.status_panel = StatusPanel.new()
root.add_child(root.ui.modals.status_panel)
root.ui.modals.status_panel.closed.connect(root._on_status_closed)
root.ui.modals.run.status_panel = StatusPanel.new()
root.add_child(root.ui.modals.run.status_panel)
root.ui.modals.run.status_panel.closed.connect(root._on_status_closed)
# 生涯页·只读全屏页(登记序=营地同档,设置层旁、确认弹窗之前;
# 树序同步=addChild 同位(登记序=树序=z 序纪律);数据=组合根开层实时读 SaveManager 计数
# 访问器+MetaProgression 访问器喂入,本层零写通路零跳转;入口=开始层「营地与系统」组生涯钮)
root.ui.modals.career_overlay = CareerOverlay.new()
root.add_child(root.ui.modals.career_overlay)
root.ui.modals.career_overlay.closed.connect(root._on_career_closed)
root.ui.modals.menu.career_overlay = CareerOverlay.new()
root.add_child(root.ui.modals.menu.career_overlay)
root.ui.modals.menu.career_overlay.closed.connect(root._on_career_closed)
# 选档界面(营地交互重做卡③):登记序=生涯同档(树序同步=addChild 同位);三动作只发意图信号
#(激活/创建/删除+确认分派归组合根——视图零数据层依赖);入口=「进入存档」钮(卡② mn3 接线)。
root.ui.modals.save_select_overlay = SaveSelectOverlay.new()
root.add_child(root.ui.modals.save_select_overlay)
root.ui.modals.save_select_overlay.closed.connect(root._on_save_select_closed)
root.ui.modals.save_select_overlay.new_slot_requested.connect(root._on_save_select_new)
root.ui.modals.save_select_overlay.load_slot_requested.connect(root._on_save_select_load)
root.ui.modals.save_select_overlay.delete_slot_requested.connect(root._on_save_select_delete)
root.ui.modals.menu.save_select_overlay = SaveSelectOverlay.new()
root.add_child(root.ui.modals.menu.save_select_overlay)
root.ui.modals.menu.save_select_overlay.closed.connect(root._on_save_select_closed)
root.ui.modals.menu.save_select_overlay.new_slot_requested.connect(root._on_save_select_new)
root.ui.modals.menu.save_select_overlay.load_slot_requested.connect(root._on_save_select_load)
root.ui.modals.menu.save_select_overlay.delete_slot_requested.connect(root._on_save_select_delete)
static func _build_curio(root: Variant) -> void:
# 提升三选一覆盖层(z 序 = 节点层之上、终局层之下;确认后同拍推进;
# 插位由 LAYER_CONTRACT curio 槽统一落位——禁散落 move_child)
root.ui.modals.curio_overlay = CurioPickOverlay.new()
root.add_child(root.ui.modals.curio_overlay)
root.ui.modals.run.curio_overlay = CurioPickOverlay.new()
root.add_child(root.ui.modals.run.curio_overlay)
# 提升三选一与替换子交互(本体在 curio_interact_controller.gd;本类只接线与回调注入,
# 模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调)
root._bonus = CurioInteractController.new(CurioFlowContext.new(root.db,
{"curio_overlay": root.ui.modals.curio_overlay, "end_overlay": root.ui.modals.end_overlay,
{"curio_overlay": root.ui.modals.run.curio_overlay, "end_overlay": root.ui.modals.run.end_overlay,
"board_view": root.ui.battle.board_zone.board_view, "toast": root.ui.battle.board_zone.toast},
{"meta_get": func() -> Variant: return root.meta, "save_get": func() -> Variant: return root.save_manager,
"adventure_get": func() -> Variant: return root.run_scope.adventure,
"runner_get": func() -> Variant: return root.run_scope.runner,
"adventure_mode_get": func() -> Variant: return root.run_scope.is_adventure},
{"open_modal": root.ui.modals.flow.open, "open_modal_after_playback": root.ui.modals.flow.open_after_playback,
"close_modal": root.ui.modals.flow.close, "refresh_presentations": root._refresh_presentations,
{"open_modal": root.ui.flow.open, "open_modal_after_playback": root.ui.flow.open_after_playback,
"close_modal": root.ui.flow.close, "refresh_presentations": root._refresh_presentations,
"fatal_screen": root._fatal_screen,
"gp_summary": _gp_summary_cb(root)})) # 构造参数收拢 context(u20 三束)
root.ui.modals.curio_overlay.pick_confirmed.connect(root._bonus.on_pick_confirmed)
root.ui.modals.curio_overlay.continue_pressed.connect(root._bonus.on_continue)
root.ui.modals.curio_overlay.refresh_requested.connect(root._bonus.on_refresh)
root.ui.modals.run.curio_overlay.pick_confirmed.connect(root._bonus.on_pick_confirmed)
root.ui.modals.run.curio_overlay.continue_pressed.connect(root._bonus.on_continue)
root.ui.modals.run.curio_overlay.refresh_requested.connect(root._bonus.on_refresh)
static func _build_menu_wiring(root: Variant) -> void:
# 菜单屏接线(句柄经屏壳暴露;三入口化——进入存档/设置/退出游戏,卡② mn3:
@@ -192,17 +192,17 @@ static func _build_menu_wiring(root: Variant) -> void:
root._camp_pres = CampShopPresenter.new(CampFlowContext.new(root.db,
{"camp_scene": root.ui.modals.camp.scene, "forge_view": root.ui.modals.camp.forge,
"treasure_view": root.ui.modals.camp.treasure, "tree_view": root.ui.modals.camp.tree,
"codex_view": root.ui.modals.camp.codex, "shop_overlay": root.ui.modals.shop_overlay,
"codex_view": root.ui.modals.camp.codex, "shop_overlay": root.ui.modals.menu.shop_overlay,
"toast": root.ui.battle.board_zone.toast, "start_overlay": root.ui.screens.start_overlay,
"save_select_overlay": root.ui.modals.save_select_overlay, "logger": root.logger,
"save_select_overlay": root.ui.modals.menu.save_select_overlay, "logger": root.logger,
"refresh_bus": camp_bus},
{"meta_get": func() -> Variant: return root.meta, "save_get": func() -> Variant: return root.save_manager,
"runner_get": func() -> Variant: return root.run_scope.runner},
{"open_modal": root.ui.modals.flow.open, "close_modal": root.ui.modals.flow.close,
{"open_modal": root.ui.flow.open, "close_modal": root.ui.flow.close,
"refresh_presentations": root._refresh_presentations})) # 构造参数收拢 context(u21 三束)
root.ui.modals.shop_overlay.buy_requested.connect(root._camp_pres.on_shop_buy)
root.ui.modals.shop_overlay.refresh_requested.connect(root._camp_pres.on_shop_refresh) # MC-1
root.ui.modals.shop_overlay.leave_pressed.connect(root._camp_pres.on_shop_leave)
root.ui.modals.menu.shop_overlay.buy_requested.connect(root._camp_pres.on_shop_buy)
root.ui.modals.menu.shop_overlay.refresh_requested.connect(root._camp_pres.on_shop_refresh) # MC-1
root.ui.modals.menu.shop_overlay.leave_pressed.connect(root._camp_pres.on_shop_leave)
# 营地信号接线(CampOverlay 退役——四子界面意图信号+场景点选组合根路由+closed 经 bind 携视图)
root.ui.modals.camp.forge.forge_buy_requested.connect(root._camp_pres.on_camp_forge_buy)
root.ui.modals.camp.forge.forge_unlock_requested.connect(root._camp_pres.on_camp_forge_unlock)
@@ -229,13 +229,13 @@ static func _build_debug_and_resolvers(root: Variant) -> void:
#(纪律「登记序 = 树序 = z 序」——confirm_dialog.open 禁运行时 move_child 自提抬层;
# 弹窗只在开始菜单域弹出,绘制序恒最上;
# 诊断转储入口在调试浮窗)
root.ui.modals.confirm_dialog = ConfirmDialog.new()
root.add_child(root.ui.modals.confirm_dialog)
root.ui.modals.menu.confirm_dialog = ConfirmDialog.new()
root.add_child(root.ui.modals.menu.confirm_dialog)
# 消费方编排(卡③ 契约 3/sl8——禁双常驻静态接线):open 前设 _confirm_consumer,
# confirmed/cancelled 后按该状态量分派;分派面可容纳后续消费方(卡④ 到位直接接入不返工)
root.ui.modals.confirm_dialog.confirmed.connect(root._on_confirm_accepted)
root.ui.modals.confirm_dialog.secondary_confirmed.connect(root._on_confirm_secondary) # 三钮次确认(卡④ 大门「开始新冒险」出口)
root.ui.modals.confirm_dialog.cancelled.connect(root._on_confirm_declined)
root.ui.modals.menu.confirm_dialog.confirmed.connect(root._on_confirm_accepted)
root.ui.modals.menu.confirm_dialog.secondary_confirmed.connect(root._on_confirm_secondary) # 三钮次确认(卡④ 大门「开始新冒险」出口)
root.ui.modals.menu.confirm_dialog.cancelled.connect(root._on_confirm_declined)
# Esc 浮窗三出口接线(卡⑤——意图信号经薄委托归 MainEscFlow 动线;输入入口=main._unhandled_input)
root.ui.modals.camp.esc_overlay.return_requested.connect(root._on_esc_return)
root.ui.modals.camp.esc_overlay.settings_requested.connect(root._on_esc_settings)
@@ -246,15 +246,15 @@ static func _build_debug_and_resolvers(root: Variant) -> void:
# 诊断转储入口在本浮窗,玩家画面零常驻调试件)。
# 树序在开始层与确认弹窗之后 = 浮于菜单之上的 Dialog(不隐藏开始层——与确认弹窗同款形态);
# 开窗唯一入口 = _show_debug_overlay(C6 单口——角标/探针/启动路径全收口)
root.ui.modals.debug_overlay = DebugOverlay.new(root.db, int(root.db.combat.hero.atk), int(root.db.combat.hero.max_hp))
root.add_child(root.ui.modals.debug_overlay)
root.ui.modals.debug_overlay.closed.connect(root.ui.modals.flow.close.bind(root.ui.modals.debug_overlay))
root.ui.modals.debug_overlay.apply_requested.connect(root._on_tune_apply)
root.ui.modals.debug_overlay.simulate_requested.connect(root._on_tune_simulate)
root.ui.modals.debug_overlay.debug_levels_requested.connect(root._start_debug_levels)
root.ui.modals.menu.debug_overlay = DebugOverlay.new(root.db, int(root.db.combat.hero.atk), int(root.db.combat.hero.max_hp))
root.add_child(root.ui.modals.menu.debug_overlay)
root.ui.modals.menu.debug_overlay.closed.connect(root.ui.flow.close.bind(root.ui.modals.menu.debug_overlay))
root.ui.modals.menu.debug_overlay.apply_requested.connect(root._on_tune_apply)
root.ui.modals.menu.debug_overlay.simulate_requested.connect(root._on_tune_simulate)
root.ui.modals.menu.debug_overlay.debug_levels_requested.connect(root._start_debug_levels)
# 棋盘解析回调族(有态绑定件——board_resolvers.gd 承接;组合根只接线;局部持有不占类成员位)
var resolvers = BoardResolversScript.new(root.db, root.ui.modals.debug_overlay, func() -> Variant: return root.run_scope.settlement)
var resolvers = BoardResolversScript.new(root.db, root.ui.modals.menu.debug_overlay, func() -> Variant: return root.run_scope.settlement)
# 奖励徽章数据源:地图战斗资源 身份解析只读回调(已开格身份 icon——挖掘期 Active 相位内有效)
root.ui.battle.board_zone.board_view.set_identity_resolver(resolvers.identity_lookup)
# 数字遮蔽数据源:地形变异 hide_number 解析只读回调(信息型呈现——board_view 渲染面读取)
@@ -271,21 +271,21 @@ static func _build_modal_flow(root: Variant) -> void:
# _apply_layer_contract 统一落位(关卡画面宿住冒险屏壳、营地屏壳夹商店与设置层之间、
# 三选一对位终局层之下等特例均在合同数据内表达,禁散落 move_child)
# 模态编排注入(ModalFlow 纯逻辑类:依赖仅 ModalStack+查询 Callable——实例随 ui.modals 容器构造建立)
root.ui.modals.flow.start_visible_get = func() -> bool: return root.ui.screens.start_overlay.visible
root.ui.modals.flow.inter_visible_get = func() -> bool: return root.ui.screens.inter_overlay.visible
root.ui.modals.flow.settlement_get = func() -> Variant: return root.run_scope.settlement
root.ui.modals.flow.rows_empty_get = func() -> bool: return root.ui.screens.row_pacer.pending_rows().is_empty()
root.ui.modals.flow.flush_rows = root.ui.screens.row_pacer.flush
root.ui.modals.flow.on_open_sync = func() -> void:
root.ui.flow.start_visible_get = func() -> bool: return root.ui.screens.start_overlay.visible
root.ui.flow.inter_visible_get = func() -> bool: return root.ui.screens.inter_overlay.visible
root.ui.flow.settlement_get = func() -> Variant: return root.run_scope.settlement
root.ui.flow.rows_empty_get = func() -> bool: return root.ui.screens.row_pacer.pending_rows().is_empty()
root.ui.flow.flush_rows = root.ui.screens.row_pacer.flush
root.ui.flow.on_open_sync = func() -> void:
root._sync_status_entry() # 状态面板入口角标可见性/置灰随开态同步
root._sync_debug_overlay() # 浮窗当局数据/按钮态随开态同步
root.ui.modals.flow.on_close_sync = func() -> void:
root.ui.flow.on_close_sync = func() -> void:
root._sync_status_entry() # 关闭恢复角标可点态(无残留——同源口径)
root._sync_debug_overlay() # 他层关闭复活浮窗时同步刷新(关后自动恢复语义配套)
root.ui.modals.flow.replay_click = root._replay_locked_click
root.ui.flow.replay_click = root._replay_locked_click
# 视觉层合同装配(唯一事实源=LAYER_CONTRACT:树序+登记序同源派生——改序只改合同表)
root.ui.apply_layer_contract(root)
root._tune_sim = TuneSimController.new(root.db, root.ui.modals.debug_overlay, int(root.db.combat.hero.max_hp))
root._tune_sim = TuneSimController.new(root.db, root.ui.modals.menu.debug_overlay, int(root.db.combat.hero.max_hp))
static func fatal_screen(root: Variant, msg: String) -> void:
var l := UiKit.label(msg,UiKit.FS_SUB, true, UiKit.RED_ALERT)
+2 -2
View File
@@ -1,6 +1,6 @@
## 营地交互域模态句柄子容器:持营地域全部模态句柄(终态六枚=营地场景根+锻造/秘宝/天赋树/图鉴四子界面+Esc 浮窗)。
## RefCounted 不挂树;随 UiModals._init 构造(与 flow 同拍)。Esc 浮窗枚随卡⑤落——禁空占位凑结构。
## 新营地句柄禁平铺回 ui_modals:营地域句柄寻址一律 ui.modals.camp.X 二层(模态句柄册分域纪律——全量分家另立批)。
## RefCounted 不挂树;随 UiModals._init 构造(flow 同拍升格由 UiRegistry._init 直驻)。Esc 浮窗枚随卡⑤落——禁空占位凑结构。
## 新营地句柄禁平铺回 ui_modals:营地域句柄寻址一律 ui.modals.camp.X 二层(模态句柄册分域纪律——册级全量分家已落地)。
class_name UiCampModals
extends RefCounted
+11
View File
@@ -0,0 +1,11 @@
## 菜单与跨语境域六句柄唯一容器——菜单层与多语境共用模态的句柄册(纯字段 RefCounted)。
## 分域纪律册级化:任何模态句柄禁平铺回 ui_modals(营地先例条款推广)——寻址一律 ui.modals.menu.X 二段式。
class_name UiMenuModals
extends RefCounted
var shop_overlay: ShopOverlay # 商店(冒险商店节点+营地商店跨语境)
var settings_overlay: SettingsOverlay # 设置(开始层+营地 Esc 双路径跨语境)
var career_overlay: CareerOverlay # 生涯记录(开始层笔记本+营地语境跨语境)
var save_select_overlay: SaveSelectOverlay # 选档(开始层+营地大门流跨语境)
var confirm_dialog: ConfirmDialog # 确认框(全语境)
var debug_overlay: DebugOverlay # 调试浮窗(全语境,non_locking)
+1
View File
@@ -0,0 +1 @@
uid://byakojaxcljbw
+9 -25
View File
@@ -1,33 +1,17 @@
## 模态层域句柄册:十二个平铺模态句柄+营地交互域子容器(camp)+模态栈编排(ModalFlow——登记/开合/输入锁谓词)的注册面域容器。
## RefCounted 不挂树;模态登记唯一口=register_layer(anchors 撑满宿主+排入 ModalStack)。
## flow 随容器构造即建(装配早期注入点经 root.ui.modals.flow.* 取 Callable——晚于 flow 建立时点)。
## 模态层域句柄册:三子容器(run=局内冒险域六句柄/menu=菜单与跨语境域六句柄/camp=营地域六句柄)
## 的纯字段容器——与 UiScreens/UiBattle 同型。
## 分域纪律全册化:任何模态句柄禁平铺回本册(营地先例条款推广)——子容器成员寻址一律 ui.modals.<域>.X 二段式。
## RefCounted 不挂树;模态登记唯一口=register_layer 已迁驻注册面 ui_registry(登记职责随编排域——
## ModalFlow 升格由 UiRegistry._init 直驻,本册无 flow 成员)。
class_name UiModals
extends RefCounted
var flow: ModalFlow
var legend_overlay: LegendOverlay
var node_overlay: NodeOverlay
var level_screen: LevelMapScreen
var curio_overlay: CurioPickOverlay
var end_overlay: BattleEndOverlay
var shop_overlay: ShopOverlay
var run: UiRunModals
var menu: UiMenuModals
var camp: UiCampModals
var settings_overlay: SettingsOverlay
var status_panel: StatusPanel
var career_overlay: CareerOverlay
var save_select_overlay: SaveSelectOverlay
var confirm_dialog: ConfirmDialog
var debug_overlay: DebugOverlay
func _init() -> void:
flow = ModalFlow.new() # 查询/钩子 Callable 由 _build_modal_flow 注入(注入时点不变——注入值面向已在座实例)
run = UiRunModals.new()
menu = UiMenuModals.new()
camp = UiCampModals.new() # 营地域句柄子容器(分域纪律——营地域句柄禁平铺回本册)
func register_layer(m: Control, tags: Array = []) -> void:
# 撑满宿主(Control 默认锚 = 0×0——全屏变暗层与居中布局整体失效;route 选路层预览截图实证后
# 统一收口:六个模态同病,探针经信号直发绕过真实点击从未暴露。anchors_and_offsets 而非 anchors
# ——后者会把控件重置回最小内容尺寸,ui_assembler 头注同款纪律)
m.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
flow.register(m, tags) # 排除组随层登记(non_locking=调试浮窗/screen_state=关卡地图屏)
+29 -17
View File
@@ -1,6 +1,7 @@
## 界面注册面:呈现层界面元素清单的唯一事实源——哪些层、什么序、地址在哪。
## 持有三域容器(screens/battle/modals)、LAYER_CONTRACT 合同表、node(key) 合同寻址、
## apply_layer_contract(root) 唯一落位口(全仓唯一 move_child 点)。
## 持有三域容器(screens/battle/modals)+模态编排件 ModalFlow 直驻(自 UiModals 升格——构造时点
## 与今等价:注册面构造点先于装配)、模态登记唯一口 register_layer(随登记职责迁驻)、
## LAYER_CONTRACT 合同表、node(key) 合同寻址、apply_layer_contract(root) 唯一落位口(全仓唯一 move_child 点)。
## RefCounted 不挂树:零信号连接、零会话期引用、零核心写调用——组合根单点装配不变。
class_name UiRegistry
extends RefCounted
@@ -42,12 +43,23 @@ const LAYER_CONTRACT: Array[Dictionary] = [
var screens: UiScreens
var battle: UiBattle
var modals: UiModals
var flow: ModalFlow # 模态编排件直驻注册面(升格自 UiModals——查询/钩子 Callable 由 _build_modal_flow 注入,注入时点不变)
func _init() -> void:
screens = UiScreens.new()
battle = UiBattle.new()
modals = UiModals.new()
flow = ModalFlow.new()
# 模态登记唯一口(自 ui_modals.gd 逐字迁驻——登记职责随编排域归注册面单点)
func register_layer(m: Control, tags: Array = []) -> void:
# 撑满宿主(Control 默认锚 = 0×0——全屏变暗层与居中布局整体失效;route 选路层预览截图实证后
# 统一收口:六个模态同病,探针经信号直发绕过真实点击从未暴露。anchors_and_offsets 而非 anchors
# ——后者会把控件重置回最小内容尺寸,ui_assembler 头注同款纪律)
m.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
flow.register(m, tags) # 排除组随层登记(non_locking=调试浮窗/screen_state=关卡地图屏)
# 合同层寻址(key→节点;纯查表不含层序语义——层序唯一事实源=LAYER_CONTRACT)
@@ -56,12 +68,12 @@ func node(key: String) -> Node:
"adventure_screen": return screens.adventure_screen
"menu_screen": return screens.menu_screen
"row_pacer": return screens.row_pacer
"legend_overlay": return modals.legend_overlay
"node_overlay": return modals.node_overlay
"level_screen": return modals.level_screen
"curio_overlay": return modals.curio_overlay
"end_overlay": return modals.end_overlay
"shop_overlay": return modals.shop_overlay
"legend_overlay": return modals.run.legend_overlay
"node_overlay": return modals.run.node_overlay
"level_screen": return modals.run.level_screen
"curio_overlay": return modals.run.curio_overlay
"end_overlay": return modals.run.end_overlay
"shop_overlay": return modals.menu.shop_overlay
"camp_screen": return screens.camp_screen
"camp_scene": return modals.camp.scene
"forge_view": return modals.camp.forge
@@ -69,13 +81,13 @@ func node(key: String) -> Node:
"tree_view": return modals.camp.tree
"codex_view": return modals.camp.codex
"camp_esc_overlay": return modals.camp.esc_overlay
"settings_overlay": return modals.settings_overlay
"status_panel": return modals.status_panel
"career_overlay": return modals.career_overlay
"save_select_overlay": return modals.save_select_overlay
"settings_overlay": return modals.menu.settings_overlay
"status_panel": return modals.run.status_panel
"career_overlay": return modals.menu.career_overlay
"save_select_overlay": return modals.menu.save_select_overlay
"inter_overlay": return screens.inter_overlay
"confirm_dialog": return modals.confirm_dialog
"debug_overlay": return modals.debug_overlay
"confirm_dialog": return modals.menu.confirm_dialog
"debug_overlay": return modals.menu.debug_overlay
"debug_badge": return screens.debug_badge
return null
@@ -94,7 +106,7 @@ func apply_layer_contract(root: Control) -> void:
root.move_child(shell, slot)
slot += 1
if member != null:
modals.register_layer(member as Control, tags)
register_layer(member as Control, tags)
else:
push_error("[合同装配] 层缺失: %s" % String(entry["key"]))
continue
@@ -103,9 +115,9 @@ func apply_layer_contract(root: Control) -> void:
push_error("[合同装配] 层缺失: %s" % String(entry["key"]))
continue
if entry.has("host"):
modals.register_layer(layer as Control, tags)
register_layer(layer as Control, tags)
continue
root.move_child(layer, slot)
slot += 1
if String(entry["kind"]) == "modal":
modals.register_layer(layer as Control, tags)
register_layer(layer as Control, tags)
+11
View File
@@ -0,0 +1,11 @@
## 局内冒险域六句柄唯一容器——冒险会话内开合的模态呈现面句柄册(纯字段 RefCounted)。
## 分域纪律册级化:任何模态句柄禁平铺回 ui_modals(营地先例条款推广)——寻址一律 ui.modals.run.X 二段式。
class_name UiRunModals
extends RefCounted
var legend_overlay: LegendOverlay # 战斗中图例(棋盘角标触发)
var node_overlay: NodeOverlay # 节点结算窗
var level_screen: LevelMapScreen # 关卡地图屏(screen_state 全屏态)
var curio_overlay: CurioPickOverlay # 奖励三选一
var end_overlay: BattleEndOverlay # 终局面板
var status_panel: StatusPanel # 状态面板(地图/棋盘两角标入口)
+1
View File
@@ -0,0 +1 @@
uid://ssh8gogewptf