"""Offline tests for b14.py (temp dirs only; no game, no git writes).""" import gzip import json import struct import sys import tempfile import unittest import zipfile import zlib from pathlib import Path HERE = Path(__file__).resolve().parent sys.path.insert(0, str(HERE.parent)) sys.path.insert(0, str(HERE.parent.parent)) import b14 # noqa: E402 import bench # noqa: E402 import nbt # noqa: E402 AM_PARTIAL = """# partial [spawning] \tgazelleSpawnWeight = 20 \tcapuchinMonkeySpawnWeight = 14 \telephantSpawnWeight = 15 \tstraddlerSpawnWeight = 35 \trockyRollerSpawnWeight = 30 \tunderminerSpawnWeight = 25 \tblobfishSpawnWeight = 15 \tmudskipperSpawnWeight = 14 \tcaimanSpawnWeight = 15 """ FP_PARTIAL = """// partial { "duckSpawnWeight": 2, "duckMaxGroupSize": 4, "gooseSpawnWeight": 2, "gooseMaxGroupSize": 3, "gullSpawnWeight": 3, "gullMaxGroupSize": 5, "sparrowSpawnWeight": 12, "sparrowMaxGroupSize": 4 } """ CAC_PARTIAL = """{ "configVersion": 2, "spawning": { "leaf_insect": [ {"biome_spec": "#c:is_jungle", "weight": 8, "min": 1, "max": 1}, {"biome_spec": "#c:is_forest", "weight": 4, "min": 1, "max": 1} ], "sea_bunny": [ {"biome_spec": "minecraft:ocean", "weight": 10, "min": 1, "max": 2}, {"biome_spec": "minecraft:warm_ocean", "weight": 20, "min": 1, "max": 3} ], "dragonfly": [ {"biome_spec": "#c:is_river", "weight": 4, "min": 1, "max": 1}, {"biome_spec": "#c:is_lush", "weight": 6, "min": 1, "max": 1} ] } } """ SHIPPED = {"alexsmobs-common.toml": AM_PARTIAL, "fowlplay.json5": FP_PARTIAL, "crittersandcompanions-common.json5": CAC_PARTIAL} # What the mods write back (re-saved, json5 unquoted keys, tabs) with default values. AM_RESAVED_DEFAULT = "[spawning]\n" + "".join( f"\t#Spawn Weight\n\t# Default: {v}\n\t{k} = {v}\n" for k, v in b14.EXPECT_DEFAULTS["alexsmobs-common.toml"].items()) FP_RESAVED_DEFAULT = "{\n" + ",\n".join( f"\t{k}: {v}" for k, v in b14.EXPECT_DEFAULTS["fowlplay.json5"].items()) + "\n}" CAC_RESAVED_DEFAULT = """{ \tconfigVersion: 2, \tspawning: { \t\tleaf_insect: [ \t\t\t{ \t\t\t\tbiome_spec: "#c:is_jungle", \t\t\t\tweight: 14, \t\t\t\tmin: 1, \t\t\t\tmax: 1 \t\t\t}, \t\t\t{ biome_spec: "#c:is_forest", weight: 6, min: 1, max: 1 } \t\t], \t\tred_panda: [ { biome_spec: "#c:is_jungle", weight: 8, min: 1, max: 2 } ], \t\tsea_bunny: [ \t\t\t{ biome_spec: "minecraft:ocean", weight: 16, min: 1, max: 2 }, \t\t\t{ biome_spec: "minecraft:warm_ocean", weight: 32, min: 1, max: 4 } \t\t], \t\tdragonfly: [ \t\t\t{ biome_spec: "#c:is_river", weight: 4, min: 1, max: 1 }, \t\t\t{ biome_spec: "#c:is_lush", weight: 10, min: 1, max: 1 } \t\t] \t} }""" class ConfigVariants(unittest.TestCase): def test_render(self): d = b14.render_variant("defaults", SHIPPED) self.assertEqual(set(d), set(b14.SPAWN_FILES)) self.assertTrue(all(v is None for v in d.values())) s = b14.render_variant("shipped", SHIPPED) self.assertEqual(s["fowlplay.json5"], FP_PARTIAL) with self.assertRaises(ValueError): b14.render_variant("nope", SHIPPED) def test_extract_partial_and_resaved(self): keys = b14.EXPECT_DEFAULTS got = b14.extract_values("crittersandcompanions-common.json5", CAC_PARTIAL, keys["crittersandcompanions-common.json5"]) self.assertEqual(got["sea_bunny@minecraft:warm_ocean"], 20) self.assertEqual(got["leaf_insect@#c:is_jungle"], 8) self.assertEqual(got["dragonfly@#c:is_lush"], 6) got = b14.extract_values("crittersandcompanions-common.json5", CAC_RESAVED_DEFAULT, keys["crittersandcompanions-common.json5"]) self.assertEqual(got, keys["crittersandcompanions-common.json5"]) self.assertEqual(b14.extract_values("fowlplay.json5", FP_RESAVED_DEFAULT, keys["fowlplay.json5"]), keys["fowlplay.json5"]) self.assertEqual(b14.extract_values("alexsmobs-common.toml", AM_RESAVED_DEFAULT, keys["alexsmobs-common.toml"]), keys["alexsmobs-common.toml"]) # "Default: 40" comment lines must not be mistaken for the value self.assertEqual(b14.extract_values("alexsmobs-common.toml", AM_PARTIAL, ["gazelleSpawnWeight"]), {"gazelleSpawnWeight": 20}) def test_check_config_and_journaled_apply(self): with tempfile.TemporaryDirectory() as td: game = Path(td) (game / "config").mkdir() originals = {"alexsmobs-common.toml": AM_RESAVED_DEFAULT + "# orig\n", "fowlplay.json5": FP_RESAVED_DEFAULT} # CAC absent on purpose for n, t in originals.items(): (game / "config" / n).write_text(t) j = bench.Journal.begin(game, "b14-1") b14.apply_spawn_config(game, j, b14.render_variant("shipped", SHIPPED)) for n in b14.SPAWN_FILES: self.assertEqual((game / "config" / n).read_text(), SHIPPED[n]) self.assertTrue(b14.check_config(game, "shipped", SHIPPED)["ok"]) chk = b14.check_config(game, "defaults", SHIPPED) self.assertFalse(chk["ok"]) self.assertIn("fowlplay.json5:duckSpawnWeight want 4 got 2", chk["mismatch"]) self.assertEqual(bench.restore_pending(game), []) for n, t in originals.items(): self.assertEqual((game / "config" / n).read_text(), t) self.assertFalse((game / "config" / "crittersandcompanions-common.json5").exists()) self.assertFalse((game / bench.PENDING).exists()) def test_defaults_delete_and_restore(self): with tempfile.TemporaryDirectory() as td: game = Path(td) (game / "config").mkdir() for n in b14.SPAWN_FILES: (game / "config" / n).write_text("x" + n) j = bench.Journal.begin(game, "b14-2") b14.apply_spawn_config(game, j, b14.render_variant("defaults", SHIPPED)) self.assertFalse(any((game / "config" / n).exists() for n in b14.SPAWN_FILES)) self.assertTrue(b14.check_config(game, "defaults", SHIPPED)["ok"]) # absent = defaults self.assertFalse(b14.check_config(game, "shipped", SHIPPED)["ok"]) # what the mods write after load (game / "config" / "alexsmobs-common.toml").write_text(AM_RESAVED_DEFAULT) (game / "config" / "fowlplay.json5").write_text(FP_RESAVED_DEFAULT) (game / "config" / "crittersandcompanions-common.json5").write_text(CAC_RESAVED_DEFAULT) self.assertTrue(b14.check_config(game, "defaults", SHIPPED)["ok"]) bench.restore_pending(game) for n in b14.SPAWN_FILES: self.assertEqual((game / "config" / n).read_text(), "x" + n) def test_config_diff(self): with tempfile.TemporaryDirectory() as td: game = Path(td) (game / "config").mkdir() (game / "config" / "fowlplay.json5").write_text(FP_RESAVED_DEFAULT) txt = b14.config_diff(game, b14.render_variant("shipped", SHIPPED), SHIPPED) self.assertIn("duckSpawnWeight: 4 -> 2 <- changes", txt) self.assertIn("config/alexsmobs-common.toml: WRITE", txt) txt = b14.config_diff(game, b14.render_variant("defaults", SHIPPED), SHIPPED) self.assertIn("config/fowlplay.json5: DELETE", txt) SITES = [("river", {"x": 904, "y": 63, "z": 88}), ("warm_ocean", {"x": -200, "y": 63, "z": 56})] class Datapack(unittest.TestCase): def setUp(self): self.files = b14.generate_datapack("b14-20261018000000", SITES, 2, 20) self.base = "data/b14/function/b14-20261018000000" def test_structure(self): f = self.files self.assertEqual(json.loads(f["pack.mcmeta"])["pack"]["pack_format"], 48) self.assertEqual(json.loads(f["data/minecraft/tags/function/load.json"]), {"values": ["b14:b14-20261018000000/load"]}) begin = f[f"{self.base}/begin.mcfunction"] self.assertIn("gamerule doMobSpawning true", begin) self.assertIn("gamemode creative @a", begin) self.assertNotIn("spectator", "".join(f.values())) self.assertIn("step0 400t", begin) # 2 sites x (tp, arrive, t=0..2) + done steps = [k for k in f if "/step" in k] self.assertEqual(len(steps), 2 * (2 + 3) + 1) self.assertIn("B14_DONE", f[f"{self.base}/step{len(steps) - 1}.mcfunction"]) def test_counts_and_chain(self): count = self.files[f"{self.base}/count.mcfunction"] for _, eid in b14.TUNED + b14.VANILLA: self.assertIn(f"@e[type={eid},distance=..128]", count) self.assertIn("@e[type=!minecraft:player,distance=..128]", count) self.assertEqual(count.count("+= #"), len(b14.TUNED)) tl = b14.build_timeline("b14-20261018000000", SITES, 2, 20) delays = [d for _, d in tl] self.assertEqual(delays[:5], [100, 200, 1200, 1200, 20]) self.assertEqual(delays[-1], 0) for i in range(len(tl) - 1): body = self.files[f"{self.base}/step{i}.mcfunction"] self.assertIn(f"schedule function b14:b14-20261018000000/step{i + 1} ", body) s2 = self.files[f"{self.base}/step2.mcfunction"] self.assertIn("setblock 904 62 88 minecraft:barrier", s2) self.assertIn("tp @a 904.5 63 88.5", s2) self.assertIn("if biome ~ ~ ~ minecraft:river", s2) def test_tellraw_roundtrip(self): """The rendered tellraw text, with scores substituted, parses back.""" cmd = b14.tellraw("b14-20261018000000", "river", 3) parts = json.loads(cmd[len("tellraw @a "):]) text = "".join(p if isinstance(p, str) else p.get("text", "7") for p in parts) line = ("[18Oct2026 00:10:00.000] [Render thread/INFO] [net.minecraft.client.gui." f"components.ChatComponent/]: [System] [CHAT] {text}") r = b14.parse_count_line(line) self.assertEqual((r["sid"], r["biome"], r["t"], r["total"], r["biome_ok"]), ("b14-20261018000000", "river", 3, 7, 7)) self.assertEqual(set(r["counts"]), {eid for _, eid in b14.TUNED + b14.VANILLA}) def test_world_build(self): with tempfile.TemporaryDirectory() as td: td = Path(td) zp = td / "w.zip" with zipfile.ZipFile(zp, "w") as z: z.writestr("onionbench/level.dat", b"lvl") z.writestr("onionbench/region/r.0.0.mca", b"x") z.writestr("onionbench/data/chunks.dat", b"x") z.writestr("onionbench/datapacks/onion-bench/pack.mcmeta", "{}") z.writestr("onionbench/serverconfig/a.toml", "a") game = td / "game" (game / "saves").mkdir(parents=True) j = bench.Journal.begin(game, "b14-3") root = b14.build_world(game, j, zp, self.files) self.assertEqual((root / "level.dat").read_bytes(), b"lvl") self.assertFalse((root / "region").exists()) self.assertFalse((root / "data").exists()) self.assertTrue((root / "serverconfig/a.toml").exists()) self.assertTrue((root / "datapacks" / b14.PACK / "pack.mcmeta").exists()) with self.assertRaises(ValueError): b14.build_world(game, bench.Journal(game, "x"), zp, self.files) bench.restore_pending(game) self.assertFalse(root.exists()) def _line(sid, biome, t, total, counts, ok=1): body = " ".join(f"{k}={v}" for k, v in counts.items()) return (f"[x] [Render thread/INFO] [ChatComponent/]: [System] [CHAT] B14 {sid} {biome} " f"t={t} biome_ok={ok} total={total} nonitem={total} tuned=0 {body}") class ParseAndSummary(unittest.TestCase): def test_parse_dedupes_and_filters(self): lines = [_line("b14-1", "river", 0, 5, {"fowlplay:duck": 1}), _line("b14-1", "river", 0, 5, {"fowlplay:duck": 1}), "[Server thread/INFO]: [Server] B14_ARRIVE b14-1 river 1 2 3", _line("b14-2", "river", 0, 9, {"fowlplay:duck": 2}), "noise B14 garbage"] rows = b14.parse_lines(lines) self.assertEqual(len(rows), 2) self.assertEqual(len(b14.parse_lines(lines, "b14-2")), 1) self.assertEqual(rows[0]["counts"], {"fowlplay:duck": 1}) def _rows(self, config, sid, biome, totals, duck): out = [] for t, (tot, d) in enumerate(zip(totals, duck)): r = b14.parse_count_line(_line(sid, biome, t, tot, {"fowlplay:duck": d, "minecraft:cow": 1})) out.append({**r, "config": config, "kind": "b14_count"}) return out def _sess(self, sid, ok=True): return {"kind": "b14_session", "sid": sid, "outcome": "ok", "config_check": {"ok": ok}} def test_keep_and_back_off(self): counts = (self._rows("defaults", "b14-1", "river", [10, 40], [2, 6]) + self._rows("shipped", "b14-2", "river", [8, 25], [0, 3])) sess = [self._sess("b14-1"), self._sess("b14-2")] out, verdicts, overall = b14.summarize(counts, sess) self.assertEqual(verdicts["river"]["verdict"], "keep") self.assertEqual(out["river"]["shipped"]["seen"]["fowlplay:duck"], 3) self.assertEqual(overall, "incomplete") # jungle / warm_ocean missing counts2 = (self._rows("defaults", "b14-1", "river", [10, 40], [2, 6]) + self._rows("shipped", "b14-2", "river", [8, 25], [0, 0])) _, v2, o2 = b14.summarize(counts2, sess) self.assertEqual(v2["river"]["verdict"], "back off") self.assertEqual(v2["river"]["disappeared"], ["fowlplay:duck"]) self.assertIn("back off", b14.render_summary(*b14.summarize(counts2, sess))) def test_bad_sessions_and_biome_check_excluded(self): counts = (self._rows("defaults", "b14-1", "river", [10, 40], [2, 6]) + self._rows("shipped", "b14-2", "river", [8, 25], [1, 3])) _, v, _ = b14.summarize(counts, [self._sess("b14-1"), self._sess("b14-2", ok=False)]) self.assertEqual(v["river"]["verdict"], "incomplete") for r in counts: r["biome_ok"] = 0 _, v, _ = b14.summarize(counts, [self._sess("b14-1"), self._sess("b14-2")]) self.assertTrue(v["river"]["verdict"].startswith("invalid")) def test_all_biomes_keep(self): counts, sess = [], [] for i, biome in enumerate(b14.BIOMES): counts += self._rows("defaults", f"b14-{i}0", biome, [5, 30], [1, 2]) counts += self._rows("shipped", f"b14-{i}1", biome, [5, 20], [1, 1]) sess += [self._sess(f"b14-{i}0"), self._sess(f"b14-{i}1")] self.assertEqual(b14.summarize(counts, sess)[2], "keep") # ---------------------------------------------------------------- nbt / locate def _tag(t, name, payload): n = name.encode() return bytes([t]) + struct.pack(">H", len(n)) + n + payload def _str(s): b = s.encode() return struct.pack(">H", len(b)) + b def _compound(*items): return b"".join(items) + b"\x00" def _chunk_nbt(biome, height=70, status="minecraft:full"): hm_longs = [] vals = [height + 64] * 256 # stored relative to min_y -64, 9 bits, 7 per long for i in range(0, 256, 7): w = 0 for k, v in enumerate(vals[i:i + 7]): w |= v << (9 * k) hm_longs.append(w if w < 2 ** 63 else w - 2 ** 64) hm = struct.pack(">i", len(hm_longs)) + struct.pack(f">{len(hm_longs)}q", *hm_longs) section = _compound( _tag(nbt.BYTE, "Y", struct.pack(">b", (height - 1) >> 4)), _tag(nbt.COMPOUND, "biomes", _compound( _tag(nbt.LIST, "palette", bytes([nbt.STRING]) + struct.pack(">i", 1) + _str(biome))))) root = _compound( _tag(nbt.STRING, "Status", _str(status)), _tag(nbt.LIST, "sections", bytes([nbt.COMPOUND]) + struct.pack(">i", 1) + section), _tag(nbt.COMPOUND, "Heightmaps", _compound( _tag(nbt.LONG_ARRAY, "MOTION_BLOCKING_NO_LEAVES", hm)))) return bytes([nbt.COMPOUND]) + _str("") + root def _region(chunks): """chunks: {(lx, lz): nbt bytes} -> .mca bytes.""" header, body, sector = bytearray(8192), bytearray(), 2 for (lx, lz), raw in chunks.items(): data = zlib.compress(raw) blob = struct.pack(">I", len(data) + 1) + b"\x02" + data blob += b"\x00" * (-len(blob) % 4096) n = len(blob) // 4096 idx = lz * 32 + lx header[idx * 4:idx * 4 + 4] = sector.to_bytes(3, "big") + bytes([n]) body += blob sector += n return bytes(header) + bytes(body) class NbtAndLocate(unittest.TestCase): def test_packed(self): self.assertEqual(nbt.unpack_packed([0b10_01_00 | (3 << 62)], 2, 3), [0, 1, 2]) self.assertEqual(nbt.unpack_packed([-1], 4, 2), [15, 15]) def test_chunk_roundtrip(self): _, c = nbt.loads(_chunk_nbt("minecraft:river", 63)) self.assertEqual(nbt.heightmap(c)[0], 63) self.assertEqual(nbt.biome_at(c, 5, 62, 9), "minecraft:river") ld = gzip.compress(bytes([nbt.COMPOUND]) + _str("") + _compound( _tag(nbt.COMPOUND, "Data", _compound(_tag(nbt.INT, "SpawnX", struct.pack(">i", 7)))))) self.assertEqual(nbt.load_gzip(ld)[1]["Data"]["SpawnX"], 7) def test_scan_and_pick(self): reg = {} for lx in range(5): for lz in range(5): reg[(lx, lz)] = _chunk_nbt("minecraft:river" if lx < 3 else "minecraft:forest", 63) reg[(lx + 20, lz)] = _chunk_nbt("minecraft:warm_ocean", 63) reg[(lx + 10, lz + 20)] = _chunk_nbt("minecraft:jungle", 80, "minecraft:structure_starts") with tempfile.TemporaryDirectory() as td: zp = Path(td) / "w.zip" with zipfile.ZipFile(zp, "w") as z: z.writestr("w/level.dat", b"") z.writestr("w/region/r.0.0.mca", _region(reg)) chunks = b14.scan_world(zp) self.assertEqual(len(chunks), 50) # structure_starts placeholders skipped sites = b14.pick_sites(chunks, avoid=[(-1000, -1000)]) self.assertEqual(sites["river"]["biome"], "minecraft:river") self.assertEqual(sites["river"]["y"], 63) self.assertIn("warm_ocean", sites) self.assertNotIn("jungle", sites) self.assertGreaterEqual(abs(sites["warm_ocean"]["x"] - sites["river"]["x"]), 320) # avoid radius excludes everything near the avoid point self.assertEqual(b14.pick_sites(chunks, avoid=[(40, 40), (360, 40)]), {}) class Busy(unittest.TestCase): def test_ancestors_include_self(self): import os self.assertIn(os.getpid(), b14._ancestors(os.getpid())) self.assertNotIn(os.getpid(), b14.bench_busy()) if __name__ == "__main__": unittest.main()