55 lines
4.2 KiB
Python
55 lines
4.2 KiB
Python
"""Verify evidence emitted by the real packaged process; never generate screenshots."""
|
|
import argparse
|
|
import json
|
|
import math
|
|
import struct
|
|
from pathlib import Path
|
|
|
|
|
|
def verify(folder):
|
|
launch=json.loads((folder/'launch.json').read_text(encoding='utf-8-sig'))
|
|
checks=[]
|
|
def check(name,value):checks.append({'name':name,'passed':bool(value)})
|
|
def load(name):return json.loads((folder/(name+'.json')).read_text(encoding='utf-8'))
|
|
images=[]
|
|
for p in sorted(folder.glob('*.png')):
|
|
data=p.read_bytes()
|
|
if not data.startswith(b'\x89PNG\r\n\x1a\n'):raise ValueError('Not a PNG: '+str(p))
|
|
w,h=struct.unpack('>II',data[16:24]);images.append({'file':p.name,'width':w,'height':h,'environment':launch['environment']})
|
|
expected=tuple(map(int,launch['requestedResolution'].split('x')))
|
|
required={'Full':11,'UI':4,'Restart':3}[launch['suite']]
|
|
check('Actual native screenshots captured',len(images)==required)
|
|
check('PNG dimensions match requested resolution',bool(images) and all((i['width'],i['height'])==expected for i in images))
|
|
records=[load(Path(i['file']).stem) for i in images]
|
|
check('Exactly one overlay after each map transition',bool(records) and all(r['overlayInstances']==1 for r in records))
|
|
check('Progress save version remains V1',all(r['saveVersion']==1 for r in records))
|
|
check('Measured frame timing and reciprocal FPS agree',all(r['metrics']['fps']>0 and abs(r['metrics']['fps']*r['metrics']['frameMs']-1000)<.001 for r in records))
|
|
check('Actual CPU and GPU readings are available on this DX12 device',all(all(math.isfinite(r['metrics'][k]) and r['metrics'][k]>0 for k in ('gameMs','renderMs','gpuMs')) for r in records))
|
|
if launch['suite']=='Full':
|
|
initial=load('home-default-zh')
|
|
check('Fresh preferences default to enabled top-right Chinese',initial['enabled'] and initial['corner']==0 and not initial['english'])
|
|
check('Settings toggle disables the overlay',not load('disabled-zh')['enabled'])
|
|
check('All four corners selected through settings actions',{load(n)['corner'] for n in ('home-default-zh','top-left-en','bottom-right-en','bottom-left-en')}=={0,1,2,3})
|
|
check('Language switches while overlay is visible',load('top-left-en')['enabled'] and load('top-left-en')['english'])
|
|
start,end=load('pause-begin'),load('pause-updating')
|
|
check('Sampling continues during real world pause',start['worldPaused'] and end['worldPaused'] and end['metrics']['revision']>start['metrics']['revision'])
|
|
check('Training and home transitions exercised','TrainingRange' in load('training-shoulder')['map'] and 'TrainingRange' in load('training-cockpit')['map'] and 'Hangar' in load('after-travel')['map'])
|
|
elif launch['suite']=='Restart':
|
|
saved=load('restart-disabled');enabled=load('restart-enabled')
|
|
check('Disabled and bottom-left settings persist across process restart',not saved['enabled'] and saved['corner']==3 and saved['english'])
|
|
check('Overlay and default corner can be restored',enabled['enabled'] and enabled['corner']==0 and not enabled['english'])
|
|
compare=load('native-comparison');native=compare['nativeStatUnitRaw'];raw=compare['raw']
|
|
check('Native stat unit collected actual timings',all(native[k]>0 for k in ('frameMs','gameMs','renderMs','gpuMs')))
|
|
check('Raw thread and GPU samples agree with native readouts within frame jitter',all(abs(raw[k]-native[k])<=max(3,.5*native[k]) for k in ('gameMs','renderMs','gpuMs')))
|
|
else:
|
|
check('Both languages and settings page captured',load('settings-en')['english'] and not load('settings-zh')['english'])
|
|
result={'passed':all(c['passed'] for c in checks),'checks':checks,'screenshots':images}
|
|
(folder/'verification.json').write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
|
|
print(json.dumps({'folder':str(folder),'passed':result['passed'],'checks':len(checks),'failed':[c['name'] for c in checks if not c['passed']]},ensure_ascii=False))
|
|
return result['passed']
|
|
|
|
|
|
if __name__=='__main__':
|
|
parser=argparse.ArgumentParser();parser.add_argument('folder',type=Path)
|
|
raise SystemExit(0 if verify(parser.parse_args().folder.resolve()) else 1)
|