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:
1 parent
4e7b16abae
commit
4f0bc03919
11 files changed
+810
-9
No files matched your search
@@ -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
@@ -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)}")
|
||||
|
||||
Reference in new issue
Block a user