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:
udx
2026-10-21 12:23:55 +08:00
co-authored by Claude Opus 5.5
parent 6c64210331
commit 89e0fa246f
780 changed files with 7117051 additions and 15906 deletions
@@ -48,6 +48,69 @@ class EnemyProductionTests(unittest.TestCase):
def test_generation_success_commits_matching_receipt_last(self):
self.run_generation(True)
def test_flame_roar_requires_own_head_paws_and_mane_region(self):
lion={**self.data,'profile':'biped_short','attackStyle':'flame_roar','boneMappingReady':True,
'rootBone':'bone_0','chestBone':'bone_1','neckBone':'bone_2','headBone':'bone_3',
'rightArm':'bone_4','leftArm':'bone_6','rightPawTip':'bone_5','leftPawTip':'bone_7',
'rightFoot':'bone_8','leftFoot':'bone_10','rightFootTip':'bone_9','leftFootTip':'bone_11',
'faceRight':'bone_8','faceLeft':'bone_10','maneRegionMin':[-.5,.5,-.3],
'maneRegionMax':[.5,1,.3],'maneRigidityTolerance':.001,'attackHeadReach':.015,
'attackHeadPitchDegrees':8,'attackSpreadDegrees':25}
with mock.patch.object(Path,'read_text',return_value=json.dumps(lion)):
self.assertEqual(pipeline.recipe(Path('fixture'))['attackStyle'],'flame_roar')
for change in ({'headBone':''},{'headBone':'bone_1'},{'leftPawTip':'bone_5'},
{'maneRegionMin':[]},{'maneRigidityTolerance':.1},{'attackHeadPitchDegrees':40},
{'attackSpreadDegrees':0},{'profile':'quadruped_heavy'},{'boneMappingReady':False}):
with mock.patch.object(Path,'read_text',return_value=json.dumps({**lion,**change})),self.assertRaises(ValueError):
pipeline.recipe(Path('fixture'))
def test_turtle_requires_four_feet_shell_region_and_bind_up(self):
turtle={**self.data,'profile':'quadruped_turtle','attackStyle':'turtle_bubble','boneMappingReady':True,
'rootBone':'bone_0','shellBone':'bone_0','neckBone':'bone_1','headBone':'bone_2',
'faceRight':'bone_4','faceLeft':'bone_6','frontRightFoot':'bone_4','frontLeftFoot':'bone_6',
'hindRightFoot':'bone_8','hindLeftFoot':'bone_10','frontRightFootTip':'bone_5','frontLeftFootTip':'bone_7',
'hindRightFootTip':'bone_9','hindLeftFootTip':'bone_11','rootTiltReference':'bind_up','walkSwingFraction':.2,
'shellRegionMin':[-1,.3,-.2],'shellRegionMax':[1,1,1],'shellRigidityTolerance':.001,'attackHeadReach':.015}
with mock.patch.object(Path,'read_text',return_value=json.dumps(turtle)):
self.assertEqual(pipeline.recipe(Path('fixture'))['profile'],'quadruped_turtle')
for change in ({'hindLeftFoot':'bone_4'},{'hindLeftFootTip':'bone_5'},{'shellRegionMin':[]},
{'rootTiltReference':'bone_up'},{'walkSwingFraction':.5},{'attackStyle':'ranged_cast'},
{'headBone':''},{'boneMappingReady':False},{'shellRigidityTolerance':.1}):
with mock.patch.object(Path,'read_text',return_value=json.dumps({**turtle,**change})),self.assertRaises(ValueError):
pipeline.recipe(Path('fixture'))
def test_heavy_bash_rejects_turtle_style_missing_rigid_regions_and_shared_head(self):
heavy={**self.data,'profile':'quadruped_heavy','attackStyle':'braced_bash','boneMappingReady':True,
'rootBone':'bone_0','chestBone':'bone_1','neckBone':'bone_2','headBone':'bone_3',
'faceRight':'bone_5','faceLeft':'bone_7','frontRightFoot':'bone_5','frontLeftFoot':'bone_7',
'hindRightFoot':'bone_9','hindLeftFoot':'bone_11','frontRightFootTip':'bone_6','frontLeftFootTip':'bone_8',
'hindRightFootTip':'bone_10','hindLeftFootTip':'bone_12','rootTiltReference':'bind_up','walkSwingFraction':.2,
'armorRegionMin':[-.3,.3,-.1],'armorRegionMax':[.3,.7,.5],'armorRigidityTolerance':.001,
'hornRegionMin':[-.1,.7,-.6],'hornRegionMax':[.1,1,-.4],'hornRigidityTolerance':.001,
'attackHeadReach':.015,'attackLunge':.025,'attackHeadPitchDegrees':10}
with mock.patch.object(Path,'read_text',return_value=json.dumps(heavy)):
self.assertEqual(pipeline.recipe(Path('fixture'))['profile'],'quadruped_heavy')
for change in ({'attackStyle':'turtle_bubble'},{'hornRegionMin':[]},{'armorRigidityTolerance':.1},
{'headBone':'bone_1'},{'hindLeftFoot':'bone_5'},{'attackLunge':.5},
{'attackHeadPitchDegrees':0},{'boneMappingReady':False}):
with mock.patch.object(Path,'read_text',return_value=json.dumps({**heavy,**change})),self.assertRaises(ValueError):
pipeline.recipe(Path('fixture'))
def test_bird_requires_own_two_wing_mapping_and_matching_style(self):
bird={**self.data,'profile':'avian_grounded','attackStyle':'wing_cast',
'boneMappingReady':True,'rootBone':'bone_0','chestBone':'bone_1',
'rightArm':'bone_2','leftArm':'bone_4','rightWingTip':'bone_3','leftWingTip':'bone_5',
'rightFoot':'bone_6','leftFoot':'bone_8','rightFootTip':'bone_7','leftFootTip':'bone_9',
'faceRight':'bone_6','faceLeft':'bone_8','wingSpreadDegrees':25}
with mock.patch.object(Path,'read_text',return_value=json.dumps(bird)):
self.assertEqual(pipeline.recipe(Path('fixture'))['profile'],'avian_grounded')
for change in ({'rightWingTip':''},{'leftArm':'bone_2'},{'leftFootTip':'bone_7'},
{'attackStyle':'ranged_cast'},{'wingSpreadDegrees':90},{'boneMappingReady':False}):
with mock.patch.object(Path,'read_text',return_value=json.dumps({**bird,**change})),self.assertRaises(ValueError):
pipeline.recipe(Path('fixture'))
with mock.patch.object(Path,'read_text',return_value=json.dumps({**self.data,'attackStyle':'wing_cast'})),self.assertRaises(ValueError):
pipeline.recipe(Path('fixture'))
def run_generation(self,success):
root=REPO/'Tools/output/enemy_pipeline_tests'
root.mkdir(exist_ok=True)