RECORDANDRONCO/tools/render_map.py
type-two e11ec402ff Terrain overhaul: 52-tile atlas from 5 generated surfaces, tone-harmonized so they read as one material family
Replaces the single flat grey tile. Adds tools/build_tiles.py (atlas builder
with edge-blending + tone matching), paint_terrain.py (surgical tile-plane
repaint: asphalt base, concrete pads, dirt blobs, groove patches near wax,
rubble clumps), jitter_wax.py, render_map.py (offline map preview), and
check_assets.py.

Also fixes the arena harness being rigged: both wax fields sat ~12 cells from
BotA and ~18 from BotB, so BotB starved for map reasons, not balance reasons.
Arena wax is now mirrored (7.1/18/18/19.8 from both starts). Seep interval
90 -> 20 after telemetry showed the slower rate still drained both economies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 22:37:38 +10:00

48 lines
1.9 KiB
Python

#!/usr/bin/env python3
"""Render a map.bin to a PNG using the real tile atlases + wax sprites.
Offline view of exactly what the engine will draw, so terrain can be checked
without a display. Usage: render_map.py <mapname> [out.png]
"""
import os, struct, sys
from PIL import Image
ROOT = os.path.join(os.path.dirname(__file__), '..')
MOD = os.path.join(ROOT, 'mods', 'vinylgod')
TPL = {255: 'asphalt', 1: 'concrete', 2: 'dirt', 3: 'grooves', 4: 'rubble'}
TILE = 32
name = sys.argv[1] if len(sys.argv) > 1 else 'arena'
out = sys.argv[2] if len(sys.argv) > 2 else os.path.join(ROOT, 'assets_src', f'map_{name}.png')
atlas = {}
for tid, n in TPL.items():
im = Image.open(os.path.join(MOD, 'tilesets', 'vinylgod', f'{n}.png')).convert('RGBA')
atlas[tid] = [im.crop((i * TILE, 0, (i + 1) * TILE, TILE)) for i in range(im.width // TILE)]
wax_sheet = Image.open(os.path.join(MOD, 'sequences', 'assets', 'wax.png')).convert('RGBA')
wax = [wax_sheet.crop((i * 32, 0, (i + 1) * 32, 32)) for i in range(wax_sheet.width // 32)]
d = open(os.path.join(MOD, 'maps', name, 'map.bin'), 'rb').read()
fmt, w, h = d[0], *struct.unpack_from('<HH', d, 1)
t_off, ht_off, res_off = struct.unpack_from('<III', d, 5)
img = Image.new('RGBA', (w * TILE, h * TILE))
for x in range(w):
for y in range(h):
tid, idx = struct.unpack_from('<HB', d, t_off + 3 * (x * h + y))
frames = atlas.get(tid) or atlas[255]
img.paste(frames[idx % len(frames)], (x * TILE, y * TILE))
for x in range(w):
for y in range(h):
rt, dens = d[res_off + 2 * (x * h + y)], d[res_off + 2 * (x * h + y) + 1]
if rt:
s = wax[min(dens, len(wax) - 1)]
img.paste(s, (x * TILE, y * TILE), s)
scale = max(1, min(2, 2600 // (w * TILE)))
img = img.resize((w * TILE * scale // 2 or w * TILE, h * TILE * scale // 2 or h * TILE), Image.LANCZOS)
img.convert('RGB').save(out)
print(f'{name}: {w}x{h} -> {out} ({img.width}x{img.height})')