- 底座下名字行改 HBox 组合:护甲类型图标占位 Label + 名字 Label;文本格式「名字 · 类型·AC%d」退役 - 图标形状+色相双通道占位(◇轻甲·灰石 / ◆魔法护甲·小人蓝 / ▣重甲·深金;避开 Boss 专属紫赤 #8F2F6A 与妨害紫),终稿图标资产归美术批 - _armor_class 字段与 set_data 读取删除(站桩视图零消费方;核心/数据层 armor_class 不动);_armor 保留供图标 - battle_stage_view._card_by_name 与 smoke/ui_probe 名字行断言同步新节点口径(ui_probe 增护甲图标非空检查) - 全量冒烟 557 项 0 失败退出码 0
217 lines
13 KiB
GDScript
217 lines
13 KiB
GDScript
# 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.route_overlay.abandon_btn == null or main.route_overlay.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 后、首战前弹选路层;
|
||
# 真实按钮路径 pick 入口单候选进首战)----
|
||
if not main.route_overlay.visible:
|
||
problems.append("关卡开始未弹入口选路停点(route_overlay 应可见)")
|
||
elif main.runner == null or not main.runner.awaiting_choice() \
|
||
or main.runner.next_candidate_ids().size() != 1:
|
||
problems.append("入口停点状态失守(选路态 + 入口单候选)")
|
||
else:
|
||
main.route_overlay.press_candidate(0)
|
||
await get_tree().process_frame
|
||
await get_tree().process_frame
|
||
# ---- 放弃入口·冒险模式可见 + 战斗中按下被拒(真实按钮路径——入口挂选路停点,战斗中拒绝为防御分支)----
|
||
if main.route_overlay.abandon_btn == null or not main.route_overlay.abandon_btn.visible:
|
||
problems.append("冒险模式放弃按钮未开放(选路停点入口在案)")
|
||
else:
|
||
main.route_overlay.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.hp_text().begins_with("HP —") or main.info_panel.hp_text().contains("—"):
|
||
problems.append("C 区血量未显示(%s)" % main.info_panel.hp_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.slot_row == null:
|
||
problems.append("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 全站桩拍板)----
|
||
var standee = main.stage.enemy_box.get_child(0)
|
||
if standee != null and (standee._name_label.text == "" or standee._armor_icon.text == ""):
|
||
problems.append("敌立绘名字行缺失(名字或护甲类型图标占位为空——2026-09-26 终裁:AC 不展示、类型图标化)")
|
||
if standee != null and standee._intent_badge == null:
|
||
problems.append("敌立绘意图指示缺失")
|
||
elif standee != null:
|
||
# 三档动作动画占位契约(表现层升级批拍板:不显示技能名——图标档位 + 一次性动画)
|
||
standee.set_pending("damage", "")
|
||
if not standee._intent_badge.visible or standee._intent_badge.text != "⚔":
|
||
problems.append("攻击系意图未呈现(visible=%s text=%s)" % [str(standee._intent_badge.visible), standee._intent_badge.text])
|
||
standee.set_pending("basic", "")
|
||
if standee._intent_badge.visible:
|
||
problems.append("普攻应零表示(意图指示应隐藏)")
|
||
if main.stage.hero_standee == null or main.stage.hero_standee._hp_bar == null:
|
||
problems.append("B 区主角站桩缺失")
|
||
# ---- BUG-0001 回归(tooltip 入树后 set_effects 槽位不错位——结算软锁修复沉淀)----
|
||
var slots_row = main.info_panel.slot_row
|
||
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.hp_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
|