Files
mtgodot-poc/project/test_refine_parity.gd
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight
  - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程
  - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题
  - 新增 test_bridge_height_parity.gd 自动化对拍测试
- 40250 怪物击杀经验动效:
  - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附
- 40250 客户端全系统功能对齐(Batches 1-31):
  - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试
- 文档沉淀:
  - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

179 lines
6.7 KiB
GDScript

# test_refine_parity.gd —— 40250 强化系统 1:1 纯净无头自检套件
# 验证:
# 1. RefineDialogNew 动态几何尺寸计算与 Tooltip
# 2. 材料槽位与 ThinBoard,背包存量白字/红字对比
# 3. 金币费用与不足警示
# 4. 100% 成功率与 type==5 免弹窗直接发包
# 5. type==3 (铁匠石)、type==2 (祝福卷轴)、type==0 (普通铁匠) 对应专属 QuestionDialog2 文案
# 6. 二次确认弹窗 "否" 拦截发包、"是" 触发发包
# 7. 40250 取消规范:点击取消/ESC/关闭标题栏发送 refine(255, 255)
# 8. 10.0m 超距自动取消发包
# 9. 强化成功与失败音画回显
extends SceneTree
const RefineUI = preload("res://ui/refine_ui.gd")
class FakeAudio extends Node:
var calls: Array[String] = []
func play_ui(name: String) -> void:
calls.append(name)
class FakeClient extends Node:
signal refine_ask(info: Dictionary)
signal refine_result(ok: bool)
var calls: Array = []
var gold := 1000000
var inventory: Array = [
{"vnum": 30001, "count": 5, "cell": 0, "window": 1},
{"vnum": 30002, "count": 1, "cell": 1, "window": 1},
]
var main_pos := Vector3(100.0, 10.0, 100.0)
func is_in_game() -> bool: return true
func get_gold() -> int: return gold
func get_inventory() -> Array: return inventory
func get_main_vid() -> int: return 1001
func get_entity(vid: int) -> Dictionary:
if vid == 1001:
return {"pos": main_pos}
return {}
func refine(pos: int, type: int) -> bool:
calls.append(["refine", pos, type])
return true
var _fail := 0
func _ck(c: bool, m: String) -> void:
if not c:
_fail += 1
printerr("FAIL: " + m)
func _init() -> void:
_run()
if _fail == 0:
print("PASS: test_refine_parity (40250 RefineDialogNew 1:1 parity)")
quit(0)
else:
printerr("%d check(s) failed in test_refine_parity" % _fail)
quit(1)
func _run() -> void:
var canvas := CanvasLayer.new()
get_root().add_child(canvas)
var fc := FakeClient.new()
get_root().add_child(fc)
var fa := FakeAudio.new()
get_root().add_child(fa)
var ru: Node = RefineUI.new()
get_root().add_child(ru)
ru.setup(fc, canvas, null, fa)
_ck(not ru.is_open(), "1. 初始状态关闭")
# --- Case 1: 普通强化 (type 0, prob 60 < 100, 材料足) ---
fc.refine_ask.emit({
"type": 0, "pos": 5, "src_vnum": 10, "result_vnum": 11,
"cost": 50000, "prob": 60,
"materials": [{"vnum": 30001, "count": 2}],
})
_ck(ru.is_open(), "2. 收到 refine_ask 打开窗口")
_ck(ru._prob_label.text == "成功率: 60%", "3. 成功率文本准确")
_ck(ru._cost_label.text == "费用: 50000 金", "4. 费用文本准确")
_ck(ru._material_container.get_child_count() == 1, "5. 材质行生成")
var mat_lbl: Label = ru._material_container.get_child(0).get_child(1).get_child(0)
_ck(mat_lbl.modulate.r < 0.95 and mat_lbl.modulate.g > 0.8, "6. 材料足 (5>=2) 显示白字")
# 点击精炼 -> 应当弹出 QuestionDialog2,而不是直接发包
ru._refine_button.pressed.emit()
_ck(ru._question_dialog.visible, "7. prob 60 < 100 必须弹出 QuestionDialog2")
_ck(ru._qd_message1.text.contains("销毁"), "8. type 0 对应彻底销毁文案")
_ck(fc.calls.is_empty(), "9. 弹窗期间未发包")
# 点击 "否" -> 取消确认,精炼窗口不关,不发包
ru._qd_cancel_btn.pressed.emit()
_ck(not ru._question_dialog.visible, "10. 点否关闭确认弹窗")
_ck(ru.is_open(), "11. 主强化窗依然保持开启")
_ck(fc.calls.is_empty(), "12. 依然未发包")
# 再次点击精炼,并点 "是" -> 确认发包
ru._refine_button.pressed.emit()
ru._qd_accept_btn.pressed.emit()
_ck(fc.calls == [["refine", 5, 0]], "13. 确认后发出 client.refine(pos=5, type=0)")
_ck(not ru.is_open(), "14. 确认后主窗关闭")
fc.calls.clear()
# --- Case 2: 100% 成功率免弹窗 ---
fc.refine_ask.emit({
"type": 0, "pos": 2, "src_vnum": 1, "result_vnum": 2,
"cost": 1000, "prob": 100, "materials": [],
})
ru._refine_button.pressed.emit()
_ck(not ru._question_dialog.visible, "15. 100% 成功率不弹 QuestionDialog2")
_ck(fc.calls == [["refine", 2, 0]], "16. 100% 成功率直接发出 refine(2, 0)")
_ck(not ru.is_open(), "17. 发包后关闭")
fc.calls.clear()
# --- Case 3: 祝福卷轴 (type 2) 降级文案 ---
fc.refine_ask.emit({
"type": 2, "pos": 10, "src_vnum": 15, "result_vnum": 16,
"cost": 10000, "prob": 30, "materials": [],
})
ru._refine_button.pressed.emit()
_ck(ru._question_dialog.visible, "18. type 2 弹确认框")
_ck(ru._qd_message1.text.contains("降低1级"), "19. type 2 对应降级文案")
ru._qd_cancel_btn.pressed.emit()
# 点击取消按钮 -> 40250 发送 refine(255, 255)
ru._cancel_button.pressed.emit()
_ck(not ru.is_open(), "20. 取消后关闭")
_ck(fc.calls == [["refine", 255, 255]], "21. 取消发包 refine(255, 255)")
fc.calls.clear()
# --- Case 4: 铁匠石 (type 3) 加成文案 ---
fc.refine_ask.emit({
"type": 3, "pos": 12, "src_vnum": 18, "result_vnum": 19,
"cost": 20000, "prob": 50, "materials": [],
})
ru._refine_button.pressed.emit()
_ck(ru._qd_message1.text.contains("加成") or ru._qd_message1.text.contains("提升"), "22. type 3 对应概率提升文案")
# ESC 取消 -> 发送 refine(255, 255)
ru.cancel_refine()
_ck(not ru.is_open() and fc.calls == [["refine", 255, 255]], "23. cancel_refine 发送 (255, 255)")
fc.calls.clear()
# --- Case 5: 材料不足红字标红 ---
fc.refine_ask.emit({
"type": 0, "pos": 1, "src_vnum": 1, "result_vnum": 2,
"cost": 1000, "prob": 80,
"materials": [{"vnum": 30002, "count": 5}], # 背包只有 1 个
})
var shortage_lbl: Label = ru._material_container.get_child(0).get_child(1).get_child(0)
_ck(shortage_lbl.modulate.r > 0.9 and shortage_lbl.modulate.g < 0.4, "24. 材料不足 (1<5) 显示红字")
ru.close()
# --- Case 6: 10 米超距自动发包取消 ---
fc.refine_ask.emit({
"type": 0, "pos": 1, "src_vnum": 1, "result_vnum": 2,
"cost": 1000, "prob": 80, "materials": [],
})
_ck(ru.is_open(), "25. 再次打开")
# 玩家移动 5 米 (仍在 10 米内)
fc.main_pos = Vector3(105.0, 10.0, 100.0)
ru._process(0.1)
_ck(ru.is_open(), "26. 移动 5 米不关闭")
# 玩家移动 15 米 (超出 10 米)
fc.main_pos = Vector3(120.0, 10.0, 100.0)
ru._process(0.1)
_ck(not ru.is_open(), "27. 移动 15 米超出限制自动关闭")
_ck(fc.calls == [["refine", 255, 255]], "28. 超距自动发送取消包 refine(255, 255)")
fc.calls.clear()
# --- Case 7: 结果音效与状态回显 ---
fc.refine_result.emit(true)
_ck(ru.is_open() and ru._prob_label.text.contains("成功"), "29. 成功结果回显")
_ck(fa.calls.has("refine_success.wav"), "30. 播放 refine_success.wav")
fc.refine_result.emit(false)
_ck(ru._prob_label.text.contains("失败"), "31. 失败结果回显")
_ck(fa.calls.has("refine_fail.wav"), "32. 播放 refine_fail.wav")