# 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): 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")}]) # ---- 卡④ 封印取证保底(卡面「每关放一枚封印后抓整板帧」):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) 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) _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) 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) # ---- 卡④ 封印格取证(法阵化):从微光关帧裁剪封印格+环径读数+光圈不越格比对 ---- # 判据:环外径像素 ÷ 格边 落 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 兑现下轮 # 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)])