# _torch_penetration_test.py — 토치 관통 검출 실효성 검증 [CEO 지시 2026-07-19]
#   배경: 제품 메쉬 음수 스케일로 레이캐스트가 면을 놓쳐 관통해도 무경고였음 (CEO 실사례).
#   방법: 제품 살 속 점을 광선으로 구해, 실제 검사 표본 하나를 그 점에 정확히 배치 → 검출돼야 함.
#   원상복구 포함 (규약 제8조 — 메인 상태 오염 금지).
import asyncio, json
from playwright.async_api import async_playwright

JS = """()=>{
    const a=window.__dzw, T=window.THREE, out={};
    if(!window.DZW_COLLIDE) return {준비:false};
    DZW_COLLIDE.rebuild();
    const mesh=a._knuckleMesh;
    const ray=new T.Raycaster(new T.Vector3(1800,-100,-300).applyMatrix4(mesh.matrixWorld),
                              new T.Vector3(0,1,0).transformDirection(mesh.matrixWorld).normalize());
    ray.far=10;
    const hits=ray.intersectObject(mesh,false);
    if(hits.length<2) return {준비:true, 교차수:hits.length};
    const inside=hits[0].point.clone().add(hits[1].point).multiplyScalar(0.5);
    const tm=a._torchMesh, pos=tm.geometry.attributes.position, n=pos.count;
    const tip=new T.Vector3(38.7211,-22.4686,8.5812);
    const stride=Math.max(1,Math.floor(n/240));
    let s0=null;
    for(let i=0;i<n;i+=stride){ const v=new T.Vector3(pos.getX(i),pos.getY(i),pos.getZ(i));
      if(v.distanceTo(tip)<25) continue; s0=v; break; }
    tm.updateMatrixWorld(true);
    const keep=tm.matrix.clone(), keepAuto=tm.matrixAutoUpdate;
    const pInv=new T.Matrix4().copy(tm.parent.matrixWorld).invert();
    const w0=s0.clone().applyMatrix4(tm.matrixWorld);
    const dP=inside.clone().applyMatrix4(pInv).sub(w0.clone().applyMatrix4(pInv));
    tm.matrixAutoUpdate=false;
    tm.matrix.premultiply(new T.Matrix4().makeTranslation(dP.x,dP.y,dP.z));
    tm.updateMatrixWorld(true);
    let hit=null; try{ hit=DZW_COLLIDE.check(a); }catch(e){ hit=null; out.오류=e.message.slice(0,80); }
    out.준비=true; out.관통검출=!!hit&&typeof hit==='object';
    tm.matrix.copy(keep); tm.matrixAutoUpdate=keepAuto; tm.updateMatrixWorld(true);   // 원상복구
    let after=null; try{ after=DZW_COLLIDE.check(a); }catch(_){}
    out.복원후무충돌=!after;
    return out;
}"""

async def main():
    async with async_playwright() as p:
        br = await p.chromium.launch(headless=True)
        pg = await (await br.new_context()).new_page()
        await pg.set_viewport_size({'width': 1600, 'height': 900})
        await pg.goto('http://localhost:8090/DOZIKWORKS_OS_v5_locked.dc.html')
        await pg.wait_for_timeout(13000)
        r = await pg.evaluate(JS)
        print('상태:', json.dumps(r, ensure_ascii=False))
        ok = r.get('준비') and r.get('관통검출') is True and r.get('복원후무충돌') is True
        print()
        print('=== 결과:', 'PASS' if ok else 'FAIL')
        await br.close()
        return 0 if ok else 1

raise SystemExit(asyncio.run(main()))
