# VERTICAL SLICE - NOT FOR PRODUCTION # 全屏 UI 审计截图探针(实机每一屏 vs demo 逐屏对比;shot_probe 扩展形)。 # 必须**窗口化**运行(真渲染面;headless 守卫同 shot_probe): # Godot_console.exe --path . res://smoke/ui_audit_probe.tscn -- <输出目录> # 覆盖 = demo 八映射屏(start/camp/settings/map/battle/bonus/shop/end 胜败两态)+ demo 无映射三屏 # (legend 图例/confirm 确认框/debug 调试浮窗)。真实信号路径驱动 + 合成面开窗(ui_struct_probe 同款 # 真实 open API+拟真数据);双帧 frame_post_draw 同步抓视口纹理。 extends Node const ProbeUiUtil := preload("res://smoke/probe_ui_util.gd") const Narrator := preload("res://presentation/battle_narrator.gd") var main: Node var out_dir := "" var _problems: Array = [] func _ready() -> void: if DisplayServer.get_name() == "headless": print("[ui_audit] FAIL — 本探针必须窗口化运行(headless 无渲染面)") get_tree().quit(1) return var args := OS.get_cmdline_user_args() out_dir = args[0] if args.size() >= 1 and args[0] != "" else "user://ui-audit" DirAccess.make_dir_recursive_absolute(out_dir) await get_tree().process_frame main = load("res://presentation/main.tscn").instantiate() add_child(main) await get_tree().process_frame ProbeUiUtil.isolate_save(main, "smoke_ui_audit", true, true, false) await get_tree().process_frame # 断点种子(2026-10-06 判官 checkpoint——demo 标题屏=有档态「继续上局」主钮亮出;无种子= # 续玩钮隐藏,主操作语义对齐⑤无从呈现。最小合法断点快照;后续「开始冒险」流程自然覆盖) main.save_manager.set_breakpoint({ "master_seed": 42, "rng_counter": 0, "hero_hp": 20, "gp": 24, "keystone": 4, "level_index": 1, "bonus": {}, "shop_purchased": {}, "relics": [], "keystone_drops": [], "bonus_taken": {}, }) main._refresh_resume_button() await get_tree().process_frame # 1 标题屏(启动态=有档态) await _snap("1_start") # 2 调试浮窗(P3 仅开始菜单可开——趁 _level_active=false 先截) main._show_debug_overlay() await _settle() await _snap("9_debug") main._close_debug_overlay() await _settle() # 3 设置 main._open_settings() await _settle() await _snap("3_settings") main._on_settings_closed() await _settle() # 4 营地 var campbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "营地", "contains") if campbtn != null: campbtn.pressed.emit() await _settle() await _snap("2_camp") var close_btn: Button = ProbeUiUtil.find_button(main.camp_overlay, "关闭营地", "contains") if close_btn != null: close_btn.pressed.emit() await _settle() # 5 关卡地图(开始冒险→停点出图) # 收种子断点(有断点时「开始冒险」=清档确认互斥弹窗——不收则断流,5_battle 截不上) main.save_manager.clear_breakpoint() main._refresh_resume_button() await get_tree().process_frame var lvbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "开始冒险", "contains") if lvbtn != null: lvbtn.pressed.emit() for i in 4: await get_tree().process_frame await _settle() await _snap("4_map") # 6 战斗(真实选路提交) if main.level_screen != null and main.level_screen.visible: main.level_screen.press_candidate(0) for i in 10: await get_tree().process_frame await _settle() await _snap("5_battle") # 7 战斗结算·胜利(真实签名的拟真载荷——main.gd:697 同序) main._end_overlay.open_victory(Narrator.battle_end_text("victory", true), "返回", main.board_view.get_global_rect(), "战利品 GP +5 · 合计 24", "钥石 · 累计 27", "本关加成:攻击 +1", 5, 24) await _settle() await _snap("8_end_victory") main._end_overlay.close() await _settle() # 8 战斗结算·失败 main._end_overlay.open_defeat(Narrator.battle_end_text("defeat", false), "返回", main.board_view.get_global_rect()) await _settle() await _snap("8_end_defeat") main._end_overlay.close() await _settle() # 9 三选一(拟真三卡) main.curio_overlay.open_pick(Narrator.node_curio_header(), [ {"id": "heal_rare", "text": "回血\n恢复 3 点"}, {"id": "blast_rare", "text": "爆裂轰击\n下一击 +5"}, {"id": "pierce_rare", "text": "穿甲\n无视护甲"}, ]) await _settle() await _snap("6_bonus") main.curio_overlay.visible = false await _settle() # 10 商店(买前态——pool 货架面=玩家正主形态,demo #v-shop 对比基线;T6 全屏页) main.shop_overlay.open_shelf([ {"kind_id": "attack", "display_name": "攻击强化", "value": 1, "price": 12}, {"kind_id": "max_hp", "display_name": "生命强化", "value": 3, "price": 12}, {"kind_id": "armor", "display_name": "护甲片", "value": 1, "price": 15}, ], 27) main.shop_overlay.set_visit_info(0, 1.8) # 爬价规则行(T6——presenter 同款投喂) await _settle() await _snap("7_shop") main.shop_overlay.visible = false await _settle() # 11 图例 main.legend_overlay.open() await _settle() await _snap("10_legend") main.legend_overlay.close() await _settle() # 12 确认框(放弃二次确认实文案) main.confirm_dialog.open("放弃冒险", "确认放弃本次冒险?已入账钥匙与 GP 将结算保留") await _settle() await _snap("11_confirm") main.confirm_dialog.close() await _settle() for s in ["1_start", "9_debug", "3_settings", "2_camp", "4_map", "5_battle", "8_end_victory", "8_end_defeat", "6_bonus", "7_shop", "10_legend", "11_confirm"]: var p := out_dir.path_join("real_%s.png" % s) if not FileAccess.file_exists(p): _problems.append("截图缺失: %s" % s) else: var fa := FileAccess.open(p, FileAccess.READ) if fa == null or fa.get_length() < 1000: _problems.append("截图疑似空文件: %s" % s) print("[ui_audit] 完成 out=%s 问题=%d %s" % [out_dir, _problems.size(), str(_problems)]) get_tree().quit(1 if _problems.size() > 0 else 0) func _settle() -> void: for i in 3: await get_tree().process_frame await RenderingServer.frame_post_draw func _snap(key: String) -> void: await RenderingServer.frame_post_draw await RenderingServer.frame_post_draw var img: Image = get_viewport().get_texture().get_image() if img == null: _problems.append("视口纹理为空: %s" % key) return img.save_png(out_dir.path_join("real_%s.png" % key)) print("[ui_audit] saved %s (%dx%d)" % [key, img.get_width(), img.get_height()])