#!/usr/bin/env python3 """contact_sheet.py — every shipped sprite on one page, for judging. python3 tools/gen/contact_sheet.py [out.png] [--filter substr] Built because 127 sprites had been generated and only a handful had ever been LOOKED at. The pipeline is happy to produce a confident, well-lit render of the wrong object, and no test can tell — `barrier` and `flightCase` are both "a grey box with edges" as far as any assertion goes. Sprites are shown at an integer upscale on the game's own dark floor colour, which is the honest way to see them: SPACES.md's rule is to judge at ship size rather than at render size, so this deliberately does NOT show the 512px render. The point is to catch sprites that are unrecognisable or wrongly cut out, not to pixel-peep detail that never reaches the screen. """ from __future__ import annotations import sys from pathlib import Path from PIL import Image, ImageDraw PROPS = Path(__file__).resolve().parent.parent.parent / "public" / "props" CELL_W, CELL_H = 108, 124 COLS = 12 LABEL_H = 14 # The venue floor colour from venueMap COLOURS.floor — sprites are judged against # what they actually sit on, not against white. BG = (34, 30, 43) CELL_BG = (24, 21, 30) def main() -> None: out = Path(sys.argv[1]) if len(sys.argv) > 1 and not sys.argv[1].startswith("--") \ else Path("art_incoming/contact_sheet.png") needle = sys.argv[sys.argv.index("--filter") + 1] if "--filter" in sys.argv else "" names = sorted(p.stem for p in PROPS.glob("*.png") if needle in p.stem) if not names: sys.exit("no sprites matched") rows = (len(names) + COLS - 1) // COLS sheet = Image.new("RGB", (COLS * CELL_W, rows * CELL_H), BG) draw = ImageDraw.Draw(sheet) for i, name in enumerate(names): cx = (i % COLS) * CELL_W cy = (i // COLS) * CELL_H draw.rectangle([cx + 1, cy + 1, cx + CELL_W - 2, cy + CELL_H - LABEL_H - 2], fill=CELL_BG) img = Image.open(PROPS / f"{name}.png").convert("RGBA") room_w, room_h = CELL_W - 8, CELL_H - LABEL_H - 8 # Integer upscale only — a fractional resize would invent detail the game # never shows and blur the pixel register these were quantised into. scale = max(1, min(room_w // max(1, img.width), room_h // max(1, img.height))) shown = img.resize((img.width * scale, img.height * scale), Image.NEAREST) px = cx + (CELL_W - shown.width) // 2 py = cy + (CELL_H - LABEL_H - shown.height) // 2 sheet.paste(shown, (px, py), shown) draw.text((cx + 3, cy + CELL_H - LABEL_H), f"{name} {img.width}x{img.height}", fill=(150, 150, 165)) out.parent.mkdir(parents=True, exist_ok=True) sheet.save(out) print(f"{len(names)} sprites -> {out} ({sheet.width}x{sheet.height})") if __name__ == "__main__": main()