Files
AIGame/Tools/tests/test_skill_design.py
T

65 lines
3.3 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.assertTrue(r['generatedAudio'])
self.assertEqual(r['generatedSfxCount'],9)
def test_generated_sound_requires_matching_file_hash_and_receipt(self):
item=next(i for i in self.data[2]['items'] if i.get('production'))
item['production']['sha256']='0'*64
with self.assertRaisesRegex(ValueError,'hash mismatch'):validate(*self.data)
self.setUp();self.data[2]['status']='partially_generated'
with self.assertRaisesRegex(ValueError,'status does not match'):validate(*self.data)
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()