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
+137
View File
@@ -0,0 +1,137 @@
# test_guild_war_parity.gd —— 40250 官方公会战备宣战与积分决斗 1:1 回归测试套件
extends SceneTree
const GuildWarSystem = preload("res://guild_war_system.gd")
var _passed := 0
var _failed := 0
func _assert(cond: bool, msg: String) -> void:
if cond:
_passed += 1
print(" [PASS] %s" % msg)
else:
_failed += 1
printerr(" [FAIL] %s" % msg)
func _init() -> void:
print("\n=== Running test_guild_war_parity (Direction 3: Guild War 1:1) ===")
_test_war_declaration_and_start()
_test_score_tracking_and_target_win()
_test_war_timeout_resolution()
_test_serialization()
_finish()
func _test_war_declaration_and_start() -> void:
print("\n--- Test 1: War Declaration & State Machine ---")
var sys := GuildWarSystem.new()
_assert(sys.war_state == GuildWarSystem.STATE_NONE, "Initially not at war")
var dec_sig := {"fired": false, "opp": "", "score": 0}
sys.war_declared.connect(func(opp: String, _wt: int, s: int):
dec_sig["fired"] = true
dec_sig["opp"] = opp
dec_sig["score"] = s
)
# 1. Declare war against Shadow Assassins (101)
var dec_res = sys.declare_war(101, GuildWarSystem.WAR_TYPE_ARENA, 50, 1800.0)
_assert(dec_res["ok"] == true, "War declared")
_assert(sys.war_state == GuildWarSystem.STATE_DECLARED, "State is STATE_DECLARED")
_assert(dec_sig["fired"] == true, "war_declared signal emitted")
_assert(dec_sig["opp"] == "暗影刺客团", "Opponent is 暗影刺客团")
# Cannot declare war while already declared
var dec_again = sys.declare_war(102)
_assert(dec_again["ok"] == true, "Replaced declaration before start")
var start_sig := {"fired": false, "dur": 0.0}
sys.war_started.connect(func(_wt: int, d: float):
start_sig["fired"] = true
start_sig["dur"] = d
)
# 2. Start war
var start_res = sys.start_war()
_assert(start_res["ok"] == true, "War started")
_assert(sys.war_state == GuildWarSystem.STATE_FIGHTING, "State is STATE_FIGHTING")
_assert(start_sig["fired"] == true, "war_started signal emitted")
func _test_score_tracking_and_target_win() -> void:
print("\n--- Test 2: Score Tracking & Target Score (50 Points) Victory ---")
var sys := GuildWarSystem.new()
sys.declare_war(102, GuildWarSystem.WAR_TYPE_ARENA, 50, 1800.0)
sys.start_war()
var score_sig := {"fired": false, "sa": 0, "sb": 0}
sys.score_updated.connect(func(sa: int, sb: int):
score_sig["fired"] = true
score_sig["sa"] = sa
score_sig["sb"] = sb
)
var end_sig := {"fired": false, "winner": "", "reward": {}}
sys.war_ended.connect(func(w: String, _sa: int, _sb: int, rew: Dictionary):
end_sig["fired"] = true
end_sig["winner"] = w
end_sig["reward"] = rew
)
# Score increments
sys.record_score("A", 10)
sys.record_score("B", 5)
_assert(sys.score_a == 10 and sys.score_b == 5, "Score is 10 - 5")
_assert(score_sig["fired"] == true, "score_updated signal emitted")
# Side A scores 40 more to hit 50 points (target score)
var win_res = sys.record_score("A", 40)
_assert(win_res["winner"] == "A", "Side A declared winner")
_assert(sys.war_state == GuildWarSystem.STATE_FINISHED, "State is STATE_FINISHED")
_assert(sys.player_guild_ladder == 1030, "Ladder points +30 (1000 -> 1030)")
_assert(sys.total_wars_won == 1, "Total wars won is 1")
_assert(end_sig["fired"] == true, "war_ended signal emitted")
_assert(end_sig["reward"]["guild_gold"] == 1000000, "Received 1,000,000 guild gold")
func _test_war_timeout_resolution() -> void:
print("\n--- Test 3: War Timeout & Decisive Score Ruling ---")
var sys := GuildWarSystem.new()
sys.declare_war(103, GuildWarSystem.WAR_TYPE_FIELD, 100, 1800.0)
sys.start_war()
sys.record_score("A", 25)
sys.record_score("B", 18)
# Advance 1800s to timeout
sys.update(1800.0)
_assert(sys.war_state == GuildWarSystem.STATE_FINISHED, "War finished on timeout")
_assert(sys.score_a == 25 and sys.score_b == 18, "Final score is 25 - 18")
_assert(sys.player_guild_ladder == 1030, "Player won on higher score (+30 ladder)")
func _test_serialization() -> void:
print("\n--- Test 4: Serialization & Restoration ---")
var sys1 := GuildWarSystem.new()
sys1.player_guild_ladder = 1450
sys1.total_wars_won = 15
sys1.declare_war(101, GuildWarSystem.WAR_TYPE_FLAG, 50, 1500.0)
sys1.start_war()
sys1.record_score("A", 30)
sys1.record_score("B", 20)
var data = sys1.serialize()
var sys2 := GuildWarSystem.new()
sys2.deserialize(data)
_assert(sys2.war_state == GuildWarSystem.STATE_FIGHTING, "Restored state FIGHTING")
_assert(sys2.active_war_type == GuildWarSystem.WAR_TYPE_FLAG, "Restored war type FLAG")
_assert(sys2.score_a == 30 and sys2.score_b == 20, "Restored scores")
_assert(sys2.player_guild_ladder == 1450, "Restored ladder rating 1450")
_assert(sys2.total_wars_won == 15, "Restored total wars won 15")
func _finish() -> void:
print("\n=======================================================")
print("Guild War System Parity Tests: %d Passed, %d Failed" % [_passed, _failed])
print("=======================================================\n")
if _failed > 0:
quit(1)
else:
quit(0)