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 客户端对拍缺陷发现与修复工程指南
This commit is contained in:
shen
2026-09-19 08:51:25 -07:00
parent 1db9e9a129
commit 66d217b313
650 changed files with 53046 additions and 1586 deletions
+25
View File
@@ -63,6 +63,9 @@ func _run() -> void:
if hair_image != null:
hair_image.save_png(output.path_join("hair-texture.png"))
var mesh_bounds: Array = []
var body_uses_blue_steel_skin := false
var blue_steel_path := AssetRoot.path().path_join("patch2/ymir work/pc/warrior/warrior_4-2.dds")
var blue_steel_image: Image = ua.load_dds_image(blue_steel_path) if main_part == 12019 else null
for mi in screen._pv.find_children("*", "MeshInstance3D", true, false):
var local_aabb: AABB = mi.get_aabb()
var world_aabb: AABB = mi.get_global_transform() * local_aabb
@@ -78,6 +81,17 @@ func _run() -> void:
var mat: Material = mi.get_active_material(surface)
if mat is ShaderMaterial:
print("MATERIAL mode=", mat.get_shader_parameter("mode"), " alpha_write=", mat.shader.code.contains("ALPHA ="))
var albedo := mat.get_shader_parameter("albedo_tex") as Texture2D
if albedo and blue_steel_image:
var actual := albedo.get_image()
# Character textures generate mipmaps before upload. Compare only
# level 0 with the decoded DDS or identical pixels would differ by
# the appended mip levels.
var expected_data := blue_steel_image.get_data()
var actual_data := actual.get_data() if actual else PackedByteArray()
if actual and actual.get_size() == blue_steel_image.get_size() \
and actual_data.slice(0, expected_data.size()) == expected_data:
body_uses_blue_steel_skin = true
if "--solid-body" in OS.get_cmdline_user_args():
var solid := StandardMaterial3D.new()
solid.albedo_texture = mat.get_shader_parameter("albedo_tex")
@@ -87,12 +101,23 @@ func _run() -> void:
printerr("FAIL: server armor was not applied")
quit(1)
return
if main_part == 12019 and not body_uses_blue_steel_skin:
printerr("FAIL: armor 12019 geometry loaded without warrior_4-2 target skin")
quit(1)
return
if hair_part == 1001 and not String(screen._pv.model.get("hair_gr2")).to_lower().contains("hair_2_1"):
printerr("FAIL: standard HairIndex 1001 did not replace the default hair model")
quit(1)
return
if DisplayServer.get_name() != "headless":
await RenderingServer.frame_post_draw
if screen._viewport:
var viewport_image := screen._viewport.get_texture().get_image()
if viewport_image.detect_alpha() != Image.ALPHA_NONE:
printerr("FAIL: selection stage must be opaque; transparent additive particles create black quads")
quit(1)
return
viewport_image.save_png(output.path_join("viewport.png"))
if root.get_texture().get_image().save_png(output.path_join("selection.png")) != OK:
quit(1)
return