65 lines
3.7 KiB
Python
65 lines
3.7 KiB
Python
"""Check the C++ slice trace, immutable inputs, and TangoFlux audio provenance."""
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import numpy as np
|
|
from scipy.io import wavfile
|
|
from export_skill_slice import canonical
|
|
|
|
ROOT=Path(__file__).resolve().parents[1]
|
|
DESIGN=ROOT/'Doc/Design/Combat/SkillSlice'
|
|
|
|
def main():
|
|
DESIGN.mkdir(parents=True,exist_ok=True)
|
|
trace_path=ROOT/'AIClient/Assets/Bundles/Combat/SkillSlice/trace.json'
|
|
trace=json.loads(trace_path.read_text(encoding='utf-8'))
|
|
exported=json.loads((DESIGN/'export.json').read_text(encoding='utf-8'))
|
|
assert hashlib.sha256(trace_path.read_bytes()).hexdigest()==exported['traceSha256']
|
|
for row in exported['inputs']:
|
|
snapshot=(ROOT/row['snapshot']).read_bytes()
|
|
assert hashlib.sha256(snapshot).hexdigest()==row['sourceSha256']
|
|
assert trace[row['field']]==json.loads(snapshot)
|
|
assert hashlib.sha256(canonical(trace[row['field']])).hexdigest()==row['payloadSha256']
|
|
casts={};shots={};hits=set();deaths=set();last=-1
|
|
for seq,e in enumerate(trace['events']):
|
|
assert e['seq']==seq and e['tick']>=last
|
|
last=e['tick'];kind=e['kind'];cast=e.get('cast')
|
|
if kind=='cast_start':
|
|
assert cast not in casts
|
|
casts[cast]=e
|
|
elif kind=='release': assert e['tick']==casts[cast]['release']
|
|
elif kind=='projectile':
|
|
key=(cast,e['target']);assert key not in shots
|
|
shots[key]=e
|
|
assert e['arrival']>e['tick']
|
|
elif kind=='hit':
|
|
key=(cast,e['target']);assert key not in hits
|
|
hits.add(key)
|
|
if e['source']==1: assert e['tick']==shots[key]['arrival']
|
|
else: assert e['tick']==casts[cast]['release']
|
|
elif kind=='death':
|
|
assert e['target'] not in deaths
|
|
deaths.add(e['target'])
|
|
assert trace['events'][-1]['kind']=='battle_end' and trace['won']
|
|
audio=json.loads((ROOT/'Doc/Design/Sound/BattleSkills/Slice/generation.json').read_text(encoding='utf-8'))
|
|
rows=[]
|
|
for item in audio['items']:
|
|
source=ROOT/item['source'];target=ROOT/item['output']
|
|
assert hashlib.sha256(source.read_bytes()).hexdigest()==item['sourceSha256']
|
|
assert hashlib.sha256(target.read_bytes()).hexdigest()==item['outputSha256']
|
|
assert hashlib.sha256(json.dumps(item['request'],sort_keys=True).encode()).hexdigest()==item['requestHash']
|
|
assert item['job']['status']=='succeeded'
|
|
rate,samples=wavfile.read(target);x=samples.astype(float)/32768
|
|
assert rate==44100 and samples.dtype==np.int16 and x.shape[1]==2
|
|
assert .1<len(x)/rate<1 and np.isfinite(x).all()
|
|
assert np.max(np.abs(x))<.95 and np.max(np.abs(x[:4410]))>.05
|
|
assert np.max(np.abs(x[-10:]))<.003
|
|
rms=float(20*np.log10(np.sqrt(np.mean(x*x))));assert rms>-36
|
|
rows.append(dict(id=item['id'],seconds=len(x)/rate,rmsDb=rms,clippedSamples=int(np.sum(np.abs(x)>=.999))))
|
|
assert len(rows)==5
|
|
report=dict(status='pass',events=len(trace['events']),casts=len(casts),projectiles=len(shots),hits=len(hits),deaths=len(deaths),traceSha256=hashlib.sha256(trace_path.read_bytes()).hexdigest(),frozenInputs={r['source']:r['sourceSha256'] for r in exported['inputs']},currentSourceChanged=[r['source'] for r in exported['inputs'] if hashlib.sha256((ROOT/r['source']).read_bytes()).hexdigest()!=r['sourceSha256']],audio=rows,subjectiveListening='not_asserted',productionIntegrated=False)
|
|
(DESIGN/'validation.json').write_text(json.dumps(report,indent=2),encoding='utf-8')
|
|
print(f"PASS trace: {len(casts)} casts, {len(shots)} projectiles, {len(hits)} unique hits; five audio clips and frozen source hashes")
|
|
|
|
if __name__=='__main__':main()
|