# VERTICAL SLICE - NOT FOR PRODUCTION # 棋盘渲染帧取证探针(目视验证面:机检≠看得清)。 # 必须**窗口化**运行(headless 无渲染面): # Godot_console.exe --path . res://smoke/board_shot_probe.tscn -- <输出目录> # 流程:L1/L2/L3 各起一板(真实 LevelRunner+settle_batch 驱动 ~18 步 novice——自然揭示数字/旗), # BoardView 真渲染,frame_post_draw 同步后抓视口 PNG。零存档触碰(无 save_manager 引用)。 # 护栏:headless fail-fast(shot_probe 同款);空纹理取证 fail——取证段失败=闸红, # 零字节/空纹理不允许静默落盘;board_meta.json 取样坐标落盘(棋盘视图全局矩形—— # golden 前置裁剪坐标源,禁硬编码像素)。 extends Node const AutoPlayer := preload("res://smoke/auto_player.gd") var _meta := {} # 棋盘视图实测几何(全局 rect——tools/golden_board_crop.py 取样坐标源) func _ready() -> void: # 运行前提=窗口化真渲染面(shot_probe 同款护栏——headless 下 frame_post_draw 永不触发+视口纹理恒空) if DisplayServer.get_name() == "headless": print("[board_shot] FAIL — 本探针必须窗口化运行(headless 无渲染面,截图必空);正主=tools/render_gate.sh 取证段") get_tree().quit(1) return var args := OS.get_cmdline_user_args() var out := args[0] if args.size() >= 1 and args[0] != "" else "user://board-shots" DirAccess.make_dir_recursive_absolute(out) await get_tree().process_frame var db = load("res://core/config_db.gd").new() var ok: bool = db.load_tables("res://data/tables", true) if not ok: push_error("[board_shot] 表加载失败") get_tree().quit(1) return var ui_font: Font = load("res://presentation/views/ui_kit.gd").load_font(load("res://presentation/views/ui_kit.gd").FONT_UI) var targets := [[1, "L1_dungeon"], [2, "L2_underdark"], [3, "L3_deep_underdark"]] for t in targets: var level_index: int = t[0] var tag: String = t[1] var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(420000 + level_index) var runner = load("res://core/level_runner.gd").new(db, rng) if not runner.start_level(level_index, 420000 + level_index, {}, 0): push_error("[board_shot] 开关失败 level=%d" % level_index) get_tree().quit(1) return var settlement = runner.current_battle() for i in range(18): var act: Dictionary = AutoPlayer.novice_action(settlement.minesweeper) var kind := String(act.get("kind", "stop")) if kind == "stop": break var op := kind if kind == "flag": op = "flag_place" settlement.minesweeper.settle_batch([{"action": op, "cell": act.get("cell")}]) var view = load("res://presentation/views/board_view.gd").new() view.size = Vector2(900, 900) view.position = Vector2(40, 40) if ui_font != null: view.set_world_font(ui_font) add_child(view) view.rebuild(settlement.minesweeper.current_board(), settlement.minesweeper) # 抓帧前等入场动画完结(迭代 19 卡① S2 观察项根因修复 2026-10-08):rebuild 有全板淡入 # motion(0.2)+R0 涟漪逐格延迟显形(motion(0.04×离板心距离)+motion(0.12),最远格 ≈0.56s), # 原制 6 个 process_frame 热机后 ≈0.1s=捕在涟漪中途——逐跑帧时序抖动使揭示 alpha 漂移, # golden L2/L3 跨 2% 容差线波动(实测 0.00-2.53%)。改时间制 1.2s(2×余量)=锚定动画完结态。 await get_tree().create_timer(1.2).timeout await get_tree().process_frame await RenderingServer.frame_post_draw var img := get_viewport().get_texture().get_image() if img == null or img.is_empty(): push_error("[board_shot] FAIL — 抓帧空纹理: %s(Q7 取证语义=闸红)" % tag) get_tree().quit(1) return var path := "%s/board_%s.png" % [out, tag] img.save_png(path) _meta[tag] = {"x": view.global_position.x, "y": view.global_position.y, "w": view.size.x, "h": view.size.y} print("[board_shot] saved %s (%dx%d)" % [path, img.get_width(), img.get_height()]) view.queue_free() await get_tree().process_frame var mf := FileAccess.open(out.path_join("board_meta.json"), FileAccess.WRITE) if mf != null: mf.store_string(JSON.stringify(_meta)) mf.close() print("[board_shot] done") get_tree().quit(0)