Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore unversioniert; letztere sind unter ../backups gesichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
37 lines
2.2 KiB
Python
37 lines
2.2 KiB
Python
"""Validate the portable skin/animation asset, independent of Godot's wrapper."""
|
|
from pathlib import Path
|
|
import json,math,sys
|
|
sys.path.insert(0,str(Path(__file__).resolve().parent))
|
|
from verify_phase4_glb import GLB,topology
|
|
p=Path(__file__).resolve().parents[1];g=GLB(p/'assets/characters/ringborke.glb');d=g.doc
|
|
checks=[]
|
|
def check(v,msg):
|
|
assert v,msg
|
|
checks.append(msg)
|
|
check(len(d['skins'])==1,'one portable skin')
|
|
check(len(d['skins'][0]['joints'])==27,'27 distinct joint bindings')
|
|
expected={'idle','start','walk','stop','attack_a','attack_b','hit','ability','death'}
|
|
check({a['name'] for a in d['animations']}==expected,'nine complete clips')
|
|
weighted=0;total=0;max_error=0
|
|
for m in d['meshes']:
|
|
for q in m['primitives']:
|
|
attr=q['attributes']
|
|
vertices=g.accessor(attr['POSITION']);indices=[x[0] for x in g.accessor(q['indices'])]
|
|
t=topology(vertices,[indices[i:i+3] for i in range(0,len(indices),3)])
|
|
check(t['closed'] and t['signed_volume']>0 and t['degenerate_triangles']==0,'closed outward geometry '+d['materials'][q['material']]['name'])
|
|
check(all(x in attr for x in ('JOINTS_0','WEIGHTS_0','POSITION','NORMAL','TEXCOORD_0')),'skin, normals and UVs')
|
|
joints=g.accessor(attr['JOINTS_0']);weights=g.accessor(attr['WEIGHTS_0']);normals=g.accessor(attr['NORMAL'])
|
|
for js,ws,n in zip(joints,weights,normals):
|
|
assert all(0<=j<27 for j in js)
|
|
assert all(math.isfinite(w) and 0<=w<=1 for w in ws)
|
|
max_error=max(max_error,abs(sum(ws)-1));weighted+=sum(w>.001 for w in ws)>1;total+=1
|
|
assert all(math.isfinite(v) for v in n)
|
|
check(max_error<.0001,'all vertex weights normalized')
|
|
check(weighted/total>.15,'flexible tissue has blended joints')
|
|
for a in d['animations']:
|
|
check(len(a['channels'])>5,a['name']+' animates multiple joints')
|
|
for samp in a['samplers']:
|
|
times=[v[0] for v in g.accessor(samp['input'])];assert times==sorted(times) and times[0]>=0
|
|
assert all(math.isfinite(x) for row in g.accessor(samp['output']) for x in row)
|
|
report={'checks':len(checks),'failures':[],'vertices':total,'blended_vertices':weighted,'blended_percent':round(weighted/total*100,2),'max_weight_sum_error':max_error,'clips':sorted(expected),'bones':27}
|
|
(p/'docs/phase11-skin-audit.json').write_text(json.dumps(report,indent=2)+'\n');print(report)
|