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

139 lines
5.9 KiB
GDScript

# test_soulbound_lock_security_parity.gd —— 40250 灵魂绑定与防丢安全锁 1:1 回归测试套件
extends SceneTree
const SoulboundLockSecuritySystem = preload("res://soulbound_lock_security_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_soulbound_lock_security_parity (Direction 3: Soulbound Security 1:1) ===")
_test_security_predicates()
_test_soul_binding_flow()
_test_unbinding_and_cancellation()
_test_unbinding_completion()
_finish()
func _test_security_predicates() -> void:
print("\n--- Test 1: Static Security Predicates (Death Drop, Sell, Stall) ---")
var normal_item := {"vnum": 10, "is_soulbound": false}
var bound_item := {"vnum": 189, "is_soulbound": true}
# Normal item can drop, sell, stall
_assert(SoulboundLockSecuritySystem.can_drop_on_death(normal_item) == true, "Normal item drops on red name death")
_assert(SoulboundLockSecuritySystem.can_sell_to_shop(normal_item) == true, "Normal item can be sold to shop")
_assert(SoulboundLockSecuritySystem.can_drop_to_ground(normal_item) == true, "Normal item can be dropped to ground")
_assert(SoulboundLockSecuritySystem.can_trade_or_stall(normal_item) == true, "Normal item can be traded or stalled")
# Soulbound item is 100% protected
_assert(SoulboundLockSecuritySystem.can_drop_on_death(bound_item) == false, "Soulbound item IMMUNE to death drop")
_assert(SoulboundLockSecuritySystem.can_sell_to_shop(bound_item) == false, "Soulbound item CANNOT be sold to shop")
_assert(SoulboundLockSecuritySystem.can_drop_to_ground(bound_item) == false, "Soulbound item CANNOT be dropped to ground")
_assert(SoulboundLockSecuritySystem.can_trade_or_stall(bound_item) == false, "Soulbound item CANNOT be put on bazaar stalls")
func _test_soul_binding_flow() -> void:
print("\n--- Test 2: Applying Soul Binding (72051) ---")
var sys := SoulboundLockSecuritySystem.new()
var inv: Array = [
{"vnum": SoulboundLockSecuritySystem.VNUM_SOULBIND_SCROLL, "name": "灵魂绑定卷轴", "count": 2},
{"vnum": 189, "name": "毒剑+9", "is_soulbound": false}
]
var bind_sig := {"fired": false, "slot": -1, "name": ""}
sys.item_soulbound.connect(func(s: int, iname: String):
bind_sig["fired"] = true
bind_sig["slot"] = s
bind_sig["name"] = iname
)
var res = sys.bind_item(0, 1, inv)
_assert(res["ok"] == true, "Item soulbound")
_assert(inv[1]["is_soulbound"] == true, "is_soulbound is true")
_assert(inv[0]["count"] == 1, "Scroll count decreased (2 -> 1)")
_assert(bind_sig["fired"] == true and bind_sig["slot"] == 1 and bind_sig["name"] == "毒剑+9", "item_soulbound emitted")
# Duplicate binding rejected
var fail_dup = sys.bind_item(0, 1, inv)
_assert(fail_dup["ok"] == false and fail_dup["reason"] == "ALREADY_SOULBOUND", "Duplicate binding rejected")
_assert(inv[0]["count"] == 1, "Scroll not consumed on failure")
func _test_unbinding_and_cancellation() -> void:
print("\n--- Test 3: Unbinding Initiation (72052) & Cancellation ---")
var sys := SoulboundLockSecuritySystem.new()
var inv: Array = [
{"vnum": SoulboundLockSecuritySystem.VNUM_SOULUNBIND_SCROLL, "name": "灵魂解绑卷轴", "count": 1},
{"vnum": 189, "name": "毒剑+9", "is_soulbound": true}
]
var unbind_start_sig := {"fired": false, "time": 0.0}
sys.unbinding_started.connect(func(_s: int, _n: String, t: float):
unbind_start_sig["fired"] = true
unbind_start_sig["time"] = t
)
# Start unbinding (72 hours)
var res = sys.start_unbinding(0, 1, inv, 72.0)
_assert(res["ok"] == true, "Unbinding started")
_assert(inv[0] == null, "Unbind scroll consumed")
_assert(unbind_start_sig["fired"] == true and unbind_start_sig["time"] == 72.0 * 3600.0, "unbinding_started emitted with 72h")
_assert(inv[1]["unbind_remaining_time"] == 72.0 * 3600.0, "Cooldown active")
# Cancel unbinding
var cancel_sig := {"fired": false}
sys.unbinding_cancelled.connect(func(_s: int, _n: String): cancel_sig["fired"] = true)
var cancel_res = sys.cancel_unbinding(1, inv)
_assert(cancel_res["ok"] == true, "Unbinding cancelled")
_assert(cancel_sig["fired"] == true, "unbinding_cancelled emitted")
_assert(inv[1]["unbind_remaining_time"] == 0.0, "Timer cleared")
_assert(inv[1]["is_soulbound"] == true, "Item remains safely soulbound")
func _test_unbinding_completion() -> void:
print("\n--- Test 4: Finalizing Unbinding After Cooldown ---")
var sys := SoulboundLockSecuritySystem.new()
var inv: Array = [
{"vnum": SoulboundLockSecuritySystem.VNUM_SOULUNBIND_SCROLL, "count": 1},
{"vnum": 189, "name": "毒剑+9", "is_soulbound": true}
]
# Start short 1-second unbind for testing
sys.start_unbinding(0, 1, inv, 1.0 / 3600.0) # 1 second
# Immediate finalize attempt should fail
var fail_early = sys.finalize_unbinding(1, inv)
_assert(fail_early["ok"] == false and fail_early["reason"] == "COOLDOWN_NOT_FINISHED", "Cannot finalize before timer")
# Update 2 seconds
sys.update(2.0, inv)
_assert(inv[1]["unbind_remaining_time"] == 0.0, "Timer expired")
# Finalize unbind
var unbound_sig := {"fired": false}
sys.item_unbound.connect(func(_s: int, _n: String): unbound_sig["fired"] = true)
var done_res = sys.finalize_unbinding(1, inv)
_assert(done_res["ok"] == true, "Item unbound")
_assert(unbound_sig["fired"] == true, "item_unbound emitted")
_assert(inv[1]["is_soulbound"] == false, "Item is no longer soulbound")
_assert(SoulboundLockSecuritySystem.can_drop_on_death(inv[1]) == true, "Now subject to normal drop rules")
func _finish() -> void:
print("\n==========================================")
print("Soulbound Security Parity Tests Finished:")
print("Passed: %d, Failed: %d" % [_passed, _failed])
print("==========================================")
if _failed > 0:
printerr("FAILED: Some tests did not pass.")
quit(1)
else:
print("SUCCESS: All Soulbound Security parity tests passed!")
quit(0)