#!/usr/bin/env python3
import json,os,http.server,socketserver,threading,time,urllib.parse
from pathlib import Path
from playwright.sync_api import sync_playwright
ROOT=Path('/mnt/data/v325root/NEXT11_V325_MCP_PROXIMAL_PALM_POINTS_ROOT');OUT=ROOT/'diagnostics/v325';OUT.mkdir(parents=True,exist_ok=True)
class Q(http.server.SimpleHTTPRequestHandler):
 def log_message(self,*a):pass
EVAL=r'''() => {const d=window.__NEXPROD01_DEBUG_SCENE||null,r=window.__NEXPROD01_GRIP_SOLVER_V1||null,s=window.__NEXPROD01_V325_ACTUAL_CALF_PALM_SEARCH_SOLVE||null,m=window.__NEXPROD01_V318_ACTUAL_CALF_PALM_MESH_TRUTH||null,g=window.__NEXPROD01_V311_ACTUAL_MCP_PALM_PREFINGER_GATE||window.__NEXPROD01_V308_ACTUAL_MCP_PALM_PREFINGER_GATE||null,f=window.__NEXPROD01_V301_FINGER_CHILD_LAYER_GATE||window.__NEXPROD01_V304_ZPLUS_SEQUENTIAL_FINGER_CONTACT||null;let bc=0,sm=0;try{d?.model?.traverse?.(o=>{if(o?.isBone)bc++;if(o?.isSkinnedMesh)sm++;});}catch(e){}return {hasModel:!!d?.model,runtimeVersion:r?.version||null,side:r?.side||null,solve:s,mesh:m,prefinger:g?{schema:g.schema,pass:g.pass,status:g.status,orientationFail:g.orientationFail,wrongWay:g.wrongWay}:null,finger:f?{active:f.active,blocked:f.blocked,ready:f.ready,reason:f.reason}:null,boneCount:bc,skinnedMeshCount:sm};}'''
def sm(v):
 v=v or {};s=v.get('solve') or {};a=s.get('after') or {};m=v.get('mesh') or {};g=v.get('prefinger') or {};f=v.get('finger') or {}
 return {'hasModel':v.get('hasModel'),'runtimeVersion':v.get('runtimeVersion'),'side':v.get('side'),'solvePass':s.get('pass'),'firstPalm':s.get('firstPalm'),'rollDeg':s.get('rollDeg'),'changedFingerLocals':len(s.get('changedFingerLocals') or []),'indexDist':(a.get('index') or {}).get('distanceM'),'pinkyDist':(a.get('pinky') or {}).get('distanceM'),'palmFacesCalf':a.get('palmFacesCalf'),'meshPass':m.get('pass'),'meshStatus':m.get('status'),'meshGapM':m.get('gapM'),'nearestPalmCalfM':m.get('nearestPalmCalfM'),'nearestPalmFootM':m.get('nearestPalmFootM'),'prefingerPass':g.get('pass'),'prefingerStatus':g.get('status'),'fingerBlocked':f.get('blocked'),'fingerActive':f.get('active'),'boneCount':v.get('boneCount'),'skinnedMeshCount':v.get('skinnedMeshCount')}
def main():
 os.chdir(ROOT);srv=socketserver.TCPServer(('127.0.0.1',0),Q);threading.Thread(target=srv.serve_forever,daemon=True).start();port=srv.server_address[1];rows=[]
 try:
  with sync_playwright() as p:
   env=os.environ.copy();env['LIBGL_ALWAYS_SOFTWARE']='1';env['VK_ICD_FILENAMES']='/usr/lib/chromium/vk_swiftshader_icd.json';env['DISPLAY']=''
   b=p.chromium.launch(executable_path='/usr/bin/chromium',headless=True,env=env,args=['--no-sandbox','--disable-dev-shm-usage','--ignore-gpu-blocklist','--enable-webgl','--enable-webgl2','--enable-unsafe-swiftshader','--disable-gpu-sandbox','--ozone-platform=headless','--use-gl=angle','--use-angle=swiftshader-webgl'])
   c=b.new_context(viewport={'width':1400,'height':1000})
   for name,case,phase in [('right','v94_right_full','0.75'),('left','v94_left_full','0.25')]:
    pg=c.new_page();qs=urllib.parse.urlencode({'exercise':'hurdle-stretch','caseId':case,'phase':phase,'play':'0','visualMode':'nex','nativeRuntimeParity':'1','nativeSkin':'1','skinPreset':'freeridehuman','safeBoot':'0','lowPower':'1','maxFPS':'24','readyTimeoutMs':'18000','build':'V325_ACTUAL_CALF_PALM_SEARCH','v':'V325_'+name});url=f'http://127.0.0.1:{port}/assets/nexprod/nexprod01-player.html?{qs}'
    try:pg.goto(url,wait_until='domcontentloaded',timeout=15000)
    except Exception:pass
    val=None
    for _ in range(28):
     pg.wait_for_timeout(500)
     try:val=pg.evaluate(EVAL)
     except Exception:continue
     if val.get('hasModel') and val.get('solve') is not None and val.get('mesh') is not None:break
    shot=OUT/f'V325_{name}_canvas.png'
    try:pg.locator('canvas').first.screenshot(path=str(shot),timeout=8000)
    except Exception:pass
    rows.append({'name':name,'value':val,'summary':sm(val),'screenshot':str(shot)});pg.close()
   b.close()
 finally:srv.shutdown();srv.server_close()
 audit={'schema':'NEXT11_V325_BROWSER_AUDIT','generatedAt':time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),'rows':rows,'summaries':[r['summary'] for r in rows]}
 audit['passBrowserRuntime']=all(x['summary']['hasModel'] and x['summary']['runtimeVersion']=='V325_ACTUAL_CALF_PALM_SEARCH' for x in rows);audit['noFingerMutation']=all(x['summary']['changedFingerLocals']==0 for x in rows);audit['fingersBlocked']=all(x['summary']['fingerBlocked'] is True and x['summary']['fingerActive'] is not True for x in rows)
 (OUT/'V325_BROWSER_AUDIT.json').write_text(json.dumps(audit,indent=2));print(json.dumps(audit,indent=2))
if __name__=='__main__':main()
