Files
mtgodot-poc/project/movement_parity_test.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

114 lines
5.1 KiB
GDScript

# movement_parity_test.gd —— 验证角色与怪物移动对齐 40250 的实现机制:
# 1. PlayerController 转向 45° 阻尼减速(5/12)与 ARRIVE_EPS 到达精度。
# 2. PlayerController 遇障碍物切线滑动判定。
# 3. EntityStore 网络命令队列 EMA 滤波与动作速度 fallback。
# 4. NetWorld 远端实体朝向收敛 45° 阻尼。
#
# godot --headless --path project --script movement_parity_test.gd
extends SceneTree
const PlayerController = preload("res://player_controller.gd")
const NetWorld = preload("res://net_world.gd")
const MobView = preload("res://ui/mob_view.gd")
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: movement_parity_test (40250 rotation damping + arrival epsilon + collision tangent + EMA network gap)")
quit(0)
else:
printerr("%d check(s) failed" % _fail)
quit(1)
func _run() -> void:
# =========================================================================
# 1. PlayerController: 转向 45° 阻尼减速测试 (40250 InstanceBaseMovement.cpp:142-154)
# =========================================================================
var pc := PlayerController.new()
var player := Node3D.new()
get_root().add_child(player)
get_root().add_child(pc)
pc.player = player
pc.rotation_speed_deg = 1200.0
# 角度差 > 45° 时(例如 90° = PI/2):转向速度应为全速 1200°/s
player.rotation.y = 0.0
var target_90 := deg_to_rad(90.0)
pc._turn_toward(target_90, 0.01) # dt = 10ms -> 预期前进 12°
var turned_90: float = rad_to_deg(player.rotation.y)
_ck(is_equal_approx(turned_90, 12.0), "Over 45 deg: full speed 1200 deg/s (turned %.2f, expected 12.0)" % turned_90)
# 角度差 <= 45° 时(例如 30°):转向速度应降为 5/12(500°/s)
player.rotation.y = 0.0
var target_30 := deg_to_rad(30.0)
pc._turn_toward(target_30, 0.01) # dt = 10ms -> 预期前进 5° (12 * 5/12 = 5)
var turned_30: float = rad_to_deg(player.rotation.y)
_ck(is_equal_approx(turned_30, 5.0), "Under 45 deg: damped speed 500 deg/s (turned %.2f, expected 5.0)" % turned_30)
# =========================================================================
# 2. PlayerController: ARRIVE_EPS 精准到达测试 (40250 InstanceBase.cpp:1600)
# =========================================================================
_ck(pc.ARRIVE_EPS <= 0.05, "ARRIVE_EPS is <= 0.05 (got %f)" % pc.ARRIVE_EPS)
player.position = Vector3(10.0, 0.0, 10.0)
pc._goto(Vector3(12.0, 0.0, 10.0)) # 距离目标点 2m (> 1m MOVABLE_GROUND_DISTANCE_M)
_ck(pc.is_going(), "_goto started (is_going == true)")
# 模拟角色走到了距目标 3cm 处
player.position = Vector3(11.97, 0.0, 10.0)
pc._process(0.016)
_ck(not pc.is_going(), "Within ARRIVE_EPS (3cm <= 5cm) stops immediately (is_going == false)")
_ck(is_equal_approx(player.position.x, 12.0), "Player position snapped to exact clicked target X")
# =========================================================================
# 3. PlayerController: 切线滑动测试 (40250 AdjustDynamicCollisionMovement)
# =========================================================================
var obstacle := Node3D.new()
get_root().add_child(obstacle)
# 障碍物放在 (11.0, 0, 10.0),半径 0.55m -> combined radius 1.1m
obstacle.position = Vector3(11.0, 0.0, 10.0)
obstacle.set_meta("kind", 0)
pc.pickables.append(obstacle)
player.position = Vector3(10.0, 0.0, 10.0)
# 阻挡判定:朝向 (10.5, 0.0, 10.0) 会撞击障碍物(距离 0.5m < 1.1m 且更趋近)
_ck(pc._actor_blocked(Vector3(10.5, 0.0, 10.0)), "Blocked when moving into obstacle body radius")
_ck(pc._get_blocking_actor(Vector3(10.5, 0.0, 10.0)) == obstacle, "_get_blocking_actor returns obstacle")
# =========================================================================
# 4. NetWorld: 远端实体朝向 45° 阻尼收敛验证
# =========================================================================
var nw := NetWorld.new()
get_root().add_child(nw)
var remote_node := Node3D.new()
remote_node.rotation.y = 0.0
var target_yaw := deg_to_rad(30.0)
var turn_rate := 1200.0
var diff: float = wrapf(target_yaw - remote_node.rotation.y, -PI, PI)
if absf(diff) <= deg_to_rad(45.0):
turn_rate *= 5.0 / 12.0
remote_node.rotation.y = rotate_toward(remote_node.rotation.y, target_yaw, deg_to_rad(turn_rate) * 0.01)
var nw_turned: float = rad_to_deg(remote_node.rotation.y)
_ck(is_equal_approx(nw_turned, 5.0), "NetWorld rotation damped within 45 deg: turned %.2f, expected 5.0" % nw_turned)
# =========================================================================
# 5. M2Client / EntityStore: 单动作 speed fallback 与 EMA 滤波测试
# =========================================================================
if ClassDB.class_exists("M2Client"):
var client = ClassDB.instantiate("M2Client")
get_root().add_child(client)
_ck(client.has_method("set_entity_motion_speed"), "M2Client has set_entity_motion_speed")
client.call("set_entity_motion_speed", 12345, 0.0, 350.0) # 仅有 run 速度
client.queue_free()
pc.queue_free()
player.queue_free()
obstacle.queue_free()
remote_node.queue_free()
nw.queue_free()