Add formal enemies, remaining formal partners and synthesized UI SFX
- UI SFX v3: procedurally synthesized one-shots (Tools/synth_ui_sfx.py) wired to buttons, rewards, errors and red dots via UiSfx, gated by the SFX setting. - Five formal enemies (Hornlet, Dashlet, Shellhorn, HornCaptain, GreatHorn): Hunyuan 50k -> 2000-tri XPbr, DGXA TokenRig, Idle/Walk/Attack, nested per type in PrototypeArena; PrototypeBattle.Enemy gains presentation-only Moving/Strikes. - Formal partners for all ten pets in the arena and portraits, including new VineSprite, ThunderLeopard and TideWhale. - Pipeline fixes: reject local TokenRig fallback, drop loose mesh islands, configurable Unity run_script timeout, companion static stage. - Also includes concurrent work from another tool in this working tree (FlameLion, enemy combat FX, challenge mode, home explorer, server updates). - Resources registered in Doc/ResDesc_*.md; verified on isolated :18081. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,20 +31,67 @@ 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.')
|
||||
if r['profile'] not in ('biped_short','avian_grounded','quadruped_turtle','quadruped_heavy'):
|
||||
raise ValueError('Unsupported anatomy. 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'):
|
||||
if r['attackStyle'] not in ('body_slam','ranged_cast','wing_cast','turtle_bubble','braced_bash','flame_roar'):
|
||||
raise ValueError('Unsupported attack style')
|
||||
if (r['profile']=='avian_grounded') != (r['attackStyle']=='wing_cast'):
|
||||
raise ValueError('Grounded birds require wing_cast; wing_cast requires avian_grounded')
|
||||
if (r['profile']=='quadruped_turtle') != (r['attackStyle']=='turtle_bubble'):
|
||||
raise ValueError('Turtles require their own four-legged bubble adapter')
|
||||
if (r['profile']=='quadruped_heavy') != (r['attackStyle']=='braced_bash'):
|
||||
raise ValueError('Heavy quadrupeds require their own braced bash adapter')
|
||||
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['attackStyle']=='flame_roar':
|
||||
required=('rootBone','chestBone','neckBone','headBone','rightArm','leftArm','rightPawTip','leftPawTip',
|
||||
'rightFoot','leftFoot','rightFootTip','leftFootTip','faceRight','faceLeft')
|
||||
if r['profile']!='biped_short' or any(not re.fullmatch(r'bone_\d+',r.get(k,'')) for k in required):
|
||||
raise ValueError('Flame roar requires this biped\'s own head, neck, two arms/paws and feet')
|
||||
for right,left in (('rightArm','leftArm'),('rightPawTip','leftPawTip'),('rightFoot','leftFoot'),('rightFootTip','leftFootTip'),('faceRight','faceLeft'),('headBone','chestBone')):
|
||||
if r[right]==r[left]:raise ValueError('Flame roar requires independent head/body and left/right mappings')
|
||||
lo,hi=r.get('maneRegionMin',[]),r.get('maneRegionMax',[])
|
||||
if len(lo)!=3 or len(hi)!=3 or any(a>=b for a,b in zip(lo,hi)) or not 0<r.get('maneRigidityTolerance',0)<=.002:
|
||||
raise ValueError('Inspect the mane geometry region and rigidity budget')
|
||||
if not (0<r.get('attackHeadReach',0)<r['height']*.1 and 0<r.get('attackHeadPitchDegrees',0)<=15 and 0<r.get('attackSpreadDegrees',0)<=45):
|
||||
raise ValueError('Invalid flame roar head reach, pitch or paw spread')
|
||||
if r['profile'] in ('quadruped_turtle','quadruped_heavy'):
|
||||
feet=('frontRightFoot','frontLeftFoot','hindRightFoot','hindLeftFoot')
|
||||
tips=tuple(k+'Tip' for k in feet)
|
||||
body='shellBone' if r['profile']=='quadruped_turtle' else 'chestBone'
|
||||
if any(not re.fullmatch(r'bone_\d+',r.get(k,'')) for k in ('rootBone',body,'neckBone','headBone','faceRight','faceLeft',*feet,*tips)):
|
||||
raise ValueError('Map this quadruped\'s own body, head/neck and four paws')
|
||||
if len({r[k] for k in feet})!=4 or len({r[k] for k in tips})!=4 or r['faceRight']==r['faceLeft']:
|
||||
raise ValueError('Four separate paw mappings and distinct facing references required')
|
||||
if r.get('rootTiltReference')!='bind_up' or not 0<r.get('walkSwingFraction',0)<=.25:
|
||||
raise ValueError('Quadrupeds require bind-up orientation and a four-beat support gait')
|
||||
if not 0<r.get('attackHeadReach',0)<r['height']*.15:
|
||||
raise ValueError('Invalid quadruped head reach')
|
||||
regions=('shell',) if r['profile']=='quadruped_turtle' else ('armor','horn')
|
||||
for region in regions:
|
||||
lo,hi=r.get(region+'RegionMin',[]),r.get(region+'RegionMax',[])
|
||||
if len(lo)!=3 or len(hi)!=3 or any(a>=b for a,b in zip(lo,hi)) or not 0<r.get(region+'RigidityTolerance',0)<=.002:
|
||||
raise ValueError('Inspected '+region+' geometry region and rigidity budget required')
|
||||
if r['profile']=='quadruped_heavy' and (r['chestBone']==r['headBone'] or not 0<r.get('attackLunge',0)<r['height']*.12 or not 0<r.get('attackHeadPitchDegrees',0)<=20):
|
||||
raise ValueError('Heavy bash requires independent head/chest, a short shoulder lunge and controlled head pitch')
|
||||
if r['profile']=='avian_grounded':
|
||||
required=('rootBone','chestBone','rightArm','leftArm','rightWingTip','leftWingTip',
|
||||
'rightFoot','leftFoot','rightFootTip','leftFootTip','faceRight','faceLeft')
|
||||
if any(not re.fullmatch(r'bone_\d+',r.get(k,'')) for k in required):
|
||||
raise ValueError('Inspect and map this bird\'s own wings, wing tips and feet')
|
||||
for right,left in (('rightArm','leftArm'),('rightWingTip','leftWingTip'),
|
||||
('rightFoot','leftFoot'),('rightFootTip','leftFootTip'),('faceRight','faceLeft')):
|
||||
if r[right]==r[left]:raise ValueError('Bird left/right mappings must be distinct')
|
||||
if not 0<r.get('wingSpreadDegrees',0)<=60:
|
||||
raise ValueError('Bird wingSpreadDegrees must be in (0,60]')
|
||||
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'):
|
||||
@@ -69,10 +116,19 @@ def recipe(path):
|
||||
return r
|
||||
|
||||
def unity(command,**kwargs):
|
||||
args=['unity','--json','command','--project-path',str(PROJECT),command]
|
||||
args=['unity','--json','command','--project-path',str(PROJECT)]
|
||||
if command=='run_script':
|
||||
# Import/refresh and ephemeral compilation can delay dispatch in a live
|
||||
# Editor. Bound both server dispatch and CLI response, without retrying
|
||||
# a mutation whose outcome is unknown.
|
||||
# AIGAME_RUN_SCRIPT_TIMEOUT_MS raises the window for heavy QA (31-bone quadruped skin sampling took >90s).
|
||||
server_ms=int(os.environ.get('AIGAME_RUN_SCRIPT_TIMEOUT_MS','90000'))
|
||||
args.extend(['--timeout',str(server_ms//1000+30)])
|
||||
kwargs.setdefault('timeout_ms',server_ms)
|
||||
args.append(command)
|
||||
for k,v in kwargs.items():
|
||||
args.extend(['--'+k, json.dumps(v,ensure_ascii=False) if isinstance(v,(list,dict,bool)) else str(v)])
|
||||
proc=subprocess.run(args,cwd=REPO,capture_output=True,text=True,encoding='utf-8',errors='replace',timeout=180)
|
||||
proc=subprocess.run(args,cwd=REPO,capture_output=True,text=True,encoding='utf-8',errors='replace',timeout=int(os.environ.get('AIGAME_RUN_SCRIPT_TIMEOUT_MS','90000'))//1000+90)
|
||||
if proc.returncode:
|
||||
raise RuntimeError(proc.stderr or proc.stdout)
|
||||
data=json.loads(proc.stdout)
|
||||
@@ -156,10 +212,14 @@ def main():
|
||||
for name,record in sources.items():
|
||||
unity('import_asset',source=record['source'],path=r['assetRoot']+'/Anims/'+r['id']+'_'+name+'.fbx',confirm=True)
|
||||
script('SetupAnimalAnimator','SetupAnimalAnimator.Run',[r['kind'],r['id'],r['height'],False])
|
||||
script('FinalizeAnimalWalk','FinalizeAnimalWalk.RunFor',[r['assetRoot'],r['id'],r['height'],r['rootBone'],r['rightFoot'],r['leftFoot'],r['walkStride'],r['walkLift'],r['walkSeconds'],r['outputDir'],r.get('walkBindCalibration',False),r.get('walkSourceWeight',1)])
|
||||
relative=recipe_path.relative_to(REPO).as_posix()
|
||||
if r['profile'] in ('quadruped_turtle','quadruped_heavy'):
|
||||
script('FinalizeAnimalWalk','FinalizeAnimalWalk.RunQuadruped',[relative])
|
||||
else:
|
||||
script('FinalizeAnimalWalk','FinalizeAnimalWalk.RunFor',[r['assetRoot'],r['id'],r['height'],r['rootBone'],r['rightFoot'],r['leftFoot'],r['walkStride'],r['walkLift'],r['walkSeconds'],r['outputDir'],r.get('walkBindCalibration',False),r.get('walkSourceWeight',1)])
|
||||
script('FinalizeEnemyMotions','FinalizeEnemyMotions.Run',[relative])
|
||||
script('VerifyEnemyMotions','VerifyEnemyMotions.Run',[relative])
|
||||
verifier='VerifyQuadrupedMotions' if r['profile'] in ('quadruped_turtle','quadruped_heavy') else 'VerifyEnemyMotions'
|
||||
script(verifier,verifier+'.Run',[relative])
|
||||
script('BuildEnemyReview','BuildEnemyReview.Publish',[relative])
|
||||
if args.build_review:
|
||||
script('BuildEnemyReview','BuildEnemyReview.Build',[relative])
|
||||
@@ -169,7 +229,7 @@ def main():
|
||||
unity('editor_play')
|
||||
try:
|
||||
wait_editor('playing')
|
||||
live_result=script('VerifyEnemyMotions','VerifyEnemyMotions.Live',[relative])['result']
|
||||
live_result=script(verifier,verifier+'.Live',[relative])['result']
|
||||
prefix='CompanionReview_'+r['id'] if r['usage']=='companion_visual' else 'EnemyReview'
|
||||
role='companion' if r['usage']=='companion_visual' else 'enemy'
|
||||
unity('capture_game_view',camera=prefix+'Camera',width=1900,height=760,
|
||||
|
||||
Reference in New Issue
Block a user