"""Synthetic geometry tests, not game captures. Anchors are mock rig outputs."""
from pathlib import Path
import json,math,random,copy
from lupa.luajit21 import LuaRuntime
R=Path(__file__).resolve().parents[1];L=LuaRuntime(unpack_returned_tuples=True)
M=L.execute((R/'src/mount_policy.lua').read_text());P=L.execute((R/'src/spatial_policy.lua').read_text())
profiles=json.loads((R/'validation/mount-schema.json').read_text())['profiles'];rng=random.Random(704)
def lua(v):
 if isinstance(v,dict):return L.table_from({k:lua(x)for k,x in v.items()})
 if isinstance(v,list):return L.table_from([lua(x)for x in v])
 return v
def d(i,r):return {'entity':i,'goid':i+100,'unit':4194560+i,'resource':r}
def body(i,r,center,angle):
 slots={};a=math.radians(angle)
 for side,x in [('L',-1),('R',1)]:
  v=profiles[r][side];slots[side]={'name':v['name'],'resource':v['resource'],'position':[center[0]+x*math.cos(a),center[1]+x*math.sin(a),center[2]+3]}
 return {'descriptor':d(i,r),'mounts':slots}
def arms_for(b,i,noise=0):
 return [{'descriptor':d(i+j,b['mounts'][s]['resource']),'position':[x+(rng.uniform(-noise,noise)if noise else 0)for x in b['mounts'][s]['position']]}for j,s in enumerate(['L','R'])]
def select(current,bs,aa):return M.select(lua(current),lua(bs),lua(aa),P.key,P.distance)
def entities(result):return [row['entity']for row in result.values()]if result else []
cases=allowed=refused=shuffles=0
for resource in profiles:
 for gap in [0,.25,.49,.5,.51,.75,1,1.5,2,3,4,6,8,12]:
  for angle_a in range(0,360,45):
   for angle_b in range(0,360,45):
    for height,noise in [(0,0),(.4,.03),(2,.06)]:
     bs=[body(1,resource,[0,0,0],angle_a),body(2,resource,[gap,0,height],angle_b)]
     aa=arms_for(bs[0],11,noise)+arms_for(bs[1],21,noise)
     for current,wanted in [(bs[0]['descriptor'],[11,12]),(bs[1]['descriptor'],[21,22])]:
      result,why,meta=select(current,bs,aa);got=entities(result);cases+=1
      if result:
       assert got==wanted,(gap,angle_a,angle_b,height,noise,wanted,got);allowed+=1
       assert meta['method']=='MOUNT_POINT_TEST'and meta['native_parent_proven']is False
       for row in meta['slots'].values():
        assert row['best_residual']<=.35
        if row['other_mount_gap']is not None:assert row['other_mount_gap']>=.5
        if row['other_arm_gap']is not None:assert row['other_arm_gap']>=.5
      else:
       assert why in ['MOUNT_COMPETING_ARM','MOUNT_COMPETING_SLOT'];refused+=1
      if cases%23==0:
       rng.shuffle(bs);rng.shuffle(aa);r2,w2,m2=select(current,bs,aa);assert entities(r2)==got and w2==why;shuffles+=1
# Explicit two-way ambiguity: unique arm but conflicting sockets, and vice versa.
r=next(iter(profiles));bs=[body(1,r,[0,0,0],0),body(2,r,[.4,0,0],0)];aa=arms_for(bs[0],11)
assert select(bs[0]['descriptor'],bs,aa)[1]=='MOUNT_COMPETING_SLOT'
bs=bs[:1];aa+=copy.deepcopy(aa[:1]);aa[-1]['descriptor']=d(99,aa[-1]['descriptor']['resource']);aa[-1]['position'][0]+=.4
assert select(bs[0]['descriptor'],bs,aa)[1]=='MOUNT_COMPETING_ARM'
# Missing/destroyed arm: return only present, fitted descriptor, never fabricate.
aa=arms_for(bs[0],11)[1:];res,why,_=select(bs[0]['descriptor'],bs,aa);assert entities(res)==[12]
aa[0]['position'][0]+=.36;assert select(bs[0]['descriptor'],bs,aa)[1]=='MOUNT_NO_MATCH'
# Mixed resources: coincident geometric points are not shared weapon types.
bs=[body(i+1,r,[0,0,0],0)for i,r in enumerate(profiles)];aa=[]
for i,b in enumerate(bs):aa+=arms_for(b,11+10*i)
for i,b in enumerate(bs):assert entities(select(b['descriptor'],bs,aa)[0])==[11+10*i,12+10*i]
# Invalid data and count limits cannot produce a match.
bad=copy.deepcopy(bs);bad[0]['mounts']['L']['name']='invented_node';assert select(bs[0]['descriptor'],bad,aa)[1]=='MOUNT_SLOT_INVALID'
bad=copy.deepcopy(bs);bad[0]['mounts']['L']['position'][0]=float('nan');assert select(bs[0]['descriptor'],bad,aa)[1]=='MOUNT_SLOT_INVALID'
assert select(bs[0]['descriptor'],bs*3,aa)[1]=='MOUNT_COUNT_BOUND'
assert select(bs[0]['descriptor'],bs,aa*3)[1]=='MOUNT_COUNT_BOUND'
assert select(bs[0]['descriptor'],bs,aa+[aa[0]])[1]=='MOUNT_ARM_INVALID'
report={'synthetic_driver_views':cases,'accepted':allowed,'refused':refused,'accepted_wrong_mech':0,'order_shuffle_checks':shuffles,'body_resources':4,'two_way_ambiguity_refused':True,'missing_arm_not_fabricated':True,'mixed_models_coincident_points_typed_correctly':True,'fixtures':'Synthetic named rig anchors, varying yaw, height, noise and separation. Not measured game mount coordinates.','live_success_claimed':False}
(R/'validation/mount-policy-tests.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report))
