Files
AIGame/Tools/tests/test_skill_design.py
T
udxandClaude Opus 5.5 89e0fa246f 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>
2026-10-21 12:23:55 +08:00

57 lines
2.8 KiB
Python

"""Contract guards for timing, frozen-source coverage and production readiness."""
import sys
from pathlib import Path
import unittest
ROOT=Path(__file__).resolve().parents[2]
sys.path.insert(0,str(ROOT/'Tools'))
from validate_skill_design import load,validate
class SkillDesignTests(unittest.TestCase):
def setUp(self):
self.data=[load(ROOT/p) for p in ['Config/Combat/skill_system_v1.proposal.json',
'Config/Combat/skill_presentations_v1.proposal.json','Doc/Design/Sound/BattleSkills/manifest.json',
'Config/Gameplay/greenfield.json']]
def test_covers_current_actors_without_claiming_runtime(self):
r=validate(*self.data)
self.assertEqual(r['skills'],len(self.data[3]['pets'])*2+len(self.data[3]['enemies']))
self.assertFalse(r['runtimeIntegrated']);self.assertFalse(r['generatedAudio'])
def test_missing_actor_or_wrong_skill_kind_is_rejected(self):
self.data[0]['skills'].pop()
with self.assertRaisesRegex(ValueError,'coverage'):validate(*self.data)
self.setUp();self.data[0]['skills'][1]['template']='ability_heal'
with self.assertRaisesRegex(ValueError,'semantics'):validate(*self.data)
def test_animation_and_cooldown_conflicts_are_rejected(self):
skill=next(s for s in self.data[0]['skills'] if s['id']=='pet.1.basic')
skill['timing']=dict(releaseTicks=11,recoveryTicks=11)
with self.assertRaisesRegex(ValueError,'cooldown'):validate(*self.data)
skill['timing']=dict(releaseTicks=2,recoveryTicks=16)
with self.assertRaisesRegex(ValueError,'marker'):validate(*self.data)
def test_missing_resource_cannot_claim_ready(self):
asset=next(a for a in self.data[1]['assets'] if a['status']=='planned')
asset['status']='existing'
with self.assertRaisesRegex(ValueError,'missing'):validate(*self.data)
def test_unresolved_cue_and_invalid_audio_request_are_rejected(self):
self.data[1]['profiles'][0]['bindings']['HitResolved'][0]['asset']='missing'
with self.assertRaisesRegex(ValueError,'Unresolved'):validate(*self.data)
self.setUp();self.data[2]['items'][0]['duration']=.5
with self.assertRaisesRegex(ValueError,'1..30'):validate(*self.data)
def test_actions_compose_but_cannot_repeat_an_action_id(self):
t=self.data[0]['templates'][0]
t['actions'].append({**t['actions'][0],'id':'additional_hit'})
validate(*self.data)
t['actions'][1]['id']=t['actions'][0]['id']
with self.assertRaisesRegex(ValueError,'Duplicate logical action'):validate(*self.data)
def test_model_forward_does_not_force_world_direction(self):
self.data[1]['anchorPolicy']['worldFacing']='fixed_world_minus_z'
with self.assertRaisesRegex(ValueError,'world facing'):validate(*self.data)
if __name__=='__main__':unittest.main()