卡①T2(组合根注册面批): main.gd改形49→22(表A 28成员迁出+ui新增,7别名留T4)+合同机器四样迁驻(ui_registry/ui_modals)+main_ui_build八步赋值目标改root.ui.*+三流程件root.X同表映射+_settlement_stop_pending写点改run_scope.settlement_stop_timer直呼+smoke_boot get()改址(4改2留)+V1d重锚全presentation目录扫描〔注册面V1〕+V6改断言新串〔注册面V2〕——实现决定: ModalFlow实例随UiModals容器构造即建(装配第二步pump_ended注入早于第八步flow建立,转发壳退役需flow先行在座;查询/钩子注入时点不变);冒烟+三套件绿+gdstyle零恶化(main实测22)

This commit is contained in:
DKChan committed 2026-10-30 04:08:07 +08:00
1 parent bee0682f4c
commit b89ee8505d
9 files changed
+343 -440

No files matched your search

+90 -211
View File
@@ -1,7 +1,8 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 组合根:唯一的模式流转与 core↔view 接线处(垂直切片验证:线性第一关承载元进度循环)。
## db.hero 运行期只读,加成经 runner.set_hero_override / debug_session 生效值入战斗。
## 禁链式穿透 settlement.*(相位读取只走 presentation_snapshot);模态开合只走 _open_modal/_close_modal。
## 禁链式穿透 settlement.*(相位读取只走 presentation_snapshot);模态开合只走 ui.modals.flow(注册面编排口)。
## 界面句柄与层清单在 ui 注册面(ui_registry.gd)——屏壳/战区/模态三域句柄与 LAYER_CONTRACT 单点宿主。
## 局态全在 run_scope.gd 节点,禁在屏/视图侧持有局态引用;autoload 节点路径常量单点在 AUTOLOAD_PATH_*。
extends Control
@@ -15,10 +16,8 @@ const AUTOLOAD_PATH_META := "/root/MetaProgression"
const UiAssembler := preload("res://presentation/ui_assembler.gd")
const Narrator := preload("res://presentation/battle_narrator.gd")
const ModalStack := preload("res://presentation/modal_stack.gd")
const TuneSimController := preload("res://presentation/tune_sim_controller.gd")
const DebugSessionScript := preload("res://presentation/debug_session.gd")
const RowPacer := preload("res://presentation/row_pacer.gd") # 结算行排播泵
const TreasureInteractController := preload("res://presentation/treasure_interact_controller.gd") # 秘宝两步交互
const CurioInteractController := preload("res://presentation/curio_interact_controller.gd") # 三选一与替换子交互
const CampShopPresenter := preload("res://presentation/camp_shop_presenter.gd") # 营地/商店投影
@@ -31,33 +30,6 @@ const MenuScreenScene := preload("res://presentation/scenes/menu_screen.tscn")
const CampScreenScene := preload("res://presentation/scenes/camp_screen.tscn")
const AdventureScreenScene := preload("res://presentation/scenes/adventure_screen.tscn")
# ==================== 视觉层合同(唯一事实源——树序=登记序=z 序;改序只改此表) ====================
# kind: shell=屏壳容器(成员随壳不占独立树位;level_screen 例外另占登记位)/modal=模态层(入登记册,
# tags 随登记)/state=全屏态(占树位不入登记册)/node=非视觉节点(占树位不入登记册)。
# host=宿住屏壳(树序随宿主、登记序按本合同);shell=模态成员的屏壳(成员与屏壳同槽:屏壳占该树位、
# 成员占该登记位)。落位=_apply_layer_contract(全仓唯一层序落位点,add_child 之后统一排序+登记)。
const LAYER_CONTRACT: Array[Dictionary] = [
{"key": "adventure_screen", "kind": "shell"},
{"key": "menu_screen", "kind": "shell"},
{"key": "row_pacer", "kind": "node"},
{"key": "legend_overlay", "kind": "modal", "tags": []},
{"key": "node_overlay", "kind": "modal", "tags": []},
{"key": "level_screen", "kind": "modal", "tags": [ModalStack.TAG_SCREEN_STATE], "host": "adventure_screen"},
{"key": "curio_overlay", "kind": "modal", "tags": []},
{"key": "end_overlay", "kind": "modal", "tags": []},
{"key": "shop_overlay", "kind": "modal", "tags": []},
{"key": "camp_overlay", "kind": "modal", "tags": [], "shell": "camp_screen"},
{"key": "settings_overlay", "kind": "modal", "tags": []},
{"key": "status_panel", "kind": "modal", "tags": []},
{"key": "career_overlay", "kind": "modal", "tags": []},
{"key": "save_select_overlay", "kind": "modal", "tags": []}, # 选档界面(营地交互重做卡③——登记生涯同档)
{"key": "inter_overlay", "kind": "state", "tags": []},
{"key": "confirm_dialog", "kind": "modal", "tags": []},
{"key": "debug_overlay", "kind": "modal", "tags": [ModalStack.TAG_NON_LOCKING]},
{"key": "debug_badge", "kind": "float"}, # 调试入口全局浮层——末位=最顶绘制序;
# float 类不注册 ModalStack(open() 遮挡记账只隐藏登记层)→ 任何 UI 打开都不遮它
]
var db
var run_scope = null # 会话状态承载(run_scope.gd;_ready 即建闲时态,永非 null)
# SaveManager/MetaProgression 升格 autoload——成员名保留为「autoload 读视图」
@@ -71,62 +43,30 @@ var meta: MetaProgressionCore:
return get_node(AUTOLOAD_PATH_META)
var logger = null # 试玩日志器(JSONL 持久化 + 卡死诊断转储——呈现层组件)
var _debug_entry := false # 调试会话门控(缺省 false=发布态:角标恒隐、浮窗无 UI 开启口;沿革查 git log)
var _seed_edit: LineEdit
var confirm_dialog: ConfirmDialog # 通用确认弹窗(消费方编排——现行消费方=选档删档确认;新冒险互斥确认已随卡②主菜单撤出,归卡④大门)
var ui: UiRegistry # 界面注册面(ui_registry.gd——界面句柄/层序合同/模态编排句柄的单点宿主)
var _confirm_consumer := "" # 确认弹窗消费方状态量(契约 3 编排——open 前设、分派后清)
var _confirm_consumer_slot := 0 # 消费方附随槽号(delete_slot 消费方专用)
var curio_overlay: CurioPickOverlay # 提升三选一覆盖层(level-clear.md v2 选择面承载;奖励节点共用)
var _start_overlay: Control
var _end_overlay: BattleEndOverlay
var _inter_overlay: Control
var _inter_label: Label
var level_screen: LevelMapScreen # 关卡画面句柄(指向 adventure_screen.level_screen)
var _battle_root: Control # 战斗屏宿主句柄(指向 adventure_screen.battle_root)
var _seed_edit: LineEdit # 探针兼容别名(本体=ui.modals.debug_overlay.seed_edit;T4 随改址退役)
# 调试模式编排(单场/连续两层——状态机与战斗生命周期归 debug_session.gd,本类只接线与出 UI)
var _debug = null
# 视图(views/)——battle.md 三区(节点链进度
# 由关卡画面本体选路覆盖层承载)
var stage: BattleStageView # B 区(含 log_view 明细带)——句柄指向 adventure_screen
var log_view: BattleLogView
var info_panel: HeroInfoPanelView # C 区(攻击/AC 数据框+两预留区)
var board_zone: BoardZoneView # D 区(含 ap_bar/board_view/toast/fx)
var board_view: BoardView # 探针兼容别名(= board_zone.board_view)
var ap_bar: ApBarView # 探针兼容别名(= board_zone.ap_bar)
var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道/光带——A–D 之上、模态之下)
# 三屏壳实例(屏成员经本类句柄暴露;探针挂载假设 main.X 一级路径)
var _menu_screen = null # MenuScreen(开始菜单屏壳)
var _camp_screen = null # CampScreen(营地屏壳)
var _adventure_screen = null # AdventureScreen(冒险屏壳——内含关卡地图/战斗两容器)
# 拆件后的组合根持有面(职责见各类头注;装配见 _build_ui)
var _row_pacer = null # 结算行排播泵(row_pacer.gd——本类只入队/排空/读队列)
var _treasure_ui = null # 秘宝两步交互(treasure_interact_controller.gd——本类只接线与转发意图)
var _bonus = null # 三选一与替换子交互(curio_interact_controller.gd——本类只接线与回调注入)
var _camp_pres = null # 营地/商店投影(camp_shop_presenter.gd——本类只接线、探针缝保缝转发)
# 探针兼容别名(本体已迁注册面——board_view=ui.battle.board_zone.board_view、ap_bar=ui.battle.board_zone.ap_bar;T4 随改址退役)
var board_view: BoardView
var ap_bar: ApBarView
# 排播队列只读委托面(探针/测试读 main._row_queue——与 RowPacer 队列共享同一 Array 引用)
var _row_queue: Array[Variant]:
get:
return _row_pacer.pending_rows()
return ui.screens.row_pacer.pending_rows()
# 模态 overlay(z 序 = 登记序;pattern 6 层级纪律)
var legend_overlay: LegendOverlay
var node_overlay: NodeOverlay
var shop_overlay: ShopOverlay # 商店覆盖层(商店房节点兑付呈现)
var camp_overlay: CampOverlay # 营地覆盖层句柄(指向 camp_screen.camp_overlay)
var settings_store # 系统级设置独立存储(音量/全屏/减动效)
var settings_overlay: SettingsOverlay # 设置覆盖层(最小设置面)
var status_panel: StatusPanel # 局内状态面板·只读(钥石余额+营地三柜台进度查看;登记设置层同档)
var career_overlay: CareerOverlay # 生涯页·只读全屏页(跨局汇总六类九项;登记营地同档〔设置层旁、确认弹窗之前〕)
var save_select_overlay: SaveSelectOverlay # 选档界面·3 存档位(新档/进档/删档——营地交互重做卡③;登记生涯同档)
var debug_overlay: DebugOverlay # 调试浮窗(种子/数值调整/批量模拟/两层调试入口收一)
var debug_badge: Button # 调试浮层入口角标(UI 最顶层浮层——LAYER_CONTRACT 末位 float 类,任何 UI 不遮挡)
var _modal_flow = null # 模态编排纯逻辑类(modal_flow.gd——依赖仅 ModalStack+查询 Callable)
var _modals = null # 模态层级状态机(=_modal_flow.stack——探针 _list 登记序断言缝保留)
var _modals = null # 模态层级状态机(=ui.modals.flow.stack——探针 _list 登记序断言缝;T4 退役)
var _tune_sim = null # 调试浮窗批量模拟控制器(tune_sim_controller.gd)
# 结算框挂起态属性化(探针/冒烟直读写接缝语义零变化——读=armed,写=arm/disarm;计时器随会话住
@@ -145,25 +85,25 @@ var _settlement_stop_pending: bool:
# 锁期点击重放(v3.1):延迟一帧重入唯一动作通道——此刻队列空锁已解,按普通动作受理
#(局面在演播期无变化:逻辑先行、演播只是回放——点击语义保真;_row_queue=RowPacer 委托面,
# 排播泵本体在 row_pacer.gd)。
var _deferred_modal: Control: # 演播后开窗挂起只读视图(t17_defer_probe 挂起态断言缝;本体归 _modal_flow)
var _deferred_modal: Control: # 演播后开窗挂起只读视图(t17_defer_probe 挂起态断言缝;本体归 ui.modals.flow)
get:
return _modal_flow._deferred if _modal_flow != null else null
return ui.modals.flow._deferred if ui != null and ui.modals.flow != null else null
# 演播后排窗(伤害过程演完才切结算窗口):排播队列在播=挂起,演播完边沿
# (_on_pump_ended 派发)再开——旧路径 _open_modal 前置 flush 会把击杀轮的伤害/死亡演出
# 同帧倒掉(「直接跳战斗胜利」缺陷本体)。队列空=立即开(行为同旧路径)。
func _open_modal_after_playback(m: Control) -> void:
_modal_flow.open_after_playback(m) # 演播后排窗挂起编排归 ModalFlow
ui.modals.flow.open_after_playback(m) # 演播后排窗挂起编排归 ModalFlow
# 演播完边沿派发(row_pacer.pump_ended 注入口——先开挂起 modal,后锁期点击重放):
func _on_pump_ended() -> void:
_modal_flow.pump_ended() # 派发编排归 ModalFlow(重放经注入 Callable 回本类)
ui.modals.flow.pump_ended() # 派发编排归 ModalFlow(重放经注入 Callable 回本类)
func _replay_locked_click() -> void:
if run_scope.locked_click.is_empty() or not _row_queue.is_empty():
if run_scope.locked_click.is_empty() or not ui.screens.row_pacer.pending_rows().is_empty():
return
var click: Dictionary[Variant, Variant] = run_scope.locked_click
run_scope.locked_click = {}
@@ -173,7 +113,7 @@ func _replay_locked_click() -> void:
# 会话生命周期:_ready 建闲时态(永非 null——全组合根零空判责),
# 开局/续玩/调试两层各建新会话,旧会话整节点 queue_free()(帧末回收;新节点先挂树无真空窗)。
func _begin_session(adventure_session: bool) -> void:
_row_pacer.flush() # 会话更替即清队(待播行不住局态——机制保证,不靠时序约定)
ui.screens.row_pacer.flush() # 会话更替即清队(待播行不住局态——机制保证,不靠时序约定)
if run_scope != null:
run_scope.queue_free()
run_scope = RunScope.new()
@@ -194,10 +134,11 @@ func _ready() -> void:
settings_store = SettingsStore.new()
settings_store.load_settings()
settings_store.apply()
ui = UiRegistry.new() # 注册面先于装配建(_build_ui 八步向容器登记——装配时序零变化)
_build_ui()
_begin_session(false) # 闲时会话(gp_ledger 等探针注入面自 _ready 起恒在——ui_probe 合成段时序)
_start_overlay.visible = true
debug_badge.visible = _debug_entry # 浮层角标可见性须门控落定后赋值(构建/连接在 _build_ui);_debug_entry 缺省 false=发布态恒隐
ui.screens.start_overlay.visible = true
ui.screens.debug_badge.visible = _debug_entry # 浮层角标可见性须门控落定后赋值(构建/连接在 _build_ui);_debug_entry 缺省 false=发布态恒隐
# boot 菜单优先——启动不自动开浮窗(开始屏不得被调试面板盖住);
# 调试入口=UI 最顶层全局浮层角标(LAYER_CONTRACT 末位 float 类,任何 UI 不遮挡)
@@ -206,7 +147,7 @@ func _ready() -> void:
func _build_ui() -> void:
MainUiBuild.build(self)
debug_overlay.diagnose_requested.connect(_on_diagnose_pressed) # 诊断转储入口
ui.modals.debug_overlay.diagnose_requested.connect(_on_diagnose_pressed) # 诊断转储入口
func _fatal_screen(msg: String) -> void:
@@ -215,84 +156,22 @@ func _fatal_screen(msg: String) -> void:
# ==================== 模态 overlay 层级管理(pattern 6 / 缺陷⑤——状态机归 modal_stack.gd) ====================
func _register_modal(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)
_modal_flow.register(m, tags) # 排除组随层登记(non_locking=调试浮窗/screen_state=关卡地图屏)
# 合同层寻址(key→节点;纯查表不含层序语义——层序唯一事实源=LAYER_CONTRACT)
func _layer_node(key: String) -> Node:
match key:
"adventure_screen": return _adventure_screen
"menu_screen": return _menu_screen
"row_pacer": return _row_pacer
"legend_overlay": return legend_overlay
"node_overlay": return node_overlay
"level_screen": return level_screen
"curio_overlay": return curio_overlay
"end_overlay": return _end_overlay
"shop_overlay": return shop_overlay
"camp_screen": return _camp_screen
"camp_overlay": return camp_overlay
"settings_overlay": return settings_overlay
"status_panel": return status_panel
"career_overlay": return career_overlay
"save_select_overlay": return save_select_overlay
"inter_overlay": return _inter_overlay
"confirm_dialog": return confirm_dialog
"debug_overlay": return debug_overlay
"debug_badge": return debug_badge
return null
# 合同装配(_build_ui 尾调用):按 LAYER_CONTRACT 序统一落位——直接子层 move_child 到合同槽、
# 模态层按合同序登记;宿住层(host)树序随宿主只登记;成员层(shell)与屏壳同槽(屏壳占树位、成员占登记位)。
# 本函数是全仓唯一层序落位点(move_child 单口)——改层序只改 LAYER_CONTRACT。
func _apply_layer_contract() -> void:
var slot := 0
for entry: Dictionary in LAYER_CONTRACT:
var tags: Array[Variant] = entry.get("tags", [])
if entry.has("shell"):
var shell: Node = _layer_node(String(entry["shell"]))
var member: Node = _layer_node(String(entry["key"]))
if shell != null:
move_child(shell, slot)
slot += 1
if member != null:
_register_modal(member as Control, tags)
else:
push_error("[合同装配] 层缺失: %s" % String(entry["key"]))
continue
var node: Node = _layer_node(String(entry["key"]))
if node == null:
push_error("[合同装配] 层缺失: %s" % String(entry["key"]))
continue
if entry.has("host"):
_register_modal(node as Control, tags)
continue
move_child(node, slot)
slot += 1
if String(entry["kind"]) == "modal":
_register_modal(node as Control, tags)
func _open_modal(m: Control) -> void:
_modal_flow.open(m) # 挂起兜底/排播排空/遮挡记账/开侧同步编排归 ModalFlow
ui.modals.flow.open(m) # 挂起兜底/排播排空/遮挡记账/开侧同步编排归 ModalFlow
func _close_modal(m: Control) -> void:
_modal_flow.close(m) # 关闭恢复+关侧同步归 ModalFlow(同上)
ui.modals.flow.close(m) # 关闭恢复+关侧同步归 ModalFlow(同上)
func _on_modal_closed(m: Control) -> void:
_close_modal(m)
ui.modals.flow.close(m)
func _input_locked() -> bool:
# 模态/非战斗期棋盘输入锁(判据编排归 ModalFlow.locks_input:互斥域随层登记
# [non_locking=调试浮窗]+ 三例外经查询 Callable 注入;本函数留探针缝)
return _modal_flow.locks_input()
return ui.modals.flow.locks_input()
# ==================== 冒险模式入口(唯一试玩入口)====================
@@ -301,7 +180,7 @@ func _start_adventure_from_menu() -> void:
# 弹窗本体与两分支逻辑归卡④大门承接)。本函数为探针直调中间态保留(mn10——冒险段探针
# 直调位点逐个登记实现报告「已知中间态」;卡④到位后由大门两分支换代)。无激活态写盘拒绝
# 由卡① sv3 承接(结算不落盘)。
_start_overlay.visible = false
ui.screens.start_overlay.visible = false
_start_adventure()
@@ -309,16 +188,16 @@ func _start_adventure_from_menu() -> void:
#(确认才删、取消只关窗——裁决 3)。"new_adventure" 消费方已随卡② 主菜单确认链摘除退役
#(mn6/V5——主菜单零确认残留),分派机制保留(卡④ 到位接入第三消费方不返工)。
func _on_confirm_accepted() -> void:
_close_modal(confirm_dialog) # 模态栈单口
ui.modals.flow.close(ui.modals.confirm_dialog) # 模态栈单口
match _confirm_consumer:
"delete_slot":
save_manager.slots.delete_slot(_confirm_consumer_slot)
save_select_overlay.open_save_select(HudPresenter.save_select_data(save_manager)) # 就地刷新(该槽回空态)
ui.modals.save_select_overlay.open_save_select(HudPresenter.save_select_data(save_manager)) # 就地刷新(该槽回空态)
_confirm_consumer = ""
func _on_confirm_declined() -> void:
_close_modal(confirm_dialog) # 只关窗(取消无状态变更——裁决 3:取消不删);关侧恢复归 ModalFlow
ui.modals.flow.close(ui.modals.confirm_dialog) # 只关窗(取消无状态变更——裁决 3:取消不删);关侧恢复归 ModalFlow
_confirm_consumer = ""
@@ -334,16 +213,16 @@ func _resume_from_menu() -> void:
if not save_manager.has_breakpoint():
return
var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot()
_close_modal(debug_overlay) # 开冒险路径显式关浮窗(续玩同口径)
_start_overlay.visible = false
ui.modals.flow.close(ui.modals.debug_overlay) # 开冒险路径显式关浮窗(续玩同口径)
ui.screens.start_overlay.visible = false
_begin_session(true)
_wire_adventure()
run_scope.level_active = true
_sync_status_entry() # 续玩恢复点亮状态面板入口角标
level_screen.set_abandon_available(true)
log_view.clear()
ui.modals.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(debug_overlay.atk_pct(), debug_overlay.hp_pct())
run_scope.adventure.set_hero_override(ui.modals.debug_overlay.atk_pct(), ui.modals.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()})):
@@ -379,16 +258,16 @@ static func build_adventure_params(save_manager_ref: SaveManagerCore, meta_ref:
# 冒险模式(三关连探——AdventureRunner 编排;转发信号签名与 runner 一致,接线照原样对接;
# 信号连接只在组合根)
func _start_adventure() -> void:
_close_modal(debug_overlay) # 开冒险路径显式关浮窗(调试浮窗默认开态下开局不带窗;对局中经角标再开)
ui.modals.flow.close(ui.modals.debug_overlay) # 开冒险路径显式关浮窗(调试浮窗默认开态下开局不带窗;对局中经角标再开)
_begin_session(true)
_wire_adventure()
run_scope.level_active = true
_sync_status_entry() # 进对局点亮状态面板入口角标
level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期)
log_view.clear()
ui.modals.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(debug_overlay.atk_pct(), debug_overlay.hp_pct())
run_scope.adventure.set_hero_override(ui.modals.debug_overlay.atk_pct(), ui.modals.debug_overlay.hp_pct())
# 永久强化注入:冒险开始参数面——meta 现算 override 显式传参(全零 = 白板)
# 开局参数面(同调三键同源构造——确定性承载=固定种子+参数档,首局教学图由种子域三分判定给 42)
if not run_scope.adventure.start_adventure(seed_val, build_adventure_params(save_manager, meta,
@@ -435,33 +314,33 @@ func _on_inter_next_pressed() -> void:
func _show_board_fx_for_node(node_type: String = "") -> void:
# Boss 遭遇变体:紫赤影子爬棋盘一次性占位演出(§2.6)
if node_type == "boss":
board_zone.fx.play_boss_shadow()
ui.battle.board_zone.fx.play_boss_shadow()
func _on_battle_finished(payload: Dictionary) -> void:
# 仅调试模式接线(冒险的关卡结束归 AdventureRunner._on_level_finished——大奖励/冒险终局分派)
var result: String = String(payload.get("result", ""))
_sync_settlement_visuals() # → 全关(battle_over 反馈面)
HudPresenter.battle_end_feedback(stage, ap_bar, true) # 灰化/idle 回正/RAP 清零/状态全清——HUD 单通道
HudPresenter.battle_end_feedback(ui.battle.stage, ui.battle.board_zone.ap_bar, true) # 灰化/idle 回正/RAP 清零/状态全清——HUD 单通道
var decision: Dictionary[Variant, Variant] = _debug.on_battle_finished(payload)
match String(decision.outcome):
"next_layer":
_inter_label.text = "第 %d 层通过!\nHP %d/%d 带入下一层" % [
ui.screens.inter_label.text = "第 %d 层通过!\nHP %d/%d 带入下一层" % [
int(decision.layer), int(decision.carry_hp), int(db.combat.hero.max_hp)]
_inter_overlay.visible = true
ui.screens.inter_overlay.visible = true
_refresh_presentations()
return
"level_end":
_end_overlay.open_victory(Narrator.battle_end_text("victory", true),
"返回", board_view.get_global_rect(), _end_victory_opts(payload))
ui.modals.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":
_end_overlay.open_victory(Narrator.battle_end_text("victory", false),
"回到遭遇选择", board_view.get_global_rect(), _end_victory_opts(payload))
ui.modals.end_overlay.open_victory(Narrator.battle_end_text("victory", false),
"回到遭遇选择", ui.battle.board_zone.board_view.get_global_rect(), _end_victory_opts(payload))
else:
_end_overlay.open_defeat(Narrator.battle_end_text("defeat", false),
"返回", board_view.get_global_rect())
_open_modal_after_playback(_end_overlay)
ui.modals.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)
_refresh_presentations()
@@ -499,22 +378,22 @@ func _on_step_row(row: Dictionary) -> void:
# 清图奖励可感知反馈(挖完全图须有奖励感——clear_blast 兑付对敌伤害
# 零可见,toast 桥接归组合根呈现层;零兑付(未生效)不弹安静留明细带)
if String(row.get("source_class", "")) == "clear_reward" and String(row.get("type", "")) != "reward_ineffective":
board_zone.toast.show_toast("清图奖励:%s" % String(row.get("display", "")).replace("(清图奖励)", ""), "info")
ui.battle.board_zone.toast.show_toast("清图奖励:%s" % String(row.get("display", "")).replace("(清图奖励)", ""), "info")
# 排播入口:减动效=延迟归零同帧直落(行呈现不跳过);常态入队按段阶梯播放
if UiKit.motion_scale < 1.0:
_row_pacer.consume_now(row)
ui.screens.row_pacer.consume_now(row)
return
_row_pacer.enqueue(row)
ui.screens.row_pacer.enqueue(row)
# 段间延迟取数面(减动效双态断言用——pacing_queue_test 直读;委托 row_pacer 同名面)
func pace_delay_for(prev_seg: String, next_seg: String) -> float:
return _row_pacer.pace_delay_for(prev_seg, next_seg)
return ui.screens.row_pacer.pace_delay_for(prev_seg, next_seg)
# 强制排空(模态开启前调用——明细回看/停点覆盖层/终局面所见明细带恒完整;pacing_queue_test 直读)
func _flush_row_queue() -> void:
_row_pacer.flush()
ui.screens.row_pacer.flush()
func _on_round_started(ri: int) -> void:
@@ -522,10 +401,10 @@ func _on_round_started(ri: int) -> void:
#(view 内判减动效=静态化变体)。减动效快路=分隔行同帧直落(不跳过——consume_now 复用
# _consume_row 轮首分隔行分支)。
if UiKit.motion_scale < 1.0:
_row_pacer.consume_now({"__round_line": ri})
ui.screens.row_pacer.consume_now({"__round_line": ri})
else:
_row_pacer.enqueue({"__round_line": ri})
settlement_fx.play_round_banner(ri)
ui.screens.row_pacer.enqueue({"__round_line": ri})
ui.battle.settlement_fx.play_round_banner(ri)
# 秘宝呈现同步:每战首轮装配链
# 显式重算〔🔴 契约复原语义:下一战装配链重算即含「未装配→写 0」复原〕。
# 挂点时序依据:round_started 首轮发射位在 begin_battle 尾(attunement 注入之后)。
@@ -534,7 +413,7 @@ func _on_round_started(ri: int) -> void:
_sync_treasure_glow()
# 巢穴预告呈现(装板报数量读数口消费:≥0=本战装配命中,toast 一次性提示)
if run_scope.settlement != null and int(run_scope.settlement.nest_preview_count) >= 0:
board_zone.toast.show_toast("巢穴预告——本板危险格共 %d 个" % int(run_scope.settlement.nest_preview_count), "info")
ui.battle.board_zone.toast.show_toast("巢穴预告——本板危险格共 %d 个" % int(run_scope.settlement.nest_preview_count), "info")
# 开战横幅——整图地形变异预告:首轮快照 board_variants
# 非空才发射(Boss 图恒带一型=替换制;普通图空快照零发射);轮横幅下方叠行,view 内判减动效。
if ri == 1 and run_scope.settlement != null:
@@ -542,7 +421,7 @@ func _on_round_started(ri: int) -> void:
if not bvs.is_empty():
var vlines: Array[String] = HudPresenter.board_variant_lines(db, bvs)
if not vlines.is_empty():
settlement_fx.play_board_variant_banner("地形变异 · " + " · ".join(vlines))
ui.battle.settlement_fx.play_board_variant_banner("地形变异 · " + " · ".join(vlines))
_refresh_presentations()
@@ -552,19 +431,19 @@ func _on_board_changed() -> void:
run_scope.settlement = run_scope.runner.current_battle()
if run_scope.settlement == null:
return
board_view.rebuild(run_scope.settlement.minesweeper.current_board(), run_scope.settlement.minesweeper)
ui.battle.board_zone.board_view.rebuild(run_scope.settlement.minesweeper.current_board(), run_scope.settlement.minesweeper)
_check_board_hints()
_refresh_presentations()
# 秘宝视野呈现桥:阔视野带 light_radius_bonus
# 装配参数合计显式写给 board_view.set_fog_glow_bonus(装配=半径 8+4 / 未装配=复原 0),写入即 setter
# 装配参数合计显式写给 ui.battle.board_zone.board_view.set_fog_glow_bonus(装配=半径 8+4 / 未装配=复原 0),写入即 setter
# 内 refresh 全板重算雾态。调用点=每战首轮装配链(_on_round_started ri==1)——纯呈现边界 🔴 契约 2:
# 只写呈现态参数,minesweeper 结算零触碰;off 态(未装配)写 0=缺省基线。
func _sync_treasure_glow() -> void:
if run_scope.settlement == null or board_view == null:
if run_scope.settlement == null or ui.battle.board_zone.board_view == null:
return
board_view.set_fog_glow_bonus(run_scope.settlement.attune_param_sum("light_radius_bonus"))
ui.battle.board_zone.board_view.set_fog_glow_bonus(run_scope.settlement.attune_param_sum("light_radius_bonus"))
# ---- 一次性非模态提示(每 run 一次——pattern 10)与诚实提示 ----
@@ -575,10 +454,10 @@ func _check_board_hints() -> void:
return
# 零合法板(棋盘生成 EC5 降级)诚实提示——非静默(toast,非模态)
if board.illegal_product:
board_zone.toast.show_toast("本板为零合法板降级产物——出图器已尽力,数据面诚实告知", "warn")
ui.battle.board_zone.toast.show_toast("本板为零合法板降级产物——出图器已尽力,数据面诚实告知", "warn")
# 接受最优降级(有合法板但无一达标)——降级标记独立于非法产物标记,同走诚实告知
elif board.degraded_product:
board_zone.toast.show_toast("本板为接受最优降级产物(未达可推理率及格线)——数据面诚实告知", "warn")
ui.battle.board_zone.toast.show_toast("本板为接受最优降级产物(未达可推理率及格线)——数据面诚实告知", "warn")
func _on_hover_forbidden(_cell: Vector2i) -> void:
@@ -594,7 +473,7 @@ func _on_chord_attempted(_cell: Vector2i) -> void:
func _show_hint(text: String, hint_id: String) -> void:
run_scope.hints_shown[hint_id] = true
logger.log_ux("ux_hint_shown", {"hint_id": hint_id})
board_zone.toast.show_toast(text, "info")
ui.battle.board_zone.toast.show_toast(text, "info")
# ---- 节点覆盖层"继续"(奖励/恢复点兑付确认)与节点图选路覆盖层 ----
@@ -706,7 +585,7 @@ func _on_wallet_changed(event: String, gp: int, extra: Dictionary)-> void:
func _on_board_action(cell: Vector2i, kind: String) -> void:
# 玩家动作 → 核心写调用的唯一通道(模态/无战斗拒绝在此收口)
if _input_locked():
if ui.modals.flow.locks_input():
return
# 结算演播期棋盘冻结(v3 口径——不可点/不可移/不可挖):
# 锁面=排播队列活跃(纯队列判据;排播泵尾迹窗口不可作演播中标志——见 row_pacer._process 注)。
@@ -714,7 +593,7 @@ func _on_board_action(cell: Vector2i, kind: String) -> void:
# _flush_row_queue 自行排空)。
# **点击不丢弃**(v3.1——「卡死」体感根因):锁期点击缓存最后一次,演播完边沿
# (_process 泵 _was_pumping 分支)自动重放——玩家操作零丢失、体感=「演播一停动作就生效」。
if not _row_queue.is_empty():
if not ui.screens.row_pacer.pending_rows().is_empty():
run_scope.locked_click = {"cell": cell, "kind": kind}
return
# 秘宝选中态劫持(两步交互第二步):点格=使用目标拾取,不落挖格;
@@ -734,22 +613,22 @@ func _on_board_action(cell: Vector2i, kind: String) -> void:
# settle_batch 内部同步走完 结算→战斗结束→关卡推进(事件同步发射)——关卡终局时 settlement 已被
# runner 置空,清图演出判定须先判 settlement(重入隐患守卫)
if accepted and kind == "dig" and run_scope.settlement != null and run_scope.settlement.minesweeper.current_board() != null and run_scope.settlement.minesweeper.clear_map_cleared():
board_zone.fx.play_clear_burst()
ui.battle.board_zone.fx.play_clear_burst()
if accepted and kind == "dig" and run_scope.settlement != null and run_scope.settlement.minesweeper != null:
# 挖掘演出:挥铲三帧 + 角色弹跳——触发=格子点开那一刻
#(settle_batch 同步受理即状态已变,动画纯表现与 refresh 同拍零竞态;
# 挖掘不移动英雄,出手方向 = 目标格−英雄格,圆心=英雄持握手位)
var hero_cell: Vector2i = run_scope.settlement.minesweeper.hero_pos()
board_zone.fx.play_shovel_swing(board_view.hero_hand_point(), cell - hero_cell, board_view.hero_sprite_scale())
board_view.play_hero_dig_bounce()
ui.battle.board_zone.fx.play_shovel_swing(ui.battle.board_zone.board_view.hero_hand_point(), cell - hero_cell, ui.battle.board_zone.board_view.hero_sprite_scale())
ui.battle.board_zone.board_view.play_hero_dig_bounce()
elif accepted and kind == "move":
board_view.notify_hero_move() # 原地移动(目标=当前格幂等受理)帧闪回执——refresh diff 前置告知
board_view.refresh()
ui.battle.board_zone.board_view.notify_hero_move() # 原地移动(目标=当前格幂等受理)帧闪回执——refresh diff 前置告知
ui.battle.board_zone.board_view.refresh()
_refresh_presentations()
func _on_stop_pressed() -> void:
if _input_locked():
if ui.modals.flow.locks_input():
return
# stop 经批次提交(排雷玩法 D1×6①——不直调 行动经济)
if run_scope.settlement == null:
@@ -758,15 +637,15 @@ func _on_stop_pressed() -> void:
logger.log_action("stop", Vector2i(-1, -1), not outcomes.is_empty() and bool(outcomes[0].accepted))
if not outcomes.is_empty() and String(outcomes[0].get("outcome", "")) == "voided":
# voided ≠ 拒绝——独立文案(pattern 1:防静默改判读感欺骗)
board_zone.toast.show_toast("本轮已清图,停手未执行 · 剩余 AP 已作废", "warn")
ui.battle.board_zone.toast.show_toast("本轮已清图,停手未执行 · 剩余 AP 已作废", "warn")
logger.log_ux("ux_reject", {"source": "voided", "sub_reason": "stop_voided"})
board_view.refresh()
ui.battle.board_zone.board_view.refresh()
_refresh_presentations()
func _on_refresh_confirmed() -> void:
# 弃图刷新(挖掘期 Active 当场换图继续挖——D4;RAP 按门槛扣费 · 旧板封印作废,每轮上限读表)
if _input_locked():
if ui.modals.flow.locks_input():
return
var ok: bool = false
if run_scope.level_active and run_scope.runner != null:
@@ -776,7 +655,7 @@ func _on_refresh_confirmed() -> void:
logger.log_action("refresh", Vector2i(-1, -1), ok)
if ok:
logger.log_ux("ux_refresh_attempt", {"result": "accepted", "round_refresh_index": _refresh_used_snapshot()})
board_zone.toast.show_toast("RAP−%d · 旧板封印作废——新图已就位,AP 保留继续挖" % int(db.run.action_economy.rap_refresh_cost), "gold")
ui.battle.board_zone.toast.show_toast("RAP−%d · 旧板封印作废——新图已就位,AP 保留继续挖" % int(db.run.action_economy.rap_refresh_cost), "gold")
else:
# 资源面/上限拒绝(EC13 合法拒绝可见;行动经济 无载荷——客户端按上下文推断)
var rap: int = int(run_scope.settlement.presentation_snapshot().get("rap", 0)) if run_scope.settlement != null else 0
@@ -784,11 +663,11 @@ func _on_refresh_confirmed() -> void:
var result := "rejected_rap" if rap < cost else "rejected_cap"
logger.log_ux("ux_refresh_attempt", {"result": result, "round_refresh_index": _refresh_used_snapshot() + 1})
if result == "rejected_rap":
board_zone.toast.show_toast("RAP 不足 · 还差 %d 点(弃图刷新需 8)" % (cost - rap), "warn")
ap_bar.pulse_rap()
ui.battle.board_zone.toast.show_toast("RAP 不足 · 还差 %d 点(弃图刷新需 8)" % (cost - rap), "warn")
ui.battle.board_zone.ap_bar.pulse_rap()
else:
board_zone.toast.show_toast("本轮刷新次数已用完(每轮 2 次)", "warn")
board_view.refresh()
ui.battle.board_zone.toast.show_toast("本轮刷新次数已用完(每轮 2 次)", "warn")
ui.battle.board_zone.board_view.refresh()
_refresh_presentations()
@@ -798,9 +677,9 @@ func _handle_outcome_feedback(outcome: Dictionary) -> void:
return
logger.log_ux("ux_reject", {"source": String(outcome.get("reject_reason", "")),
"sub_reason": String(outcome.get("sub_reason", ""))})
board_zone.toast.show_toast(HudPresenter.outcome_text(outcome), "warn")
ui.battle.board_zone.toast.show_toast(HudPresenter.outcome_text(outcome), "warn")
if String(outcome.get("reject_reason", "")) == "resource_face":
ap_bar.pulse_ap()
ui.battle.board_zone.ap_bar.pulse_ap()
# ==================== 全屏 States 状态机(挖掘/结算/窗口/终局)====================
@@ -816,7 +695,7 @@ func _refresh_used_snapshot() -> int:
func _refresh_presentations() -> void:
# 每个核心信号/写调用返回后的全量重查装配(更新触发注记——无推送信号,命令式重查)
var refresh_remaining: int = maxi(0, int(db.run.action_economy.refresh_cap_per_round) - _refresh_used_snapshot())
HudPresenter.refresh(stage, info_panel, ap_bar, {"settlement": run_scope.settlement, "runner": run_scope.runner, "level_active": run_scope.level_active, "db": db, "refresh_remaining": refresh_remaining})
HudPresenter.refresh(ui.battle.stage, ui.battle.info_panel, ui.battle.board_zone.ap_bar, {"settlement": run_scope.settlement, "runner": run_scope.runner, "level_active": run_scope.level_active, "db": db, "refresh_remaining": refresh_remaining})
_treasure_ui.refresh_slots() # C 区秘宝槽重查(试炼镐积点进度随挖格刷新悬停浮窗)
_sync_settlement_visuals()
@@ -825,9 +704,9 @@ func _sync_settlement_visuals() -> void:
# 结算阶段(round_end → 下一轮):D 区去饱和+金橙偏移锁输入(明度不变)+ 明细带展开 84px
var settling := _is_settling()
if settling and not run_scope.was_settling:
settlement_fx.play_round_sweep(board_view.get_global_rect()) # 结算轮光带——时序敏感演出触发,例外留在组合根
ui.battle.settlement_fx.play_round_sweep(ui.battle.board_zone.board_view.get_global_rect()) # 结算轮光带——时序敏感演出触发,例外留在组合根
run_scope.was_settling = settling
HudPresenter.sync_settlement_visuals(stage, board_view, log_view, settling,
HudPresenter.sync_settlement_visuals(ui.battle.stage, ui.battle.board_zone.board_view, ui.battle.log_view, settling,
HudPresenter.phase_text(run_scope.settlement, run_scope.runner, run_scope.boss_active, db))
@@ -839,10 +718,10 @@ func _is_settling() -> bool:
# overlay 开启即 _flush_row_queue 排空,视觉随队列空同步交接结算框。
# 相位取数经 presentation_snapshot 成文面——禁 settlement.action_economy 链式穿透)。
if run_scope.settlement == null or not run_scope.settlement.is_battle_live():
return not _row_queue.is_empty()
return not ui.screens.row_pacer.pending_rows().is_empty()
if String(run_scope.settlement.presentation_snapshot().get("phase", "")) == ActionEconomy.PHASE_WAITING:
return true
return not _row_queue.is_empty()
return not ui.screens.row_pacer.pending_rows().is_empty()
# 相位行 —— 承接体见 HudPresenter.phase_text(caller 直接改引)
@@ -908,8 +787,8 @@ func _seed_value() -> int:
# 重放模板 0 即在此种子下标定〕;置位 true=教学局已收束的非首局走 ③ 随机);
# ③非首局空/非法框=引擎全局熵 randi()(Godot 4 启动自播种子,headless 同;不走 rng_service
# 封闭流、禁 randomize()/自建 RNG——组合根建种子属引擎熵豁免面,不进玩法 RNG 消费)
if _seed_edit.text.is_valid_int():
return int(_seed_edit.text)
if ui.modals.debug_overlay.seed_edit.text.is_valid_int():
return int(ui.modals.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
+45 -45
View File
@@ -30,10 +30,10 @@ 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.debug_overlay.atk_pct() > 0 or root.debug_overlay.hp_pct() > 0 \
or root.ui.modals.debug_overlay.atk_pct() > 0 or root.ui.modals.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.log_view.append_bb(root.Narrator.level_open_line(display_name, int(root.run_scope.adventure.master_seed),
root.ui.battle.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=重连恰好一次契约承载)
@@ -62,30 +62,30 @@ static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -
root.logger.attach_battle(root.run_scope.settlement)
root.run_scope.boss_active = t == "boss"
root._show_board_fx_for_node(t)
root.log_view.append_bb(root.Narrator.node_banner(root.run_scope.runner, node, t))
root.ui.battle.log_view.append_bb(root.Narrator.node_banner(root.run_scope.runner, node, t))
elif t == "reward":
# 奖励节点三选一(与关卡完成三选一共享同池同权重同上限;CurioPickOverlay 共用面)。
# 呈现编排归 curio_interact_controller.present_node_curio。
root._bonus.present_node_curio()
root.log_view.append_bb(root.Narrator.reward_line())
root.ui.battle.log_view.append_bb(root.Narrator.reward_line())
elif t == "rest":
var healed: int = int(info.get("healed", 0))
root._open_modal(root.node_overlay)
root.node_overlay.show_rest(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0)))
root.log_view.append_bb(root.Narrator.rest_line(healed, int(info.get("hp", 0)), int(info.get("max_hp", 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.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._open_modal(root.shop_overlay)
root.ui.modals.flow.open(root.ui.modals.shop_overlay)
if root.run_scope.runner.shop.is_shop_pool():
root.shop_overlay.open_shelf(root.run_scope.runner.shop.shop_shelf(), int(root.run_scope.runner.gp))
root.shop_overlay.set_visit_info(int(root.run_scope.runner.shop.shop_bp_visit_buys()), float(root.db.run.shop.price_a)) # 爬价规则行
root.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.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
else:
root.shop_overlay.open_shop(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
root.shop_overlay.set_visit_info(-1, 1.0) # legacy 面无爬价行(off 对照承载)
root.log_view.append_bb(root.Narrator.shop_line(int(root.run_scope.runner.gp)))
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.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()
@@ -101,7 +101,7 @@ static func _on_adventure_finished(root: Variant, summary: Dictionary) -> void:
_exit_level_screen(root) # 终局兜底:独立屏在开态(放弃入口)也先回战斗屏呈现摘要——屏级互斥单一出口
root._sync_status_entry() # 离局熄灭状态面板入口角标(防菜单误现)
root._sync_settlement_visuals() # → 全关
HudPresenter.battle_end_feedback(root.stage, root.ap_bar, false) # 灰化/idle 回正/状态全清——HUD 单通道
HudPresenter.battle_end_feedback(root.ui.battle.stage, root.ui.battle.board_zone.ap_bar, false) # 灰化/idle 回正/状态全清——HUD 单通道
# 存档入账:settle = Boss 两档入账 + 工坊解锁 + 摘要入档 + 写盘(唯一写档点不变——
# 调试两层不经过本处理器,天然不写档不污染生涯数据)。
var detail: Dictionary[String, Variant] = {}
@@ -110,30 +110,30 @@ 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._end_overlay.open_victory(text, "返回冒险", root.board_view.get_global_rect(),
root.ui.modals.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._end_overlay.open_defeat(text, "返回", root.board_view.get_global_rect())
root.ui.modals.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._open_modal_after_playback(root._end_overlay)
root.ui.modals.flow.open_after_playback(root.ui.modals.end_overlay)
root._refresh_presentations()
static func _on_battle_end_continue(root: Variant) -> void:
# 终局出口:返回开始界面(冒险摘要留档试玩日志;钥石在收束时入存档)
root._close_modal(root._end_overlay)
root._end_overlay.close()
root.ui.modals.flow.close(root.ui.modals.end_overlay)
root.ui.modals.end_overlay.close()
# 三选一停点挂起分支:战斗总结结算(Boss 胜利钥石增量行)确认后进三选一面
if root._bonus.resume_if_pending(): # 三选一停点在途恢复入口(curio_interact_controller)
return
# 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现)
if root._settlement_stop_pending:
root._settlement_stop_pending = false
if root.run_scope.settlement_stop_timer.is_armed():
root.run_scope.settlement_stop_timer.disarm()
_enter_level_screen(root)
root.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held())))
root.level_screen.open_choices(root.run_scope.pending_candidates, root.run_scope.runner)
root.level_screen.apply_status(level_map_status_payload(root.run_scope.runner))
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.run_scope.pending_candidates = []
root._refresh_presentations()
return
@@ -141,12 +141,12 @@ static func _on_battle_end_continue(root: Variant) -> void:
# 局态封印/账本/模态挂起全复位(局态字段清单见 run_scope.gd)
root._debug.reset()
root._begin_session(false)
root.debug_overlay.reset_sliders()
root._start_overlay.visible = true
root.ui.modals.debug_overlay.reset_sliders()
root.ui.screens.start_overlay.visible = true
root._refresh_presentations()
static func _on_inter_next_pressed(root: Variant) -> void:
root._inter_overlay.visible = false
root.ui.screens.inter_overlay.visible = false
if root.run_scope.is_adventure:
# 冒险模式:大奖励屏确认 → 进下一关(AdventureRunner 序列推进——零自动推进的冒险级停点)
if root.run_scope.adventure != null and not root.run_scope.adventure.continue_to_next_level():
@@ -158,7 +158,7 @@ static func _on_inter_next_pressed(root: Variant) -> void:
static func _on_node_continue(root: Variant) -> void:
root._close_modal(root.node_overlay)
root.ui.modals.flow.close(root.ui.modals.node_overlay)
if root.run_scope.runner != null:
# 确认 ≠ 推进——兑付确认后经 choices_presented 弹出选路口
root.run_scope.runner.continue_after_node()
@@ -167,33 +167,33 @@ static func _on_choices_presented(root: Variant, candidates: Array) -> void:
# 零自动推进的停点呈现:整幅节点图 + 图上点击选路(战斗胜利/兑付确认统一到达此处)
if root.run_scope.runner == null:
return
root.log_view.append_bb(root.Narrator.route_line(candidates, root.run_scope.runner))
root.ui.battle.log_view.append_bb(root.Narrator.route_line(candidates, root.run_scope.runner))
# 战斗胜利总结结算(battle.md OQ14):战斗胜利停点先弹 BattleEndOverlay
# 胜利形态(金蜡封+GP 增量+总量+判定行+「下一节点」+明细回看),确认后进选路页。判据 = 战斗节点
# 的选路停点且战斗已收场(battle 收尾分支 _settlement 置 null 先于本信号;入口/兑付/商店停点
# 的当前节点非 battle 型——三态天然分流)。Boss 胜/失败走 level_finished 另路不经此处。
if root.run_scope.is_adventure and root.run_scope.runner.current_battle() == null \
and String(root.run_scope.runner.current_node().get("type", "")) == "battle" and not candidates.is_empty():
root._settlement_stop_pending = true
root.run_scope.settlement_stop_timer.arm()
root.run_scope.pending_candidates = candidates.duplicate()
root._end_overlay.open_victory(root.Narrator.battle_end_text("victory", false), "下一节点",
root.board_view.get_global_rect(),
root.ui.modals.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._open_modal_after_playback(root._end_overlay)
root.ui.modals.flow.open_after_playback(root.ui.modals.end_overlay)
root._refresh_presentations()
return
_enter_level_screen(root)
root.level_screen.set_feat_holdings(" · ".join(HudPresenter.feat_holdings_lines(root.db, root.run_scope.runner.feats_held())))
root.level_screen.open_choices(candidates, root.run_scope.runner)
root.level_screen.apply_status(level_map_status_payload(root.run_scope.runner))
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._refresh_presentations()
static func _on_route_chosen(root: Variant, node_id: int) -> void:
# 视图已按节点类型定去留——战斗/Boss 出屏(visible=false,此处收栈
# 记录一致性+回战斗屏);奖励/恢复/商店本屏保持激活(兑付覆盖层由节点事件域内弹出,模态栈
# 打开低层时自动隐藏本屏、关闭自动恢复)
if not root.level_screen.visible:
root._close_modal(root.level_screen)
if not root.ui.modals.level_screen.visible:
root.ui.modals.flow.close(root.ui.modals.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)
@@ -202,15 +202,15 @@ 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._battle_root.visible = false
root._open_modal(root.level_screen)
root.ui.screens.battle_root.visible = false
root.ui.modals.flow.open(root.ui.modals.level_screen)
root._sync_status_entry() # 屏级互斥切换同步(_open_modal 单口不识屏级状态)
static func _exit_level_screen(root: Variant) -> void:
root.run_scope.level_screen_active = false
root._battle_root.visible = true
if root.level_screen.visible:
root._close_modal(root.level_screen)
root.ui.screens.battle_root.visible = true
if root.ui.modals.level_screen.visible:
root.ui.modals.flow.close(root.ui.modals.level_screen)
root._sync_status_entry() # 同上
@@ -251,7 +251,7 @@ static func _on_abandon_requested(root: Variant) -> void:
return
if not root.run_scope.adventure.abandon():
root.logger.log_ux("ux_reject", {"source": "abandon", "sub_reason": "battle_in_flight"})
root.board_zone.toast.show_toast("战斗进行中——无法放弃", "warn")
root.ui.battle.board_zone.toast.show_toast("战斗进行中——无法放弃", "warn")
# ---- 营地覆盖层与钥石账本(写调用唯一归组合根)----
+15 -15
View File
@@ -13,7 +13,7 @@ static func _on_diagnose_pressed(root: Variant) -> void:
if root.run_scope.settlement != null:
ctx["settlement"] = root.run_scope.settlement.diagnostic_snapshot() # 诊断观测面(私有字段零直读)
var path: String = root.logger.dump_diagnosis(ctx)
root.board_zone.toast.show_toast("已转储诊断: " + path, "info")
root.ui.battle.board_zone.toast.show_toast("已转储诊断: " + path, "info")
@@ -21,17 +21,17 @@ static func _show_debug_overlay(root: Variant) -> void:
# 门禁:对局中可开;本函数仍是唯一开窗入口,门禁逻辑收函数内:
# 棋盘输入锁/状态角标判据排除浮窗,
# 遮挡语义=ModalStack 登记制(他模态开时暂隐、关后自动恢复),此处零场景限制
if root.debug_overlay == null:
if root.ui.modals.debug_overlay == null:
return
root._open_modal(root.debug_overlay)
root.ui.modals.flow.open(root.ui.modals.debug_overlay)
_sync_debug_overlay(root)
# 调试浮窗角标点击(两屏同款处理器——与状态角标同形制守卫;仍收口 _show_debug_overlay 单口)
static func _on_debug_badge_pressed(root: Variant) -> void:
var map_on: bool = root.level_screen != null and root.level_screen.visible
if (map_on and root._modal_flow.badge_greyed_map()) or (not map_on and root._modal_flow.badge_greyed_battle()):
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()):
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.debug_overlay == null:
if root.ui.modals.debug_overlay == null:
return
root.debug_overlay.set_context_locked(root.run_scope.settlement != null or root.run_scope.is_adventure) # 对局中批量模拟/两层调试入口禁用(长程异步模拟与活跃结算并存态不开放)
root.debug_overlay.update_run_data(_debug_run_data(root)) # 当局数据只读区块喂入(开窗+模态开合信号驱动)
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)) # 当局数据只读区块喂入(开窗+模态开合信号驱动)
@@ -63,7 +63,7 @@ static func _debug_run_data(root: Variant) -> Dictionary:
static func _close_debug_overlay(root: Variant) -> void:
root._close_modal(root.debug_overlay) # 模态栈单口——开始层全程未隐藏,无需恢复
root.ui.modals.flow.close(root.ui.modals.debug_overlay) # 模态栈单口——开始层全程未隐藏,无需恢复
static func _on_tune_apply(root: Variant, atk_pct: int, hp_pct: int) -> void:
# 加成无持久态可"落"——各战斗开局经 board_resolvers.debug_hero_override/runner 现算生效(应用=关闭浮窗)
@@ -83,11 +83,11 @@ 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.debug_overlay.reset_sliders()
root._close_modal(root.debug_overlay) # 浮窗内入口发起——随开随收(对局期不可再开:门禁在 _show_debug_overlay)
root._start_overlay.visible = false
root.log_view.clear()
root.log_view.append_bb(root.Narrator.debug_open_line(root._seed_value(), 1, true))
root.ui.modals.debug_overlay.reset_sliders()
root.ui.modals.flow.close(root.ui.modals.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))
root._begin_session(false)
if not root._debug.start_debug_levels():
root._fatal_screen("战斗启动失败——见输出日志")
@@ -100,6 +100,6 @@ static func _on_debug_battle_created(root: Variant, settlement_ref: BattleOrches
static func _on_debug_battle_ready(root: Variant, settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void:
root.run_scope.settlement = settlement_ref
root.board_view.rebuild(root.run_scope.settlement.minesweeper.current_board(), root.run_scope.settlement.minesweeper)
root.ui.battle.board_zone.board_view.rebuild(root.run_scope.settlement.minesweeper.current_board(), root.run_scope.settlement.minesweeper)
root._check_board_hints()
root._refresh_presentations()
+33 -33
View File
@@ -5,12 +5,12 @@
extends RefCounted
static func _open_settings(root: Variant) -> void:
root._start_overlay.visible = false
root._open_modal(root.settings_overlay) # 模态栈单口
root.ui.screens.start_overlay.visible = false
root.ui.modals.flow.open(root.ui.modals.settings_overlay) # 模态栈单口
static func _on_settings_closed(root: Variant) -> void:
root._close_modal(root.settings_overlay) # 模态栈单口
root._start_overlay.visible = true
root.ui.modals.flow.close(root.ui.modals.settings_overlay) # 模态栈单口
root.ui.screens.start_overlay.visible = true
# ---- 局内状态面板·只读(入口角标在两屏装配段;开合与数据喂入归组合根)----
@@ -25,22 +25,22 @@ 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._open_modal(root.status_panel) # 模态栈单口(开层前排空排播队列——既有层同款行为,目检确认项)
root.status_panel.open_status(_status_data(root))
root.ui.modals.flow.open(root.ui.modals.status_panel) # 模态栈单口(开层前排空排播队列——既有层同款行为,目检确认项)
root.ui.modals.status_panel.open_status(_status_data(root))
static func _on_status_closed(root: Variant) -> void:
root._close_modal(root.status_panel) # 模态栈单口
root.ui.modals.flow.close(root.ui.modals.status_panel) # 模态栈单口
static func _open_career(root: Variant) -> void:
root._start_overlay.visible = false
root._open_modal(root.career_overlay) # 模态栈单口
root.career_overlay.open_career(HudPresenter.career_data(root.db, root.save_manager, root.meta))
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))
static func _on_career_closed(root: Variant) -> void:
root._close_modal(root.career_overlay) # 模态栈单口
root._start_overlay.visible = true
root.ui.modals.flow.close(root.ui.modals.career_overlay) # 模态栈单口
root.ui.screens.start_overlay.visible = true
# 生涯页快照投影 —— 承接体见 HudPresenter.career_data(开层调用点直接改引)
@@ -52,13 +52,13 @@ static func _on_career_closed(root: Variant) -> void:
# 开层(模态栈单口;与开始层互斥=career 同款先例)。「进入存档」钮归卡② mn3——本函数为
# 该钮的唯一落点(卡③ 批内探针函数直调=中间态,实现报告「已知中间态」登记)。
static func _open_save_select(root: Variant) -> void:
root._start_overlay.visible = false
root._open_modal(root.save_select_overlay) # 模态栈单口
root.save_select_overlay.open_save_select(HudPresenter.save_select_data(root.save_manager))
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))
static func _on_save_select_closed(root: Variant) -> void:
root._close_modal(root.save_select_overlay) # 模态栈单口
root._start_overlay.visible = true
root.ui.modals.flow.close(root.ui.modals.save_select_overlay) # 模态栈单口
root.ui.screens.start_overlay.visible = true
# 槽数据快照 —— 承接体见 HudPresenter.save_select_data(开层调用点直接改引)
@@ -85,7 +85,7 @@ 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._open_modal(root.confirm_dialog) # 模态栈单口(显示编排/挂起兜底/遮挡记账——与其余覆盖层同口)
root.ui.modals.flow.open(root.confirm_dialog) # 模态栈单口(显示编排/挂起兜底/遮挡记账——与其余覆盖层同口)
root.confirm_dialog.open("删除存档位 %d?删除后该档不可恢复。" % slot, "删除", "取消")
@@ -93,10 +93,10 @@ static func _on_save_select_delete(root: Variant, slot: int) -> void:
# 入口角标点击(两屏同款处理器):P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰,
# 探针 pressed.emit 绕过 disabled 时由此兜住;地图屏自身的登记开态不计——has_open_excluding)
static func _on_status_badge_pressed(root: Variant) -> void:
var map_on: bool = root.level_screen != null and root.level_screen.visible
var map_on: bool = root.ui.modals.level_screen != null and root.ui.modals.level_screen.visible
# P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰,探针 pressed.emit 绕过 disabled 时由此兜住;
# 地图屏自身的登记开态不计;判据排除调试浮窗:对局中浮窗开着时状态角标仍可点)
if (map_on and root._modal_flow.badge_greyed_map()) or (not map_on and root._modal_flow.badge_greyed_battle()):
if (map_on and root.ui.modals.flow.badge_greyed_map()) or (not map_on and root.ui.modals.flow.badge_greyed_battle()):
return
_open_status_panel(root)
@@ -106,17 +106,17 @@ static func _on_status_badge_pressed(root: Variant) -> void:
# 地图屏角标=随屏;置灰=登记册上除地图屏自身外任一层开着(防「看得见点不着」态)。
# 调用面=模态开合单口与屏级互斥口(凡影响开态/屏态的路径必经其一)。
static func _sync_status_entry(root: Variant) -> void:
if root.status_panel == null or root._modals == null or root.board_zone == null or root.level_screen == null:
if root.ui.modals.status_panel == null or root._modals == null or root.ui.battle.board_zone == null or root.ui.modals.level_screen == null:
return
var map_on: bool = root.level_screen.visible
root.board_zone.status_badge.visible = root.run_scope.level_active and not map_on
root.level_screen.status_badge.visible = map_on
var map_on: bool = root.ui.modals.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.board_zone.status_badge.disabled = root._modal_flow.badge_greyed_battle() # 互斥域谓词
root.level_screen.status_badge.disabled = root._modal_flow.badge_greyed_map()
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()
# 调试浮层角标(全局浮层任何 UI 不遮挡——可见性不随场景变,只抬 offset
# 避让:地图屏底栏 85px 常驻,角标抬至底栏上方 16px;其余场景右下角 16px 贴底)
root.debug_badge.offset_bottom = -101.0 if map_on else -16.0
root.ui.screens.debug_badge.offset_bottom = -101.0 if map_on else -16.0
@@ -131,19 +131,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.shop_overlay.visible and root.run_scope.runner != null:
if root.ui.modals.shop_overlay.visible and root.run_scope.runner != null:
if root.run_scope.runner.shop.is_shop_pool():
root.shop_overlay.refresh_shelf_state(int(root.run_scope.runner.gp)) # pool 面余额联动(货架内容仅买后重抽变)
root.ui.modals.shop_overlay.refresh_shelf_state(int(root.run_scope.runner.gp)) # pool 面余额联动(货架内容仅买后重抽变)
else:
root.shop_overlay.refresh_state(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp))
root.ui.modals.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.legend_overlay.is_open():
root._close_modal(root.legend_overlay)
if root.ui.modals.legend_overlay.is_open():
root.ui.modals.flow.close(root.ui.modals.legend_overlay)
else:
root._open_modal(root.legend_overlay)
root.ui.modals.flow.open(root.ui.modals.legend_overlay)
+119 -121
View File
@@ -1,7 +1,7 @@
## VERTICAL SLICE - NOT FOR PRODUCTION
## 组合根 UI 装配分步承接件:三屏壳实例化、视图句柄接线、覆盖层装配与模态编排注入按步拆开。
## 本件是 main._build_ui 的逐字分步搬运(成员访问改 root 前缀)——main 侧留同名薄委托,装配序与 z 序零改动。
## 层序唯一事实源仍是 main.LAYER_CONTRACT + _apply_layer_contract(本件不持有层序逻辑)。
## 层序唯一事实源=root.ui.LAYER_CONTRACT + root.ui.apply_layer_contract(ui_registry.gd——本件不持有层序逻辑)。
extends RefCounted
const UiAssembler := preload("res://presentation/ui_assembler.gd")
@@ -35,50 +35,50 @@ static func _build_screens(root: Variant) -> void:
# 屏内 level_screen 与 battle_root 无同时可见态(屏级互斥+模态栈遮挡),层内先后零视觉差;
# 视觉共存对逐对验算:legend/battle(图例战斗中开——legend 在冒险屏壳之后=其上 ✓)、
# debug/level_screen(浮窗最后子=其上 ✓)、shop/level_screen(商店节点域弹出——模态均在屏壳上 ✓)。
root._adventure_screen = AdventureScreenScene.instantiate()
root.add_child(root._adventure_screen)
root._menu_screen = MenuScreenScene.instantiate()
root.add_child(root._menu_screen)
root._start_overlay = root._menu_screen.start_overlay
root._battle_root = root._adventure_screen.battle_root
root.stage = root._adventure_screen.stage
root.log_view = root._adventure_screen.log_view
root.info_panel = root._adventure_screen.info_panel
root.board_zone = root._adventure_screen.board_zone
root.board_view = root._adventure_screen.board_view # 探针兼容别名(= board_zone.board_view)
root.ap_bar = root._adventure_screen.ap_bar # 探针兼容别名(= board_zone.ap_bar)
root.settlement_fx = root._adventure_screen.settlement_fx
root.level_screen = root._adventure_screen.level_screen
root.ui.screens.adventure_screen = AdventureScreenScene.instantiate()
root.add_child(root.ui.screens.adventure_screen)
root.ui.screens.menu_screen = MenuScreenScene.instantiate()
root.add_child(root.ui.screens.menu_screen)
root.ui.screens.start_overlay = root.ui.screens.menu_screen.start_overlay
root.ui.screens.battle_root = root.ui.screens.adventure_screen.battle_root
root.ui.battle.stage = root.ui.screens.adventure_screen.stage
root.ui.battle.log_view = root.ui.screens.adventure_screen.log_view
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.board_view = root.ui.screens.adventure_screen.board_view # 探针兼容别名(= ui.battle.board_zone.board_view;T4 退役)
root.ap_bar = root.ui.screens.adventure_screen.ap_bar # 探针兼容别名(= ui.battle.board_zone.ap_bar;T4 退役)
root.ui.battle.settlement_fx = root.ui.screens.adventure_screen.settlement_fx
root.ui.modals.level_screen = root.ui.screens.adventure_screen.level_screen
static func _build_battle_wiring(root: Variant) -> void:
# 结算行排播泵(本体在 row_pacer.gd;本类注入视图句柄与呈现刷新回调,零信号连接)
root._row_pacer = RowPacer.new()
root._row_pacer.log_view = root.log_view
root._row_pacer.stage = root.stage
root._row_pacer.board_zone = root.board_zone
root._row_pacer.settlement_fx = root.settlement_fx
root._row_pacer.board_view = root.board_view
root._row_pacer.refresh_request = root._refresh_presentations
root._row_pacer.pump_ended = root._on_pump_ended # 演播完边沿统一派发(挂起 modal 优先,其次锁期点击重放)
root.add_child(root._row_pacer)
root.ui.screens.row_pacer = RowPacer.new()
root.ui.screens.row_pacer.log_view = root.ui.battle.log_view
root.ui.screens.row_pacer.stage = root.ui.battle.stage
root.ui.screens.row_pacer.board_zone = root.ui.battle.board_zone
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.add_child(root.ui.screens.row_pacer)
# 秘宝两步交互(本体在 treasure_interact_controller.gd;settlement/runner/grant 随关卡/战斗更替,
# 经现值回调注入——仿 identity_resolver 只读回调先例;grant 每战新建编排器,现值取件防悬挂)
root._treasure_ui = TreasureInteractController.new({
"db": root.db, "meta": root.meta, "info_panel": root.info_panel, "toast": root.board_zone.toast,
"board_view": root.board_view,
"db": root.db, "meta": root.meta, "info_panel": root.ui.battle.info_panel, "toast": root.ui.battle.board_zone.toast,
"board_view": root.ui.battle.board_zone.board_view,
"settlement_get": func() -> Variant: return root.run_scope.settlement,
"runner_get": func() -> Variant: return root.run_scope.runner,
"grant_get": func() -> Variant: return root.run_scope.settlement.reward_grant() if root.run_scope.settlement != null else null,
})
root.board_zone.action_requested.connect(root._on_board_action)
root.board_zone.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(战斗屏 HUD 角标)
root.info_panel.treasure_slot_clicked.connect(root._treasure_ui.on_slot_clicked) # 两步交互第一步
root.board_zone.stop_pressed.connect(root._on_stop_pressed)
root.board_zone.refresh_confirmed.connect(root._on_refresh_confirmed)
root.board_zone.legend_toggled.connect(root._on_legend_toggled)
root.board_zone.hover_forbidden.connect(root._on_hover_forbidden)
root.board_zone.chord_attempted.connect(root._on_chord_attempted)
root.ui.battle.board_zone.action_requested.connect(root._on_board_action)
root.ui.battle.board_zone.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(战斗屏 HUD 角标)
root.ui.battle.info_panel.treasure_slot_clicked.connect(root._treasure_ui.on_slot_clicked) # 两步交互第一步
root.ui.battle.board_zone.stop_pressed.connect(root._on_stop_pressed)
root.ui.battle.board_zone.refresh_confirmed.connect(root._on_refresh_confirmed)
root.ui.battle.board_zone.legend_toggled.connect(root._on_legend_toggled)
root.ui.battle.board_zone.hover_forbidden.connect(root._on_hover_forbidden)
root.ui.battle.board_zone.chord_attempted.connect(root._on_chord_attempted)
root.logger = PlaytestLogger.new()
root.logger.ap_per_round = int(root.db.run.action_economy.ap_per_round)
@@ -88,136 +88,135 @@ static func _build_overlays(root: Variant) -> void:
# < 商店 < 营地 < 设置 < 状态面板(只读) < 生涯页(只读) < 确认弹窗 < 调试浮窗;
# 关卡画面住冒险屏壳(树序在模态之下)——登记序仍夹在节点层与三选一层之间,遮挡语义零改动)
var overlays: Dictionary[Variant, Variant] = UiAssembler.build_overlays(root, root.db)
root.legend_overlay = overlays.legend_overlay
root.node_overlay = overlays.node_overlay
root._end_overlay = overlays.end_overlay
root.shop_overlay = overlays.shop_overlay
root._camp_screen = CampScreenScene.instantiate()
root.add_child(root._camp_screen)
root.camp_overlay = root._camp_screen.camp_overlay
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.screens.camp_screen = CampScreenScene.instantiate()
root.add_child(root.ui.screens.camp_screen)
root.ui.modals.camp_overlay = root.ui.screens.camp_screen.camp_overlay
# 营地屏壳插位(夹商店层与设置层之间)由 LAYER_CONTRACT 营地槽统一落位——禁散落 move_child
root.legend_overlay.closed.connect(root._on_modal_closed.bind(root.legend_overlay))
root.node_overlay.continue_pressed.connect(root._on_node_continue)
root.level_screen.route_chosen.connect(root._on_route_chosen)
root.level_screen.status_badge.pressed.connect(root._on_status_badge_pressed) # 状态面板入口(地图屏顶栏角标)
root.level_screen.abandon_requested.connect(root._on_abandon_requested) # 放弃入口
root._end_overlay.continue_pressed.connect(root._on_battle_end_continue)
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)
static func _build_aux_panels(root: Variant) -> void:
root.settings_overlay = SettingsOverlay.new(root.settings_store) # 最小设置面(音量/全屏/减动效)
root.add_child(root.settings_overlay)
root.settings_overlay.closed.connect(root._on_settings_closed)
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)
# 局内状态面板·只读(登记设置层同档带 dim 遮罩,确认弹窗之前;
# 数据=组合根开层实时读 MetaProgression 访问器喂入,本层零写通路)
root.status_panel = StatusPanel.new()
root.add_child(root.status_panel)
root.status_panel.closed.connect(root._on_status_closed)
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)
# 生涯页·只读全屏页(登记序=营地同档,设置层旁、确认弹窗之前;
# 树序同步=addChild 同位(登记序=树序=z 序纪律);数据=组合根开层实时读 SaveManager 计数
# 访问器+MetaProgression 访问器喂入,本层零写通路零跳转;入口=开始层「营地与系统」组生涯钮)
root.career_overlay = CareerOverlay.new()
root.add_child(root.career_overlay)
root.career_overlay.closed.connect(root._on_career_closed)
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)
# 选档界面(营地交互重做卡③):登记序=生涯同档(树序同步=addChild 同位);三动作只发意图信号
#(激活/创建/删除+确认分派归组合根——视图零数据层依赖);入口=「进入存档」钮(卡② mn3 接线)。
root.save_select_overlay = SaveSelectOverlay.new()
root.add_child(root.save_select_overlay)
root.save_select_overlay.closed.connect(root._on_save_select_closed)
root.save_select_overlay.new_slot_requested.connect(root._on_save_select_new)
root.save_select_overlay.load_slot_requested.connect(root._on_save_select_load)
root.save_select_overlay.delete_slot_requested.connect(root._on_save_select_delete)
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)
static func _build_curio(root: Variant) -> void:
# 提升三选一覆盖层(z 序 = 节点层之上、终局层之下;确认后同拍推进;
# 插位由 LAYER_CONTRACT curio 槽统一落位——禁散落 move_child)
root.curio_overlay = CurioPickOverlay.new()
root.add_child(root.curio_overlay)
root.ui.modals.curio_overlay = CurioPickOverlay.new()
root.add_child(root.ui.modals.curio_overlay)
# 提升三选一与替换子交互(本体在 curio_interact_controller.gd;本类只接线与回调注入,
# 模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调)
root._bonus = CurioInteractController.new(CurioFlowContext.new(root.db,
{"curio_overlay": root.curio_overlay, "end_overlay": root._end_overlay,
"board_view": root.board_view, "toast": root.board_zone.toast},
{"curio_overlay": root.ui.modals.curio_overlay, "end_overlay": root.ui.modals.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._open_modal, "open_modal_after_playback": root._open_modal_after_playback,
"close_modal": root._close_modal, "refresh_presentations": root._refresh_presentations,
{"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,
"fatal_screen": root._fatal_screen,
"gp_summary": _gp_summary_cb(root)})) # 构造参数收拢 context(u20 三束)
root.curio_overlay.pick_confirmed.connect(root._bonus.on_pick_confirmed)
root.curio_overlay.continue_pressed.connect(root._bonus.on_continue)
root.curio_overlay.refresh_requested.connect(root._bonus.on_refresh)
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)
static func _build_menu_wiring(root: Variant) -> void:
# 菜单屏接线(句柄经屏壳暴露;三入口化——进入存档/设置/退出游戏,卡② mn3:
# 进入存档钮落点=卡③选档层 _open_save_select,点击链在此闭环)
root._menu_screen.entersavebtn.pressed.connect(root._open_save_select)
root.ui.screens.menu_screen.entersavebtn.pressed.connect(root._open_save_select)
# 调试入口=UI 最顶层全局浮层角标(任何 UI 不遮挡——菜单/战斗/地图三枚
# 屏内角标由全局浮层承载;连接归组合根,树序/登记面见 LAYER_CONTRACT 末位)
root.debug_badge = UiAssembler.build_debug_badge(root)
root.add_child(root.debug_badge)
root.debug_badge.pressed.connect(root._on_debug_badge_pressed)
root.ui.screens.debug_badge = UiAssembler.build_debug_badge(root)
root.add_child(root.ui.screens.debug_badge)
root.ui.screens.debug_badge.pressed.connect(root._on_debug_badge_pressed)
# 营地/商店投影(本体在 camp_shop_presenter.gd;本类只接线、探针缝保缝转发;
# 购买写调用唯一经 MetaProgression/LevelRunner core 单口——组合根 lambda 现值注入)
root._camp_pres = CampShopPresenter.new(CampFlowContext.new(root.db,
{"camp_overlay": root.camp_overlay, "shop_overlay": root.shop_overlay,
"toast": root.board_zone.toast, "start_overlay": root._start_overlay, "logger": root.logger},
{"camp_overlay": root.ui.modals.camp_overlay, "shop_overlay": root.ui.modals.shop_overlay,
"toast": root.ui.battle.board_zone.toast, "start_overlay": root.ui.screens.start_overlay, "logger": root.logger},
{"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._open_modal, "close_modal": root._close_modal,
{"open_modal": root.ui.modals.flow.open, "close_modal": root.ui.modals.flow.close,
"refresh_presentations": root._refresh_presentations})) # 构造参数收拢 context(u21 三束)
root.shop_overlay.buy_requested.connect(root._camp_pres.on_shop_buy)
root.shop_overlay.refresh_requested.connect(root._camp_pres.on_shop_refresh) # MC-1
root.shop_overlay.leave_pressed.connect(root._camp_pres.on_shop_leave)
root.camp_overlay.buy_requested.connect(root._camp_pres.on_camp_buy)
root.camp_overlay.attune_toggled.connect(root._camp_pres.on_camp_attune_toggle)
root.camp_overlay.tree_buy_requested.connect(root._camp_pres.on_camp_tree_buy)
root.camp_overlay.tree_fork_toggled.connect(root._camp_pres.on_camp_fork_toggle)
root.camp_overlay.forge_buy_requested.connect(root._camp_pres.on_camp_forge_buy) # 锻造三意图(写口唯一归 MetaProgression)
root.camp_overlay.forge_unlock_requested.connect(root._camp_pres.on_camp_forge_unlock)
root.camp_overlay.forge_select_requested.connect(root._camp_pres.on_camp_forge_select)
root.camp_overlay.closed.connect(root._camp_pres.on_camp_closed)
root._menu_screen.settingsbtn.pressed.connect(root._open_settings) # 最小设置面入口
root._menu_screen.quitbtn.pressed.connect(root._quit_game) # 退出游戏提级一级菜单
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.camp_overlay.buy_requested.connect(root._camp_pres.on_camp_buy)
root.ui.modals.camp_overlay.attune_toggled.connect(root._camp_pres.on_camp_attune_toggle)
root.ui.modals.camp_overlay.tree_buy_requested.connect(root._camp_pres.on_camp_tree_buy)
root.ui.modals.camp_overlay.tree_fork_toggled.connect(root._camp_pres.on_camp_fork_toggle)
root.ui.modals.camp_overlay.forge_buy_requested.connect(root._camp_pres.on_camp_forge_buy) # 锻造三意图(写口唯一归 MetaProgression)
root.ui.modals.camp_overlay.forge_unlock_requested.connect(root._camp_pres.on_camp_forge_unlock)
root.ui.modals.camp_overlay.forge_select_requested.connect(root._camp_pres.on_camp_forge_select)
root.ui.modals.camp_overlay.closed.connect(root._camp_pres.on_camp_closed)
root.ui.screens.menu_screen.settingsbtn.pressed.connect(root._open_settings) # 最小设置面入口
root.ui.screens.menu_screen.quitbtn.pressed.connect(root._quit_game) # 退出游戏提级一级菜单
static func _build_debug_and_resolvers(root: Variant) -> void:
var inter: Dictionary[Variant, Variant] = UiAssembler.build_inter_overlay(root)
root._inter_overlay = inter.overlay
root._inter_label = inter.label
root.ui.screens.inter_overlay = inter.overlay
root.ui.screens.inter_label = inter.label
inter.next_btn.pressed.connect(root._on_inter_next_pressed)
# 确认弹窗(新冒险互斥确认弹窗化):树序落在开始层/过场层之后 = 子级最末
#(纪律「登记序 = 树序 = z 序」——confirm_dialog.open 禁运行时 move_child 自提抬层;
# 弹窗只在开始菜单域弹出,绘制序恒最上;
# 诊断转储入口在调试浮窗)
root.confirm_dialog = ConfirmDialog.new()
root.add_child(root.confirm_dialog)
root.ui.modals.confirm_dialog = ConfirmDialog.new()
root.add_child(root.ui.modals.confirm_dialog)
# 消费方编排(卡③ 契约 3/sl8——禁双常驻静态接线):open 前设 _confirm_consumer,
# confirmed/cancelled 后按该状态量分派;分派面可容纳后续消费方(卡④ 到位直接接入不返工)
root.confirm_dialog.confirmed.connect(root._on_confirm_accepted)
root.confirm_dialog.cancelled.connect(root._on_confirm_declined)
root.ui.modals.confirm_dialog.confirmed.connect(root._on_confirm_accepted)
root.ui.modals.confirm_dialog.cancelled.connect(root._on_confirm_declined)
# 调试浮窗(主菜单三散件收一:随机种子/数值调整/批量模拟/两层调试入口;
# 全局可开浮窗:调试会话恒开=启动即开〔_ready〕+两屏角标再开+当局数据只读区块;
# 诊断转储入口在本浮窗,玩家画面零常驻调试件)。
# 树序在开始层与确认弹窗之后 = 浮于菜单之上的 Dialog(不隐藏开始层——与确认弹窗同款形态);
# 开窗唯一入口 = _show_debug_overlay(C6 单口——角标/探针/启动路径全收口)
root.debug_overlay = DebugOverlay.new(root.db, int(root.db.combat.hero.atk), int(root.db.combat.hero.max_hp))
root.add_child(root.debug_overlay)
root._seed_edit = root.debug_overlay.seed_edit # 种子读取面在浮窗(_seed_value 唯一消费点不变)
root.debug_overlay.closed.connect(root._on_modal_closed.bind(root.debug_overlay))
root.debug_overlay.apply_requested.connect(root._on_tune_apply)
root.debug_overlay.simulate_requested.connect(root._on_tune_simulate)
root.debug_overlay.debug_levels_requested.connect(root._start_debug_levels)
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)
# 棋盘解析回调族(有态绑定件——board_resolvers.gd 承接;组合根只接线;局部持有不占类成员位)
var resolvers = BoardResolversScript.new(root.db, root.debug_overlay, func() -> Variant: return root.run_scope.settlement)
var resolvers = BoardResolversScript.new(root.db, root.ui.modals.debug_overlay, func() -> Variant: return root.run_scope.settlement)
# 奖励徽章数据源:地图战斗资源 身份解析只读回调(已开格身份 icon——挖掘期 Active 相位内有效)
root.board_view.set_identity_resolver(resolvers.identity_lookup)
root.ui.battle.board_zone.board_view.set_identity_resolver(resolvers.identity_lookup)
# 数字遮蔽数据源:地形变异 hide_number 解析只读回调(信息型呈现——board_view 渲染面读取)
root.board_view.set_variant_mask_resolver(resolvers.variant_mask_lookup)
root.board_view.set_variant_mark_resolver(resolvers.variant_mark_lookup) # flat_damage 未开格角标
root.ui.battle.board_zone.board_view.set_variant_mask_resolver(resolvers.variant_mask_lookup)
root.ui.battle.board_zone.board_view.set_variant_mark_resolver(resolvers.variant_mark_lookup) # flat_damage 未开格角标
# 调试会话(英雄生效值预览回调绑同一解析件——创建晚于浮窗、随 _build_ui 一次接线)
root._debug = DebugSessionScript.new(root.db, root.get_node(root.AUTOLOAD_PATH_RNG),
func() -> Dictionary: return resolvers.debug_hero_override(), root._seed_value)
@@ -228,24 +227,23 @@ static func _build_modal_flow(root: Variant) -> void:
# 模态登记(缺陷⑤纪律——登记序 = 树序 = z 序):层序唯一事实源=LAYER_CONTRACT,
# _apply_layer_contract 统一落位(关卡画面宿住冒险屏壳、营地屏壳夹商店与设置层之间、
# 三选一对位终局层之下等特例均在合同数据内表达,禁散落 move_child)
# 模态编排注入(ModalFlow 纯逻辑类:依赖仅 ModalStack+查询 Callable)
root._modal_flow = ModalFlow.new()
root._modal_flow.start_visible_get = func() -> bool: return root._start_overlay.visible
root._modal_flow.inter_visible_get = func() -> bool: return root._inter_overlay.visible
root._modal_flow.settlement_get = func() -> Variant: return root.run_scope.settlement
root._modal_flow.rows_empty_get = func() -> bool: return root._row_pacer.pending_rows().is_empty()
root._modal_flow.flush_rows = root._flush_row_queue
root._modal_flow.on_open_sync = func() -> void:
# 模态编排注入(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._sync_status_entry() # 状态面板入口角标可见性/置灰随开态同步
root._sync_debug_overlay() # 浮窗当局数据/按钮态随开态同步
root._modal_flow.on_close_sync = func() -> void:
root.ui.modals.flow.on_close_sync = func() -> void:
root._sync_status_entry() # 关闭恢复角标可点态(无残留——同源口径)
root._sync_debug_overlay() # 他层关闭复活浮窗时同步刷新(关后自动恢复语义配套)
root._modal_flow.replay_click = root._replay_locked_click
root._modals = root._modal_flow.stack # 探针 _list 登记序断言缝(ui_struct 登记序=z 序纪律断言)
root.ui.modals.flow.replay_click = root._replay_locked_click
root._modals = root.ui.modals.flow.stack # 探针 _list 登记序断言缝(ui_struct 登记序=z 序纪律断言)
# 视觉层合同装配(唯一事实源=LAYER_CONTRACT:树序+登记序同源派生——改序只改合同表)
root._apply_layer_contract()
root._tune_sim = TuneSimController.new(root.db, root.debug_overlay, int(root.db.combat.hero.max_hp))
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))
static func fatal_screen(root: Variant, msg: String) -> void:
var l := UiKit.label(msg,UiKit.FS_SUB, true, UiKit.RED_ALERT)
+5
View File
@@ -1,5 +1,6 @@
## 模态层域句柄册:十三个模态覆盖层句柄+模态栈编排(ModalFlow——登记/开合/输入锁谓词)的注册面域容器。
## RefCounted 不挂树;模态登记唯一口=register_layer(anchors 撑满宿主+排入 ModalStack)。
## flow 随容器构造即建(装配早期注入点经 root.ui.modals.flow.* 取 Callable——晚于 flow 建立时点)。
class_name UiModals
extends RefCounted
@@ -19,6 +20,10 @@ var confirm_dialog: ConfirmDialog
var debug_overlay: DebugOverlay
func _init() -> void:
flow = ModalFlow.new() # 查询/钩子 Callable 由 _build_modal_flow 注入(注入时点不变——注入值面向已在座实例)
func register_layer(m: Control, tags: Array = []) -> void:
# 撑满宿主(Control 默认锚 = 0×0——全屏变暗层与居中布局整体失效;route 选路层预览截图实证后
# 统一收口:六个模态同病,探针经信号直发绕过真实点击从未暴露。anchors_and_offsets 而非 anchors
+4 -4
View File
@@ -67,9 +67,9 @@ func _check_main_scene(main: Node) -> void:
return
var run_scope: Node = main.get("run_scope")
check(run_scope != null and run_scope.is_inside_tree(), "闲时会话 run_scope 在树")
var start_overlay: Control = main.get("_start_overlay")
var start_overlay: Control = main.get("ui").screens.start_overlay
check(start_overlay != null and start_overlay.visible, "启动态菜单可见(boot 后玩家见菜单)")
check(main.get("settings_store") != null, "系统设置存储已装配(settings_store)")
check(main.get("debug_badge") != null, "调试入口角标已装配(debug_badge)")
check(main.get("legend_overlay") != null, "图例覆盖层已装配(legend_overlay)")
check(main.get("status_panel") != null, "局内状态面板已装配(status_panel)")
check(main.get("ui").screens.debug_badge != null, "调试入口角标已装配(debug_badge)")
check(main.get("ui").modals.legend_overlay != null, "图例覆盖层已装配(legend_overlay)")
check(main.get("ui").modals.status_panel != null, "局内状态面板已装配(status_panel)")
+30 -9
View File
@@ -66,12 +66,33 @@ func _smoke_hygiene_gates() -> void:
var mv: String = FileAccess.get_file_as_string("res://presentation/main.gd")
Kit.check("V8 GP 影子账残留零命中(main 源码面——文件已删+委托缝已拆)〔迭代27 T3〕",
not mv.contains("gp_shadow_ledger") and not mv.contains("_gp_ledger"))
# V1d 层序落位单口:main.gd 的 move_child 调用全部收在 _apply_layer_contract 内(合同装配单口——
# 函数体之外零调用点;出现第二处落位=硬编码层序,须改合同表而非加调用)
var fn_start: int = mv.find("func _apply_layer_contract")
var fn_end: int = mv.find("\nfunc ", fn_start + 10)
if fn_end < 0:
fn_end = mv.length()
var outside_calls: int = (mv.substr(0, fn_start) + mv.substr(fn_end)).count("move_child(")
Kit.check("V1d 层序落位单口(_apply_layer_contract 之外 move_child 调用点=%d 应=0)〔装配生命批 V1〕" % outside_calls,
outside_calls == 0)
# V1d 层序落位单口:注册面建件后扫全 presentation——move_child 只允许出现在 ui_registry.gd 的
# apply_layer_contract 函数体内(合同装配单口——出现第二处落位=硬编码层序,须改合同表而非加调用)
var scanned: int = 0
var outside_calls: int = 0
for fn_path: String in _list_scripts("res://presentation"):
var src: String = FileAccess.get_file_as_string(fn_path)
scanned += 1
var fn_start: int = src.find("func apply_layer_contract") if fn_path.contains("ui_registry.gd") else -1
var fn_end: int = src.find("\nfunc ", fn_start + 10) if fn_start >= 0 else -1
if fn_end < 0:
fn_end = src.length()
outside_calls += (src.substr(0, fn_start) + src.substr(fn_end)).count("move_child(")
Kit.check("V1d 层序落位单口(presentation %d 文件——apply_layer_contract 之外 move_child 调用点=%d 应=0)〔注册面 V1〕" % [scanned, outside_calls],
scanned > 0 and outside_calls == 0)
# 目录 .gd 清单(DirAccess 平铺——presentation 无子目录;扫描面>0 前置由调用侧断言承载)
static func _list_scripts(dir_path: String) -> PackedStringArray:
var out: PackedStringArray = PackedStringArray()
var dir := DirAccess.open(dir_path)
if dir == null:
return out
dir.list_dir_begin()
var fname := dir.get_next()
while fname != "":
if fname.ends_with(".gd"):
out.append(dir_path + "/" + fname)
fname = dir.get_next()
dir.list_dir_end()
return out
@@ -722,8 +722,8 @@ func _smoke_diag_button_retired() -> void:
var main_src: String = FileAccess.get_file_as_string("res://presentation/main.gd")
Kit.check("V6 主区无 build_diag_button 调用残留(旧实现同批清理)",
not main_src.contains("build_diag_button"))
Kit.check("V6 转储入口连接随迁(组合根接浮窗 diagnose_requested 信号)",
main_src.contains("debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)"))
Kit.check("V6 转储入口连接随迁(组合根经注册面接浮窗 diagnose_requested 信号)〔注册面 V2〕",
main_src.contains("ui.modals.debug_overlay.diagnose_requested.connect(_on_diagnose_pressed)"))
Kit.check("V6 转储编排承接函数在位(dump_diagnosis 卡死取证保留)",
main_src.contains("func _on_diagnose_pressed()"))
var asm_src: String = FileAccess.get_file_as_string("res://presentation/ui_assembler.gd")