<!-- SOLID CITY · part 3599 -->
 ES module.
 * No registry assumed. No scene mutation. Call explicitly, never per frame.
 */
const finite = Number.isFinite;
const length3 = v => Math.hypot(...v);
const sub = (a,b) => [a[0]-b[0],a[1]-b[1],a[2]-b[2]];
const cross = (a,b) => [a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]];
const dot = (a,b) => a[0]*b[0]+a[1]*b[1]+a[2]*b[2];
const mix = (a,b,c,w) => a.map((x,i)=>w[0]*x+w[1]*b[i]+w[2]*c[i]);
const dist = (a,b) => length3(sub(a,b));
function worldVertex(mesh,index){
  const p=mesh.geometry.getAttribute('position');
  const e=mesh.matrixWorld.elements, x=p.getX(index),y=p.getY(index),z=p.getZ(index);
  return [e[0]*x+e[4]*y+e[8]*z+e[12],e[1]*x+e[5]*y+e[9]*z+e[13],e[2]*x+e[6]*y+e[10]*z+e[14]];
}
function triangles(mesh){
  if(!mesh || !mesh.isMesh || !mesh.geometry?.getAttribute('position'))throw Error('mesh with position attribute required');
  mesh.updateWorldMatrix(true,false);
  const g=mesh.geometry, pos=g.getAttribute('position'), ind=g.getIndex();
  const n=ind?ind.count:pos.count;
  if(n%3)throw Error('triangle count is not divisible by three');
  const out=[];
  for(let i=0;i<n;i+=3){
    const ids=[0,1,2].map(k=>ind?ind.getX(i+k):i+k);
    if(ids.some(id=>!Number.isInteger(id)||id<0||id>=pos.count))throw Error('invalid vertex index');
    out.push({id:i/3,ids,verts:ids.map(id=>worldVertex(mesh,id))});
  }
  return out;
}
function key(v,tol){return v.map(n=>Math.round(n/tol)).join(':');}
function edgeKey(a,b,tol){const ka=key(a,tol),kb=key(b,tol);return ka<kb?ka+'|'+kb:kb+'|'+ka;}
function horizontalHeight(tri,x,z,tol=1e-9){
  const [a,b,c]=tri.verts;
  const den=(b[2]-c[2])*(a[0]-c[0])+(c[0]-b[0])*(a[2]-c[2]);
  if(Math.abs(den)<tol)return null;
  const u=((b[2]-c[2])*(x-c[0])+(c[0]-b[0])*(z-c[2]))/den;
  const v=((c[2]-a[2])*(x-c[0])+(a[0]-c[0])*(z-c[2]))/den;
  const w=1-u-v;
  if(Math.min(u,v,w)<-1e-7)return null;
  return u*a[1]+v*b[1]+w*c[1];
}
function quantile(a,p){if(!a.length)return null;const b=[...a].sort((x,y)=>x-y);return b[Math.min(b.length-1,Math.floor(p*(b.length-1)))];}
/**
 * auditStructure({mesh,name,standAt,expectedUp,edgeTolerance,areaEpsilon,
 *   samplesPerTriangle,maxSamples,standTolerance,referenceSurface})
 * standAt(x,z,referenceY,context) is supplied by Solid; return finite y or null.
 * referenceY is triangle height. It is NOT an avatar foot position.
 * standAt must query the intended structure's registered surface, not global terrain.
 * referenceSurface is optional (x,z)=>height for independent comparison.
 * This instrument only reads; it never registers or refuses a build.
 */
export function auditStructure({mesh,name=mesh?.name||'unnamed',standAt=null,
  expectedUp=null,edgeTolerance=0.002,areaEpsilon=1e-8,
  samplesPerTriangle=2,maxSamples=4000,standTolerance=0.01,referenceSurface=null}={}){
  if(!(edgeTolerance>0) || !(areaEpsilon>0) || !(maxSamples>0))throw Error('invalid audit thresholds');
  const ts=triangles(mesh), edges=new Map(), areas=[],qualities=[],angles=[];
  const out={name,triangles:ts.length,degenerate:0,nonFinite:0,up:0,down:0,vertical:0,
    windingConflicts:0,nonManifoldEdges:0,boundaryEdges:0,internalSeamMaxDelta:0,
    minimumQuality:null,medianQuality:null,maxAspect:null,
    sampled:0,standMissing:0,standMaxDelta:null,standP95Delta:null,
    referenceSamples:0,referenceMaxDelta:null,unexaminedStand:standAt==null,
    findings:[],limits:[]};
  const standErrors=[],refErrors=[];
  for(const t of ts){
    const [a,b,c]=t.verts;
    if(t.verts.some(v=>v.some(n=>!finite(n)))){out.nonFinite++;continue;}
    const e0=sub(b,a),e1=sub(c,a),normal=cross(e0,e1),twiceArea=length3(normal),area=twiceArea/2;
    if(area<=areaEpsilon){out.degenerate++;continue;}
    areas.push(area);
    if(normal[1]>areaEpsilon)out.up++;else if(normal[1]<-areaEpsilon)out.down++;else out.vertical++;
    const sides=[dist(a,b),dist(b,c),dist(c,a)],s2=sides.reduce((s,v)=>s+v*v,0);
    const quality=4*Math.sqrt(3)*area/s2; // 1 equilateral, ->0 slender
    qualities.push(quality);
    const aspect=Math.max(...sides)/Math.min(...sides);
    angles.push(aspect);
    for(const [v0,v1] of [[a,b],[b,c],[c,a]]){
      const k=edgeKey(v0,v1,edgeTolerance),arr=edges.get(k)||[];
      arr.push({a:v0,b:v1,tri:t.id});edges.set(k,arr);
    }
  }
  for(const arr of edges.values()){
    if(arr.length===1){out.boundaryEdges++;continue;}
    if(arr.length!==2){out.nonManifoldEdges++;continue;}
    // Directed edges of consistently oriented neighboring triangles must oppose.
    const [p,q]=arr;
    const same=dist(p.a,q.a)+dist(p.b,q.b),opposite=dist(p.a,q.b)+dist(p.b,q.a);
    if(same<opposite)out.windingConflicts++;
    out.internalSeamMaxDelta=Math.max(out.internalSeamMaxDelta,
      Math.abs(p.a[1]- (same<opposite?q.a[1]:q.b[1])),
      Math.abs(p.b[1]- (same<opposite?q.b[1]:q.a[1])));
  }
  out.minimumQuality=qualities.length?Math.min(...qualities):null;
  out.medianQuality=quantile(qualities,0.5);
  out.maxAspect=angles.length?