"""One explicit logical-button5 selection trial; adapters own all native I/O. Each snapshot joins native pose/input/gate and choices at the same nonce/index/ real clock. choices contains nonce,sample,real_ms,selected_id,actions; actions contains exactly [{class,id,permitted}, ...]. This deliberately supports only closed disabled ERR_PrisonDoorAction followed by permitted ERR_UnlockPrisonAction. Their distinct stock/authored names cannot merge in stock action aggregation. wheel.pulse(5,guard,deadline=...) must return button/down_monotonic/up_monotonic/ released, after actual paired release confirmation. A failure must retain its own uncertain-button evidence and attempt safe release; this policy cannot send another button event. No default workflow activates this module. """ import copy import math from prison_approach_proof import number, vector, length, yaw, delta, horizontal, target_for class Refused(ValueError): pass def need(ok, message): if not ok: raise Refused(message) def choices_for(s, geometry, *, selected_lock=False): """Require the exact two-entry experiment, not arbitrary raw-list navigation.""" c = s.get('choices') need(type(c) is dict and set(c) == {'nonce','sample','real_ms','selected_id','selected_class','actions'}, 'Missing exact native choices') need(c['nonce'] == geometry['nonce'] and type(c['sample']) is int and c['sample'] == s['sample'] and number(c['real_ms']) == s['real_ms'], 'Choices are not joined to the native sample') actions = c['actions'] need(type(actions) is list and len(actions) == 2, 'Selection requires exactly two native actions') for a, name, permitted in zip(actions, ('ERR_PrisonDoorAction','ERR_UnlockPrisonAction'), (False, True)): need(type(a) is dict and set(a) == {'class','id','permitted'} and a['class'] == name and type(a['id']) is int and a['id'] >= 0 and a['permitted'] is permitted, 'Native action class/order/permission differs from bounded trial') need(actions[0]['id'] != actions[1]['id'], 'Native action IDs collide') need(type(c['selected_id']) is int and c['selected_id'] == actions[int(selected_lock)]['id'], 'Unexpected native selected action ID') need(c['selected_class'] == actions[int(selected_lock)]['class'], 'Native selected action class disagrees') need(s['context_selected'] is True and s['cached_performable'] is True and s['selected'] is selected_lock and s['progress_bound'] is selected_lock, 'Native selected/context/permission binding disagrees') return copy.deepcopy(actions) def perform_selection(guard, require_idle, wheel, observe, geometry, calibration, facing_evidence, report, *, clock, sleep, notify): need('action_selection_proof' not in report, 'Selection attempt already exists') pinned = copy.deepcopy(geometry); target_for(pinned) facing = report.get('facing_proof', {}) need(facing_evidence.get('verified') is True and facing.get('completed') is True and facing.get('nonce') == pinned['nonce'] and facing.get('geometry') == pinned and facing_evidence.get('last_sample') == facing.get('final_sample'), 'Independent same-run facing proof required') prior = facing.get('samples', []) need(prior and prior[-1]['sample'] == facing['final_sample'], 'Facing final native sample unavailable') anchor = prior[-1]; body = vector(anchor['origin']); actions = choices_for(anchor, pinned) started = clock(); deadline = number(facing['deadline_monotonic']) queued = number(calibration['observer_start_real_ms']); ready = number(calibration['ready_real_ms']) ready_at = number(calibration['ready_monotonic']) need(calibration.get('nonce') == pinned['nonce'] and 0 <= queued and 2000 <= ready-queued < 45000 and 0 <= ready_at <= started, 'Original observation clock binding invalid') original = ready_at+(45000-(ready-queued))/1000-.5 need(deadline <= number(facing['started_monotonic'])+8 and deadline <= original, 'Facing deadline renewed the original lease') state = {'requested':True,'completed':False,'nonce':pinned['nonce'],'geometry':pinned, 'started_monotonic':started,'finished_monotonic':None,'deadline_monotonic':deadline, 'initial_sample':None,'final_sample':None,'selection_samples':[], 'actions':actions, 'wheel':None,'samples':[]} report['action_selection_proof'] = state previous = None; dispatched = False def consume(raw): nonlocal previous s = copy.deepcopy(raw) index = s['sample']; real = number(s['real_ms']); receipt = number(s['received_monotonic']) need(type(index) is int and facing['final_sample'] <= index <= 450 and 0 <= receipt <= clock() and queued <= real < queued+45000, 'Native selection index/clock invalid') need(s['geometry'] == pinned and s['phase'] == 1 and s['inventory'] is False and number(s['door_state']) == 0 and number(s['door_control']) == 0, 'Original closed prison context changed') for k in ('origin','camera','direction','body_forward','input','velocity'): s[k] = vector(s[k]) need(math.dist(body,s['origin']) <= .05 and abs(number(s['forward'])) <= .01 and length(s['input']) <= .01 and length(s['velocity']) <= .05, 'Body or movement changed during selection') for k in ('selected','context_selected','cached_performable','progress_bound','performing'): need(type(s[k]) is bool, 'Malformed native action flag') need(number(s['pressed']) == 0 and number(s['progress']) == 0 and not s['performing'] and number(s['duration']) == 4, 'Action began during selection-only trial') need(.25 <= horizontal(s['camera'],pinned['context']) <= 5, 'Camera/handle bearing invalid') bearing = math.degrees(math.atan2(pinned['context'][0]-s['camera'][0],pinned['context'][2]-s['camera'][2])) need(abs(delta(bearing,yaw(s['direction']))) <= 3 and abs(delta(yaw(s['direction']),yaw(anchor['direction']))) <= .2, 'Facing changed during wheel-only trial') observed = choices_for(s,pinned,selected_lock=s['selected']) need(observed == actions, 'Original action identities/order changed') if not dispatched: need(not s['selected'], 'Lock selected before wheel dispatch') if previous: need(not previous['selected'] or s['selected'], 'Native lock selection dropped after wheel acknowledgement') need(index >= previous['sample'] and real >= previous['real_ms'], 'Native samples reversed') if index == previous['sample']: need(s == previous, 'Repeated native sample changed') else: need(index == previous['sample']+1 and 0 < real-previous['real_ms'] <= 500, 'Native sample gap') if previous is None or index != previous['sample']: state['samples'].append(copy.deepcopy(s)) previous = s if state['initial_sample'] is None: state['initial_sample'] = index state['final_sample'] = index return s def read(): need(clock() < deadline, 'Original facing/selection deadline expired') guard.check(); require_idle() batch = observe() need(type(batch) is list and 1 <= len(batch) <= 450, 'Missing joined selection samples') for raw in batch: s = consume(raw) need(0 <= clock()-s['received_monotonic'] <= .5 and clock() < deadline, 'Native selection evidence stale or expired') return s try: notify({'kind':'action-selection-pending','deadline_monotonic':deadline}) current = read() need(clock()+1.5 <= deadline, 'Insufficient unchanged lease for one wheel trial') # No native calls here: adapter rechecks ownership/idle/deadline immediately # before each request and confirms release on the same connection. before = current['sample']; request_at = clock() state['wheel'] = {'button':5,'attempted_monotonic':request_at,'after_sample':before,'confirmed':False} result = wheel.pulse(5,guard,deadline=min(deadline,request_at+1.5)) need(type(result) is dict and set(result) == {'button','down_monotonic','up_monotonic','released'} and type(result['button']) is int and result['button'] == 5 and result['released'] is True, 'Wheel paired release not confirmed') down = number(result['down_monotonic']); up = number(result['up_monotonic']) need(request_at <= down < up <= clock() < deadline and up-request_at <= 1.5, 'Wheel attempt exceeded immutable lease') state['wheel'].update(result, confirmed=True) dispatched = True stop = min(deadline,up+1.5) while clock() < stop: current = read() need(clock() < stop, 'Native selection acknowledgement exceeded bound') samples = state['samples'] if len(samples) >= 2: a,b = samples[-2:] if a['sample'] > before and a['received_monotonic'] >= down and a['selected'] and b['selected'] and b['sample'] == a['sample']+1 and b['real_ms']-a['real_ms'] >= 100 and abs(delta(yaw(b['direction']),yaw(a['direction']))) <= .2: state['selection_samples'] = [a['sample'],b['sample']] state['completed'] = True return state sleep(.025) raise Refused('Single wheel did not select original lock before deadline') except Exception as error: state['error'] = str(error) raise finally: state['finished_monotonic'] = clock() notify({'kind':'action-selection-finished','successful':state['completed']})