Phase 9: reproducible builds, balance from the server, spark check, play feedback
Build: every tool pinned (JDK, packwiz, Gradle plugins, Python packages), exports normalised, tools/check_reproducible.py proves two clean builds are byte-identical. Server-side balance: a server can load config/cobblesurvival/datapack.zip in place of the jar's data, /reload applies it, tools/server_update.py packages it and refuses when a client change is needed. JEI drop pages are sent by the server. Code-held balance numbers moved to tuning/server.json. Performance: spark in the pack (server only), tools/perf_test.py with fake players whose chunks really are active, docs/performance.md. Adrian's feedback: default keys R, U, J, §; drop pages link to Cobblemon Info and show real chances; bosses only hurt in battles; battle XP halved; channels between the islands low level; temperature ×3 with insulation doubled; hazards start at once; no item dots on the map; a wild attack starts a battle; the starter only faints; quest book on join with a basics page and an HMs page; regional forms obtainable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0741c84077
commit
4083791944
133 files changed
+8350
-3273
No files matched your search
+103
@@ -0,0 +1,103 @@
|
||||
"""The build's pinned tools (tools/build-lock.toml, tools/requirements.txt) and the environment it runs in.
|
||||
|
||||
Each tool is downloaded once into build/.cache and checked against its hash, so what a build runs depends on the
|
||||
repo, not on what the machine has installed. COBBLESURVIVAL_CACHE moves the cache (tools/check_reproducible.py shares
|
||||
one between its builds: everything in it is checked by hash, so it can't change their result).
|
||||
"""
|
||||
import hashlib
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tomllib
|
||||
import urllib.request
|
||||
import venv
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
LOCK = tomllib.loads((ROOT / "tools/build-lock.toml").read_text())
|
||||
CACHE = Path(os.environ.get("COBBLESURVIVAL_CACHE", ROOT / "build/.cache"))
|
||||
# Nothing the build writes may depend on the machine's clock zone, language or Python's per-run hash seed.
|
||||
BUILD_ENV = {"TZ": "UTC", "LC_ALL": "C.UTF-8", "PYTHONHASHSEED": "0", "PYTHONUTF8": "1"}
|
||||
|
||||
|
||||
def digest(path, algorithm="sha256"):
|
||||
return hashlib.new(algorithm, Path(path).read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def fetch(url, dest, expected, algorithm="sha256"):
|
||||
"""[dest], downloaded from [url] if it isn't there yet; exits if the file is not the one pinned."""
|
||||
if dest.exists() and digest(dest, algorithm) == expected:
|
||||
return dest
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
part = dest.with_name(dest.name + ".part")
|
||||
request = urllib.request.Request(url, headers={"User-Agent": "cobblemon-survival-pack build"})
|
||||
with urllib.request.urlopen(request, timeout=300) as response, open(part, "wb") as out:
|
||||
shutil.copyfileobj(response, out)
|
||||
if digest(part, algorithm) != expected:
|
||||
part.unlink()
|
||||
sys.exit(f"{url} does not match the pinned {algorithm} {expected}")
|
||||
part.replace(dest)
|
||||
return dest
|
||||
|
||||
|
||||
def download(name):
|
||||
"""A file listed under [downloads] in tools/build-lock.toml."""
|
||||
entry = LOCK["downloads"][name]
|
||||
return fetch(entry["url"], CACHE / name, entry["sha256"])
|
||||
|
||||
|
||||
def mod_jar(slug):
|
||||
"""A mod's jar as pack/mods/<slug>.pw.toml pins it, for the tools that read a mod's own data."""
|
||||
meta = tomllib.loads((ROOT / f"pack/mods/{slug}.pw.toml").read_text())
|
||||
file = meta["download"]
|
||||
return fetch(file["url"], CACHE / "mods" / meta["filename"], file["hash"], file["hash-format"])
|
||||
|
||||
|
||||
def jdk():
|
||||
"""The pinned JDK's home, the only one Gradle gets to compile with."""
|
||||
pin = LOCK["jdk"]
|
||||
here = f"{platform.system().lower()}-{platform.machine()}"
|
||||
if here != pin["platform"]:
|
||||
sys.exit(f"tools/build-lock.toml pins a JDK for {pin['platform']}, this machine is {here}")
|
||||
home = CACHE / f"jdk-{pin['version']}"
|
||||
if not (home / "bin/javac").exists():
|
||||
archive = fetch(pin["url"], CACHE / f"jdk-{pin['version']}.tar.gz", pin["sha256"])
|
||||
unpacking = home.with_name(home.name + ".part")
|
||||
shutil.rmtree(unpacking, ignore_errors=True)
|
||||
unpacking.mkdir(parents=True)
|
||||
with tarfile.open(archive) as tar:
|
||||
tar.extractall(unpacking, filter="tar")
|
||||
shutil.rmtree(home, ignore_errors=True)
|
||||
next(unpacking.iterdir()).rename(home)
|
||||
unpacking.rmdir()
|
||||
return home
|
||||
|
||||
|
||||
def packwiz():
|
||||
"""The pinned packwiz, built from its pinned commit with the pinned Go."""
|
||||
pin = LOCK["packwiz"]
|
||||
binary = CACHE / f"packwiz-{pin['version']}" / "packwiz"
|
||||
if not binary.exists():
|
||||
if not shutil.which("go"):
|
||||
sys.exit("building the pinned packwiz needs Go (https://go.dev/dl/)")
|
||||
env = os.environ | {"GOBIN": str(binary.parent), "GOTOOLCHAIN": pin["go"], "GOFLAGS": "-trimpath"}
|
||||
subprocess.run(["go", "install", f"{pin['module']}@{pin['version']}"], env=env, check=True)
|
||||
return binary
|
||||
|
||||
|
||||
def enter_venv():
|
||||
"""Re-runs the calling script in a private venv with exactly tools/requirements.txt and the build's environment."""
|
||||
requirements = ROOT / "tools/requirements.txt"
|
||||
home = CACHE / f"venv-{digest(requirements)[:12]}"
|
||||
if Path(sys.prefix) == home and all(os.environ.get(k) == v for k, v in BUILD_ENV.items()):
|
||||
return
|
||||
if f"{sys.version_info[0]}.{sys.version_info[1]}" != LOCK["python"]:
|
||||
print(f"warning: tools/build-lock.toml pins Python {LOCK['python']}, this is {sys.version.split()[0]}", file=sys.stderr)
|
||||
python = home / "bin/python"
|
||||
if not python.exists():
|
||||
venv.create(home, with_pip=True)
|
||||
subprocess.run([python, "-m", "pip", "install", "--quiet", "--require-hashes", "-r", requirements], check=True)
|
||||
os.execve(python, [str(python), sys.argv[0], *sys.argv[1:]], os.environ | BUILD_ENV)
|
||||
Reference in new issue
Block a user