Prepare independent successor playtest with spawn recovery and casualty feedback

This commit is contained in:
2026-09-19 22:12:52 +02:00
parent 61e165ef5b
commit 6cf989a060
266 changed files with 134064 additions and 154 deletions
@@ -47,6 +47,64 @@ class ChoiceScene(Scene):
class SelectionTests(unittest.TestCase):
def aligned_pair(self, cls=ChoiceScene):
scene=cls();scene.guard_cost=0
scene.angle=math.degrees(math.atan2(scene.geometry['context'][0]-scene.camera[0],scene.geometry['context'][2]-scene.camera[2]))
scene.snapshot=scene.make();scene.history=[copy.deepcopy(scene.snapshot)]
scene.sleep(.1)
return scene
def test_facing_commits_existing_fresh_pair_without_second_drain(self):
scene=self.aligned_pair();reads=[];original=scene.observe
def observe():reads.append(scene.clock());return original()
scene.observe=observe
result=scene.facing()
self.assertTrue(result['completed']);self.assertEqual(len(reads),1)
self.assertEqual(result['facing_samples'],[310,311])
self.assertEqual([x['sample'] for x in result['samples']],[310,311])
self.assertEqual(result['final_sample'],311)
self.assertEqual(result['deadline_monotonic'],39)
self.assertEqual(scene.motions,[]);self.assertEqual(scene.wheels,[])
# The existing strict alignment path still performs its original reread.
strict=self.aligned_pair(Scene);strict_reads=[];original=strict.observe
def strict_observe():strict_reads.append(strict.clock());return original()
strict.observe=strict_observe
self.assertTrue(strict.run()['completed']);self.assertEqual(len(strict_reads),2)
def test_final_facing_idle_freshness_clock_and_deadline_rechecked(self):
for fault in ('not-idle','stale','clock-reversed','deadline'):
scene=self.aligned_pair();calls=[]
if fault=='deadline':scene.calibration['alignment_deadline_monotonic']=31.15
def idle():
calls.append(scene.now)
if len(calls)==2:
if fault=='not-idle':raise alignment.Refused('native keys are not idle')
if fault=='stale':scene.sleep(.51)
if fault=='clock-reversed':scene.now-=.2
if fault=='deadline':scene.sleep(.06)
scene.idle=idle
with self.subTest(fault=fault),self.assertRaises(alignment.Refused):scene.facing()
self.assertEqual(len(calls),2);self.assertFalse(scene.report['facing_proof']['completed'])
self.assertEqual(scene.motions,[]);self.assertEqual(scene.wheels,[])
def test_facing_complete_pair_does_not_hide_intermediate_native_reversal(self):
scene=self.aligned_pair();scene.history[1]['real_ms']=scene.history[0]['real_ms']-1
scene.history[1]['choices']['real_ms']=scene.history[1]['real_ms']
with self.assertRaisesRegex(ValueError,'reversed'):scene.facing()
self.assertEqual(scene.motions,[]);self.assertEqual(scene.wheels,[])
self.assertFalse(scene.report['facing_proof']['completed'])
def test_selection_consumes_every_later_record_after_early_facing_commit(self):
scene=self.aligned_pair();facing=scene.facing();self.assertEqual(facing['final_sample'],311)
scene.sleep(.3)
bad=next(x for x in scene.history if x['sample']==312)
bad['pressed']=1
# Later records are clean: latest-only selection would incorrectly pass.
self.assertEqual(scene.history[-1]['pressed'],0)
with self.assertRaisesRegex(ValueError,'Action began'):scene.select_action()
self.assertEqual(scene.wheels,[])
self.assertEqual(scene.report['action_selection_proof']['initial_sample'],311)
def test_actual_pose_facing_then_one_wheel_selects_with_same_lease(self):
s=ChoiceScene();f=s.facing();r=s.select_action()
self.assertTrue(f['completed']);self.assertNotIn('alignment_proof',s.report)