"""chunk 解析回归。 核心验证方式:用 **prismarine-chunk(独立实现)产出的真实格式数据** 交给 Python 解 —— 两边独立,对得上才算格式对。 样本由 /home/newqqagent/plugin_data/mc/runtime/chunkgen*.js 生成。 """ import sys, os, json, subprocess, tempfile sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from mcfold.chunk import (decode_chunk, BlockRegistry, MIN_Y, NUM_SECTIONS, BitArray, Reader, ChunkError) RUNTIME = "/home/newqqagent/plugin_data/mc/runtime" BLOCKS = f"{RUNTIME}/node_modules/minecraft-data/minecraft-data/data/pc/1.20/blocks.json" def gen(script): """跑 Node 生成独立实现产出的样本。""" r = subprocess.run(["node", f"{RUNTIME}/{script}"], capture_output=True, text=True, cwd=RUNTIME, timeout=120) if r.returncode != 0: return None, r.stderr[:300] return json.loads(r.stdout), None def reg(): return BlockRegistry.from_json_file(BLOCKS) def test_bitarray(): """位打包:值不跨 long 边界(这是最容易写错的地方)。""" # 4 位/值,一个 long 放 16 个值 ba = BitArray(data=[0x3210], capacity=16, bits_per_value=4) assert [ba.get(i) for i in range(4)] == [0, 1, 2, 3], [ba.get(i) for i in range(4)] print(" ✓ 位打包:值不跨 long 边界(4位/值 → 16 值/long)") # 5 位/值 → floor(64/5)=12 值/long ba2 = BitArray(data=[0x1], capacity=12, bits_per_value=5) assert ba2.values_per_long == 12, ba2.values_per_long assert ba2.get(0) == 1 print(" ✓ 位打包:5位/值 → 12 值/long(余位丢弃)") def test_real_chunk_from_prismarine(): """核心回归:解独立实现产出的区块。""" d, err = gen("chunkgen.js") if d is None: print(f" - 跳过(Node 生成失败: {err})"); return exp = d["expect"] ch = decode_chunk(0, 0, bytes.fromhex(d["hex"])) assert len(ch.sections) == NUM_SECTIONS, len(ch.sections) r = reg() checks = [((0,-60,0),"stone"), ((1,-59,2),"gold_block"), ((5,-59,7),"diamond_block"), ((3,20,4),"diamond_block")] for (x,y,z), want in checks: sid = ch.state_id_at(x,y,z) got = r.name_or_unknown(sid) assert got == want, f"({x},{y},{z}) {sid} -> {got}, 期望 {want}" nonempty = [s.y_index for s in ch.sections if s.solid_block_count > 0] assert nonempty == [0,5], nonempty print(f" ✓ 解 prismarine 真实区块: 4/4 方块命中, 非空 section={nonempty}") def test_all_encoding_branches(): """覆盖单值/本地调色板/高 section 三种编码分支。""" d, err = gen("chunkgen2.js") if d is None: print(f" - 跳过(Node 生成失败: {err})"); return r = reg() # A: 全空气 → 应无实心方块 chA = decode_chunk(0,0,bytes.fromhex(d["A"]["hex"])) assert all(s.solid_block_count == 0 for s in chA.sections), "全空气却有实心方块" print(" ✓ 分支 A(全空气): solidBlockCount 全 0") # B: 一行石头 chB = decode_chunk(0,0,bytes.fromhex(d["B"]["hex"])) names = {r.name_or_unknown(chB.state_id_at(i,0,0)) for i in range(16)} assert names == {"stone"}, names print(f" ✓ 分支 B(一行石头): y=0 的 16 个方块全是 stone") # C: 四种混合 → 验证本地调色板 chC = decode_chunk(0,0,bytes.fromhex(d["C"]["hex"])) got = [r.name_or_unknown(chC.state_id_at(i,60,0)) for i in range(4)] assert got == ["stone","dirt","gold_block","diamond_block"], got print(f" ✓ 分支 C(本地调色板): {got}") # D: 高 section chD = decode_chunk(0,0,bytes.fromhex(d["D"]["hex"])) assert r.name_or_unknown(chD.state_id_at(3,20,4)) == "diamond_block" print(" ✓ 分支 D(高 section y=20)") def test_mod_block_explicit_unknown(): """★ 关键行为:本机注册表没有的 state id 必须显式 unknown,**不可静默错配**。 这正是 mineflayer 的缺陷(越界 id 静默变成 note_block),我们要把它变成可见降级。 说明:格式正确性已由 test_real_chunk_from_prismarine(独立实现数据,4/4)证明; 这里聚焦**注册表行为**,故用 Python 侧的 writer 构造样本(自洽即可)。 """ from mcfold.protodef import Writer w = Writer() # section 0: solidCount=1, bits=4, palette=[99999], longs=256, 全 0 位 w.write((1).to_bytes(2, "big")) w.write(bytes([4])) w.varint(1); w.varint(99999) w.varint(256) w.write(b"\x00" * (256 * 8)) # biome section(单值) w.write(bytes([0])); w.varint(0); w.varint(0) # 其余 23 个 section:单值空气 + biome for _ in range(23): w.write((0).to_bytes(2, "big")); w.write(bytes([0])) w.varint(0); w.varint(0) w.write(bytes([0])); w.varint(0); w.varint(0) ch = decode_chunk(0, 0, w.getvalue()) # 末尾 section 属半写状态,取已完整解出的 section 0(y=-64..-49) sid = ch.state_id_at(0, -64, 0) assert sid == 99999, f"state id 解码错: {sid}" r = reg() got = r.name_or_unknown(sid) assert got.startswith("unknown("), f"越界 id 竟解析出了名字: {got}" assert r.is_out_of_range(sid), "越界判定失效" # 对照:mineflayer 的行为是静默错配 —— 我们明确不同 assert r.name_of(sid) is None, "越界 id 不应能查到名字" print(f" ✓ ★ mod 方块显式降级: state {sid} -> {got}(而非静默错配)") def test_registry_degradation_direct(): """直接验证注册表降级语义(不依赖线格式)。""" r = reg() assert r.name_or_unknown(0) == "air" assert r.name_or_unknown(24134) is not None # 原版上界内 assert r.name_or_unknown(24135).startswith("unknown(") # 超出上界 assert r.is_out_of_range(50000) print(" ✓ 注册表降级语义:原版内可查、越界显式 unknown") def test_out_of_bounds_safe(): """世界边界外的查询必须安全返回空气,而不是崩。""" from mcfold.protodef import Writer w = Writer() for _ in range(NUM_SECTIONS): w.write((0).to_bytes(2, "big")); w.write(bytes([0])) w.varint(0); w.varint(0) # 方块 section(单值空气) w.write(bytes([0])); w.varint(0); w.varint(0) # biome section ch = decode_chunk(0, 0, w.getvalue()) assert ch.state_id_at(0, -1000, 0) == 0 assert ch.state_id_at(0, 1000, 0) == 0 assert ch.state_id_at(0, MIN_Y, 0) == 0 print(" ✓ 世界边界外查询安全返回空气") def test_bad_data_raises(): """损坏数据必须报错,不能静默产出错数据。""" try: decode_chunk(0,0,b"\x00\x01\xff") # bitsPerBlock=255 assert False, "应当报错" except (ChunkError, Exception) as e: print(f" ✓ 损坏数据显式报错: {str(e)[:45]}") if __name__ == "__main__": for fn in [test_bitarray, test_real_chunk_from_prismarine, test_all_encoding_branches, test_mod_block_explicit_unknown, test_registry_degradation_direct, test_out_of_bounds_safe, test_bad_data_raises]: fn() print("\nchunk 解析:全部通过")