Add typed field-flight prototype and preserve native qualification failures

This commit is contained in:
2026-09-19 21:01:20 +02:00
parent 83ea247e62
commit 61e165ef5b
91 changed files with 40570 additions and 85 deletions
+59 -4
View File
@@ -21,7 +21,7 @@ class Scene:
self.position=[98.5,0.,98.5];self.angle=0.;self.speed=0.;self.held=False
self.key_events=[];self.turns=[];self.notifications=[];self.focus=True
self.gain=.085;self.acceleration=8.;self.braking=8.;self.move_scale=1.
self.guard_cost=.22;self.ignore_turn=False;self.wrong_turn=False
self.coarse=False;self.guard_cost=.22;self.ignore_turn=False;self.wrong_turn=False
self.turn_delay=0.;self.pending_turns=[]
self.stale=False;self.no_movement=False;self.changed_geometry=False
self.context_lost=False;self.fail_down=False;self.coast_events=[]
@@ -64,7 +64,7 @@ class Scene:
self.held=down
if down and self.fail_down:raise OSError('uncertain native press')
def move(self,dx,dy,guard):
if dy != 0 or not 1<=abs(dx)<=128:raise AssertionError('motion cap')
if dy != 0 or not 1<=abs(dx)<=(384 if self.coarse else 128):raise AssertionError('motion cap')
guard.check();guard.check()
self.turns.append((dx,dy,self.now))
if not self.ignore_turn:
@@ -78,14 +78,15 @@ class Scene:
batch=[s for s in self.history if s['sample'] > self.last_read] or [self.sample]
self.last_read=batch[-1]['sample']
return copy.deepcopy(batch)
def run(self,report=None,calibration=None):
def run(self,report=None,calibration=None,profile="standard"):
self.coarse=profile=="coarse"
self.report={} if report is None else report
angle=math.radians(24*.085)
calibration=calibration or {'nonce':'test','completed':True,'pixels':24,
'baseline_direction':[0.,0.,1.], 'response_direction':[math.sin(angle),0.,math.cos(angle)],
'ready_real_ms':2000,'ready_monotonic':0.,'observer_start_real_ms':0}
return policy.perform_approach(self,self,self,self.observe,self.geometry,calibration,self.report,
clock=self.clock,sleep=self.sleep,notify=self.notifications.append,key_state_busy=Busy)
clock=self.clock,sleep=self.sleep,notify=self.notifications.append,key_state_busy=Busy,profile=profile)
class ApproachTests(unittest.TestCase):
@@ -325,5 +326,59 @@ class ApproachTests(unittest.TestCase):
self.assertEqual([event[1] for event in scene.key_events],[True,False])
self.assertFalse(proof['completed']);self.assertFalse(scene.held)
def test_explicit_coarse_profile_reduces_heading_requests_with_original_clocks(self):
fine=Scene();standard=fine.run()
coarse=Scene();result=coarse.run(profile='coarse')
self.assertEqual(standard['profile'],'standard');self.assertEqual(result['profile'],'coarse')
self.assertLess(len(coarse.turns),len(fine.turns));self.assertTrue(any(abs(dx)>128 for dx,_,_ in coarse.turns))
self.assertTrue(all(abs(dx)<=384 and dy==0 for dx,dy,_ in coarse.turns))
self.assertLessEqual(sum(abs(dx) for dx,_,_ in coarse.turns),2048)
self.assertLessEqual(len(coarse.turns),16);self.assertLessEqual(len(coarse.key_events)//2,16)
self.assertEqual(result['deadline_monotonic'],35);self.assertLess(coarse.now,35)
self.assertLessEqual(result['cumulative_horizontal_m'],6);self.assertFalse(coarse.coast_events)
self.assertTrue(all(key=='W' for key,_,_ in coarse.key_events))
def test_coarse_profile_never_hides_intermediate_native_overshoot(self):
scene=Scene()
def turn(dx,dy,guard):
guard.check();guard.check();scene.angle+=36;scene.sleep(.2);scene.angle-=11
scene.move=turn
with self.assertRaisesRegex(policy.Refused,'angle bound'):scene.run(profile='coarse')
self.assertEqual(scene.key_events,[])
def test_coarse_requires_known_profile_and_respects_focus_and_deadline(self):
for profile in ('other',True,None):
scene=Scene()
with self.assertRaisesRegex(policy.Refused,'Unknown approach profile'):scene.run(profile=profile)
self.assertEqual(scene.key_events,[]);self.assertEqual(scene.turns,[])
scene=Scene();scene.focus=False
with self.assertRaisesRegex(policy.Refused,'focus lost'):scene.run(profile='coarse')
self.assertEqual(scene.key_events,[])
scene=Scene();scene.now=35
with self.assertRaisesRegex(policy.Refused,'Insufficient'):scene.run(profile='coarse')
self.assertEqual(scene.key_events,[]);self.assertEqual(scene.turns,[])
def test_coarse_keeps_actual_hold_and_stopping_deadzone(self):
scene=Scene();original=scene.send
def slow(key,down):
original(key,down)
if down:scene.guard_cost=.41
scene.send=slow
with self.assertRaisesRegex(policy.Refused,'400ms'):scene.run(profile='coarse')
self.assertEqual([e[1] for e in scene.key_events],[True,False])
scene=Scene();scene.position=[102.7,0.,97.3];scene.angle=180.;scene.guard_cost=.33
scene.sample=scene.snapshot();scene.history=[copy.deepcopy(scene.sample)]
with self.assertRaisesRegex(policy.Refused,'measured stopping reserve'):scene.run(profile='coarse')
self.assertEqual([e[1] for e in scene.key_events],[True,False])
def test_coarse_has_exact_sixteen_pulse_cap_not_unbounded_retries(self):
scene=Scene();scene.guard_cost=.19
result=scene.run(profile='coarse')
self.assertTrue(result['completed']);self.assertEqual(len(scene.key_events),32)
self.assertLess(scene.now,35);self.assertFalse(scene.held)
scene=Scene();scene.guard_cost=.18
with self.assertRaisesRegex(policy.Refused,'pulse budget'):scene.run(profile='coarse')
self.assertEqual(len(scene.key_events),32);self.assertFalse(scene.held)
if __name__=='__main__':unittest.main()