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>
48 lines
1.9 KiB
Python
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})')
|