#!/usr/bin/env python3 """Package generated masters at logical/HD footprints and synthesize local cues. Only deterministic resampling, palette conversion and whole-map tile slicing are performed here; the source illustrations are built-in imagegen outputs. """ import argparse import hashlib import json import math import random import struct import wave from pathlib import Path import runpy from PIL import Image, ImageOps ROOT = Path(__file__).resolve().parent def digest(path): return hashlib.sha256(path.read_bytes()).hexdigest() def palette(path): data = path.read_bytes() if len(data) != 768: raise ValueError("Expected the original 768-byte VGA palette") colors = [(min(v, 63) << 2) | (min(v, 63) >> 4) for v in data] image = Image.new("P", (1, 1)) image.putpalette(colors) return image, colors def sd(image, size, colors): rgba = image.convert("RGBA").resize(size, Image.Resampling.LANCZOS) alpha = rgba.getchannel("A").point(lambda a: 255 if a >= 128 else 0) result = rgba.convert("RGB").quantize(palette=colors, dither=Image.Dither.NONE).convert("RGBA") result.putalpha(alpha) return result def wav(name, duration, frequencies, mode="tone"): rate = 22050 count = int(rate * duration) rng = random.Random(7053 + sum(ord(c) for c in name)) pcm = bytearray() for i in range(count): t = i / rate note = min(len(frequencies)-1, int(t / duration * len(frequencies))) f = frequencies[note] phase = (t / duration * len(frequencies)) % 1 envelope = min(1, phase * 24) * (1-phase)**1.2 sample = math.sin(2*math.pi*f*t) + 0.2*math.sin(2*math.pi*f*2*t) if mode == "metal": sample += 0.8*rng.uniform(-1,1)*math.exp(-t*7) sample += 0.3*math.sin(2*math.pi*713*t)*math.exp(-t*10) elif mode == "steam": sample = rng.uniform(-1,1)*0.85 + 0.35*math.sin(2*math.pi*f*t) elif mode == "alarm": sample += 0.3*math.sin(2*math.pi*f*0.5*t) value = max(-32767,min(32767,int(sample*envelope*6700))) pcm += struct.pack(" 0, "foundry sprite requires transparency" report = {"tile_count":number,"tile_logical_size":[32,32],"hd_tile_size":[128,128], "vertical_repeat":"12 forward + 12 reflected rows; identical wrap-boundary pixels", "seam_checks_passed":True,"sd_pixels_match_original_palette":True, "palette_sha256":digest(args.palette),"audio":audio, "sources":{p.name:{"size":list(Image.open(p).size),"sha256":digest(p)} for p in art.glob("*-source-hd.png")}, "foundry_transparent_pixels":sum(a==0 for a in alpha_pixels)} (ROOT/"asset-report.json").write_text(json.dumps(report,indent=2)+"\n") print(f"Packaged {number} SD/HD tiles, briefing/foundry sprite and {len(audio)} WAV cues") if __name__ == "__main__": main()