Files
agentmc/tests/test_world.py

121 lines
5.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""世界模型回归:区块聚合 + 世界坐标查询 + 显式降级。"""
import sys, os, json, subprocess
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from mcfold.world import World, in_world
from mcfold.chunk import BlockRegistry, MIN_Y, WORLD_HEIGHT
from mcfold.protodef import Writer
RUNTIME = "/home/newqqagent/plugin_data/mc/runtime"
BLOCKS = f"{RUNTIME}/node_modules/minecraft-data/minecraft-data/data/pc/1.20/blocks.json"
def reg(): return BlockRegistry.from_json_file(BLOCKS)
def test_negative_coords():
"""★ 负坐标定位:MC 世界坐标可为负,这是最容易错的地方。"""
w = World(reg())
# 在 (-1,-1) 区块放一个方块:(x=-1,z=-1) ⇒ chunk (-1,-1), 内部 (15,15)
assert (-1) >> 4 == -1 and (-1) & 15 == 15
# 先确认未加载时报 unloaded(而不是假装空气)
hit = w.block_at(-1, 64, -1)
assert not hit.loaded and hit.source == "unloaded", hit
print(" ✓ 负坐标:未加载区块显式返回 unloaded(非假装空气)")
def test_unloaded_vs_air_distinction():
"""★ 关键语义:「未加载」与「空气」必须可区分。
mineflayer 的 bot.blockAt 在未加载时返回 null,但很多调用方当空气处理;
我们要求显式区分,避免 agent 误判。
"""
w = World(reg())
unloaded = w.block_at(0, 64, 0)
assert unloaded.source == "unloaded", unloaded
# 加载一个全空气区块后,同一坐标应变成 air(registry)
wr = Writer()
for _ in range(24):
wr.write((0).to_bytes(2,"big")); wr.write(bytes([0]))
wr.varint(0); wr.varint(0)
wr.write(bytes([0])); wr.varint(0); wr.varint(0)
w.load_chunk_data(0, 0, wr.getvalue())
air = w.block_at(0, 64, 0)
assert air.loaded and air.name == "air", (air.loaded, air.name)
print(" ✓ 未加载(unloaded) 与 空气(air) 语义分明")
def test_real_chunk_via_world():
"""用 prismarine 产出的真实区块走完 World 全路径。"""
r = subprocess.run(["node", f"{RUNTIME}/chunkgen.js"], capture_output=True,
text=True, cwd=RUNTIME, timeout=120)
if r.returncode != 0:
print(f" - 跳过({r.stderr[:80]})"); return
d = json.loads(r.stdout)
w = World(reg())
w.load_chunk_data(0, 0, bytes.fromhex(d["hex"]))
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:
hit = w.block_at(x,y,z)
assert hit.loaded and hit.name == want, (x,y,z,hit)
print(f" ✓ World 全路径: 4/4 方块命中(解真实区块数据)")
def test_world_height_bounds():
w = World(reg())
assert w.block_at(0, MIN_Y-1, 0).source == "out-of-world"
assert w.block_at(0, MIN_Y+WORLD_HEIGHT, 0).source == "out-of-world"
assert w.block_at(0, MIN_Y, 0).source == "unloaded" # 合法高度但未加载
assert in_world(MIN_Y) and not in_world(MIN_Y-1)
print(f" ✓ 世界高度边界: [{MIN_Y}, {MIN_Y+WORLD_HEIGHT}) 之外显式 out-of-world")
def test_unknown_state_reported():
"""★ mod 方块的可见降级:unknown(id) 且计入统计。"""
r = reg()
w = World(r)
wr = Writer()
# section0: palette=[99999]
wr.write((1).to_bytes(2,"big")); wr.write(bytes([4]))
wr.varint(1); wr.varint(99999); wr.varint(256)
wr.write(b"\x00"*(256*8))
wr.write(bytes([0])); wr.varint(0); wr.varint(0)
for _ in range(23):
wr.write((0).to_bytes(2,"big")); wr.write(bytes([0]))
wr.varint(0); wr.varint(0)
wr.write(bytes([0])); wr.varint(0); wr.varint(0)
w.load_chunk_data(0, 0, wr.getvalue())
hit = w.block_at(0, MIN_Y, 0) # section 0 的首个方块
assert hit.source == "unknown-state", hit
assert "mod" in hit.describe(), hit.describe()
s = w.summary()
assert s.get("unknown_state_count") == 1, s
assert "mod" in s.get("warning",""), s
print(f" ✓ ★ mod 方块可见降级: {hit.describe()[:40]}… + 世界摘要含告警")
def test_multiple_chunks():
"""多区块聚合:跨区块查询各自命中自己的列。"""
w = World(reg())
wr = Writer()
for _ in range(24):
wr.write((0).to_bytes(2,"big")); wr.write(bytes([0]))
wr.varint(0); wr.varint(0)
wr.write(bytes([0])); wr.varint(0); wr.varint(0)
data = wr.getvalue()
for cx, cz in [(0,0), (1,0), (-1,-1), (2,-3)]:
w.load_chunk_data(cx, cz, data)
assert w.loaded_count == 4, w.loaded_count
# (16,64,0) 属于区块 (1,0)
assert w.block_at(16,64,0).loaded
# (-1,64,-1) 属于区块 (-1,-1)
assert w.block_at(-1,64,-1).loaded
# (32,64,-48) 属于区块 (2,-3)
assert w.block_at(32,64,-48).loaded
# (16,64,-1) 属于区块 (1,-1) —— 未加载
assert w.block_at(16,64,-1).source == "unloaded"
print(" ✓ 多区块聚合: 4 列,跨区块/负坐标定位均正确,未加载区可辨")
# 卸载
w.unload_chunk(1, 0)
assert w.block_at(16,64,0).source == "unloaded"
print(" ✓ 区块卸载后查询回到 unloaded")
if __name__ == "__main__":
for fn in [test_negative_coords, test_unloaded_vs_air_distinction, test_real_chunk_via_world,
test_world_height_bounds, test_unknown_state_reported, test_multiple_chunks]:
fn()
print("\n世界模型:全部通过")