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
+82 -2
View File
@@ -27,16 +27,17 @@ static int dump_strings(const gr2::File& f, size_t minlen) {
int main(int argc, char** argv) {
if (argc < 2) {
std::fprintf(stderr, "usage: gr2dump <file.gr2> [--sections] [--strings] [--gltf out.glb]\n");
std::fprintf(stderr, "usage: gr2dump <file.gr2> [--sections] [--strings] [--members] [--transforms] [--gltf out.glb]\n");
return 2;
}
const std::string path = argv[1];
bool want_sections = false, want_strings = false, want_members = false;
bool want_sections = false, want_strings = false, want_members = false, want_transforms = false;
std::string gltf_out;
for (int i = 2; i < argc; ++i) {
if (!std::strcmp(argv[i], "--sections")) want_sections = true;
else if (!std::strcmp(argv[i], "--strings")) want_strings = true;
else if (!std::strcmp(argv[i], "--members")) want_members = true;
else if (!std::strcmp(argv[i], "--transforms")) want_transforms = true;
else if (!std::strcmp(argv[i], "--gltf") && i + 1 < argc) gltf_out = argv[++i];
}
@@ -114,7 +115,15 @@ int main(int argc, char** argv) {
int depth = 0;
for (int p = b.parent; p >= 0 && depth < 64; ) { ++depth; p = sk.bones[p].parent; }
std::printf(" %*s[%zu] %s (parent=%d)\n", depth * 2, "", i, b.name.c_str(), b.parent);
if (want_transforms) {
std::printf(" local.t=(%.3f,%.3f,%.3f) inverse_world.t=(%.3f,%.3f,%.3f)\n",
b.local_transform[12], b.local_transform[13], b.local_transform[14],
b.inverse_world[12], b.inverse_world[13], b.inverse_world[14]);
}
}
if (want_transforms)
std::printf(" initial_placement.t=(%.3f,%.3f,%.3f)\n",
sk.initial_placement[12], sk.initial_placement[13], sk.initial_placement[14]);
}
for (size_t mi = 0; mi < fi.meshes.size(); ++mi) {
@@ -131,6 +140,21 @@ int main(int argc, char** argv) {
std::printf(" group %zu: material=%d tri_first=%d tri_count=%d tex=%s\n",
g, mix, me.tri_groups[g].tri_first,
me.tri_groups[g].tri_count, tn);
int g_up = 0, g_down = 0, g_side = 0;
for (int t = 0; t < me.tri_groups[g].tri_count; ++t) {
uint32_t idx = (me.tri_groups[g].tri_first + t) * 3;
if (idx + 2 < me.indices.size()) {
uint32_t i0 = me.indices[idx], i1 = me.indices[idx+1], i2 = me.indices[idx+2];
const auto& v0 = me.vertices[i0]; const auto& v1 = me.vertices[i1]; const auto& v2 = me.vertices[i2];
float e1[3] = {v1.pos[0]-v0.pos[0], v1.pos[1]-v0.pos[1], v1.pos[2]-v0.pos[2]};
float e2[3] = {v2.pos[0]-v0.pos[0], v2.pos[1]-v0.pos[1], v2.pos[2]-v0.pos[2]};
float nz = e1[0]*e2[1] - e1[1]*e2[0];
if (nz > 1e-3f) ++g_up;
else if (nz < -1e-3f) ++g_down;
else ++g_side;
}
}
std::printf(" tri face normal: +Z(up)=%d -Z(down)=%d sideways=%d\n", g_up, g_down, g_side);
}
for (size_t b = 0; b < me.material_textures.size(); ++b)
std::printf(" binding %zu -> %s\n", b, me.material_textures[b].c_str());
@@ -138,6 +162,62 @@ int main(int argc, char** argv) {
for (int32_t b : me.bone_bindings) if (b < 0) ++dangling;
if (!me.bone_bindings.empty())
std::printf(" dangling bone bindings: %d\n", dangling);
if (want_transforms && !me.vertices.empty()) {
float lo[3] = {me.vertices[0].pos[0], me.vertices[0].pos[1], me.vertices[0].pos[2]};
float hi[3] = {lo[0], lo[1], lo[2]};
for (const auto& v : me.vertices) for (int a = 0; a < 3; ++a) {
lo[a] = std::min(lo[a], v.pos[a]); hi[a] = std::max(hi[a], v.pos[a]);
}
std::printf(" aabb min=(%.3f,%.3f,%.3f) max=(%.3f,%.3f,%.3f)\n",
lo[0], lo[1], lo[2], hi[0], hi[1], hi[2]);
int normal_agree = 0, normal_disagree = 0;
for (size_t t = 0; t + 2 < me.indices.size(); t += 3) {
uint32_t i0 = me.indices[t + 0];
uint32_t i1 = me.indices[t + 1];
uint32_t i2 = me.indices[t + 2];
if (i0 < me.vertices.size() && i1 < me.vertices.size() && i2 < me.vertices.size()) {
const auto& v0 = me.vertices[i0];
const auto& v1 = me.vertices[i1];
const auto& v2 = me.vertices[i2];
float e1[3] = {v1.pos[0] - v0.pos[0], v1.pos[1] - v0.pos[1], v1.pos[2] - v0.pos[2]};
float e2[3] = {v2.pos[0] - v0.pos[0], v2.pos[1] - v0.pos[1], v2.pos[2] - v0.pos[2]};
float nx = e1[1]*e2[2] - e1[2]*e2[1];
float ny = e1[2]*e2[0] - e1[0]*e2[2];
float nz = e1[0]*e2[1] - e1[1]*e2[0];
float dot = nx * v0.normal[0] + ny * v0.normal[1] + nz * v0.normal[2];
if (dot > 0.0f) ++normal_agree;
else if (dot < 0.0f) ++normal_disagree;
}
}
std::printf(" winding vs normal: agree=%d disagree=%d\n", normal_agree, normal_disagree);
float avg_n[3] = {0,0,0};
for (const auto& v : me.vertices) {
avg_n[0] += v.normal[0]; avg_n[1] += v.normal[1]; avg_n[2] += v.normal[2];
}
if (!me.vertices.empty()) {
float inv = 1.0f / me.vertices.size();
std::printf(" avg normal: (%.3f, %.3f, %.3f)\n", avg_n[0]*inv, avg_n[1]*inv, avg_n[2]*inv);
}
int z_up = 0, z_down = 0, xy_side = 0;
for (const auto& v : me.vertices) {
if (v.normal[2] > 0.5f) ++z_up;
else if (v.normal[2] < -0.5f) ++z_down;
else ++xy_side;
}
std::printf(" vertex normal dir: +Z(up)=%d -Z(down)=%d sideways=%d\n", z_up, z_down, xy_side);
if (me.indices.size() >= 6) {
for (int tr = 0; tr < 2; ++tr) {
uint32_t i0 = me.indices[tr*3+0], i1 = me.indices[tr*3+1], i2 = me.indices[tr*3+2];
const auto &v0 = me.vertices[i0], &v1 = me.vertices[i1], &v2 = me.vertices[i2];
std::printf(" tri%d: v0=(%.1f,%.1f,%.1f) n=(%.2f,%.2f,%.2f)\n"
" v1=(%.1f,%.1f,%.1f) n=(%.2f,%.2f,%.2f)\n"
" v2=(%.1f,%.1f,%.1f) n=(%.2f,%.2f,%.2f)\n",
tr, v0.pos[0], v0.pos[1], v0.pos[2], v0.normal[0], v0.normal[1], v0.normal[2],
v1.pos[0], v1.pos[1], v1.pos[2], v1.normal[0], v1.normal[1], v1.normal[2],
v2.pos[0], v2.pos[1], v2.pos[2], v2.normal[0], v2.normal[1], v2.normal[2]);
}
}
}
}
for (size_t ai = 0; ai < fi.animations.size(); ++ai) {