"""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()