From d44fdd54c5db6cb92d57980c3570809c110d0cc0 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 6 Oct 2026 22:38:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=82=E5=81=9C=E5=B1=82=E7=BB=84B=EF=BC=9A?= =?UTF-8?q?=E4=B8=A4=E5=B1=8F=E5=85=A5=E5=8F=A3=E8=A7=92=E6=A0=87+has=5Fop?= =?UTF-8?q?en=E5=AE=88=E5=8D=AB+=E5=9B=9B=E6=8E=A2=E9=92=88=E6=96=AD?= =?UTF-8?q?=E8=A8=80+=E5=86=92=E7=83=9F=E6=95=B0=E6=8D=AE=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E6=80=A7=EF=BC=88=E8=BF=AD=E4=BB=A317=E5=8D=A1?= =?UTF-8?q?=E2=91=A2T2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - modal_stack.gd 新增 has_open_excluding(skip):排除式开态查询(地图屏系登记册模态成员=常态 屏态,其上角标必须可点;只读查询零副作用,登记序/开合记账语义零改动) - board_zone_view.gd 战斗屏 HUD 右下角+level_map_screen.gd 地图屏顶栏 GP 徽右——各一枚「暂停」 角标(debug 角标同形制:UiKit.button 紧凑豁免宽度基准+FS_HINT 字号降档;可见性/置灰归组合根, 信号连接归组合根 ADR-0001;非对局期显式不可见防菜单误现——P1 丁) - main.gd 接线:两角标 pressed→_on_pause_badge_pressed(守卫兜底处理器:他层开着即拒开—— 常态防线=置灰,探针 emit 绕过 disabled 时由此兜住;地图屏开态经 has_open_excluding 不计自身) +_sync_pause_entry 单一同步点(可见性=_level_active∧屏态;置灰=has_open)挂模态开合单口与 屏级互斥口/进局/离局/续玩五路径 - ui_struct_probe:pause 结构断言+只读性契约(游戏语义信号清单巡检——pressed/toggled/text/ value/item/gui_input/focus/closed 十二名,白名单外零连接,合法连接恰 2=关闭钮+closed 外发) +零动效契约(子树零 AnimationPlayer+源码零 create_tween——R5/P4 甲)【首跑 L1 修正:全集 巡检把 Godot 容器布局生命线连接(child_order_changed/item_rect_changed 等引擎自动脏标记) 误判非法——收敛为信号名清单,任务书 v1.1「巡检信号名清单落码列全」本意】 - ui_input_probe case⑯:暂停层输入锁语义(角标真点击 press+release 开层→锁期挖掘零穿透→ 「继续」关层→同格挖掘重新到达;十七断言 PASS)【首跑 L1 修正:域恰 2 枚时 flag_t 带旗+dig_t 揭开后零无旗格可取——case 挪全 case 之尾+前沿移位扩大可挖域兜底(板面扰动零波及)】 - ui_click_probe:战斗屏角标开合/守卫断言(图例开着=置灰+emit 兜底拒开)+地图屏角标一次性 case(选路前开/关,地图屏选路态零扰动);ui_contrast_probe 增钥石徽深墨/烛光黄对(12 对全绿) - smoke_all._pause_overlay_projection:暂停层投影==MetaProgression 访问器逐项(余额徽/锻造三行/ 工坊六行解锁态/天赋树概览——隔离档 smoke_meta 复用,+5 check) - 任务书回填 T0 基线注记(冒烟 783/0·run_all 17 套·交集核对零命中,HEAD=44328b1 时点) - T2 闸读数:冒烟 788/0(基线 783/0,+5 本批)· ui_input 十七断言 PASS · ui_struct 全绿 · ui_contrast 12 对 PASS · ui_click 问题=0 · ui_layout_probe 退出码 0;纯呈现零数值=零 MC --- .../任务书-暂停层只读入口批-2026-10-05.md | 2 + game/presentation/main.gd | 32 ++++++++ game/presentation/modal_stack.gd | 12 +++ game/presentation/views/board_zone_view.gd | 10 +++ game/presentation/views/level_map_screen.gd | 9 +++ game/smoke/smoke_all.gd | 76 +++++++++++++++++++ game/smoke/ui_click_probe.gd | 51 +++++++++++++ game/smoke/ui_contrast_probe.gd | 1 + game/smoke/ui_input_probe.gd | 65 +++++++++++++++- game/smoke/ui_struct_probe.gd | 68 +++++++++++++++++ 10 files changed, 325 insertions(+), 1 deletion(-) diff --git a/design/research/任务书-暂停层只读入口批-2026-10-05.md b/design/research/任务书-暂停层只读入口批-2026-10-05.md index 3de46cc0..84a0ad7a 100644 --- a/design/research/任务书-暂停层只读入口批-2026-10-05.md +++ b/design/research/任务书-暂停层只读入口批-2026-10-05.md @@ -1,5 +1,7 @@ # 任务书:暂停层只读入口批(v1.1 定稿,零待裁点,开工门=U0 用户发令) +> **T0 基线回填(2026-10-06 执行会话实测,HEAD=44328b1)**:冒烟 **783/0**(§1.2 注记 782 系 ed38c54 时点参考值,卡④落籍后 +1,以本读数为对拍基线)· run_all **17 套全绿** · probe_gate 默认段(ui_input+ui_struct+ui_contrast)全绿。§2 事实表 F1-F10 逐条实核全绿(F2 登记册=main.gd:332、z 序注记=:242-243;F3 badge 工厂缺位+营地本地拼装=camp_overlay_view.gd:79-92;F5 访问器行号逐一符合、boss_kill_count=save_manager.gd:250 精确;F8 settings=main.gd:260;F10 dbgbtn=ui_assembler.gd:161-164)。**迭代 15② 交集核对:零命中**——卡②/卡③均已 2026-10-05 落籍(roadmap 现行口径,无在飞面);在飞 worktree 三件(iter17-dd 卡⓪〔尚无超前提交〕/uialign/standee〔均零 diff〕)与本批文件面零交集。 + > 成文 2026-10-05,v1.1 修订 2026-10-06(五任务书联审:R4 快照口径收敛、badge 工厂路径裁决〔Q10 甲〕、P1 丁的 06 Esc 登记补办、基线换代 iter16ui 批后、F5/F10 事实勘误、入口守卫与只读断言口径补列)。 > **性质**:功能性 UI 交互新增——局内暂停层,承载钥石余额与营地柜台解锁进度的**只读**查看。 > 事实源 = [06-待办清单](../gdd/06-待办清单.md)「解锁界面与钥石呈现的交互形态(残余:暂停层只读入口)」条。 diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 9b077747..6bc64d3c 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -224,6 +224,7 @@ func _build_ui() -> void: func() -> Variant: return settlement, func() -> Variant: return runner) board_zone.action_requested.connect(_on_board_action) + board_zone.pause_badge.pressed.connect(_on_pause_badge_pressed) # 暂停层入口(迭代 17 卡③——战斗屏 HUD 角标) info_panel.treasure_slot_clicked.connect(_treasure_ui.on_slot_clicked) # 迭代 7 T4——两步交互第一步 board_zone.stop_pressed.connect(_on_stop_pressed) board_zone.refresh_confirmed.connect(_on_refresh_confirmed) @@ -256,6 +257,7 @@ func _build_ui() -> void: legend_overlay.closed.connect(_on_modal_closed.bind(legend_overlay)) node_overlay.continue_pressed.connect(_on_node_continue) level_screen.route_chosen.connect(_on_route_chosen) + level_screen.pause_badge.pressed.connect(_on_pause_badge_pressed) # 暂停层入口(迭代 17 卡③——地图屏顶栏角标) level_screen.abandon_requested.connect(_on_abandon_requested) # 放弃入口(2026-09-21 用户拍板——台账 119) _end_overlay.continue_pressed.connect(_on_battle_end_continue) settings_overlay = SettingsOverlay.new(settings_store) # 迭代 8 卡④——最小设置面(音量/全屏/减动效) @@ -367,10 +369,12 @@ func _open_modal(m: Control) -> void: # 打开时显式隐藏树序在其上方的可见层(遮挡物隐藏),关闭恢复——单一模态栈 _flush_row_queue() # 卡⑦——任何覆盖层开启前排空排播队列(明细带恒完整,回看/停点面不缺行) _modals.open(m) + _sync_pause_entry() # 迭代 17 卡③——暂停层入口角标可见性/置灰随开态同步 func _close_modal(m: Control) -> void: _modals.close(m) + _sync_pause_entry() # 迭代 17 卡③——关闭恢复角标可点态(无残留——R3 同源口径) func _on_modal_closed(m: Control) -> void: @@ -436,6 +440,7 @@ func _resume_from_menu() -> void: _start_overlay.visible = false _wire_adventure() _level_active = true + _sync_pause_entry() # 迭代 17 卡③——续玩恢复点亮暂停入口角标 level_screen.set_abandon_available(true) _reset_run_scope() log_view.clear() @@ -470,6 +475,7 @@ func _wire_adventure() -> void: func _start_adventure() -> void: _wire_adventure() _level_active = true + _sync_pause_entry() # 迭代 17 卡③——进对局点亮暂停入口角标 level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期——2026-09-21 用户拍板) _reset_run_scope() log_view.clear() @@ -546,6 +552,7 @@ func _on_adventure_finished(summary: Dictionary) -> void: var result: String = String(summary.get("result", "")) settlement = null _exit_level_screen() # 终局兜底:独立屏在开态(放弃入口)也先回战斗屏呈现摘要——屏级互斥单一出口 + _sync_pause_entry() # 迭代 17 卡③——离局熄灭暂停入口角标(防菜单误现) _sync_settlement_visuals() # → 全关 HudPresenter.battle_end_feedback(stage, ap_bar, false) # 灰化/idle 回正/状态全清——HUD 单通道(T3 收口批) # 存档入账(元进度实现批):settle = Boss 两档入账 + 工坊解锁 + 摘要入档 + 写盘(唯一写档点不变—— @@ -896,6 +903,7 @@ func _enter_level_screen() -> void: _level_screen_active = true _battle_root.visible = false _open_modal(level_screen) + _sync_pause_entry() # 迭代 17 卡③——屏级互斥切换同步(_open_modal 单口不识屏级状态) func _exit_level_screen() -> void: @@ -903,6 +911,7 @@ func _exit_level_screen() -> void: _battle_root.visible = true if level_screen.visible: _close_modal(level_screen) + _sync_pause_entry() # 迭代 17 卡③——同上 func _on_abandon_requested() -> void: @@ -963,6 +972,29 @@ func _on_pause_closed() -> void: _close_modal(pause_overlay) # 模态栈单口 +# 入口角标点击(两屏同款处理器):P1 守卫兜底——其他模态开着即拒开(常态防线=角标置灰, +# 探针 pressed.emit 绕过 disabled 时由此兜住;地图屏自身的登记开态不计——has_open_excluding) +func _on_pause_badge_pressed() -> void: + var map_on: bool = level_screen != null and level_screen.visible + if _modals == null or (map_on and _modals.has_open_excluding(level_screen)) \ + or (not map_on and _modals.has_open()): + return + _open_pause_layer() + + +# 入口角标可见性/置灰同步(P1 丁+守卫——单一同步点):战斗屏角标=对局内且地图屏未激活; +# 地图屏角标=随屏;置灰=登记册上除地图屏自身外任一层开着(防「看得见点不着」态)。 +# 调用面=模态开合单口与屏级互斥口(凡影响开态/屏态的路径必经其一)。 +func _sync_pause_entry() -> void: + if pause_overlay == null or _modals == null or board_zone == null or level_screen == null: + return + var map_on: bool = level_screen.visible + board_zone.pause_badge.visible = _level_active and not map_on + level_screen.pause_badge.visible = map_on + board_zone.pause_badge.disabled = _modals.has_open() + level_screen.pause_badge.disabled = _modals.has_open_excluding(level_screen) + + # ---- 探针缝保缝(T4/D4 拆件——click/ui_probe 营地段直呼组合根面;实现归 camp_shop_presenter.gd)---- func _on_camp_closed() -> void: diff --git a/game/presentation/modal_stack.gd b/game/presentation/modal_stack.gd index 642cf409..80f1ac55 100644 --- a/game/presentation/modal_stack.gd +++ b/game/presentation/modal_stack.gd @@ -41,3 +41,15 @@ func has_open() -> bool: if is_instance_valid(m) and m.visible: return true return false + + +# 排除式开态查询(迭代 17 卡③ 2026-10-06——暂停层入口守卫判据):skip 层自身的开态不计入 +#(关卡地图屏系登记册模态成员=常态屏态,其上的地图屏角标必须可点;其余任一层开着=角标置灰)。 +# 只读查询零副作用——登记序/开合记账语义零改动。 +func has_open_excluding(skip: Control) -> bool: + for m in _list: + if m == skip: + continue + if is_instance_valid(m) and m.visible: + return true + return false diff --git a/game/presentation/views/board_zone_view.gd b/game/presentation/views/board_zone_view.gd index 776ce1a1..301bf3bb 100644 --- a/game/presentation/views/board_zone_view.gd +++ b/game/presentation/views/board_zone_view.gd @@ -11,6 +11,7 @@ var ap_bar: ApBarView var board_view: BoardView var toast: ToastStack var fx: BoardFxView +var pause_badge: Button # 暂停层入口角标(迭代 17 卡③ P1 丁——debug 角标同形制;连接归组合根) var _board_area: Control # resized 命名转发用(原 lambda 局部捕获——P1-3 转命名 2026-09-29) signal action_requested(cell: Vector2i, kind: String) @@ -41,6 +42,15 @@ func _init() -> void: board_area.add_child(fx) toast = ToastStack.new() board_area.add_child(toast) + # 暂停层入口角标(迭代 17 卡③ P1 丁——战斗屏 HUD 右下角常驻;debug 角标同形制:紧凑钮豁免 + # 宽度基准+字号降档+角距 16px。可见性/置灰归组合根 _sync_pause_entry(其他模态开着=置灰 + # 不可点——has_open 守卫);信号连接归组合根(ADR-0001——装配器/视图零连接) + pause_badge = UiKit.button("暂停", 0.0) # 角落小件豁免宽度基准(同调试角标) + pause_badge.add_theme_font_size_override("font_size", UiKit.FS_HINT) + pause_badge.tooltip_text = "查看钥石与永久强化进度(只读)" + pause_badge.visible = false # 显式初始化——非对局期不误现(可见性归组合根同步) + pause_badge.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_RIGHT, Control.PRESET_MODE_MINSIZE, 16) + board_area.add_child(pause_badge) # M8(UI 落码批):棋盘底常驻操作提示条(demo ⑦——文案经 board_view 输入代码证实; # 旧 chord 一次性 toast 提示退役归此常驻条;审计第 7 条用户批) var hint := PanelContainer.new() diff --git a/game/presentation/views/level_map_screen.gd b/game/presentation/views/level_map_screen.gd index 50bf204e..a627361b 100644 --- a/game/presentation/views/level_map_screen.gd +++ b/game/presentation/views/level_map_screen.gd @@ -61,6 +61,7 @@ var _cand_ids: Array[int] = [] # 本停点候选 id 集(图上命 var _content_size := Vector2(620, 240) # 图内容包络(open_choices 按图拟合——画布 min 尺寸随它) var _hover_id := -1 # 悬停预承诺候选(-1 = 无) var abandon_btn: Button # 放弃入口(可见性归组合根 set_abandon_available——只在冒险模式开) +var pause_badge: Button # 暂停层入口角标(迭代 17 卡③ P1 丁——顶栏 GP 徽右侧;连接归组合根) var _abandon_timer := ArmedTimer.new() # 放弃确认窗(T3/D3 P4 乙——_process 帧驱动倒计时机制收口;窗口=CONFIRM_WINDOW_S) var _node_icons := {} # 节点图标贴图(load_tex 守卫——缺位 null → 字形回退,冒烟/无素材检出安全) var _noise_rect: TextureRect # 屏底石纹(换层色板渐变承载面——null = 纯色回退无挂点) @@ -112,6 +113,14 @@ func _init() -> void: gp_box.add_child(gp_row) _gp_value = UiKit.label("",UiKit.FS_BODY, true, UiKit.INK_ON_GOLD) gp_row.add_child(_gp_value) + # 暂停层入口角标(迭代 17 卡③ P1 丁——顶栏常驻;debug 角标同形制:紧凑钮豁免宽度基准+字号降档。 + # 置灰守卫归组合根 _sync_pause_entry(其他模态开着=不可点;本屏自身的登记开态不计——has_open_excluding); + # 信号连接归组合根(ADR-0001——视图零连接)) + pause_badge = UiKit.button("暂停", 0.0) + pause_badge.add_theme_font_size_override("font_size", UiKit.FS_HINT) + pause_badge.tooltip_text = "查看钥石与永久强化进度(只读)" + pause_badge.size_flags_vertical = Control.SIZE_SHRINK_CENTER + top.add_child(pause_badge) _title = UiKit.label(TITLE_FORK,UiKit.FS_SUB, true, UiKit.WARM) _title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(_title) diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index 9efd35de..5239d316 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -4112,6 +4112,82 @@ func _smoke_meta() -> void: _meta_pricing_and_buy() _meta_settle_and_workshop() _meta_injection() + _pause_overlay_projection() + + +# ---- 暂停层只读投影一致性(迭代 17 卡③——R4 数据只读同源:暂停层展示值逐项 == MetaProgression +# 访问器读数;投影拼装与 main._pause_data 同构(同访问器族同序)。只读性连接巡检与零动效 +# 断言归 ui_struct_probe 呈现侧,本段只管数据面)---- +func _pause_overlay_projection() -> void: + print("--- 暂停层·只读投影一致性(隔离档 smoke_meta_buy 复用)---") + _meta_smoke_cleanup() + var sv = _meta_new_save(_META_SMOKE_PATHS[0]) + var mp = load("res://core/meta_progression.gd").new(_db, sv) + # 已购态夹具:余额/锻造两档非零 + 工坊首件解锁——投影可辨非全零白板 + sv.add_keystone(37) + sv.set_forge_level("atk", 2) + sv.set_forge_level("max_hp", 1) + var ws_meta: Array = _db.meta_workshop_items() + if not ws_meta.is_empty(): + sv.unlock_item(String(ws_meta[0].get("item_id", ""))) + sv.write_to_disk() + # 投影拼装(main._pause_data 同构)+ 视图喂入 + var data := {"balance": mp.keystone_balance(), "forge": [], "workshop": mp.workshop_status(), + "tree": mp.tree_camp_rows(), "fork_groups": mp.tree_fork_groups()} + for attr in MetaTable.FORGE_ATTRS: + var frow: Dictionary = _db.meta_forge_row(attr) + data["forge"].append({"attr": attr, "display_name": String(frow.get("display_name", "")), + "level": mp.forge_level(attr), "flat": int(frow.get("flat_value", 0))}) + var po = load("res://presentation/views/pause_overlay.gd").new() + po.open_pause(data) + check(po.visible, "暂停层开层可见(open_pause 直开——P4 甲零动效路径)") + check(String(po._balance_badge.value_label.text) == "钥石 %d" % mp.keystone_balance(), + "余额徽投影 = keystone_balance() 读数(%s)" % po._balance_badge.value_label.text) + var forge_ok: bool = po._forge_holder.get_child_count() == MetaTable.FORGE_ATTRS.size() + var fi := 0 + for attr in MetaTable.FORGE_ATTRS: + if fi >= po._forge_holder.get_child_count(): + forge_ok = false + break + var lbls: Array = [] + for ch in po._forge_holder.get_child(fi).get_children(): + if ch is Label: + lbls.append(ch) + forge_ok = forge_ok and lbls.size() == 2 \ + and String(lbls[0].text).begins_with(String(_db.meta_forge_row(attr).get("display_name", ""))) \ + and String(lbls[1].text) == "Lv %d" % mp.forge_level(attr) + fi += 1 + check(forge_ok, "锻造三行投影逐行 = display_name + forge_level() 读数(已购夹具 atk Lv2 / max_hp Lv1 在列)") + var ws: Array = mp.workshop_status() + var ws_ok: bool = po._workshop_holder.get_child_count() == ws.size() + for i in mini(ws.size(), po._workshop_holder.get_child_count()): + var row: Dictionary = ws[i] + var lbls2: Array = [] + for ch in po._workshop_holder.get_child(i).get_children(): + if ch is Label: + lbls2.append(ch) + var unlocked: bool = bool(row.get("unlocked", false)) + var expect_st := "未解锁" + if bool(row.get("retired", false)): + expect_st = "已退役" + elif bool(row.get("deferred", false)) and unlocked: + expect_st = "已解锁 · 能力封存中" + elif unlocked: + expect_st = "已解锁" + ws_ok = ws_ok and lbls2.size() == 3 \ + and String(lbls2[0].text) == String(row.get("display_name", "")) \ + and String(lbls2[1].text) == "累计通关 %d/%d 次" % [int(row.get("progress", 0)), int(row.get("threshold", 0))] \ + and String(lbls2[2].text) == expect_st + check(ws_ok, "工坊六行投影 = workshop_status() 逐项(名称/判定量进度/解锁态——首件解锁夹具在列)") + var owned := 0 + for trow in (mp.tree_camp_rows() as Array): + if int(trow.get("level", 0)) > 0: + owned += 1 + var tree_txt := String(po._tree_label.text) + check(tree_txt == "尚未购买任何节点(天赋树在营地柜台三)" or tree_txt.begins_with("已购 %d 项:" % owned), + "天赋树概览聚合文案与已购节点数一致(零已购=空态句)") + po.visible = false + po.free() # 冒烟隔离存档名册(段首清陈旧 + 段尾清理——契约:测试不污染 user://save.json 真实档) diff --git a/game/smoke/ui_click_probe.gd b/game/smoke/ui_click_probe.gd index 0fa541e2..99b315c9 100644 --- a/game/smoke/ui_click_probe.gd +++ b/game/smoke/ui_click_probe.gd @@ -27,6 +27,8 @@ var _tested_review := false var _tested_end_review := false # 迭代 6 T7——终局面板回看入口(z 序修复回归) var _tested_refresh_chain := false var _tested_tooltip := false +var _tested_pause := false # 迭代 17 卡③——暂停层角标开合/守卫断言(每 run 一次) +var _tested_map_pause := false # 迭代 17 卡③——地图屏角标一次性 case(选路态零扰动) var _inter_confirms := 0 # 大奖励屏确认计数(冒险推进停点——首见确认推进 L2,再见 = L2 通关收口) var _settlement_confirms := 0 # 卡⑥ 胜利结算框确认计数(观测面——每场普通战斗胜利恰一) var _route_clicks := 0 # 选路覆盖层点击计数(岔路使用率观测面) @@ -220,6 +222,22 @@ func _ready() -> void: continue if main.level_screen.visible: # 节点图选路口(零自动推进停点)——图上命中路径交替选候选(岔路使用行为;布局决策 2 改造) + # 迭代 17 卡③——地图屏角标一次性 case:选路前经角标开/关暂停层(层在地图屏之上开合, + # 关闭后地图屏选路态零扰动——P2 甲层级语义) + if not _tested_map_pause and not main.pause_overlay.visible: + _tested_map_pause = true + var mpb: Button = main.level_screen.pause_badge + if mpb == null or not mpb.visible or mpb.disabled: + _problems.append("地图屏暂停角标缺失/未点亮/被置灰(P1 丁)") + else: + mpb.pressed.emit() + if not main.pause_overlay.visible or not main.level_screen.visible: + _problems.append("地图屏角标开暂停层失败或地图屏被误藏(P2 甲层级失守)") + var rbtn2: Button = ProbeUiUtil.find_button(main.pause_overlay, "继续", "prefix") + if rbtn2 != null: + rbtn2.pressed.emit() + if main.pause_overlay.visible or not main.level_screen.visible: + _problems.append("暂停层关闭后地图屏态失守(恢复语义破)") main.level_screen.press_candidate(_route_clicks % 2) _route_clicks += 1 _flags_placed.clear() @@ -479,6 +497,39 @@ func _run_modal_probes_once() -> void: if main.legend_overlay.is_open() or main._input_locked(): _problems.append("图例关闭后未恢复输入/可见性") # ②/②b 明细回看模态探针退役(M9/P-C7——冒险中回看归「生涯」,06 登记) + # ⑤ 暂停层(迭代 17 卡③——P1 丁角标真实信号路径 + P2 甲模态语义 + P1 守卫 + R3 锁恢复): + # 角标打开 → 只读层可见+输入锁定+余额徽非空 → 「继续」关闭恢复;图例开着 → 角标置灰且点击被守卫兜住 + if not _tested_pause and not main.legend_overlay.is_open() and not main.pause_overlay.visible: + _tested_pause = true + var pbtn: Button = main.board_zone.pause_badge + if pbtn == null or not pbtn.visible or pbtn.disabled: + _problems.append("战斗屏暂停角标缺失/未点亮/被置灰(P1 丁入口失守 visible=%s disabled=%s)" + % [str(pbtn != null and pbtn.visible), str(pbtn != null and pbtn.disabled)]) + else: + pbtn.pressed.emit() # 真实按钮信号路径 → 组合根 _on_pause_badge_pressed + if not main.pause_overlay.visible: + _problems.append("暂停层未随角标打开(组合根接线失守)") + if not main._input_locked(): + _problems.append("暂停层打开时输入未锁定(R3)") + var bal_txt: String = main.pause_overlay._balance_badge.value_label.text + if not bal_txt.begins_with("钥石 "): + _problems.append("暂停层余额徽投影缺失(%s)" % bal_txt) + var resume_btn: Button = ProbeUiUtil.find_button(main.pause_overlay, "继续", "prefix") + if resume_btn == null: + _problems.append("暂停层关闭钮缺失(「继续」)") + else: + resume_btn.pressed.emit() + if main.pause_overlay.visible or main._input_locked(): + _problems.append("暂停层关闭后未恢复(visible=%s locked=%s——R3 残留)" + % [str(main.pause_overlay.visible), str(main._input_locked())]) + # P1 守卫:图例开着 → 两处入口角标置灰;置灰态点击被处理器兜住(探针 emit 绕过 disabled) + main.ap_bar._btn_legend.pressed.emit() + if not main.board_zone.pause_badge.disabled: + _problems.append("图例开着时战斗屏暂停角标未置灰(P1 守卫失守)") + main.board_zone.pause_badge.pressed.emit() + if main.pause_overlay.visible: + _problems.append("置灰态仍可开暂停层(守卫兜底失守)") + main.legend_overlay.close() # ③ tooltip(状态效果槽悬停——来源+语义+剩余):有槽可见时触发 if not _tested_tooltip: var row = main.stage.hero_standee.slot_row # 状态 tooltip 载体 2026-09-26 迁 B 区站桩 diff --git a/game/smoke/ui_contrast_probe.gd b/game/smoke/ui_contrast_probe.gd index 866c8662..34c5141a 100644 --- a/game/smoke/ui_contrast_probe.gd +++ b/game/smoke/ui_contrast_probe.gd @@ -28,6 +28,7 @@ func _pairs() -> Array: ["锚·正文墨/数据板", UiKit.INK, UiKit.PANEL_DATA, 1.0], # 全站锚点:正文墨对数据板 ["锚·暖白/屏根", UiKit.WARM, UiKit.BG_ROOT, 1.0], # 全站锚点:暗底正文 ["锚·暖白弱/舞台底", UiKit.WARM_FAINT, UiKit.STAGE_BG, 1.0], # 全站锚点:B区辅助墨 + ["钥石徽·深墨/烛光黄", UiKit.INK_ON_GOLD, UiKit.CANDLE, 1.0], # 迭代17卡③——badge 工厂件文字(营地徽+暂停层余额行同形制) ] diff --git a/game/smoke/ui_input_probe.gd b/game/smoke/ui_input_probe.gd index cf3c5803..f4cba8e5 100644 --- a/game/smoke/ui_input_probe.gd +++ b/game/smoke/ui_input_probe.gd @@ -415,8 +415,61 @@ func _ready() -> void: _problems.append("case⑮ 提灯关闭后雾参数未复原(lantern 应=0)") if int(bv.cells_probe()[ring_cell].fog_state) == 0: _problems.append("case⑮ 提灯关闭后 dist==R+1 格未复原遮蔽") + # ---- case⑯ 暂停层输入锁语义(迭代 17 卡③ R3——开层锁棋盘、关层恢复无残留;真实 push_input + # 路径:入口角标本体 press+release 真点击 → 暂停层开;锁期挖掘点击零穿透;「继续」关层后 + # 同格挖掘重新到达——锁语义与既有模态同款,无状态残留。置于全 case 之尾:取格若需前沿移位 + #(真实点击 move 扩大可挖域),其板面扰动不再波及任何既有断言——域恰 2 枚时 flag_t 带旗+ + # dig_t 揭开后域内零无旗格,正是首跑实测踩中的形态)---- + var pause_dig := Vector2i(-9, -9) + for c in bv.diggable_probe(): + if not ms.has_flag(c): + pause_dig = c + break + if pause_dig.x == -9: + var mv16 := _pick_frontier(bv, ms) + if mv16.x == -9: + _problems.append("case⑯ 板面无可迫近边界(锁断言取格中断)") + else: + _push_click(_pos(bv, mv16), MOUSE_BUTTON_LEFT) + await get_tree().process_frame + await get_tree().process_frame + for c in bv.diggable_probe(): + if not ms.has_flag(c): + pause_dig = c + break + if pause_dig.x == -9: + _problems.append("case⑯ 无可挖无旗格(板面异常——锁断言中断)") + else: + var pbtn16: Button = main.board_zone.pause_badge + var bpos: Vector2 = pbtn16.get_global_rect().get_center() + _push_click(bpos, MOUSE_BUTTON_LEFT) + _push_release(bpos, MOUSE_BUTTON_LEFT) # Button pressed 信号走 release 沿——按压+释放成对 + await get_tree().process_frame + if not main.pause_overlay.visible or not main._input_locked(): + _problems.append("case⑯ 角标真点击未开暂停层(visible=%s locked=%s——入口可达性失守)" + % [str(main.pause_overlay.visible), str(main._input_locked())]) + else: + _push_click(_pos(bv, pause_dig), MOUSE_BUTTON_LEFT) + await get_tree().process_frame + await get_tree().process_frame + if _has_action(pause_dig, "dig") or ms.is_revealed(pause_dig): + _problems.append("case⑯ 暂停层开启时挖掘穿透(R3 输入锁破——目标=%s)" % pause_dig) + var resume_btn: Button = ProbeUiUtil.find_button(main.pause_overlay, "继续", "prefix") + if resume_btn == null: + _problems.append("case⑯ 暂停层关闭钮缺失") + else: + resume_btn.pressed.emit() + await get_tree().process_frame + if main.pause_overlay.visible or main._input_locked(): + _problems.append("case⑯ 关闭后未恢复(visible=%s locked=%s——R3 残留)" + % [str(main.pause_overlay.visible), str(main._input_locked())]) + _push_click(_pos(bv, pause_dig), MOUSE_BUTTON_LEFT) + await get_tree().process_frame + await get_tree().process_frame + if not _has_action(pause_dig, "dig") or not ms.is_revealed(pause_dig): + _problems.append("case⑯ 关闭后挖掘未到达(恢复无残留验证失守)") if _problems.is_empty(): - print("[ui_input_probe] PASS — 悬停金框/禁入信号/路径预览/flag_place/chord/dig/英雄落位/影子构成/朝向/缩格重排/缺位回退/变异角标/视野迷雾 十六断言全绿(真实 push_input 路径——⑫图上命中⑬画布拟合 2026-09-28 增,⑭变异角标 2026-09-29 增,④b 旗纹理槽回归 2026-10-02 增,⑮视野迷雾 2026-10-03 增〔+提灯接线断言 2026-10-05 迭代14⑤〕)") + print("[ui_input_probe] PASS — 悬停金框/禁入信号/路径预览/flag_place/chord/dig/英雄落位/影子构成/朝向/缩格重排/缺位回退/变异角标/视野迷雾/暂停层输入锁 十七断言全绿(真实 push_input 路径——⑫图上命中⑬画布拟合 2026-09-28 增,⑭变异角标 2026-09-29 增,④b 旗纹理槽回归 2026-10-02 增,⑮视野迷雾 2026-10-03 增〔+提灯接线断言 2026-10-05 迭代14⑤〕,⑯暂停层输入锁 2026-10-06 迭代17卡③增)") get_tree().quit(0) else: for p in _problems: @@ -493,6 +546,16 @@ func _push_click(pos: Vector2, btn: int) -> void: main.get_viewport().push_input(ev, true) +# 释放沿(case⑯——BaseButton pressed 信号默认 release 触发:角标真点击须按压+释放成对) +func _push_release(pos: Vector2, btn: int) -> void: + var ev := InputEventMouseButton.new() + ev.position = pos + ev.global_position = pos + ev.button_index = btn + ev.pressed = false + main.get_viewport().push_input(ev, true) + + func _pre_fail(why: String) -> void: push_error("[ui_input_probe] FAIL — " + why) print("[ui_input_probe] FAIL — " + why) diff --git a/game/smoke/ui_struct_probe.gd b/game/smoke/ui_struct_probe.gd index 743775e2..370068a3 100644 --- a/game/smoke/ui_struct_probe.gd +++ b/game/smoke/ui_struct_probe.gd @@ -72,6 +72,19 @@ func _ready() -> void: _settle(6) _audit("battle_hud", [main.board_zone, main.info_panel, main.stage]) _standeefit_contract(main) + # ---- 暂停层(迭代 17 卡③):组合根真实路径开层→结构断言→关闭。附只读性契约 + #(R4——子树信号连接白名单巡检)与零动效契约(R5/P4 甲——零 AnimationPlayer/零 tween)。 + # 巡检信号名清单=子树全控件的 get_signal_list() 全集(不放行任何未列名信号——新连接 + # 一律红,须先入册审查);白名单={视图自身 _emit_closed, 组合根 _on_pause_closed}。 + main._open_pause_layer() + _settle() + await _audit("pause", [main.pause_overlay]) + _pause_readonly_contract(main) + _pause_zero_motion_contract(main) + main._on_pause_closed() + _settle() + if main.pause_overlay.visible: + problems.append("[pause] 关闭后层未隐藏(模态栈单口失守)") else: problems.append("[battle_hud] 候选中无战斗节点(驱动中断——候选=%s)" % str(main.level_screen._cand_ids)) @@ -164,6 +177,61 @@ func _pick_battle_node(main: Node) -> int: return -1 +# ⑧pause 只读性契约(迭代 17 卡③ R4):暂停层子树全控件 × 游戏语义信号连接巡检——清单外零连接。 +# 巡检信号名清单(任务书 v1.1「巡检信号名清单落码时列全进探针」)= 玩家交互可携带写意图的通道 +# 全集(按钮按下/拨杆/文本/滑值/选项/键盘焦点)+ 本层外发信号 closed;引擎内部布局生命线信号 +# 不计(child_order_changed/item_rect_changed/visibility_changed/size_flags_changed/ +# minimum/maximum_size_changed 等系 Godot 容器系统对每控件自动建立的脏标记连接,与写通路无关 +#——首跑实测踩中:全集巡检把布局生命线误判为非法连接)。清单外任何连接(尤其指向 +# 购买/解锁/写入路径的)一律红,须先入册审查再改清单。 +const _PAUSE_READONLY_SIGNALS: Array[String] = [ + "pressed", "button_down", "button_up", "toggled", + "text_changed", "text_submitted", "value_changed", "item_selected", + "gui_input", "focus_entered", "focus_exited", "closed", +] + + +func _pause_readonly_contract(main: Node) -> void: + var overlay: Control = main.pause_overlay + var stack: Array = [overlay] + var conn_count := 0 + while not stack.is_empty(): + var n: Node = stack.pop_back() + for sig_name in _PAUSE_READONLY_SIGNALS: + for conn in n.get_signal_connection_list(sig_name): + conn_count += 1 + var cb: Callable = conn["callable"] + var host: Object = cb.get_object() + var m := String(cb.get_method()) + if host == overlay and m == "_emit_closed": + continue + if host == main and m == "_on_pause_closed": + continue + problems.append("[pause][readonly] 非法信号连接 %s.%s → %s.%s(零写通路巡检——白名单=视图 _emit_closed/组合根 _on_pause_closed)" + % [n.name, sig_name, host, m]) + for ch in n.get_children(): + stack.append(ch) + if conn_count != 2: + problems.append("[pause][readonly] 信号连接数=%d 应=2(关闭钮 pressed+closed 外发——多连接=写通路嫌疑)" % conn_count) + + +# ⑨pause 零动效契约(迭代 17 卡③ R5/P4 甲——零动效直开直关路径存在断言): +# 子树零 AnimationPlayer + 视图源码零 tween/动效包装调用(源码 token 扫描=ui_contrast 静态段同手法) +func _pause_zero_motion_contract(main: Node) -> void: + var overlay: Control = main.pause_overlay + var stack: Array = [overlay] + while not stack.is_empty(): + var n: Node = stack.pop_back() + if n is AnimationPlayer: + problems.append("[pause][motion] 子树含 AnimationPlayer(R5 零动效破)") + for ch in n.get_children(): + stack.append(ch) + var src := FileAccess.get_file_as_string("res://presentation/views/pause_overlay.gd") + for token in ["create_tween", "UiKit.motion"]: + if src.contains(token): + problems.append("[pause][motion] 视图源码含「%s」(R5 零动效直开直关破——P4 甲)" % token) + + # ⑥standee 专项:B 区站桩内容矩形 ⊆ 252px 舞台矩形(内缩 8px)且不被裁切 # (UI 设计对齐批 N2 2026-10-06——名行基线离盒底 1px 同型高危写法返修回归闸; # 内容矩形=StandeeBody.content_rect(),最低绘制元素=底座下缘)