#!/usr/bin/env python3 """ Render every classic dungeon's webp with AtlasLoot boss coords overlaid as red rings, side-by-side with the bare texture. Output to /tmp/alignment/ for human review. """ from __future__ import annotations import json import re import sys from pathlib import Path from PIL import Image, ImageDraw ROOT = Path(__file__).resolve().parent.parent WEB_MAPS = ROOT / "web" / "assets" / "maps" DUNGEONS_JSON = ROOT / "web" / "assets" / "dungeons.json" OUT_DIR = Path("/tmp/alignment") def main() -> int: if not DUNGEONS_JSON.exists(): print("dungeons.json missing, run build_data.py first", file=sys.stderr) return 1 OUT_DIR.mkdir(parents=True, exist_ok=True) data = json.loads(DUNGEONS_JSON.read_text()) rendered = [] for d in data["dungeons"]: if d.get("expansion") != "OriginalWoW": continue # Use bosses from the first map (single-floor) or unassignedBosses (multi) bosses = d["maps"][0]["bosses"] if d["maps"] and d["maps"][0]["bosses"] else d.get("unassignedBosses", []) if not d["maps"]: continue # Pick the first map for the alignment check m = d["maps"][0] src = WEB_MAPS / Path(m["image"]).name if not src.exists(): continue with Image.open(src) as im: im = im.convert("RGBA") W, H = im.size draw = ImageDraw.Draw(im) for b in bosses: px = int((b["x"] / 100) * W) py = int((b["y"] / 100) * H) r = 80 draw.ellipse([px - r, py - r, px + r, py + r], outline="red", width=14) draw.ellipse([px - 4, py - 4, px + 4, py + 4], fill="red") # Scale down for review scale = 1024 / W im_small = im.resize((1024, int(H * scale)), Image.LANCZOS) out = OUT_DIR / f"{d['id']}.png" im_small.save(out) rendered.append((d["id"], d["name"], len(bosses), out)) print(f"Rendered {len(rendered)} dungeons → {OUT_DIR}") for did, name, n, p in rendered: print(f" {did:30s} {n:>2d} bosses → {p.name}") return 0 if __name__ == "__main__": raise SystemExit(main())