"""Recipe-driven character art production: same rig -> three clips -> visual prefab -> QA. Default is local cached production. --generate explicitly requests DGXA inference. Unity-managed assets are imported/authored only through Unity Pipeline. """ from __future__ import annotations import argparse import hashlib import json import os from pathlib import Path import re import shutil import subprocess import sys import time REPO=Path(__file__).resolve().parents[1] PROJECT=REPO/'AIClient' def sha(path): return hashlib.sha256(Path(path).read_bytes()).hexdigest() def resolve(path): result=(REPO/path).resolve() if not result.is_relative_to(REPO): raise ValueError(f'Path leaves repository: {path}') return result def recipe(path): r=json.loads(path.read_text(encoding='utf-8-sig')) if r.get('schemaVersion')!=1 or not re.fullmatch(r'[A-Za-z][A-Za-z0-9_]*',r['id']): raise ValueError('Unsupported schema / invalid actor id') if r['profile']!='biped_short': raise ValueError('This offline motion adapter supports biped_short. Add and validate a different anatomy adapter before using it.') r.setdefault('usage','enemy_visual') if r['usage'] not in ('enemy_visual','companion_visual'): raise ValueError('Unsupported character usage') r.setdefault('visualPrefab',r.get('enemyPrefab','')) r.setdefault('attackStyle','body_slam') if r['attackStyle'] not in ('body_slam','ranged_cast'): raise ValueError('Unsupported attack style') r.setdefault('attackEvent','OnCompanionAttackImpact' if r['usage']=='companion_visual' else 'OnEnemyAttackImpact') if not re.fullmatch(r'[A-Za-z][A-Za-z0-9_]*',r['attackEvent']): raise ValueError('Invalid attack event') if r.get('boneMappingReady') is False: raise ValueError('Inspect this character\'s generated rig and complete its bone mapping before production') if r['assetRoot']!=f"Assets/Bundles/Characters/{r['kind']}/{r['id']}": raise ValueError('assetRoot must match kind/id') for key in ('assetRoot','visualPrefab','staticPrefab','reviewScene'): if not r[key].startswith('Assets/Bundles/') or '..' in Path(r[key]).parts: raise ValueError(f'Invalid Unity asset path: {key}') for key in ('approvedMesh','rigSource','outputDir'): resolve(r[key]) if not (.05