The End gets Pokémon: a dimension zone with Cobblemon's End lines

Adrian, 2026-11-02: the End looked barren. The End is now a zone of its own
(Lv 50-70, world/zones.json -> dimensions; Zones.at returns it anywhere in the
End). tools/zone_spawns.py copies every line Cobblemon spawns in End biomes
into spawn_pool_world/the_end.json, on top of its island home: Beheeyem,
Reuniclus, Metagross; Gothitelle and Sigilyph in End cities; Malamar and
Unown with a Poke Snack. Cobblemon rolls the bucket before the Pokemon and
has these all uncommon or rare, so they move to the common bucket with even
line weights (Metagross a fifth).

Self-test end_has_spawns: the main island is in the_end and offers only End
spawns, some in the common bucket. Self-tests and the island check pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
claudeandClaude Opus 5.5 committed 2026-11-02 17:13:12 +01:00
1 parent 4e7b16abae
commit 4f0bc03919
11 files changed
+810 -9

No files matched your search

+1
View File
@@ -312,6 +312,7 @@ def write_datapack(zone, plan, preview):
"seed": preview["seed"], "radius": preview["radius"], "step": preview["step"], "size": preview["size"],
"zones": [{k: z[k] for k in keep} for z in plan["zones"]],
"caves": [{**{k: c[k] for k in keep}, "biome": c["biome"]} for c in plan["caves"]],
"dimensions": [{**{k: d[k] for k in keep}, "dimension": d["dimension"]} for d in plan.get("dimensions", [])],
"level_pressure": plan.get("level_pressure", {}),
"wanderers": plan.get("wanderers", {}),
"samples": samples(zone, plan, preview),
+48 -4
View File
@@ -32,6 +32,11 @@ datapacks/core/data/cobblesurvival/spawn_pool_world/<zone>.json:
6. Each zone with a "guardian" gets datapacks/core/data/cobblesurvival/guardians/<zone>.json: the
strongest Pokémon spawning in the zone (or "species"), uncatchable, half again as big (or "scale"), five levels
above the band, with the zone's boss drops and world/zones.json → guardian_respawn_hours.
7. world/zones.json → "dimensions" are zones that are a whole other dimension (the End; Adrian, 2026-11-02: it was
barren). Every line Cobblemon spawns in their "biome_tags" spawns there too, on top of its island home, with
its stages fitted to the band as in 2. The entries go into "bucket" (Cobblemon rolls the bucket first, and its
End entries are all uncommon or rare), the heaviest of each line weighing "line_weight", Cobblemon's rare ones
"rare_factor" of that.
Also writes docs/zone-species.md, the list per zone for review.
@@ -306,6 +311,45 @@ def main():
names = sorted({species[m]["name"] for _, m, _, _ in fitting})
report[zid].append((species.get(root, {}).get("name", root), names))
# 4. Dimension zones (Adrian, 2026-11-02: the End was barren): every line Cobblemon spawns in the dimension's
# biome tags spawns there too, at the dimension's band, on top of its home on the island. Read straight from
# Cobblemon's spawn files, since the field guide only resolves the island's biomes. Cobblemon rolls the bucket
# before the Pokémon, and its End entries are all uncommon or rare: they move to the dimension's bucket, the
# heaviest entry of each line weighing line_weight (Cobblemon's rare ones rare_factor of that).
dimension_roots = set()
for dim in plan.get("dimensions", []):
did = dim["id"]
zones[did] = dim
lo, hi = dim["levels"]
tags = set(dim["biome_tags"])
found = collections.defaultdict(list)
for entry in entries.values():
if "pokemon" not in entry or not tags & set(entry.get("condition", {}).get("biomes", [])):
continue
member = f"cobblemon:{entry['pokemon'].split()[0].lower()}"
if EXCLUDED_LABELS & set(species.get(member, {}).get("labels", [])):
continue
a, _, b = str(entry["level"]).partition("-")
found[line_root(member, species)].append((entry, member, (int(a), int(b or a))))
for root, items in sorted(found.items()):
fitting = [(e, m, max(lv[0], lo), hi) for e, m, lv in items if lv[0] <= hi and lv[1] >= lo]
if not fitting:
_, m, lv = min(items, key=lambda u: min(abs(u[2][0] - hi), abs(u[2][1] - lo)))
fitting = [(e, um, lo, hi) for e, um, ulv in items if um == m and ulv == lv]
heaviest = max(e.get("weight", 1) for e, _, _, _ in fitting)
for entry, member, a, b in fitting:
spawn = copy.deepcopy(entry)
spawn["id"] = f"{did}-{entry.get('id') or member.split(':')[1]}-{len(pools[did])}"
spawn["level"] = f"{a}-{b}"
rare = entry.get("bucket") in ("rare", "ultra-rare")
spawn["weight"] = round(dim["line_weight"] * entry.get("weight", 1) / heaviest * (dim["rare_factor"] if rare else 1), 3)
spawn["bucket"] = dim["bucket"]
spawn.setdefault("condition", {})["zone"] = did
pools[did].append(spawn)
names = sorted({species[m]["name"] for _, m, _, _ in fitting if m in species})
report[did].append((species.get(root, {}).get("name", root), names))
dimension_roots.add(root)
low_flyers = [(zid, s["pokemon"]) for zid, spawns in pools.items() for s in spawns
if zones[zid]["levels"][0] < HIGH_ZONE_LEVEL and f"cobblemon:{s['pokemon'].split()[0].lower()}" in strong_flyers]
if low_flyers:
@@ -346,9 +390,9 @@ def main():
lines_out = ["# Pokémon per zone", "",
"Generated by `tools/zone_spawns.py` from Cobblemon's spawn data and `world/zones.json`; "
"do not edit by hand. Each evolution line spawns in one zone; the stages listed are the ones "
"whose levels reach the zone's band. Pin a line to another zone in `world/zones.json` → "
"`species`.", ""]
"do not edit by hand. Each evolution line spawns in one zone of the island; the stages listed are "
"the ones whose levels reach the zone's band. Pin a line to another zone in `world/zones.json` → "
"`species`. The End's lines spawn there as well as in their island zone.", ""]
for zid, zone in zones.items():
rows = sorted(report.get(zid, []))
lines_out += [f"## {zone['name']} (Lv {zone['levels'][0]}–{zone['levels'][1]}, {len(rows)} lines)", ""]
@@ -364,7 +408,7 @@ def main():
f"| {zone['levels'][1] + GUARDIAN_LEVELS_ABOVE_BAND} | {drop_text} |")
REPORT.write_text("\n".join(lines_out) + "\n")
unplaced = [r for r in lines if r not in home]
unplaced = [r for r in lines if r not in home and r not in dimension_roots]
grounded = sorted(species.get(r, {}).get("name", r) for r in unplaced if r in flying_lines)
if grounded:
print(f"strong flyers kept out of the wild (no high zone fits their habitat): {', '.join(grounded)}")