From 67024cb9ecc4ec0dd236b8dc7d39d1d41cb66e09 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 5 Nov 2026 03:04:21 +0800 Subject: [PATCH] =?UTF-8?q?iter52-=E5=8D=A13=EF=BC=9A=E8=BF=AD=E4=BB=A352?= =?UTF-8?q?=E5=8D=A1=E2=91=A2(presentation=E5=B1=82=E5=B0=81=E8=A3=85?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D):=20A1=E4=B8=89=E7=B1=BB42=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E5=85=AC=E5=BC=80=E5=8C=96(280=E5=A4=84=E6=94=B9=E5=9D=80)+A2?= =?UTF-8?q?=E6=8B=86=E4=BB=B6=E5=AD=90=E4=BB=B6=E5=85=AC=E5=BC=80=E9=9D=A2?= =?UTF-8?q?(vitals14/motion14/board=5Fhover=E4=B8=83=E5=8F=A3)+B=E6=97=8F?= =?UTF-8?q?=E7=BB=84=E5=90=88=E6=A0=B9=E4=BA=A4=E6=8E=A5=E9=9D=A2(18?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1+12=E5=8F=A5=E6=9F=84=E5=8F=A3,=E6=9D=9F?= =?UTF-8?q?=E5=8F=82=E4=B8=A4=E5=A4=84,on=5F*=E4=BF=9D=E7=A7=81=E6=9C=89)+?= =?UTF-8?q?C=E6=97=8F=E6=9D=82=E9=A1=B9(UiKit=E4=B8=89=E5=B7=A5=E5=8E=82/?= =?UTF-8?q?=E6=9E=84=E9=80=A0=E4=BA=A4=E4=BB=98)=E2=80=94=E2=80=94presenta?= =?UTF-8?q?tion=E7=A7=81=E5=8F=96sweep=E6=81=92=E9=9B=B6(V1-V4,105checks),?= =?UTF-8?q?main.gd=E4=BE=8B=E5=A4=96=E9=94=AE32=E4=B8=8Eui=5Fkit=E4=BE=8B?= =?UTF-8?q?=E5=A4=96=E9=94=AE23=E8=90=BD=E5=9F=BA=E7=BA=BF(=C2=A73.2?= =?UTF-8?q?=E8=AF=AF=E6=B5=8B0/0=E5=8F=8D=E5=93=BA=E7=82=B9),=E6=8E=A2?= =?UTF-8?q?=E9=92=88=E6=B5=8B=E8=AF=9522=E5=A4=84=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E8=BF=81=E7=A7=BB,=E6=B8=B2=E6=9F=93golden=E4=B8=8E=E6=8E=A2?= =?UTF-8?q?=E9=92=88=E5=A5=97=E4=BB=B6=E9=9B=B6=E8=BD=AC=E7=BA=A2;=20?= =?UTF-8?q?=E9=AA=8C=E6=94=B6=E6=A1=A3=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/presentation/main.gd | 285 ++++++++++++------ game/presentation/main_adventure_flow.gd | 88 +++--- game/presentation/main_debug_flow.gd | 56 ++-- game/presentation/main_esc_flow.gd | 10 +- game/presentation/main_overlay_flow.gd | 74 ++--- game/presentation/main_ui_build.gd | 166 +++++----- game/presentation/ui_assembler.gd | 2 +- game/presentation/views/battle_end_overlay.gd | 169 +++++------ game/presentation/views/board_cell_unit.gd | 37 ++- game/presentation/views/board_hover.gd | 25 +- game/presentation/views/board_view.gd | 27 +- game/presentation/views/cell_mark_overlay.gd | 19 +- game/presentation/views/debug_overlay.gd | 131 ++++---- game/presentation/views/level_map_screen.gd | 211 ++++++------- game/presentation/views/seal_mark.gdshader | 50 +++ .../presentation/views/seal_mark.gdshader.uid | 1 + game/presentation/views/seal_mark_layer.gd | 42 +++ .../presentation/views/seal_mark_layer.gd.uid | 1 + game/presentation/views/settings_overlay.gd | 4 +- game/presentation/views/standee_body.gd | 107 +++---- game/presentation/views/standee_motion.gd | 38 +++ game/presentation/views/standee_vitals.gd | 63 ++++ game/presentation/views/tile_palette.gd | 4 - game/presentation/views/ui_kit.gd | 14 +- game/probe/board_shot_probe.gd | 134 ++++++++ game/probe/probe_ui_util.gd | 2 +- game/probe/screen_flow_probe.gd | 2 +- game/probe/ui_click_probe.gd | 8 +- game/probe/ui_input_probe.gd | 12 +- game/probe/ui_probe.gd | 2 +- game/probe/ui_struct_probe.gd | 14 +- game/probe/ui_tune_probe.gd | 6 +- game/tests/run_all.gd | 2 + game/tests/system_test.gd | 6 +- .../unit/presentation/pacing_queue_test.gd | 2 +- .../presentation_encapsulation_sweep_test.gd | 192 ++++++++++++ ...esentation_encapsulation_sweep_test.gd.uid | 1 + .../presentation/seal_mark_params_test.gd | 52 ++++ .../presentation/seal_mark_params_test.gd.uid | 1 + .../presentation/stage_background_test.gd | 4 +- .../presentation/theme_projection_test.gd | 4 +- tools/gdstyle_baseline.txt | 2 + 42 files changed, 1391 insertions(+), 679 deletions(-) create mode 100644 game/presentation/views/seal_mark.gdshader create mode 100644 game/presentation/views/seal_mark.gdshader.uid create mode 100644 game/presentation/views/seal_mark_layer.gd create mode 100644 game/presentation/views/seal_mark_layer.gd.uid create mode 100644 game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd create mode 100644 game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd.uid create mode 100644 game/tests/unit/presentation/seal_mark_params_test.gd create mode 100644 game/tests/unit/presentation/seal_mark_params_test.gd.uid diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 051ecd60..bd941859 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -49,7 +49,57 @@ var settings_store # 系统级设置独立存储(音量 var _tune_sim = null # 调试浮窗批量模拟控制器(tune_sim_controller.gd) -func _replay_locked_click() -> void: +# ---- 句柄读投影/设置口(pres封装 B 规则 4——成员保持私有,分文件经此读写;BreakpointStore 方法对先例)---- + +func set_treasure_ui(v: Variant) -> void: + _treasure_ui = v + + +func treasure_ui() -> Variant: + return _treasure_ui + + +func set_bonus(v: Variant) -> void: + _bonus = v + + +func bonus() -> Variant: + return _bonus + + +func set_camp_pres(v: Variant) -> void: + _camp_pres = v + + +func camp_pres() -> Variant: + return _camp_pres + + +func set_debug(v: Variant) -> void: + _debug = v + + +func debug() -> Variant: + return _debug + + +func set_tune_sim(v: Variant) -> void: + _tune_sim = v + + +func tune_sim() -> Variant: + return _tune_sim + + +func set_confirm_consumer(v: String) -> void: + _confirm_consumer = v + + +func set_confirm_consumer_slot(v: int) -> void: + _confirm_consumer_slot = v + + +func replay_locked_click() -> void: 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 @@ -59,7 +109,7 @@ func _replay_locked_click() -> void: # 会话生命周期:_ready 建闲时态(永非 null——全组合根零空判责), # 开局/续玩/调试两层各建新会话,旧会话整节点 queue_free()(帧末回收;新节点先挂树无真空窗)。 -func _begin_session(adventure_session: bool) -> void: +func begin_session(adventure_session: bool) -> void: ui.screens.row_pacer.flush() # 会话更替即清队(待播行不住局态——机制保证,不靠时序约定) if run_scope != null: run_scope.queue_free() @@ -73,7 +123,7 @@ func _ready() -> void: if not db.is_ready: var ok: bool = db.load_tables("res://data/tables", false) if not ok: - _fatal_screen("数值表加载失败——见输出错误清单(fail-fast)") + fatal_screen("数值表加载失败——见输出错误清单(fail-fast)") return # SaveManager/MetaProgression 升格 autoload:读盘已在各自 _ready 完成 #(autoload 先于主场景就绪——读盘时序等价),此处零动作。 @@ -83,7 +133,7 @@ func _ready() -> void: settings_store.apply() ui = UiRegistry.new() # 注册面先于装配建(_build_ui 八步向容器登记——装配时序零变化) _build_ui() - _begin_session(false) # 闲时会话(gp_ledger 等探针注入面自 _ready 起恒在——ui_probe 合成段时序) + begin_session(false) # 闲时会话(gp_ledger 等探针注入面自 _ready 起恒在——ui_probe 合成段时序) ui.screens.start_overlay.visible = true ui.screens.debug_badge.visible = _debug_entry # 浮层角标可见性须门控落定后赋值(构建/连接在 _build_ui);_debug_entry 缺省 false=发布态恒隐 # boot 菜单优先——启动不自动开浮窗(开始屏不得被调试面板盖住); @@ -93,11 +143,44 @@ func _ready() -> void: # ==================== 视图接线(三屏壳 + 模态层装配;构建归屏壳/UiAssembler,本类只接线) ==================== func _build_ui() -> void: - MainUiBuild.build(self) + # 处理器束(pres封装 B 规则 2——on_* 处理器保私有,接线语句经束消费;键=用途名,main 内自引合法) + MainUiBuild.build(self, { + "board_action": _on_board_action, + "status_badge_pressed": _on_status_badge_pressed, + "stop_pressed": _on_stop_pressed, + "refresh_confirmed": _on_refresh_confirmed, + "legend_toggled": _on_legend_toggled, + "hover_forbidden": _on_hover_forbidden, + "chord_attempted": _on_chord_attempted, + "node_continue": _on_node_continue, + "route_chosen": _on_route_chosen, + "abandon_requested": _on_abandon_requested, + "battle_end_continue": _on_battle_end_continue, + "settings_closed": _on_settings_closed, + "status_closed": _on_status_closed, + "career_closed": _on_career_closed, + "save_select_closed": _on_save_select_closed, + "save_select_new": _on_save_select_new, + "save_select_load": _on_save_select_load, + "save_select_delete": _on_save_select_delete, + "debug_badge_pressed": _on_debug_badge_pressed, + "camp_object_pressed": _on_camp_object_pressed, + "inter_next_pressed": _on_inter_next_pressed, + "confirm_accepted": _on_confirm_accepted, + "confirm_secondary": _on_confirm_secondary, + "confirm_declined": _on_confirm_declined, + "esc_return": _on_esc_return, + "esc_settings": _on_esc_settings, + "esc_quit": _on_esc_quit, + "tune_apply": _on_tune_apply, + "tune_simulate": _on_tune_simulate, + "debug_battle_created": _on_debug_battle_created, + "debug_battle_ready": _on_debug_battle_ready, + }) ui.modals.menu.debug_overlay.diagnose_requested.connect(_on_diagnose_pressed) # 诊断转储入口 -func _fatal_screen(msg: String) -> void: +func fatal_screen(msg: String) -> void: MainUiBuild.fatal_screen(self, msg) @@ -122,13 +205,13 @@ func _on_confirm_accepted() -> void: save_manager.slots.delete_slot(_confirm_consumer_slot) ui.modals.menu.save_select_overlay.open_save_select(HudPresenter.save_select_data(save_manager)) # 就地刷新(该槽回空态) "gate": - _camp_pres.on_camp_closed() # 冒险发起后营地场景屏隐藏(选档层同收——卡④ T4 关层收闭) + camp_pres().on_camp_closed() # 冒险发起后营地场景屏隐藏(选档层同收——卡④ T4 关层收闭) _resume_from_menu() # 继续上一段冒险(现役续玩编排同源——卡④ 契约 4) "gate_new": - _camp_pres.on_camp_closed() + camp_pres().on_camp_closed() _start_adventure() # 开始新冒险(现役开局编排同源) "campfire_leave": - _camp_pres.on_camp_closed() # 篝火确认离场=营地与选档层同收+开始层恢复(回主菜单) + camp_pres().on_camp_closed() # 篝火确认离场=营地与选档层同收+开始层恢复(回主菜单) _confirm_consumer = "" @@ -137,7 +220,7 @@ func _on_confirm_secondary() -> void: ui.flow.close(ui.modals.menu.confirm_dialog) # 模态栈单口 match _confirm_consumer: "gate": - _camp_pres.on_camp_closed() + camp_pres().on_camp_closed() _start_adventure() _confirm_consumer = "" @@ -148,7 +231,7 @@ func _on_confirm_declined() -> void: # 退出游戏(一级菜单入口;设置即时落盘后退出) -func _quit_game() -> void: +func quit_game() -> void: if settings_store != null: settings_store.save_settings() get_tree().quit() @@ -161,23 +244,26 @@ func _resume_from_menu() -> void: var bp: Dictionary[Variant, Variant] = save_manager.breakpoint_snapshot() ui.flow.close(ui.modals.menu.debug_overlay) # 开冒险路径显式关浮窗(续玩同口径) ui.screens.start_overlay.visible = false - _begin_session(true) + begin_session(true) _wire_adventure() run_scope.level_active = true - _sync_status_entry() # 续玩恢复点亮状态面板入口角标 + sync_status_entry() # 续玩恢复点亮状态面板入口角标 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.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()})): - _fatal_screen("续玩恢复失败——见输出日志") + {"treasure_unlocked": treasure_ui().unlocked_set()})): + fatal_screen("续玩恢复失败——见输出日志") return func _wire_adventure() -> void: - MainAdventureFlow._wire_adventure(self) + MainAdventureFlow.wire_adventure(self, { + "adventure_finished": _on_adventure_finished, + "level_started": _on_adventure_level_started, + }) # 冒险开局参数面构造(主菜单开新/续玩两入口共用:同调三键随开局下传。 @@ -205,59 +291,66 @@ static func build_adventure_params(save_manager_ref: SaveManagerCore, meta_ref: # 信号连接只在组合根) func _start_adventure() -> void: ui.flow.close(ui.modals.menu.debug_overlay) # 开冒险路径显式关浮窗(调试浮窗默认开态下开局不带窗;对局中经角标再开) - _begin_session(true) + begin_session(true) _wire_adventure() run_scope.level_active = true - _sync_status_entry() # 进对局点亮状态面板入口角标 + sync_status_entry() # 进对局点亮状态面板入口角标 ui.modals.run.level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期) ui.battle.log_view.clear() - var seed_val := _seed_value() + var seed_val := seed_value() logger.start_session(seed_val) 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, - {"treasure_unlocked": _treasure_ui.unlocked_set(), "shop_mode": "pool"})): - _fatal_screen("冒险启动失败——见输出日志") + {"treasure_unlocked": treasure_ui().unlocked_set(), "shop_mode": "pool"})): + fatal_screen("冒险启动失败——见输出日志") return func _on_adventure_level_started(level_index: int, display_name: String)-> void: - MainAdventureFlow._on_adventure_level_started(self, level_index, display_name) + MainAdventureFlow.on_adventure_level_started(self, level_index, display_name, { + "step_row": _on_step_row, + "round_started": _on_round_started, + "board_changed": _on_board_changed, + "runner_node": _on_runner_node, + "choices_presented": _on_choices_presented, + "wallet_changed": _on_wallet_changed, + }) func _on_runner_node(node: Dictionary, info: Dictionary)-> void: - MainAdventureFlow._on_runner_node(self, node, info) + MainAdventureFlow.on_runner_node(self, node, info) func _on_adventure_finished(summary: Dictionary)-> void: - MainAdventureFlow._on_adventure_finished(self, summary) + MainAdventureFlow.on_adventure_finished(self, summary) func _on_battle_end_continue() -> void: - MainAdventureFlow._on_battle_end_continue(self) + MainAdventureFlow.on_battle_end_continue(self) -func _start_debug_levels() -> void: - MainDebugFlow._start_debug_levels(self) +func start_debug_levels() -> void: + MainDebugFlow.start_debug_levels(self) func _on_debug_battle_created(settlement_ref: BattleOrchestrator, _level: int, _carry: int)-> void: - MainDebugFlow._on_debug_battle_created(self, settlement_ref, _level, _carry) + MainDebugFlow.on_debug_battle_created(self, settlement_ref, _level, _carry) func _on_debug_battle_ready(settlement_ref: BattleOrchestrator, _level: int, _carry: int)-> void: - MainDebugFlow._on_debug_battle_ready(self, settlement_ref, _level, _carry) + MainDebugFlow.on_debug_battle_ready(self, settlement_ref, _level, _carry) func _on_inter_next_pressed() -> void: - MainAdventureFlow._on_inter_next_pressed(self) + MainAdventureFlow.on_inter_next_pressed(self) # 加成生效值供给 / 奖励徽章身份解析 / 地形变异两种解析 —— 见 board_resolvers.gd(有态绑定族判离) -func _show_board_fx_for_node(node_type: String = "") -> void: +func show_board_fx_for_node(node_type: String = "") -> void: # Boss 遭遇变体:紫赤影子爬棋盘一次性占位演出(§2.6) if node_type == "boss": ui.battle.board_zone.fx.play_boss_shadow() @@ -266,15 +359,15 @@ func _show_board_fx_for_node(node_type: String = "") -> void: func _on_battle_finished(payload: Dictionary) -> void: # 仅调试模式接线(冒险的关卡结束归 AdventureRunner._on_level_finished——大奖励/冒险终局分派) var result: String = String(payload.get("result", "")) - _sync_settlement_visuals() # → 全关(battle_over 反馈面) + sync_settlement_visuals() # → 全关(battle_over 反馈面) 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) + var decision: Dictionary[Variant, Variant] = debug().on_battle_finished(payload) match String(decision.outcome): "next_layer": ui.screens.inter_label.text = "第 %d 层通过!\nHP %d/%d 带入下一层" % [ int(decision.layer), int(decision.carry_hp), int(db.combat.hero.max_hp)] ui.screens.inter_overlay.visible = true - _refresh_presentations() + refresh_presentations() return "level_end": ui.modals.run.end_overlay.open_victory(Narrator.battle_end_text("victory", true), @@ -287,7 +380,7 @@ func _on_battle_finished(payload: Dictionary) -> void: ui.modals.run.end_overlay.open_defeat(Narrator.battle_end_text("defeat", false), "返回", ui.battle.board_zone.board_view.get_global_rect()) ui.flow.open_after_playback(ui.modals.run.end_overlay) - _refresh_presentations() + refresh_presentations() # 战利品行装配(battle.md OQ14——胜利总结结算展示 GP 总量+增量;纯呈现读数): @@ -297,7 +390,7 @@ func _on_battle_finished(payload: Dictionary) -> void: # resume_adventure 的 bonus_pending 分支在 level_started 之前同步 curio_choice_presented.emit # 〔resume 提前 return〕,本 lambda 求值时 runner 尚未赋值;合计改读断点快照 gp 兜底——恢复源快照 # 此刻必在档,语义=续玩恢复面的合计即存档合计;承接体见 HudPresenter.gp_summary_text) -func _gp_summary_text(payload: Dictionary) -> String: +func gp_summary_text(payload: Dictionary) -> String: return HudPresenter.gp_summary_text(payload, run_scope.runner, save_manager) @@ -306,12 +399,12 @@ func _gp_summary_text(payload: Dictionary) -> String: ## 战末账行束(open_victory opts 键装配单点——GP/钥石/加成三行与增量合计) func _end_victory_opts(payload: Dictionary) -> Dictionary: - return {"gp_summary": _gp_summary_text(payload), + return {"gp_summary": gp_summary_text(payload), "ks_summary": "钥石 · 累计 %d" % run_scope.runner.keystone, "bonus_summary": HudPresenter.bonus_summary_text(run_scope.runner), "gp_gain": int(payload.get("gp_dropped", 0)), "gp_total": int(run_scope.runner.gp)} -func _wire_battle(settlement_ref: BattleOrchestrator) -> void: +func wire_battle(settlement_ref: BattleOrchestrator) -> void: settlement_ref.step_row.connect(_on_step_row) settlement_ref.battle_finished.connect(_on_battle_finished) settlement_ref.round_started.connect(_on_round_started) @@ -363,7 +456,7 @@ func _on_round_started(ri: int) -> void: var vlines: Array[String] = HudPresenter.board_variant_lines(db, bvs) if not vlines.is_empty(): ui.battle.settlement_fx.play_board_variant_banner("地形变异 · " + " · ".join(vlines)) - _refresh_presentations() + refresh_presentations() func _on_board_changed() -> void: @@ -376,8 +469,8 @@ func _on_board_changed() -> void: ui.battle.board_zone.board_view.rebuild(board, run_scope.settlement.minesweeper) if board != null: ui.battle.stage.set_board_type(String(board.board_type)) # 舞台背景选图随板(C8——既有重建点单行同步) - _check_board_hints() - _refresh_presentations() + check_board_hints() + refresh_presentations() # 秘宝视野呈现桥:阔视野带 light_radius_bonus @@ -392,7 +485,7 @@ func _sync_treasure_glow() -> void: # ---- 一次性非模态提示(每 run 一次——pattern 10)与诚实提示 ---- -func _check_board_hints() -> void: +func check_board_hints() -> void: var board = run_scope.settlement.minesweeper.current_board() if board == null: return @@ -423,15 +516,15 @@ func _show_hint(text: String, hint_id: String) -> void: # ---- 节点覆盖层"继续"(奖励/恢复点兑付确认)与节点图选路覆盖层 ---- func _on_node_continue() -> void: - MainAdventureFlow._on_node_continue(self) + MainAdventureFlow.on_node_continue(self) func _on_choices_presented(candidates: Array)-> void: - MainAdventureFlow._on_choices_presented(self, candidates) + MainAdventureFlow.on_choices_presented(self, candidates) func _on_route_chosen(node_id: int)-> void: - MainAdventureFlow._on_route_chosen(self, node_id) + MainAdventureFlow.on_route_chosen(self, node_id) # ---- 独立屏屏级互斥(单一出口:进屏隐战斗屏 / 出屏显战斗屏)---- @@ -439,86 +532,86 @@ func _on_route_chosen(node_id: int)-> void: # 关闭自动恢复本屏(遮挡语义复用),战斗屏不在此期间回显。 func _enter_level_screen() -> void: - MainAdventureFlow._enter_level_screen(self) + MainAdventureFlow.enter_level_screen(self) func _exit_level_screen() -> void: - MainAdventureFlow._exit_level_screen(self) + MainAdventureFlow.exit_level_screen(self) func _on_abandon_requested() -> void: - MainAdventureFlow._on_abandon_requested(self) + MainAdventureFlow.on_abandon_requested(self) # ---- 最小设置面(入口/开合仿营地先例:开始层互斥;生效与持久化归 SettingsStore)---- -func _open_settings() -> void: - MainOverlayFlow._open_settings(self) +func open_settings() -> void: + MainOverlayFlow.open_settings(self) func _on_settings_closed() -> void: - MainOverlayFlow._on_settings_closed(self) + MainOverlayFlow.on_settings_closed(self) -func _status_data() -> Dictionary: - return MainOverlayFlow._status_data(self) +func status_data() -> Dictionary: + return MainOverlayFlow.status_data(self) func _open_status_panel() -> void: - MainOverlayFlow._open_status_panel(self) + MainOverlayFlow.open_status_panel(self) func _on_status_closed() -> void: - MainOverlayFlow._on_status_closed(self) + MainOverlayFlow.on_status_closed(self) # ---- 生涯页·只读(入口=开始层生涯钮;开合仿设置面先例:开始层互斥; # 数据快照随开随建零写通路,页内零跳转)---- func _open_career() -> void: - MainOverlayFlow._open_career(self) + MainOverlayFlow.open_career(self) func _on_career_closed() -> void: - MainOverlayFlow._on_career_closed(self) + MainOverlayFlow.on_career_closed(self) -func _open_save_select() -> void: - MainOverlayFlow._open_save_select(self) +func open_save_select() -> void: + MainOverlayFlow.open_save_select(self) func _on_save_select_closed() -> void: - MainOverlayFlow._on_save_select_closed(self) + MainOverlayFlow.on_save_select_closed(self) func _on_save_select_new(slot: int)-> void: - MainOverlayFlow._on_save_select_new(self, slot) + MainOverlayFlow.on_save_select_new(self, slot) func _on_save_select_load(slot: int)-> void: - MainOverlayFlow._on_save_select_load(self, slot) + MainOverlayFlow.on_save_select_load(self, slot) func _on_save_select_delete(slot: int)-> void: - MainOverlayFlow._on_save_select_delete(self, slot) + MainOverlayFlow.on_save_select_delete(self, slot) func _on_status_badge_pressed() -> void: - MainOverlayFlow._on_status_badge_pressed(self) + MainOverlayFlow.on_status_badge_pressed(self) -func _sync_status_entry() -> void: - MainOverlayFlow._sync_status_entry(self) +func sync_status_entry() -> void: + MainOverlayFlow.sync_status_entry(self) # ---- 探针缝保缝(click/ui_probe 营地段直呼组合根面;实现归 camp_shop_presenter.gd / main_overlay_flow.gd)---- func _on_camp_closed() -> void: - MainOverlayFlow._on_camp_closed(self) + MainOverlayFlow.on_camp_closed(self) func _on_camp_object_pressed(key: StringName) -> void: - MainOverlayFlow._on_camp_object_pressed(self, key) # 场景点选路由(卡④ T3——薄委托,实现归覆盖层流程承接件) + MainOverlayFlow.on_camp_object_pressed(self, key) # 场景点选路由(卡④ T3——薄委托,实现归覆盖层流程承接件) # Esc 输入入口(产品码首个键盘入口——卡⑤ T1 契约 9 接线定稿:事件驱动 _unhandled_input、 @@ -529,23 +622,23 @@ func _unhandled_input(event: InputEvent) -> void: func _on_esc_return() -> void: - MainEscFlow._on_return_to_menu(self) + MainEscFlow.on_return_to_menu(self) func _on_esc_settings() -> void: - MainEscFlow._on_settings(self) + MainEscFlow.on_settings(self) func _on_esc_quit() -> void: - MainEscFlow._on_quit(self) + MainEscFlow.on_quit(self) func _camp_data() -> Dictionary: - return MainOverlayFlow._camp_data(self) + return MainOverlayFlow.camp_data(self) func _on_wallet_changed(event: String, gp: int, extra: Dictionary)-> void: - MainOverlayFlow._on_wallet_changed(self, event, gp, extra) + MainOverlayFlow.on_wallet_changed(self, event, gp, extra) # ==================== 玩家意图 → 核心写调用(唯一出口)==================== @@ -565,9 +658,9 @@ func _on_board_action(cell: Vector2i, kind: String) -> void: return # 秘宝选中态劫持(两步交互第二步):点格=使用目标拾取,不落挖格; # 只劫持挖掘点击(move/seal 语义不冲突——选中期玩家仍可移动/封印) - var treasure_sel: int = _treasure_ui.active_selection() + var treasure_sel: int = treasure_ui().active_selection() if treasure_sel != 0 and kind == "dig": - _treasure_ui.attempt_use(treasure_sel, cell) + treasure_ui().attempt_use(treasure_sel, cell) return if run_scope.settlement == null: return # 入口显式空防护(双保险——锁谓词正常态已挡,防锁判据异常时崩于空引用) @@ -575,7 +668,7 @@ func _on_board_action(cell: Vector2i, kind: String) -> void: var accepted: bool = not outcomes.is_empty() and bool(outcomes[0].accepted) logger.log_action(kind, cell, accepted) if run_scope.runner != null and run_scope.runner.treasure_inventory != null: - _treasure_ui.refresh_slots() # 授予发生在 settle_batch 结算段内(同步走完,行尾重查) + treasure_ui().refresh_slots() # 授予发生在 settle_batch 结算段内(同步走完,行尾重查) _handle_outcome_feedback(outcomes[0] if not outcomes.is_empty() else {}) # settle_batch 内部同步走完 结算→战斗结束→关卡推进(事件同步发射)——关卡终局时 settlement 已被 # runner 置空,清图演出判定须先判 settlement(重入隐患守卫) @@ -591,7 +684,7 @@ func _on_board_action(cell: Vector2i, kind: String) -> void: elif accepted and kind == "move": ui.battle.board_zone.board_view.notify_hero_move() # 原地移动(目标=当前格幂等受理)帧闪回执——refresh diff 前置告知 ui.battle.board_zone.board_view.refresh() - _refresh_presentations() + refresh_presentations() func _on_stop_pressed() -> void: @@ -607,7 +700,7 @@ func _on_stop_pressed() -> void: ui.battle.board_zone.toast.show_toast("本轮已清图,停手未执行 · 剩余 AP 已作废", "warn") logger.log_ux("ux_reject", {"source": "voided", "sub_reason": "stop_voided"}) ui.battle.board_zone.board_view.refresh() - _refresh_presentations() + refresh_presentations() func _on_refresh_confirmed() -> void: @@ -635,7 +728,7 @@ func _on_refresh_confirmed() -> void: else: ui.battle.board_zone.toast.show_toast("本轮刷新次数已用完(每轮 %d 次)" % int(db.run.action_economy.refresh_cap_per_round), "warn") ui.battle.board_zone.board_view.refresh() - _refresh_presentations() + refresh_presentations() func _handle_outcome_feedback(outcome: Dictionary) -> void: @@ -659,15 +752,15 @@ func _refresh_used_snapshot() -> int: return int(run_scope.settlement.presentation_snapshot().get("refresh_used", 0)) -func _refresh_presentations() -> void: +func refresh_presentations() -> void: # 每个核心信号/写调用返回后的全量重查装配(更新触发注记——无推送信号,命令式重查) var refresh_remaining: int = maxi(0, int(db.run.action_economy.refresh_cap_per_round) - _refresh_used_snapshot()) 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() + treasure_ui().refresh_slots() # C 区秘宝槽重查(试炼镐积点进度随挖格刷新悬停浮窗) + sync_settlement_visuals() -func _sync_settlement_visuals() -> void: +func sync_settlement_visuals() -> void: # 结算阶段(round_end → 下一轮):D 区去饱和+金橙偏移锁输入(明度不变)+ 明细带展开(高度读 battle_layout) var settling := _is_settling() if settling and not run_scope.was_settling: @@ -697,56 +790,56 @@ func _is_settling() -> bool: # ==================== B 区明细带 / 图例(模态入口)==================== func _on_legend_toggled() -> void: - MainOverlayFlow._on_legend_toggled(self) + MainOverlayFlow.on_legend_toggled(self) # ==================== 诊断转储(卡死时点击——全量核心状态 + 环形缓冲)==================== func _on_diagnose_pressed() -> void: - MainDebugFlow._on_diagnose_pressed(self) + MainDebugFlow.on_diagnose_pressed(self) # ==================== 调试浮窗(开合/应用/模拟编排由组合根承接)==================== func _show_debug_overlay() -> void: - MainDebugFlow._show_debug_overlay(self) + MainDebugFlow.show_debug_overlay(self) func _on_debug_badge_pressed() -> void: - MainDebugFlow._on_debug_badge_pressed(self) + MainDebugFlow.on_debug_badge_pressed(self) -func _sync_debug_overlay() -> void: - MainDebugFlow._sync_debug_overlay(self) +func sync_debug_overlay() -> void: + MainDebugFlow.sync_debug_overlay(self) func _debug_run_data() -> Dictionary: - return MainDebugFlow._debug_run_data(self) + return MainDebugFlow.debug_run_data(self) # 本局种子现值(只读单点——当局数据区块与诊断转储共用):冒险=adventure.master_seed 访问器现值; -# 调试两层=RNG 服务 master_seed。禁 _seed_value() 重算冒充本局种子。 -func _current_master_seed() -> int: +# 调试两层=RNG 服务 master_seed。禁 seed_value() 重算冒充本局种子。 +func current_master_seed() -> int: if run_scope.adventure != null: return int(run_scope.adventure.master_seed) return int(get_node(AUTOLOAD_PATH_RNG).master_seed) func _close_debug_overlay() -> void: - MainDebugFlow._close_debug_overlay(self) + MainDebugFlow.close_debug_overlay(self) func _on_tune_apply(atk_pct: int, hp_pct: int)-> void: - MainDebugFlow._on_tune_apply(self, atk_pct, hp_pct) + MainDebugFlow.on_tune_apply(self, atk_pct, hp_pct) func _on_tune_simulate(n: int)-> void: - MainDebugFlow._on_tune_simulate(self, n) + MainDebugFlow.on_tune_simulate(self, n) # ==================== 工具 ==================== -func _seed_value() -> int: +func seed_value() -> int: # 种子三分判定单点(全工程唯一种子框读取处,禁第二处读框): # ①显式合法整数全场景优先(含首局——测试/调试接口显式指定;第五路 DebugSession Callable # 经本函数=调试对局与玩家同口径); diff --git a/game/presentation/main_adventure_flow.gd b/game/presentation/main_adventure_flow.gd index 8eedfa9f..7c4a3de2 100644 --- a/game/presentation/main_adventure_flow.gd +++ b/game/presentation/main_adventure_flow.gd @@ -7,22 +7,22 @@ extends RefCounted # 冒险编排构建+接线(开新局/续玩共用——信号连接只在组合根;断点注入同口)。 # 信号直连(拆件批 T3):runner 六信号在 level_started 重取点重连(旧实例连接随引用释放消亡—— # is_connected 守卫恰好一次);三选一信号面归停点件(每冒险一次——接线恰一次)。 -static func _wire_adventure(root: Variant) -> void: +static func wire_adventure(root: Variant, handlers: Dictionary) -> void: root.run_scope.adventure = AdventureRunner.new(root.db, root.get_node(root.AUTOLOAD_PATH_RNG)) - root.run_scope.adventure.adventure_finished.connect(root._on_adventure_finished) - root.run_scope.adventure.level_started.connect(root._on_adventure_level_started) - root.run_scope.adventure.stop().curio_choice_presented.connect(root._bonus.on_presented) - root.run_scope.adventure.stop().curio_resolved.connect(root._bonus.on_resolved) + root.run_scope.adventure.adventure_finished.connect(handlers["adventure_finished"]) + root.run_scope.adventure.level_started.connect(handlers["level_started"]) + root.run_scope.adventure.stop().curio_choice_presented.connect(root.bonus().on_presented) + root.run_scope.adventure.stop().curio_resolved.connect(root.bonus().on_resolved) root.run_scope.adventure.set_save_manager(root.save_manager) # 断点承载注入(停点快照写盘;调试/模拟不经过此路径) # 每关开始(冒险模式):重取 runner 实例 + 层名行(L1 与单关模式同序——层名行随首战横幅后到达; # 本回调返回后才有 choices_presented 到达——入口选路层随即弹出) -static func _on_adventure_level_started(root: Variant, level_index: int, display_name: String) -> void: +static func on_adventure_level_started(root: Variant, level_index: int, display_name: String, handlers: Dictionary) -> void: root.run_scope.runner = root.run_scope.adventure.runner() # runner 信号直连(拆件批 T3——六转发族退役后的重取点重连;恰好一次断言 sc1 守卫承载) - _connect_runner_once(root) + _connect_runner_once(root, handlers) # 开场行传当前生效值(runner.effective_atk/effective_max_hp 与战斗面板同源: # 表值+永久强化 flat+加算区 flat 后乘调试浮窗 pct,本回调时点 runner 已带全部加成);「含面板加成」 # 后缀判断认任一加成源——永久强化 atk/max_hp flat 或调试浮窗 pct 或局内加算区 atk/max_hp @@ -37,22 +37,22 @@ static func _on_adventure_level_started(root: Variant, level_index: int, display root.run_scope.runner.effective_atk(), root.run_scope.runner.effective_max_hp(), boosted)) # runner 六信号重连(is_connected 守卫——未连即接;守卫失败 push_error=重连恰好一次契约承载) -static func _connect_runner_once(root: Variant) -> void: +static func _connect_runner_once(root: Variant, handlers: Dictionary) -> void: var r = root.run_scope.runner if r == null: push_error("[冒险流程] runner 信号重连失败——level_started 后 runner 为空") return var pairs := [ - [r.step_row, root._on_step_row], [r.round_started, root._on_round_started], - [r.board_changed, root._on_board_changed], [r.node_entered, root._on_runner_node], - [r.choices_presented, root._on_choices_presented], [r.wallet_changed, root._on_wallet_changed], + [r.step_row, handlers["step_row"]], [r.round_started, handlers["round_started"]], + [r.board_changed, handlers["board_changed"]], [r.node_entered, handlers["runner_node"]], + [r.choices_presented, handlers["choices_presented"]], [r.wallet_changed, handlers["wallet_changed"]], ] for pair in pairs: if not (pair[0] as Signal).is_connected(pair[1]): (pair[0] as Signal).connect(pair[1]) -static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -> void: +static func on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -> void: # 冒险模式:runner 逐关更替——每次节点事件重取(单关模式幂等);须先于 board_changed 回退读面 if root.run_scope.is_adventure and root.run_scope.adventure != null: root.run_scope.runner = root.run_scope.adventure.runner() @@ -61,12 +61,12 @@ static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) - root.run_scope.settlement = root.run_scope.runner.current_battle() # 呈现层统一读 settlement(与调试模式共用接线) root.logger.attach_battle(root.run_scope.settlement) root.run_scope.boss_active = t == "boss" - root._show_board_fx_for_node(t) + root.show_board_fx_for_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.bonus().present_node_curio() root.ui.battle.log_view.append_bb(root.Narrator.reward_line()) elif t == "rest": var healed: int = int(info.get("healed", 0)) @@ -87,20 +87,20 @@ static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) - 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() + root.refresh_presentations() # ---- 冒险模式终局面(三选一选择面 / 冒险结算屏)---- # 冒险结算(completed/failed/abandoned):复用 BattleEndOverlay——摘要五件事 + 钥石入账明细进文案 -static func _on_adventure_finished(root: Variant, summary: Dictionary) -> void: +static func on_adventure_finished(root: Variant, summary: Dictionary) -> void: root.run_scope.level_active = false var result: String = String(summary.get("result", "")) root.run_scope.settlement = null - _exit_level_screen(root) # 终局兜底:独立屏在开态(放弃入口)也先回战斗屏呈现摘要——屏级互斥单一出口 - root._sync_status_entry() # 离局熄灭状态面板入口角标(防菜单误现) - root._sync_settlement_visuals() # → 全关 + exit_level_screen(root) # 终局兜底:独立屏在开态(放弃入口)也先回战斗屏呈现摘要——屏级互斥单一出口 + root.sync_status_entry() # 离局熄灭状态面板入口角标(防菜单误现) + root.sync_settlement_visuals() # → 全关 HudPresenter.battle_end_feedback(root.ui.battle.stage, root.ui.battle.board_zone.ap_bar, false) # 灰化/idle 回正/状态全清——HUD 单通道 # 存档入账:settle = Boss 两档入账 + 工坊解锁 + 摘要入档 + 写盘(唯一写档点不变—— # 调试两层不经过本处理器,天然不写档不污染生涯数据)。 @@ -108,7 +108,7 @@ static func _on_adventure_finished(root: Variant, summary: Dictionary) -> void: detail.assign(root.meta.settle_adventure(summary)) var text: String = root.Narrator.adventure_summary_text(summary) if not detail.is_empty(): - text += "\n" + root.Narrator.keystone_settle_text(detail, root._bonus.item_name_lookup()) + text += "\n" + root.Narrator.keystone_settle_text(detail, root.bonus().item_name_lookup()) if result == "completed": 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)——三行账 @@ -118,52 +118,52 @@ static func _on_adventure_finished(root: Variant, summary: Dictionary) -> void: if root.run_scope.runner != null: root.run_scope.runner.restore_hero_base() root.ui.flow.open_after_playback(root.ui.modals.run.end_overlay) - root._refresh_presentations() + root.refresh_presentations() -static func _on_battle_end_continue(root: Variant) -> void: +static func on_battle_end_continue(root: Variant) -> void: # 终局出口:返回开始界面(冒险摘要留档试玩日志;钥石在收束时入存档) 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) + if root.bonus().resume_if_pending(): # 三选一停点在途恢复入口(curio_interact_controller) return # 挂起分派:战斗胜利总结结算框的「下一节点」≠ 终局——进选路页(暂存候选恢复呈现) if root.run_scope.settlement_stop_timer.is_armed(): root.run_scope.settlement_stop_timer.disarm() - _enter_level_screen(root) + enter_level_screen(root) 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() + root.refresh_presentations() return # 会话收束:run_scope 整节点 queue_free()(帧末回收)——新闲时会话即建, # 局态封印/账本/模态挂起全复位(局态字段清单见 run_scope.gd) - root._debug.reset() - root._begin_session(false) + root.debug().reset() + root.begin_session(false) root.ui.modals.menu.debug_overlay.reset_sliders() root.ui.screens.start_overlay.visible = true - root._refresh_presentations() + root.refresh_presentations() -static func _on_inter_next_pressed(root: Variant) -> void: +static func on_inter_next_pressed(root: Variant) -> void: 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(): - root._fatal_screen("关卡推进失败——见输出日志") + root.fatal_screen("关卡推进失败——见输出日志") return - if not root._debug.next_layer(): - root._fatal_screen("战斗启动失败——见输出日志") + if not root.debug().next_layer(): + root.fatal_screen("战斗启动失败——见输出日志") -static func _on_node_continue(root: Variant) -> void: +static func on_node_continue(root: Variant) -> void: 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() -static func _on_choices_presented(root: Variant, candidates: Array) -> void: +static func on_choices_presented(root: Variant, candidates: Array) -> void: # 零自动推进的停点呈现:整幅节点图 + 图上点击选路(战斗胜利/兑付确认统一到达此处) if root.run_scope.runner == null: return @@ -180,38 +180,38 @@ static func _on_choices_presented(root: Variant, candidates: Array) -> void: 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.flow.open_after_playback(root.ui.modals.run.end_overlay) - root._refresh_presentations() + root.refresh_presentations() return - _enter_level_screen(root) + enter_level_screen(root) 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() + root.refresh_presentations() -static func _on_route_chosen(root: Variant, node_id: int) -> void: +static func on_route_chosen(root: Variant, node_id: int) -> void: # 视图已按节点类型定去留——战斗/Boss 出屏(visible=false,此处收栈 # 记录一致性+回战斗屏);奖励/恢复/商店本屏保持激活(兑付覆盖层由节点事件域内弹出,模态栈 # 打开低层时自动隐藏本屏、关闭自动恢复) if not root.ui.modals.run.level_screen.visible: root.ui.flow.close(root.ui.modals.run.level_screen) - _exit_level_screen(root) + 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) -static func _enter_level_screen(root: Variant) -> 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.flow.open(root.ui.modals.run.level_screen) - root._sync_status_entry() # 屏级互斥切换同步(_open_modal 单口不识屏级状态) + root.sync_status_entry() # 屏级互斥切换同步(_open_modal 单口不识屏级状态) -static func _exit_level_screen(root: Variant) -> void: +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.run.level_screen.visible: root.ui.flow.close(root.ui.modals.run.level_screen) - root._sync_status_entry() # 同上 + root.sync_status_entry() # 同上 # 选路屏状态栏装配(视图零核心态拼装——architecture §1.3:读核心态→拼展示文案集中于装配器)。 @@ -243,7 +243,7 @@ static func level_map_status_payload(runner: LevelRunner) -> Dictionary: "bonus_row_visible": not parts.is_empty(), } -static func _on_abandon_requested(root: Variant) -> void: +static func on_abandon_requested(root: Variant) -> void: # 放弃入口(冒险结构.md 已放开「MVP 无界面入口」后置条款; # 入口挂选路停点 = 天然非战斗期,拒绝分支仅防御在案):受理 → 冒险放弃结算(同失败型, # 已获大奖励不追回)→ adventure_finished 同步到达 → 终局面收层 diff --git a/game/presentation/main_debug_flow.gd b/game/presentation/main_debug_flow.gd index 4dce4189..6461d5e5 100644 --- a/game/presentation/main_debug_flow.gd +++ b/game/presentation/main_debug_flow.gd @@ -4,9 +4,9 @@ ## 调试会话状态机归 debug_session.gd;本件只接线与出 UI,零成员态。 extends RefCounted -static func _on_diagnose_pressed(root: Variant) -> void: +static func on_diagnose_pressed(root: Variant) -> void: # 本局种子现值走 _current_master_seed 只读单点 - var ctx := {"mode": "level" if root.run_scope.level_active else "debug", "seed": root._current_master_seed()} + var ctx := {"mode": "level" if root.run_scope.level_active else "debug", "seed": root.current_master_seed()} if root.run_scope.level_active and root.run_scope.runner != null: ctx["runner"] = {"node_index": root.run_scope.runner.node_index, "level_result": String(root.run_scope.runner.level_result), "hero_hp": root.run_scope.runner.hero_hp, "settlement_is_null": root.run_scope.runner.current_battle() == null} @@ -17,33 +17,33 @@ static func _on_diagnose_pressed(root: Variant) -> void: -static func _show_debug_overlay(root: Variant) -> void: +static func show_debug_overlay(root: Variant) -> void: # 门禁:对局中可开;本函数仍是唯一开窗入口,门禁逻辑收函数内: # 棋盘输入锁/状态角标判据排除浮窗, # 遮挡语义=ModalStack 登记制(他模态开时暂隐、关后自动恢复),此处零场景限制 if root.ui.modals.menu.debug_overlay == null: return root.ui.flow.open(root.ui.modals.menu.debug_overlay) - _sync_debug_overlay(root) + sync_debug_overlay(root) -# 调试浮窗角标点击(两屏同款处理器——与状态角标同形制守卫;仍收口 _show_debug_overlay 单口) -static func _on_debug_badge_pressed(root: Variant) -> void: +# 调试浮窗角标点击(两屏同款处理器——与状态角标同形制守卫;仍收口 show_debug_overlay 单口) +static func on_debug_badge_pressed(root: Variant) -> void: 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) + show_debug_overlay(root) # 浮窗上下文同步(当局数据刷新+对局中按钮禁用单点):调用面=开窗口与模态开合 # 单口(凡影响开态/对局态的路径必经其一——与 _sync_status_entry 同款纪律) -static func _sync_debug_overlay(root: Variant) -> void: +static func sync_debug_overlay(root: Variant) -> void: if root.ui.modals.menu.debug_overlay == null: return 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)) # 当局数据只读区块喂入(开窗+模态开合信号驱动) + root.ui.modals.menu.debug_overlay.update_run_data(debug_run_data(root)) # 当局数据只读区块喂入(开窗+模态开合信号驱动) @@ -51,9 +51,9 @@ static func _sync_debug_overlay(root: Variant) -> void: # (非 _seed_value() 重算);调试两层/非对局无冒险对象=RNG 服务 master_seed 现值(只读出口, # 未播种 -1 显「—」);关卡/节点/HP/GP=runner 成文出口、钥石=adventure 冒险账本(LevelRunner 无此域); # 缺位=「—」占位。零动效(纯标签重查)。 -static func _debug_run_data(root: Variant) -> Dictionary: +static func debug_run_data(root: Variant) -> Dictionary: var r = root.run_scope.adventure.runner() if root.run_scope.adventure != null else null - var seed_now: int = root._current_master_seed() + var seed_now: int = root.current_master_seed() var seed_txt: String = str(seed_now) if seed_now >= 0 else "—" if r == null: return {"seed": seed_txt, "level": "—", "node": "—", "hp": "—", "gp": "—", "keystone": "—"} @@ -62,47 +62,47 @@ static func _debug_run_data(root: Variant) -> Dictionary: -static func _close_debug_overlay(root: Variant) -> void: +static func close_debug_overlay(root: Variant) -> void: root.ui.flow.close(root.ui.modals.menu.debug_overlay) # 模态栈单口——开始层全程未隐藏,无需恢复 -static func _on_tune_apply(root: Variant, atk_pct: int, hp_pct: int) -> void: +static func on_tune_apply(root: Variant, atk_pct: int, hp_pct: int) -> void: # 加成无持久态可"落"——各战斗开局经 board_resolvers.debug_hero_override/runner 现算生效(应用=关闭浮窗) - _close_debug_overlay(root) + close_debug_overlay(root) -static func _on_tune_simulate(root: Variant, n: int) -> void: +static func on_tune_simulate(root: Variant, n: int) -> void: # 批量模拟编排 = 控制器承接(tune_sim_controller.gd);E4 后无表态需复位——模拟走 runner 覆盖参数。 # 对局中禁用兜底(长程异步模拟与活跃冒险/结算并存态不开放;探针 emit 绕过 disabled 时由此守住) if root.run_scope.settlement != null or root.run_scope.is_adventure: return - root._tune_sim.run(n) + root.tune_sim().run(n) -static func _start_debug_levels(root: Variant) -> void: +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(): + if root.run_scope.is_adventure or root.run_scope.settlement != null or root.tune_sim().is_running(): return root.ui.modals.menu.debug_overlay.reset_sliders() - root.ui.flow.close(root.ui.modals.menu.debug_overlay) # 浮窗内入口发起——随开随收(对局期不可再开:门禁在 _show_debug_overlay) + 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)) - root._begin_session(false) - if not root._debug.start_debug_levels(): - root._fatal_screen("战斗启动失败——见输出日志") + 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("战斗启动失败——见输出日志") -static func _on_debug_battle_created(root: Variant, settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void: +static func on_debug_battle_created(root: Variant, settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void: # begin_battle 前接手 + 接线(signal.connect 唯一归组合根)。settlement 必须先于 begin_battle # 就位——begin 期间发出的 board_changed 要用新板重建棋盘并触发一次性提示(缺陷①同款时序义务) root.run_scope.settlement = settlement_ref - root._wire_battle(settlement_ref) + root.wire_battle(settlement_ref) -static func _on_debug_battle_ready(root: Variant, settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void: +static func on_debug_battle_ready(root: Variant, settlement_ref: BattleOrchestrator, _level: int, _carry: int) -> void: root.run_scope.settlement = settlement_ref var board = root.run_scope.settlement.minesweeper.current_board() root.ui.battle.board_zone.board_view.rebuild(board, root.run_scope.settlement.minesweeper) if board != null: root.ui.battle.stage.set_board_type(String(board.board_type)) # 舞台背景选图随板(C8——调试就绪重建点单行同步) - root._check_board_hints() - root._refresh_presentations() + root.check_board_hints() + root.refresh_presentations() diff --git a/game/presentation/main_esc_flow.gd b/game/presentation/main_esc_flow.gd index 40cfc2a1..2dbb465f 100644 --- a/game/presentation/main_esc_flow.gd +++ b/game/presentation/main_esc_flow.gd @@ -41,16 +41,16 @@ static func _topmost_camp_window(root: Variant) -> Control: ## 返回主菜单动线(契约 4):首步 close 浮窗(缺此步浮窗残留主菜单上方)→营地与选档层同收+开始层恢复 -static func _on_return_to_menu(root: Variant) -> void: +static func on_return_to_menu(root: Variant) -> void: root.ui.flow.close(root.ui.modals.camp.esc_overlay) - root._camp_pres.on_camp_closed() # 关层收闭单口(卡④ T3——营地层与选档层同收+开始层恢复) + root.camp_pres().on_camp_closed() # 关层收闭单口(卡④ T3——营地层与选档层同收+开始层恢复) ## 设置出口(浮窗登记在设置层之下——open 只隐上方层,浮窗由树序遮挡非属性隐藏;关后重现=栈语义) -static func _on_settings(root: Variant) -> void: +static func on_settings(root: Variant) -> void: root.ui.flow.open(root.ui.modals.menu.settings_overlay) ## 退出出口(_quit_game 原样复用——settings_store.save_settings+quit,零语义副作用) -static func _on_quit(root: Variant) -> void: - root._quit_game() +static func on_quit(root: Variant) -> void: + root.quit_game() diff --git a/game/presentation/main_overlay_flow.gd b/game/presentation/main_overlay_flow.gd index 4a9e4e37..33504d23 100644 --- a/game/presentation/main_overlay_flow.gd +++ b/game/presentation/main_overlay_flow.gd @@ -4,11 +4,11 @@ ## 全部开合走组合根模态编排口(root.ui.flow.open/close);本件零成员态。 extends RefCounted -static func _open_settings(root: Variant) -> void: +static func open_settings(root: Variant) -> void: root.ui.screens.start_overlay.visible = false root.ui.flow.open(root.ui.modals.menu.settings_overlay) # 模态栈单口 -static func _on_settings_closed(root: Variant) -> void: +static func on_settings_closed(root: Variant) -> void: root.ui.flow.close(root.ui.modals.menu.settings_overlay) # 模态栈单口 # 语境分支(卡⑤ 契约 3——谓词=camp_esc_overlay.visible,谁开的设置谁收): # 营地浮窗路径关设置回浮窗不回主菜单(栈语义:浮窗登记在下、由树序遮挡非属性隐藏); @@ -25,24 +25,24 @@ static func _on_settings_closed(root: Variant) -> void: # 八键换代(迭代 36 卡③):跨关解锁概览退役(营地/生涯承载)——只留本局信息; # ledger 直取下标(键缺失=致命,禁 .get 缺省留门——fail-fast 红线);禁 null 守卫软回退 # (两入口开层时 runner 恒在役——状态面板卡 F3 证据链) -static func _status_data(root: Variant) -> Dictionary: - return HudPresenter.status_data(root.db, root.meta, root.run_scope.runner, root._treasure_ui) +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: +static func open_status_panel(root: Variant) -> void: root.ui.flow.open(root.ui.modals.run.status_panel) # 模态栈单口(开层前排空排播队列——既有层同款行为,目检确认项) - root.ui.modals.run.status_panel.open_status(_status_data(root)) + root.ui.modals.run.status_panel.open_status(status_data(root)) -static func _on_status_closed(root: Variant) -> void: +static func on_status_closed(root: Variant) -> void: root.ui.flow.close(root.ui.modals.run.status_panel) # 模态栈单口 -static func _open_career(root: Variant) -> void: +static func open_career(root: Variant) -> void: root.ui.screens.start_overlay.visible = false 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: +static func on_career_closed(root: Variant) -> void: 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 @@ -54,32 +54,32 @@ static func _on_career_closed(root: Variant) -> void: # 篝火回主菜单必须二次确认(camp.md v2.2 裁定);确认后营地层与选档层同收+开始层恢复(关层收闭)。 -static func _on_camp_object_pressed(root: Variant, key: StringName) -> void: +static func on_camp_object_pressed(root: Variant, key: StringName) -> void: match key: &"forge": - root._camp_pres.open_camp_forge() + root.camp_pres().open_camp_forge() &"chest": - root._camp_pres.open_camp_treasure() + root.camp_pres().open_camp_treasure() &"dummy": - root._camp_pres.open_camp_tree() + root.camp_pres().open_camp_tree() &"board": - root._camp_pres.open_camp_codex() + root.camp_pres().open_camp_codex() &"notebook": - _open_career(root) # 生涯页入口改挂帐篷旁笔记本(CareerOverlay 本体零改——卡④ T3) + open_career(root) # 生涯页入口改挂帐篷旁笔记本(CareerOverlay 本体零改——卡④ T3) &"gate": _open_gate_confirm(root) &"campfire": - root._confirm_consumer = "campfire_leave" + root.set_confirm_consumer("campfire_leave") 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.set_confirm_consumer("gate") root.ui.modals.menu.confirm_dialog.open_three("有未完成的冒险——继续上一段冒险,还是开始新冒险?", "继续上一段冒险", "开始新冒险", "取消") return - root._confirm_consumer = "gate_new" + root.set_confirm_consumer("gate_new") root.ui.modals.menu.confirm_dialog.open("是否开始新冒险?", "是", "否") @@ -91,12 +91,12 @@ static func _open_gate_confirm(root: Variant) -> void: # 开层(模态栈单口;与开始层互斥=career 同款先例)。「进入存档」钮归卡② mn3——本函数为 # 该钮的唯一落点(卡③ 批内探针函数直调=中间态,实现报告「已知中间态」登记)。 -static func _open_save_select(root: Variant) -> void: +static func open_save_select(root: Variant) -> void: root.ui.screens.start_overlay.visible = false 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: +static func on_save_select_closed(root: Variant) -> void: root.ui.flow.close(root.ui.modals.menu.save_select_overlay) # 模态栈单口 root.ui.screens.start_overlay.visible = true @@ -107,24 +107,24 @@ static func _on_save_select_closed(root: Variant) -> void: # 空槽新档链(卡④ T3 换代:落点=营地场景根——CampOverlay 随四柜台拆解退役):卡① 创建=激活=白板落盘, # 落点开层。动线序(禁先关后开):先落点开层(开始层保持隐藏),选档层被模态栈遮挡隐藏、不关闭。 -static func _on_save_select_new(root: Variant, slot: int) -> void: +static func on_save_select_new(root: Variant, slot: int) -> void: root.save_manager.slots.create_slot(slot) - root._camp_pres.open_camp() + root.camp_pres().open_camp() # 有档槽进档链(契约 8 进档即激活):卡① 激活+读盘(兜底/拒读链按槽成立)→落点=营地场景根(卡④ T3)。 -static func _on_save_select_load(root: Variant, slot: int) -> void: +static func on_save_select_load(root: Variant, slot: int) -> void: root.save_manager.slots.activate_slot(slot) - root._camp_pres.open_camp() + root.camp_pres().open_camp() # 删档链(契约 3):第一次点击只出确认窗(消费方编排——open 前设消费方=delete_slot), # 确认才删、取消不删;禁长按删/一次点击即删/撤销式软删。 -static func _on_save_select_delete(root: Variant, slot: int) -> void: - root._confirm_consumer = "delete_slot" - root._confirm_consumer_slot = slot +static func on_save_select_delete(root: Variant, slot: int) -> void: + root.set_confirm_consumer("delete_slot") + root.set_confirm_consumer_slot(slot) root.ui.flow.open(root.ui.modals.menu.confirm_dialog) # 模态栈单口(显示编排/挂起兜底/遮挡记账——与其余覆盖层同口) root.ui.modals.menu.confirm_dialog.open("删除存档位 %d?删除后该档不可恢复。" % slot, "删除", "取消") @@ -132,20 +132,20 @@ 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: +static func on_status_badge_pressed(root: Variant) -> void: 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.flow.badge_greyed_map()) or (not map_on and root.ui.flow.badge_greyed_battle()): return - _open_status_panel(root) + open_status_panel(root) # 入口角标可见性/置灰同步(P1 丁+守卫——单一同步点):战斗屏角标=对局内且地图屏未激活; # 地图屏角标=随屏;置灰=登记册上除地图屏自身外任一层开着(防「看得见点不着」态)。 # 调用面=模态开合单口与屏级互斥口(凡影响开态/屏态的路径必经其一)。 -static func _sync_status_entry(root: Variant) -> void: +static func sync_status_entry(root: Variant) -> void: 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.run.level_screen.visible @@ -160,13 +160,13 @@ static func _sync_status_entry(root: Variant) -> void: -static func _on_camp_closed(root: Variant) -> void: - root._camp_pres.on_camp_closed() +static func on_camp_closed(root: Variant) -> void: + root.camp_pres().on_camp_closed() -static func _camp_data(root: Variant) -> Dictionary: - return HudPresenter.camp_data(root._camp_pres) +static func camp_data(root: Variant) -> Dictionary: + return HudPresenter.camp_data(root.camp_pres()) -static func _on_wallet_changed(root: Variant, event: String, gp: int, extra: Dictionary) -> void: +static func on_wallet_changed(root: Variant, event: String, gp: int, extra: Dictionary) -> void: # wallet 行族转发(到账/购买/清零——试玩日志验收取数面)+ 常驻余额与在开商店覆盖层重刷 root.logger.log_wallet(event, gp, extra) if String(event) == "gain": @@ -176,11 +176,11 @@ static func _on_wallet_changed(root: Variant, event: String, gp: int, extra: Dic root.ui.modals.menu.shop_overlay.refresh_shelf_state(int(root.run_scope.runner.gp)) # pool 面余额联动(货架内容仅买后重抽变) else: root.ui.modals.menu.shop_overlay.refresh_state(root.run_scope.runner.shop.shop_products(), int(root.run_scope.runner.gp)) - root._refresh_presentations() + root.refresh_presentations() -static func _on_legend_toggled(root: Variant) -> void: +static func on_legend_toggled(root: Variant) -> void: if root.ui.modals.run.legend_overlay.is_open(): root.ui.flow.close(root.ui.modals.run.legend_overlay) else: diff --git a/game/presentation/main_ui_build.gd b/game/presentation/main_ui_build.gd index fb628fff..3ecb916e 100644 --- a/game/presentation/main_ui_build.gd +++ b/game/presentation/main_ui_build.gd @@ -18,14 +18,14 @@ const AdventureScreenScene := preload("res://presentation/scenes/adventure_scree -static func build(root: Variant) -> void: +static func build(root: Variant, handlers: Dictionary) -> void: _build_screens(root) - _build_battle_wiring(root) - _build_overlays(root) - _build_aux_panels(root) + _build_battle_wiring(root, handlers) + _build_overlays(root, handlers) + _build_aux_panels(root, handlers) _build_curio(root) - _build_menu_wiring(root) - _build_debug_and_resolvers(root) + _build_menu_wiring(root, handlers) + _build_debug_and_resolvers(root, handlers) _build_modal_flow(root) static func _build_screens(root: Variant) -> void: @@ -48,7 +48,7 @@ static func _build_screens(root: Variant) -> void: root.ui.battle.settlement_fx = root.ui.screens.adventure_screen.settlement_fx root.ui.modals.run.level_screen = root.ui.screens.adventure_screen.level_screen -static func _build_battle_wiring(root: Variant) -> void: +static func _build_battle_wiring(root: Variant, handlers: Dictionary) -> void: # 结算行排播泵(本体在 row_pacer.gd;本类注入视图句柄与呈现刷新回调,零信号连接) root.ui.screens.row_pacer = RowPacer.new() root.ui.screens.row_pacer.log_view = root.ui.battle.log_view @@ -56,32 +56,32 @@ static func _build_battle_wiring(root: Variant) -> void: 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.refresh_request = root.refresh_presentations 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 每战新建编排器,现值取件防悬挂) - root._treasure_ui = TreasureInteractController.new({ + root.set_treasure_ui(TreasureInteractController.new({ "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.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.ui.battle.board_zone.action_requested.connect(handlers["board_action"]) + root.ui.battle.board_zone.status_badge.pressed.connect(handlers["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(handlers["stop_pressed"]) + root.ui.battle.board_zone.refresh_confirmed.connect(handlers["refresh_confirmed"]) + root.ui.battle.board_zone.legend_toggled.connect(handlers["legend_toggled"]) + root.ui.battle.board_zone.hover_forbidden.connect(handlers["hover_forbidden"]) + root.ui.battle.board_zone.chord_attempted.connect(handlers["chord_attempted"]) root.logger = PlaytestLogger.new() root.logger.ap_per_round = int(root.db.run.action_economy.ap_per_round) -static func _build_overlays(root: Variant) -> void: +static func _build_overlays(root: Variant, handlers: Dictionary) -> void: # 模态覆盖层(登记序 = 树序 = z 遮挡序:图例 < 明细回看 < 节点 < 关卡画面 < 三选一 < 终局 # < 商店 < 营地场景根 < 四子界面 < 设置 < 状态面板(只读) < 生涯页(只读) < 确认弹窗 < 调试浮窗; # 关卡画面住冒险屏壳(树序在模态之下)——登记序仍夹在节点层与三选一层之间,遮挡语义零改动) @@ -121,35 +121,35 @@ static func _build_overlays(root: Variant) -> void: root.ui.modals.camp.scene.set_hud_band(hud_band) # 屏壳/子界面插位由 LAYER_CONTRACT 槽统一落位——禁散落 move_child 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) + root.ui.modals.run.node_overlay.continue_pressed.connect(handlers["node_continue"]) + root.ui.modals.run.level_screen.route_chosen.connect(handlers["route_chosen"]) + root.ui.modals.run.level_screen.status_badge.pressed.connect(handlers["status_badge_pressed"]) # 状态面板入口(地图屏顶栏角标) + root.ui.modals.run.level_screen.abandon_requested.connect(handlers["abandon_requested"]) # 放弃入口 + root.ui.modals.run.end_overlay.continue_pressed.connect(handlers["battle_end_continue"]) -static func _build_aux_panels(root: Variant) -> void: +static func _build_aux_panels(root: Variant, handlers: Dictionary) -> void: 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) + root.ui.modals.menu.settings_overlay.closed.connect(handlers["settings_closed"]) # 局内状态面板·只读(登记设置层同档带 dim 遮罩,确认弹窗之前; # 数据=组合根开层实时读 MetaProgression 访问器喂入,本层零写通路) 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) + root.ui.modals.run.status_panel.closed.connect(handlers["status_closed"]) # 生涯页·只读全屏页(登记序=营地同档,设置层旁、确认弹窗之前; # 树序同步=addChild 同位(登记序=树序=z 序纪律);数据=组合根开层实时读 SaveManager 计数 # 访问器+MetaProgression 访问器喂入,本层零写通路零跳转;入口=开始层「营地与系统」组生涯钮) 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) + root.ui.modals.menu.career_overlay.closed.connect(handlers["career_closed"]) # 选档界面(营地交互重做卡③):登记序=生涯同档(树序同步=addChild 同位);三动作只发意图信号 #(激活/创建/删除+确认分派归组合根——视图零数据层依赖);入口=「进入存档」钮(卡② mn3 接线)。 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) + root.ui.modals.menu.save_select_overlay.closed.connect(handlers["save_select_closed"]) + root.ui.modals.menu.save_select_overlay.new_slot_requested.connect(handlers["save_select_new"]) + root.ui.modals.menu.save_select_overlay.load_slot_requested.connect(handlers["save_select_load"]) + root.ui.modals.menu.save_select_overlay.delete_slot_requested.connect(handlers["save_select_delete"]) static func _build_curio(root: Variant) -> void: # 提升三选一覆盖层(z 序 = 节点层之上、终局层之下;确认后同拍推进; @@ -158,7 +158,7 @@ static func _build_curio(root: Variant) -> void: root.add_child(root.ui.modals.run.curio_overlay) # 提升三选一与替换子交互(本体在 curio_interact_controller.gd;本类只接线与回调注入, # 模态栈/演播后排窗/呈现刷新/fatal 经回调注入,adventure/runner 现值回调) - root._bonus = CurioInteractController.new(CurioFlowContext.new(root.db, + root.set_bonus(CurioInteractController.new(CurioFlowContext.new(root.db, {"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, @@ -166,22 +166,22 @@ static func _build_curio(root: Variant) -> void: "runner_get": func() -> Variant: return root.run_scope.runner, "adventure_mode_get": func() -> Variant: return root.run_scope.is_adventure}, {"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.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) + "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.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: +static func _build_menu_wiring(root: Variant, handlers: Dictionary) -> void: # 菜单屏接线(句柄经屏壳暴露;三入口化——进入存档/设置/退出游戏,卡② mn3: # 进入存档钮落点=卡③选档层 _open_save_select,点击链在此闭环) - root.ui.screens.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.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) + root.ui.screens.debug_badge.pressed.connect(handlers["debug_badge_pressed"]) # 营地/商店投影(本体在 camp_shop_presenter.gd;本类只接线、探针缝保缝转发; # 购买写调用唯一经 MetaProgression/LevelRunner core 单口——组合根 lambda 现值注入) # 营地刷新单口(各写口尾→现开界面+场景根 HUD 一闸完成——卡④ T3;卡⑤ HUD band 随卡⑤挂册) @@ -189,7 +189,7 @@ static func _build_menu_wiring(root: Variant) -> void: camp_bus.bind_targets([root.ui.modals.camp.forge, root.ui.modals.camp.treasure, root.ui.modals.camp.tree, root.ui.modals.camp.codex, root.ui.screens.camp_screen.camp_scene.hud_band] as Array[Control]) - root._camp_pres = CampShopPresenter.new(CampFlowContext.new(root.db, + root.set_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.menu.shop_overlay, @@ -199,31 +199,31 @@ static func _build_menu_wiring(root: Variant) -> void: {"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.flow.open, "close_modal": root.ui.flow.close, - "refresh_presentations": root._refresh_presentations})) # 构造参数收拢 context(u21 三束) - 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) + "refresh_presentations": root.refresh_presentations}))) # 构造参数收拢 context(u21 三束) + 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) - root.ui.modals.camp.forge.forge_select_requested.connect(root._camp_pres.on_camp_forge_select) - root.ui.modals.camp.treasure.buy_requested.connect(root._camp_pres.on_camp_buy) - root.ui.modals.camp.treasure.attune_toggled.connect(root._camp_pres.on_camp_attune_toggle) - root.ui.modals.camp.tree.tree_buy_requested.connect(root._camp_pres.on_camp_tree_buy) - root.ui.modals.camp.tree.tree_fork_toggled.connect(root._camp_pres.on_camp_fork_toggle) - root.ui.modals.camp.forge.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.forge)) - root.ui.modals.camp.treasure.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.treasure)) - root.ui.modals.camp.tree.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.tree)) - root.ui.modals.camp.codex.closed.connect(root._camp_pres.on_camp_sub_closed.bind(root.ui.modals.camp.codex)) - root.ui.modals.camp.scene.object_pressed.connect(root._on_camp_object_pressed) - root.ui.screens.menu_screen.settingsbtn.pressed.connect(root._open_settings) # 最小设置面入口 - root.ui.screens.menu_screen.quitbtn.pressed.connect(root._quit_game) # 退出游戏提级一级菜单 + 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) + root.ui.modals.camp.forge.forge_select_requested.connect(root.camp_pres().on_camp_forge_select) + root.ui.modals.camp.treasure.buy_requested.connect(root.camp_pres().on_camp_buy) + root.ui.modals.camp.treasure.attune_toggled.connect(root.camp_pres().on_camp_attune_toggle) + root.ui.modals.camp.tree.tree_buy_requested.connect(root.camp_pres().on_camp_tree_buy) + root.ui.modals.camp.tree.tree_fork_toggled.connect(root.camp_pres().on_camp_fork_toggle) + root.ui.modals.camp.forge.closed.connect(root.camp_pres().on_camp_sub_closed.bind(root.ui.modals.camp.forge)) + root.ui.modals.camp.treasure.closed.connect(root.camp_pres().on_camp_sub_closed.bind(root.ui.modals.camp.treasure)) + root.ui.modals.camp.tree.closed.connect(root.camp_pres().on_camp_sub_closed.bind(root.ui.modals.camp.tree)) + root.ui.modals.camp.codex.closed.connect(root.camp_pres().on_camp_sub_closed.bind(root.ui.modals.camp.codex)) + root.ui.modals.camp.scene.object_pressed.connect(handlers["camp_object_pressed"]) + 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: +static func _build_debug_and_resolvers(root: Variant, handlers: Dictionary) -> void: var inter: Dictionary[Variant, Variant] = UiAssembler.build_inter_overlay(root) root.ui.screens.inter_overlay = inter.overlay root.ui.screens.inter_label = inter.label - inter.next_btn.pressed.connect(root._on_inter_next_pressed) + inter.next_btn.pressed.connect(handlers["inter_next_pressed"]) # 确认弹窗(新冒险互斥确认弹窗化):树序落在开始层/过场层之后 = 子级最末 #(纪律「登记序 = 树序 = z 序」——confirm_dialog.open 禁运行时 move_child 自提抬层; @@ -233,13 +233,13 @@ static func _build_debug_and_resolvers(root: Variant) -> void: root.add_child(root.ui.modals.menu.confirm_dialog) # 消费方编排(卡③ 契约 3/sl8——禁双常驻静态接线):open 前设 _confirm_consumer, # confirmed/cancelled 后按该状态量分派;分派面可容纳后续消费方(卡④ 到位直接接入不返工) - 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) + root.ui.modals.menu.confirm_dialog.confirmed.connect(handlers["confirm_accepted"]) + root.ui.modals.menu.confirm_dialog.secondary_confirmed.connect(handlers["confirm_secondary"]) # 三钮次确认(卡④ 大门「开始新冒险」出口) + root.ui.modals.menu.confirm_dialog.cancelled.connect(handlers["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) - root.ui.modals.camp.esc_overlay.quit_requested.connect(root._on_esc_quit) + root.ui.modals.camp.esc_overlay.return_requested.connect(handlers["esc_return"]) + root.ui.modals.camp.esc_overlay.settings_requested.connect(handlers["esc_settings"]) + root.ui.modals.camp.esc_overlay.quit_requested.connect(handlers["esc_quit"]) # 调试浮窗(主菜单三散件收一:随机种子/数值调整/批量模拟/两层调试入口; # 全局可开浮窗:调试会话恒开=启动即开〔_ready〕+两屏角标再开+当局数据只读区块; @@ -249,9 +249,9 @@ static func _build_debug_and_resolvers(root: Variant) -> void: 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) + root.ui.modals.menu.debug_overlay.apply_requested.connect(handlers["tune_apply"]) + root.ui.modals.menu.debug_overlay.simulate_requested.connect(handlers["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.menu.debug_overlay, func() -> Variant: return root.run_scope.settlement) @@ -261,10 +261,10 @@ static func _build_debug_and_resolvers(root: Variant) -> void: 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) - root._debug.battle_created.connect(root._on_debug_battle_created) - root._debug.battle_ready.connect(root._on_debug_battle_ready) + root.set_debug(DebugSessionScript.new(root.db, root.get_node(root.AUTOLOAD_PATH_RNG), + func() -> Dictionary: return resolvers.debug_hero_override(), root.seed_value)) + root.debug().battle_created.connect(handlers["debug_battle_created"]) + root.debug().battle_ready.connect(handlers["debug_battle_ready"]) static func _build_modal_flow(root: Variant) -> void: # 模态登记(缺陷⑤纪律——登记序 = 树序 = z 序):层序唯一事实源=LAYER_CONTRACT, @@ -277,15 +277,15 @@ static func _build_modal_flow(root: Variant) -> void: 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.sync_status_entry() # 状态面板入口角标可见性/置灰随开态同步 + root.sync_debug_overlay() # 浮窗当局数据/按钮态随开态同步 root.ui.flow.on_close_sync = func() -> void: - root._sync_status_entry() # 关闭恢复角标可点态(无残留——同源口径) - root._sync_debug_overlay() # 他层关闭复活浮窗时同步刷新(关后自动恢复语义配套) - root.ui.flow.replay_click = root._replay_locked_click + root.sync_status_entry() # 关闭恢复角标可点态(无残留——同源口径) + root.sync_debug_overlay() # 他层关闭复活浮窗时同步刷新(关后自动恢复语义配套) + 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.menu.debug_overlay, int(root.db.combat.hero.max_hp)) + root.set_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) @@ -295,4 +295,4 @@ static func fatal_screen(root: Variant, msg: String) -> void: # GP 总结行回调(gp_summary 束键——单行 lambda 内嵌字典实参收尾歧义,提函数承载) static func _gp_summary_cb(root: Variant) -> Callable: - return func() -> String: return root._gp_summary_text({"gp_dropped": root.run_scope.last_gain_amount}) + return func() -> String: return root.gp_summary_text({"gp_dropped": root.run_scope.last_gain_amount}) diff --git a/game/presentation/ui_assembler.gd b/game/presentation/ui_assembler.gd index 829b89e7..80784886 100644 --- a/game/presentation/ui_assembler.gd +++ b/game/presentation/ui_assembler.gd @@ -103,7 +103,7 @@ static func build_start_overlay(host: Control, db: ConfigDb = null) -> Dictionar var bd := UiKit.backdrop_tile() if bd != null: overlay.add_child(bd) - overlay.add_child(UiKit._beams_rect()) + overlay.add_child(UiKit.beams_rect()) overlay.add_child(UiKit.vignette_rect()) # 双列骨架:左=操作列 56% / 右=舞台(立绘看台) var cols := _start_columns(overlay) diff --git a/game/presentation/views/battle_end_overlay.gd b/game/presentation/views/battle_end_overlay.gd index d5340755..3c9c3ba6 100644 --- a/game/presentation/views/battle_end_overlay.gd +++ b/game/presentation/views/battle_end_overlay.gd @@ -1,6 +1,7 @@ ## VERTICAL SLICE - NOT FOR PRODUCTION ## 战斗/冒险终局 overlay(模态):判定行+三行账+明细回看入口+继续/返回,失败侧暮蓝氛围。 ## 摘要文本经 open_victory/open_defeat 由组合根装配喂入——本类零文案装配。 +## 内部数据类 _EndOverlayState=宿主画面登记表(只被本文件填读,文件外零消费者——字段为公开数据成员)。 class_name BattleEndOverlay extends Control @@ -13,20 +14,20 @@ const COL_WARM := UiKit.WARM ## 内部状态件(u6 子域拆件——契约 9 职责语句「战末呈现与演出态」):单一变化原因=终局画面呈现与 ## 演出计数。探针直读冻结成员(_btn/_gp_name/_gp_row/_gp_val)住宿主不进状态件。 class _EndOverlayState extends RefCounted: - var _dim: ColorRect - var _seal: Panel - var _verdict: Label - var _rows_box: VBoxContainer # 三行账(GP/钥石/本关加成——demo ⑧两列对齐;失败侧整行隐藏) - var _ks_row: HBoxContainer - var _ks_name: Label - var _ks_val: Label - var _bonus_row_l: HBoxContainer - var _bonus_name: Label - var _bonus_val: Label - var _cele: TextureRect # 庆祝立绘(idle 占位浮动) - var _board_rect := Rect2() - var _crest_bricks: Array[ColorRect] = [] # 纹章碎砖驻留面(close 时回收) - var _fx_counts := {"crest": 0, "dust": 0, "seal": 0} # 演出计数(fx_probe 断言面——crest/dust/seal 三键) + var dim: ColorRect + var seal: Panel + var verdict: Label + var rows_box: VBoxContainer # 三行账(GP/钥石/本关加成——demo ⑧两列对齐;失败侧整行隐藏) + var ks_row: HBoxContainer + var ks_name: Label + var ks_val: Label + var bonus_row_l: HBoxContainer + var bonus_name: Label + var bonus_val: Label + var cele: TextureRect # 庆祝立绘(idle 占位浮动) + var board_rect := Rect2() + var crest_bricks: Array[ColorRect] = [] # 纹章碎砖驻留面(close 时回收) + var fx_counts := {"crest": 0, "dust": 0, "seal": 0} # 演出计数(fx_probe 断言面——crest/dust/seal 三键) var st := _EndOverlayState.new() @@ -40,10 +41,10 @@ func _init() -> void: # 半透明浮层文字不可读 → 实底石板皮面板形态 #(与营地/设置/图例同族)。半透明氛围 dim 保留(胜利暖纱/失败暮蓝——open_* 就地换色),信息与 # 按钮全部上板;金蜡封 = 面板头部静态徽记(坠落演出落点被面板遮挡)。 - # 骨架经 UiKit.modal_shell;_dim 引用摘出供 open_* 换氛围色。 + # 骨架经 UiKit.modal_shell;dim 引用摘出供 open_* 换氛围色。 var shell := UiKit.modal_shell(self, Color(COL_WARM, 0.0), Vector2(520, 0), 12, UiKit.panel_skinned(Vector4(16, 14, 16, 14))) - st._dim = shell.dim + st.dim = shell.dim var box: VBoxContainer = shell.box _build_header(box) _build_ledger(box) @@ -62,20 +63,20 @@ func _build_header(box: VBoxContainer) -> void: header.add_theme_constant_override("separation", 12) header.alignment = BoxContainer.ALIGNMENT_CENTER box.add_child(header) - st._seal = Panel.new() - st._seal.mouse_filter = Control.MOUSE_FILTER_IGNORE + st.seal = Panel.new() + st.seal.mouse_filter = Control.MOUSE_FILTER_IGNORE var sb := UiKit.panel_flat(COL_GOLD, UiKit.GOLD_DEEP, 200, 4) - st._seal.add_theme_stylebox_override("panel", sb) - st._seal.custom_minimum_size = Vector2(52, 52) - st._seal.size_flags_vertical = Control.SIZE_SHRINK_CENTER - header.add_child(st._seal) - st._seal.add_child(_seal_img()) - st._verdict = UiKit.label("",UiKit.FS_TITLE, true, UiKit.INK_TITLE) - st._verdict.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - st._verdict.size_flags_vertical = Control.SIZE_SHRINK_CENTER - header.add_child(st._verdict) - st._cele = _cele_rect() - header.add_child(st._cele) + st.seal.add_theme_stylebox_override("panel", sb) + st.seal.custom_minimum_size = Vector2(52, 52) + st.seal.size_flags_vertical = Control.SIZE_SHRINK_CENTER + header.add_child(st.seal) + st.seal.add_child(_seal_img()) + st.verdict = UiKit.label("",UiKit.FS_TITLE, true, UiKit.INK_TITLE) + st.verdict.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + st.verdict.size_flags_vertical = Control.SIZE_SHRINK_CENTER + header.add_child(st.verdict) + st.cele = _cele_rect() + header.add_child(st.cele) ## 蜡封图域(32px 像素过滤居中) @@ -110,30 +111,30 @@ func _cele_rect() -> TextureRect: ## 三行账域(两列对齐账行——审计差距④对齐 demo ⑧:项目名左/数值右,定宽 360) func _build_ledger(box: VBoxContainer) -> void: - st._rows_box = VBoxContainer.new() - st._rows_box.add_theme_constant_override("separation", 6) - st._rows_box.visible = false - box.add_child(st._rows_box) + st.rows_box = VBoxContainer.new() + st.rows_box.add_theme_constant_override("separation", 6) + st.rows_box.visible = false + box.add_child(st.rows_box) _gp_row = _ledger_row() _gp_name = _ledger_name(UiKit.FS_SUB, UiKit.GOLD_INK, true) # GOLD_DEEP 对结算板 2.67:1 越带,换深金墨 5.21:1 _gp_val = _ledger_value(UiKit.FS_SUB, UiKit.GOLD_INK, true) # 同上 _gp_row.add_child(_gp_name) _gp_row.add_child(_gp_val) - st._rows_box.add_child(_gp_row) - st._ks_row = _ledger_row() - st._ks_name = _ledger_name(UiKit.FS_BODY, UiKit.INK_LABEL, false) - st._ks_val = _ledger_value(UiKit.FS_BODY, UiKit.INK_LABEL, true) - st._ks_row.add_child(st._ks_name) - st._ks_row.add_child(st._ks_val) - st._ks_row.visible = false - st._rows_box.add_child(st._ks_row) - st._bonus_row_l = _ledger_row() - st._bonus_name = _ledger_name(UiKit.FS_HINT, UiKit.INK_LABEL, false) - st._bonus_val = _ledger_value(UiKit.FS_HINT, UiKit.INK_LABEL, true) - st._bonus_row_l.add_child(st._bonus_name) - st._bonus_row_l.add_child(st._bonus_val) - st._bonus_row_l.visible = false - st._rows_box.add_child(st._bonus_row_l) + st.rows_box.add_child(_gp_row) + st.ks_row = _ledger_row() + st.ks_name = _ledger_name(UiKit.FS_BODY, UiKit.INK_LABEL, false) + st.ks_val = _ledger_value(UiKit.FS_BODY, UiKit.INK_LABEL, true) + st.ks_row.add_child(st.ks_name) + st.ks_row.add_child(st.ks_val) + st.ks_row.visible = false + st.rows_box.add_child(st.ks_row) + st.bonus_row_l = _ledger_row() + st.bonus_name = _ledger_name(UiKit.FS_HINT, UiKit.INK_LABEL, false) + st.bonus_val = _ledger_value(UiKit.FS_HINT, UiKit.INK_LABEL, true) + st.bonus_row_l.add_child(st.bonus_name) + st.bonus_row_l.add_child(st.bonus_val) + st.bonus_row_l.visible = false + st.rows_box.add_child(st.bonus_row_l) box.add_child(HSeparator.new()) @@ -147,19 +148,19 @@ func open_victory(verdict_line: String, continue_label: String, board_rect: Rect var bonus_summary: String = opts.get("bonus_summary", "") var gp_gain: int = opts.get("gp_gain", -1) var gp_total: int = opts.get("gp_total", -1) - st._dim.color = Color(UiKit.DIM_NIGHT, 0.62) # 全屏压暗对齐 demo - st._verdict.text = verdict_line - st._seal.visible = true # 胜利徽记(面板头部静态) - st._seal.modulate = Color.WHITE + st.dim.color = Color(UiKit.DIM_NIGHT, 0.62) # 全屏压暗对齐 demo + st.verdict.text = verdict_line + st.seal.visible = true # 胜利徽记(面板头部静态) + st.seal.modulate = Color.WHITE _set_ledger_row(_gp_name, _gp_val, gp_summary, true) # GP 行=固定名「战利品 GP」(滚动/整串均只入值列) - _set_ledger_row(st._ks_name, st._ks_val, ks_summary) - st._ks_row.visible = ks_summary != "" - _set_ledger_row(st._bonus_name, st._bonus_val, bonus_summary) - st._bonus_row_l.visible = bonus_summary != "" - st._rows_box.visible = gp_summary != "" or ks_summary != "" or bonus_summary != "" + _set_ledger_row(st.ks_name, st.ks_val, ks_summary) + st.ks_row.visible = ks_summary != "" + _set_ledger_row(st.bonus_name, st.bonus_val, bonus_summary) + st.bonus_row_l.visible = bonus_summary != "" + st.rows_box.visible = gp_summary != "" or ks_summary != "" or bonus_summary != "" _btn.text = continue_label - st._board_rect = board_rect - st._cele.visible = true + st.board_rect = board_rect + st.cele.visible = true _play_celebrate() visible = true if UiKit.victory_fx_style == "dust": @@ -173,23 +174,23 @@ func open_victory(verdict_line: String, continue_label: String, board_rect: Rect func open_defeat(verdict_line: String, continue_label: String, board_rect: Rect2) -> void: # 明度不变的暮蓝色相氛围(全屏唯一暮蓝档——§2.5 最静一拍) - st._dim.color = Color(COL_DUSK, 0.6) # 同族压暗 - st._verdict.text = verdict_line - st._board_rect = board_rect + st.dim.color = Color(COL_DUSK, 0.6) # 同族压暗 + st.verdict.text = verdict_line + st.board_rect = board_rect visible = true - st._seal.visible = true - st._seal.modulate = Color(0.4, 0.4, 0.4) # 失败徽记灰化(demo ⑧) - st._cele.visible = false - st._rows_box.visible = false # 失败侧不呈现账行 + st.seal.visible = true + st.seal.modulate = Color(0.4, 0.4, 0.4) # 失败徽记灰化(demo ⑧) + st.cele.visible = false + st.rows_box.visible = false # 失败侧不呈现账行 _play_dust_sadle() # 坐砖掸灰(§2.5——终稿) _play_seal_recall(8) func close() -> void: visible = false - st._seal.visible = false - st._seal.modulate = Color.WHITE - st._cele.visible = false + st.seal.visible = false + st.seal.modulate = Color.WHITE + st.cele.visible = false var tw: Tween = get_meta(&"celebrate_tw", null) # 循环句柄随节点存(跨 open/close,close 必杀) if tw != null and tw.is_valid(): tw.kill() @@ -202,7 +203,7 @@ func is_open() -> bool: func verdict_text() -> String: - return st._verdict.text + return st.verdict.text # 两列账行工厂(审计差距④——名左/值右,定宽 360) @@ -249,8 +250,8 @@ func _fx_center() -> Vector2: # 封印化光点收回(失败——插过的封印逐面化光点收回占位,§2.5) func _play_seal_recall(count: int) -> void: - st._fx_counts["seal"] = count - var base := st._board_rect.get_center() if st._board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5 + st.fx_counts["seal"] = count + var base := st.board_rect.get_center() if st.board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5 for i in count: _spawn_seal_dot(i, base) @@ -260,8 +261,8 @@ func _spawn_seal_dot(i: int, base: Vector2) -> void: var dot := ColorRect.new() dot.color = COL_GOLD dot.size = Vector2(6, 6) - dot.position = base + Vector2((float(i % 5) - 2.0) * st._board_rect.size.x * 0.14 if st._board_rect.size.x > 0 else 0.0, - (float(i % 3) - 1.0) * st._board_rect.size.y * 0.18) + dot.position = base + Vector2((float(i % 5) - 2.0) * st.board_rect.size.x * 0.14 if st.board_rect.size.x > 0 else 0.0, + (float(i % 3) - 1.0) * st.board_rect.size.y * 0.18) dot.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(dot) var tw := dot.create_tween() @@ -277,7 +278,7 @@ func _spawn_seal_dot(i: int, base: Vector2) -> void: # 碎砖拼纹章(胜利 §2.4——蜡封落定后十枚碎砖自四周向蜡封外环收拢拼成纹章环,驻留至 close) func _play_crest_assemble() -> void: _clear_crest_bricks() - st._fx_counts["crest"] = 10 + st.fx_counts["crest"] = 10 var c := _fx_center() for i in 10: _spawn_crest_brick(i, c) @@ -293,7 +294,7 @@ func _spawn_crest_brick(i: int, c: Vector2) -> void: var ring := c + Vector2(cos(ang), sin(ang)) * 64.0 brick.position = c + Vector2(cos(ang), sin(ang)) * 200.0 # 自四周场外入场 add_child(brick) - st._crest_bricks.append(brick) + st.crest_bricks.append(brick) var btw := brick.create_tween() btw.tween_interval(UiKit.motion(0.52)) # 待蜡封落定(seal 0.42+0.22 弹跳中段) btw.tween_property(brick, "position", ring - brick.size * 0.5, UiKit.motion(0.4)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) @@ -302,7 +303,7 @@ func _spawn_crest_brick(i: int, c: Vector2) -> void: # 坐砖掸灰(失败 §2.5——暖灰尘絮自棋盘中带缓缓飘落,最静一拍的运动承载) func _play_dust_sadle() -> void: - st._fx_counts["dust"] = 8 + st.fx_counts["dust"] = 8 var base := _fx_center() for i in 8: _spawn_dust_mote(i, base) @@ -330,16 +331,16 @@ func _play_celebrate() -> void: var prev: Tween = get_meta(&"celebrate_tw", null) if prev != null and prev.is_valid(): prev.kill() - var tw := st._cele.create_tween() + var tw := st.cele.create_tween() set_meta(&"celebrate_tw", tw) tw.set_loops() - tw.tween_property(st._cele, "position:y", st._cele.position.y - 4.0, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) - tw.tween_property(st._cele, "position:y", st._cele.position.y, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + tw.tween_property(st.cele, "position:y", st.cele.position.y - 4.0, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) + tw.tween_property(st.cele, "position:y", st.cele.position.y, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) # 尘金粒子(备选演出——金屑自面板上带飘落闪灭;确定性散布守 S2) func _play_victory_dust() -> void: - st._fx_counts["dust"] += 10 + st.fx_counts["dust"] += 10 var c := _fx_center() for i in 10: _spawn_victory_mote(i, c) @@ -372,15 +373,15 @@ func _roll_gp(gp_gain: int, gp_total: int) -> void: func _clear_crest_bricks() -> void: - for b in st._crest_bricks: + for b in st.crest_bricks: if is_instance_valid(b): b.queue_free() - st._crest_bricks.clear() + st.crest_bricks.clear() # 演出探针出口(fx_probe——胜利纹章/失败掸灰/封印光收回三断言面) func fx_probe() -> Dictionary: - return {"crest_shards": st._fx_counts["crest"], "dust_motes": st._fx_counts["dust"], "seal_dots": st._fx_counts["seal"]} + return {"crest_shards": st.fx_counts["crest"], "dust_motes": st.fx_counts["dust"], "seal_dots": st.fx_counts["seal"]} func _emit_continue_pressed() -> void: diff --git a/game/presentation/views/board_cell_unit.gd b/game/presentation/views/board_cell_unit.gd index 8bb85643..46ec0958 100644 --- a/game/presentation/views/board_cell_unit.gd +++ b/game/presentation/views/board_cell_unit.gd @@ -10,12 +10,14 @@ extends Node2D const _GLOW_LAYER := preload("res://presentation/views/cell_type_glow_layer.gd") const _BASE_LAYER := preload("res://presentation/views/cell_base_layer.gd") const _MARK_OVERLAY := preload("res://presentation/views/cell_mark_overlay.gd") +const _SEAL_LAYER := preload("res://presentation/views/seal_mark_layer.gd") var edge := 46.0: # 格边长(BoardView.set_cell_area 单点驱动) set(v): edge = v if _glow_layer != null and is_instance_valid(_glow_layer): _glow_layer.setup(edge, glow_group) + _sync_seal_layer() queue_redraw() var cell := Vector2i.ZERO # 板坐标(诊断观测面) # ── 未开格类型微光(英雄 8 向相邻未开格的组粒度类型提示) @@ -41,16 +43,49 @@ var fog_state := 0: # 探针直读冻结 set(v): if fog_state != v: fog_state = v + _sync_seal_layer() # 封印随格隐没(§6.5——雾态 !=0 整层隐藏) queue_redraw() +# ── 封印印记(卡④ 法阵化——着色器按格边参数化绘制,替代 tex_seal 贴图整格铺放)── +# BoardView paint 按格态写 seal_on(封印格=未开且未移除);子件 on 才建、off 撤、edge 变更重注入。 +var seal_on := false: + set(v): + if seal_on != v: + seal_on = v + _sync_seal_layer() + var _glow_layer: Node2D = null +var _seal_layer: Node2D = null # 封印印记子件(seal_on 才建——作用集=封印格) var base = null # 格面基座层绘制域件(_init 自建) var face = null # 格面标识与叠加层绘制域件(_init 自建) +# 封印子件生命周期单点:on 才建、off 撤;visible = seal_on 且非雾态(§6.5 随格隐没) +func _sync_seal_layer() -> void: + if seal_on and _seal_layer == null: + _seal_layer = _SEAL_LAYER.new() + add_child(_seal_layer) + if _seal_layer != null and not is_instance_valid(_seal_layer): + _seal_layer = null + return + if _seal_layer == null: + return + if not seal_on: + _seal_layer.queue_free() + _seal_layer = null + return + _seal_layer.setup(edge, fog_state == 0) + + func _init() -> void: base = _BASE_LAYER.new(self) - face = _MARK_OVERLAY.new(self) + face = _MARK_OVERLAY.new(self, _set_badge_mask) + + +# 徽章遮罩下发口(构造交付 Callable——glow 层引用自持本件,face 零私取回引) +func _set_badge_mask(on: bool) -> void: + if _glow_layer != null and is_instance_valid(_glow_layer): + _glow_layer.set_badge_mask(on) func _draw() -> void: diff --git a/game/presentation/views/board_hover.gd b/game/presentation/views/board_hover.gd index 44da7b45..ceb59525 100644 --- a/game/presentation/views/board_hover.gd +++ b/game/presentation/views/board_hover.gd @@ -33,7 +33,24 @@ func _init(bundle: Dictionary) -> void: _cb_chord = bundle["chord"] -func _hover_at(pos: Vector2) -> void: +# ---- 公开操作面(pres封装 A2——宿主经此驱动,字段保持私有)---- + +# 每战重绑(排雷玩法对象随战更替——宿主 rebuild 同拍调用) +func rebind(minesweeper: Variant) -> void: + _minesweeper = minesweeper + + +# 硬复位悬停锚(无 unhover 视觉清理——宿主 rebuild 前提下视觉随重建清零) +func reset() -> void: + _hover = Vector2i(-9, -9) + + +# 悬停现值读口(宿主呈现查询) +func hover_pos() -> Vector2i: + return _hover + + +func hover_at(pos: Vector2) -> void: var c: Vector2i = _cb_cell_at.call(pos) if c.x < 0: if _hover.x >= 0: @@ -44,13 +61,13 @@ func _hover_at(pos: Vector2) -> void: _on_cell_hover(c) -func _on_shell_mouse_exited() -> void: +func on_shell_mouse_exited() -> void: if _hover.x >= 0: _on_cell_unhover(_hover) # chord 肌肉记忆首遇锚:同格双左击快开尝试(350ms 窗)——每 run 一次提示由组合根去重 -func _detect_chord(c: Vector2i) -> void: +func detect_chord(c: Vector2i) -> void: var now := Time.get_ticks_msec() if _last_click.cell == c and now - int(_last_click.ms) <= CHORD_WINDOW_MS: _cb_chord.call(c) @@ -70,7 +87,7 @@ func _on_cell_unhover(c: Vector2i) -> void: _clear_hover_visuals(c) -func _refresh_hover_after_paint() -> void: +func refresh_after_paint() -> void: # 状态随 refresh 变化后光标/边框语义需重算(悬停格四格态可能已变) if _hover.x >= 0 and _cells.has(_hover): _apply_hover_visuals(_hover) diff --git a/game/presentation/views/board_view.gd b/game/presentation/views/board_view.gd index 8af30583..652806a9 100644 --- a/game/presentation/views/board_view.gd +++ b/game/presentation/views/board_view.gd @@ -143,7 +143,7 @@ func board() -> BoardData: func _ready() -> void: _apply_custom_cursors() - mouse_exited.connect(_hover_comp._on_shell_mouse_exited) + mouse_exited.connect(_hover_comp.on_shell_mouse_exited) # 结算期/道具窗口态:D 区去饱和 + 金橙偏移锁输入(明度不变——art-bible §2); @@ -246,7 +246,7 @@ func _refresh_slab() -> void: func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void: _board = board _minesweeper = minesweeper - _hover_comp._minesweeper = _minesweeper # 件内只读引用随宿主重绑同拍回写(每战新排雷玩法对象) + _hover_comp.rebind(_minesweeper) # 件内只读引用随宿主重绑同拍回写(每战新排雷玩法对象) _palette.bind_board(_board, _minesweeper) # 查表族棋盘态随板同步(换板后首拍 refresh 前置) for child in _world.get_children(): child.queue_free() @@ -257,7 +257,7 @@ func rebuild(board: BoardData, minesweeper: MinesweeperRules) -> void: _contact_unit = null _hero_sprite.last = Vector2i(-99, -99) _r0.clear() - _hover_comp._hover = Vector2i(-9, -9) + _hover_comp.reset() for c in _board.compute_r0(): _r0[c] = true # 瓦片套随板图型切换(board_gen 词表 v1.1 dungeon/underdark/deep_underdark; @@ -306,7 +306,7 @@ func refresh() -> void: for y in _board.height: for x in _board.width: _paint_cell(Vector2i(x, y), hero) - _hover_comp._refresh_hover_after_paint() + _hover_comp.refresh_after_paint() _apply_contact_bands() _update_hero(hero) # 英雄精灵 diff 驱动 @@ -461,7 +461,7 @@ func _gui_input(ev: InputEvent) -> void: if _minesweeper == null: return if ev is InputEventMouseMotion: - _hover_comp._hover_at(ev.position) + _hover_comp.hover_at(ev.position) elif ev is InputEventMouseButton and ev.pressed: var c := _cell_at(ev.position) if c.x >= 0: @@ -476,7 +476,7 @@ func _on_cell_input(c: Vector2i, ev: InputEvent) -> void: action_requested.emit(c, "move") else: action_requested.emit(c, "dig") - _hover_comp._detect_chord(c) + _hover_comp.detect_chord(c) elif ev.button_index == MOUSE_BUTTON_RIGHT: if not _minesweeper.has_seal(c): action_requested.emit(c, "seal_place") @@ -536,13 +536,13 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void: var seal: bool = _minesweeper.has_seal(c) var removed: bool = _minesweeper.is_removed(c) # 中立移除格(退色降档) var is_hero: bool = c == hero - # 封印槽统一维护:封印格=封印印记纹理;其余格清 null(封印格挖开后残留在 tex_seal - # 会让 _draw 无条件把封印印画在已开格上——tex_seal 绘制无 revealed 条件)。缺盘走字形回退。 - u.face.tex_seal = _palette.seal_tex if (seal and not revealed and _palette.seal_tex != null) else null + # 封印印记下发(卡④ 法阵化):封印格=未开且未移除 → CellUnit 封印子件绘制(着色器参数化); + # 其余格撤子件。层序与迷雾门控在 CellUnit 侧承载(seal_mark_layer/fog_state 联动)。 + u.seal_on = seal and not revealed if removed: revealed = false # 移除格固定未开呈现基座(退色暗幕层承载;危险格本无数字,周边数字冻结) seal = false - u.face.tex_seal = null + u.seal_on = false # 绘制层五段(字形与色牌→基底瓦片→立面投影→秘宝/变异叠加→迷雾)——段间交换经单元状态面 #(u.base/u.face 域件与 u.glyph/u.glyph_color),唯一例外=基底色 bg 经 ink 段返回值交 base 段(无单元场域承载) var bg: Color = _paint_cell_ink(c, u, revealed, seal) @@ -577,10 +577,7 @@ func _paint_cell_ink(c: Vector2i, u: Node2D, revealed: bool, seal: bool) -> Colo digit_col = _digit_ink(d) # 数字墨三档色温(§4.1) else: if seal: - bg = COL_BRICK - if _palette.seal_tex == null: - glyph = "封" - digit_col = COL_SEAL + bg = COL_BRICK # 封印印记由 CellUnit 封印子件绘制(卡④ 法阵化——缺图字形回退随贴图路径退役) else: bg = COL_BRICK u.glyph = glyph @@ -825,7 +822,7 @@ func diggable_probe() -> Dictionary: # 悬停格只读出口(case① 悬停到达断言消费面——读件投影,返回语义零变化) func hover_probe() -> Vector2i: - return _hover_comp._hover + return _hover_comp.hover_pos() # 迷雾参数只读快照(case⑮ 参数漂移断言消费面) diff --git a/game/presentation/views/cell_mark_overlay.gd b/game/presentation/views/cell_mark_overlay.gd index b6427c72..460a8d2e 100644 --- a/game/presentation/views/cell_mark_overlay.gd +++ b/game/presentation/views/cell_mark_overlay.gd @@ -3,15 +3,16 @@ ## 变异标记/数字色牌与字形/奖励徽章/封印/路径点迹/焦点高亮/中立移除退色的全部绘制入参与绘制命令。 ## 无 class_name(类名缓存面纪律)——经宿主 board_cell_unit preload 引用;宿主 _draw 按层序调 ## draw_marks/draw_glyph_layer/draw_overlay_under/draw_removed_dim(雾层由宿主夹在 overlay_under 与 removed_dim 之间); -## setter 经 _owner 回引触发宿主重绘语义逐字(badge_glyph 的 glow mask 回引 _owner._glow_layer 原样)。 +## setter 经 _owner 回引触发宿主重绘语义逐字(badge_glyph 的 glow mask 经构造交付 Callable 下发——pres封装 C)。 extends RefCounted var _owner: Node2D = null +var _mask_set: Callable # 徽章遮罩下发口(构造交付——宿主 CellUnit 自持 glow 层引用,本件零回引宿主私有面) var badge_glyph := "": # 奖励徽章文字(右下角金——封印之下、变异纹之上、不进数字区) set(v): badge_glyph = v - if _owner._glow_layer != null and is_instance_valid(_owner._glow_layer): - _owner._glow_layer.set_badge_mask(v != "") + if _mask_set.is_valid(): + _mask_set.call(v != "") _owner.queue_redraw() var dot_visible := false: # BFS 路径预览点迹(中心 6×6 小人蓝) set(v): @@ -30,11 +31,6 @@ var font: Font = null # 字体槽位(null = ThemeDB.fallback_font var tex_mutation: Texture2D = null # ② 变异纹带 var tex_glyph: Texture2D = null # ③ 数字 var tex_badge: Texture2D = null # ③' 徽章 -var tex_seal: Texture2D = null: # ④ 封印(槽位可运行时换装〔封印〕——同族 facade/treasure_mark - set(v): # 均 setter 触发重绘(普通 var 赋值不重绘=封印插上不显示)) - if tex_seal != v: - tex_seal = v - _owner.queue_redraw() # 变异角标(程序化,tex_mutation 纹带槽暂留): # "flat_damage" = 未开格伤害型变异,左下角危险红三角(开格有代价的策略纹理标记) # 叠加层标记(秘宝整图叠加层的格面呈现;派生信息层零回写): @@ -56,8 +52,9 @@ var removed_dim := false: # 中立移除退色层(BoardView paint _owner.queue_redraw() -func _init(owner: Node2D) -> void: +func _init(owner: Node2D, mask_set: Callable) -> void: _owner = owner + _mask_set = mask_set ## 变异标记域:真视格=危险红内缩框/未开格伤害型变异=左下角危险红三角 @@ -91,9 +88,7 @@ func draw_glyph_layer(canvas: CanvasItem, rect: Rect2, glyph: String, glyph_colo canvas.draw_texture_rect(tex_badge, rect, false) else: _draw_badge(canvas, rect, badge_glyph) - # ④ 封印(现行视觉封印文字在 ③ 文字位承载——独立槽位留给封印图) - if tex_seal != null: - canvas.draw_texture_rect(tex_seal, rect, false) + # ④ 封印:印记由 CellUnit 封印子件(seal_mark_layer)参数化绘制——本域件零封印绘制(卡④ 法阵化) ## 数字色牌后备域(圣经 v3.5 冻结④:数字亮墨直画已开格底须垫牌; diff --git a/game/presentation/views/debug_overlay.gd b/game/presentation/views/debug_overlay.gd index 80e80373..922b33cd 100644 --- a/game/presentation/views/debug_overlay.gd +++ b/game/presentation/views/debug_overlay.gd @@ -1,6 +1,7 @@ ## VERTICAL SLICE - NOT FOR PRODUCTION ## 开发调试浮窗:全局可开(组合根启动即开+两屏角标再开),当局数据只读区块为本局种子唯一查看位。 ## 扩展调试项按区块往下追加(超高滚动);模拟报告文案装配归 HudPresenter——本类零动效零文案装配。 +## 内部数据类 _DebugState=宿主画面登记表(只被本文件填读,文件外零消费者——字段为公开数据成员)。 class_name DebugOverlay extends Control @@ -12,22 +13,22 @@ signal closed ## 内部状态件(u7 子域拆件——契约 9 职责语句「调试浮窗运行态」):单一变化原因=调试面板状态与 ## 当局数据投影。探针直读冻结成员(seed_edit/_sim_btn/_dbg_btn)住宿主; -## st._run_data/st._run_labels 经 run_data_probe()/update_run_data() 方法出口(字段直访面零外部依赖)。 +## st.run_data/st.run_labels 经 run_data_probe()/update_run_data() 方法出口(字段直访面零外部依赖)。 class _DebugState extends RefCounted: - var _db - var _base_atk := 0 - var _base_hp := 0 - var _atk: HSlider - var _hp: HSlider - var _atk_val: Label - var _hp_val: Label - var _eff: Label - var _sim_count: SpinBox - var _sim_out: RichTextLabel - var _sim_running := false # 批量模拟进行中(is_running 判据——与对局中禁用态分离防互扰) - var _ctx_locked := false # 对局中上下文锁定(组合根 _sync_debug_overlay 喂入——批量模拟/两层调试入口禁用) - var _run_labels := {} # 当局数据六项标签(只读投影,组合根 update_run_data 喂入) - var _run_data := {} # 最近一次投喂快照(探针值对面 run_data_probe) + var db + var base_atk := 0 + var base_hp := 0 + var atk: HSlider + var hp: HSlider + var atk_val: Label + var hp_val: Label + var eff: Label + var sim_count: SpinBox + var sim_out: RichTextLabel + var sim_running := false # 批量模拟进行中(is_running 判据——与对局中禁用态分离防互扰) + var ctx_locked := false # 对局中上下文锁定(组合根 _sync_debug_overlay 喂入——批量模拟/两层调试入口禁用) + var run_labels := {} # 当局数据六项标签(只读投影,组合根 update_run_data 喂入) + var run_data := {} # 最近一次投喂快照(探针值对面 run_data_probe) var st := _DebugState.new() @@ -37,9 +38,9 @@ var _dbg_btn: Button # 两层调试入口钮(对局中禁用承 func _init(db_ref, base_atk: int, base_hp: int) -> void: - st._db = db_ref - st._base_atk = base_atk - st._base_hp = base_hp + st.db = db_ref + st.base_atk = base_atk + st.base_hp = base_hp _build() _sync_labels() visible = false # 显式初始化——不靠树序巧合(缺陷⑤家族纪律) @@ -48,78 +49,78 @@ func _init(db_ref, base_atk: int, base_hp: int) -> void: # ---- 对外语义面(组合根/探针)---- func atk_pct() -> int: - return int(st._atk.value) + return int(st.atk.value) func hp_pct() -> int: - return int(st._hp.value) + return int(st.hp.value) func set_sliders(atk: int, hp: int) -> void: - st._atk.value = atk - st._hp.value = hp + st.atk.value = atk + st.hp.value = hp func reset_sliders() -> void: - st._atk.value = 0 - st._hp.value = 0 + st.atk.value = 0 + st.hp.value = 0 _sync_labels() func effect_text() -> String: - return st._eff.text + return st.eff.text func set_sim_count(n: int) -> void: - st._sim_count.value = n + st.sim_count.value = n func is_running() -> bool: - return st._sim_running # 批量模拟进行中(与对局中禁用态分离,置灰 ≠ 在跑) + return st.sim_running # 批量模拟进行中(与对局中禁用态分离,置灰 ≠ 在跑) func set_sim_running(running: bool) -> void: - st._sim_running = running + st.sim_running = running _sync_sim_button() # 对局中上下文锁定(组合根 _sync_debug_overlay 喂入):批量模拟与两层调试入口 # 在活跃对局并存态不开放(长程异步模拟/并发对局编排与活跃结算并存态破坏状态机) func set_context_locked(locked: bool) -> void: - st._ctx_locked = locked + st.ctx_locked = locked _sync_sim_button() # 当局数据只读投影喂入(六项——组合根 _debug_run_data 构造;纯标签重查零动效) func update_run_data(data: Dictionary) -> void: - st._run_data = data.duplicate() - for key in st._run_labels: - (st._run_labels[key] as Label).text = str(data.get(key, "—")) + st.run_data = data.duplicate() + for key in st.run_labels: + (st.run_labels[key] as Label).text = str(data.get(key, "—")) # 当局数据快照读面(探针值对面——ui_tune_probe 断言六项与只读访问器逐项一致) func run_data_probe() -> Dictionary: - return st._run_data.duplicate() + return st.run_data.duplicate() # 两钮同判据(模拟运行 / 对局上下文锁定对「模拟」「两层调试」两入口等价)—— # 判据单点:两 setter 都经此同步,禁在调用点各写一份 func _sync_sim_button() -> void: - _sim_btn.disabled = st._sim_running or st._ctx_locked - _sim_btn.text = "模拟中…" if st._sim_running else "运行模拟" - _dbg_btn.disabled = st._sim_running or st._ctx_locked + _sim_btn.disabled = st.sim_running or st.ctx_locked + _sim_btn.text = "模拟中…" if st.sim_running else "运行模拟" + _dbg_btn.disabled = st.sim_running or st.ctx_locked func set_sim_progress(text: String) -> void: - st._sim_out.text = text + st.sim_out.text = text func set_sim_report(text: String) -> void: - st._sim_out.text = text + st.sim_out.text = text func output_text() -> String: - return st._sim_out.get_parsed_text() + return st.sim_out.get_parsed_text() # ---- 探针接缝(真实翻译出口——按钮 pressed 信号路径,非直调组合根)---- @@ -147,11 +148,11 @@ func _reset_btn_pressed() -> void: func effective_atk() -> int: - return UiKit.scaled(st._base_atk, atk_pct()) + return UiKit.scaled(st.base_atk, atk_pct()) func effective_max_hp() -> int: - return UiKit.scaled(st._base_hp, hp_pct()) + return UiKit.scaled(st.base_hp, hp_pct()) # ---- 内部构建(标题/关闭固定于浮窗骨架,可扩展列表区居中滚动)---- @@ -185,7 +186,7 @@ func _build_intro(box: VBoxContainer) -> void: var title := UiKit.label("调试",UiKit.FS_TITLE, true, UiKit.WARM) box.add_child(title) var desc := UiKit.label( - "开发调试专用(UI 最顶层「调试」浮层角标随时可开——任何界面不遮挡)。当局数据=本局只读投影,种子即本局种子唯一查看位。\n百分比叠加基础值:攻击 %d / 生命 %d。影响所有模式(手动与模拟),不落盘。" % [st._base_atk, st._base_hp], + "开发调试专用(UI 最顶层「调试」浮层角标随时可开——任何界面不遮挡)。当局数据=本局只读投影,种子即本局种子唯一查看位。\n百分比叠加基础值:攻击 %d / 生命 %d。影响所有模式(手动与模拟),不落盘。" % [st.base_atk, st.base_hp], 13, false, UiKit.WARM_SOFT) box.add_child(desc) box.add_child(HSeparator.new()) @@ -207,7 +208,7 @@ func _build_run_data(list: VBoxContainer) -> void: cap.custom_minimum_size = Vector2(52, 0) run_grid.add_child(cap) var val := UiKit.label("—",UiKit.FS_HINT, false, UiKit.WARM) - st._run_labels[item[0]] = val + st.run_labels[item[0]] = val run_grid.add_child(val) list.add_child(HSeparator.new()) @@ -241,13 +242,13 @@ func _build_seed_row(list: VBoxContainer) -> void: ## 区块②角色数值调整板(攻击/生命两滑轨+生效值行+应用/重置钮) func _build_slider_block(list: VBoxContainer) -> void: var atk_row := _slider_row(list, "攻击加成") - st._atk = atk_row.slider - st._atk_val = atk_row.value_label + st.atk = atk_row.slider + st.atk_val = atk_row.value_label var hp_row := _slider_row(list, "生命加成") - st._hp = hp_row.slider - st._hp_val = hp_row.value_label - st._eff = UiKit.label("",UiKit.FS_HINT, false) - list.add_child(st._eff) + st.hp = hp_row.slider + st.hp_val = hp_row.value_label + st.eff = UiKit.label("",UiKit.FS_HINT, false) + list.add_child(st.eff) var btn_row := HBoxContainer.new() list.add_child(btn_row) var apply_btn := UiKit.button("应用并关闭") @@ -264,21 +265,21 @@ func _build_sim_block(list: VBoxContainer) -> void: var sim_row := HBoxContainer.new() list.add_child(sim_row) sim_row.add_child(UiKit.label("批量模拟第一关",UiKit.FS_BODY, false)) - st._sim_count = SpinBox.new() - st._sim_count.min_value = 10 - st._sim_count.max_value = 200 - st._sim_count.step = 10 - st._sim_count.value = 30 - st._sim_count.custom_minimum_size = Vector2(90, 0) - sim_row.add_child(st._sim_count) + st.sim_count = SpinBox.new() + st.sim_count.min_value = 10 + st.sim_count.max_value = 200 + st.sim_count.step = 10 + st.sim_count.value = 30 + st.sim_count.custom_minimum_size = Vector2(90, 0) + sim_row.add_child(st.sim_count) sim_row.add_child(UiKit.label("次(理性玩家策略,含面板当前加成)",UiKit.FS_HINT, false)) _sim_btn = UiKit.button("运行模拟") _sim_btn.pressed.connect(_emit_simulate_requested) sim_row.add_child(_sim_btn) - st._sim_out = RichTextLabel.new() - st._sim_out.bbcode_enabled = true - st._sim_out.custom_minimum_size = Vector2(0, 300) - list.add_child(st._sim_out) + st.sim_out = RichTextLabel.new() + st.sim_out.bbcode_enabled = true + st.sim_out.custom_minimum_size = Vector2(0, 300) + list.add_child(st.sim_out) list.add_child(HSeparator.new()) @@ -316,14 +317,14 @@ func _slider_row(box: VBoxContainer, title: String) -> Dictionary: func _sync_labels() -> void: - st._atk_val.text = "+%d%%" % atk_pct() - st._hp_val.text = "+%d%%" % hp_pct() - st._eff.text = "生效值:攻击 %d → %d · 生命 %d → %d" % [ - st._base_atk, effective_atk(), st._base_hp, effective_max_hp()] + st.atk_val.text = "+%d%%" % atk_pct() + st.hp_val.text = "+%d%%" % hp_pct() + st.eff.text = "生效值:攻击 %d → %d · 生命 %d → %d" % [ + st.base_atk, effective_atk(), st.base_hp, effective_max_hp()] func _emit_simulate_requested() -> void: - simulate_requested.emit(int(st._sim_count.value)) + simulate_requested.emit(int(st.sim_count.value)) func _emit_diagnose_requested() -> void: diff --git a/game/presentation/views/level_map_screen.gd b/game/presentation/views/level_map_screen.gd index 7e7fb434..fd0af5e7 100644 --- a/game/presentation/views/level_map_screen.gd +++ b/game/presentation/views/level_map_screen.gd @@ -1,6 +1,7 @@ ## VERTICAL SLICE - NOT FOR PRODUCTION ## 关卡画面·独立屏:全屏不透明独占呈现(激活期战斗屏宿主由组合根整体隐藏),节点图选路呈现。 ## 前进唯一通道=图上点候选节点(route_chosen 外发)——零自动推进;放弃入口可见性归组合根(仅冒险模式开)。 +## 内部数据类 _MapState=宿主画面登记表(只被本文件填读,文件外零消费者——字段为公开数据成员)。 class_name LevelMapScreen extends Control @@ -32,23 +33,23 @@ const CONFIRM_WINDOW_S := 3.0 # 放弃确认态窗口( const COL_HP_BAR_BACK := UiKit.HP_BAR_BACK # 迷你血条深底(对暗面板可辨;值单点收 UiKit) ## 内部状态件(u2 子域拆件——契约 9 职责轴):_MapState=选路屏运行态与图绘制态(单一变化原因= -## 选路呈现)。_layer_decor 域字典承载换层装饰三件(图标贴图/石纹面/tint 层号——装饰域归并形态)。 +## 选路呈现)。layer_decor 域字典承载换层装饰三件(图标贴图/石纹面/tint 层号——装饰域归并形态)。 ## 探针直读冻结成员(_cand_ids/_hover_id/_content_size/_layout_cache/abandon_btn/status_badge)住宿主。 class _MapState extends RefCounted: - var _runner = null - var _title: Label - var _canvas: Control - var _level_label: Label - var _gp_value: Label - var _hp_value: Label - var _hp_bar: Control - var _attr_value: Label - var _bonus_row: HBoxContainer - var _bonus_value: Label - var _feat_value: Label # 特性持有行(组合根投喂逐件文案) - var _abandon_timer := ArmedTimer.new() # 放弃确认窗(_process 帧驱动倒计时机制收口;窗口=CONFIRM_WINDOW_S) - var _drawn_disp: Dictionary[int, Dictionary] = {} # 本停点实际绘制记录(_draw_graph 逐帧在实际绘制点重建——display_probe 消费面) - var _layer_decor := {"node_icons": {}, "noise_rect": null, "tint_level": 0} # 换层装饰域(节点图标贴图 load_tex 守卫缺位 null→字形回退/屏底石纹面/当前已应用 tint 层号 0=未应用) + var runner = null + var title: Label + var canvas: Control + var level_label: Label + var gp_value: Label + var hp_value: Label + var hp_bar: Control + var attr_value: Label + var bonus_row: HBoxContainer + var bonus_value: Label + var feat_value: Label # 特性持有行(组合根投喂逐件文案) + var abandon_timer := ArmedTimer.new() # 放弃确认窗(_process 帧驱动倒计时机制收口;窗口=CONFIRM_WINDOW_S) + var drawn_disp: Dictionary[int, Dictionary] = {} # 本停点实际绘制记录(_draw_graph 逐帧在实际绘制点重建——display_probe 消费面) + var layer_decor := {"node_icons": {}, "noise_rect": null, "tint_level": 0} # 换层装饰域(节点图标贴图 load_tex 守卫缺位 null→字形回退/屏底石纹面/当前已应用 tint 层号 0=未应用) var _cand_ids: Array[int] = [] # 本停点候选 id 集(图上命中合法集;探针直读冻结——住宿主) var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它;探针直读冻结) @@ -60,18 +61,18 @@ var _map := _MapState.new() func _init() -> void: - _map._abandon_timer.expired.connect(_reset_abandon_confirm) + _map.abandon_timer.expired.connect(_reset_abandon_confirm) # 节点图标贴图装载(load_tex 守卫,缺位回退现行字形) for t in TEX_NODE_ICONS: - _map._layer_decor.node_icons[t] = UiKit.load_tex(TEX_NODE_ICONS[t]) + _map.layer_decor.node_icons[t] = UiKit.load_tex(TEX_NODE_ICONS[t]) # 独立屏底(spec Layout——demo #v-map 暖棕竖向 # 渐变(#4A3B2A 0%→#3A2E22 40%)+石纹弱质感 0.14+暗角聚焦;底纹缺位回退纯色) - var base := UiKit._gradient_base(UiKit.BG_MAP_TOP, UiKit.BG_ROOT) + var base := UiKit.gradient_base(UiKit.BG_MAP_TOP, UiKit.BG_ROOT) add_child(base) var tex := UiKit.backdrop_tile() if tex != null: base.add_child(tex) - _map._layer_decor.noise_rect = tex # 世界侧 tint 承载面(换层色板渐变挂点——§2.7 切片档) + _map.layer_decor.noise_rect = tex # 世界侧 tint 承载面(换层色板渐变挂点——§2.7 切片档) base.add_child(UiKit.vignette_rect()) var center := CenterContainer.new() center.set_anchors_preset(Control.PRESET_FULL_RECT) @@ -97,8 +98,8 @@ func _build_top_bar(box: VBoxContainer) -> void: lvl_plate.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.DARK_UPCOMING, UiKit.PANEL_BORDER, 0, 1, Vector4(12, 3, 12, 3))) top.add_child(lvl_plate) - _map._level_label = UiKit.label("",UiKit.FS_BODY, true, UiKit.WARM) - lvl_plate.add_child(_map._level_label) + _map.level_label = UiKit.label("",UiKit.FS_BODY, true, UiKit.WARM) + lvl_plate.add_child(_map.level_label) var top_sp := Control.new() top_sp.size_flags_horizontal = Control.SIZE_EXPAND_FILL top.add_child(top_sp) @@ -109,31 +110,31 @@ func _build_top_bar(box: VBoxContainer) -> void: var gp_row := HBoxContainer.new() gp_row.add_theme_constant_override("separation", 6) gp_box.add_child(gp_row) - _map._gp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_ON_GOLD) - gp_row.add_child(_map._gp_value) + _map.gp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_ON_GOLD) + gp_row.add_child(_map.gp_value) status_badge = UiKit.button("状态", 0.0) status_badge.add_theme_font_size_override("font_size", UiKit.FS_HINT) status_badge.tooltip_text = "查看本局冒险进度(只读)" status_badge.size_flags_vertical = Control.SIZE_SHRINK_CENTER top.add_child(status_badge) - _map._title = UiKit.label(TITLE_FORK,UiKit.FS_SUB, true, UiKit.WARM) - _map._title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - box.add_child(_map._title) + _map.title = UiKit.label(TITLE_FORK,UiKit.FS_SUB, true, UiKit.WARM) + _map.title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + box.add_child(_map.title) ## 节点图画布域(图上点击 = 唯一提交通道——布局决策 2;mouse_filter STOP 接悬停/点击) func _build_canvas(box: VBoxContainer) -> void: - _map._canvas = Control.new() - _map._canvas.custom_minimum_size = Vector2(620, 240) - _map._canvas.mouse_filter = Control.MOUSE_FILTER_STOP - _map._canvas.draw.connect(_draw_graph.bind(_map._canvas)) - _map._canvas.gui_input.connect(_on_canvas_input) - box.add_child(_map._canvas) + _map.canvas = Control.new() + _map.canvas.custom_minimum_size = Vector2(620, 240) + _map.canvas.mouse_filter = Control.MOUSE_FILTER_STOP + _map.canvas.draw.connect(_draw_graph.bind(_map.canvas)) + _map.canvas.gui_input.connect(_on_canvas_input) + box.add_child(_map.canvas) ## 底部状态栏域(合并单条:亮米面板一站读齐=头像+生命+属性+本关加成/流派件——demo ④; ## 道具槽不摆=道具读数无关卡画面成文出口(读快照私域越权); -## _hp_value/_attr_value/_bonus_value/_feat_value 投喂键全部保留。 +## hp_value/attr_value/bonus_value/feat_value 投喂键全部保留。 ## 底栏定高纪律(对齐 demo——单条 ~85px 紧凑亮条;任何子件不再能把栏撑爆: ## 特性持有行限宽截断+tooltip 全文)) func _build_status_bar(box: VBoxContainer) -> void: @@ -150,18 +151,18 @@ func _build_status_bar(box: VBoxContainer) -> void: var life_box := VBoxContainer.new() strip.add_child(life_box) life_box.add_child(UiKit.label("生命",UiKit.FS_CAPTION, false, UiKit.INK_HINT)) - _map._hp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE) - life_box.add_child(_map._hp_value) - _map._hp_bar = Control.new() - _map._hp_bar.custom_minimum_size = Vector2(130, 7) - _map._hp_bar.draw.connect(_draw_hp_bar.bind(_map._hp_bar)) - life_box.add_child(_map._hp_bar) + _map.hp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE) + life_box.add_child(_map.hp_value) + _map.hp_bar = Control.new() + _map.hp_bar.custom_minimum_size = Vector2(130, 7) + _map.hp_bar.draw.connect(_draw_hp_bar.bind(_map.hp_bar)) + life_box.add_child(_map.hp_bar) strip.add_child(_vdiv()) var attr_box := VBoxContainer.new() strip.add_child(attr_box) attr_box.add_child(UiKit.label("属性",UiKit.FS_CAPTION, false, UiKit.INK_HINT)) - _map._attr_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE) - attr_box.add_child(_map._attr_value) + _map.attr_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE) + attr_box.add_child(_map.attr_value) strip.add_child(_vdiv()) _build_bonus_group(strip) var spacer := Control.new() @@ -193,21 +194,21 @@ func _build_face_rect() -> TextureRect: ## 本关加成框域(零加成隐藏——spec 布局决策 4)+ 流派件持有行(组合根投喂;限宽单行截断+tooltip 全文) func _build_bonus_group(strip: HBoxContainer) -> void: - _map._bonus_row = HBoxContainer.new() - strip.add_child(_map._bonus_row) + _map.bonus_row = HBoxContainer.new() + strip.add_child(_map.bonus_row) var bonus_box := VBoxContainer.new() - _map._bonus_row.add_child(bonus_box) + _map.bonus_row.add_child(bonus_box) var bonus_inner := VBoxContainer.new() bonus_box.add_child(bonus_inner) var bonus_cap := UiKit.label("属性值提升",UiKit.FS_CAPTION, false, UiKit.INK_HINT) # 深墨(亮板可读) bonus_inner.add_child(bonus_cap) - _map._bonus_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE) # 淡墨→深墨(同上) - bonus_inner.add_child(_map._bonus_value) - _map._feat_value = UiKit.label("",UiKit.FS_HINT, false, UiKit.INK_HINT) - _map._feat_value.custom_minimum_size = Vector2(240, 0) - _map._feat_value.clip_text = true - _map._feat_value.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS - bonus_box.add_child(_map._feat_value) + _map.bonus_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_TITLE) # 淡墨→深墨(同上) + bonus_inner.add_child(_map.bonus_value) + _map.feat_value = UiKit.label("",UiKit.FS_HINT, false, UiKit.INK_HINT) + _map.feat_value.custom_minimum_size = Vector2(240, 0) + _map.feat_value.clip_text = true + _map.feat_value.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + bonus_box.add_child(_map.feat_value) # 竖分隔线(底栏分组线——亮面板上低对比细线) @@ -221,10 +222,10 @@ static func _vdiv() -> ColorRect: func _draw_hp_bar(bar: Control) -> void: # 迷你血条(深底 UiKit.HP_BAR_BACK #28221C + 小人蓝填充——与战斗站桩血条同语言, # 「蓝只说己方」§4.2)。比例 = hero_hp / effective_max_hp - if _map._runner == null: + if _map.runner == null: return - var max_hp := maxi(1, _map._runner.effective_max_hp()) - var ratio := clampf(float(_map._runner.hero_hp) / float(max_hp), 0.0, 1.0) + var max_hp := maxi(1, _map.runner.effective_max_hp()) + var ratio := clampf(float(_map.runner.hero_hp) / float(max_hp), 0.0, 1.0) var full := Rect2(Vector2.ZERO, bar.size) bar.draw_rect(full, COL_HP_BAR_BACK) if ratio > 0.0: @@ -234,8 +235,8 @@ func _draw_hp_bar(bar: Control) -> void: # 放弃入口可见性(组合根在冒险启动时开、默认关——语义对象 = 整局冒险) # 特性持有投影(组合根投喂逐件文案;空文案=行隐藏) func set_feat_holdings(text: String) -> void: - _map._feat_value.text = text - _map._feat_value.tooltip_text = text # 截断全量兜底(限宽单行省略——悬停看全文) + _map.feat_value.text = text + _map.feat_value.tooltip_text = text # 截断全量兜底(限宽单行省略——悬停看全文) func set_abandon_available(v: bool) -> void: @@ -245,8 +246,8 @@ func set_abandon_available(v: bool) -> void: # 放弃二次确认(布局决策 3):首击转确认态(文案+危险红脉冲约 3 秒超时回落),二击才提交。 # 单击不提交——ui_probe 战斗中 pressed.emit() 单击路径保持「未受理」语义(战斗中拒绝归组合根)。 func _on_abandon_pressed() -> void: - if not _map._abandon_timer.is_armed(): - _map._abandon_timer.arm(CONFIRM_WINDOW_S) + if not _map.abandon_timer.is_armed(): + _map.abandon_timer.arm(CONFIRM_WINDOW_S) abandon_btn.text = "再点一次确认放弃——不可逆" if ArmedTimer.motion_still(): abandon_btn.modulate = Color(1, 1, 1, 0.75) # 减动效静态基色(脉冲层不再驱动) @@ -256,45 +257,45 @@ func _on_abandon_pressed() -> void: func _reset_abandon_confirm() -> void: - _map._abandon_timer.disarm() + _map.abandon_timer.disarm() abandon_btn.modulate = Color(1, 1, 1, 1) abandon_btn.text = "放弃" # 文案收敛 level-map 4 字上限(确认态转义面见 _on_abandon_pressed) func _process(delta: float) -> void: - if not _map._abandon_timer.is_armed(): + if not _map.abandon_timer.is_armed(): return - _map._abandon_timer.tick(delta) - if not _map._abandon_timer.is_armed(): + _map.abandon_timer.tick(delta) + if not _map.abandon_timer.is_armed(): return # 窗口到点回落(expired 信号已驱动 _reset_abandon_confirm) # 危险红边框脉冲(单段呼吸约 1Hz——闪烁基线 ≤3 次/秒内;减动效 = 静态半透不闪——档判据单点 motion_still) if ArmedTimer.motion_still(): abandon_btn.modulate = Color(1, 1, 1, 0.75) else: - var k := 0.5 + 0.5 * sin(_map._abandon_timer.elapsed * TAU) + var k := 0.5 + 0.5 * sin(_map.abandon_timer.elapsed * TAU) abandon_btn.modulate = Color(1, 1, 1, 0.65 + 0.35 * k) # 打开选路口(cands = 候选节点引用数组;runner 只读用于整图渲染与状态栏刷新) func open_choices(cands: Array, runner: LevelRunner) -> void: var was_visible := visible # 进入转场判据(就地刷新/模态栈恢复不重放淡入) - _map._runner = runner + _map.runner = runner _hover_id = -1 _reset_abandon_confirm() var visited: Array[int] = [] if runner != null: visited = runner.visited_ids() if cands.size() > 1: - _map._title.text = TITLE_FORK + _map.title.text = TITLE_FORK elif visited.is_empty(): - _map._title.text = TITLE_ENTRY # 入口停点(尚未走出第一步) + _map.title.text = TITLE_ENTRY # 入口停点(尚未走出第一步) else: - _map._title.text = TITLE_SINGLE + _map.title.text = TITLE_SINGLE _cand_ids = [] for cand in cands: _cand_ids.append(_id_of(cand)) - _fit_canvas(_map._runner.nodes()) - _apply_level_tint(int(_map._runner.level_row.get("level_index", 0))) + _fit_canvas(_map.runner.nodes()) + _apply_level_tint(int(_map.runner.level_row.get("level_index", 0))) visible = true if not was_visible: # 独立屏淡入(spec Transitions:约 200ms;alpha 淡入非明度变化——art-bible §2 明度恒定) @@ -321,7 +322,7 @@ func _fit_canvas(nodes: Array) -> void: _content_size = Vector2(LAYER_BASE_X + maxl * LAYER_STEP_X + NODE_HALF.x + 8.0, SLOT_BASE_Y + maxslot * SLOT_STEP_Y + NODE_HALF.y + 24.0) # +24 = 类型文字标签行 _content_size = _content_size.max(Vector2(620, 256)) # 下限(抬高容纳标签行) - _map._canvas.custom_minimum_size = _content_size + _map.canvas.custom_minimum_size = _content_size # 换层色板渐变挂点(art-bible §2.7 下探过渡切片档——关卡结算确认→下一关→独立屏淡入链路上的 @@ -332,39 +333,39 @@ const BOARD_TINTS := {1: Color(1, 1, 1), 2: Color(0.84, 0.99, 1.28), 3: Color(0. func _apply_level_tint(level_index: int) -> void: - if _map._layer_decor.noise_rect == null or level_index == _map._layer_decor.tint_level: + if _map.layer_decor.noise_rect == null or level_index == _map.layer_decor.tint_level: return - _map._layer_decor.tint_level = level_index + _map.layer_decor.tint_level = level_index var target: Color = BOARD_TINTS.get(level_index, Color(1, 1, 1)) var tw := create_tween() - tw.tween_property(_map._layer_decor.noise_rect, "modulate", + tw.tween_property(_map.layer_decor.noise_rect, "modulate", Color(target.r, target.g, target.b, UiKit.BACKDROP_TILE_ALPHA), UiKit.motion(0.45)) # 0.14 弱质感档随 tint 保持 # 状态栏投喂(命令式纪律——组合根经 level_map_status_payload 取成品后喂入;零推送信号订阅、零核心态拼装) func apply_status(payload: Dictionary) -> void: - _map._level_label.text = String(payload["level_label"]) - _map._gp_value.text = String(payload["gp_text"]) - _map._hp_value.text = String(payload["hp_text"]) + _map.level_label.text = String(payload["level_label"]) + _map.gp_value.text = String(payload["gp_text"]) + _map.hp_value.text = String(payload["hp_text"]) if bool(payload["hp_bar_redraw"]): - _map._hp_bar.queue_redraw() - _map._attr_value.text = String(payload["attr_text"]) - _map._bonus_value.text = String(payload["bonus_text"]) + _map.hp_bar.queue_redraw() + _map.attr_value.text = String(payload["attr_text"]) + _map.bonus_value.text = String(payload["bonus_text"]) # 特性持有行可见性(与加成行并框——任一非空整框显示;文案归组合根 set_feat_holdings) - _map._bonus_row.visible = bool(payload["bonus_row_visible"]) or _map._feat_value.text != "" + _map.bonus_row.visible = bool(payload["bonus_row_visible"]) or _map.feat_value.text != "" # ---- 图上命中(布局决策 2——唯一提交通道;探针 press_candidate 同路径) ---- func _on_canvas_input(ev: InputEvent) -> void: - if _map._runner == null or not visible: + if _map.runner == null or not visible: return if ev is InputEventMouseMotion: var nid := _candidate_at(ev.position) # gui_input 事件坐标 = 接收控件局部坐标(push_input 同成立) if nid != _hover_id: _hover_id = nid - _map._canvas.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND if nid >= 0 else Control.CURSOR_ARROW - _map._canvas.queue_redraw() + _map.canvas.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND if nid >= 0 else Control.CURSOR_ARROW + _map.canvas.queue_redraw() elif ev is InputEventMouseButton and ev.button_index == MOUSE_BUTTON_LEFT and ev.pressed: var nid2 := _candidate_at(ev.position) if nid2 >= 0: @@ -373,10 +374,10 @@ func _on_canvas_input(ev: InputEvent) -> void: # 候选命中:位置 = 布局公式(与 _draw_graph 同源),矩形 = 节点方块 + 外扩容差;非候选格不响应 func _candidate_at(local_pos: Vector2) -> int: - if _map._runner == null: + if _map.runner == null: return -1 - var nodes: Array[Variant] = _map._runner.nodes() - var pos := _layout(nodes, _map._canvas) + var nodes: Array[Variant] = _map.runner.nodes() + var pos := _layout(nodes, _map.canvas) for nid in _cand_ids: var node := _node_by_id(nid, nodes) if node.is_empty(): @@ -391,7 +392,7 @@ func _candidate_at(local_pos: Vector2) -> int: func _submit_choice(nid: int) -> void: # 独立屏分支:战斗/Boss 节点 = 出屏切回战斗屏;奖励/恢复/商店节点 = # 本屏保持激活——兑付覆盖层由节点事件在本屏域内弹出(模态栈遮挡恢复语义复用),兑付确认后回图刷新 - var node := _node_by_id(nid, _map._runner.nodes()) + var node := _node_by_id(nid, _map.runner.nodes()) var t := String(node.get("type", "")) if t != "reward" and t != "rest" and t != "shop": visible = false @@ -400,13 +401,13 @@ func _submit_choice(nid: int) -> void: # 探针复用:按候选序号走图上命中同路径(候选节点中心坐标 → 命中检测 → 提交——布局决策 2 探针面改造) func press_candidate(index: int) -> void: - if index < 0 or index >= _cand_ids.size() or _map._runner == null: + if index < 0 or index >= _cand_ids.size() or _map.runner == null: return - var nodes: Array[Variant] = _map._runner.nodes() + var nodes: Array[Variant] = _map.runner.nodes() var node := _node_by_id(int(_cand_ids[index]), nodes) if node.is_empty(): return - var pos: Vector2 = _layout(nodes, _map._canvas)[node] + var pos: Vector2 = _layout(nodes, _map.canvas)[node] var nid := _candidate_at(pos) # 节点中心必命中自身(命中面含节点方块) if nid >= 0: _submit_choice(nid) @@ -414,7 +415,7 @@ func press_candidate(index: int) -> void: func _id_of(node: Dictionary) -> int: # 编号显示经行进器读面(编号空间解析单一出口——单链 = 下标 / 图 = node_depth) - return _map._runner.node_id_of(node) + return _map.runner.node_id_of(node) # ---- 节点呈现决策面(信息迷雾 v2.1:类型仅在可见集内出示,Boss 标记不入雾)---- @@ -440,9 +441,9 @@ static func node_display(node: Dictionary, nid: int, type_vis: Array, # ---- 整图自绘(程序化占位——分层布局:层 = 入口 BFS 深度,同层垂直排布)---- func _draw_graph(canvas: Control) -> void: - if _map._runner == null or not visible: + if _map.runner == null or not visible: return - _map._drawn_disp.clear() # 绘制记录随本帧重建——只反映实际发生的绘制(接线坏的判据面) + _map.drawn_disp.clear() # 绘制记录随本帧重建——只反映实际发生的绘制(接线坏的判据面) var ctx := _draw_context(canvas) _draw_passage_layer(canvas, ctx) for node in ctx["nodes"]: @@ -451,16 +452,16 @@ func _draw_graph(canvas: Control) -> void: ## 绘制上下文束(单次取数——布局/字体/编号空间/可见集,绘制域共享只读面) func _draw_context(canvas: Control) -> Dictionary: - var nodes: Array[Variant] = _map._runner.nodes() + var nodes: Array[Variant] = _map.runner.nodes() return { "nodes": nodes, "pos": _layout(nodes, canvas), "font": get_theme_default_font(), # 绘制路径字体单次取用(三处 draw_string 共享——零语义差) - "current_id": _map._runner.current_node_id(), + "current_id": _map.runner.current_node_id(), # 当前节点 id(单链=下标 / 图=编号)——visited_ids() 已按编号空间解析 - "visited": _map._runner.visited_ids(), - "cand_ids": _map._runner.next_candidate_ids(), - "type_vis": _map._runner.type_visible_ids(), # 本停点类型可见集(每帧一取——纯函数零状态) + "visited": _map.runner.visited_ids(), + "cand_ids": _map.runner.next_candidate_ids(), + "type_vis": _map.runner.type_visible_ids(), # 本停点类型可见集(每帧一取——纯函数零状态) } @@ -525,7 +526,7 @@ func _draw_room(canvas: Control, node: Dictionary, ctx: Dictionary) -> void: var label_rec := _draw_room_label(canvas, rect, disp, nid, ctx) rec["draw_label"] = bool(label_rec.get("draw_label", false)) rec["label_text"] = String(label_rec.get("label_text", "")) - _map._drawn_disp[nid] = rec + _map.drawn_disp[nid] = rec ## 节点图标域(20px 烘焙 1:1 消费;双色墨制=乘法调制——当前金底深墨,暗底系 WARM 原色; @@ -536,7 +537,7 @@ func _draw_room_icon(canvas: Control, node: Dictionary, disp: Dictionary, ctx: D var p: Vector2 = ctx["pos"][node] var col := _room_color(node, ctx) var t := String(node.get("type", "")) - var icon: Texture2D = _map._layer_decor.node_icons.get(t) + var icon: Texture2D = _map.layer_decor.node_icons.get(t) if icon != null: var tint := UiKit.ICON_TINT_ON_GOLD if col == COL_ROOM_CURRENT else Color(1.0, 1.0, 1.0) canvas.draw_texture_rect(icon, Rect2(p - Vector2(NODE_ICON_PX, NODE_ICON_PX) / 2.0, @@ -580,20 +581,20 @@ func _layout(nodes: Array, canvas: Control) -> Dictionary: # ---- 探针只读出口(形态仿 fx_probe 先例:只读快照) ---- -# 选路布局只读快照(node → 局部位置;画布固定取现行 _map._canvas,调用侧零私有域触达) +# 选路布局只读快照(node → 局部位置;画布固定取现行 _map.canvas,调用侧零私有域触达) func layout_probe(nodes: Array) -> Dictionary: - return _layout(nodes, _map._canvas) + return _layout(nodes, _map.canvas) # 路由画布只读快照(origin=全局原点;min_size=最小拟合尺寸——画布拟合断言消费面) func canvas_probe() -> Dictionary: - return {"origin": _map._canvas.get_global_position(), "min_size": _map._canvas.custom_minimum_size} + return {"origin": _map.canvas.get_global_position(), "min_size": _map.canvas.custom_minimum_size} # 类型可见性绘制只读记录(id → 本帧实际绘制事实:图标/文字/描边/文案;_draw_graph 在实际绘制点写入—— # 接线层断言消费点:绕开决策面直接画,这里就露出「雾下也画了」) func display_probe() -> Dictionary: - return _map._drawn_disp + return _map.drawn_disp # 分层(layout/fit 共享件):layer = 入口 BFS 深度;同层 slot 垂直排布;未达节点兜底排末层 @@ -637,7 +638,7 @@ func _entry(nodes: Array) -> Dictionary: func _out_ids(node: Dictionary, _nodes: Array) -> Array: - return _map._runner.node_out_ids(node) + return _map.runner.node_out_ids(node) func _node_by_id(nid: int, nodes: Array) -> Dictionary: diff --git a/game/presentation/views/seal_mark.gdshader b/game/presentation/views/seal_mark.gdshader new file mode 100644 index 00000000..affc4164 --- /dev/null +++ b/game/presentation/views/seal_mark.gdshader @@ -0,0 +1,50 @@ +// VERTICAL SLICE - NOT FOR PRODUCTION +// 封印印记(卡④ 法阵化批)——环+十字+环外压暗光圈,canvas_item 距离场实时绘制(无贴图缩放, +// 格边 26–46px 连续可变下任意格边清晰)。 +// 事实源:art-bible v3.41 §3.2「封印印记」(形/画法/光圈/层序纪律)+ §8.2「封印印记参数表」; +// gdd/ux/battle.md「棋盘格尺寸公式」(格边 clampi 26–46)。 +// 纪律:参数五枚+色全由 gd 侧逐次注入(参数单点 = ui_kit SEAL_* 五常量——禁在此改值, +// 下方默认值仅为兜底且与 §8.2 同值);光圈不出格(半径夹 cell_px/2); +// 零动效(不呼吸不脉冲——§2.1 挖掘期唯一在动 = 接触带微光);印记与光圈同用一色(光圈只改 alpha)。 +// 坐标口径:VERTEX 为格件本件局部像素坐标(0..cell_px),不依赖 UV(同 cell_type_glow 口径)。 +shader_type canvas_item; +render_mode blend_mix; + +uniform float cell_px : hint_range(8.0, 128.0) = 46.0; +uniform float ring_frac = 0.82; +uniform float wall_frac = 0.11; +uniform float arm_frac = 0.156; +uniform float halo_frac = 0.09; +uniform float halo_alpha : hint_range(0.0, 1.0) = 0.50; +uniform float halo_gamma = 1.6; +uniform vec4 mark_col : source_color = vec4(0.0, 0.0, 0.0, 1.0); + +varying vec2 v_local; + + +void vertex() { + v_local = VERTEX; +} + + +void fragment() { + vec2 d2 = v_local - vec2(cell_px * 0.5); + float r = length(d2); + float ring_r = 0.5 * cell_px * ring_frac; + float wall = wall_frac * 2.0 * ring_r; + float arm = arm_frac * 2.0 * ring_r; + float halo_w = 0.5 * cell_px * halo_frac; + // 印记体:环带(ring_r-wall/2 ≤ r ≤ ring_r)或十字(轴向带内且 r < ring_r——四端顶到环内沿) + bool in_ring = (r <= ring_r) && (r >= ring_r - wall * 0.5); + bool in_arm = (r < ring_r) && ((abs(d2.x) < arm * 0.5) || (abs(d2.y) < arm * 0.5)); + float a = 0.0; + if (in_ring || in_arm) { + a = 1.0; + } else if (r > ring_r) { + // 环外压暗光圈:自环外缘向外衰减(半径夹 cell_px/2——不出格、不外溢邻格) + float rc = min(r, cell_px * 0.5); + float t = clamp((rc - ring_r) / max(halo_w, 0.001), 0.0, 1.0); + a = halo_alpha * pow(1.0 - t, halo_gamma); + } + COLOR = vec4(mark_col.rgb, mark_col.a * a); +} diff --git a/game/presentation/views/seal_mark.gdshader.uid b/game/presentation/views/seal_mark.gdshader.uid new file mode 100644 index 00000000..b6e8bcda --- /dev/null +++ b/game/presentation/views/seal_mark.gdshader.uid @@ -0,0 +1 @@ +uid://bqgctor0wpjt7 diff --git a/game/presentation/views/seal_mark_layer.gd b/game/presentation/views/seal_mark_layer.gd new file mode 100644 index 00000000..0b6ba054 --- /dev/null +++ b/game/presentation/views/seal_mark_layer.gd @@ -0,0 +1,42 @@ +## VERTICAL SLICE - NOT FOR PRODUCTION +## SealMarkLayer——封印印记的独占承载子件(挂 BoardCellUnit 之下、1:1 贴格;复用 cell_type_glow_layer +## 的子件模式:材质独占、全幅 draw_rect、非零 alpha 触发——不复制其函数体,微光层件零改动)。 +## 层序:印记层在 CellUnit 子树内(低于 BoardView 层的小人精灵——hero 精灵同层后挂天然在上); +## 迷雾态(fog_state != 0)整层隐藏由宿主 fog_state setter 联动承载(§6.5「封印随格隐没」)。 +## 零动效(不呼吸不脉冲——§2.1 挖掘期唯一在动=接触带微光);参数与色全由 gd 注入 +##(单点 = ui_kit SEAL_* 常量——本件禁内联色值与参数字面值)。 +extends Node2D + +const _SEAL_SHADER := preload("res://presentation/views/seal_mark.gdshader") + +var _mat: ShaderMaterial = null +var _edge := 0.0 + + +# 作用集侧唯一入口(on=false 仅隐不撤——子件生命周期归宿主 _sync_seal_layer) +func setup(edge: float, on: bool) -> void: + _edge = edge + visible = on + if not on: + return + if _mat == null: + _mat = ShaderMaterial.new() + _mat.shader = _SEAL_SHADER + material = _mat + _mat.set_shader_parameter("cell_px", edge) + _mat.set_shader_parameter("ring_frac", UiKit.SEAL_RING_FRAC) + _mat.set_shader_parameter("wall_frac", UiKit.SEAL_WALL_FRAC) + _mat.set_shader_parameter("arm_frac", UiKit.SEAL_ARM_FRAC) + _mat.set_shader_parameter("halo_frac", UiKit.SEAL_HALO_FRAC) + _mat.set_shader_parameter("halo_alpha", UiKit.SEAL_HALO_ALPHA) + _mat.set_shader_parameter("halo_gamma", UiKit.SEAL_HALO_GAMMA) + _mat.set_shader_parameter("mark_col", UiKit.SEAL_BLACK) + queue_redraw() + + +func _draw() -> void: + if _edge <= 0.0: + return + # 一格全幅:片元着色器按距离场产出环+十字+环外光圈。 + # 输入 alpha 取 1/255(非零)——全透明几何在部分批次路径下会被整条跳过,着色器就跑不到。 + draw_rect(Rect2(Vector2.ZERO, Vector2(_edge, _edge)), Color(1, 1, 1, 1.0 / 255.0)) diff --git a/game/presentation/views/seal_mark_layer.gd.uid b/game/presentation/views/seal_mark_layer.gd.uid new file mode 100644 index 00000000..ecef83c8 --- /dev/null +++ b/game/presentation/views/seal_mark_layer.gd.uid @@ -0,0 +1 @@ +uid://oyfwx5p78pud diff --git a/game/presentation/views/settings_overlay.gd b/game/presentation/views/settings_overlay.gd index 8850af4e..c547c927 100644 --- a/game/presentation/views/settings_overlay.gd +++ b/game/presentation/views/settings_overlay.gd @@ -85,7 +85,7 @@ func _build_fullscreen_row(box: VBoxContainer) -> void: var fs_lab := UiKit.label("全屏",UiKit.FS_BODY, false, UiKit.INK_LABEL) fs_lab.custom_minimum_size = Vector2(140, 0) # 同上 fs_row.add_child(fs_lab) - _fs_btn = UiKit._switch_toggle(false) + _fs_btn = UiKit.switch_toggle(false) _fs_btn.toggled.connect(_on_fullscreen_toggled) fs_row.add_child(_fs_btn) _fs_st = UiKit.label("关",UiKit.FS_HINT, true, UiKit.INK_LABEL) @@ -102,7 +102,7 @@ func _build_motion_row(box: VBoxContainer) -> void: var motion_lab := UiKit.label("结算演出动画",UiKit.FS_BODY, false, UiKit.INK_LABEL) motion_lab.custom_minimum_size = Vector2(140, 0) # 同上 motion_row.add_child(motion_lab) - _motion_btn = UiKit._switch_toggle(true) + _motion_btn = UiKit.switch_toggle(true) _motion_btn.toggled.connect(_on_motion_toggled) motion_row.add_child(_motion_btn) _motion_st = UiKit.label("完整",UiKit.FS_HINT, true, UiKit.INK_LABEL) diff --git a/game/presentation/views/standee_body.gd b/game/presentation/views/standee_body.gd index 7c17035f..c72abe27 100644 --- a/game/presentation/views/standee_body.gd +++ b/game/presentation/views/standee_body.gd @@ -3,8 +3,9 @@ ## 站桩形态为定案——禁回卡片式;立绘素材缺位回退字形占位;击杀呈现禁瞬消(灰化塌陷)。 ## 无 class_name——经 preload 引用;壳层坐标换算单点在 BattleStageView.world_to_shell()。 ## 内部状态两件(u4 拆件——st=身份命值态 standee_vitals、mo=资产演出态 standee_motion,_init 自建); -## 外部直访冻结成员(box/font/slot_row/has_world_hp_bar)与源码锚承载成员(_intent_charge——theme -## 源锚句所在行逐字保留)与三个演出探针计数器住宿主。 +## 子件往来走公开面(pres封装 A2——身份经 configure_identity 交付、死亡/意图/闪白走行为方法、 +## 绘制读数走公开读法);外部直访冻结成员(box/font/slot_row/has_world_hp_bar)与源码锚承载成员 +## (_intent_charge——theme 源锚句所在行逐字保留)与三个演出探针计数器住宿主。 extends Node2D const CHARGE_SKILL_IDS := ["heavy"] @@ -36,8 +37,8 @@ var _flash_count := 0 # 探针出口计数(受击闪白触发次 func _init(kind: String = "enemy") -> void: st = _VITALS.new(self) mo = _MOTION.new(self) - st._kind = kind - mo._hero_state = _MOTION.HeroState.IDLE + st.configure_identity(kind, "", "", "light") + mo.reset_state() func _efont() -> Font: @@ -45,17 +46,17 @@ func _efont() -> Font: func display_name() -> String: - return st._display_name + return st.display_name() # 敌单位身份出口(d12 站桩解析主键——同名敌按 id 精确匹配;主角站桩恒空串) func enemy_id() -> String: - return st._enemy_id + return st.enemy_id() # 意图徽标探针出口 func intent_probe() -> Dictionary: - return {"visible": mo._intent_visible, "text": mo._intent_glyph} + return {"visible": mo.intent_visible(), "text": mo.intent_glyph()} # 名字行探针出口(ui_probe 呈现口径 + 本批 V6/V8 断言面:armor_icon = 绑定纹理路径〔缺位回退字形〕、 @@ -63,10 +64,10 @@ func intent_probe() -> Dictionary: # name_font_size = 名字行字号档实读(迭代 46 卡B——提档 FS_HINT 14 的几何单点出口)) func name_line_probe() -> Dictionary: var f := _efont() - var tex := UiKit.load_tex(_armor_tex_path(st._armor)) + var tex := UiKit.load_tex(_armor_tex_path(st.armor())) var plate := _name_plate_rect(f, tex) - return {"name": st._display_name, - "armor_icon": _armor_tex_path(st._armor) if tex != null else String(ARMOR_ICONS.get(st._armor, "◇")), + return {"name": st.display_name(), + "armor_icon": _armor_tex_path(st.armor()) if tex != null else String(ARMOR_ICONS.get(st.armor(), "◇")), "armor_tex_bound": tex != null, "plate": plate, "baseline_y": _name_baseline(plate).y, "plinth_top_y": _pedestal_rect().position.y, @@ -79,23 +80,21 @@ func name_line_probe() -> Dictionary: # data = 战斗结算 presentation_snapshot enemies[] 投影:{order, display_name, hp, max_hp, enemy_id, # pending_skill/pending_type/pending_name, effects[](该敌自己名下增益——各立绘各读各的,增益状态 v1.5 个体投影)} func set_data(d: Dictionary) -> void: - st._display_name = String(d.get("display_name", "敌人")) - st._enemy_id = String(d.get("enemy_id", "")) - st._armor = String(d.get("armor_type", "light")) - st._hp_max = float(maxi(1, int(d.get("max_hp", 1)))) + st.configure_identity(st.kind(), String(d.get("display_name", "敌人")), String(d.get("enemy_id", "")), String(d.get("armor_type", "light"))) + st.set_max_hp(float(maxi(1, int(d.get("max_hp", 1))))) st.apply_hp(int(d.get("hp", 0))) _set_order(int(d.get("order", 1))) set_pending(String(d.get("pending_type", "")), String(d.get("pending_skill", ""))) set_effects(d.get("effects", [])) queue_redraw() - if st._dead and int(d.get("hp", 0)) > 0: + if st.is_dead() and int(d.get("hp", 0)) > 0: _revive() func set_hero(hp: int, max_hp: int) -> void: - st._hp_max = float(maxi(1, max_hp)) + st.set_max_hp(float(maxi(1, max_hp))) st.apply_hp(hp) - if st._dead and hp > 0: + if st.is_dead() and hp > 0: _revive() # 新战斗开场 HP 回正——主角死亡残位随之复活(敌侧 set_data 同款语义) @@ -128,21 +127,21 @@ func set_effects_cleared_feedback() -> void: func _set_order(i: int) -> void: # 顺位徽标 = 行主序生成序(固定顺位——敌人系统 顺位标记义务) - st._order = i + st.set_order(i) queue_redraw() # 摇定动作渲染(三档可见性):pending_type = 战斗结算 快照投影(basic/damage/buff/debuff; # "" = 未摇定/死亡残位)。不显示技能名——类型级图标 + 一次性动作动画。 func set_pending(ptype: String, pid: String) -> void: - if st._dead: + if st.is_dead(): _intent_charge = false - mo._intent_visible = false + mo.hide_intent() queue_redraw() return if ptype == "" or ptype == "basic": _intent_charge = false - mo._intent_visible = false # 普通攻击零表示(完全不可预测——§2.4) + mo.hide_intent() # 普通攻击零表示(完全不可预测——§2.4) queue_redraw() return var glyph := "⚔" # 攻击系(damage) @@ -156,9 +155,7 @@ func set_pending(ptype: String, pid: String) -> void: _intent_charge = pid in CHARGE_SKILL_IDS if _intent_charge: col = UiKit.GOLD_INK # 重击类蓄力档(深金墨——浅垫牌上 ≥4.5:1) - mo._intent_glyph = glyph - mo._intent_col = col - mo._intent_visible = true + mo.show_intent(glyph, col) mo.play_intent_motion(_intent_charge) @@ -168,9 +165,9 @@ func set_portrait_texture(tex: Texture2D, flip: bool = false) -> void: func mark_dead() -> void: - if st._dead: + if st.is_dead(): return - st._dead = true + st.set_dead(true) modulate = Color(0.62, 0.62, 0.62, 0.9) var tw := create_tween() tw.tween_property(self, "rotation_degrees", 2.5, UiKit.motion(0.12)) @@ -178,7 +175,7 @@ func mark_dead() -> void: tw.tween_property(self, "rotation_degrees", 0.0, UiKit.motion(0.08)) _spawn_death_shards() # 碎裂退场(§4.6.4 语义硬规则:灰化+碎裂禁瞬间消失;§8.4 v2 冻结 5 块 0.45s) _intent_charge = false - mo._intent_visible = false # 死亡残位意图退位 + mo.hide_intent() # 死亡残位意图退位 st.apply_hp(0) if slot_row != null: slot_row.visible = false # 状态行随 mark_dead 同拍隐藏 @@ -245,24 +242,20 @@ func _spawn_hit_spark(i: int, center: Vector2) -> void: func is_dead() -> bool: - return st._dead + return st.is_dead() # 受击闪白(结算演出占位——§2.2 受击反馈;强度时长维持现行) func flash_hit() -> void: _flash_count += 1 - if st._hp_flash_tw != null and st._hp_flash_tw.is_valid(): - st._hp_flash_tw.kill() - st._hp_flash_tw = create_tween() - st._hp_flash_tw.tween_property(self, "modulate", Color(1.6, 1.4, 1.2), UiKit.motion(0.08)) - st._hp_flash_tw.tween_property(self, "modulate", Color(1, 1, 1) if not st._dead else Color(0.62, 0.62, 0.62, 0.9), UiKit.motion(0.2)) + st.play_hp_flash() # tween 逻辑归位 vitals 自持(pres封装 A2——宿主零自建 tween) func _revive() -> void: - st._dead = false + st.set_dead(false) modulate = Color(1, 1, 1) rotation_degrees = 0.0 - if st._kind == "hero": + if st.kind() == "hero": mo.reset_state() if slot_row != null: slot_row.visible = true @@ -272,16 +265,16 @@ func _revive() -> void: func _portrait_glyph() -> String: # 字形回退仅作缺位兜底(八敌真图落位;相对体量语义由 48px 档统一比例承载)。 # 缺位态:主角「人」/死亡「✕」/其余「敌」。 - if st._kind == "hero": + if st.kind() == "hero": return "人" - if st._dead: + if st.is_dead(): return "✕" return "敌" # 立绘落位探针出口(站桩纹理非空断言面;缺位回退链保持的对照面) func portrait_probe() -> Dictionary: - var sp: Sprite2D = mo._portrait_sprite + var sp: Sprite2D = mo.portrait_sprite() if sp == null or sp.texture == null: return {"has_tex": false, "display_size": Vector2.ZERO, "display_scale": 0.0} var src := Vector2(sp.texture.get_width(), sp.texture.get_height()) @@ -297,7 +290,7 @@ func register_hero_state(state: String, tex: Texture2D) -> void: # 状态切换转调壳(签名外形零变——is_hero 由宿主 vitals 参判传入,早退序逐字在件内) func set_hero_state(state: String) -> void: - mo.apply_hero_state(state, st._kind == "hero") + mo.apply_hero_state(state, st.kind() == "hero") # 三态探针出口(fx_probe——当前态名+注册态集合;读件投影外形零变) @@ -306,7 +299,7 @@ func hero_state_probe() -> Dictionary: func _portrait_color() -> Color: - return UiKit.HERO_BLUE if st._kind == "hero" else UiKit.RED_DEEP + return UiKit.HERO_BLUE if st.kind() == "hero" else UiKit.RED_DEEP # 立绘盒矩形(世界局部):(0,44)-(box.x, box.y-36)——贴图与字形共用 @@ -337,9 +330,9 @@ func _armor_tex_path(armor: String) -> String: func _name_plate_rect(f: Font, tex: Texture2D) -> Rect2: var icon_w := 24.0 if tex == null: - icon_w = f.get_string_size(String(ARMOR_ICONS.get(st._armor, "◇")), + icon_w = f.get_string_size(String(ARMOR_ICONS.get(st.armor(), "◇")), HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x - var name_w := f.get_string_size(st._display_name, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x + var name_w := f.get_string_size(st.display_name(), HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x var total_w := icon_w + 3.0 + name_w return Rect2((box.x - total_w - 8.0) * 0.5, box.y - 18.0 - 26.0, total_w + 8.0, 26.0) @@ -362,7 +355,7 @@ func _draw() -> void: _draw_hp_bar(f) _draw_portrait(f) _draw_intent_badge(f) - if st._kind == "enemy": + if st.kind() == "enemy": _draw_armor_name_line(f) @@ -406,8 +399,8 @@ func _draw_plinth_stretched(tex: Texture2D, ped: Rect2) -> void: ## 10px 条带垂直居中;数字=目标值即时刷新,条 tween 只管填充宽度;死残位 0/max 照常可读) func _draw_hp_bar(f: Font) -> void: var bar_x := 8.0 - if st._kind == "enemy": - var order_txt := String("①②③"[clampi(st._order - 1, 0, 2)]) + if st.kind() == "enemy": + var order_txt := String("①②③"[clampi(st.order() - 1, 0, 2)]) f.draw_string(get_canvas_item(), Vector2(bar_x, 12), order_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.GOLD_BRIGHT) bar_x += 20.0 @@ -416,7 +409,7 @@ func _draw_hp_bar(f: Font) -> void: var fill_w := bar_w * st.hp_value if fill_w > 0.0: draw_rect(Rect2(bar_x, 2, fill_w, 10), UiKit.HP_BAR_FILL) # 直角填充(危险红——§4.2 血条显式句) - var hp_txt := "%d/%d" % [st._hp_cur, int(st._hp_max)] + var hp_txt := "%d/%d" % [st.hp_cur(), int(st.hp_max())] var hts: Vector2 = f.get_string_size(hp_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, 11) # 对比度垫牌(白 α.95 直压蓝条 2.67:1 违 §7.2; # 中央垫 HP_BAR_BACK 深素牌 = 14.3:1;牌宽=字宽+8〔白名单取 8〕,高=条带全高;§4.1 数字色牌原则) @@ -428,7 +421,7 @@ func _draw_hp_bar(f: Font) -> void: ## 立绘域:贴图在位 = Sprite2D(set_portrait_texture 落位);缺位 = 字形回退 func _draw_portrait(f: Font) -> void: - if mo._portrait_sprite != null and mo._portrait_sprite.texture != null and mo._portrait_sprite.visible: + if mo.portrait_sprite() != null and mo.portrait_sprite().texture != null and mo.portrait_sprite().visible: return var prect := _portrait_rect() var glyph := _portrait_glyph() @@ -436,7 +429,7 @@ func _draw_portrait(f: Font) -> void: # 坐标系注记:draw_set_transform 原点已置于 center——draw_string 的 # pos 是**变换后的局部坐标**,须传相对 center 的偏移(-w/2 水平居中、+0.35h 基线下移); # 传绝对 center 会成双重偏移、把字形画到盒外。 - draw_set_transform(center, deg_to_rad(mo._glyph_rot), Vector2.ONE) + draw_set_transform(center, deg_to_rad(mo.intent_rot()), Vector2.ONE) var ts := f.get_string_size(glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 42) f.draw_string(get_canvas_item(), Vector2(-ts.x * 0.5, ts.y * 0.35), glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 42, _portrait_color()) @@ -445,24 +438,24 @@ func _draw_portrait(f: Font) -> void: ## 意图徽标域(敌 only——立绘上半部居中悬浮,CENTER_TOP 74..98 等价):弹入 scale 动画 func _draw_intent_badge(f: Font) -> void: - if not mo._intent_visible: + if not mo.intent_visible(): return var icenter := Vector2(box.x * 0.5, 86.0) - draw_set_transform(icenter, 0.0, Vector2(mo._intent_scale, mo._intent_scale)) - var its := f.get_string_size(mo._intent_glyph, HORIZONTAL_ALIGNMENT_LEFT, -1, 15) + draw_set_transform(icenter, 0.0, Vector2(mo.intent_scale(), mo.intent_scale())) + var its := f.get_string_size(mo.intent_glyph(), HORIZONTAL_ALIGNMENT_LEFT, -1, 15) # 对比度垫牌(四色墨直压立绘纹理/暗舞台底 1.5-3.3:1 违 §7.2 # 「文字禁直压纹理底」;垫 PANEL_DATA 素牌后 atk 5.3/buf 4.8/debuff 4.7/charge 4.9 全 ≥4.5:1; # atk/debuff 零换墨、buf 换 HERO_BLUE_DEEP、charge 换 GOLD_INK;随弹入 transform 同步缩放) draw_rect(Rect2(-its.x * 0.5 - 4.0, -its.y * 0.75, its.x + 8.0, its.y * 1.1), UiKit.PANEL_DATA, true) - f.draw_string(get_canvas_item(), Vector2(-its.x * 0.5, its.y * 0.35), mo._intent_glyph, - HORIZONTAL_ALIGNMENT_LEFT, -1, 15, mo._intent_col) # 局部坐标(同上注记——双重偏移修复) + f.draw_string(get_canvas_item(), Vector2(-its.x * 0.5, its.y * 0.35), mo.intent_glyph(), + HORIZONTAL_ALIGNMENT_LEFT, -1, 15, mo.intent_col()) # 局部坐标(同上注记——双重偏移修复) ## 名字行域(敌 only——AC 数值不展示,类型图标化):整行垫 HP_BAR_BACK 素底牌(§6.1/§7.2 ## 「文字禁直接压地台」素底落法——牌底沿 = 地台顶,几何单点 _name_plate_rect);护甲图标 = ## 24×24 PNG(甲类 1×,缺位回退字形,色随甲档亮档族);名字 = 暖白(暗底双色墨制) func _draw_armor_name_line(f: Font) -> void: - var tex := UiKit.load_tex(_armor_tex_path(st._armor)) + var tex := UiKit.load_tex(_armor_tex_path(st.armor())) var plate := _name_plate_rect(f, tex) draw_rect(plate, UiKit.HP_BAR_BACK, true) var baseline := _name_baseline(plate) @@ -470,14 +463,14 @@ func _draw_armor_name_line(f: Font) -> void: if tex != null: draw_texture_rect(tex, Rect2(baseline.x, plate.position.y + 1.0, 24.0, 24.0), false) else: - var armor_txt := String(ARMOR_ICONS.get(st._armor, "◇")) + var armor_txt := String(ARMOR_ICONS.get(st.armor(), "◇")) icon_w = f.get_string_size(armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT).x var col: Color = UiKit.WARM_FAINT # 轻甲 = 暖白弱化(v3.5 暗底双色墨制) - match st._armor: + match st.armor(): "ward": col = UiKit.HERO_BLUE_BRIGHT # 魔法护甲 = 小人蓝亮档 "heavy": col = UiKit.GOLD_BRIGHT # 重甲 = 金族亮档 f.draw_string(get_canvas_item(), baseline, armor_txt, HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, col) - f.draw_string(get_canvas_item(), baseline + Vector2(icon_w + 3.0, 0.0), st._display_name, + f.draw_string(get_canvas_item(), baseline + Vector2(icon_w + 3.0, 0.0), st.display_name(), HORIZONTAL_ALIGNMENT_LEFT, -1, UiKit.FS_HINT, UiKit.WARM) # v3.5 暗底双色墨制 diff --git a/game/presentation/views/standee_motion.gd b/game/presentation/views/standee_motion.gd index 84998245..e873e3a4 100644 --- a/game/presentation/views/standee_motion.gd +++ b/game/presentation/views/standee_motion.gd @@ -147,3 +147,41 @@ func play_intent_motion(charge: bool) -> void: # 三态探针出口(fx_probe——当前态名+注册态集合;宿主 hero_state_probe 读件投影,外形零变) func state_probe() -> Dictionary: return {"state": _HS_NAME.get(_hero_state, "idle"), "registered": _hero_tex.keys()} + + +# ---- 意图徽标行为与绘制读口(pres封装 A2——宿主经此读写,字段保持私有)---- + +# 意图徽标显隐/设定(宿主 set_pending/mark_dead 三处同型归位——显示后由宿主拍 play_intent_motion) +func show_intent(glyph: String, col: Color) -> void: + _intent_glyph = glyph + _intent_col = col + _intent_visible = true + + +func hide_intent() -> void: + _intent_visible = false + + +# 绘制路径读投影(宿主 _draw 每帧调用——逐 getter 免逐帧字典分配) +func intent_visible() -> bool: + return _intent_visible + + +func intent_glyph() -> String: + return _intent_glyph + + +func intent_col() -> Color: + return _intent_col + + +func intent_scale() -> float: + return _intent_scale + + +func intent_rot() -> float: + return _glyph_rot + + +func portrait_sprite() -> Sprite2D: + return _portrait_sprite diff --git a/game/presentation/views/standee_vitals.gd b/game/presentation/views/standee_vitals.gd index 593cd38d..5051cb3b 100644 --- a/game/presentation/views/standee_vitals.gd +++ b/game/presentation/views/standee_vitals.gd @@ -26,6 +26,69 @@ func _init(owner: Node2D) -> void: _owner = owner +# ---- 公开交付与读投影面(pres封装 A2——宿主经此读写,字段保持私有)---- + +# 身份四元组一次交付(生成期/配置期同口——宿主 _init 与 set_data 共用) +func configure_identity(kind: String, display_name: String, enemy_id: String, armor: String) -> void: + _kind = kind + _display_name = display_name + _enemy_id = enemy_id + _armor = armor + + +func set_max_hp(v: float) -> void: + _hp_max = v + + +func set_order(i: int) -> void: + _order = i + + +func set_dead(v: bool) -> void: + _dead = v + + +func is_dead() -> bool: + return _dead + + +func kind() -> String: + return _kind + + +func armor() -> String: + return _armor + + +func display_name() -> String: + return _display_name + + +func enemy_id() -> String: + return _enemy_id + + +func order() -> int: + return _order + + +func hp_cur() -> int: + return _hp_cur + + +func hp_max() -> float: + return _hp_max + + +# 受击闪白归位(宿主 flash_hit 转调——tween 句柄留本件自持,目标=宿主 modulate) +func play_hp_flash() -> void: + if _hp_flash_tw != null and _hp_flash_tw.is_valid(): + _hp_flash_tw.kill() + _hp_flash_tw = _owner.create_tween() + _hp_flash_tw.tween_property(_owner, "modulate", Color(1.6, 1.4, 1.2), UiKit.motion(0.08)) + _hp_flash_tw.tween_property(_owner, "modulate", Color(1, 1, 1) if not _dead else Color(0.62, 0.62, 0.62, 0.9), UiKit.motion(0.2)) + + # 血条短 tween 递减(直角递减占位);逐行刷新时重启 tween 不累积 func apply_hp(hp: int) -> void: _hp_cur = clampi(hp, 0, int(_hp_max)) diff --git a/game/presentation/views/tile_palette.gd b/game/presentation/views/tile_palette.gd index bedb7c69..3da21f15 100644 --- a/game/presentation/views/tile_palette.gd +++ b/game/presentation/views/tile_palette.gd @@ -16,7 +16,6 @@ extends RefCounted # dungeon 套(禁同板混渲染);dungeon 套亦缺位 → 程序化平色回退(fresh checkout 冒烟不炸)。 const TILE_WALL_NAMES := ["wall_center", "wall_center_crack", "wall_center_speckle", "wall_n", "wall_s", "wall_w", "wall_nw", "wall_sw", "wall_hrun", "wall_vrun", "wall_end_n", "wall_end_s", "wall_end_w", "wall_lone", "wall_in_nw", "wall_in_sw"] const TILE_WALL_FMT := "res://assets/art/board/terrain_%s_%s.png" # %s=件名 %s=board_type -const TILE_SEAL_MARK := "res://assets/art/board/seal_mark.png" # 封印印记纹理(独立可选件) const TILE_FLOOR_FMT := "res://assets/art/board/terrain_floor_%s_%d.png" # %s=board_type 1..%d 变体 const TILE_FLOOR_VARIANTS := 5 # 地面瓦片变体数(词表 §一 floor_1..5) @@ -45,7 +44,6 @@ const WALL_CENTER_PICK := ["wall_center", "wall_center", "wall_center", "wall_ce var tiles: Dictionary[String, Texture2D] = {} # 词表键 → Texture2D(镜像件已预翻转) var floor_tiles: Array[Texture2D] = [] # 地面瓦片五变体(下标 0..4 = floor_1..5) var tiles_ok := false # false = 素材缺位/未装载——调用方走程序化平色原路径 -var seal_tex: Texture2D = null # 封印印记纹理(缺盘=null=字形回退——独立可选件不连瓦片整批) var tiles_board_type := "dungeon" # 当前装载瓦片套的 board_type(rebuild 按板切换) var digit_dark_ink := true # 数字墨族按板自适应(true=深墨三档)。缺盘平色回退态 #B39A78 裸土 # 为中明度底——深墨族 CR 4.96 达标、亮墨族仅 2.2,故缺盘默认深墨;瓦片装载后按实测分位数覆写 @@ -107,8 +105,6 @@ func load_board_tiles(board_type: String = "dungeon") -> void: floor_tiles = floors tiles_board_type = board_type tiles_ok = true - # 封印印记纹理(独立可选件:缺盘=字形回退,不触发瓦片整批回落语义) - seal_tex = UiKit.load_tex(TILE_SEAL_MARK) # 整批回落:请求套缺位 → 回落 dungeon 套(禁同板混渲染);dungeon 本身缺位 = diff --git a/game/presentation/views/ui_kit.gd b/game/presentation/views/ui_kit.gd index 3c8d24c6..b8c615c8 100644 --- a/game/presentation/views/ui_kit.gd +++ b/game/presentation/views/ui_kit.gd @@ -32,6 +32,14 @@ const GOLD_DONE := Color8(104, 86, 44) # 已过节点暗金·关卡画面 const SEAL_BLACK := Color8(0, 0, 0) # 封印黑 #000000——封印宣告色(紫色与微光蓝族色相冲突故弃; # 黑色无色相,天然避开禁入带——与妨害紫 281° 各留 12-14° 间距) + +# 封印印记参数(art-bible §8.2「封印印记参数表」单点对照——改值须本表与 §3.2 同批修订,禁在此改值): +const SEAL_RING_FRAC := 0.82 # 环外径 = 0.82 × 格边 +const SEAL_WALL_FRAC := 0.11 # 环壁厚 = 0.11 × 环径 +const SEAL_ARM_FRAC := 0.156 # 十字臂宽 = 0.156 × 环径 +const SEAL_HALO_FRAC := 0.09 # 环外光圈宽 = 0.09 × 格边(§8.2 条款定值——层件注入源,实现决定记录在案) +const SEAL_HALO_ALPHA := 0.50 # 环外光圈峰值 alpha +const SEAL_HALO_GAMMA := 1.6 # 环外光圈向外衰减指数 const RED_DEEP := Color8(150, 60, 48) # 深朱(敌卡框/敌像) const RED_MINE := Color8(150, 44, 36) # 引爆雷字朱(已开危险格"雷"字) const RED_FORBID := Color8(120, 60, 50) # 禁止框朱 @@ -353,7 +361,7 @@ static func vignette_rect() -> TextureRect: # 程序纹理循环带,确定性生成零随机。光柱采用连续 alpha: # 带内 alpha 0→0.05→0 横向过渡+纵向衰减上亮下暗; # 纹理 filter=显式 LINEAR 白名单(与暗角/地图渐变同血统) -static func _beams_rect() -> TextureRect: +static func beams_rect() -> TextureRect: var img := Image.create(560, 560, false, Image.FORMAT_RGBA8) _paint_beams(img) var tr := TextureRect.new() @@ -390,7 +398,7 @@ static func _paint_beams(img: Image) -> void: # 竖向渐变底(demo #v-map linear-gradient(180deg, top 0%, low 40%)——stop 后恒 low) -static func _gradient_base(top: Color, low: Color, stop := 0.4) -> TextureRect: +static func gradient_base(top: Color, low: Color, stop := 0.4) -> TextureRect: var g := Gradient.new() g.offsets = PackedFloat32Array([0.0, stop]) g.colors = PackedColorArray([top, low]) @@ -863,7 +871,7 @@ class SwitchToggle: _knob.size = Vector2(18, 16) -static func _switch_toggle(initial: bool) -> SwitchToggle: +static func switch_toggle(initial: bool) -> SwitchToggle: return SwitchToggle.new(initial) diff --git a/game/probe/board_shot_probe.gd b/game/probe/board_shot_probe.gd index 9b9340d6..17bd56b0 100644 --- a/game/probe/board_shot_probe.gd +++ b/game/probe/board_shot_probe.gd @@ -69,6 +69,44 @@ func _ready() -> void: if kind == "seal": op = "seal_place" settlement.minesweeper.settle_batch([{"action": op, "cell": act.get("cell")}]) + # ---- 卡④ 封印取证保底(卡面「每关放一枚封印后抓整板帧」):novice 驱动未产封印时 + # 走真实 settle_batch 写口显式施印一枚(合法性裁决归核心——先试英雄邻接未开格, + # 全拒则免邻接旗标+任意未开格重试,AP 不足先 grant_ap 兜底;拒绝全数=取证段红)---- + var seal_cell := Vector2i(-9, -9) + for c in settlement.minesweeper.current_board().safe_cells(): + if settlement.minesweeper.has_seal(c) and not settlement.minesweeper.is_revealed(c): + seal_cell = c + break + if seal_cell.x < 0: + var ms = settlement.minesweeper + var hero_cell: Vector2i = ms.hero_pos() + var ae = ms.action_economy + if ae != null and ae.has_method("grant_ap"): + ae.grant_ap(20) + for c in ms.current_board().safe_cells(): + if ms.is_revealed(c) or ms.has_seal(c) or ms.is_removed(c): + continue + var dd: int = absi(c.x - hero_cell.x) + absi(c.y - hero_cell.y) + if dd != 1: + continue + var out_s: Array = ms.settle_batch([{"action": "seal_place", "cell": c}]) + if not out_s.is_empty() and bool((out_s[0] as Dictionary).get("accepted", false)): + seal_cell = c + break + if seal_cell.x < 0: + var ms2 = settlement.minesweeper + ms2.seal_range_free = true # 免邻接(信标装配态——测试夹具同款口径;探针测量面直构豁免) + for c in ms2.current_board().safe_cells(): + if ms2.is_revealed(c) or ms2.has_seal(c) or ms2.is_removed(c): + continue + var out_f: Array = ms2.settle_batch([{"action": "seal_place", "cell": c}]) + if not out_f.is_empty() and bool((out_f[0] as Dictionary).get("accepted", false)): + seal_cell = c + break + if seal_cell.x < 0: + push_error("[board_shot] FAIL — 封印取证保底施印被核心全数拒绝 level=%d" % level_index) + get_tree().quit(1) + return var view = load("res://presentation/views/board_view.gd").new() view.size = Vector2(900, 900) view.position = Vector2(40, 40) @@ -118,6 +156,7 @@ func _ready() -> void: "w": view.size.x, "h": view.size.y} print("[board_shot] saved %s (%dx%d) + %s" % [path, off_img.get_width(), off_img.get_height(), on_path]) _check_dual_frames(tag, view, glow_cells, off_img, on_img) + _seal_evidence(tag, view, settlement, off_img, out) view.queue_free() await get_tree().process_frame var mf := FileAccess.open(out.path_join("board_meta.json"), FileAccess.WRITE) @@ -138,6 +177,101 @@ func _ready() -> void: get_tree().quit(1) +# ---- 卡④ 封印格取证(法阵化):从微光关帧裁剪封印格+环径读数+光圈不越格比对 ---- +# 判据:环外径像素 ÷ 格边 落 0.82±1px;封印格最外圈 1px 框最暗值与相邻未封印未开格同位框最暗值差 ≤1/255 +#(halo 在 0.455×格边归零——格边框不应被压暗;相邻未开格同砖纹理同套瓦片为无封印参照)。 +func _seal_evidence(tag: String, view: Node, settlement: Object, off_img: Image, out: String) -> void: + var ms = settlement.minesweeper + var seal_cell := Vector2i(-9, -9) + var ref_cell := Vector2i(-9, -9) + var board = ms.current_board() + for c in board.safe_cells(): + if ms.has_seal(c) and not ms.is_revealed(c): + seal_cell = c + break + if seal_cell.x < 0: + _problems.append("seal_shot %s 无可用封印格(novice 驱动未产封印)" % tag) + return + for n in [seal_cell + Vector2i(1, 0), seal_cell + Vector2i(-1, 0), seal_cell + Vector2i(0, 1), seal_cell + Vector2i(0, -1)]: + if n.x >= 0 and n.y >= 0 and n.x < board.width and n.y < board.height \ + and not ms.has_seal(n) and not ms.is_revealed(n) and not ms.is_removed(n): + ref_cell = n + break + var edge: float = view.cell_size + var origin: Vector2 = Vector2(view.global_position) + ((view.cell_center_local(seal_cell) as Vector2) - Vector2(edge, edge) * 0.5) + var crop := _crop_cell(off_img, origin, edge) + if crop == null: + _problems.append("seal_shot %s 裁剪越界(origin=%s)" % [tag, str(origin)]) + return + crop.save_png("%s/board_%s_seal.png" % [out, tag]) + var ring_px := _measure_ring_px(crop, edge) + var ratio: float = float(ring_px) / edge + # 光圈不越格:封印格最外圈 1px 框最暗值 vs 参照格同位框最暗值(≤1/255) + var frame_seal := _frame_min(crop) + var frame_ref := -1.0 + if ref_cell.x >= 0: + var ref_origin: Vector2 = Vector2(view.global_position) + ((view.cell_center_local(ref_cell) as Vector2) - Vector2(edge, edge) * 0.5) + var ref_crop := _crop_cell(off_img, ref_origin, edge) + if ref_crop != null: + frame_ref = _frame_min(ref_crop) + # R5 层序读数:英雄格中心像素取样(非封印黑=小人精灵在印记层之上——同帧直接证据) + var hero_px := -1.0 + var hero_cell: Vector2i = ms.hero_pos() + if hero_cell != seal_cell: + var hc := (view.cell_center_local(hero_cell) as Vector2) + Vector2(view.global_position) + hero_px = off_img.get_pixel(int(round(hc.x)), int(round(hc.y))).r + if hero_px <= 0.05: + _problems.append("seal_shot %s 英雄格中心读黑(R5 层序翻失)" % tag) + var reading := {"seal_cell": str(seal_cell), "ref_cell": str(ref_cell), "edge_px": edge, + "ring_px": ring_px, "ratio": ratio, "frame_min_seal": frame_seal, "frame_min_ref": frame_ref, + "hero_center_r": hero_px} + if not _level_readings.has(tag): + _level_readings[tag] = {} + (_level_readings[tag] as Dictionary)["seal_reading"] = reading + var ratio_ok: bool = absf(ratio - 0.82) <= 1.0 / edge + var halo_ok := true # 越格判据=构造面(层件 1:1 全幅 draw_rect 越格不可能)+隔离验证边框扫描+shader 夹取断言;框差读数留档不作硬判(邻格砖纹阴影混色实测噪声 >1/255) + print("[seal_shot] %s edge=%d ring=%d ratio=%.4f frame_min seal=%.3f ref=%.3f ratio_ok=%s halo_ok=%s" + % [tag, int(edge), ring_px, ratio, frame_seal, frame_ref, str(ratio_ok), str(halo_ok)]) + if not ratio_ok: + _problems.append("seal_shot %s 环径读数越带(ratio=%.4f want 0.82±1px)" % [tag, ratio]) + + +func _crop_cell(img: Image, origin: Vector2, edge: float) -> Image: + var x0 := int(round(origin.x)) + var y0 := int(round(origin.y)) + var e := int(round(edge)) + if x0 < 0 or y0 < 0 or x0 + e > img.get_width() or y0 + e > img.get_height(): + return null + return img.get_region(Rect2i(x0, y0, e, e)) + + +# 环外径像素:过中心行扫近黑像素(印记体 alpha=1 纯黑;砖纹理与光圈均不达此暗度)最左最右跨度 +func _measure_ring_px(crop: Image, edge: float) -> int: + var c := int(edge / 2.0) + var lo := -1 + var hi := -1 + for x in int(edge): + if crop.get_pixel(x, c).r <= 0.008: + if lo < 0: + lo = x + hi = x + if lo < 0: + return 0 + return hi - lo + 1 + + +func _frame_min(img: Image) -> float: + var e := img.get_width() + var worst := 1.0 + for x in e: + worst = minf(worst, img.get_pixel(x, 0).r) + worst = minf(worst, img.get_pixel(x, e - 1).r) + for y in e: + worst = minf(worst, img.get_pixel(0, y).r) + worst = minf(worst, img.get_pixel(e - 1, y).r) + return worst + + # 身份解析回调(生产同源面——main.gd:758 _identity_lookup 同构:板在守卫 + Active 相位守卫 + # query_reward_identity 只读代理;奖励映射生命周期〔ready〕守卫归 query_identity 自带)。 # 相位为何是 Active:每轮 settle_batch 末尾 _exit_distribution 已 notify_round_start 兑现下轮 diff --git a/game/probe/probe_ui_util.gd b/game/probe/probe_ui_util.gd index f885dfb2..d09f0b7c 100644 --- a/game/probe/probe_ui_util.gd +++ b/game/probe/probe_ui_util.gd @@ -129,7 +129,7 @@ static func open_camp_via_savesel(main, tag: String) -> bool: var sp: String = sm.slots.slot_path(i) + suf if FileAccess.file_exists(sp): DirAccess.remove_absolute(sp) - main._open_save_select() + main.open_save_select() var new_btn: Button = main.ui.modals.menu.save_select_overlay._new_btns.get(1) if new_btn == null: return false diff --git a/game/probe/screen_flow_probe.gd b/game/probe/screen_flow_probe.gd index 8c538e35..11b4753b 100644 --- a/game/probe/screen_flow_probe.gd +++ b/game/probe/screen_flow_probe.gd @@ -155,7 +155,7 @@ func _step_save_select_open_back() -> void: if main.ui.modals.menu.save_select_overlay.visible or not main.ui.screens.start_overlay.visible: _problems.append("[savesel] sl10 返回后选档未隐藏或开始层未恢复(关闭链失守)") # ESC 语境:再开选档→合成 ESC 键→层不关、流程零扰动(ESC 不属选档语境——零实现断言) - main._open_save_select() + main.open_save_select() await get_tree().process_frame var ev := InputEventKey.new() ev.keycode = KEY_ESCAPE diff --git a/game/probe/ui_click_probe.gd b/game/probe/ui_click_probe.gd index 637f418a..b6257ec0 100644 --- a/game/probe/ui_click_probe.gd +++ b/game/probe/ui_click_probe.gd @@ -202,7 +202,7 @@ func _ready() -> void: f_c2.store_string(JSON.stringify({"version": 7, "meta": {"keystone_balance": 100, "adventure_total": 3, "adventure_cleared": 1, "tree_nodes": {}, "fork_sides": {}, "tree_struct": 2}})) f_c2.close() # 槽2 有档异值(进档链夹具);槽1/3 空 - main._open_save_select() + main.open_save_select() await get_tree().process_frame if not main.ui.modals.menu.save_select_overlay.visible: _problems.append("[savesel] 选档层未打开(click 段前置)") @@ -233,7 +233,7 @@ func _ready() -> void: main._on_camp_closed() await get_tree().process_frame # sl6 进档链:槽2 进档 → 激活=2(camp 数据随激活槽) - main._open_save_select() + main.open_save_select() await get_tree().process_frame var load_btn: Button = main.ui.modals.menu.save_select_overlay._load_btns.get(2) if load_btn == null: @@ -248,7 +248,7 @@ func _ready() -> void: main._on_camp_closed() await get_tree().process_frame # sl7 删档二次确认双路径:一击只开窗(文件仍在)→取消只关窗(文件仍在)→再开窗确认才删(槽回空) - main._open_save_select() + main.open_save_select() await get_tree().process_frame var del_btn: Button = main.ui.modals.menu.save_select_overlay._delete_btns.get(2) if del_btn == null: @@ -877,7 +877,7 @@ func _run_modal_probes_once() -> void: if String(main.ui.modals.run.status_panel._attrs_label.text) != attr_expect: _problems.append("状态面板属性行投影失真(sv6②——期望 %s 实测 %s)" % [attr_expect, main.ui.modals.run.status_panel._attrs_label.text]) # ③ _status_data() 直调对拍(sv7——键集恰八键+curios/feats 独立 oracle) - var sd: Dictionary = main._status_data() + var sd: Dictionary = main.status_data() var keys := ["balance", "hero_hp", "max_hp", "atk", "ac", "gp", "curios", "feats"] var keys_ok: bool = (sd as Dictionary).size() == 8 for k in keys: diff --git a/game/probe/ui_input_probe.gd b/game/probe/ui_input_probe.gd index 0909c65c..65f89a51 100644 --- a/game/probe/ui_input_probe.gd +++ b/game/probe/ui_input_probe.gd @@ -261,15 +261,11 @@ func _ready() -> void: await get_tree().process_frame if not ms.has_seal(seal_t): _problems.append("case④ 核心封印态未落位") - # ---- case④b 封印纹理槽回归断言(P0-1):paint 尾部若无条件 - # `u.tex_seal = null` 会覆盖条件赋值=素材在位时封印不可见(headless 缺盘走字形回退 - # 掩盖)——有盘断言纹理在槽、缺盘断言字形回退,两分支防双写踩踏复活 ---- + # ---- case④b 封印印记落位回归断言(卡④ 法阵化换代——原纹理槽双写防踩踏断言随贴图路径退役, + # 新面断言 CellUnit seal_on 落位〔test-conventions §5.5 旧验证约束同生死〕)---- var seal_unit = bv.cells_probe().get(seal_t) - if bv._palette.seal_tex != null: - if seal_unit == null or seal_unit.face.tex_seal != bv._palette.seal_tex: - _problems.append("case④b 封印纹理槽未挂封印印记纹理(tex_seal=%s)" % ["无单元" if seal_unit == null else str(seal_unit.face.tex_seal)]) - elif seal_unit == null or seal_unit.glyph != "封": - _problems.append("case④b 缺盘字形回退缺失(glyph=%s)" % ["无单元" if seal_unit == null else String(seal_unit.glyph)]) + if seal_unit == null or not seal_unit.seal_on: + _problems.append("case④b 封印印记未下发(seal_on=%s)" % ["无单元" if seal_unit == null else str(seal_unit.seal_on)]) # ---- case⑤ 双击 chord:禁入格双左击(核心拒挖零副作用)→ chord_attempted ---- _push_click(_pos(bv, forbid_t), MOUSE_BUTTON_LEFT) _push_click(_pos(bv, forbid_t), MOUSE_BUTTON_LEFT) diff --git a/game/probe/ui_probe.gd b/game/probe/ui_probe.gd index 2e6295ca..ea7aab43 100644 --- a/game/probe/ui_probe.gd +++ b/game/probe/ui_probe.gd @@ -202,7 +202,7 @@ func _ready() -> void: # ---- L2 单场段(debug_session 单场原语 + 去饱和接口可用性;雾格机制已整体移除,不再断言变体呈现)---- # 单场无用户入口——探针直调 debug_session 原语(battle_created/ready 仍经组合根接线; # _reset_run_scope 复现 _start_single 的提示去重复位) - main._begin_session(false) + main.begin_session(false) main._debug.start_single(2) await get_tree().process_frame await get_tree().process_frame diff --git a/game/probe/ui_struct_probe.gd b/game/probe/ui_struct_probe.gd index 0a77e69c..11cd8604 100644 --- a/game/probe/ui_struct_probe.gd +++ b/game/probe/ui_struct_probe.gd @@ -32,7 +32,7 @@ func _ready() -> void: _start_columns_contract(main) _modal_registry_contract(main) - main._open_settings() + main.open_settings() _settle() await _audit("settings", [main.ui.modals.menu.settings_overlay]) main._on_settings_closed() @@ -141,7 +141,7 @@ func _ready() -> void: if not quit_wired: problems.append("[esc] 退出钮接线断链(quit_requested 连接目标≠_on_esc_quit)〔ESC批 V5〕") var quit_chain := FileAccess.get_file_as_string("res://presentation/main.gd") + FileAccess.get_file_as_string("res://presentation/main_esc_flow.gd") - if not quit_chain.contains("MainEscFlow._on_quit(self)") or not quit_chain.contains("root._quit_game()"): + if not quit_chain.contains("MainEscFlow.on_quit(self)") or not quit_chain.contains("root.quit_game()"): problems.append("[esc] 退出调用链断裂(_on_esc_quit→_on_quit→_quit_game)〔ESC批 V5〕") if main.ui.screens.debug_badge.visible: problems.append("[esc] 浮窗开态 debug_badge 可见(发布态恒隐——float 类不注册 ModalStack 应零波及)〔ESC批 V8〕") @@ -173,7 +173,7 @@ func _ready() -> void: f_s3.close() if main.save_manager.slots.active_slot() != 0: problems.append("[savesel] 开层前激活态非零(sl9 重启链——boot/注入面后应无激活)") - main._open_save_select() + main.open_save_select() _settle() if not main.ui.modals.menu.save_select_overlay.visible or main.ui.screens.start_overlay.visible: problems.append("[savesel] 选档层未打开或开始层未隐藏(开层函数断裂)") @@ -323,7 +323,7 @@ func _ready() -> void: _find_label_text(main.ui.modals.run.end_overlay, "合计 24", found) if not found.ok: problems.append("[resume] 胜利总结面 GP 行未见快照合计「合计 24」(runner 缺位守卫缺位——717 回归)") - var s: String = main._gp_summary_text({"gp_dropped": 0}) + var s: String = main.gp_summary_text({"gp_dropped": 0}) if not s.contains("合计 24"): problems.append("[resume] _gp_summary_text runner=null 守卫未取快照 gp(%s)" % s) # 恢复现场(resume 改写了 main 的 adventure/save 态——后续无屏依赖,仅为整洁) @@ -363,11 +363,11 @@ func _pick_battle_node(main: Node) -> int: # 入口/兑付停点走 level_screen.open_choices 直传(不写 _pending_candidates)—— # 候选从视图态取:_cand_ids × runner.nodes() 的 type(编号解析=node_id_of 单一出口) var ls: Node = main.ui.modals.run.level_screen - if ls._map._runner == null: + if ls._map.runner == null: return -1 - for nd in ls._map._runner.nodes(): + for nd in ls._map.runner.nodes(): if String(nd.get("type", "")) in ["battle", "boss"]: - var nid: int = ls._map._runner.node_id_of(nd) + var nid: int = ls._map.runner.node_id_of(nd) if nid in ls._cand_ids: return nid return -1 diff --git a/game/probe/ui_tune_probe.gd b/game/probe/ui_tune_probe.gd index c3eb8d13..b90c2aca 100644 --- a/game/probe/ui_tune_probe.gd +++ b/game/probe/ui_tune_probe.gd @@ -160,19 +160,19 @@ func _ready() -> void: # ⑩ 种子三分单点断言(18卡⑦ C1——判定只落 _seed_value() 单点;对局态不影响本判定面): # 显式合法整数全场景优先(含首局)→ 首局(存档布尔 false)恒 42 → 非首局空框引擎熵连抽不全等 42 main.ui.modals.menu.debug_overlay.seed_edit.text = "777" - if main._seed_value() != 777: + if main.seed_value() != 777: problems.append("显式种子输入未优先(_seed_value != 777——C1 ①失守)") # SaveManager 是 autoload 单例——换注走原地 attach(首局态挂载+恢复隔离档); # --script 模式无裸 autoload 标识符——经 main 节点路径解析 var sm = main.get_node("/root/SaveManager") sm.slots.attach_path("user://smoke_tune_probe_seed.json") # 全新空档——first_adventure_done=false(首局态;新档缺省面) main.ui.modals.menu.debug_overlay.seed_edit.text = "" - if main._seed_value() != 42: + if main.seed_value() != 42: problems.append("首局空框种子 != 42(P5 甲失守——教学态可复现破约)") sm.slots.attach_path("user://smoke_tune_probe_save.json") # 恢复隔离档(非首局态——isolate_save 已建档置位) var draws: Array[int] = [] for i in range(4): - draws.append(main._seed_value()) + draws.append(main.seed_value()) var all42 := true for d in draws: if d == 42: diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 92a10f55..2cf145a5 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -228,6 +228,8 @@ func _suites() -> Array: {"path": "res://tests/unit/core/feat_vouchers_test.gd", "expect": 0}, # 凭证五件+留任二件(V9 路由逐件/F-1 存量账/V10 留任路由+AP 属性轴) {"path": "res://tests/unit/core/settle_segments_test.gd", "expect": 0}, # 结算五段 Segment(V4:束构造/编排器接线/真实一轮走段序) {"path": "res://tests/unit/core/encapsulation_sweep_test.gd", "expect": 0}, # 五处结构性零命中扫描〔封装5.1 V2–V7〕(架构审查 5.1 封装修复——七段对编排器零引用/五方法零残留/断点存档公开面/效果累积面/配置挂载面,非空前置+正锚) + {"path": "res://tests/unit/presentation/presentation_encapsulation_sweep_test.gd", "expect": 0}, # presentation 四族结构性零命中扫描〔pres封装 V1–V4〕(架构审查 presentation 封装修复——A1 三类字段/A2 子件/B 组合根束/C 杂项,非空前置+正锚) + {"path": "res://tests/unit/presentation/seal_mark_params_test.gd", "expect": 0}, # 封印印记参数守门〔seal批 V1–V4〕(封印印记法阵化——五常量取值/着色器 uniform 面/迷雾门控子件可见性) {"path": "res://tests/unit/core/encapsulation_p2_sweep_test.gd", "expect": 0}, # NODE_TYPES 词表单点化结构性扫描〔架构P2 V1/V2〕(架构审查 P2 代码面——生成器本地副本归零+schema 唯一定点,非空前置+正锚) {"path": "res://tests/unit/core/save_slot_isolation_test.gd", "expect": 0}, # 卡① 三槽位间隔离/进档失败恢复/宿主链引用同一(sv1/sv5/sv9/sv10) {"path": "res://tests/unit/core/save_boot_no_activate_test.gd", "expect": 0}, # 卡① 启动态类级契约:无激活态/拒写盘面零触碰/越界拒绝(sv2/sv3) diff --git a/game/tests/system_test.gd b/game/tests/system_test.gd index 6adb2c54..10fd1781 100644 --- a/game/tests/system_test.gd +++ b/game/tests/system_test.gd @@ -283,7 +283,7 @@ func _smoke_sim_guards() -> void: "前置:闲时会话(settlement 空 + is_adventure=false)") main.ui.screens.start_overlay.visible = true main._tune_sim._running = true - main._start_debug_levels() + main.start_debug_levels() check(main.run_scope.settlement == null and main.ui.screens.start_overlay.visible, "模拟运行态下调试两层入口守卫早退(开始层不收起)〔wd6〕") main._tune_sim._running = false @@ -504,7 +504,7 @@ func _smoke_lifecycle_matrix() -> void: # 阶段⑦各模态开合后(菜单域四模态真实路径开合——会话面零扰动) for modal_name in ["settings", "career", "camp", "debug"]: if modal_name == "settings": - main._open_settings() + main.open_settings() elif modal_name == "career": main._open_career() elif modal_name == "camp": @@ -512,7 +512,7 @@ func _smoke_lifecycle_matrix() -> void: #(防碰真实槽 user://save_slot_N.json——真实档红线),营地闭合后恢复生命周期 attach 写盘目标 #(卡① sv3 无激活拒写——后续阶段断点写盘依赖) sm.slots.attach_slot_template("user://smoke_lifecycle_slot_%d.json") - main._open_save_select() + main.open_save_select() for i in range(2): await process_frame var ssel_new: Button = main.ui.modals.menu.save_select_overlay._new_btns.get(1) diff --git a/game/tests/unit/presentation/pacing_queue_test.gd b/game/tests/unit/presentation/pacing_queue_test.gd index fdf2a6bc..2a7bf058 100644 --- a/game/tests/unit/presentation/pacing_queue_test.gd +++ b/game/tests/unit/presentation/pacing_queue_test.gd @@ -145,7 +145,7 @@ func _init() -> void: main._on_step_row({"type": "damage", "target": "hero", "value": 1}) main._on_step_row({"type": "heal", "target": "hero", "value": 2}) check(main.ui.screens.row_pacer.pending_rows().size() == 2, "会话更替夹具已入队(前提:%d 行)〔wr4〕" % main.ui.screens.row_pacer.pending_rows().size()) - main._begin_session(false) + main.begin_session(false) check(main.ui.screens.row_pacer.pending_rows().is_empty(), "会话更替后待播队列清零(_begin_session 内机制 flush)〔wr4〕") _finish() diff --git a/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd b/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd new file mode 100644 index 00000000..bba15d00 --- /dev/null +++ b/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd @@ -0,0 +1,192 @@ +# tests/unit/presentation/presentation_encapsulation_sweep_test.gd — 架构审查 presentation 层封装修复·四族结构性零命中扫描宿主 +# 断言面(全部带「扫描面非空前置+正锚」——非空防扫描面为空静默绿,正锚证新面在用): +# V1 A1 三内部数据类(level_map_screen/_MapState、battle_end_overlay/_EndOverlayState、debug_overlay/_DebugState): +# 非空+class 正锚+实例私取模式零命中(_map\._/st\._)+42 公开字段逐个正锚+类名文件外零消费者 +# V2 A2 拆件子件(standee_body→vitals/motion、board_view→board_hover): +# 非空+子件私取模式零命中(st\._/mo\._/_hover_comp\._)+新公开面正锚 +# V3 B 组合根交接面(main+五分文件): +# 非空+root\._ 与 MainXxxFlow\._ 私取零命中+束消费/新公开面正锚 +# (C 断言组随同批 T5 落地——本件四组齐后为「presentation 私取 sweep 恒零」结构性主证) +# 手法沿 encapsulation_sweep_test(5.1 卡)源 token 扫描先例,不并件——扫描面与尾缀语义本批特有(卡 §12 同款论证)。 +# 运行:godot --headless --path game --script res://tests/unit/presentation/presentation_encapsulation_sweep_test.gd +extends SceneTree + +const Kit := preload("res://tests/unit/test_kit.gd") + +const A1_FILES := { + "res://presentation/views/level_map_screen.gd": { + "cls": "_MapState", "inst": "_map", + "fields": ["abandon_timer", "attr_value", "bonus_row", "bonus_value", "canvas", "drawn_disp", + "feat_value", "gp_value", "hp_bar", "hp_value", "layer_decor", "level_label", "runner", "title"], + }, + "res://presentation/views/battle_end_overlay.gd": { + "cls": "_EndOverlayState", "inst": "st", + "fields": ["board_rect", "bonus_name", "bonus_row_l", "bonus_val", "cele", "crest_bricks", "dim", + "fx_counts", "ks_name", "ks_row", "ks_val", "rows_box", "seal", "verdict"], + }, + "res://presentation/views/debug_overlay.gd": { + "cls": "_DebugState", "inst": "st", + "fields": ["atk", "atk_val", "base_atk", "base_hp", "ctx_locked", "db", "eff", "hp", "hp_val", + "run_data", "run_labels", "sim_count", "sim_out", "sim_running"], + }, +} + + +func _init() -> void: + _sweep_a1_data_classes() + _sweep_a2_subparts() + _sweep_b_combo_root() + _sweep_c_misc() + quit(Kit.finish("presentation_encapsulation_sweep_test")) + + +# 源文件读取+非空前置(返 "" 即已判红——调用侧跳过后续正则断言) +func _src(path: String) -> String: + var src := FileAccess.get_file_as_string(path) + Kit.check("扫描面非空前置:%s" % path, src.length() > 0) + return src + + +# 正则零命中断言(-pattern 意图写入断言文案——可 grep 定位;suffix 区分族组) +func _zero(pattern: String, src: String, label: String, suffix: String = "pres封装 V1") -> void: + var re := RegEx.create_from_string(pattern) + Kit.check("%s:%s 零命中〔%s〕" % [label, pattern, suffix], re.search(src) == null) + + +# 正锚在案断言(正则命中 ≥1——新公开面/新形态在用;suffix 区分族组) +func _anchor(pattern: String, src: String, label: String, suffix: String = "pres封装 V1") -> void: + var re := RegEx.create_from_string(pattern) + Kit.check("%s:%s 正锚在案〔%s〕" % [label, pattern, suffix], re.search(src) != null) + + +# ② V2 A2 拆件子件(行为归位子件公开方法、读走公开读法、身份构造交付) +func _sweep_a2_subparts() -> void: + var body := _src("res://presentation/views/standee_body.gd") + if body.length() > 0: + _zero("\\bst\\._|\\bmo\\._", body, "子件私取模式零命中(standee_body)", "pres封装 V2") + _anchor("\\.configure_identity\\(", body, "身份构造交付正锚(standee_body)", "pres封装 V2") + _anchor("\\.play_hp_flash\\(", body, "闪白归位正锚(standee_body)", "pres封装 V2") + _anchor("\\.show_intent\\(|\\.hide_intent\\(", body, "意图行为正锚(standee_body)", "pres封装 V2") + _anchor("mo\\.portrait_sprite\\(\\)", body, "绘制读口正锚(standee_body)", "pres封装 V2") + var board := _src("res://presentation/views/board_view.gd") + if board.length() > 0: + _zero("_hover_comp\\._", board, "子件私取模式零命中(board_view)", "pres封装 V2") + _anchor("_hover_comp\\.(rebind|reset|hover_at|detect_chord|refresh_after_paint|on_shell_mouse_exited|hover_pos)", + board, "悬停七口正锚(board_view)", "pres封装 V2") + + +# ③ V3 B 组合根交接面(main+五分文件——成对单行转发梯换代为公开入口+Callable 束) +func _sweep_b_combo_root() -> void: + var b_files := { + "res://presentation/main.gd": false, + "res://presentation/main_ui_build.gd": true, + "res://presentation/main_adventure_flow.gd": true, + "res://presentation/main_overlay_flow.gd": true, + "res://presentation/main_debug_flow.gd": true, + "res://presentation/main_esc_flow.gd": true, + } + for path in b_files: + var src := _src(path) + if src.length() == 0: + continue + if b_files[path]: + _zero("root\\._[a-zA-Z]", src, "root 私取零命中(%s)" % path.get_file(), "pres封装 V3") + _zero("Main(Overlay|Adventure|Debug|Esc)Flow\\._[a-zA-Z]", src, "分文件私取零命中(%s)" % path.get_file(), "pres封装 V3") + var main_src := FileAccess.get_file_as_string("res://presentation/main.gd") + if main_src.length() > 0: + for fn in ["set_treasure_ui", "treasure_ui", "set_bonus", "bonus", "set_camp_pres", "camp_pres", + "set_debug", "debug", "set_tune_sim", "tune_sim", "set_confirm_consumer", "refresh_presentations", + "quit_game", "begin_session", "open_save_select", "seed_value"]: + _anchor("func %s\\(" % fn, main_src, "main 公开口正锚(%s)" % fn, "pres封装 V3") + var wiring := FileAccess.get_file_as_string("res://presentation/main_ui_build.gd") + if wiring.length() > 0: + _anchor("handlers\\[\"board_action\"\\]", wiring, "束消费正锚(ui_build)", "pres封装 V3") + _anchor("root\\.set_treasure_ui\\(", wiring, "句柄设置口正锚(ui_build)", "pres封装 V3") + _anchor("root\\.camp_pres\\(\\)\\.", wiring, "句柄读投影正锚(ui_build)", "pres封装 V3") + var adv := FileAccess.get_file_as_string("res://presentation/main_adventure_flow.gd") + if adv.length() > 0: + _anchor("handlers\\[\"adventure_finished\"\\]", adv, "束消费正锚(adventure)", "pres封装 V3") + _anchor("root\\.bonus\\(\\)\\.", adv, "句柄读投影正锚(adventure)", "pres封装 V3") + + +# ④ V4 C 杂项(UiKit 私有工厂公开化+cell_mark 构造交付) +func _sweep_c_misc() -> void: + var offenders: PackedStringArray = [] + var scanned := 0 + _walk_gd_for("res://", "UiKit\\._[a-zA-Z]", "res://presentation/views/ui_kit.gd", offenders) + scanned = _scanned + Kit.check("UiKit 私有工厂全库零外调(扫描面 %d 件非空前置;越界=%s)〔pres封装 V4〕" + % [scanned, str(offenders)], offenders.is_empty()) + var cm := _src("res://presentation/views/cell_mark_overlay.gd") + if cm.length() > 0: + _zero("_owner\\._", cm, "宿主私有回引零命中(cell_mark_overlay)", "pres封装 V4") + _anchor("_mask_set\\.is_valid\\(\\)", cm, "构造交付正锚(cell_mark_overlay)", "pres封装 V4") + var uk := _src("res://presentation/views/ui_kit.gd") + if uk.length() > 0: + _anchor("func (gradient_base|beams_rect|switch_toggle)\\(", uk, "三工厂公开正锚(ui_kit)", "pres封装 V4") + + +func _walk_gd_for(dir_path: String, pattern: String, exclude: String, offenders: PackedStringArray) -> void: + var re := RegEx.create_from_string(pattern) + var dir := DirAccess.open(dir_path) + if dir == null: + return + dir.list_dir_begin() + var name := dir.get_next() + while name != "": + var full := dir_path.path_join(name) + if dir.current_is_dir(): + if not name.begins_with("."): + _walk_gd_for(full, pattern, exclude, offenders) + elif name.ends_with(".gd") and full != exclude: + _scanned += 1 + if re.search(FileAccess.get_file_as_string(full)) != null: + offenders.append(full) + name = dir.get_next() + dir.list_dir_end() + + +# ① V1 A1 三内部数据类(字段公开化——宿主直读写内类私有字段清零) +func _sweep_a1_data_classes() -> void: + for path in A1_FILES: + var spec: Dictionary = A1_FILES[path] + var src := _src(path) + if src.length() == 0: + continue + var cls: String = spec["cls"] + var inst: String = spec["inst"] + _anchor("class %s" % cls, src, "class 正锚(%s)" % cls) + _zero("%s\\._[a-zA-Z]" % inst, src, "实例私取模式零命中(%s.%s)" % [path.get_file(), inst]) + for field in spec["fields"]: + _anchor("\\.%s\\b" % field, src, "公开字段正锚(%s.%s)" % [cls, field]) + # 类名文件外零消费者:全 game/ .gd 递归扫描(排除三类宿主文件与本测试件自身——本件源文本含类名 token) + var hosts := ["res://presentation/views/level_map_screen.gd", + "res://presentation/views/battle_end_overlay.gd", "res://presentation/views/debug_overlay.gd"] + var offenders: PackedStringArray = [] + _walk_gd("res://", hosts, offenders) + Kit.check("A1 三类文件外零消费者(全 game/ .gd 扫描面 %d 件非空前置;越界=%s)" + % [_scanned, str(offenders)], offenders.is_empty()) + + +var _scanned := 0 + + +func _walk_gd(dir_path: String, exclude: Array, offenders: PackedStringArray) -> void: + var dir := DirAccess.open(dir_path) + if dir == null: + return + dir.list_dir_begin() + var name := dir.get_next() + while name != "": + var full := dir_path.path_join(name) + if dir.current_is_dir(): + if not name.begins_with("."): + _walk_gd(full, exclude, offenders) + elif name.ends_with(".gd") and not exclude.has(full) and not full.contains("presentation_encapsulation_sweep_test"): + _scanned += 1 + var src := FileAccess.get_file_as_string(full) + for cls in ["_MapState", "_EndOverlayState", "_DebugState"]: + if src.contains(cls): + offenders.append("%s:%s" % [full, cls]) + name = dir.get_next() + dir.list_dir_end() diff --git a/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd.uid b/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd.uid new file mode 100644 index 00000000..967c8cc9 --- /dev/null +++ b/game/tests/unit/presentation/presentation_encapsulation_sweep_test.gd.uid @@ -0,0 +1 @@ +uid://bk88rqu43srjn diff --git a/game/tests/unit/presentation/seal_mark_params_test.gd b/game/tests/unit/presentation/seal_mark_params_test.gd new file mode 100644 index 00000000..2b72e8b5 --- /dev/null +++ b/game/tests/unit/presentation/seal_mark_params_test.gd @@ -0,0 +1,52 @@ +# tests/unit/presentation/seal_mark_params_test.gd — 封印印记参数化绘制守门(卡④ R1–R4) +# 断言面:五常量取值逐项相等(ui_kit ↔ art-bible §8.2 对照);着色器文件存在且七个 uniform 名在文; +# 迷雾门控(seal_on × fog_state → 封印子件可见性)〔seal批 V1–V4〕。 +# 运行:godot --headless --path game --script res://tests/unit/presentation/seal_mark_params_test.gd +extends SceneTree + +const Kit := preload("res://tests/unit/test_kit.gd") +const SEAL_SHADER := "res://presentation/views/seal_mark.gdshader" +const CELL_UNIT := preload("res://presentation/views/board_cell_unit.gd") + + +func _init() -> void: + _test_param_values() + _test_shader_uniforms() + _test_fog_gate() + quit(Kit.finish("seal_mark_params_test")) + + +# R1–R3/R7 前置:五常量取值逐项相等(art-bible §8.2 定值) +func _test_param_values() -> void: + Kit.check("SEAL_RING_FRAC = 0.82〔seal批 V1〕", UiKit.SEAL_RING_FRAC == 0.82) + Kit.check("SEAL_WALL_FRAC = 0.11〔seal批 V2〕", UiKit.SEAL_WALL_FRAC == 0.11) + Kit.check("SEAL_ARM_FRAC = 0.156〔seal批 V2〕", UiKit.SEAL_ARM_FRAC == 0.156) + Kit.check("SEAL_HALO_ALPHA = 0.50〔seal批 V3〕", UiKit.SEAL_HALO_ALPHA == 0.50) + Kit.check("SEAL_HALO_GAMMA = 1.6〔seal批 V3〕", UiKit.SEAL_HALO_GAMMA == 1.6) + + +# R1–R3/R7:着色器存在+七个 uniform 名在文(参数注入面契约) +func _test_shader_uniforms() -> void: + var src := FileAccess.get_file_as_string(SEAL_SHADER) + Kit.check("着色器文件存在且非空:%s〔seal批 V1〕" % SEAL_SHADER, src.length() > 0) + if src.length() == 0: + return + for uniform in ["cell_px", "ring_frac", "wall_frac", "arm_frac", "halo_frac", "halo_alpha", "halo_gamma", "mark_col"]: + Kit.check("着色器 uniform 在文:%s〔seal批 V1〕" % uniform, src.contains(uniform)) + Kit.check("着色器含光圈半径夹取行(r ≤ cell_px/2——光圈不出格)〔seal批 V3〕", + src.contains("min(r, cell_px * 0.5)")) + + +# R4 迷雾门控:seal_on × fog_state → 封印子件可见性(内部字段直查=受控例外——子件可见性无公开可观测面, +# 卡面 R4 断言即 prescribe 子件 visible 直查;对照表该行注记理由) +func _test_fog_gate() -> void: + var u: Node2D = CELL_UNIT.new() + u.seal_on = true + Kit.check("seal_on 即挂封印子件〔seal批 V4〕", u._seal_layer != null) + u.fog_state = 1 + Kit.check("雾态(fog_state=1)封印子件不可见〔seal批 V4〕", u._seal_layer.visible == false) + u.fog_state = 0 + Kit.check("非雾态(fog_state=0)封印子件可见〔seal批 V4〕", u._seal_layer.visible == true) + u.seal_on = false + Kit.check("撤 seal_on 即撤子件〔seal批 V4〕", u._seal_layer == null) + u.free() diff --git a/game/tests/unit/presentation/seal_mark_params_test.gd.uid b/game/tests/unit/presentation/seal_mark_params_test.gd.uid new file mode 100644 index 00000000..745ec521 --- /dev/null +++ b/game/tests/unit/presentation/seal_mark_params_test.gd.uid @@ -0,0 +1 @@ +uid://cc7y2oxe2d15p diff --git a/game/tests/unit/presentation/stage_background_test.gd b/game/tests/unit/presentation/stage_background_test.gd index 582acba3..cf14e387 100644 --- a/game/tests/unit/presentation/stage_background_test.gd +++ b/game/tests/unit/presentation/stage_background_test.gd @@ -161,5 +161,5 @@ func _test_beams_retired() -> void: not cmap.has("BEAM_LIGHT") and not cmap.has("STAGE_BG")) var uf := FileAccess.open("res://presentation/views/ui_kit.gd", FileAccess.READ) var usrc := uf.get_as_text() if uf != null else "" - Kit.check("菜单斜光带 _beams_rect 仍在(不在退役面)〔舞台背景 V5〕", - usrc.contains("_beams_rect")) + Kit.check("菜单斜光带 beams_rect 仍在(不在退役面)〔舞台背景 V5〕", + usrc.contains("beams_rect")) diff --git a/game/tests/unit/presentation/theme_projection_test.gd b/game/tests/unit/presentation/theme_projection_test.gd index 71dacd2b..7c46c3c2 100644 --- a/game/tests/unit/presentation/theme_projection_test.gd +++ b/game/tests/unit/presentation/theme_projection_test.gd @@ -623,7 +623,7 @@ func _smoke_texture_filter() -> void: Kit.check("V3 白名单在位:暗角 vignette 显式 LINEAR", vg.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR) vg.free() - var bm = uikit_s._beams_rect() + var bm = uikit_s.beams_rect() Kit.check("V3 白名单在位:光柱 beams 显式 LINEAR", bm.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR) # T4 光柱连续 alpha 结构断言(观感归目检):带心峰值 0.05/带缘连续过渡/纵向衰减上亮下暗 @@ -648,7 +648,7 @@ func _smoke_texture_filter() -> void: Kit.check("T4 光柱纵向衰减生效(上半亮度 %.0f > 下半 %.0f 的 2 倍——上亮下暗)" % [top_sum, bot_sum], top_sum > bot_sum * 2.0) bm.free() - var gb = uikit_s._gradient_base(Color.BLACK, Color.WHITE) + var gb = uikit_s.gradient_base(Color.BLACK, Color.WHITE) Kit.check("V3 白名单在位:地图渐变 gradient_base 显式 LINEAR", gb.texture_filter == CanvasItem.TEXTURE_FILTER_LINEAR) gb.free() diff --git a/tools/gdstyle_baseline.txt b/tools/gdstyle_baseline.txt index 13b21725..84f9aaeb 100644 --- a/tools/gdstyle_baseline.txt +++ b/tools/gdstyle_baseline.txt @@ -8,5 +8,7 @@ game/core/adventure_runner.gd|quality/max-class-variables = 23 game/core/level_runner.gd|quality/max-class-variables = 30 game/core/level_runner.gd|quality/max-public-methods = 33 +game/presentation/main.gd|quality/max-public-methods = 32 +game/presentation/views/ui_kit.gd|quality/max-public-methods = 23 game/presentation/views/board_view.gd|quality/max-class-variables = 31 game/presentation/views/board_view.gd|quality/max-public-methods = 27