Files
sanctuary-beta/scripts/finishing_atlas.py
T
2026-09-12 20:29:32 +02:00

47 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Archive native 30.7 structure witnesses over the unchanged 30.6 terrain field."""
import argparse, hashlib, json, re
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
p=argparse.ArgumentParser(description=__doc__)
for name in ('terrain','village','transit','aerial','ruins','output'): p.add_argument('--'+name,type=Path,required=True)
a=p.parse_args()
data={name:json.loads(getattr(a,name).read_text()) for name in ('terrain','village','transit','aerial','ruins')}
t=data['terrain']; seed=str(t['seed']); size=t['diameter']
for name in ('village','transit','aerial','ruins'):
d=data[name]
if str(d['seed'])!=seed or d['diameter']!=size or not d.get('passed'): raise ValueError('Unmatched/failed '+name)
cols=np.asarray(t['variants']['current306']['columns']);xs,zs=np.unique(cols[:,0]),np.unique(cols[:,1]);g=cols.reshape(len(zs),len(xs),5)
fig,ax=plt.subplots(figsize=(11,10),layout='constrained')
step=t['map_step'];extent=(xs[0]-step/2,xs[-1]+step/2,zs[-1]+step/2,zs[0]-step/2)
im=ax.imshow(np.ma.masked_where(g[:,:,2]==0,g[:,:,2]),extent=extent,cmap='terrain',vmin=0,vmax=400,interpolation='nearest')
for link in data['village']['rail_links']:
if link['kind']=='FREIGHT': ax.plot([v['x'] for v in link['points']],[v['z'] for v in link['points']],color='#962427',lw=2)
walks=data['transit'].get('walk',[])
if isinstance(walks,dict): walks=[walks]
for walk in walks:
gates=[v['gate'] for v in walk['surface_accesses']]
ax.plot([v[0] for v in gates],[v[2] for v in gates],color='#223494',lw=2,ls='--',marker='s',ms=4)
for b in data['ruins']['bounds']:
v={k:int(n) for k,n in re.findall(r'(minX|minZ|maxX|maxZ)=(-?\d+)',b)}
ax.add_patch(Rectangle((v['minX'],v['minZ']),v['maxX']-v['minX']+1,v['maxZ']-v['minZ']+1,fill=False,edgecolor='#02abb2',linewidth=2))
for site in data['aerial']['geometry']:
width,depth=(site['width'],site['depth']) if site['facing']%2==0 else (site['depth'],site['width'])
colour='#8a4196' if site['kind']=='NATURAL_ISLET' else '#41301f'
ax.add_patch(Rectangle((site['minX'],site['minZ']),width,depth,fill=False,edgecolor=colour,linewidth=1.3))
ax.scatter([site['minX']+width//2],[site['minZ']+depth//2],color=colour,s=20,marker='x')
ax.scatter([0],[0],marker='+',s=70,color='black')
for label,color,style in [('Rails prévus — lacunes réparables','#962427','-'),('Axes des traversées parcourues','#223494','--'),('Ruines asséchées — emprises','#02abb2','-'),('Trésors du terrain aérien — emprises','#8a4196','-'),('Navires restaurés — emprises','#41301f','-')]:ax.plot([],[],color=color,ls=style,label=label)
ax.legend(loc='upper left',fontsize=8);ax.set(xlabel='X (blocs)',ylabel='Z (blocs) · nord en haut',aspect='equal')
ax.set_title(f'Sanctuary alpha.30.7 · {size} · graine {seed}\nInstallations sur le relief natif inchangé de 30.6')
fig.colorbar(im,ax=ax,shrink=.7,label='Altitude du terrain Y')
fig.supxlabel('Relief échantillonné ; rails planifiés. Traversées, coffres et ruines contrôlés dans les chunks FULL.\nLes rectangles indiquent des réservations, pas la forme construite ni les frontières des biomes.',fontsize=9)
a.output.mkdir(parents=True,exist_ok=True)
for ext in ('png','pdf'):fig.savefig(a.output/f'installations-{size}-{seed}.{ext}',dpi=160)
(a.output/f'installations-{size}-{seed}-provenance.json').write_text(json.dumps({k:{'path':str(getattr(a,k)),'sha256':hashlib.sha256(getattr(a,k).read_bytes()).hexdigest()} for k in data},indent=2)+'\n')