Files
gplan-2/game/smoke/ui_probe.gd
T
DKChan 3dffbdea23 test(smoke): T4 探针独立性断言——独立屏激活期战斗屏宿主隐藏/战斗节点提交后恢复
ui_probe 入口停点段:+独立性断言二支(level_screen.visible 且 _battle_root.hidden=零透出契约;press_candidate 进首战后 _battle_root 恢复+level_screen 不可见=屏级互斥恢复);断言消息 route_overlay 残留清零+注释措辞随独立屏。ui_click_probe 入口段:+独立性断言(屏激活期宿主隐藏)。
轻闸=冒烟 557/0+五探针全 PASS 零脚本错误。
2026-09-28 06:45:04 +08:00

229 lines
14 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VERTICAL SLICE - NOT FOR PRODUCTION
# 实现 GDD:design/ux/battle.md(场景级 headless 探针——四区布局实例化断言;entry 17②③)。
# Date: 2026-09-12(战斗屏改造批扩展;基础 = 2026-09-11 首战棋盘探针——既有断言全保留)
# 项目运行模式(autoload 在位)实例化 main.tscn,直接驱动 _start_adventure_from_menu()(完整冒险——唯一试玩入口),
# 断言真实构建(棋盘/主角站桩/敌方站桩/AP 点阵/明细带/数据框;A 区进度条断言随 停点前移批 2026-09-23 退役;
# E 区道具面板已删除;2026-09-20 全站桩拍板——英雄卡/敌人卡断言随站桩改造批换装)。
# 运行:Godot_console.exe --headless --path . res://smoke/ui_probe.tscn
extends Node
func _ready() -> void:
await get_tree().process_frame # autoload 就绪
var main = load("res://presentation/main.tscn").instantiate()
add_child(main)
await get_tree().process_frame
var problems: Array = []
# ---- 放弃入口(2026-09-21 用户拍板——台账 119):默认隐藏(非冒险模式无冒险可放弃——开始层/调试两层)----
if main.level_screen.abandon_btn == null or main.level_screen.abandon_btn.visible:
print("[ui_probe] FAIL — 放弃按钮默认应隐藏(非冒险模式关)")
get_tree().quit(1)
return
# ---- 营地覆盖层(元进度实现批——主菜单入口/开合/余额文案/购买禁用态;
# 隔离存档换注 main.meta 保确定性(禁碰真实档 user://save.json——SaveManager 路径注入纪律)----
var iso = load("res://core/save_manager.gd").new("user://smoke_ui_probe_camp.json")
iso.load_from_disk()
main.meta = load("res://core/meta_progression.gd").new(main.db, iso)
var campbtn: Button = _find_button_by_prefix(main._start_overlay, "营地")
if campbtn == null:
problems.append("营地入口按钮缺失(开始层 campbtn)")
else:
campbtn.pressed.emit() # 合成信号路径(真实按钮 → 组合根 _open_camp)
await get_tree().process_frame
if not main.camp_overlay.visible:
problems.append("营地层未打开(campbtn 路径)")
elif main._start_overlay.visible:
problems.append("营地层打开时开始层未隐藏(规范 2.3 遮挡纪律)")
else:
if not String(main.camp_overlay._balance_label.text).begins_with("钥石"):
problems.append("营地余额文案缺失(%s)" % main.camp_overlay._balance_label.text)
if main.camp_overlay._forge_rows.size() != 3:
problems.append("锻造柜台行数错误(%d ≠ 3)" % main.camp_overlay._forge_rows.size())
else:
var zero_disabled := true
for card in main.camp_overlay._forge_rows:
zero_disabled = zero_disabled and bool(card.btn.disabled) # 零余额全禁用
if not zero_disabled:
problems.append("零余额下锻造购买按钮未全禁用(余额不足禁用态失守)")
if main.camp_overlay._workshop_rows.size() != 6:
problems.append("工坊柜台行数错误(%d ≠ 6)" % main.camp_overlay._workshop_rows.size())
main._on_camp_closed()
await get_tree().process_frame
if main.camp_overlay.visible or not main._start_overlay.visible:
problems.append("营地关闭后未恢复开始层(开合往返失守)")
# 换回全新零档(营地段购买态不泄入后续冒险段——确定性;先删后建防陈旧档泄入)
for p in ["user://smoke_ui_probe_camp.json", "user://smoke_ui_probe_camp.json.tmp"]:
if FileAccess.file_exists(p):
DirAccess.remove_absolute(p)
var iso_zero = load("res://core/save_manager.gd").new("user://smoke_ui_probe_camp.json")
iso_zero.load_from_disk()
main.meta = load("res://core/meta_progression.gd").new(main.db, iso_zero)
main._start_adventure_from_menu()
await get_tree().process_frame
await get_tree().process_frame
# ---- 入口选路停点(停点前移批 2026-09-23:关卡开始即出节点图——level_started 后、首战前弹选路层;
# press_candidate 图上命中路径 pick 入口单候选进首战——候选按钮退役 2026-09-28)----
# ---- 独立性断言(形态升格批 2026-09-28:独立屏激活期战斗屏宿主整体隐藏,零透出)----
if not main.level_screen.visible:
problems.append("关卡开始未弹入口选路停点(level_screen 应可见)")
elif main._battle_root.visible:
problems.append("独立屏激活期战斗屏宿主未隐藏(零透出契约失守)")
elif main.runner == null or not main.runner.awaiting_choice() \
or main.runner.next_candidate_ids().size() != 1:
problems.append("入口停点状态失守(选路态 + 入口单候选)")
else:
main.level_screen.press_candidate(0)
await get_tree().process_frame
await get_tree().process_frame
if not main._battle_root.visible or main.level_screen.visible:
problems.append("选路提交战斗节点后未切回战斗屏(屏级互斥恢复失守)")
# ---- 放弃入口·冒险模式可见 + 战斗中按下被拒(真实按钮路径——入口挂选路停点,战斗中拒绝为防御分支)----
if main.level_screen.abandon_btn == null or not main.level_screen.abandon_btn.visible:
problems.append("冒险模式放弃按钮未开放(选路停点入口在案)")
else:
main.level_screen.abandon_btn.pressed.emit()
await get_tree().process_frame
if main.adventure == null or String(main.adventure.state) != "running":
problems.append("首战中放弃未被拒(战斗域单事务在飞——state 应保持 running)")
# ---- 既有断言(回归基线;A 区进度条断言随 停点前移批 2026-09-23 退役——节点链进度由关卡画面独立屏承载)----
if main.board_view._cells.is_empty():
problems.append("棋盘格未生成(cells=0)")
if main.board_view.board() == null:
problems.append("board 未装载")
if main.info_panel.attack_text().contains("—") or main.info_panel.armor_text().contains("—"):
problems.append("C 区攻击/护甲未显示(%s | %s)" % [main.info_panel.attack_text(), main.info_panel.armor_text()])
if main.settlement == null or main.settlement.battle_result != "":
problems.append("首战未处于进行中")
if not main.log_view.parsed_text().contains("节点 1/"):
problems.append("节点头日志缺失")
# ---- 四区布局断言(battle.md §Layout)----
if not main.stage.round_label.text.contains("第 1 轮"):
problems.append("B 区轮次·阶段指示缺失(%s)" % main.stage.round_label.text)
if main.stage.enemy_box.get_child_count() < 1:
problems.append("B 区敌方站桩未构建(%d 个)" % main.stage.enemy_box.get_child_count())
if main.ap_bar._dots.get_child_count() != int(main.db.action_economy.ap_per_round):
problems.append("D 区 AP 点阵数量错误(%d)" % main.ap_bar._dots.get_child_count())
if main.ap_bar.refresh_remaining_text() == "":
problems.append("D 区弃图刷新按钮文案缺失")
if main.board_zone.toast == null:
problems.append("D 区 toast 槽缺失")
if main.info_panel.item_slots == null or main.info_panel.item_slots.get_child_count() != 4:
problems.append("C 区魔法道具槽(预留)缺失")
if main.stage.hero_standee.slot_row == null:
problems.append("B 区站桩状态行缺失(状态效果唯一展示面——2026-09-26 C 区精简批)")
if main.log_view.mode() != "dig" or main.log_view.custom_minimum_size.y != 24.0:
problems.append("B 区明细带挖掘期应收起 24px(mode=%s h=%s)" % [main.log_view.mode(), str(main.log_view.custom_minimum_size.y)])
# E 区道具面板已随可选类删除下线(格面资源批 2026-09-15)——四区构建由其余断言覆盖
# ---- 敌方站桩投影(顺位徽标/意图三档动作动画占位——敌人系统×4;2026-09-20 全站桩拍板)----
# B 区站桩 Node2D 迁移批 T3(2026-09-26)断言等价改写(台账 171 改判记录):enemy_box 子件
# 伴随件化(壳层 tooltip 载体),穿透明(子件 _name_label/_armor_icon/_intent_badge)改本体
# 探针出口 stage.enemy_body_at(i).name_line_probe()/intent_probe()——呈现口径逐字保持
var standee = main.stage.enemy_body_at(0)
if standee == null:
problems.append("敌立绘本体缺失(enemy_body_at(0) 无世界站桩)")
else:
var name_line: Dictionary = standee.name_line_probe()
if String(name_line.name) == "" or String(name_line.armor_icon) == "":
problems.append("敌立绘名字行缺失(名字或护甲类型图标占位为空——2026-09-26 终裁:AC 不展示、类型图标化)")
# 三档动作动画占位契约(表现层升级批拍板:不显示技能名——图标档位 + 一次性动画)
standee.set_pending("damage", "")
var intent_on: Dictionary = standee.intent_probe()
if not bool(intent_on.visible) or String(intent_on.text) != "⚔":
problems.append("攻击系意图未呈现(visible=%s text=%s)" % [str(intent_on.visible), str(intent_on.text)])
standee.set_pending("basic", "")
if bool(standee.intent_probe().visible):
problems.append("普攻应零表示(意图指示应隐藏)")
if main.stage.hero_standee == null or not main.stage.hero_standee.has_world_hp_bar:
problems.append("B 区主角站桩缺失(世界血条面缺失)")
# ---- BUG-0001 回归(tooltip 入树后 set_effects 槽位不错位——结算软锁修复沉淀)----
var slots_row = main.stage.hero_standee.slot_row # BUG-0001 回归随组件迁入站桩(tooltip/槽位错位同源)
var probe_eff: Array = [{"effect_id": "attack_up", "display_name": "增伤", "layers": 1,
"remaining_settlements": 1, "source_class": "safe_cell", "round_index": 1, "effective_value": 50}]
slots_row.set_effects(probe_eff)
if slots_row._slots.size() != 1:
problems.append("BUG-0001 回归:set_effects 未建槽(%d)" % slots_row._slots.size())
slots_row._on_slot_hover(slots_row._slots[0]) # 悬停 → tooltip _tip 入子节点表(野外崩溃前置态)
slots_row.set_effects(probe_eff) # 修复前:_tip 被按位序当作槽 0 → _fill_slot 崩溃 → 结算协程中断软锁
if not (slots_row._tip in slots_row.get_children()):
problems.append("BUG-0001 回归:悬停后 tooltip 未入树(回归前置失真)")
elif slots_row._slots.size() != 1 or slots_row._slots[0] == slots_row._tip:
problems.append("BUG-0001 回归:tooltip 入树后槽引用错位")
else:
var icon_rect: TextureRect = slots_row._slots[0].get_child(0).get_child(0)
if icon_rect.texture == null:
problems.append("BUG-0001 回归:悬停后重刷槽未填充(texture=null)")
slots_row.set_effects([probe_eff[0], probe_eff[0], probe_eff[0], probe_eff[0], probe_eff[0], probe_eff[0]])
if slots_row._slots.size() != 4:
problems.append("BUG-0001 回归:超上限效果未截断至 4 槽(%d)" % slots_row._slots.size())
slots_row.set_effects([])
if not slots_row._slots.is_empty():
problems.append("BUG-0001 回归:空态未清槽(%d)" % slots_row._slots.size())
# ---- 图标键回归(HUD框架 图标键契约:状态表九行全映射〔定稿 PNG 纹理,2026-09-24 状态图标接入批〕、
# 格面资源 cell_type 九型全映射〔gp 键 = 九型批 2026-09-20〕;查不到的键 fail-fast + ? 占位
# ——此处钉死映射不残缺,未知键断言由 _icon_tex/_badge_glyph 承担)----
for eid in ["attack_up", "ac_up", "alert", "weaken", "enrage", "off_guard", "sapped", "empower", "fortify"]:
if not StatusSlotRow.EFFECT_ICON.has(eid) or StatusSlotRow.EFFECT_ICON[eid] == null:
problems.append("状态图标九键纹理映射缺失(%s)" % eid)
for cat in ["attack", "defense", "heal", "blast", "weaken", "pierce", "magic", "action", "gp"]:
if not BoardView.REWARD_BADGE.has(cat):
problems.append("奖励图标表缺键(%s)" % cat)
var probe_kb: Array = [
{"effect_id": "enrage", "display_name": "暴走", "layers": 1, "remaining_settlements": 1, "source_class": "flag_miss", "round_index": 1, "effective_value": 25},
{"effect_id": "off_guard", "display_name": "失衡", "layers": 1, "remaining_settlements": 1, "source_class": "flag_miss", "round_index": 1, "effective_value": 25},
]
slots_row.set_effects(probe_kb)
if slots_row._slots.size() != 2:
problems.append("图标键回归:enrage/off_guard 未建槽(%d)" % slots_row._slots.size())
else:
var ic_enrage: TextureRect = slots_row._slots[0].get_child(0).get_child(0)
var ic_off: TextureRect = slots_row._slots[1].get_child(0).get_child(0)
if ic_enrage.texture == null:
problems.append("暴走图标键未映射(texture=null)")
if ic_off.texture == null:
problems.append("失衡图标键未映射(texture=null)")
slots_row.set_effects([])
# ---- L2 单场段(debug_session 单场原语 + 去饱和接口可用性;雾格机制已整体移除,不再断言变体呈现)----
# 2026-09-13 单场用户入口已删——探针直调 debug_session 原语(battle_created/ready 仍经组合根接线;
# _reset_run_scope 复现原 _start_single 的提示去重复位)
main._reset_run_scope()
main._debug.start_single(2)
await get_tree().process_frame
await get_tree().process_frame
if main.board_view.board() == null:
problems.append("L2 单场未启动(board=null)")
else:
# 地形变异 首批类型定稿批(2026-09-19):L2 板允许带变异(岩浆/烬覆)——布置域断言 =
# 变异格全在初始展开区外(closed_only 结构保障)
var b0 = main.board_view.board()
var r0p := {}
for c in b0.compute_r0():
r0p[c] = true
for c in b0.variants.keys():
if r0p.has(c):
problems.append("L2 板变异格落在初始展开区(地形变异 布置域违例)")
break
main.board_view.set_desaturated(true)
main.board_view.set_desaturated(false)
if problems.is_empty():
print("[ui_probe] PASS — cells=%d board=%dx%d hp='%s' enemies=%d ap_dots=%d" % [
main.board_view._cells.size(), main.board_view.board().width, main.board_view.board().height,
main.info_panel.attack_text(),
main.stage.enemy_box.get_child_count(), main.ap_bar._dots.get_child_count()])
get_tree().quit(0)
else:
for p in problems:
push_error("[ui_probe] FAIL — " + p)
print("[ui_probe] FAIL — " + "; ".join(problems))
get_tree().quit(1)
# 开始层按钮按文案前缀寻址(campbtn 句柄未存组合根成员——树遍历只读;text 前缀匹配首枚按钮)
func _find_button_by_prefix(root: Node, prefix: String) -> Button:
if root is Button and String(root.text).begins_with(prefix):
return root
for child in root.get_children():
var found := _find_button_by_prefix(child, prefix)
if found != null:
return found
return null