#!/usr/bin/env python3 """Adversarial raw-record tests; no native input, camera or display operations.""" import copy import math from pathlib import Path import sys import unittest sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'tools')) from ordinary_look_evidence import validate_look_evidence NONCE = 'nativeLook123' def fixture(sign=1, base=0): def vector(yaw): r = math.radians(yaw) return '<%.9f,0,%.9f>' % (math.sin(r), math.cos(r)) lines = [f'SCRIPT : ERR INPUT OBSERVE: run={NONCE} sample=0 real=1000 world=5000 QUEUED'] for i in range(1, 31): clock = 1000+i*100 opened = i >= 29 lines.append(f'SCRIPT : ERR INPUT OBSERVE: run={NONCE} sample={i} real={clock} world={5000+i*100} SAMPLE origin=<100,20,300> forward=0 input=<0,0,0> velocity=<0,0,0> inventory={int(opened)}') if i == 20: lines.append(f'SCRIPT : ERR INPUT OBSERVE: run={NONCE} sample={i} real={clock} world={5000+i*100} READY') if i == 1: lines.append(f'SCRIPT : ERR LOOK OBSERVE: run={NONCE} sample=1 real={clock} BASIS native-player-camera-world-transform') if i < 29: yaw = base + (sign*2 if 23 <= i <= 25 else sign*.1 if i >= 26 else 0) lines.append(f'SCRIPT : ERR LOOK OBSERVE: run={NONCE} sample={i} real={clock} SAMPLE direction={vector(yaw)} camera=<100,21.7,300> body=<100,20,300> bodyForward=<0,0,1> turn=<0,0,0>') elif i == 29: lines.append(f'SCRIPT : ERR LOOK OBSERVE: run={NONCE} sample={i} real={clock} CANCELLED menu-opened') lines.append(f'SCRIPT : ERR INPUT OBSERVE: run={NONCE} sample={i} real={clock} world={5000+i*100} INVENTORY_OPEN') proof = dict(requested=True, completed=True, started_monotonic=10.0, finished_monotonic=11.0, baseline_samples=[21,22], response_sample=24, return_sample=27, motions=[dict(dx=24,dy=0,monotonic=10.3,after_sample=22,after_real_ms=3200), dict(dx=-24,dy=0,monotonic=10.6,after_sample=24,after_real_ms=3400)]) keys = [dict(key='W', down=True, monotonic=11.1), dict(key='W', down=False, monotonic=11.4)] return '\n'.join(lines)+'\n', proof, keys class LookEvidenceTests(unittest.TestCase): def verdict(self, content=None, proof=None, keys=None): raw,p,k=fixture() return validate_look_evidence(raw if content is None else content,NONCE,p if proof is None else proof,k if keys is None else keys) def reject(self, **changes): result=self.verdict(**changes) self.assertFalse(result['verified'], result) self.assertTrue(result['errors']) def test_native_recomputed_positive_and_sign_and_wrap(self): for sign,base in ((1,0),(-1,0),(1,179),(-1,-179)): raw,p,k=fixture(sign,base) result=validate_look_evidence(raw,NONCE,p,k) self.assertTrue(result['verified'], result) self.assertAlmostEqual(result['response_yaw_degrees'],sign*2,places=5) self.assertAlmostEqual(result['return_yaw_degrees'],sign*.1,places=5) self.assertTrue(result['keyboard_order_verified']) def test_ready_separate_clock_read_binds_latest_sample(self): raw,p,k=fixture() marker=f'ERR INPUT OBSERVE: run={NONCE} sample=20 real=3000 world=7000 READY' # Actual native shape: SAMPLE, then LOOK at T, then READY at T+1. ready_line=next(line for line in raw.splitlines() if marker in line) look_line=next(line for line in raw.splitlines() if 'sample=20 real=3000 SAMPLE direction=' in line) changed=raw.replace(ready_line+'\n','').replace(look_line+'\n',look_line+'\n'+ready_line.replace('real=3000','real=3001')+'\n') result=validate_look_evidence(changed,NONCE,p,k) self.assertTrue(result['verified'],result) for replacement in ('sample=19 real=3001 world=7000', 'sample=20 real=2999 world=7000', 'sample=20 real=3501 world=7000', 'sample=20 real=3001 world=7001', 'sample=20 real=3101 world=7000'): self.reject(content=changed.replace('sample=20 real=3001 world=7000 READY',replacement+' READY')) # READY cannot bind an older sample once the next SAMPLE was emitted. line=ready_line.replace('real=3000','real=3001') next_line=next(x for x in changed.splitlines() if 'sample=21 real=3100 world=7100 SAMPLE' in x) self.reject(content=changed.replace(line+'\n','').replace(next_line+'\n',next_line+'\n'+line+'\n')) self.reject(content=changed.replace(line+'\n',line+'\n'+line+'\n')) def test_optional_keyboard_is_explicitly_unverified(self): raw,p,_=fixture(); result=validate_look_evidence(raw,NONCE,p) self.assertTrue(result['verified'],result) self.assertFalse(result['keyboard_order_verified']) def test_missing_ack_foreign_nonce_and_unknown_events(self): raw,_,_=fixture() for changed in (raw.replace('BASIS native-player-camera-world-transform','BASIS guessed'), '\n'.join(x for x in raw.splitlines() if ' BASIS ' not in x), raw.replace('ERR LOOK OBSERVE: run='+NONCE,'ERR LOOK OBSERVE: run=foreign',1), raw.replace('ERR INPUT OBSERVE: run='+NONCE,'ERR INPUT OBSERVE: run=foreign',1), raw+'ERR LOOK OBSERVE: malformed\n', raw.replace('CANCELLED menu-opened','CANCELLED invented'), raw+'ERR INPUT OBSERVE: run='+NONCE+' sample=30 real=4000 world=8000 UNKNOWN\n'): self.reject(content=changed) def test_duplicate_missing_reversed_native_samples(self): raw,_,_=fixture(); lines=raw.splitlines() for predicate in ('sample=24 real=3400 SAMPLE direction=', 'sample=24 real=3400 world=7400 SAMPLE'): index=next(i for i,line in enumerate(lines) if predicate in line) duplicate=lines[:index]+[lines[index]]+lines[index:] self.reject(content='\n'.join(duplicate)) self.reject(content='\n'.join(lines[:index]+lines[index+1:])) self.reject(content=raw.replace('sample=24 real=3400 SAMPLE direction=', 'sample=24 real=3199 SAMPLE direction=')) self.reject(content=raw.replace('world=7400 SAMPLE','world=7000 SAMPLE')) def test_cancel_before_return_or_revival_refused(self): raw,_,_=fixture() self.reject(content=raw.replace('sample=29 real=3900 CANCELLED','sample=26 real=3600 CANCELLED')) extra=next(x for x in raw.splitlines() if 'sample=28 real=3800 SAMPLE direction=' in x) self.reject(content=raw+extra+'\n') def test_strict_proof_shape_types_timestamps_and_fixed_motions(self): _,p,_=fixture() mutations=[lambda x:x.update(completed=False),lambda x:x.update(requested=1), lambda x:x.update(extra='claimed'),lambda x:x.update(started_monotonic=True), lambda x:x.update(finished_monotonic=float('nan')),lambda x:x.update(finished_monotonic=15), lambda x:x.update(baseline_samples=[20,22]),lambda x:x.update(baseline_samples=[True,22]), lambda x:x.update(response_sample=28),lambda x:x.update(return_sample=24), lambda x:x['motions'][0].update(dx=-24),lambda x:x['motions'][0].update(dy=1), lambda x:x['motions'][0].update(after_real_ms=3199),lambda x:x['motions'][1].update(after_sample=25), lambda x:x['motions'][1].update(monotonic=10.2),lambda x:x['motions'].append(copy.deepcopy(x['motions'][0])), lambda x:x['motions'][0].update(native_yaw_claim=99)] for mutation in mutations: value=copy.deepcopy(p);mutation(value) with self.subTest(value=value):self.reject(proof=value) def test_raw_stationary_input_and_body_not_helper_claim(self): raw,_,_=fixture() input_line=next(x for x in raw.splitlines() if 'sample=23 real=3300 world=7300 SAMPLE' in x) for field,value in (('forward=0','forward=0.5'),('input=<0,0,0>','input=<0,0,1>'), ('velocity=<0,0,0>','velocity=<0.2,0,0>'),('origin=<100,20,300>','origin=<100.1,20,300>'),('inventory=0','inventory=1')): self.reject(content=raw.replace(input_line,input_line.replace(field,value))) pose=next(x for x in raw.splitlines() if 'sample=23 real=3300 SAMPLE direction=' in x) self.reject(content=raw.replace(pose,pose.replace('body=<100,20,300>','body=<101,20,300>'))) # Also reject movement after READY but before the selected baseline. early=next(x for x in raw.splitlines() if 'sample=20 real=3000 world=7000 SAMPLE' in x) self.reject(content=raw.replace(early,early.replace('forward=0','forward=1'))) def test_invalid_pose_unit_nonfinite_no_response_and_no_return(self): raw,_,_=fixture() for vector in ('<0,0,0>','<0,0,2>','<0,1,0>','','<1e999,0,1>','<1,2>'): self.reject(content=raw.replace('direction=<0.000000000,0,1.000000000>', 'direction='+vector,1)) p24=next(x for x in raw.splitlines() if 'sample=24 real=3400 SAMPLE direction=' in x) p27=next(x for x in raw.splitlines() if 'sample=27 real=3700 SAMPLE direction=' in x) direction=p24.split('direction=')[1].split(' camera=')[0] self.reject(content=raw.replace(p24,p24.replace(direction,'<0,0,1>'))) old=p27.split('direction=')[1].split(' camera=')[0] self.reject(content=raw.replace(p27,p27.replace(old,direction))) def test_keyboard_before_return_unknown_or_reversed_refused(self): _,_,keys=fixture() for changed in ([],[dict(key='Tab',down=True,monotonic=11.1)], [dict(key='W',down=True,monotonic=10.9)], [dict(key='W',down=1,monotonic=11.1)], [dict(key='W',down=True,monotonic=float('inf'))],list(reversed(keys))): self.reject(keys=changed) def test_invalid_top_level_and_nonce_never_throw(self): raw,p,k=fixture() for value in (None,[],{},True): self.assertFalse(validate_look_evidence(raw,NONCE,value,k)['verified']) for nonce in ('','../x','a'*33,None): self.assertFalse(validate_look_evidence(raw,nonce,p,k)['verified']) if __name__=='__main__':unittest.main()