"""Four remaining enemy Studio trials; clouds run serially, Unity mutations stay serial.""" import argparse import hashlib import json import subprocess import sys from pathlib import Path ROOT=Path(__file__).resolve().parents[1] WORKBENCH=Path('D:/UD/AI/XModel/LLM3DGraphics/.venv/Scripts/python.exe') def main(): ap=argparse.ArgumentParser() ap.add_argument('--stage',choices=('cloud','finish','play','verify'),required=True) ap.add_argument('--actors',nargs='+') ap.add_argument('--play-check',action='store_true') a=ap.parse_args() config=json.loads((ROOT/'Config/Art/StudioTrials/Enemies.json').read_text(encoding='utf-8')) rows=[r for r in config['actors'] if not a.actors or r['actor'] in a.actors] if a.actors and set(a.actors)-{r['actor'] for r in rows}:raise ValueError('Unknown enemy') if a.stage=='play': from enemy_character_pipeline import unity,script,wait_editor if a.actors:raise ValueError('The combined Play check requires all four actors') unity('set_autotick',enable=True) if unity('editor_status')['playMode']!='stopped':raise RuntimeError('Preserve the current Play session; run this after it stops') unity('editor_play') try: wait_editor('playing');script('VerifyEnemyStudioBatch','VerifyEnemyStudioBatch.PlayAll',[]) finally:unity('editor_stop');wait_editor('stopped') for row in rows: folder=ROOT/'Tools/output/studio_trial'/row['actor'];path=folder/'trial.json';receipt=json.loads(path.read_text(encoding='utf-8')) if json.loads((folder/'unity_verification.json').read_text(encoding='utf-8'))['status']!='pass':raise ValueError('Edit QA missing') for relative,digest in receipt['originals'].items(): if hashlib.sha256((ROOT/relative).read_bytes()).hexdigest()!=digest:raise ValueError(relative+' changed') receipt['status']='validated';path.write_text(json.dumps(receipt,indent=2),encoding='utf-8') return for row in rows: actor=row['actor'] if hashlib.sha256((ROOT/row['source']).read_bytes()).hexdigest()!=row['sourceSha256']:raise ValueError(actor+' source changed') if a.stage=='cloud': cmd=[str(WORKBENCH),'Tools/studio_lowpoly_trial.py','--actor',actor,'--kind','Enemy','--source',row['source'],'--rig',row['rig'],'--prefab',row['prefab'],'--tris',str(config['targetTriangles']),'--size',str(config['textureSize'])] elif a.stage=='finish': cmd=[sys.executable,'Tools/studio_lowpoly_finish.py','--actor',actor,'--height',str(row['height']),'--recipe',row['recipe']] if a.play_check:cmd.append('--play-check') else: folder=ROOT/'Tools/output/studio_trial'/actor receipt=json.loads((folder/'trial.json').read_text(encoding='utf-8')) if receipt['status']!='validated':raise ValueError(actor+' has not passed actual Play') for path,digest in receipt['originals'].items(): if hashlib.sha256((ROOT/path).read_bytes()).hexdigest()!=digest:raise ValueError(path+' changed') for name in ('unity_verification.json','play_verification.json'): if json.loads((folder/name).read_text(encoding='utf-8'))['status']!='pass':raise ValueError(actor+' failed '+name) print('PASS '+actor+' validated; original protected hashes unchanged',flush=True) continue print('BEGIN '+actor+' '+a.stage,flush=True) child=subprocess.Popen(cmd,cwd=ROOT,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True,encoding='utf-8',errors='replace') for line in child.stdout:print(actor+': '+line.rstrip(),flush=True) if child.wait():raise RuntimeError(actor+' '+a.stage+' failed; completed task records are retained for resume') print('COMPLETE '+actor+' '+a.stage,flush=True) if __name__=='__main__':main()