from pathlib import Path
import json,hashlib,struct
P=Path('/opt/creature-lib-20261017');R=P/'realistic'
e=json.loads((P/'catalog_entries_realistic.json').read_text());old=json.loads((P/'catalog_entries.json').read_text());assert len(e)==3
assert {x['id'] for x in e}=={'enemy-stag-realistic','enemy-deer-realistic','enemy-dog-realistic'}
oldkeys=set(old[0]);m={x['id']:x for x in json.loads((R/'manifest.json').read_text())}
for x in e:
 assert set(x)==oldkeys,(x['id'],set(x)^oldkeys)
 assert x['style']=='realistic' and x['names'] and set(x['clips'])=={'idle','walk','run','attack','hit','die'}
 assert set(x['speeds'])=={'walk','run'} and all(v>0 for v in x['speeds'].values())
 p=P/'glb'/x['file'];b=p.read_bytes();assert len(b)<=1500000;assert hashlib.sha256(b).hexdigest()==m[x['id']]['sha256']==m[x['id']]['candidate_sha256']
 n=struct.unpack_from('<I',b,12)[0];g=json.loads(b[20:20+n]);assert len(g['skins'])==1 and len(g['animations'])==6;assert all('uri' not in i for i in g.get('images',[]))
 for typ in ['hero','clips','head','comparison','side','rear','walk-side','run-side']:
  assert (P/'renders'/f'{x["id"]}-{typ}.png').stat().st_size>5000
 for typ in ['walk','run']:assert (P/'renders'/f'{x["id"]}-{typ}.mp4').stat().st_size>5000
 k=x['id'].replace('enemy-','').replace('-realistic','');r=json.loads((R/k/'runtime-final.json').read_text());assert not r['errors'] and not r['browser_errors'] and len(r['clips'])==6 and r['maxWeights']<=4 and abs(r['ground'])<1e-5
for k in ['fox','horse','boar']:assert not (P/'glb'/f'enemy-{k}-realistic.glb').exists()
for line in (R/'original-glb-sha256.txt').read_text().splitlines():
 sha,path=line.split(maxsplit=1);assert hashlib.sha256(Path(path).read_bytes()).hexdigest()==sha
node=json.loads((R/'node-validation.json').read_text());assert node['models']==3 and node['clips']==18 and node['failures']==0
assert json.loads((R/'contracts.json').read_text())['failures']==0
assert all(x['exit_code']==0 for x in json.loads((R/'independent-glb-audit.json').read_text()))
report=(P/'REPORT.md').read_text();assert report.count('# W-REALISTIC:')==1
for x in e:assert x['id'] in report
print('PASS: 3 final files, 18 clips, exact catalog shape, required renders/videos, original 29 GLBs unchanged, rejected models excluded, report appended.')
