334 lines
19 KiB
Python
334 lines
19 KiB
Python
"""Bounded approach-only policy; no native library, display, process, or action calls.
|
|
|
|
Adapter contract (all records must be parsed from the same pinned console/nonce):
|
|
* observe() freshly joins INPUT/LOOK/GATE by sample+real_ms, validates the native
|
|
BASIS and absence of cancellation, and returns an ordered nonempty batch of
|
|
snapshots (all new consecutive samples, or repeat latest). Each has sample, real_ms,
|
|
received_monotonic, origin, direction, body_forward, input, forward, velocity,
|
|
inventory, phase, door_state, door_control, and geometry.
|
|
* geometry: nonce, basis, prison, gate, gate_forward, context, radius. Coordinates
|
|
are native GEOMETRY values; binding is the native observer's pinned references,
|
|
not a fabricated entity ID. This policy supports only the authored axis-aligned
|
|
12m enclosure; it derives an interior target from the observed handle.
|
|
* calibration is independently accepted same-run tiny look evidence projected to
|
|
nonce, completed, pixels, baseline_direction, response_direction,
|
|
ready_real_ms, ready_monotonic, observer_start_real_ms.
|
|
* guard.check() is the existing full private ownership/focus guard. keyboard has
|
|
require_idle()/send('W', bool). mouse.move(dx,0,guard) must use the same guarded
|
|
connection and current/projected clipped pointer checks, explicitly permitting
|
|
the explicitly selected standard128px or coarse384px cap. Existing two-motion
|
|
helper remains unchanged; profile choice never changes the observation clocks.
|
|
* notify receives the existing press-pending/release-confirmed events, plus
|
|
approach-pending (absolute monotonic deadline)/approach-finished. The wiring
|
|
MUST hard-supervise that deadline and the unchanged 1.5-second key lease.
|
|
* key_state_busy is the existing explicit Keyboard KeyStateBusy exception class;
|
|
other failures are never retried as busy-state polls.
|
|
|
|
No adapter implementation or input is executed by importing this module. A pass
|
|
proves an interior approach only, never reach, action selection, unlock or escape.
|
|
"""
|
|
import copy
|
|
import math
|
|
|
|
|
|
class Refused(ValueError):
|
|
pass
|
|
|
|
|
|
def require(value, reason):
|
|
if not value:
|
|
raise Refused(reason)
|
|
|
|
|
|
def number(value):
|
|
require(type(value) in (int, float) and math.isfinite(value), 'Nonfinite or invalid numeric observation')
|
|
return float(value)
|
|
|
|
|
|
def vector(value):
|
|
require(type(value) in (list, tuple) and len(value) == 3, 'Invalid vector observation')
|
|
result = tuple(number(v) for v in value)
|
|
require(all(abs(v) <= 10000000 for v in result), 'Unbounded native vector')
|
|
return result
|
|
|
|
|
|
def horizontal(a, b):
|
|
return math.hypot(a[0]-b[0], a[2]-b[2])
|
|
|
|
|
|
def length(value):
|
|
return math.sqrt(sum(v*v for v in value))
|
|
|
|
|
|
def yaw(direction):
|
|
d = vector(direction)
|
|
require(.95 <= length(d) <= 1.05 and math.hypot(d[0], d[2]) > .1, 'Invalid native direction')
|
|
return math.degrees(math.atan2(d[0], d[2]))
|
|
|
|
|
|
def delta(target, actual):
|
|
return (target-actual+180) % 360-180
|
|
|
|
|
|
def target_for(geometry):
|
|
require(type(geometry) is dict and set(geometry) == {'nonce', 'basis', 'prison', 'gate', 'gate_forward', 'context', 'radius'}, 'Incomplete native gate geometry')
|
|
require(type(geometry['nonce']) is str and 1 <= len(geometry['nonce']) <= 32 and geometry['nonce'].isascii() and geometry['nonce'].isalnum(), 'Invalid geometry nonce')
|
|
require(geometry['basis'] == 'registered-prison-lock-native-interaction', 'Missing pinned native gate basis')
|
|
prison, gate, context = (vector(geometry[k]) for k in ('prison', 'gate', 'context'))
|
|
facing = vector(geometry['gate_forward']); yaw(facing)
|
|
require(0 < number(geometry['radius']) <= 10, 'Invalid native context radius')
|
|
dx, dz = prison[0]-gate[0], prison[2]-gate[2]
|
|
distance = math.hypot(dx, dz)
|
|
require(5.75 <= distance <= 6.25 and min(abs(dx), abs(dz)) <= .1, 'Unsupported measured prison gate placement')
|
|
inward = (dx/distance, 0., dz/distance)
|
|
require(abs(facing[0]*inward[0]+facing[2]*inward[2]) >= .99, 'Gate orientation differs from authored enclosure')
|
|
require(horizontal(context, gate) <= 4.5, 'Context is not near original gate')
|
|
target = (context[0]+1.5*inward[0], prison[1], context[2]+1.5*inward[2])
|
|
require(inside(target, prison), 'Derived approach is outside safe prison interior')
|
|
return target, prison
|
|
|
|
|
|
def inside(position, center):
|
|
return abs(position[0]-center[0]) <= 5.25 and abs(position[2]-center[2]) <= 5.25
|
|
|
|
|
|
def perform_approach(guard, keyboard, mouse, observe, geometry, calibration, report, *, clock, sleep, notify, key_state_busy, profile="standard"):
|
|
"""Run one bounded route through explicit adapters; retain partial failure evidence."""
|
|
require(profile in ('standard', 'coarse') and type(profile) is str, 'Unknown approach profile')
|
|
max_pixels, max_angle, desired_angle, max_pulses = (384, 35, 25, 16) if profile == 'coarse' else (128, 15, 10, 12)
|
|
require('approach_proof' not in report, 'Approach attempt already exists')
|
|
pinned = copy.deepcopy(geometry)
|
|
target, center = target_for(pinned)
|
|
require(calibration.get('completed') is True and calibration.get('nonce') == pinned['nonce'] and calibration.get('pixels') == 24, 'Missing accepted same-run look calibration')
|
|
gain = delta(yaw(calibration['response_direction']), yaw(calibration['baseline_direction']))/24
|
|
require(.01 <= abs(gain) <= .5, 'Measured calibration gain outside bounded proposal range')
|
|
ready_at = number(calibration['ready_monotonic']); ready_real = number(calibration['ready_real_ms'])
|
|
native_start = number(calibration['observer_start_real_ms'])
|
|
require(0 <= native_start <= ready_real and 2000 <= ready_real-native_start < 45000, 'Invalid original native observation window')
|
|
require(0 <= ready_at <= clock(), 'Invalid original READY monotonic time')
|
|
deadline = ready_at+35
|
|
require(clock() < deadline, 'Insufficient original approach time')
|
|
state = {'profile': profile, 'requested': True, 'completed': False, 'nonce': pinned['nonce'], 'geometry': pinned,
|
|
'target': list(target), 'deadline_monotonic': deadline, 'started_monotonic': clock(),
|
|
'finished_monotonic': None, 'motions': [], 'events': [], 'stops': [],
|
|
'samples': [], 'cumulative_horizontal_m': 0., 'unconfirmed_held_key': None,
|
|
'paired_release_errors': [], 'release_timings': []}
|
|
report['approach_proof'] = state
|
|
previous = None; held = False; turn_count = 0; pixels = 0; pulse_count = 0
|
|
largest_stop = None; active_turn = None
|
|
|
|
def consume(s):
|
|
nonlocal previous
|
|
s = copy.deepcopy(s)
|
|
require(type(s['sample']) is int and 1 <= s['sample'] <= 450, 'Invalid native sample index')
|
|
real = number(s['real_ms']); received = number(s['received_monotonic'])
|
|
require(0 <= received <= clock(), 'Native receipt timestamp is invalid')
|
|
require(ready_real <= real and real-native_start < 45000 and real-ready_real < 35000, 'Original native lifetime exceeded')
|
|
require(s['geometry'] == pinned and s['phase'] == 1 and s['inventory'] is False, 'Original gate, prison phase or UI changed')
|
|
require(number(s['door_state']) == 0 and number(s['door_control']) == 0, 'Original gate is not closed and untouched')
|
|
for k in ('origin', 'direction', 'body_forward', 'input', 'velocity'):
|
|
s[k] = vector(s[k])
|
|
s['forward'] = number(s['forward']); yaw(s['direction']); yaw(s['body_forward'])
|
|
require(inside(s['origin'], center) and abs(s['origin'][1]-center[1]) <= 3, 'Body left bounded prison interior')
|
|
require(length(s['velocity']) <= 5, 'Native movement exceeds approach envelope')
|
|
require(abs(s['input'][0]) <= .01 and abs(s['input'][1]) <= .01 and -.01 <= s['input'][2] <= 1.01 and -.01 <= s['forward'] <= 1.01, 'Unexpected native movement input')
|
|
if active_turn is not None:
|
|
turn_yaw, turn_origin, turn_sign = active_turn
|
|
change = delta(yaw(s['direction']), turn_yaw)
|
|
require(abs(change) <= max_angle, 'Relative turn exceeded native angle bound')
|
|
require(horizontal(s['origin'], turn_origin) <= .05, 'Body translated during stationary heading')
|
|
if abs(change) >= .05:
|
|
require(change*turn_sign > 0, 'Native heading turned opposite to proposal')
|
|
if previous:
|
|
require(s['sample'] >= previous['sample'] and real >= previous['real_ms'], 'Native approach samples reversed')
|
|
if s['sample'] == previous['sample']:
|
|
require({k:v for k,v in s.items() if k != 'received_monotonic'} == {k:v for k,v in previous.items() if k != 'received_monotonic'}, 'Native sample changed without advancing')
|
|
else:
|
|
dt = real-previous['real_ms']
|
|
require(s['sample'] == previous['sample']+1 and 0 < dt <= 500, 'Native approach sampling gap')
|
|
distance = horizontal(s['origin'], previous['origin'])
|
|
require(distance <= .25+5*dt/1000, 'Native displacement exceeds continuous motion bound')
|
|
state['cumulative_horizontal_m'] += distance
|
|
require(state['cumulative_horizontal_m'] <= 6, 'Approach path budget exceeded')
|
|
if previous is None or s['sample'] != previous['sample']:
|
|
state['samples'].append(copy.deepcopy(s))
|
|
previous = s
|
|
return s
|
|
|
|
def read():
|
|
require(clock() < deadline, 'Original approach deadline exceeded')
|
|
guard.check()
|
|
batch = observe()
|
|
require(type(batch) is list and 1 <= len(batch) <= 450, 'Missing bounded native snapshot batch')
|
|
for item in batch:
|
|
s = consume(item)
|
|
require(clock() < deadline, 'Original approach deadline exceeded during observation')
|
|
require(0 <= clock()-s['received_monotonic'] <= .5, 'Native approach sample stale')
|
|
return s
|
|
|
|
def neutral(s):
|
|
return abs(s['forward']) <= .01 and length(s['input']) <= .01 and length(s['velocity']) <= .05
|
|
|
|
def wait_stopped(after, seconds, angle=None, moved_from=None, turn_sign=None):
|
|
end = min(deadline, clock()+seconds); first = None
|
|
while clock() < end:
|
|
s = read()
|
|
moved = moved_from is None or horizontal(s['origin'], moved_from) >= .05
|
|
if angle is not None:
|
|
change = delta(yaw(s['direction']), angle)
|
|
require(abs(change) <= max_angle, 'Relative turn exceeded native angle bound')
|
|
if abs(change) >= .05:
|
|
require(change*turn_sign > 0, 'Native heading turned opposite to proposal')
|
|
moved = moved and abs(change) >= .05
|
|
if s['sample'] > after and neutral(s) and moved:
|
|
if first is None:
|
|
first = s
|
|
elif s['real_ms']-first['real_ms'] >= 100:
|
|
if abs(delta(yaw(s['direction']), yaw(first['direction']))) <= .2 and horizontal(s['origin'], first['origin']) <= .02:
|
|
if angle is not None:
|
|
require(abs(delta(yaw(s['direction']), angle)) <= max_angle, 'Relative turn exceeded native angle bound')
|
|
require(clock() <= end, 'Settling exceeded original stage deadline')
|
|
return s
|
|
first = s
|
|
else:
|
|
first = None
|
|
sleep(.025)
|
|
raise Refused('Native full-stop or turn-settling acknowledgement missing')
|
|
|
|
def heading(s):
|
|
nonlocal turn_count, pixels, active_turn
|
|
end = min(deadline, clock()+20); growing = 0; flips = 0; old_sign = None
|
|
while True:
|
|
require(neutral(s), 'Heading correction requires stationary body')
|
|
wanted = math.degrees(math.atan2(target[0]-s['origin'][0], target[2]-s['origin'][2]))
|
|
error = delta(wanted, yaw(s['direction']))
|
|
if abs(error) <= 3:
|
|
return s
|
|
require(clock() < end and turn_count < 16, 'Heading request budget exceeded')
|
|
desired = max(-desired_angle, min(desired_angle, .75*error))
|
|
dx = int(round(desired/gain)); dx = max(-max_pixels, min(max_pixels, dx))
|
|
if dx == 0: dx = 1 if desired/gain > 0 else -1
|
|
require(pixels+abs(dx) <= 2048, 'Relative pixel budget exceeded')
|
|
before = read(); require(neutral(before), 'Body moved before turn')
|
|
keyboard.require_idle()
|
|
active_turn = (yaw(before['direction']), before['origin'], dx*gain)
|
|
mouse.move(dx, 0, guard)
|
|
turn_count += 1; pixels += abs(dx)
|
|
state['motions'].append({'dx': dx, 'dy': 0, 'monotonic': clock(), 'after_sample': before['sample']})
|
|
after = wait_stopped(before['sample'], 1.5, yaw(before['direction']), turn_sign=dx*gain)
|
|
state['motions'][-1]['response_sample'] = after['sample']
|
|
active_turn = None
|
|
require(horizontal(after['origin'], before['origin']) <= .05, 'Body translated during stationary heading')
|
|
change = delta(yaw(after['direction']), yaw(before['direction']))
|
|
require(abs(change) >= .05 and change*dx*gain > 0, 'Native heading failed or turned opposite to proposal')
|
|
new_error = delta(wanted, yaw(after['direction']))
|
|
growing = growing+1 if abs(new_error) >= abs(error) else 0
|
|
sign = new_error > 0
|
|
if old_sign is not None and sign != old_sign: flips += 1
|
|
old_sign = sign
|
|
require(growing < 2 and flips <= 2, 'Native heading failed to converge')
|
|
s = after
|
|
|
|
def release(not_before=None, cleanup=False):
|
|
nonlocal held
|
|
# At most two checks per normal pulse and one finally release attempt.
|
|
# These are diagnostic times, never authority to skip a fresh guard.
|
|
timing = {'cleanup': cleanup, 'guards': [], 'up_attempted_monotonic': None,
|
|
'up_returned_monotonic': None}
|
|
state['release_timings'].append(timing)
|
|
def checked():
|
|
record = {'entered_monotonic': clock(), 'returned_monotonic': None}
|
|
timing['guards'].append(record)
|
|
try:
|
|
guard.check()
|
|
except Exception as error:
|
|
record['error'] = str(error)
|
|
raise
|
|
finally:
|
|
record['returned_monotonic'] = clock()
|
|
# A native context failure does not prevent an owned release; only the
|
|
# full live private display/process/focus proof authorizes that release.
|
|
# Use the initial minimum hold to perform that proof instead of adding
|
|
# an unconditional 50ms before its already substantial runtime.
|
|
checked()
|
|
remaining = 0. if not_before is None else not_before-clock()
|
|
if remaining > 0:
|
|
sleep(remaining)
|
|
checked() # waiting invalidates the preceding ownership proof
|
|
timing['up_attempted_monotonic'] = clock()
|
|
keyboard.send('W', False)
|
|
timing['up_returned_monotonic'] = clock()
|
|
state['events'].append({'key': 'W', 'down': False, 'monotonic': clock()})
|
|
end = clock()+.35
|
|
while True:
|
|
guard.check()
|
|
try:
|
|
keyboard.require_idle()
|
|
break
|
|
except ValueError as error:
|
|
# Only the injected existing busy-state subtype is retryable.
|
|
if not isinstance(error, key_state_busy) or clock() >= end:
|
|
raise
|
|
sleep(min(.025, end-clock()))
|
|
held = False; state['unconfirmed_held_key'] = None
|
|
notify({'kind': 'release-confirmed', 'key': 'W'})
|
|
|
|
notify({'kind': 'approach-pending', 'deadline_monotonic': deadline})
|
|
try:
|
|
keyboard.require_idle()
|
|
current = read(); current = wait_stopped(current['sample']-1, 1.5)
|
|
initial_distance = horizontal(current['origin'], target)
|
|
require(.75 < initial_distance <= 6, 'Initial approach distance outside route scope')
|
|
while horizontal(current['origin'], target) > .75:
|
|
current = heading(current)
|
|
require(pulse_count < max_pulses, 'Forward pulse budget exceeded')
|
|
remaining = horizontal(current['origin'], target)
|
|
reserve = (largest_stop if largest_stop is not None else 1.25)+.25
|
|
require(remaining > reserve, 'Insufficient distance for measured stopping reserve')
|
|
ang = math.radians(yaw(current['direction']))
|
|
projected = (current['origin'][0]+reserve*math.sin(ang), current['origin'][1], current['origin'][2]+reserve*math.cos(ang))
|
|
require(inside(projected, center), 'Stopping reserve leaves safe interior')
|
|
before = read(); require(neutral(before), 'Forward pulse requires full native stop')
|
|
remaining = horizontal(before['origin'], target)
|
|
wanted = math.degrees(math.atan2(target[0]-before['origin'][0], target[2]-before['origin'][2]))
|
|
require(abs(delta(wanted, yaw(before['direction']))) <= 3, 'Fresh heading changed before W')
|
|
require(remaining > reserve, 'Fresh position exhausted stopping reserve')
|
|
ang = math.radians(yaw(before['direction']))
|
|
projected = (before['origin'][0]+reserve*math.sin(ang), before['origin'][1], before['origin'][2]+reserve*math.cos(ang))
|
|
require(inside(projected, center), 'Fresh stopping reserve leaves safe interior')
|
|
keyboard.require_idle()
|
|
require(clock() < deadline, 'Original approach deadline exceeded before press')
|
|
held = True; state['unconfirmed_held_key'] = 'W'
|
|
notify({'kind': 'press-pending', 'key': 'W'})
|
|
keyboard.send('W', True); pressed = clock()
|
|
state['events'].append({'key': 'W', 'down': True, 'monotonic': pressed})
|
|
release(not_before=pressed+.05)
|
|
released = state['events'][-1]['monotonic']
|
|
require(released-pressed <= .4, 'Actual W hold exceeded 400ms approach bound')
|
|
pulse_count += 1
|
|
after = wait_stopped(before['sample'], 1.5, moved_from=before['origin'])
|
|
require(any(s['forward'] > .01 and s['input'][2] > .01 for s in state['samples'] if before['sample'] < s['sample'] <= after['sample']), 'Native W acknowledgement missing from pulse samples')
|
|
distance = horizontal(before['origin'], after['origin'])
|
|
progress = remaining-horizontal(after['origin'], target)
|
|
require(.05 <= distance <= 1.25 and progress >= .05, 'Forward motion missed, overshot or moved away from target')
|
|
require(abs(delta(yaw(after['direction']), yaw(before['direction']))) <= 5, 'Heading drifted during forward pulse')
|
|
largest_stop = max(largest_stop or 0, distance)
|
|
state['stops'].append({'before_sample': before['sample'], 'after_sample': after['sample'], 'distance': distance, 'progress': progress})
|
|
current = after
|
|
final = read()
|
|
require(neutral(final) and horizontal(final['origin'], target) <= .75, 'Final native body is not stopped at approach target')
|
|
state['final_sample'] = final['sample']
|
|
keyboard.require_idle()
|
|
require(clock() < deadline, 'Original approach deadline exceeded at completion')
|
|
state['completed'] = True
|
|
return state
|
|
except Exception as error:
|
|
state['error'] = str(error)
|
|
raise
|
|
finally:
|
|
if held:
|
|
try: release(cleanup=True)
|
|
except Exception as error: state['paired_release_errors'].append(str(error))
|
|
state['finished_monotonic'] = clock()
|
|
notify({'kind': 'approach-finished', 'successful': state['completed']})
|