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
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Prove the build reproducible: build the working tree twice from clean copies in different folders, compare the bytes.
|
||||
|
||||
Copies every tracked and untracked file (not what .gitignore hides) to build/reproducible/a and build/reproducible/the/other/b,
|
||||
so a leaked path shows up. Each build gets its own Gradle home with --fresh-gradle (slower: Loom sets up Minecraft
|
||||
twice); the pinned tools are shared, since they're checked by hash. On a mismatch it names the jar or pack entries that differ.
|
||||
|
||||
Usage: tools/check_reproducible.py [--fresh-gradle]
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import lock
|
||||
|
||||
ROOT = lock.ROOT
|
||||
WORK = ROOT / "build/reproducible"
|
||||
|
||||
|
||||
def build(copy, fresh_gradle):
|
||||
shutil.rmtree(copy, ignore_errors=True)
|
||||
files = subprocess.run(["git", "ls-files", "-co", "--exclude-standard", "-z"], cwd=ROOT, check=True,
|
||||
capture_output=True, text=True).stdout.split("\0")
|
||||
for name in filter(None, files):
|
||||
if (ROOT / name).is_file():
|
||||
target = copy / name
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(ROOT / name, target)
|
||||
env = os.environ | {"COBBLESURVIVAL_CACHE": str(lock.CACHE)}
|
||||
if fresh_gradle:
|
||||
env["GRADLE_USER_HOME"] = str(copy.parent / f"{copy.name}-gradle")
|
||||
with open(copy.parent / f"{copy.name}.log", "w") as log:
|
||||
subprocess.run([sys.executable, str(copy / "tools/build.py")], cwd=copy, env=env, stdout=log, stderr=subprocess.STDOUT, check=True)
|
||||
return dict(reversed(line.split(" ", 1)) for line in (copy / "build/SHA256SUMS").read_text().splitlines())
|
||||
|
||||
|
||||
def entries(path):
|
||||
with zipfile.ZipFile(path) as z:
|
||||
return {i.filename: (i.CRC, i.date_time, i.external_attr) for i in z.infolist()}
|
||||
|
||||
|
||||
def explain(a, b, name):
|
||||
if not name.endswith((".jar", ".mrpack", ".zip")):
|
||||
return
|
||||
ea, eb = entries(a / name), entries(b / name)
|
||||
for entry in sorted(ea.keys() | eb.keys()):
|
||||
if ea.get(entry) != eb.get(entry):
|
||||
print(f" {entry}: {ea.get(entry)} / {eb.get(entry)}")
|
||||
if list(ea) != list(eb):
|
||||
print(" entries are in a different order")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument("--fresh-gradle", action="store_true", help="give each build its own empty Gradle home")
|
||||
args = parser.parse_args()
|
||||
a, b = WORK / "a", WORK / "the/other/b"
|
||||
first, second = build(a, args.fresh_gradle), build(b, args.fresh_gradle)
|
||||
different = [name for name in sorted(first.keys() | second.keys()) if first.get(name) != second.get(name)]
|
||||
for name in sorted(first):
|
||||
print(f"{'DIFFERENT' if name in different else 'same '} {first[name][:16]} {name}")
|
||||
for name in different:
|
||||
print(f" {name}:")
|
||||
explain(a, b, name)
|
||||
if different:
|
||||
sys.exit(f"{len(different)} of {len(first)} outputs differ")
|
||||
print(f"All {len(first)} outputs are byte-identical.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user