Phase 5: breeding with Cobbreeding, Ditto a rare Deep Dark find

Adrian (2026-10-31): add Cobbreeding without a gate; only Ditto should
be rare, in an endgame area or gated behind something else.

- Cobbreeding 2.4.1 (Modrinth only) and Cloth Config (client) in the
  pack; Cobblebase's hatchery takes Cobbreeding's eggs.
- Ditto moved from Button Isle to the Deep Dark with both of Cobblemon's
  entries (rare in slime chunks, ultra-rare elsewhere): zone_spawns.py
  pins take {"zone", "all_entries"}; Deep Dark target +1, Button Isle -1
  so no other line moved; Southwold and Frostpeak guardians pinned (they
  tie on base stats and flipped between runs).
- HMs' Trainer Ditto is made uncatchable; it still trades.
- check_deps.py skips other-side dependencies; Cobbreeding's harmless
  client-mixin log line allowlisted.
- Self-tests cobbreeding_eggs_fit_the_hatchery, trainer_ditto_cant_be_caught;
  docs, balance table, Phase 5 checklist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
claudeandClaude Opus 5.5 committed 2026-10-31 19:13:55 +01:00
1 parent 5b304025c6
commit 5cae9252c3
20 files changed
+274 -148

No files matched your search

+8 -6
View File
@@ -11,9 +11,10 @@ Build metadata after "+" is ignored, so Cobblemon "1.8.4+1.21.1" compares as
1.8.4; qualifiers follow Maven (21-beta < 21 < 21.1.265). A mismatch on the `minecraft` dependency is only a warning: every JEI
build for 1.21.1 declares "[1.21, 1.21.1)" and NeoForge 21.1 still loads it
(checked on the test server, 2026-10-28), so this check is stricter than the
loader there.
loader there. A dependency limited to the other side (`side = "CLIENT"` in a
server folder, e.g. Cobbreeding's Cloth Config) is skipped.
Usage: tools/check_deps.py <mods_dir> [--neoforge VER] [--minecraft VER]
Usage: tools/check_deps.py <mods_dir> [--neoforge VER] [--minecraft VER] [--side server|client]
"""
import argparse
import io
@@ -65,7 +66,7 @@ def in_range(version, spec):
return False
def read_mods(data, name, found, deps):
def read_mods(data, name, found, deps, side):
z = zipfile.ZipFile(io.BytesIO(data))
names = set(z.namelist())
if "META-INF/neoforge.mods.toml" in names:
@@ -78,10 +79,10 @@ def read_mods(data, name, found, deps):
for mod_id, entries in toml.get("dependencies", {}).items():
for dep in entries:
required = dep.get("type", "required" if dep.get("mandatory", True) else "optional") == "required"
if required:
if required and dep.get("side", "BOTH").upper() in ("BOTH", side.upper()):
deps.append((mod_id, dep["modId"], dep.get("versionRange", "*"), name))
for inner in (n for n in names if n.startswith("META-INF/jarjar/") and n.endswith(".jar")):
read_mods(z.read(inner), f"{name}!{inner.rsplit('/', 1)[-1]}", found, deps)
read_mods(z.read(inner), f"{name}!{inner.rsplit('/', 1)[-1]}", found, deps, side)
def main():
@@ -89,11 +90,12 @@ def main():
parser.add_argument("mods_dir", type=Path)
parser.add_argument("--neoforge")
parser.add_argument("--minecraft", default="1.21.1")
parser.add_argument("--side", choices=["server", "client"], default="server")
args = parser.parse_args()
found, deps = {}, []
for jar in sorted(args.mods_dir.glob("*.jar")):
read_mods(jar.read_bytes(), jar.name, found, deps)
read_mods(jar.read_bytes(), jar.name, found, deps, args.side)
if args.neoforge:
found["neoforge"] = (args.neoforge, "loader")
found["minecraft"] = (args.minecraft, "game")