Files
cobblemon-survival-pack/tools/quick_check.py
T
claudeandClaude Opus 5.5 5d275e4e14 Phase 4: regenerating ore veins, each tier in one zone
Regenerating Ore Veins 1.4.0 (CurseForge only, MIT). tools/zone_ores.py
writes its config from world/zones.json: surface zones as boxes from the
zone grid, cave tiers by cave biome, 21 tiers including Cobblemon's
evolution stones (their natural ores are off). The world report now
allows ore only in tracked veins inside their tier's zone. Self-tests:
a mined vein regrows after its chunk unloads and after a restart (new
after_restart pass in selftest.py and quick_check.py). make_world places
showcase veins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 02:30:34 +01:00

139 lines
6.9 KiB
Python

#!/usr/bin/env python3
"""Fast check after a change: one server boot, a sampled world, no full template.
1. Prepare the test server (mods, dependency check) and boot a fresh world
"quick-check" with the recipe's seed, world type and gamerules.
2. Run the smoke checks (tools/smoke_checks.json) and the companion mod's
in-game tests (`/cobblesurvival selftest`, e.g. the permadeath matrix; they
play on a platform high above spawn).
3. `/locate` every structure in world/world.json → quick_check.absent_structures
and fail if one is found (villages, ocean ruins, ... must not generate).
4. `/locate` the sample structures (quick_check.structures) and pregenerate a
square of structure_radius around each, plus spawn_radius around spawn.
5. Force-load the chunks around the sampled village for a few seconds, as a
nearby player would, so Lootr converts its loot chests.
6. Stop, scan the server log, boot the same world again and run the second
halves of the self-tests that span a restart (`selftest run after_restart`,
e.g. a mined ore vein regrows), stop and scan that log too.
7. Run tools/world_report.py --sample on the sampled world: ore only in
regenerating veins inside their tier's zone, no vanilla mobs or mob sources,
only listed biomes, gamerules as in the recipe, and no vanilla loot chest
left near the village.
The border, strongholds-inside-border and the map need the whole island:
tools/make_world.py checks those when the template is rebuilt.
Usage: tools/quick_check.py [--accept-eula] (run tools/build.py first)
"""
import argparse
import json
import re
import shutil
import subprocess
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from mcserver import ROOT, SELFTEST_JVM_ARGS, SERVER, Server, chunky, eula_accepted, prepare, scan_log # noqa: E402
LEVEL = "quick-check"
FOUND = re.compile(r"is at \[(-?\d+), [^,\]]+, (-?\d+)\]")
def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--accept-eula", action="store_true", help="write eula=true for the test server")
args = parser.parse_args()
if args.accept_eula:
(SERVER / "eula.txt").parent.mkdir(parents=True, exist_ok=True)
(SERVER / "eula.txt").write_text("eula=true\n")
elif not eula_accepted():
sys.exit("build/server/eula.txt does not accept the Minecraft EULA yet; pass --accept-eula if you accept it.")
started = time.time()
recipe = json.loads((ROOT / "world/world.json").read_text())
sample = recipe["quick_check"]
args_file = prepare()
shutil.rmtree(SERVER / LEVEL, ignore_errors=True)
props = {"level-name": LEVEL, "level-seed": recipe["seed"], "level-type": recipe["level_type"]}
failures, village = [], None
with chunky(recipe), Server(args_file, props, heap="8G", stdout_name="quick-check-stdout.log",
jvm_args=SELFTEST_JVM_ARGS) as server:
try:
print(f"server started in {server.wait_ready():.0f}s")
for rule, value in recipe.get("gamerules", {}).items():
server.command(f"gamerule {rule} {str(value).lower()}")
failures += server.run_checks()
failures += server.run_selftest()
for structure in sample["absent_structures"]:
reply = server.command(f"locate structure {structure}").strip()
found = FOUND.search(reply)
print(f" [{'FAIL' if found else 'ok'}] {structure} absent: {reply[:120]!r}")
if found:
failures.append(f"{structure} generates but should not: {reply}")
areas = [("spawn", 0, 0, sample["spawn_radius"])]
for structure in sample["structures"]:
reply = server.command(f"locate structure {structure}").strip()
found = FOUND.search(reply)
if not found:
print(f" note: {structure} not found near spawn, not sampled ({reply[:80]!r})")
continue
areas.append((structure, int(found[1]), int(found[2]), sample["structure_radius"]))
for name, x, z, radius in areas:
seconds = server.pregenerate(x, z, radius, report_every=0)
print(f" sampled {name} at {x},{z} (radius {radius}) in {seconds:.0f}s")
village = next(((x, z, r) for name, x, z, r in areas if name == "#minecraft:village"), None)
if village:
x, z, r = village
server.command(f"forceload add {x - r} {z - r} {x + r} {z + r}")
time.sleep(5)
server.command(f"forceload remove {x - r} {z - r} {x + r} {z + r}")
server.command("save-all flush")
server.stop()
except Exception as e: # report; the context manager still kills the process
failures.append(f"{type(e).__name__}: {e}")
failures += [f"log: {line}" for line in scan_log(server.log)]
failures += restart_selftest(args_file, props)
report = subprocess.run([sys.executable, "-I", str(ROOT / "tools/world_report.py"), str(SERVER / LEVEL),
"--sample", "--json", str(ROOT / "build/quick-check-report.json")])
if report.returncode:
failures.append("world report failed (see above)")
if village:
# Lootr only converts a chest once the 5x5 chunks around it are loaded, so check the inner part.
x, z, r = village
inner = r - 32
loot = json.loads((ROOT / "build/quick-check-report.json").read_text())["loot_containers"]
left = [p for p in loot["unconverted_at"] if abs(p[0] - x) <= inner and abs(p[2] - z) <= inner]
print(f" loot containers: {loot['lootr']} Lootr, {len(left)} vanilla left near the village")
if left:
failures.append(f"Lootr did not convert {len(left)} loot chests near the village: {left[:5]}")
print(f"\nquick check took {time.time() - started:.0f}s")
if failures:
print("QUICK CHECK FAILED")
print("\n".join(f" - {f}" for f in failures))
sys.exit(1)
print("QUICK CHECK PASSED")
def restart_selftest(args_file, props):
"""Boot the quick-check world again and run the self-tests' after-restart halves; returns the failures."""
failures = []
with Server(args_file, props, heap="8G", stdout_name="quick-check-restart-stdout.log",
jvm_args=SELFTEST_JVM_ARGS) as server:
try:
print(f"server restarted in {server.wait_ready():.0f}s")
failures += server.run_selftest(after_restart=True)
server.command("save-all flush")
server.stop()
except Exception as e: # report; the context manager still kills the process
failures.append(f"{type(e).__name__}: {e}")
return failures + [f"log after restart: {line}" for line in scan_log(server.log)]
if __name__ == "__main__":
main()