test_skeleton.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. """分层可导入、核心契约、消息号无碰撞 — 骨架期测试的真实现版。"""
  2. import pbot
  3. from pbot.codec.frame import Frame, build_frame, parse_frame
  4. from pbot.game.messages import CsMsg, ScMsg
  5. from pbot.types import PointCode, pack_point, unpack_point
  6. from pbot.world.mirror import WorldMirror
  7. from pbot.world.update import (
  8. WorldUpdater,
  9. body_json,
  10. decode_body_variants,
  11. y_with_flag,
  12. )
  13. def sc(mid: int, recog: int, p1: int, p2: int, p3: int, body: bytes = b"") -> "Frame":
  14. """构造服务器帧(测试辅助): build → parse 得 Frame。"""
  15. return parse_frame(build_frame(mid, recog, p1, p2, p3, body))
  16. def test_top_level_exports_are_importable() -> None:
  17. """Given: 包顶层声明公共契约 When: 逐个访问 Then: 全部存在。"""
  18. for name in pbot.__all__:
  19. assert getattr(pbot, name) is not None
  20. def test_pack_point_roundtrip() -> None:
  21. """Given: 平面坐标 When: pack 后 unpack Then: 还原原值 (Y<<16|X 布局)。"""
  22. code: PointCode = pack_point(289, 615)
  23. assert code == (615 << 16) | 289
  24. assert unpack_point(code) == (289, 615)
  25. def test_sc_and_cs_msg_ids_are_unique_within_each_table() -> None:
  26. """Given: 两个消息号表 When: 取值集合 Then: 各自内部无重复值。"""
  27. sc_values = [m.value for m in ScMsg]
  28. cs_values = [m.value for m in CsMsg]
  29. assert len(sc_values) == len(set(sc_values))
  30. assert len(cs_values) == len(set(cs_values))
  31. def test_all_game_layers_importable() -> None:
  32. """Given: 分层架构 When: 自底向上导入每层 Then: 无循环依赖错误。"""
  33. from pbot import bot, codec, game, login, transport, world
  34. assert bot is not None and codec is not None
  35. assert game is not None and login is not None
  36. assert transport is not None and world is not None
  37. def test_cs_walk_layout_matches_capture() -> None:
  38. """Given: 实测帧布局 When: 检查关键消息号 Then: 与 backup 实测值一致。"""
  39. assert CsMsg.WALK == 3011
  40. assert CsMsg.RUN == 3013
  41. assert CsMsg.ATTACK == 3014
  42. assert CsMsg.HEARTBEAT == 60000
  43. assert ScMsg.BATCH == -1
  44. def test_y_flag_bit_masked() -> None:
  45. """Given: Y 带 bit11 标志(2048) When: _y_with_flag Then: 掩掉标志位。
  46. 实测(quest_bot3 SC51 解析): bit11 是编码标志非坐标 — 旧实现把它当坐标
  47. 导致出生坐标整体读错(如 (23,25) 读成 (25,0))。
  48. """
  49. assert y_with_flag(2048 + 25) == 25
  50. assert y_with_flag(25) == 25
  51. def test_body_json_decode6_variant() -> None:
  52. """Given: encode6(JSON) body(SC11 同款) When: body_json Then: 解出 dict。"""
  53. from pbot.codec.bitbuf import encode6
  54. raw = b'{"UserID":"123","X":10,"Y":20}'
  55. f = sc(11, 10, 20, 0, 0, body=encode6(raw))
  56. j = body_json(f)
  57. assert j["UserID"] == "123" and j["X"] == 10
  58. def test_body_variants_zlib_wrapped() -> None:
  59. """Given: zlib(encode6(JSON)) body When: decode_body_variants Then: 含明文 JSON。"""
  60. import zlib
  61. from pbot.codec.bitbuf import encode6
  62. inner = encode6(b'{"MapName":"xx"}')
  63. blob = zlib.compress(inner)
  64. variants = decode_body_variants(blob)
  65. assert b'{"MapName":"xx"}' in variants
  66. def test_sc51_sets_walk_position() -> None:
  67. """Given: SC51(recog=22, p1=26, body=MapName) When: apply Then:
  68. 位置=SC51 坐标(cap_live2 定案: 真客户端首步起点=(22,26) 与 SC51 一致,
  69. SC50.p2 是回家位墙格不采信)。"""
  70. f = sc(51, 22, 26, 2048, 0, body=b'{"MapName":"xx","MapId":"0"}')
  71. world = WorldMirror()
  72. world.me.x, world.me.y = 0, 1 # SC50 的回家位(墙格)
  73. WorldUpdater().apply(world, f)
  74. assert world.me.map_name == "xx"
  75. assert world.me.map_id == "0"
  76. assert (world.me.x, world.me.y) == (22, 26) # SC51 覆盖为真实位置
  77. assert any(e.kind == "map" for e in world.events)
  78. def test_sc28_pull_back_updates_authority() -> None:
  79. """Given: 主玩家在(20,26), SC28(recog=21,p1=27,p2=dir) When: apply Then:
  80. 权威位更新为 (21,27)(SC28 布局 recog=X, p1=Y, p2=dir — 盟重探针定案)。"""
  81. world = WorldMirror()
  82. world.me.x, world.me.y = 20, 26
  83. f = sc(28, 21, 27, 4, 0)
  84. WorldUpdater().apply(world, f)
  85. assert (world.me.x, world.me.y) == (21, 27)
  86. assert world.me.dir == 4
  87. def test_sc28_zero_frame_ignored() -> None:
  88. """Given: SC28 (0,0) 清位帧 When: apply Then: 忽略, 坐标不变。"""
  89. world = WorldMirror()
  90. world.me.x, world.me.y = 20, 26
  91. f = sc(28, 0, 0, 0, 0)
  92. WorldUpdater().apply(world, f)
  93. assert (world.me.x, world.me.y) == (20, 26)
  94. def test_sc11_self_echo_confirms_move() -> None:
  95. """Given: 主玩家 uid=u1 在(20,26), SC11 body.UserID=u1 recog=21,p1=2048+27
  96. When: apply Then: 权威坐标更新 (21,27) — 自回显即服务器确认移动。"""
  97. from pbot.codec.bitbuf import encode6
  98. world = WorldMirror()
  99. world.me.uid = "u1"
  100. world.me.x, world.me.y = 20, 26
  101. body = encode6(b'{"UserID":"u1"}')
  102. f = sc(11, 21, 2048 + 27, 0, 0, body=body)
  103. WorldUpdater().apply(world, f)
  104. assert (world.me.x, world.me.y) == (21, 27)
  105. assert any(e.kind == "self_move_confirmed" for e in world.events)
  106. def test_sc53_hp_pipe_format() -> None:
  107. """Given: SC53 body=encode6("u1&95&100&30&50|1") When: apply Then: HP=95/100。"""
  108. from pbot.codec.bitbuf import encode6
  109. world = WorldMirror()
  110. world.me.uid = "u1"
  111. f = sc(53, 0, 0, 0, 0, body=encode6(b"u1&95&100&30&50|1"))
  112. WorldUpdater().apply(world, f)
  113. assert (world.me.hp, world.me.max_hp) == (95, 100)