#!/usr/bin/env python3 """Render a diagnostic first-stage preview from source evidence.""" from __future__ import annotations import json from pathlib import Path from typing import Any from PIL import Image, ImageDraw, ImageFont PROJECT = Path(__file__).resolve().parents[1] SOURCE_NODES = PROJECT / "source_export/source_nodes.json" OUT_DIR = PROJECT / "artifacts/canvas_preview" def read_json(path: Path) -> Any: with path.open("r", encoding="utf-8") as fh: return json.load(fh) def cocos_to_image(point: dict[str, float], width: int, height: int) -> tuple[float, float]: return width / 2 + float(point.get("x", 0)), height / 2 - float(point.get("y", 0)) def draw_centered_rect(draw: ImageDraw.ImageDraw, node: dict[str, Any], canvas: tuple[int, int], outline: str, fill: str | None = None) -> None: pos = node.get("world_position_transformed") or node.get("position") or {"x": 0, "y": 0} size = node.get("primary_size") or node.get("content_size") or {"width": 40, "height": 40} cx, cy = cocos_to_image(pos, canvas[0], canvas[1]) w = float(size.get("width") or 40) h = float(size.get("height") or 40) box = [cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2] draw.rectangle(box, outline=outline, fill=fill, width=2) def draw_land_cell(draw: ImageDraw.ImageDraw, node: dict[str, Any], canvas: tuple[int, int]) -> None: pos = node.get("world_position_transformed") or {"x": 0, "y": 0} cx, cy = cocos_to_image(pos, canvas[0], canvas[1]) size = node.get("primary_size") or {"width": 130, "height": 130} w = float(size.get("width") or 130) h = float(size.get("height") or 130) * 0.5 pts = [(cx, cy - h / 2), (cx + w / 2, cy), (cx, cy + h / 2), (cx - w / 2, cy)] draw.polygon(pts, outline="#2f7d32", fill="#86bf6a") def main() -> None: data = read_json(SOURCE_NODES) width = int(data["design_resolution"]["width"]) height = int(data["design_resolution"]["height"]) img = Image.new("RGB", (width, height), "#d7efff") draw = ImageDraw.Draw(img, "RGBA") for node in data["background_nodes"]: draw_centered_rect(draw, node, (width, height), "#7a9f4b", "#b7d77a55") for node in data["land_candidates"]["nodes"]: draw_land_cell(draw, node, (width, height)) for node in data["house_and_doghouse"]["doghouse_nodes"]: draw_centered_rect(draw, node, (width, height), "#7a3f16", "#b8753555") for record in data["house_and_doghouse"]["skin_cfg_default_records"]: pos = record.get("position") or {"x": 0, "y": 0} cx, cy = cocos_to_image(pos, width, height) label = str(record.get("skin_name", record.get("id"))) draw.rectangle([cx - 55, cy - 35, cx + 55, cy + 35], outline="#5c2d91", fill="#c7a5ff66", width=2) draw.text((cx - 48, cy - 8), label, fill="#2b1545") for node in data["bottom_menu_nodes"]: draw_centered_rect(draw, node, (width, height), "#1f4f99", "#6fa8ff55") draw.rectangle([0, 0, width - 1, height - 1], outline="#222222", width=2) draw.text((12, 12), "Stage1 diagnostic preview - source evidence only", fill="#111111") draw.text((12, 34), f"land candidates: {data['land_candidates']['source_count']} / visible 24 blocked", fill="#8a1f11") OUT_DIR.mkdir(parents=True, exist_ok=True) png = OUT_DIR / "stage1_source_preview.png" report = OUT_DIR / "stage1_source_preview_report.json" img.save(png) report.write_text(json.dumps({ "status": "generated", "image": str(png), "resolution": {"width": width, "height": height}, "land_candidates_rendered": data["land_candidates"]["source_count"], "visible_land_selection": data["land_candidates"]["selection_status"], "note": "Diagnostic rendering only; it does not define final Cocos coordinates.", }, indent=2) + "\n", encoding="utf-8") print(png) if __name__ == "__main__": main()