Phase 3: Alpha guardians, one per zone, back 5 hours after a defeat
CI / build-and-smoke-test (push) Failing after 7m6s

- Companion mod (zone/Guardians.kt): each zone's guardian stands at the
  zone's sample point, appears when a player is within 96 blocks, stays
  within 24 blocks, is uncatchable and half again as big. Beating it in a
  battle gives every player in the battle its drops; it returns after 5
  hours of game time (Adrian: fixed spots, 5 hours). State lives in the
  world save; /cobblesurvival guardians lists it.
- tools/zone_spawns.py writes data/cobblesurvival/guardians/<zone>.json:
  the strongest walking (or swimming) Pokemon of the zone at band top + 5,
  with the drops from world/zones.json. Boss drops follow Adrian's rule:
  Icy Rocks before Sunscorch, Heat Rocks before Frostpeak.
- Goat Fur and Hoglin Hide recipes make 8 per rock, so one guardian's 3
  rocks pay for one full insulating set.
- Self-test guardian_appears_and_returns; hazard tests cleaned up; the
  zone generator reads Cobblemon's own entries from the field guide's
  hidden_spawns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
claudeandClaude Opus 5.5 committed 2026-10-29 19:01:21 +01:00
1 parent 398301f0be
commit 73fe71918f
25 files changed
+607 -23

No files matched your search

+39 -1
View File
@@ -21,6 +21,9 @@ datapacks/core/data/cobblesurvival/spawn_pool_world/<zone>.json:
doesn't spawn there. If no stage fits, the closest one spawns across the whole band.
3. world/zones.json → "species" pins a line to a zone by hand: {"pikachu": "southwold"}.
4. In cave zones, sky-light minimums, "can see sky" and the "natural" ground preset are dropped.
5. 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, five levels
above the band, with the zone's boss drops and world/zones.json → guardian_respawn_hours.
Also writes docs/zone-species.md, the list per zone for review.
@@ -41,6 +44,9 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
OUT = ROOT / "datapacks/core/data/cobblesurvival/spawn_pool_world"
GUARDIANS = ROOT / "datapacks/core/data/cobblesurvival/guardians"
GUARDIAN_SCALE = 1.5 # "Alpha": half again as big as a normal one
GUARDIAN_LEVELS_ABOVE_BAND = 5
REPORT = ROOT / "docs/zone-species.md"
EXCLUDED_LABELS = {"legendary", "mythical", "ultra_beast", "paradox", "restricted"}
MIN_CELLS = 5 # a zone "has" a biome when at least this many grid cells of it lie in the zone
@@ -242,6 +248,29 @@ def main():
"spawns": sorted(spawns, key=lambda s: s["id"]),
}, indent=1) + "\n")
GUARDIANS.mkdir(parents=True, exist_ok=True)
for old in GUARDIANS.glob("*.json"):
old.unlink()
guardians = {}
for zid, zone in zones.items():
config = zone.get("guardian")
if not config:
continue
# A guardian stands its ground: on land a walking spawn, at sea a swimming one, never a fishing catch.
kinds = {"submerged", "surface"} if "water" in zone.get("rule", {}) else {"grounded"}
spawning = {s["pokemon"].split()[0].lower() for s in pools.get(zid, []) if s.get("spawnablePositionType") in kinds}
species_name = config.get("species") or max(spawning, key=lambda n: bst.get(f"cobblemon:{n}", 0))
level = zone["levels"][1] + GUARDIAN_LEVELS_ABOVE_BAND
guardians[zid] = species_name
(GUARDIANS / f"{zid}.json").write_text(json.dumps({
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": zid,
"pokemon": f"{species_name} level={level} uncatchable=yes",
"scale": GUARDIAN_SCALE,
"drops": [{"item": item, "count": count} for item, count in config["drops"]],
"respawn_hours": plan["guardian_respawn_hours"],
}, indent=1) + "\n")
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 "
@@ -251,7 +280,16 @@ def main():
rows = sorted(report.get(zid, []))
lines_out += [f"## {zone['name']} (Lv {zone['levels'][0]}–{zone['levels'][1]}, {len(rows)} lines)", ""]
lines_out += [", ".join(f"{root}" + (f" ({', '.join(n)})" if n != [root] else "") for root, n in rows) or "none", ""]
REPORT.write_text("\n".join(lines_out))
lines_out += ["## Alpha guardians", "",
f"One per zone at its sample point, uncatchable, five levels above the band; it returns "
f"{plan['guardian_respawn_hours']} hours after it is beaten. Its drops help with the next zone.", "",
"| Zone | Guardian | Level | Drops |", "| --- | --- | --- | --- |"]
for zid, species_name in guardians.items():
zone = zones[zid]
drop_text = ", ".join(f"{count}× {item.split(':')[1]}" for item, count in zone["guardian"]["drops"])
lines_out.append(f"| {zone['name']} | {species[f'cobblemon:{species_name}']['name'] if f'cobblemon:{species_name}' in species else species_name} "
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]
for zid, zone in zones.items():