Files
EscapistsReintroduced/tests/test_prison_approach_evidence.py

121 lines
7.7 KiB
Python

"""Independent native route policy regression; no input/display operations."""
import copy
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'tools'))
import prison_approach_evidence as v
def fixture():
samples={}
for i in range(21,36):
moved=min(6,max(0,i-24))*.15
p=[6092.77,211.44,7157.1-moved]
active=25<=i<=29
samples[i]={'sample':i,'real_ms':1000+i*100,
'input':{'origin':p,'forward':1. if active else 0.,'input':[0,0,1 if active else 0],'velocity':[0,0,-1.5 if active else 0]},
'look':{'direction':[0,0,-1]},
'action':{'pressed':0.,'performing':False,'progress':0.},
'geometry':{'body':p,'prison':[6090,211.44,7160],'gate':[6090,211.34,7154],'context':[6092.77,213.22,7154],'gate_forward':[0,0,1],'door_state':0.,'door_control':0.}}
data={'snapshots':samples,'ready':{'sample':20,'real_ms':3000}}
look={'return_sample':20,'finished_monotonic':9.}
proof={'final_sample':35,'requested':True,'completed':True,'nonce':'N','paired_release_errors':[],'unconfirmed_held_key':None,'started_monotonic':10.,'finished_monotonic':12.,'deadline_monotonic':35.,'events':[{'key':'W','down':True,'monotonic':10.5},{'key':'W','down':False,'monotonic':10.8}],'stops':[{'before_sample':24,'after_sample':33}],'motions':[],'samples':[{'sample':i,'real_ms':1000+i*100,'received_monotonic':10+(i-21)*.1} for i in range(21,36)]}
return data,look,proof
class RouteEvidence(unittest.TestCase):
def run_case(self, mutate=None, geometry=True, calibration=True):
d,l,p=fixture()
if mutate:mutate(d,l,p)
with patch.object(v,'join_gate_snapshots',return_value=d),patch.object(v,'validate_gate_geometry',return_value={'verified':geometry,'errors':[]}),patch.object(v,'validate_look_evidence',return_value={'verified':calibration,'errors':[],'response_yaw_degrees':2.}) as validator:
r=v.validate_approach_evidence('raw','N',l,p)
if r['verified']:self.assertEqual(validator.call_args.args[-1],p['events'][:2])
return r
def reject(self, mutation):
r=self.run_case(mutation);self.assertFalse(r['verified'],r);self.assertTrue(r['errors'])
def test_stopped_interior_route_is_limited_not_escape(self):
r=self.run_case();self.assertTrue(r['verified'],r);self.assertAlmostEqual(r['path_metres'],.9);self.assertAlmostEqual(r['final_distance_metres'],.7);self.assertFalse(r['escape_verified']);self.assertFalse(r['held_action_verified'])
def test_no_helper_completion_or_release_shortcut(self):
for field,value in [('completed',False),('nonce','foreign'),('paired_release_errors',['failed']),('unconfirmed_held_key','W')]:
self.reject(lambda d,l,p:p.update({field:value}))
def test_requires_both_independent_raw_gate_and_calibration_verification(self):
self.assertFalse(self.run_case(geometry=False)['verified']);self.assertFalse(self.run_case(calibration=False)['verified'])
def test_actual_held_time_and_key_shape_not_requested_sleep(self):
self.reject(lambda d,l,p:p['events'][1].update(monotonic=11.))
self.reject(lambda d,l,p:p['events'][0].update(key='F'))
self.reject(lambda d,l,p:p['events'][0].update(down=1))
self.reject(lambda d,l,p:p['events'][1].update(monotonic=10.4))
def test_final_coasting_or_wrong_waypoint_not_success(self):
self.reject(lambda d,l,p:d['snapshots'][35]['input'].update(velocity=[0,0,.1]))
self.reject(lambda d,l,p:d['snapshots'][34]['input'].update(velocity=[0,0,.1]))
self.reject(lambda d,l,p:d['snapshots'][35]['input'].update(origin=[6092.77,211.44,7156.4]))
def test_native_forward_input_required(self):
def mutation(d,l,p):
for s in d['snapshots'].values():s['input']['forward']=0
self.reject(mutation)
def test_gate_operation_or_teleport_refused(self):
self.reject(lambda d,l,p:d['snapshots'][27]['geometry'].update(door_state=.1))
self.reject(lambda d,l,p:d['snapshots'][27]['action'].update(pressed=1))
self.reject(lambda d,l,p:d['snapshots'][27]['input'].update(origin=[6092.77,211.44,7153.5]))
self.reject(lambda d,l,p:d['snapshots'][27].update(real_ms=5000))
def test_final_reference_and_unexpected_input_rejected(self):
self.reject(lambda d,l,p:p.update(final_sample=34))
self.reject(lambda d,l,p:d['snapshots'][27]['input'].update(input=[.2,0,1]))
def no_z(d,l,p):
for item in d['snapshots'].values():item['input']['input']=[0,0,0]
self.reject(no_z)
def test_native_clock_and_sample_references_bound(self):
self.reject(lambda d,l,p:d['ready'].update(real_ms=-40000))
self.reject(lambda d,l,p:p['stops'][0].update(before_sample=21))
self.reject(lambda d,l,p:p['samples'].append({'sample':35}))
self.reject(lambda d,l,p:p.update(deadline_monotonic=100.))
def test_helper_native_clocks_and_receipts_cannot_be_rewritten(self):
self.reject(lambda d,l,p:p['samples'][3].update(real_ms=99999))
self.reject(lambda d,l,p:p['samples'][3].update(received_monotonic=11.9))
self.reject(lambda d,l,p:p['events'][0].update(monotonic=10.1))
def test_unacknowledged_or_oversized_mouse_proposals_refused(self):
self.reject(lambda d,l,p:p['motions'].append({'dx':129,'dy':0,'monotonic':10.2,'after_sample':21,'response_sample':23}))
self.reject(lambda d,l,p:p['motions'].append({'dx':24,'dy':0,'monotonic':10.2,'after_sample':21,'response_sample':23}))
def test_coarse_profile_is_known_explicit_and_has_no_default_upgrade(self):
import math
def turn(d,l,p):
p['profile']='coarse'
angle=math.radians(155)
d['snapshots'][21]['look']['direction']=[math.sin(angle),0,math.cos(angle)]
p['motions']=[{'dx':300,'dy':0,'monotonic':10.1,'after_sample':21,'response_sample':23}]
self.assertTrue(self.run_case(turn)['verified'])
def fine(d,l,p):turn(d,l,p);p.pop('profile')
self.assertFalse(self.run_case(fine)['verified'])
for profile in ('unknown',True,None):self.reject(lambda d,l,p:p.update(profile=profile))
def overshoot(d,l,p):
turn(d,l,p);p['motions'][0]['response_sample']=24
angle=math.radians(191)
d['snapshots'][22]['look']['direction']=[math.sin(angle),0,math.cos(angle)]
self.reject(overshoot)
def excessive(d,l,p):turn(d,l,p);p['motions'][0]['dx']=385
self.reject(excessive)
def test_actual_coarse_policy_trace_passes_independent_raw_chain(self):
# Simulated dynamics exercise actual policy and every raw parser; no
# native runtime/input claim and no mocked evidence verdicts.
from test_prison_alignment_evidence import build, raw, ApproachScene
original = ApproachScene.run
with patch.object(ApproachScene, 'run', lambda scene: original(scene, profile='coarse')):
frames, look, route, _ = build()
self.assertEqual(route['profile'], 'coarse')
self.assertTrue(any(abs(m['dx']) > 128 for m in route['motions']))
result = v.validate_approach_evidence(raw(frames), 'test', look, route)
self.assertTrue(result['verified'], result)
route['profile'] = 'standard'
self.assertFalse(v.validate_approach_evidence(raw(frames), 'test', look, route)['verified'])
def test_coarse_does_not_expand_clocks_or_permit_action_keys(self):
for change in (lambda p:p.update(deadline_monotonic=100),lambda p:p['events'][1].update(monotonic=11),lambda p:p['events'][0].update(key='F')):
def mutate(d,l,p):p['profile']='coarse';change(p)
self.reject(mutate)
if __name__=='__main__':unittest.main()