Qualify route gain and observe native gate context through ordinary input
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
"""Opt-in stationary yaw alignment policy, with no input backend or keyboard send.
|
||||
|
||||
perform_alignment consumes the same validated ordered native snapshot batches as
|
||||
prison_approach_proof plus camera and flat native selected/context_selected/
|
||||
cached_performable/progress_bound/performing/pressed/progress/duration fields.
|
||||
The adapter pins the original console, body, camera and gate identities and
|
||||
rejects cancellation. No field here permits native identity re-adoption.
|
||||
|
||||
require_idle() is read-only. mouse.move(dx,0,guard,deadline=absolute) is an explicit
|
||||
alignment-only capability with existing ownership/focus/current+projected-pointer
|
||||
checks and a final absolute deadline check before its relative input call.
|
||||
notify({kind:'alignment-pending',deadline_monotonic:...}) requires an independent
|
||||
hard supervisor; alignment-finished is only a policy result, not final run proof.
|
||||
No F, W, wheel, pitch, focus, camera write or native connection exists here.
|
||||
"""
|
||||
import copy
|
||||
import math
|
||||
|
||||
from prison_approach_proof import vector, number, yaw, delta, length, horizontal, target_for
|
||||
|
||||
|
||||
class Refused(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def need(value, reason):
|
||||
if not value:
|
||||
raise Refused(reason)
|
||||
|
||||
|
||||
def perform_alignment(guard, require_idle, mouse, observe, geometry, calibration, approach_evidence, report, *, clock, sleep, notify):
|
||||
need('alignment_proof' not in report, 'Alignment attempt already exists')
|
||||
pinned = copy.deepcopy(geometry)
|
||||
target_for(pinned) # Existing measured authored-enclosure compatibility gate.
|
||||
approach = report.get('approach_proof', {})
|
||||
need(approach_evidence.get('verified') is True and approach.get('completed') is True and
|
||||
approach.get('nonce') == pinned['nonce'] and approach.get('geometry') == pinned and
|
||||
approach_evidence.get('last_sample') == approach.get('final_sample'),
|
||||
'Alignment requires independently completed same-run approach')
|
||||
prior = approach.get('samples', [])
|
||||
need(prior and prior[-1]['sample'] == approach['final_sample'], 'Original approach final body sample unavailable')
|
||||
body = vector(prior[-1]['origin'])
|
||||
need(calibration.get('completed') is True and calibration.get('nonce') == pinned['nonce'] and calibration.get('pixels') == 24,
|
||||
'Alignment requires accepted same-run yaw calibration')
|
||||
gain = delta(yaw(calibration['response_direction']), yaw(calibration['baseline_direction']))/24
|
||||
need(.01 <= abs(gain) <= .5, 'Native yaw calibration outside bounded range')
|
||||
queued = number(calibration['observer_start_real_ms']); ready = number(calibration['ready_real_ms'])
|
||||
ready_at = number(calibration['ready_monotonic']); started = clock()
|
||||
need(0 <= queued and 2000 <= ready-queued < 45000 and 0 <= ready_at <= started, 'Original observation clocks invalid')
|
||||
original_deadline = ready_at+(45000-(ready-queued))/1000-.5
|
||||
supplied = number(calibration.get('observer_deadline_monotonic', original_deadline))
|
||||
need(supplied <= original_deadline, 'Supplied observer deadline renews original lifetime')
|
||||
stage_supplied = number(calibration.get('alignment_deadline_monotonic', started+8))
|
||||
need(stage_supplied <= started+8, 'Supplied alignment deadline exceeds stage cap')
|
||||
deadline = min(started+8, original_deadline, supplied, stage_supplied)
|
||||
need(started < deadline, 'No original observation time remains for alignment')
|
||||
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': [], 'motions': [], 'samples': []}
|
||||
report['alignment_proof'] = state
|
||||
previous = None; active_turn = None; requests = 0
|
||||
|
||||
def neutral(s):
|
||||
return abs(s['forward']) <= .01 and length(s['input']) <= .01 and length(s['velocity']) <= .05
|
||||
|
||||
def bearing(s):
|
||||
camera = s['camera']; context = pinned['context']
|
||||
need(.25 <= horizontal(camera, context) <= 5, 'Native camera/handle bearing is degenerate or out of scope')
|
||||
return math.degrees(math.atan2(context[0]-camera[0], context[2]-camera[2]))
|
||||
|
||||
def consume(s):
|
||||
nonlocal previous
|
||||
s = copy.deepcopy(s)
|
||||
index = s['sample']; real = number(s['real_ms']); receipt = number(s['received_monotonic'])
|
||||
need(type(index) is int and approach['final_sample'] <= index <= 450, 'Alignment sample precedes approach or exceeds bound')
|
||||
need(0 <= receipt <= clock() and queued <= real < queued+45000, 'Original native observation lifetime exceeded')
|
||||
need(s['geometry'] == pinned and s['phase'] == 1 and s['inventory'] is False,
|
||||
'Original prison geometry, phase or UI changed')
|
||||
need(number(s['door_state']) == 0 and number(s['door_control']) == 0, 'Original gate was operated')
|
||||
for k in ('origin', 'camera', 'direction', 'body_forward', 'input', 'velocity'):
|
||||
s[k] = vector(s[k])
|
||||
s['forward'] = number(s['forward']); yaw(s['direction']); yaw(s['body_forward'])
|
||||
need(neutral(s) and math.dist(body, s['origin']) <= .05,
|
||||
'Original stopped body moved during alignment')
|
||||
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 s['performing'] is False and number(s['duration']) == 4,
|
||||
'Gate interaction began during yaw-only alignment')
|
||||
bearing(s)
|
||||
if active_turn is not None:
|
||||
before_yaw, sign = active_turn
|
||||
change = delta(yaw(s['direction']), before_yaw)
|
||||
need(abs(change) <= 15, 'Native yaw exceeded alignment turn bound')
|
||||
if abs(change) >= .05:
|
||||
need(change*sign > 0, 'Native yaw reversed alignment request')
|
||||
if previous:
|
||||
need(index >= previous['sample'] and real >= previous['real_ms'], 'Alignment native samples reversed')
|
||||
if index == previous['sample']:
|
||||
need(s == previous, 'Repeated alignment sample changed')
|
||||
else:
|
||||
need(index == previous['sample']+1 and 0 < real-previous['real_ms'] <= 500, 'Alignment 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, 'Frozen alignment deadline exceeded')
|
||||
guard.check(); require_idle()
|
||||
batch = observe()
|
||||
need(type(batch) is list and 1 <= len(batch) <= 450, 'Missing ordered alignment snapshot batch')
|
||||
for raw in batch:
|
||||
s = consume(raw)
|
||||
need(0 <= clock()-s['received_monotonic'] <= .5, 'Latest native alignment observation is stale')
|
||||
need(clock() < deadline, 'Frozen alignment deadline exceeded during observation')
|
||||
return s
|
||||
|
||||
def stable(first, second):
|
||||
return (second['sample'] == first['sample']+1 and second['real_ms']-first['real_ms'] >= 100 and
|
||||
abs(delta(yaw(second['direction']), yaw(first['direction']))) <= .2)
|
||||
|
||||
def selected(s):
|
||||
return s['selected'] and s['context_selected'] and s['cached_performable'] and s['progress_bound'] and abs(delta(bearing(s), yaw(s['direction']))) <= 3
|
||||
|
||||
def selection_pair():
|
||||
samples = state['samples']
|
||||
if len(samples) >= 2 and stable(samples[-2], samples[-1]) and selected(samples[-2]) and selected(samples[-1]):
|
||||
return [samples[-2]['sample'], samples[-1]['sample']]
|
||||
return []
|
||||
|
||||
try:
|
||||
notify({'kind': 'alignment-pending', 'deadline_monotonic': deadline})
|
||||
initial = read()
|
||||
# First receipt cannot extend the frozen original READY-derived lease.
|
||||
# Deduct the explicit 500ms observation/dispatch reserve, not just age.
|
||||
deadline = min(deadline, initial['received_monotonic']+(queued+45000-initial['real_ms'])/1000-.5)
|
||||
state['deadline_monotonic'] = deadline
|
||||
need(clock() < deadline, 'Fresh native sample leaves no alignment time')
|
||||
notify({'kind': 'alignment-pending', 'deadline_monotonic': deadline})
|
||||
current = initial
|
||||
while clock() < deadline:
|
||||
pair = selection_pair()
|
||||
if pair:
|
||||
current = read()
|
||||
pair = selection_pair()
|
||||
if not pair:
|
||||
continue
|
||||
require_idle()
|
||||
need(clock() < deadline, 'Frozen alignment deadline exceeded at completion')
|
||||
state['selection_samples'] = pair; state['completed'] = True
|
||||
return state
|
||||
error = delta(bearing(current), yaw(current['direction']))
|
||||
if abs(error) <= 3:
|
||||
# Reached measured bearing; allow bounded native selection update.
|
||||
# Never substitute additional movement, pitch or interaction.
|
||||
sleep(.025); current = read(); continue
|
||||
need(requests < 6, 'Alignment relative-request budget exhausted')
|
||||
need(len(state['samples']) >= 2 or clock()+.1 < deadline, 'Insufficient time for stable initial pose')
|
||||
if len(state['samples']) < 2 or not stable(state['samples'][-2], state['samples'][-1]):
|
||||
sleep(.025); current = read(); continue
|
||||
before = read()
|
||||
# Always propose from the final fresh camera/handle bearing.
|
||||
error = delta(bearing(before), yaw(before['direction']))
|
||||
if abs(error) <= 3:
|
||||
current = before; continue
|
||||
desired = max(-10., min(10., .75*error))
|
||||
dx = max(-128, min(128, int(round(desired/gain))))
|
||||
if dx == 0: dx = 1 if desired/gain > 0 else -1
|
||||
active_turn = (yaw(before['direction']), dx*gain)
|
||||
mouse.move(dx, 0, guard, deadline=deadline)
|
||||
requests += 1
|
||||
motion = {'dx': dx, 'dy': 0, 'monotonic': clock(), 'after_sample': before['sample'], 'response_sample': None}
|
||||
state['motions'].append(motion)
|
||||
end = min(deadline, clock()+1.5)
|
||||
while clock() < end:
|
||||
current = read()
|
||||
change = delta(yaw(current['direction']), active_turn[0])
|
||||
samples = state['samples']
|
||||
if abs(change) >= .05 and len(samples) >= 2 and stable(samples[-2], samples[-1]):
|
||||
need(clock() <= end, 'Native yaw response exceeded frozen turn interval')
|
||||
motion['response_sample'] = current['sample']; active_turn = None
|
||||
break
|
||||
sleep(.025)
|
||||
else:
|
||||
raise Refused('Signed native yaw response and settling missing')
|
||||
raise Refused('Exact lock selection missing before frozen alignment deadline')
|
||||
except Exception as error:
|
||||
state['error'] = str(error)
|
||||
raise
|
||||
finally:
|
||||
state['finished_monotonic'] = clock()
|
||||
notify({'kind': 'alignment-finished', 'successful': state['completed']})
|
||||
Reference in New Issue
Block a user