Cobblemon Survival Pack: phases 0-2 and Phase 3 zones and hazards

Free NeoForge 1.21.1 modpack on Cobblemon 1.8.4 built with packwiz, plus
the Kotlin companion mod (cobblesurvival) and its built-in data pack.

- Phase 0: packwiz pack with Modrinth/CurseForge cross-links, build and
  export tooling, Prism instance sync, test server with smoke checks.
- Phase 1: Poke Ball shaped island (WorldGen Editor preset, seed isle-26),
  Adrian's biome list, no natural ore, structures without ore or vanilla
  mobs, no Nether portals, world template builder and world report.
- Phase 2: permadeath (CobbleHardcoreMon plus the companion mod's gap
  fixes), Revival Charm, expensive Healing Machine, Lootr refreshing
  loot, in-game self-tests on Minecraft's GameTest framework.
- Phase 3 so far: 14 zones with their own spawn pools and level bands
  (tools/zone_layout.py, tools/zone_spawns.py, "zone" spawn condition),
  Cold Sweat heat and cold as gates with boss-drop insulation recipes,
  spawn survey and hazard self-tests in tools/quick_check.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
claudeandClaude Opus 5.5 committed 2026-10-29 19:01:21 +01:00
commit 398301f0be
232 files changed
+72665

No files matched your search

+118
View File
@@ -0,0 +1,118 @@
#!/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, and run tools/world_report.py --sample on the
sampled world: no ore, 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)]
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")
if __name__ == "__main__":
main()