refactor(波2 目录收尾): game/smoke/ 目录退役——44 件探针三件套迁 game/probe/、30 件测量/诊断/驱动件迁 game/sim/;全仓 100+ 处 res://smoke 与 game/smoke 引用改指(闸件/产品码 tune_sim_controller/宿主 preload/根规则/README/设计文档);熵增闸 RETIRED 收编 game/smoke 防回潮;AGENTS.md Project Layout 换代;static_scan 豁免理由换新宿主;波 2 施工件预计算表全部命中(8d 预扫零红灯);全量域闸验收通过零新增红

This commit is contained in:
DKChan committed 2026-10-24 02:09:27 +08:00
1 parent c5f1383766
commit 16c6f1362e
110 files changed
+139 -7305

No files matched your search

+739
View File
@@ -0,0 +1,739 @@
# VERTICAL SLICE - NOT FOR PRODUCTION
# push_input 端到端输入探针(场景结构登记项转义务 + Node2D 呈现层 Risks 缓解面)。
# 现役探针全走 BoardView.click_cell 直调(合成鼠标事件零命中已实证)——
# 本探针是真实输入路径的唯一自动化覆盖:鼠标事件 → 壳层 _gui_input → 整图公式换算(_cell_at)→
# 悬停/点选/chord 既有出口 → 组合根信号。四 case:①悬停可挖域(金框+十字光标)②悬停禁入
# (hover_forbidden 信号+灰禁红框)③悬停已开格(路径预览点迹+移动光标)④右键封印(seal_place
# 到达组合根+核心封印态)⑤双击 chord(禁入格双左击,核心拒挖零副作用)⑥左键挖(dig 到达组合根+核心揭格)
# ⑦英雄精灵世界落位(回归闸——SubViewport 世界坐标判据,素材缺位树跳过)
# ⑧影子构成与贴脚 ⑨右移朝向+到位回站帧+缩放档 ⑩变格边长即时重排 ⑪贴图缺位回退字形
#(⑧–⑪=Node2D 展示几何/状态面单元化断言)。
# 事件路径:Viewport.push_input(ev, in_local_coords=true)——落点用 BoardView.cell_center_local
# (与 _cell_at 同一公式族)换算画布坐标,SubViewportContainer mouse_filter=IGNORE 不拦截。
# 运行:Godot_console.exe --headless --path . res://probe/ui_input_probe.tscn
extends Node
const ProbeUiUtil := preload("res://probe/probe_ui_util.gd") # T6 收口:隔离样板单点
var main: Node
var _problems: Array = []
var _actions: Array = [] # action_requested 收集([cell, kind])
var _chords: Array = [] # chord_attempted 收集
var _forbids: Array = [] # hover_forbidden 收集
func _ready() -> void:
await get_tree().process_frame # autoload 就绪
main = load("res://presentation/main.tscn").instantiate()
add_child(main)
await get_tree().process_frame
# 存档隔离(断点写盘面随冒险挂 main.save_manager;探针禁碰真实档 user://save.json,
# 同 ui_click_probe 隔离纪律。首次冒险布尔位置位=对齐真实档游玩史口径,模板抽签路径不变;
# isolate_save 先删后建——上次运行的残留断点会触发互斥确认早退)
ProbeUiUtil.isolate_save(main, "smoke_input_probe_save", true, false, true)
main.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,探针确定性禁依赖默认分支)
main._start_adventure_from_menu()
await get_tree().process_frame
# ---- ⑫ 图上命中真实输入(回归闸):入口停点候选节点中心
# push_input 真实鼠标路径 motion+click → 悬停预承诺置位 → route_chosen 到达组合根。
# 由来=真机 bug:深层节点(L1 Boss 内容 x=802)在固定画布命中区外,press_candidate 直调
# 命中函数探针全绿、真鼠标点不中——本 case 封住输入管线,⑬封住几何拟合。
if main.level_screen.visible:
var ro = main.level_screen
var cands: Array = main.run_scope.runner.next_candidate_ids()
# ---- ⑱ 信息迷雾呈现接线(回归闸):真机 display_probe 读实际绘制记录(雾下节点类型未画、
# Boss 描边画了、候选类型画了)+点击雾下非候选节点零提交(雾不扩大可点域——候选集即合法集)----
var disp: Dictionary = ro.display_probe()
var vis: Array = main.run_scope.runner.type_visible_ids()
var cur_id: int = main.run_scope.runner.current_node_id()
var fog_id := -1
var boss_id := -1
var nds_vis: Array = main.run_scope.runner.nodes()
for n in nds_vis:
var vnid: int = main.run_scope.runner.node_id_of(n)
if String(n.get("type", "")) == "boss":
boss_id = vnid
elif fog_id < 0 and vnid != cur_id and not cands.has(vnid) and not vis.has(vnid):
# 选区只按核心可见集判(不读绘制记录——选区与断言解耦,防「按记录选、再断言记录」自指)
fog_id = vnid
if boss_id < 0 or fog_id < 0 or not disp.has(fog_id) or not disp.has(boss_id) \
or not disp.has(int(cands[0])):
_pre_fail("case⑱ 雾面节点选取失败或绘制记录缺位(boss=%d fog=%d 记录数=%d)"
% [boss_id, fog_id, disp.size()])
return
if bool(disp[fog_id]["draw_icon"]) or bool(disp[fog_id]["draw_label"]):
_pre_fail("case⑱ 雾下节点类型仍出示(图标=%s 文字=%s——范围外只见结构)〔fv2〕"
% [str(disp[fog_id]["draw_icon"]), str(disp[fog_id]["draw_label"])])
return
if not bool(disp[boss_id]["boss_border"]):
_pre_fail("case⑱ Boss 节点未带描边标记(id=%d)〔fv3〕" % boss_id)
return
if not bool(disp[int(cands[0])]["draw_icon"]):
_pre_fail("case⑱ 候选节点类型未出示(id=%d——下一跳恒可见)〔fv4〕" % int(cands[0]))
return
var fog_node := {}
for n in nds_vis:
if main.run_scope.runner.node_id_of(n) == fog_id:
fog_node = n
var fog_pos: Vector2 = ro.canvas_probe()["origin"] + ro.layout_probe(nds_vis)[fog_node]
# 零提交判据直接绑 route_chosen 发射:终态会被雾下节点类型(奖励/恢复/商店保持本屏、
# 战斗/Boss 出屏)与运行器候选校验兜底,单看终态测不出命中面穿透——信号面才是提交面
var submitted: Array = []
var on_route := func(nid: int) -> void: submitted.append(nid)
ro.route_chosen.connect(on_route)
var evf := InputEventMouseButton.new()
evf.button_index = MOUSE_BUTTON_LEFT
evf.pressed = true
evf.position = fog_pos
evf.global_position = fog_pos
main.get_viewport().push_input(evf, true)
await get_tree().process_frame
await get_tree().process_frame
ro.route_chosen.disconnect(on_route)
if not submitted.is_empty() or not main.level_screen.visible or not main.run_scope.runner.awaiting_choice():
_pre_fail("case⑱ 雾下节点点击产生提交(提交=%s visible=%s 选路态=%s——雾不扩大可点域)〔fv9〕"
% [str(submitted), str(main.level_screen.visible), str(main.run_scope.runner.awaiting_choice())])
return
var nds: Array = main.run_scope.runner.nodes()
var tgt := {}
for n in nds:
if main.run_scope.runner.node_id_of(n) == int(cands[0]):
tgt = n
var npos: Vector2 = ro.layout_probe(nds)[tgt]
var gpos: Vector2 = ro.canvas_probe()["origin"] + npos
var evm := InputEventMouseMotion.new()
evm.position = gpos
evm.global_position = gpos
main.get_viewport().push_input(evm, true)
await get_tree().process_frame
if int(ro._hover_id) != int(cands[0]):
_pre_fail("图上悬停预承诺未置位(hover=%s 候选=%s)" % [str(ro._hover_id), str(cands[0])])
var evc := InputEventMouseButton.new()
evc.button_index = MOUSE_BUTTON_LEFT
evc.pressed = true
evc.position = gpos
evc.global_position = gpos
main.get_viewport().push_input(evc, true)
await get_tree().process_frame
await get_tree().process_frame
if main.level_screen.visible or main.run_scope.runner.awaiting_choice():
_pre_fail("图上真实点击未提交选路(overlay 可见=%s 选路态=%s)" % [str(main.level_screen.visible), str(main.run_scope.runner.awaiting_choice())])
for i in range(3):
await get_tree().process_frame
# ---- ⑬ 画布随图拟合(同 bug 的几何面回归):复刻分层公式独立推导最深节点内容 x,
# 断言画布 min 尺寸覆盖之(open_choices 每 stop 拟合——L1 单链 Boss x=70+6*122=802 曾越界)
var ro2 = main.level_screen
var nds2: Array = main.run_scope.runner.nodes()
var layer2 := {}
var inbound2 := {}
for n2 in nds2:
for tid2 in main.run_scope.runner.node_out_ids(n2):
inbound2[tid2] = true
var queue2: Array = []
for n2 in nds2:
if not inbound2.has(main.run_scope.runner.node_id_of(n2)):
queue2.append(n2)
layer2[n2] = 0
while not queue2.is_empty():
var cur2: Dictionary = queue2.pop_front()
for tid2 in main.run_scope.runner.node_out_ids(cur2):
var tg2 := {}
for n2 in nds2:
if main.run_scope.runner.node_id_of(n2) == tid2:
tg2 = n2
if tg2.is_empty() or layer2.has(tg2):
continue
layer2[tg2] = int(layer2[cur2]) + 1
queue2.append(tg2)
var maxl2 := 0
for k2 in layer2.keys():
maxl2 = maxi(maxl2, int(layer2[k2]))
# 常数锚(画布拟合探针):直接引屏内导出常量(LAYER_BASE_X/LAYER_STEP_X/NODE_HALF)——
# 与 _content_size 公式(LAYER_BASE_X + maxl*LAYER_STEP_X + NODE_HALF.x + 8)逐项同源,禁再手抄数字
var need_x2: float = LevelMapScreen.LAYER_BASE_X + maxl2 * LevelMapScreen.LAYER_STEP_X \
+ LevelMapScreen.NODE_HALF.x + 8.0
var cvmin: Dictionary = ro2.canvas_probe()
if float(cvmin["min_size"].x) < need_x2 - 0.5:
_pre_fail("画布拟合不足(min.x=%.0f < 最深内容 %.0f——深层节点不可点回归)" % [cvmin["min_size"].x, need_x2])
var bv = main.board_view
var ms = main.run_scope.settlement.minesweeper if main.run_scope.settlement != null else null
if ms == null or bv.cells_probe().is_empty():
_pre_fail("战斗未就绪(cells=%d settlement=%s)" % [bv.cells_probe().size(), str(main.run_scope.settlement)])
return
bv.action_requested.connect(func(c: Vector2i, kind: String): _actions.append([c, kind]))
bv.chord_attempted.connect(func(c: Vector2i): _chords.append(c))
bv.hover_forbidden.connect(func(c: Vector2i): _forbids.append(c))
# ---- 先行移动(真实点击路径):R₀ 展开后主角邻域全已开(_diggable 空)——点击前沿已开格
# 迫近未开边界(与 ui_click_probe 行为同构:先 move 后 dig)----
var hero0: Vector2i = ms.hero_pos()
var mv := Vector2i(-9, -9)
for c in bv._board.neighbors8(hero0):
if ms.is_revealed(c) and _has_hidden_neighbor(bv, ms, c):
mv = c
break
if mv.x == -9:
mv = _pick_frontier(bv, ms)
if mv.x == -9:
_pre_fail("前沿已开格选取失败(板面无可迫近边界)")
return
_push_click(_pos(bv, mv), MOUSE_BUTTON_LEFT)
await get_tree().process_frame
await get_tree().process_frame
if not _has_action(mv, "move") or ms.hero_pos() == hero0:
_pre_fail("先导移动未到达(actions=%s hero=%s→%s)" % [str(_actions), hero0, ms.hero_pos()])
return
# ---- 目标格实证选取(不写死坐标;两枚相邻可挖格 + 禁入格 + 已开格)----
# 首战板面种子随全局序漂移——_diggable(主角 8 邻未开域)
# 不足两枚时,沿已开区真点击移位到「≥2 未开邻」的前沿格再选;移位失败即收手交由选取失败分支报状态
for _expand in range(16):
if bv.diggable_probe().size() >= 2:
break
var mv_t := Vector2i(-9, -9)
for y in bv._board.height:
for x in bv._board.width:
var c := Vector2i(x, y)
if not ms.is_revealed(c):
continue
var hidden := 0
for n in bv._board.neighbors8(c):
if not ms.is_revealed(n):
hidden += 1
if hidden >= 2:
mv_t = c
break
if mv_t.x != -9:
break
if mv_t.x == -9:
break
var hero_before: Vector2i = ms.hero_pos()
_push_click(_pos(bv, mv_t), MOUSE_BUTTON_LEFT)
await get_tree().process_frame
await get_tree().process_frame
if ms.hero_pos() == hero_before:
break
var seal_t := Vector2i(-9, -9)
var dig_t := Vector2i(-9, -9)
for c in bv.diggable_probe():
if not ms.has_seal(c):
if seal_t.x == -9:
seal_t = c
elif dig_t.x == -9:
dig_t = c
break
var forbid_t := _pick_forbidden(bv, ms)
var reveal_t := _pick_revealed(ms)
if seal_t.x == -9 or dig_t.x == -9 or forbid_t.x == -9 or reveal_t.x == -9:
_pre_fail("目标格选取失败 seal=%s dig=%s forbid=%s reveal=%s" % [seal_t, dig_t, forbid_t, reveal_t])
return
# ---- case① 悬停可挖域:motion → 悬停态到达(金框 + 十字光标)----
_push_motion(_pos(bv, seal_t))
await get_tree().process_frame
if bv.hover_probe() != seal_t:
_problems.append("case① 悬停未到达(_hover=%s 应=%s)" % [bv.hover_probe(), seal_t])
var u1 = bv.cells_probe()[seal_t]
if not u1.hl_visible or u1.hl_color != UiKit.GOLD:
_problems.append("case① 悬停金框缺失(visible=%s color=%s)" % [str(u1.hl_visible), str(u1.hl_color)])
if bv.mouse_default_cursor_shape != Input.CURSOR_CROSS:
_problems.append("case① 挖掘光标未切十字(%d)" % bv.mouse_default_cursor_shape)
# ---- case② 悬停禁入:hover_forbidden 信号 + 灰禁红框 ----
_push_motion(_pos(bv, forbid_t))
await get_tree().process_frame
if _forbids.is_empty() or _forbids[_forbids.size() - 1] != forbid_t:
_problems.append("case② hover_forbidden 未达(%s)" % [str(_forbids)])
if bv.cells_probe()[forbid_t].hl_color != UiKit.RED_FORBID:
_problems.append("case② 禁入红框缺失(color=%s)" % str(bv.cells_probe()[forbid_t].hl_color))
# ---- case③ 悬停已开格:路径预览点迹 + 移动光标 ----
_push_motion(_pos(bv, reveal_t))
await get_tree().process_frame
var dots := 0
for c in bv.cells_probe():
if bv.cells_probe()[c].dot_visible:
dots += 1
if dots < 1:
_problems.append("case③ 路径预览点迹未亮(dots=0)")
if bv.mouse_default_cursor_shape != Input.CURSOR_ARROW:
_problems.append("case③ 移动光标缺失(%d)" % bv.mouse_default_cursor_shape)
# ---- case④ 右键封印:seal_place 到达组合根 + 核心封印态落位 ----
_push_click(_pos(bv, seal_t), MOUSE_BUTTON_RIGHT)
await get_tree().process_frame
if not _has_action(seal_t, "seal_place"):
_problems.append("case④ seal_place 未达组合根(actions=%s)" % [str(_actions)])
await get_tree().process_frame
if not ms.has_seal(seal_t):
_problems.append("case④ 核心封印态未落位")
# ---- case④b 封印纹理槽回归断言(P0-1):paint 尾部若无条件
# `u.tex_seal = null` 会覆盖条件赋值=素材在位时封印不可见(headless 缺盘走字形回退
# 掩盖)——有盘断言纹理在槽、缺盘断言字形回退,两分支防双写踩踏复活 ----
var seal_unit = bv.cells_probe().get(seal_t)
if bv._seal_tex != null:
if seal_unit == null or seal_unit.tex_seal != bv._seal_tex:
_problems.append("case④b 封印纹理槽未挂封印印记纹理(tex_seal=%s)" % ["无单元" if seal_unit == null else str(seal_unit.tex_seal)])
elif seal_unit == null or seal_unit.glyph != "封":
_problems.append("case④b 缺盘字形回退缺失(glyph=%s)" % ["无单元" if seal_unit == null else String(seal_unit.glyph)])
# ---- case⑤ 双击 chord:禁入格双左击(核心拒挖零副作用)→ chord_attempted ----
_push_click(_pos(bv, forbid_t), MOUSE_BUTTON_LEFT)
_push_click(_pos(bv, forbid_t), MOUSE_BUTTON_LEFT)
await get_tree().process_frame
if _chords.is_empty() or _chords[_chords.size() - 1] != forbid_t:
_problems.append("case⑤ chord_attempted 未达(chords=%s)" % [str(_chords)])
# ---- case⑥ 左键挖:dig 到达组合根 + 核心揭格落位 ----
_push_click(_pos(bv, dig_t), MOUSE_BUTTON_LEFT)
await get_tree().process_frame
if not _has_action(dig_t, "dig"):
_problems.append("case⑥ dig 未达组合根(actions=%s)" % [str(_actions)])
await get_tree().process_frame
if not ms.is_revealed(dig_t):
_problems.append("case⑥ 核心揭格未落位(目标=%s)" % dig_t)
# ---- case⑦ 英雄精灵世界落位(回归闸):精灵在 SubViewport 世界内,
# 落位 = 格×步距 + 脚底锚(横=格中线/纵=格底往上 25% 线=HERO_GROUND_FRAC 自底边口径——「脚踩
# 格子 1/4 处·从下往上数」;禁带壳层 _svp_container.position 居中偏移——上批误用壳层
# cell_origin 实测右偏 ~7 格+偏离格心,实机截图用户报缺陷)。素材缺位树(hero==null,字形
# 「人」回退在役)跳过;断言前等 0.3s 墙钟——headless 帧率无上限,帧数等待 < 位移 Tween
# 140ms 时会采到半途位置(首跑实证)。 ----
if bv._world.hero != null:
await get_tree().create_timer(0.3).timeout
var expect: Vector2 = Vector2(ms.hero_pos()) * (bv.cell_size + float(bv.SEP)) \
+ Vector2(bv.cell_size * 0.5, bv.cell_size * (1.0 - bv.HERO_GROUND_FRAC))
if bv._world.hero.position.distance_to(expect) > 0.5:
_problems.append("case⑦ 英雄精灵世界落位偏移 pos=%s expect=%s" % [str(bv._world.hero.position), str(expect)])
# ---- case⑧ 影子构成与贴脚(回归闸):椭圆影=英雄子件、中心贴脚锚
#((0,2) 下坠脱脚=用户报缺陷)、z 相对回落格面之上英雄之下、黑色半透明、渐变纹理组件
# 合成;父子 use_parent_material 双挂(结算期随世界去饱和与格面同口径)。 ----
if bv._world.hero != null:
var kids: Array = bv._world.hero.get_children()
if kids.size() != 1 or not (kids[0] is Sprite2D):
_problems.append("case⑧ 影子子件缺失或类型不对(children=%d)" % kids.size())
else:
var sh: Sprite2D = kids[0]
if sh.position != Vector2.ZERO:
_problems.append("case⑧ 影子未贴脚(pos=%s 应=ZERO)" % str(sh.position))
if sh.z_index != -1:
_problems.append("case⑧ 影子 z 相对档不对(%d 应=-1)" % sh.z_index)
if absf(sh.modulate.a - 0.35) > 0.01:
_problems.append("case⑧ 影子透明度漂移(%s 应=0.35)" % str(sh.modulate.a))
if not (sh.texture is GradientTexture2D):
_problems.append("case⑧ 影子纹理非径向渐变组件(%s)" % str(sh.texture))
if not sh.use_parent_material or not bv._world.hero.use_parent_material:
_problems.append("case⑧ 去饱和材质链漏挂(shadow=%s hero=%s)" % [str(sh.use_parent_material), str(bv._world.hero.use_parent_material)])
# ---- case⑨ 右移朝向+到位回站帧+缩放档(回归闸):素材基准朝左——向右移动 flip_h=true;
# 到位(0.3s 墙钟 > 位移 Tween 140ms)纹理回站帧、scale 恒 1.5 均匀档。无可达右邻已开格跳过。 ----
if bv._world.hero != null:
var hp9: Vector2i = ms.hero_pos()
var mv9 := Vector2i(-9, -9)
for n in bv._board.neighbors8(hp9):
if n.x == hp9.x + 1 and ms.is_revealed(n):
mv9 = n
break
if mv9.x == -9:
print("[ui_input_probe] SKIP case⑨(无可达右邻已开格)")
else:
_push_click(_pos(bv, mv9), MOUSE_BUTTON_LEFT)
await get_tree().create_timer(0.3).timeout
var sp9: Sprite2D = bv._world.hero
if not sp9.flip_h:
_problems.append("case⑨ 右移后未翻转朝向(flip_h=false,素材基准朝左)")
if sp9.texture != bv._hero_tex_idle:
_problems.append("case⑨ 到位未回站帧(texture=%s)" % str(sp9.texture))
if sp9.scale != Vector2.ONE * bv.HERO_SPRITE_SCALE:
_problems.append("case⑨ 缩放档漂移(%s 应=%s 均匀)" % [str(sp9.scale), str(bv.HERO_SPRITE_SCALE)])
# ---- case⑩ 变格边长即时重排(窗口缩放路径回归闸):set_cell_area 换小格 → 英雄即时落新脚锚、
# 缩放均匀不残弹跳形;还原 → 46px 档复验。 ----
if bv._world.hero != null:
var hp10: Vector2i = ms.hero_pos()
bv.set_cell_area(Vector2(300, 300))
var expect10: Vector2 = Vector2(hp10) * (bv.cell_size + float(bv.SEP)) \
+ Vector2(bv.cell_size * 0.5, bv.cell_size * (1.0 - bv.HERO_GROUND_FRAC))
if bv._world.hero.position.distance_to(expect10) > 0.5 or bv._world.hero.scale != Vector2.ONE * bv.HERO_SPRITE_SCALE:
_problems.append("case⑩ 缩格后英雄未即时重排(pos=%s expect=%s scale=%s)" % [str(bv._world.hero.position), str(expect10), str(bv._world.hero.scale)])
bv.set_cell_area(bv.size)
var expect10b: Vector2 = Vector2(hp10) * (bv.cell_size + float(bv.SEP)) \
+ Vector2(bv.cell_size * 0.5, bv.cell_size * (1.0 - bv.HERO_GROUND_FRAC))
if bv._world.hero.position.distance_to(expect10b) > 0.5:
_problems.append("case⑩ 还原后英雄未复位(pos=%s expect=%s)" % [str(bv._world.hero.position), str(expect10b)])
# ---- case⑪ 贴图缺位回退(字形「人」回归闸):texture 置 null + refresh → 英雄格数字 0 时字形
# 必须回退「人」(数字格仍显数字——回退判据与抑制判据同源);验毕还原纹理重绘。 ----
if bv._world.hero != null:
var hp11: Vector2i = ms.hero_pos()
var u11 = bv.cells_probe()[hp11]
var tex_saved: Texture2D = bv._hero_tex_idle
bv._hero_tex_idle = null
bv.refresh()
var digit11: int = int(ms.reveal_record(hp11).get("digit", 0))
if digit11 == 0:
if u11.glyph != "人":
_problems.append("case⑪ 贴图缺位未回退字形「人」(glyph=%s)" % u11.glyph)
elif u11.glyph != str(digit11):
_problems.append("case⑪ 缺位态数字格字形被破坏(glyph=%s 应=%d)" % [u11.glyph, digit11])
bv._hero_tex_idle = tex_saved
bv.refresh()
# ---- case⑭ 变异角标(flat_damage 未开格视觉标记;resolver 注入面+落位+复位) ----
var mark_t: Vector2i = _pick_forbidden(bv, ms) # 任一未开格(禁入=未开非相邻)
if mark_t.x >= 0:
bv.set_variant_mark_resolver(func(_c): return "flat_damage")
bv.refresh()
if String(bv.cells_probe()[mark_t].mutation_mark) != "flat_damage":
_problems.append("case⑭ 变异角标未落位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].mutation_mark))
bv.set_variant_mark_resolver(Callable())
bv.refresh()
if String(bv.cells_probe()[mark_t].mutation_mark) != "":
_problems.append("case⑭ 角标未随 resolver 复位(mutation_mark=%s)" % str(bv.cells_probe()[mark_t].mutation_mark))
else:
_problems.append("case⑭ 无未开格可验(板面异常)")
# ---- case⑮ 视野迷雾(三态呈态/半径圈/移动跟随/开关两态零扰动/提灯插值位;
# 纯呈现零逻辑——迷雾开关切换前后 minesweeper 状态摘要逐位一致) ----
var fogp: Dictionary = bv.fog_params_probe()
if int(bv.FOG_RADIUS) != 8 or int(fogp["lantern"]) != 0 or not fogp["enabled"]:
_problems.append("case⑮ 迷雾参数漂移(R=%s lantern=%s enabled=%s 应=8/0/true——旧提灯常量随行退役摘除)"
% [str(bv.FOG_RADIUS), str(fogp["lantern"]), str(fogp["enabled"])])
else:
var hero15: Vector2i = ms.hero_pos()
var b15 = ms.current_board()
var fog_ok := true
var cells_f1: Dictionary = bv.cells_probe()
for y in b15.height:
for x in b15.width:
var c15 := Vector2i(x, y)
var d15: int = absi(c15.x - hero15.x) + absi(c15.y - hero15.y)
var expect15: int = 0
if d15 > bv.FOG_RADIUS:
expect15 = 2 if ms.is_revealed(c15) else 1
var got15: int = int(cells_f1[c15].fog_state)
if got15 != expect15:
fog_ok = false
_problems.append("case⑮ 迷雾呈态不符(%s dist=%d got=%d expect=%d)" % [str(c15), d15, got15, expect15])
break
if not fog_ok:
break
# 半径边界格恰在圈内(曼哈顿距离恰 =R 的格 fog_state=0——存在性校验,12×12 板必有)
var has_boundary := false
var cells_f2: Dictionary = bv.cells_probe()
for y in b15.height:
for x in b15.width:
if absi(x - hero15.x) + absi(y - hero15.y) == int(bv.FOG_RADIUS):
has_boundary = true
if int(cells_f2[Vector2i(x, y)].fog_state) != 0:
_problems.append("case⑮ 边界圈内格被误遮(dist==R 应 fog_state=0)")
break
if has_boundary:
break
if not has_boundary:
_problems.append("case⑮ 板面无 dist==R 格(板面异常)")
# 移动跟随:把英雄挪到板角(move 动作逐格逼近成本高——直接白-box 校验公式换心后远格态翻转:
# 取一个对当前英雄 dist>R 的已开格(记忆态),以远角为新心重算应翻转/保持一致性由公式承载,
# 此处只验 set_fog_enabled(false) 全板归零与 true 恢复——开关两态契约主断言)
var digest_before: Array = []
for y in b15.height:
for x in b15.width:
var c16 := Vector2i(x, y)
digest_before.append(ms.is_revealed(c16))
bv.set_fog_enabled(false)
var all_clear := true
var cells_f3: Dictionary = bv.cells_probe()
for y in b15.height:
for x in b15.width:
if int(cells_f3[Vector2i(x, y)].fog_state) != 0:
all_clear = false
if not all_clear:
_problems.append("case⑮ 迷雾关未全板归零(off 态应 fog_state=0)")
var digest_off: Array = []
for y in b15.height:
for x in b15.width:
var c17 := Vector2i(x, y)
digest_off.append(ms.is_revealed(c17))
if digest_off != digest_before:
_problems.append("case⑮ 迷雾开关改写逻辑态(两态结算逐位一致 🔴 契约破)")
bv.set_fog_enabled(true)
if int(bv.cells_probe()[Vector2i(0, 0)].fog_state) == 0 \
and absi(0 - hero15.x) + absi(0 - hero15.y) > int(bv.FOG_RADIUS):
_problems.append("case⑮ 迷雾复开后远角未复原遮蔽(fog_state=0)")
# 视野加成接线断言(主语=阔视野带:参数注入 set_fog_glow_bonus(4) → 半径 8+4:
# dist==R+1 格由雾态转可见,卸装复原 bonus=0;纯接线零逻辑——半径算式归迷雾断言面)
var ring_cell := Vector2i(-99, -99)
for y in b15.height:
for x in b15.width:
if absi(x - hero15.x) + absi(y - hero15.y) == int(bv.FOG_RADIUS) + 1:
ring_cell = Vector2i(x, y)
break
if ring_cell.x != -99:
break
if ring_cell.x == -99:
_problems.append("case⑮ 板面无 dist==R+1 格(视野加成断言取数异常)")
else:
var pre_lantern: int = int(bv.cells_probe()[ring_cell].fog_state)
bv.set_fog_glow_bonus(4)
if int(bv.fog_params_probe()["lantern"]) != 4:
_problems.append("case⑮ 视野加成接雾参数未变(lantern 应=4)")
if int(bv.cells_probe()[ring_cell].fog_state) != 0:
_problems.append("case⑮ 视野 +4 后 dist==R+1 格仍被遮(应转可见 fog_state=0,改前=%d)" % pre_lantern)
bv.set_fog_glow_bonus(0)
if int(bv.fog_params_probe()["lantern"]) != 0:
_problems.append("case⑮ 视野加成清零后雾参数未复原(lantern 应=0)")
if int(bv.cells_probe()[ring_cell].fog_state) == 0:
_problems.append("case⑮ 视野加成清零后 dist==R+1 格未复原遮蔽")
# 装配链级断言(wide_scout_band 装配链贯通:①装配→开局参数面
# 〔build_adventure_params 统一构造〕②装配集→呈现桥〔_sync_treasure_glow 消费
# attune_param_sum 只读出口→set_fog_glow_bonus〕→视野 +4 生效与卸装复原。
# settlement.attunement 手动注入=模拟 begin_battle enc 注入后的运行态〔注入面本体归
# 冒烟+单测覆盖〕;不重启冒险——case⑯ 起既有板面继续用)
main.save_manager.set_attunement_equipped(["wide_scout_band"])
var p_lantern: Dictionary = main.build_adventure_params(main.save_manager, main.meta)
if not (p_lantern.get("attunement", []) as Array).has("wide_scout_band"):
_problems.append("case⑮ 装配未进开局参数面(attunement=%s 应含 wide_scout_band)" % str(p_lantern.get("attunement", [])))
main.run_scope.settlement.attunement = ["wide_scout_band"]
main._sync_treasure_glow()
if int(bv.fog_params_probe()["lantern"]) != 4:
_problems.append("case⑮ 装配后视野未扩(lantern 应=4——呈现桥未生效)")
if int(bv.cells_probe()[ring_cell].fog_state) != 0:
_problems.append("case⑮ 装配后 dist==R+1 格仍被遮(应转可见)")
main.run_scope.settlement.attunement = []
main._sync_treasure_glow()
if int(bv.fog_params_probe()["lantern"]) != 0:
_problems.append("case⑮ 卸装后视野未复原(lantern 应=0——契约复原语义显式重算)")
if int(bv.cells_probe()[ring_cell].fog_state) == 0:
_problems.append("case⑮ 卸装后 dist==R+1 格未复原遮蔽")
main.save_manager.set_attunement_equipped([])
# ---- case⑯ 状态面板输入锁语义(R3——开层锁棋盘、关层恢复无残留;真实 push_input
# 路径:入口角标本体 press+release 真点击 → 状态面板开;锁期挖掘点击零穿透;「继续」关层后
# 同格挖掘重新到达——锁语义与既有模态同款,无状态残留。置于全 case 之尾:取格若需前沿移位
#(真实点击 move 扩大可挖域),其板面扰动不再波及任何既有断言——域恰 2 枚时 seal_t 带封印+
# dig_t 揭开后域内零无封印格,正是首跑实测踩中的形态)----
var status_dig := Vector2i(-9, -9)
for c in bv.diggable_probe():
if not ms.has_seal(c):
status_dig = c
break
if status_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_seal(c):
status_dig = c
break
if status_dig.x == -9:
_problems.append("case⑯ 无可挖无封印格(板面异常——锁断言中断)")
else:
var pbtn16: Button = main.board_zone.status_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.status_panel.visible or not main._input_locked():
_problems.append("case⑯ 角标真点击未开状态面板(visible=%s locked=%s——入口可达性失守)"
% [str(main.status_panel.visible), str(main._input_locked())])
else:
_push_click(_pos(bv, status_dig), MOUSE_BUTTON_LEFT)
await get_tree().process_frame
await get_tree().process_frame
if _has_action(status_dig, "dig") or ms.is_revealed(status_dig):
_problems.append("case⑯ 状态面板开启时挖掘穿透(R3 输入锁破——目标=%s)" % status_dig)
var resume_btn: Button = ProbeUiUtil.find_button(main.status_panel, "继续", "prefix")
if resume_btn == null:
_problems.append("case⑯ 状态面板关闭钮缺失")
else:
resume_btn.pressed.emit()
await get_tree().process_frame
if main.status_panel.visible or main._input_locked():
_problems.append("case⑯ 关闭后未恢复(visible=%s locked=%s——R3 残留)"
% [str(main.status_panel.visible), str(main._input_locked())])
_push_click(_pos(bv, status_dig), MOUSE_BUTTON_LEFT)
await get_tree().process_frame
await get_tree().process_frame
if not _has_action(status_dig, "dig") or not ms.is_revealed(status_dig):
_problems.append("case⑯ 关闭后挖掘未到达(恢复无残留验证失守)")
# ---- case⑰ 未开格类型微光(活板面走查:作用集恒等/开格灭/移动复亮
# 与出圈灭/相位门控/开关两态零扰动。与冒烟段数据面断言分工:此处走真实点击路径) ----
if not bool(bv.type_glow_probe().get("enabled", false)):
_problems.append("case⑰ 微光默认态非开(生产默认开——Q4 丙案)")
if not (bv.type_glow_probe().get("errors", []) as Array).is_empty():
_problems.append("case⑰ 活板清单外登记非空(errors=%s——生产数据应全集合法)" % str(bv.type_glow_probe().get("errors", [])))
if bv.diggable_probe().is_empty():
var mv17a := _pick_frontier(bv, ms)
if mv17a.x == -9:
_problems.append("case⑰ 板面无 diggable 且无前沿可迫近(活板面前提异常)")
else:
_push_click(_pos(bv, mv17a), MOUSE_BUTTON_LEFT)
await get_tree().process_frame
await get_tree().process_frame
var digp17: Dictionary = bv.diggable_probe()
if digp17.is_empty():
_problems.append("case⑰ 板面仍无 diggable(活板面前提异常)")
else:
# ① 作用集恒等:cells 键集 == diggable 键集(生产身份回调面——未开格恒可查,F4)
var keys17: Array = (bv.type_glow_probe().get("cells", {}) as Dictionary).keys()
var eq17 := keys17.size() == digp17.size()
if eq17:
for k in keys17:
if not digp17.has(k):
eq17 = false
break
if not eq17:
_problems.append("case⑰ 作用集与可挖域不等(cells=%d diggable=%d)" % [keys17.size(), digp17.size()])
# ② 开格灭:真实点击挖开一格 → 该格退出作用集(S3 生命周期)
var dig17 := Vector2i(-9, -9)
for c in digp17:
if not ms.has_seal(c):
dig17 = c
break
if dig17.x == -9:
_problems.append("case⑰ 无无封印可挖格(板面异常)")
else:
if not (bv.type_glow_probe().get("cells", {}) as Dictionary).has(dig17):
_problems.append("case⑰ 待挖格不在作用集(%s)" % str(dig17))
_push_click(_pos(bv, dig17), MOUSE_BUTTON_LEFT)
await get_tree().process_frame
await get_tree().process_frame
if not ms.is_revealed(dig17):
_problems.append("case⑰ 真实点击未挖开目标格(%s)" % str(dig17))
if (bv.type_glow_probe().get("cells", {}) as Dictionary).has(dig17):
_problems.append("case⑰ 开格未灭(%s 仍在作用集——S3 生命周期破)" % str(dig17))
# ③ 移动复亮/出圈灭:点击前沿已开格移英雄 → 作用集随新 diggable 重算(恒等面承载两者)
var mv17 := _pick_frontier(bv, ms)
if mv17.x == -9:
_problems.append("case⑰ 无前沿可迫近(移动复亮断言取格中断)")
else:
_push_click(_pos(bv, mv17), MOUSE_BUTTON_LEFT)
await get_tree().process_frame
await get_tree().process_frame
var glowp2: Dictionary = bv.type_glow_probe()
var digp18: Dictionary = bv.diggable_probe()
var keys18: Array = (glowp2.get("cells", {}) as Dictionary).keys()
var eq18 := keys18.size() == digp18.size()
if eq18:
for k in keys18:
if not digp18.has(k):
eq18 = false
break
if not eq18:
_problems.append("case⑰ 移动后作用集与新可挖域不等(cells=%d diggable=%d——复亮/出圈灭破)" % [keys18.size(), digp18.size()])
# ④ 相位门控:结算相位(_dig_glow off)作用集空、微光开关读数不变;回挖掘相位逐位复位
var before_phase: Dictionary = (bv.type_glow_probe().get("cells", {}) as Dictionary).duplicate()
bv.set_dig_glow_enabled(false)
var glowp4: Dictionary = bv.type_glow_probe()
if not (glowp4.get("cells", {}) as Dictionary).is_empty():
_problems.append("case⑰ 结算相位未灭(作用集 %d 格——相位门控破)" % (glowp4.get("cells", {}) as Dictionary).size())
if not bool(glowp4.get("enabled", false)):
_problems.append("case⑰ 相位门控误改微光开关态(enabled 应保持 true)")
bv.set_dig_glow_enabled(true)
var glowp5: Dictionary = bv.type_glow_probe()
var after_phase: Dictionary = glowp5.get("cells", {})
var eq_phase := after_phase.size() == before_phase.size()
if eq_phase:
for k in before_phase:
if int(after_phase.get(k, -1)) != int(before_phase[k]):
eq_phase = false
break
if not eq_phase:
_problems.append("case⑰ 回挖掘相位作用集未逐位复位(%d→%d)" % [before_phase.size(), after_phase.size()])
# ⑤ 开关两态零扰动(case⑮ 同款升级到全板 revealed 摘要):off 前后 ms 逻辑态逐位一致
var b17 = ms.current_board()
var digest_before17: Array = []
for y in b17.height:
for x in b17.width:
digest_before17.append(ms.is_revealed(Vector2i(x, y)))
bv.set_type_glow_enabled(false)
var digest_off17: Array = []
for y in b17.height:
for x in b17.width:
digest_off17.append(ms.is_revealed(Vector2i(x, y)))
if digest_off17 != digest_before17:
_problems.append("case⑰ 微光开关改写逻辑态(两态结算逐位一致 🔴 契约破)")
bv.set_type_glow_enabled(true)
if not bool(bv.type_glow_probe().get("enabled", false)):
_problems.append("case⑰ 微光开关未恢复开态")
if _problems.is_empty():
print("[ui_input_probe] PASS — 悬停金框/禁入信号/路径预览/seal_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卡③增,⑰未开格类型微光 2026-10-17 迭代30卡①增,⑱信息迷雾绘制接线(display_probe 读实际绘制记录)增)")
get_tree().quit(0)
else:
for p in _problems:
push_error("[ui_input_probe] FAIL — " + p)
print("[ui_input_probe] FAIL — " + "; ".join(_problems))
get_tree().quit(1)
func _has_action(cell: Vector2i, kind: String) -> bool:
for a in _actions:
if a[0] == cell and a[1] == kind:
return true
return false
func _pick_forbidden(bv, ms) -> Vector2i:
var b = ms.current_board()
var digp: Dictionary = bv.diggable_probe()
for y in b.height:
for x in b.width:
var c := Vector2i(x, y)
if not ms.is_revealed(c) and not digp.has(c):
return c
return Vector2i(-9, -9)
func _pick_revealed(ms) -> Vector2i:
var b = ms.current_board()
var hero: Vector2i = ms.hero_pos()
for y in b.height:
for x in b.width:
var c := Vector2i(x, y)
if ms.is_revealed(c) and c != hero:
return c
return Vector2i(-9, -9)
# 前沿已开格兜底选取(主角邻域无前沿时全板扫描)
func _pick_frontier(bv, ms) -> Vector2i:
var b = ms.current_board()
for y in b.height:
for x in b.width:
var c := Vector2i(x, y)
if ms.is_revealed(c) and _has_hidden_neighbor(bv, ms, c):
return c
return Vector2i(-9, -9)
func _has_hidden_neighbor(bv, ms, c: Vector2i) -> bool:
for n in bv._board.neighbors8(c):
if not ms.is_revealed(n):
return true
return false
# 合成事件落点 = BoardView 画布原点 + 格中心(cell_center_local 与 _cell_at 同一公式族)
func _pos(bv, c: Vector2i) -> Vector2:
return bv.global_position + bv.cell_center_local(c)
func _push_motion(pos: Vector2) -> void:
var ev := InputEventMouseMotion.new()
ev.position = pos
ev.global_position = pos
main.get_viewport().push_input(ev, true)
func _push_click(pos: Vector2, btn: int) -> void:
var ev := InputEventMouseButton.new()
ev.position = pos
ev.global_position = pos
ev.button_index = btn
ev.pressed = true
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)
get_tree().quit(1)