65 lines
3.3 KiB
Python
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()
|