import json,struct,hashlib
from pathlib import Path
import numpy as np
from PIL import Image
root=Path(__file__).parent
kit=json.loads((root/'kit.json').read_text());shelf=json.loads((root/'materials.json').read_text());b=(root/'kit.glb').read_bytes();n=struct.unpack_from('<I',b,12)[0];g=json.loads(b[20:20+n]);nodes=g['nodes'];roots=g['scenes'][g.get('scene',0)]['nodes']
assert not g.get('images') and not g.get('textures')
assert set(nodes[i]['name'] for i in roots)==set(a['node_name'] for a in kit['atoms'])
for i in roots:
 node=nodes[i];assert 'mesh' in node
 assert node.get('translation',[0,0,0])==[0,0,0] and node.get('rotation',[0,0,0,1])==[0,0,0,1] and node.get('scale',[1,1,1])==[1,1,1]
for a in kit['atoms']:assert a['triangles']<=a['triangle_budget']
door=next(n for n in nodes if n['name']=='door');assert any(nodes[i]['name']=='door_leaf' for i in door['children'])
seams={}
for p in sorted((root/'materials').glob('*/*.png')):
 im=Image.open(p);a=np.array(im).astype('int64');delta=max(int(abs(a[0]-a[-1]).max()),int(abs(a[:,0]-a[:,-1]).max()));assert delta==0,(p,delta);assert im.width==im.height and im.width in (512,1024)
 seams[str(p.relative_to(root))]={'size':im.size,'opposite_edge_delta':delta,'mode':im.mode}
h=Image.open(root/'materials/Granite_lime_moss/Height.png');assert h.mode in ('I;16','I') and np.array(h).min()==0
assert len((root/'NOTES.md').read_text().splitlines())==6
for n,d in shelf['materials'].items():assert d['tile_size_metres']>0
au=json.loads((root/'audit.json').read_text());assert au['zero_area_triangles']==au['nonfinite_attribute_components']==0
assert set(au['findings'])<={'duplicate_primitives'}
for d in au['duplicate_primitives']:
 assert d['primitive'].split('[')[0]==d['same_as'].split('[')[0]+'_shutters'
e=json.loads((root/'build_evidence.json').read_text())
result={'passed':True,'atoms':len(roots),'triangles':kit['total_triangles'],'embedded_images':0,'all_atom_budgets_pass':True,'all_shelf_maps_exact_edge_match':True,'maps':seams,'expected_duplicate_geometry':'Six primitive pairs are shared window components mandated in both plain and shuttered complete units. No duplicate faces within atoms.','kit_sha256':hashlib.sha256(b).hexdigest()}
(root/'validation.json').write_text(json.dumps(result,indent=2));print({k:v for k,v in result.items() if k!='maps'})
