Phase 9: safe start island, better quest book, every useful item obtainable

- Button Isle is peaceful: wild Pokémon only fight a player who hit them first
  (tuning/server.json peaceful_zones); its apricorns and coal make Poké Balls
  (4 apricorns + coal or charcoal), and the world report checks it has enough.
- Quest book: no boilerplate; each zone page says what is particular to it, and
  every zone that needs gear or a field move to be safe has its own quest. New
  page on the bigger mods (Cold Sweat, Fight or Flight, Cobblebase, breeding,
  veins, shared loot, maps and info).
- The Mire's poison stops 10 blocks below the surface.
- Player battles never cost a Pokémon (CobbleHardcoreMon config, mixins, and the
  companion mod's own permadeath).
- tools/item_audit.py: every item with a use has a source on the island; new
  sources for the gaps it found (species additions, three recipes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
claudeandClaude Opus 5.5 committed 2026-11-10 15:04:09 +01:00
1 parent 6ace26e012
commit d29e177f9d
65 files changed
+1920 -364

No files matched your search

+38
View File
@@ -14,6 +14,8 @@
the check, and so (except with --sample) do an ore tier without nodes, a
surface tier without underground veins, and a chunk that never got its
veins and nodes.
* Start island: Button Isle has enough apricorns and coal ore for Poké Balls
(four apricorns and coal or charcoal make one; Adrian, 2026-11-10).
* Biome set: every biome stored in any chunk section (surface and caves)
must be listed in world/biomes.json ("allowed" or "water").
* End access: at least one stronghold must start inside the world border.
@@ -42,6 +44,7 @@ import math
import os
import re
import sys
from collections import defaultdict
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path
@@ -331,6 +334,38 @@ def island_mask(data, cell=16, min_share=0.01):
return out
# Adrian, 2026-11-10: the start island alone makes Poké Balls (four apricorns and coal or charcoal, the pack's
# poke_ball_from_coal recipe), with some exploring. Coal grows back in its veins; apricorns grow back on their trees.
START_MINIMUM = {"apricorns": 100, "coal ore": 500}
def check_start_isle(world):
"""Apricorns and coal ore on Button Isle (the zone within world/zones.json's radius of 0,0), from its stored chunks."""
plan = json.loads((ROOT / "world/zones.json").read_text())
radius = next(z for z in plan["zones"] if z["id"] == "button_isle")["rule"]["within"]
found = defaultdict(int)
span = range(-((radius + 511) // 512), (radius + 511) // 512)
for rx in span:
for rz in span:
path = world / "region" / f"r.{rx}.{rz}.mca"
if not path.exists():
continue
for cx, cz, chunk in anvil.region_chunks(path):
x0, z0 = cx * 16, cz * 16
if math.hypot(x0 + 8, z0 + 8) > radius + 12:
continue
inside = np.hypot(np.arange(16)[None, :] + x0 + .5, np.arange(16)[:, None] + z0 + .5) <= radius
for section in chunk.get("sections", []):
palette, indices = anvil.section_blocks(section)
wanted = [i for i, name in enumerate(palette) if name.endswith("_apricorn") or name.endswith("coal_ore")]
cube = indices.reshape(16, 16, 16)
for i in wanted:
kind = "apricorns" if palette[i].endswith("_apricorn") else "coal ore"
found[kind] += int(((cube == i) & inside[None, :, :]).sum())
problems = [f"Button Isle has {found[k]} {k}, fewer than {n}" for k, n in START_MINIMUM.items() if found[k] < n]
return dict(found), problems
def check_border(data, level, margin):
zs, xs = np.nonzero(island_mask(data))
ox, oz = data["origin"]
@@ -479,10 +514,12 @@ def main():
listed = set(biomes["allowed"]) | set(biomes["water"])
unlisted = {b: n for b, n in data["sections_per_biome"].items() if b not in listed}
vein_counts, vein_problems = check_veins(args.world, data["ore_at"], data["full_chunks"], args.sample)
start_isle, start_problems = check_start_isle(args.world)
report = {
"chunks_scanned": data["chunks"],
"ore_blocks": {k: v for k, v in data["ores"].items() if ORE.search(k) or k in VEIN_BLOCKS},
"vein_blocks_per_tier_and_zone": vein_counts,
"start_isle_ball_materials": start_isle,
"mob_source_blocks": {k: v for k, v in data["ores"].items() if MOB_BLOCK.search(k) or "shrieker" in k},
"unlisted_biomes": unlisted,
"gamerules_not_as_recipe": wrong_rules,
@@ -502,6 +539,7 @@ def main():
failures = ([f"{n} {name}" for name, n in data["ores"].items() if MOB_BLOCK.search(name) or "shrieker" in name]
+ vein_problems
+ ([] if args.sample else start_problems)
+ border["problems"]
+ [f"unlisted biome {b} in {n} chunk sections" for b, n in unlisted.items()]
+ [f"gamerule {r} is {v}, recipe says {recipe['gamerules'][r]}" for r, v in wrong_rules.items()]