"""Delivery checks independent of Blender authoring; run python3 validate_kit.py."""
import json,struct,hashlib,sys,math
from pathlib import Path
import numpy as np
R=Path(__file__).parent
sys.path.insert(0,'/home/claude-agent/.agents/skills/blender-game-assets/scripts')
from audit_glb import audit
j=json.loads((R/'kit.json').read_text());a=audit(R/'kit.glb');data=(R/'kit.glb').read_bytes();n=struct.unpack_from('<I',data,12)[0];g=json.loads(data[20:20+n]);offset=28+n;binary=data[offset:]
expected='dormer_gabled dormer_hipped dormer_wall louvre_ridge chimney_cluster chimney_twin sign_boot sign_pig sign_key sign_pretzel oriel door_arched_stone door_cellar window_cellar window_shuttered finial thatch_panel thatch_ridge pillory well_wheel'.split()
assert set(expected)=={x['node_name'] for x in j['atoms']}=={x['name'] for x in g['nodes']}
assert a['triangles']==j['total_triangles']<=45000
assert a['zero_area_triangles']==a['nonfinite_attribute_components']==0
assert all(v['triangles']<=v['triangle_budget'] for v in j['atoms'])
# Duplicate primitive detection here deliberately permits identical components in
# different frozen atoms; it rejects accidental duplicates WITHIN any single atom.
assert all(x['primitive'].split('[')[0]!=x['same_as'].split('[')[0] for x in a['duplicate_primitives'])
assert all('translation' not in node and 'rotation' not in node and 'scale' not in node and 'matrix' not in node for node in g['nodes'])
meta={v['node_name']:v for v in j['atoms']}
for name in ['dormer_gabled','dormer_hipped']:
 b=meta[name]['bounding_box'];assert b['max'][1]+math.tan(math.radians(55))*b['min'][2]<.05
assert meta['door_arched_stone']['opening']['width']==1.1 and meta['door_arched_stone']['opening']['height']==2.3
assert meta['door_cellar']['opening']['width']==.9 and meta['door_cellar']['opening']['height']==1.8
assert meta['thatch_panel']['grid_step_metres']==[1,1]
assert meta['thatch_panel']['mesh_overlap_metres']=={'x':.01,'z':.04}
assert meta['thatch_panel']['triangles']<=300 and meta['thatch_ridge']['triangles']<=300
shelf=json.loads((R/'materials.json').read_text())['materials'];used={m['name'] for m in g['materials']};assert used<=set(shelf)
for name in used:
 for file in shelf[name]['maps'].values():assert (R/file).is_file()
# Shared maps are byte-for-byte preserved, where the read-only reference is present.
ref=R.parent/'medieval-atom-kit-v1';checked=0
for name in used-{'Thatch_reed','Thatch_reed_cut'}:
 for p in shelf[name]['maps'].values():
  if (ref/p).exists():assert (R/p).read_bytes()==(ref/p).read_bytes();checked+=1
report={'status':'PASS','glb_sha256':hashlib.sha256(data).hexdigest(),'frozen_names':20,'triangles':a['triangles'],'budget':45000,'per_atom':{x['node_name']:x['triangles'] for x in j['atoms']},'nonfinite':0,'zero_area':0,'identity_node_transforms':True,'dormer_rear_buried_on_55_degree_roof':True,'shared_texture_files_identical':checked,'documented_audit_exception':a['duplicate_primitives'],'audit_exception_reason':'Separate dormer atoms intentionally reuse identical component geometry. No duplicate primitive inside a single atom.'}
(R/'validation.json').write_text(json.dumps(report,indent=2));print(json.dumps(report,indent=2))
# Keep the independent audit evidence current with this exact export.
(R/'audit_glb.json').write_text(json.dumps(a,indent=2))
assembly=R/'test_house.glb'
if assembly.exists():
 aa=audit(assembly);assert aa['zero_area_triangles']==aa['nonfinite_attribute_components']==0
 (R/'audit_assembly.json').write_text(json.dumps(aa,indent=2))
 raw=assembly.read_bytes();nn=struct.unpack_from('<I',raw,12)[0];ag=json.loads(raw[20:20+nn])
 counts={name:sum(node.get('name','').split('.')[0]==name for node in ag['nodes']) for name in expected}
 required={'thatch_panel':12,'thatch_ridge':3,'dormer_gabled':2,'chimney_cluster':1,'door_arched_stone':1,'window_cellar':1,'oriel':1,'sign_boot':1,'window_shuttered':1,'louvre_ridge':1,'pillory':1,'well_wheel':1}
 assert all(counts[k]==v for k,v in required.items()),counts
 ar={'status':'PASS','counts':{k:counts[k] for k in required},'rich_roof_pitch_degrees':55,'thatch_roof_pitch_degrees':55,'thatch_grid_per_side':[3,2],'source':'render_kit.py and exported glTF node census','glb_sha256':hashlib.sha256(raw).hexdigest(),'material_names':[m['name'] for m in ag['materials']]}
 (R/'assembly_validation.json').write_text(json.dumps(ar,indent=2))
# Frozen node interfaces and budgets survive this revision.
baseline=R/'revision_2_before/kit.json'
if baseline.exists():
 old={v['node_name']:v for v in json.loads(baseline.read_text())['atoms']}
 for name,v in meta.items():
  for key in ['origin_rule','nominal_size_whd_metres','triangle_budget']:
   assert v[key]==old[name][key],(name,key)
assert 'Leather_brown' in meta['sign_boot']['materials']
assert 'Sign_paint' not in meta['sign_boot']['materials']
print('ASSEMBLY_AND_REVISION_CONTRACT_PASS')
