from pathlib import Path
import json,hashlib,shutil
from PIL import Image,ImageDraw,ImageFont
P=Path('/opt/creature-lib-20261017');specs={
'stag':{'names':['stag','stags','deer'],'about':'A realistic brown red-deer stag with rigid branched antlers; walks, gallops, headbutts, reacts and collapses. Soft face detail is intended for game distance.'},
'deer':{'names':['deer','doe','does','hind','hinds'],'about':'A realistic antlerless female red deer with tan coat and pale rump; walks, gallops, headbutts, reacts and collapses. Not a close-up hero animal.'},
'dog':{'names':['dog','dogs','hound','hounds'],'about':'A large dark brindled hound with floppy ears, long legs and a hanging tail; walks, gallops, lunges, reacts and collapses. Closed-mouth attack, not an articulated bite.'}}
entries=[];manifest=[]
font=ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',22)
for k,info in specs.items():
 D=P/'realistic'/k;e=json.loads((D/'entry-v6.json').read_text());r=json.loads((D/'runtime-v6.json').read_text());assert not r['errors'] and not r['browser_errors'];assert len(r['clips'])==6 and r['maxWeights']<=4
 source=D/'clearance-v6.glb';assert source.stat().st_size<=1500000;target=P/'glb'/f'enemy-{k}-realistic.glb';assert not target.exists(),target
 with target.open('xb') as f:f.write(source.read_bytes())
 e.update(file=target.name,height_m=round(r['height_m'],6),names=info['names'],about=info['about'],speeds={x:r['speeds'][x]['speed'] for x in ['walk','run']},style='realistic',mood=['natural','wilderness'],tint=[])
 assert all(isinstance(x,(int,float)) and x>0 for x in e['speeds'].values());entries.append(e);(D/'entry-final.json').write_text(json.dumps(e,indent=2))
 # Every image/video is a render of identical bytes; final-path checks run separately after promotion.
 for suffix in ['hero','clips','head','side','rear','walk-side','run-side']:
  out=P/'renders'/f'{e["id"]}-{suffix}.png';assert not out.exists();shutil.copyfile(D/f'v6-{suffix}.png',out)
 for lane in ['walk','run']:
  out=P/'renders'/f'{e["id"]}-{lane}.mp4';assert not out.exists();shutil.copyfile(D/f'v6-{lane}.mp4',out)
 sheet=Image.new('RGB',(1280,690),'#17212c');draw=ImageDraw.Draw(sheet)
 for i,style in enumerate(['stylised','realistic']):
  sheet.paste(Image.open(D/f'v6-comparison-{style}.png').convert('RGB'),(i*640,50));draw.text((i*640+16,12),k.upper()+' | '+style,fill='white',font=font)
 out=P/'renders'/f'{e["id"]}-comparison.png';assert not out.exists();sheet.save(out)
 manifest.append({'id':e['id'],'file':str(target.relative_to(P)),'bytes':target.stat().st_size,'sha256':hashlib.sha256(target.read_bytes()).hexdigest(),'candidate_sha256':hashlib.sha256(source.read_bytes()).hexdigest(),'triangles':r['triangles'],'verdict':'OK','status':'asset-handoff, not connected to games'})
assert not (P/'catalog_entries_realistic.json').exists();(P/'catalog_entries_realistic.json').write_text(json.dumps(entries,indent=2)+'\n');(P/'realistic/manifest.json').write_text(json.dumps(manifest,indent=2))
sheet=Image.new('RGB',(1440,530),'#17212c');draw=ImageDraw.Draw(sheet)
for i,k in enumerate(specs):
 sheet.paste(Image.open(P/'renders'/f'enemy-{k}-realistic-hero.png').resize((480,480)),(i*480,50));draw.text((i*480+16,12),k.upper()+' | OK at game distance',fill='white',font=font)
sheet.save(P/'realistic-contact-sheet.png');print('PROMOTED',len(entries))
