# VERTICAL SLICE - NOT FOR PRODUCTION # 棋盘渲染帧取证探针(目视验证面:机检≠看得清)。 # 必须**窗口化**运行(headless 无渲染面): # Godot_console.exe --path . res://probe/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 前置裁剪坐标源,禁硬编码像素)。 # 双帧取证:每关抓 微光开(生产默认态·Q4 丙案)/关(golden 基线保护面) # 两帧——① 开帧逐像素判定「非生效格零变化」(禁整格染色/禁越圈;生效格内只准落在内缘 # 带 = cell÷8)② 每个生效格须有实绘像素(微光不空转)③ 关帧即 golden 对比帧(原名落盘, # 基线不重制——契约 1 off 态基线)。隔离纪律 = 与 type_glow_shot_probe 同款: # 前置 UiKit.motion_scale=0.5 冻结接触带呼吸相位(静态带色),两帧差异唯一定位在类型微光。 extends Node const AutoPlayer := preload("res://sim/auto_player.gd") const ActionEconomy := preload("res://core/action_economy.gd") var _meta := {} # 棋盘视图实测几何(全局 rect——tools/golden_board_crop.py 取样坐标源) var _problems: Array = [] var _level_readings := {} # 关 → 双帧比对读数(实现报告读数表源) var _cur_settlement = null # 当关 settlement(身份回调读取面——仿生产 run_scope.settlement 现值; # 不用 Callable.bind 传参:bind 的 Callable 动态派发时对类型化参数做 Object 硬校验, # 报「Cannot convert argument 2 from Object to Vector2i」——直接方法引用+成员现值无此坑) 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 # 双帧单一变量隔离:冻结接触带呼吸相位——motion_scale<1.0 ⇒ _apply_contact_bands # 施静态色(减动效停格路径)。必须在 rebuild 前置位(rebuild/set_dig_glow_enabled 按当时相位 # 重施带色)。golden 侧影响:静态带色=呼吸周期中值,与基线捕获相的像素差最坏 ≈灰度24 于 # 接触带像素(占比 <0.4%),≤2% 判据不受影响;停格同时令「非生效格零变化」可精确判定。 UiKit.motion_scale = 0.5 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() _cur_settlement = settlement 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 == "seal": op = "seal_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) # 身份解析接线(生产同源——main.gd:251 set_identity_resolver(_identity_lookup)):必须在 # rebuild / set_dig_glow_enabled 前接好,二者尾链 _recompute_glow 同步读身份建作用集。 # 裸视图不接 = _identity_cb 无效 ⇒ info 恒 null ⇒ 作用集恒空(三关 glow=0 根因—— # 双帧比对空转、PASS 假绿)。现值经 _cur_settlement 成员读入 # (仿生产 run_scope.settlement;bind 传参有 Callable 类型校验坑,见成员注)。 view.set_identity_resolver(_probe_identity_lookup) view.rebuild(settlement.minesweeper.current_board(), settlement.minesweeper) # 进挖掘相位(生产态 = hud_presenter.set_dig_glow_enabled(not settling) 接线——裸视图默认 # _dig_glow=false,接触带与类型微光同源门控都不渲染;golden 基线即捕获于 # 该裸态。双帧取证要求生产默认态,故显式开——两帧均含静态接触带(motion_scale=0.5 停格), # 差异唯一定位在类型微光开关) view.set_dig_glow_enabled(true) # 抓帧前等入场动画完结(S2 观察项根因):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 on_img := get_viewport().get_texture().get_image() if on_img == null or on_img.is_empty(): push_error("[board_shot] FAIL — 抓帧空纹理: %s(Q7 取证语义=闸红)" % tag) get_tree().quit(1) return var on_path := "%s/board_%s_glow_on.png" % [out, tag] on_img.save_png(on_path) # 作用集快照必须在关开关之前取(set_type_glow_enabled(false) 会清空 _glow 重算) var glow_cells: Dictionary = (view.type_glow_probe().get("cells", {}) as Dictionary).duplicate() # 帧 B = 微光关(golden 基线保护面——与基线同文件名落盘,golden 对比取本帧) view.set_type_glow_enabled(false) await get_tree().process_frame await RenderingServer.frame_post_draw var off_img := get_viewport().get_texture().get_image() if off_img == null or off_img.is_empty(): push_error("[board_shot] FAIL — 抓帧空纹理: %s_off(Q7 取证语义=闸红)" % tag) get_tree().quit(1) return var path := "%s/board_%s.png" % [out, tag] off_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) + %s" % [path, off_img.get_width(), off_img.get_height(), on_path]) _check_dual_frames(tag, view, glow_cells, off_img, on_img) 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() var rf := FileAccess.open(out.path_join("board_dual_frame_readings.json"), FileAccess.WRITE) if rf != null: rf.store_string(JSON.stringify(_level_readings, " ")) rf.close() if _problems.is_empty(): print("[board_shot] PASS — 三关双帧取证:非生效格零变化/生效格内缘带外零侵入/生效格实绘非空;golden 帧=微光关态(契约 1 基线保护面)") get_tree().quit(0) else: for p in _problems: push_error("[board_shot] FAIL — " + p) print("[board_shot] FAIL — " + "; ".join(_problems)) get_tree().quit(1) # 身份解析回调(生产同源面——main.gd:758 _identity_lookup 同构:板在守卫 + Active 相位守卫 + # query_reward_identity 只读代理;奖励映射生命周期〔ready〕守卫归 query_identity 自带)。 # 相位为何是 Active:每轮 settle_batch 末尾 _exit_distribution 已 notify_round_start 兑现下轮 # AP ⇒ 18 步 novice 驱动后相位回 Active,即生产「挖掘期看板」态;本回调零玩法数据写入 # (🔴 契约 1——只读投影面)。 func _probe_identity_lookup(c: Vector2i): if _cur_settlement == null or _cur_settlement.minesweeper.current_board() == null: return null if String(_cur_settlement.action_economy.phase) != ActionEconomy.PHASE_ACTIVE: return null return _cur_settlement.query_reward_identity(c) # 双帧逐像素比对:所有变化像素必须落在生效格的内缘带内 # (de < cell÷8——带外含砖心零侵入=禁整格染色/禁越圈);每个生效格须有实绘像素(不空转)。 func _check_dual_frames(tag: String, view, glow_cells: Dictionary, off_img: Image, on_img: Image) -> void: var board = view.board() if board == null: _problems.append("%s 双帧比对取板失败(view.board() 为 null)" % tag) return var w: int = off_img.get_width() var h: int = off_img.get_height() var cell: float = view.cell_size var band_px: float = cell * 0.125 # 逐格窗口矩形 + 到格缘距离判据(与 type_glow_shot_probe 同口径) var rects := {} for gy in range(board.height): for gx in range(board.width): var c := Vector2i(gx, gy) rects[c] = view.cell_rect_probe(c) var changed_total := 0 var changed_outside := 0 var first_outside := "" var glow_hits := {} for c in glow_cells: glow_hits[c] = 0 for gy in range(board.height): for gx in range(board.width): var c2 := Vector2i(gx, gy) var r: Rect2 = rects[c2] if r.size.x <= 0.0: continue var is_glow: bool = glow_cells.has(c2) var x0: int = int(floor(r.position.x)) var y0: int = int(floor(r.position.y)) var x1: int = int(ceil(r.position.x + r.size.x)) var y1: int = int(ceil(r.position.y + r.size.y)) for yy in range(maxi(0, y0), mini(h, y1)): for xx in range(maxi(0, x0), mini(w, x1)): var de: int = mini(mini(xx - x0, x1 - 1 - xx), mini(yy - y0, y1 - 1 - yy)) var p1: Color = off_img.get_pixel(xx, yy) var p2: Color = on_img.get_pixel(xx, yy) if p1.r == p2.r and p1.g == p2.g and p1.b == p2.b and p1.a == p2.a: continue changed_total += 1 if is_glow: if float(de) < band_px: glow_hits[c2] = int(glow_hits[c2]) + 1 else: changed_outside += 1 if first_outside == "": first_outside = "%s 带外(de=%d≥%.0f)" % [str(c2), de, band_px] else: changed_outside += 1 if first_outside == "": first_outside = "%s 非生效格" % str(c2) _level_readings[tag] = {"glow_cells": glow_cells.size(), "changed_total": changed_total, "changed_outside": changed_outside, "band_px": band_px} print("[board_shot] %s 双帧读数 glow=%d changed=%d outside=%d band=%.0fpx" % [tag, glow_cells.size(), changed_total, changed_outside, band_px]) if changed_outside != 0: _problems.append("%s 非生效格/带外像素变化 %d 个(首例 %s——禁整格染色/禁越圈)" % [tag, changed_outside, first_outside]) if glow_cells.size() > 0 and changed_total == 0: _problems.append("%s 微光开帧整板零变化(微光未生效)" % tag) for c in glow_cells: if int(glow_hits[c]) == 0: _problems.append("%s 生效格 %s 零实绘像素(微光空转)" % [tag, str(c)])