"""Loadout Game-3D harness v0.1: objective checks on a GLB file.
Checks: loads, triangle/vertex counts, mesh count, UV presence, material/texture count,
texture resolutions, watertightness, non-manifold edge share, bounding box, skinning/animation presence."""
import sys, json, struct, trimesh, numpy as np
def gltf_json(path):
    b=open(path,'rb').read(); assert b[:4]==b'glTF'
    l=struct.unpack('<I',b[12:16])[0]; return json.loads(b[20:20+l])
def inspect(path):
    j=gltf_json(path)
    scene=trimesh.load(path, force='scene')
    geoms=list(scene.geometry.values())
    tris=sum(len(g.faces) for g in geoms); verts=sum(len(g.vertices) for g in geoms)
    uv=[getattr(g.visual,'uv',None) is not None for g in geoms]
    wt=[bool(g.is_watertight) for g in geoms]
    nm=[]
    for g in geoms:
        e=g.edges_sorted; _,c=np.unique(e,axis=0,return_counts=True); nm.append(float((c>2).sum()/max(len(c),1)))
    imgs=[]
    for g in geoms:
        m=getattr(g.visual,'material',None)
        for a in ['baseColorTexture','normalTexture','metallicRoughnessTexture','emissiveTexture','occlusionTexture']:
            im=getattr(m,a,None) if m is not None else None
            if im is not None: imgs.append(f"{a}:{im.size[0]}x{im.size[1]}")
    return {"file":path.split('/')[-1],"loads":True,"meshes":len(geoms),"triangles":int(tris),"vertices":int(verts),
      "all_meshes_have_uvs":all(uv),"materials":len(j.get('materials',[])),"textures":len(j.get('textures',[])),
      "texture_maps":sorted(set(imgs)),"watertight_meshes":f"{sum(wt)}/{len(wt)}",
      "max_nonmanifold_edge_share":round(max(nm) if nm else 0,4),
      "bbox_extents":[round(float(x),3) for x in scene.extents],
      "skins":len(j.get('skins',[])),"animations":len(j.get('animations',[])),
      "generator":j.get('asset',{}).get('generator')}
if __name__=='__main__':
    print(json.dumps([inspect(p) for p in sys.argv[1:]],indent=1))
