标识符面:flag_*→seal_ 族(含 Godot 信号 flag_judged→seal_judged 与连接/回调同名、 FACTS 两键 flags_count→seals_count/dug_under_flag→dug_under_seal 全链、 .tres 键名与 schema .gd 同批同步);显式豁免零改动=size_flags_*(Godot 布局属性)+_fatal_flag。 中文面:金币→GP(金币袋→GP 袋)/插旗→封印/雷格→危险格/惩罚格→危险格/旗→封印·封印印记族, game/ 内 *.gd/.tres/.md/.json 四类禁用词与 flag 双双零命中(V1/V4)。 守卫字面量不动(被检查的禁字不是使用):smoke_all V21 零「雷/旗」检查面、 ui_font_asset_test 字形集攻/雷/旗/封 保持原断言逻辑。 代拍板:板面单字槽 glyph「旗」→「封」(禁字纪律+单字位宽,消费面四处同名随动)。 素材换代(2026-10-18 用户裁案甲):assets/art/board/flag_seal.png→seal_mark.png(素材不入 git, 盘上改名+导入重刷),代码两处 res:// 字符串+COL_FLAG/CURSOR_FLAG 常量同步, 闸件四处随批(run_all_gates.sh/render_gate.sh/golden_baseline_refresh.sh/path_check.py)。 anchor.json 键名手改,重锚与改前改后 diff 随本卡收口(同裁案甲)。 基线读数(改前,主区 ed2dfaf6):冒烟 1151 检查 0 失败/单元 34 套全绿/探针 --click 存量红仅 screen_flow_probe(user:// 断点污染在册已知项)。 改后同批读数(本 worktree):冒烟 1151 检查 0 失败/探针 --click 同一存量红、其余全绿。
187 lines
7.8 KiB
GDScript
187 lines
7.8 KiB
GDScript
# screen_flow_probe.gd — L3 屏壳切换探针(迭代 29 T4)
|
||
# 判的是「跑起来之后,屏与场景的切换对不对」:菜单 ⇄ 营地 ⇄ 冒险 ⇄ 终局 的主流程与回退路径,
|
||
# 每一步断言 **屏级互斥**(同一时刻最多一个屏可见)+ 流程状态位落到正确值。
|
||
# 与 ui_click_probe 的分工:那件走的是「点某个按钮有没有反应」的行为走查(含战斗推进与结算数值面);
|
||
# 本件只走屏壳与流程状态,不推进战斗、不碰结算数值——屏级互斥是最容易被装配顺序改动悄悄破坏
|
||
# 又最难被单测覆盖的面(全库 change_scene 零命中,屏切换全靠 visible 互斥)。
|
||
# 运行:Godot_console.exe --headless --path . res://smoke/screen_flow_probe.tscn
|
||
extends Node
|
||
|
||
const ProbeUiUtil := preload("res://smoke/probe_ui_util.gd")
|
||
|
||
var main: Node
|
||
var _problems: Array[String] = []
|
||
|
||
|
||
func _ready() -> void:
|
||
await get_tree().process_frame
|
||
main = load("res://presentation/main.tscn").instantiate()
|
||
add_child(main)
|
||
await get_tree().process_frame
|
||
await get_tree().process_frame
|
||
|
||
# 隔离存档(全 worktree 共享同一份 user://save.json——探针必须换注,禁碰真实档)
|
||
ProbeUiUtil.isolate_save(main, "smoke_screen_flow", false, false, false, false)
|
||
await get_tree().process_frame
|
||
|
||
await _step_boot_menu()
|
||
await _step_menu_to_camp()
|
||
await _step_camp_back_to_menu()
|
||
await _step_menu_to_adventure()
|
||
await _step_level_to_battle_and_back()
|
||
await _step_abandon_to_end()
|
||
await _step_end_back_to_menu()
|
||
|
||
if _problems.is_empty():
|
||
print("[screen_flow_probe] PASS — 六步主流程+回退路径全绿(屏级互斥:任意时刻 ≤1 屏可见)")
|
||
get_tree().quit(0)
|
||
return
|
||
print("[screen_flow_probe] FAIL — %d 处问题:" % _problems.size())
|
||
for p in _problems:
|
||
print("[screen_flow_probe] - " + p)
|
||
get_tree().quit(1)
|
||
|
||
|
||
# ---- 屏级互斥判据 ----
|
||
# 屏标记取法:菜单/营地/终局看各自主内容层的 visible;冒险取流程状态位 run_scope.is_adventure
|
||
#(战斗容器 battle_root 在局外也 visible——裸看 visible 会把 boot 态误判成「菜单与冒险同时在场」)。
|
||
# **合法叠加只有一对**:终局覆盖层盖在冒险内容之上(终局态 is_adventure 尚未收束,是设计如此)。
|
||
# 其余任意两屏同时为真=互斥失守。
|
||
func _screen_seals() -> Dictionary:
|
||
return {
|
||
"菜单": main._start_overlay != null and main._start_overlay.visible,
|
||
"营地": main.camp_overlay != null and main.camp_overlay.visible,
|
||
"冒险": main.run_scope != null and main.run_scope.is_adventure,
|
||
"终局": main._end_overlay != null and main._end_overlay.visible,
|
||
}
|
||
|
||
|
||
func _seals_text(f: Dictionary) -> String:
|
||
var on: Array[String] = []
|
||
for k in ["菜单", "营地", "冒险", "终局"]:
|
||
if bool(f[k]):
|
||
on.append(k)
|
||
return "/".join(on) if not on.is_empty() else "无"
|
||
|
||
|
||
# ---- 流程状态位(与可见性是两回事,逐步单独判)----
|
||
func _expect_adventure_seal(step: String, expect: bool) -> void:
|
||
var got: bool = main.run_scope != null and main.run_scope.is_adventure
|
||
if got != expect:
|
||
_problems.append("%s:流程状态位 is_adventure 应为 %s,实测 %s" % [step, str(expect), str(got)])
|
||
|
||
|
||
func _expect_exclusive(step: String, expect: String) -> void:
|
||
var f: Dictionary = _screen_seals()
|
||
var pairs := [["菜单", "营地"], ["菜单", "冒险"], ["营地", "冒险"], ["终局", "营地"], ["终局", "菜单"]]
|
||
for pair in pairs:
|
||
var a: String = pair[0]
|
||
var b: String = pair[1]
|
||
if bool(f[a]) and bool(f[b]):
|
||
_problems.append("%s:屏级互斥失守(%s 与 %s 同时在场)" % [step, a, b])
|
||
if not bool(f[expect]):
|
||
_problems.append("%s:期望屏「%s」不在场(实测 %s)" % [step, expect, _seals_text(f)])
|
||
return
|
||
for k in ["菜单", "营地", "冒险", "终局"]:
|
||
if k == expect:
|
||
continue
|
||
if k == "冒险" and expect == "终局":
|
||
continue # 合法叠加:终局覆盖在冒险内容之上
|
||
if bool(f[k]):
|
||
_problems.append("%s:屏「%s」不该在场(实测 %s)" % [step, k, _seals_text(f)])
|
||
|
||
|
||
func _frames(n: int = 2) -> void:
|
||
for _i in n:
|
||
await get_tree().process_frame
|
||
|
||
|
||
# ---- ① boot:菜单独占 ----
|
||
func _step_boot_menu() -> void:
|
||
_expect_exclusive("①boot", "菜单")
|
||
_expect_adventure_seal("①boot", false)
|
||
if main.debug_overlay != null and main.debug_overlay.visible:
|
||
_problems.append("①boot:调试浮窗 boot 自动开(菜单优先失守)")
|
||
|
||
|
||
# ---- ② 菜单 → 营地(真实按钮信号路径)----
|
||
func _step_menu_to_camp() -> void:
|
||
var campbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "营地", "prefix")
|
||
if campbtn == null:
|
||
_problems.append("②菜单→营地:营地按钮缺失")
|
||
return
|
||
campbtn.pressed.emit()
|
||
await _frames()
|
||
_expect_exclusive("②菜单→营地", "营地")
|
||
|
||
|
||
# ---- ③ 营地 → 菜单(回退路径:营地关闭发自覆盖层自身信号)----
|
||
func _step_camp_back_to_menu() -> void:
|
||
if main.camp_overlay == null:
|
||
_problems.append("③营地→菜单:营地覆盖层句柄缺失")
|
||
return
|
||
main.camp_overlay.closed.emit()
|
||
await _frames()
|
||
_expect_exclusive("③营地→菜单(回退)", "菜单")
|
||
|
||
|
||
# ---- ④ 菜单 → 冒险(开新冒险 → 入口停点落关卡地图屏)----
|
||
func _step_menu_to_adventure() -> void:
|
||
var lvbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "开始冒险", "contains")
|
||
if lvbtn == null:
|
||
lvbtn = ProbeUiUtil.find_button(main._start_overlay, "冒险", "contains")
|
||
if lvbtn == null:
|
||
_problems.append("④菜单→冒险:开始冒险按钮缺失")
|
||
return
|
||
lvbtn.pressed.emit()
|
||
await _frames(3)
|
||
_expect_exclusive("④菜单→冒险", "冒险")
|
||
_expect_adventure_seal("④菜单→冒险", true)
|
||
if main.run_scope != null and main.run_scope.adventure == null:
|
||
_problems.append("④菜单→冒险:冒险实例未建立(入口停点未达)")
|
||
if main.level_screen != null and not main.level_screen.visible:
|
||
_problems.append("④菜单→冒险:入口停点未落在关卡地图屏(level_screen 不可见)")
|
||
|
||
|
||
# ---- ⑤ 冒险内屏切换:关卡地图屏 ⇄ 战斗屏(屏内两态,屏级仍独占)----
|
||
func _step_level_to_battle_and_back() -> void:
|
||
if not main.level_screen.visible:
|
||
_problems.append("⑤冒险内切换:前置不在关卡地图屏")
|
||
return
|
||
main._exit_level_screen()
|
||
await _frames()
|
||
if main.level_screen.visible:
|
||
_problems.append("⑤冒险内切换:退出关卡屏后 level_screen 仍可见")
|
||
if main._battle_root != null and not main._battle_root.visible:
|
||
_problems.append("⑤冒险内切换:战斗屏未接手(battle_root 不可见)")
|
||
_expect_exclusive("⑤冒险内切换(关卡→战斗)", "冒险")
|
||
main._enter_level_screen()
|
||
await _frames()
|
||
if not main.level_screen.visible:
|
||
_problems.append("⑤冒险内切换:回到关卡地图屏失败")
|
||
_expect_exclusive("⑤冒险内切换(战斗→关卡)", "冒险")
|
||
|
||
|
||
# ---- ⑥ 放弃 → 终局(放弃路径=回退路径的非正常出口)----
|
||
func _step_abandon_to_end() -> void:
|
||
if main.run_scope == null or main.run_scope.adventure == null:
|
||
_problems.append("⑥放弃→终局:前置无冒险实例")
|
||
return
|
||
main._on_abandon_requested()
|
||
await _frames(4)
|
||
_expect_exclusive("⑥放弃→终局", "终局")
|
||
if main._end_overlay != null and not main._end_overlay.visible:
|
||
_problems.append("⑥放弃→终局:终局覆盖层未开")
|
||
|
||
|
||
# ---- ⑦ 终局 → 菜单(终局出口返回开始界面)----
|
||
func _step_end_back_to_menu() -> void:
|
||
if main._end_overlay == null or not main._end_overlay.visible:
|
||
_problems.append("⑦终局→菜单:前置不在终局")
|
||
return
|
||
main._end_overlay.continue_pressed.emit()
|
||
await _frames(3)
|
||
_expect_exclusive("⑦终局→菜单", "菜单")
|
||
if main.run_scope != null and main.run_scope.is_adventure:
|
||
_problems.append("⑦终局→菜单:冒险态未收束(run_scope 仍标 is_adventure)")
|