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:
claudeandClaude Opus 5.5 committed 2026-11-09 16:26:30 +01:00
1 parent 0741c84077
commit 4083791944
133 files changed
+8350 -3273

No files matched your search

+6 -4
View File
@@ -12,7 +12,6 @@ writes, under datapacks/core/data/cobblesurvival/:
and the sigil's texture and item model in the companion mod. The fights are written by tools/zone_bosses.py, so run that
afterwards, and tools/jei_sources.py and tools/quests.py after it.
Needs the Cobblemon jar in build/server/mods (any test-server run), for the names.
Usage: tools/summons.py
"""
@@ -23,6 +22,8 @@ from pathlib import Path
from PIL import Image
import lock
ROOT = Path(__file__).resolve().parent.parent
DATA = ROOT / "datapacks/core/data/cobblesurvival"
ASSETS = ROOT / "mod/src/main/resources/assets/cobblesurvival"
@@ -65,7 +66,7 @@ def main():
table = json.loads((ROOT / "world/summons.json").read_text())
plan = json.loads((ROOT / "world/zones.json").read_text())
zones = {z["id"]: z["name"] for z in plan["zones"] + plan["caves"] + plan.get("dimensions", [])}
species_names = names(next((ROOT / "build/server/mods").glob("Cobblemon-*.jar")))
species_names = names(lock.mod_jar("cobblemon"))
summons, recipes = DATA / "summons", DATA / "recipe"
summons.mkdir(parents=True, exist_ok=True)
@@ -74,9 +75,10 @@ def main():
comment = "Generated by tools/summons.py from world/summons.json; do not edit by hand."
for species, s in table["summons"].items():
tier = table["tiers"][str(s["tier"])]
name = species_names[species]
form = s.get("form")
name = f"{form.title()} {species_names[s['species']]}" if form else species_names[species]
(summons / f"{species}.json").write_text(json.dumps(
{"_comment": comment, "species": species, "level": tier["level"], "zone": s["zone"]}, indent=1) + "\n")
{"_comment": comment, "species": species, "pokemon": f"{s['species']} {form}" if form else species, "level": tier["level"], "zone": s["zone"]}, indent=1) + "\n")
lore = f"Use in {zones[s['zone']]} to summon a Lv {tier['level']} {name}"
(recipes / f"summon_{species}.json").write_text(json.dumps({
"_comment": comment, "type": "minecraft:crafting_shaped", "category": "misc",