Qualify route gain and observe native gate context through ordinary input
This commit is contained in:
+1
-1
@@ -59,7 +59,7 @@ def check():
|
||||
assert set(mapped)==set(inventory['script_paths']), 'Original inventory has untracked or unknown entries'
|
||||
print(f'PASS: {len(mapped)} original script entries tracked across {len(feature_ids)} unfinished responsibilities')
|
||||
print(f'PASS: {len(ids)} isolated addons, acyclic dependencies, {len(resources)} unique resources, exact vanilla references')
|
||||
for name in ['test_prison_approach_proof.py', 'test_prison_approach_evidence.py', 'test_ordinary_gate_evidence.py', 'test_ordinary_gate_observer.py', 'test_ordinary_look_observer.py', 'test_ordinary_look_evidence.py', 'test_relative_look_runner.py', 'test_radio_waiting_text.py', 'test_inventory_control_runner.py', 'test_inventory_layout.py', 'test_headless_input_proof.py', 'test_headless_input_exercise_runner.py', 'test_private_prefix_cleanup.py', 'test_runner_private_cleanup.py', 'test_ordinary_input_runner.py', 'test_ordinary_input_observer.py', 'test_aircraft_route_envelope.py', 'test_aircraft_route_policy.py', 'test_aircraft_route_probe.py', 'test_rough_intel_feedback.py', 'test_headless_display_probe.py', 'test_headless_display_runner.py', 'test_preparation_status.py', 'test_hint_delivery.py', 'test_native_end_lifecycle.py', 'test_native_end_runner.py', 'test_escape_result_wording.py', 'test_native_stats.py', 'test_aircraft_translation.py', 'test_spawn_retry_budget.py', 'test_action_feedback.py', 'test_native_aircraft_control.py', 'test_aircraft_flight_policy.py', 'test_aircraft_flight_envelope.py', 'test_ordinary_screenshot.py', 'test_participant_selection.py', 'test_native_shared_map_ownership.py', 'test_shared_map_ownership.py', 'test_intel_registration.py', 'test_mortar_activity_completion.py', 'test_packed_chat_game.py', 'test_native_mortar_refresh.py', 'test_archive_packed.py', 'test_mortar_refresh.py', 'test_packed_core_game.py', 'test_pak_inventory.py', 'test_native_mortar_salvo.py', 'test_spectator_guidance.py', 'test_native_package.py', 'test_item_station_teardown.py', 'test_source_bundle.py', 'test_native_flight_footprint.py', 'test_native_flight_input_diagnostics.py', 'test_mortar_logistics.py', 'test_mortar_courier_storage.py','test_powered_flight_pad.py','test_prison_escape_policy.py','test_powered_flight_policy.py','test_flight_action_runner.py','test_native_local_admission_probe.py','test_mortar_report_ledger.py','test_dedicated_native.py','test_native_mortar_transfer.py','test_mortar_aim_renewal.py','test_dedicated_preflight.py','test_mortar_transactions.py','test_native_mortar_probe.py','test_diagnostic_no_splash.py','test_casualty_incapacitation.py','test_casualty_remove_probe.py','test_extraction_placement_policy.py','test_guard_cancellation_policy.py','test_casualty_ground_policy.py','test_casualty_reservation_death.py','test_casualty_loading_policy.py','test_initial_guard_policy.py','test_headless_game_runner.py','test_vanilla_game_control.py','test_archive_game_control.py','test_site_discovery_policy.py','test_faction_catalog_scope.py','test_mission_rules.py','test_admin_setting_policy.py', 'test_player_admission_policy.py','test_progression_identity.py','test_discovery_map_position.py','test_local_admission.py','test_prison_placement_policy.py', 'test_recovery_policy.py','test_time_policy.py','test_environment_policy.py','test_environment_restart_runner.py', 'test_inventory_enumeration_policy.py','test_radio_defence_policy.py','test_radio_feedback_policy.py','test_performance_comparison.py','test_release_check.py','test_settings_migration.py','hq_network_regression.py','helicopter_availability_regression.py','mortar_salvo_regression.py']:
|
||||
for name in ['test_prison_alignment_proof.py', 'test_prison_alignment_evidence.py', 'test_aircraft_route_demand.py', 'test_prison_approach_proof.py', 'test_prison_approach_evidence.py', 'test_ordinary_gate_evidence.py', 'test_ordinary_gate_observer.py', 'test_ordinary_look_observer.py', 'test_ordinary_look_evidence.py', 'test_relative_look_runner.py', 'test_radio_waiting_text.py', 'test_inventory_control_runner.py', 'test_inventory_layout.py', 'test_headless_input_proof.py', 'test_headless_input_exercise_runner.py', 'test_private_prefix_cleanup.py', 'test_runner_private_cleanup.py', 'test_ordinary_input_runner.py', 'test_ordinary_input_observer.py', 'test_aircraft_route_envelope.py', 'test_aircraft_route_policy.py', 'test_aircraft_route_probe.py', 'test_rough_intel_feedback.py', 'test_headless_display_probe.py', 'test_headless_display_runner.py', 'test_preparation_status.py', 'test_hint_delivery.py', 'test_native_end_lifecycle.py', 'test_native_end_runner.py', 'test_escape_result_wording.py', 'test_native_stats.py', 'test_aircraft_translation.py', 'test_spawn_retry_budget.py', 'test_action_feedback.py', 'test_native_aircraft_control.py', 'test_aircraft_flight_policy.py', 'test_aircraft_flight_envelope.py', 'test_ordinary_screenshot.py', 'test_participant_selection.py', 'test_native_shared_map_ownership.py', 'test_shared_map_ownership.py', 'test_intel_registration.py', 'test_mortar_activity_completion.py', 'test_packed_chat_game.py', 'test_native_mortar_refresh.py', 'test_archive_packed.py', 'test_mortar_refresh.py', 'test_packed_core_game.py', 'test_pak_inventory.py', 'test_native_mortar_salvo.py', 'test_spectator_guidance.py', 'test_native_package.py', 'test_item_station_teardown.py', 'test_source_bundle.py', 'test_native_flight_footprint.py', 'test_native_flight_input_diagnostics.py', 'test_mortar_logistics.py', 'test_mortar_courier_storage.py','test_powered_flight_pad.py','test_prison_escape_policy.py','test_powered_flight_policy.py','test_flight_action_runner.py','test_native_local_admission_probe.py','test_mortar_report_ledger.py','test_dedicated_native.py','test_native_mortar_transfer.py','test_mortar_aim_renewal.py','test_dedicated_preflight.py','test_mortar_transactions.py','test_native_mortar_probe.py','test_diagnostic_no_splash.py','test_casualty_incapacitation.py','test_casualty_remove_probe.py','test_extraction_placement_policy.py','test_guard_cancellation_policy.py','test_casualty_ground_policy.py','test_casualty_reservation_death.py','test_casualty_loading_policy.py','test_initial_guard_policy.py','test_headless_game_runner.py','test_vanilla_game_control.py','test_archive_game_control.py','test_site_discovery_policy.py','test_faction_catalog_scope.py','test_mission_rules.py','test_admin_setting_policy.py', 'test_player_admission_policy.py','test_progression_identity.py','test_discovery_map_position.py','test_local_admission.py','test_prison_placement_policy.py', 'test_recovery_policy.py','test_time_policy.py','test_environment_policy.py','test_environment_restart_runner.py', 'test_inventory_enumeration_policy.py','test_radio_defence_policy.py','test_radio_feedback_policy.py','test_performance_comparison.py','test_release_check.py','test_settings_migration.py','hq_network_regression.py','helicopter_availability_regression.py','mortar_salvo_regression.py']:
|
||||
subprocess.run([sys.executable,str(ROOT/'tests'/name)],check=True)
|
||||
print('All repository checks passed. Native compilation and multiplayer tests are separate requirements.')
|
||||
if __name__=='__main__':check()
|
||||
|
||||
+38
-2
@@ -635,6 +635,8 @@ def validate_input_observer(a):
|
||||
|
||||
|
||||
def validate_input_exercise(a):
|
||||
if getattr(a, 'exercise_prison_alignment', False) and not getattr(a, 'exercise_prison_approach', False):
|
||||
refuse('Prison lock alignment requires the explicit prison approach experiment')
|
||||
if getattr(a, 'approach_wide_display', False) and not getattr(a, 'exercise_prison_approach', False):
|
||||
refuse('Wide private viewport requires the explicit prison approach experiment')
|
||||
if getattr(a, 'exercise_prison_approach', False) and not (getattr(a, 'exercise_relative_look', False) and getattr(a, 'observe_prison_gate', False)):
|
||||
@@ -848,7 +850,17 @@ def approach_module_hashes(root):
|
||||
return hashes
|
||||
|
||||
|
||||
def input_exercise_command(command, root, compat, profile, executable, run_id, host_display, display_context, look=False, approach=False):
|
||||
def alignment_module_hashes(root):
|
||||
hashes = {}
|
||||
for name in ('prison_alignment_proof.py', 'prison_alignment_evidence.py'):
|
||||
path = root/'tools'/name
|
||||
if path.is_symlink() or not path.is_file():
|
||||
refuse('Alignment requires regular local proof modules')
|
||||
hashes[name] = hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
return hashes
|
||||
|
||||
|
||||
def input_exercise_command(command, root, compat, profile, executable, run_id, host_display, display_context, look=False, approach=False, alignment=False):
|
||||
"""Put the input supervisor around the display inspector, inside Gamescope."""
|
||||
separator = command.index('--')
|
||||
if command[:separator].count('--backend') != 1 or command[command.index('--backend')+1] != 'headless':
|
||||
@@ -859,6 +871,8 @@ def input_exercise_command(command, root, compat, profile, executable, run_id, h
|
||||
if (not display_context or len(child) < 3 or child[:2] != [sys.executable, str(ownership)]
|
||||
or not re.fullmatch(r'[A-Za-z0-9]{1,32}', run_id)):
|
||||
refuse('Input exercise requires the existing scoped display wrapper and observer nonce')
|
||||
if type(alignment) is not bool or (alignment and not approach):
|
||||
refuse('Prison alignment requires explicit prior approach')
|
||||
if type(approach) is not bool or (approach and not look):
|
||||
refuse('Prison approach requires explicit relative look')
|
||||
if approach:
|
||||
@@ -890,6 +904,10 @@ def input_exercise_command(command, root, compat, profile, executable, run_id, h
|
||||
context['approach_requested'] = True
|
||||
context['approach_module_sha256'] = approach_module_hashes(root)
|
||||
wrapper.insert(-1, '--enable-private-approach-proof')
|
||||
if alignment:
|
||||
context['alignment_requested'] = True
|
||||
context['alignment_module_sha256'] = alignment_module_hashes(root)
|
||||
wrapper.insert(-1, '--enable-private-alignment-proof')
|
||||
return command[:separator+1] + wrapper + child, context
|
||||
|
||||
|
||||
@@ -911,6 +929,10 @@ def input_exercise_poll_failure(context):
|
||||
for key in ('helper_sha256', 'ownership_helper_sha256', 'prefix', 'profile', 'game_executable', 'report', 'command', 'nonce'):
|
||||
if report.get(key) != context[key]:
|
||||
return False
|
||||
if context.get('alignment_requested') and report.get('alignment_module_sha256') != context.get('alignment_module_sha256'):
|
||||
return False
|
||||
if report.get('alignment_requested', False) != context.get('alignment_requested', False):
|
||||
return False
|
||||
if context.get('approach_requested') and report.get('approach_module_sha256') != context.get('approach_module_sha256'):
|
||||
return None
|
||||
if report.get('approach_requested', False) != context.get('approach_requested', False):
|
||||
@@ -958,6 +980,13 @@ def input_exercise_result(result, context, session, observation, display_result)
|
||||
for key in ('helper_sha256', 'ownership_helper_sha256', 'prefix', 'profile', 'game_executable', 'report', 'command', 'nonce'):
|
||||
if report.get(key) != context[key]:
|
||||
refuse('Input report binding mismatch: '+key)
|
||||
alignment_requested = context.get('alignment_requested', False)
|
||||
if type(report.get('alignment_requested', False)) is not bool or report.get('alignment_requested', False) != alignment_requested:
|
||||
refuse('Prison alignment request does not match launch context')
|
||||
if alignment_requested and (not context.get('approach_requested') or report.get('alignment_module_sha256') != context.get('alignment_module_sha256') or context.get('alignment_module_sha256') != alignment_module_hashes(ROOT)):
|
||||
refuse('Alignment requires prior approach and unchanged module sources')
|
||||
if not alignment_requested and report.get('alignment_proof') is not None:
|
||||
refuse('Unexpected alignment evidence')
|
||||
approach_requested = context.get('approach_requested', False)
|
||||
if type(report.get('approach_requested', False)) is not bool or report.get('approach_requested', False) != approach_requested:
|
||||
refuse('Prison approach request does not match launch context')
|
||||
@@ -1017,6 +1046,12 @@ def input_exercise_result(result, context, session, observation, display_result)
|
||||
proof['scope'] = 'private relative mouse and W input with independently observed interior prison approach; no held action, gate exit, full escape or multiplayer qualification'
|
||||
if proof['approach_proof'].get('verified') is not True:
|
||||
refuse('Independent native prison approach evidence is incomplete')
|
||||
if alignment_requested:
|
||||
from prison_alignment_evidence import validate_alignment_evidence
|
||||
proof['alignment_proof'] = validate_alignment_evidence((session/'console.log').read_text(errors='replace'), context['nonce'], report.get('look_proof'), route, report.get('alignment_proof'))
|
||||
proof['scope'] = 'private approach and stationary yaw alignment with independently observed native prison lock selection; no F hold, unlock, exit or multiplayer qualification'
|
||||
if proof['alignment_proof'].get('verified') is not True:
|
||||
refuse('Independent native prison lock alignment evidence is incomplete')
|
||||
elif look_requested:
|
||||
from ordinary_look_evidence import validate_look_evidence
|
||||
proof['look_proof'] = validate_look_evidence((session/'console.log').read_text(errors='replace'), context['nonce'], report.get('look_proof'), events)
|
||||
@@ -1098,7 +1133,7 @@ def run_locked(a, steam, compat, profile, packed=None):
|
||||
command, display_context = display_probe_command(command, ROOT, compat, profile, executable, run_id, os.environ.get('DISPLAY', ''))
|
||||
exercise_context = None
|
||||
if getattr(a, 'exercise_player_input', False):
|
||||
command, exercise_context = input_exercise_command(command, ROOT, compat, profile, executable, run_id, os.environ.get('DISPLAY', ''), display_context, getattr(a, 'exercise_relative_look', False), getattr(a, 'exercise_prison_approach', False))
|
||||
command, exercise_context = input_exercise_command(command, ROOT, compat, profile, executable, run_id, os.environ.get('DISPLAY', ''), display_context, getattr(a, 'exercise_relative_look', False), getattr(a, 'exercise_prison_approach', False), getattr(a, 'exercise_prison_alignment', False))
|
||||
silent_sink = 'erri_game_'+run_id
|
||||
application_id = 'org.escapistsrereintroduced.game.'+run_id
|
||||
env = os.environ.copy()
|
||||
@@ -1263,6 +1298,7 @@ def main():
|
||||
parser.add_argument('--capture-prison-view', action='store_true', help='Opt-in single genuine local prison screenshot; loose successor source only, no camera or input changes')
|
||||
parser.add_argument('--observe-player-input', action='store_true', help='Read-only native local-input sampler; loose Core/Chat only, requires --settle >=50; sends no input')
|
||||
parser.add_argument('--approach-wide-display', action='store_true', help='Use a disposable 2560x720 headless viewport for the explicit approach experiment; no physical display changes')
|
||||
parser.add_argument('--exercise-prison-alignment', action='store_true', help='Explicit stationary yaw-only alignment to native prison lock after verified approach; no F or gate operation')
|
||||
parser.add_argument('--exercise-prison-approach', action='store_true', help='Explicit bounded relative-mouse/W approach inside prison; requires relative look and gate observer; never operates gate')
|
||||
parser.add_argument('--observe-prison-gate', action='store_true', help='Read-only native registered prison lock/context/progress telemetry; requires --observe-player-input')
|
||||
parser.add_argument('--exercise-relative-look', action='store_true', help='Explicit tiny relative right/return mouse experiment before W/Tab; requires --exercise-player-input and native view proof')
|
||||
|
||||
+133
-21
@@ -117,9 +117,10 @@ def yaw_delta(first, second):
|
||||
|
||||
|
||||
class RelativeMouse:
|
||||
def __init__(self, query, keyboard, owner, enable_approach=False):
|
||||
def __init__(self, query, keyboard, owner, enable_approach=False, enable_alignment=False):
|
||||
self.query, self.keyboard, self.owner = query, keyboard, owner
|
||||
self.enable_approach = enable_approach
|
||||
self.enable_alignment = enable_alignment
|
||||
self.xt = keyboard.xt
|
||||
self.xt.XTestFakeRelativeMotionEvent.restype = C.c_int
|
||||
self.xt.XTestFakeRelativeMotionEvent.argtypes = [C.c_void_p, C.c_int, C.c_int, C.c_ulong]
|
||||
@@ -163,6 +164,16 @@ class RelativeMouse:
|
||||
within_lease()
|
||||
self._move(dx, dy, guard, before_send=within_lease)
|
||||
|
||||
def alignment_move(self, dx, dy, guard, *, deadline, clock=time.monotonic):
|
||||
if (self.enable_alignment is not True or self.enable_approach is not True or type(dx) is not int
|
||||
or not 1 <= abs(dx) <= 128 or type(dy) is not int or dy != 0):
|
||||
raise Refused('Unsupported or unenabled alignment relative step')
|
||||
def within_lease():
|
||||
if clock() >= deadline:
|
||||
raise Refused('Original alignment deadline exceeded before relative input')
|
||||
within_lease()
|
||||
self._move(dx, dy, guard, before_send=within_lease)
|
||||
|
||||
def _move(self, dx, dy, guard, before_send=None):
|
||||
target = guard.check()
|
||||
self.keyboard.require_idle()
|
||||
@@ -293,7 +304,7 @@ class Observer:
|
||||
self.enable_look = enable_look
|
||||
self.enable_approach = enable_approach
|
||||
self.approach_lines = []; self.gate_records = []; self.gate_received = {}
|
||||
self.gate_complete_end = 0; self.approach_previous = None
|
||||
self.gate_complete_end = 0; self.approach_previous = None; self.alignment_previous = None
|
||||
self.ready_at = self.ready_real = self.queued_real = None
|
||||
self.look_records = []; self.input_samples = []; self.ready_sample = None
|
||||
|
||||
@@ -414,21 +425,29 @@ class Observer:
|
||||
return content, data
|
||||
|
||||
def approach_snapshots(self):
|
||||
return self._snapshot_batch('approach_previous')
|
||||
|
||||
def alignment_snapshots(self):
|
||||
return self._snapshot_batch('alignment_previous')
|
||||
|
||||
def _snapshot_batch(self, cursor):
|
||||
_, data = self.approach_data()
|
||||
snapshots = data['snapshots']; latest = max(snapshots)
|
||||
first = latest if self.approach_previous is None else min(self.approach_previous + 1, latest)
|
||||
if self.approach_previous is not None and latest < self.approach_previous:
|
||||
previous = getattr(self, cursor)
|
||||
first = latest if previous is None else min(previous + 1, latest)
|
||||
if previous is not None and latest < previous:
|
||||
raise Refused('Native approach snapshot index reversed')
|
||||
result = []
|
||||
for index in range(first, latest + 1):
|
||||
item = snapshots[index]; geo, action, native, pose = item['geometry'], item['action'], item['input'], item['look']
|
||||
result.append({'sample': index, 'real_ms': item['real_ms'], 'received_monotonic': self.gate_received[index],
|
||||
'origin': native['origin'], 'direction': pose['direction'], 'body_forward': pose['body_forward'],
|
||||
'origin': native['origin'], 'direction': pose['direction'], 'body_forward': pose['body_forward'], 'camera': pose['camera'],
|
||||
**{key: action[key] for key in ('selected', 'context_selected', 'cached_performable', 'progress_bound', 'pressed', 'progress', 'performing', 'duration')},
|
||||
'input': native['input'], 'forward': native['forward'], 'velocity': native['velocity'],
|
||||
'inventory': native['inventory'], 'phase': action['phase'], 'door_state': geo['door_state'], 'door_control': geo['door_control'],
|
||||
'geometry': {'nonce': self.nonce, 'basis': 'registered-prison-lock-native-interaction',
|
||||
**{key: geo[key] for key in ('prison', 'gate', 'gate_forward', 'context', 'radius')}}})
|
||||
self.approach_previous = latest
|
||||
setattr(self, cursor, latest)
|
||||
return result
|
||||
|
||||
|
||||
@@ -524,9 +543,12 @@ def confirm_release(guard, keyboard, report, key, clock=time.monotonic, sleep=ti
|
||||
observation['end'] = clock()
|
||||
|
||||
|
||||
def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time.sleep, mouse=None, approach=False):
|
||||
def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time.sleep, mouse=None, approach=False, alignment=False):
|
||||
deadline = clock() + 70
|
||||
held = None
|
||||
approach_completed = False
|
||||
if alignment and (not approach or mouse is None):
|
||||
raise Refused("Alignment requires approach and look capabilities")
|
||||
report.update(events=[], paired_release_errors=[], sequence_completed=False)
|
||||
|
||||
def check(native=True):
|
||||
@@ -596,6 +618,10 @@ def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time
|
||||
if clock() >= deadline:
|
||||
raise Refused("Original approach deadline exceeded")
|
||||
keyboard.require_idle()
|
||||
approach_completed = True
|
||||
if alignment:
|
||||
notify({'kind': 'approach-finished', 'successful': True})
|
||||
run_alignment(guard, keyboard, observer, mouse, report, clock, sleep)
|
||||
report["sequence_completed"] = True
|
||||
return
|
||||
pulse('W', 0.30)
|
||||
@@ -630,7 +656,7 @@ def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time
|
||||
route = report.get('approach_proof', {})
|
||||
report['paired_release_errors'].extend(route.get('paired_release_errors', []))
|
||||
report['unconfirmed_held_key'] = route.get('unconfirmed_held_key')
|
||||
notify({'kind': 'approach-finished', 'successful': report['sequence_completed']})
|
||||
notify({'kind': 'approach-finished', 'successful': approach_completed if alignment else report['sequence_completed']})
|
||||
report['all_attempted_presses_released'] = not report.get('unconfirmed_held_key') and not report['paired_release_errors']
|
||||
|
||||
|
||||
@@ -691,10 +717,71 @@ def run_approach(guard, keyboard, observer, mouse, report, clock, sleep):
|
||||
raise Refused('Approach module did not complete')
|
||||
|
||||
|
||||
class AlignmentMouse:
|
||||
"""Explicit yaw-only capability. Caller cannot extend its original lease."""
|
||||
def __init__(self, mouse, report, clock, deadline):
|
||||
self.mouse, self.report, self.clock, self.deadline = mouse, report, clock, deadline
|
||||
def move(self, dx, dy, guard, *, deadline):
|
||||
if type(deadline) not in (int, float) or not math.isfinite(deadline) or deadline > self.deadline:
|
||||
raise Refused('Alignment module attempted to extend original deadline')
|
||||
self.report['input_attempted'] = True
|
||||
self.mouse.alignment_move(dx, dy, guard, deadline=deadline, clock=self.clock)
|
||||
self.report['input_sent'] = True
|
||||
|
||||
|
||||
def run_alignment(guard, keyboard, observer, mouse, report, clock, sleep):
|
||||
from prison_alignment_proof import perform_alignment
|
||||
from prison_approach_evidence import validate_approach_evidence
|
||||
if observer.ready_at is None or observer.ready_real is None or observer.queued_real is None:
|
||||
raise Refused('Original native alignment clocks missing')
|
||||
original_end = observer.ready_at + (45000 - (observer.ready_real - observer.queued_real))/1000 - .5
|
||||
started = clock()
|
||||
deadline = min(started + 8, original_end)
|
||||
if deadline <= started:
|
||||
raise Refused('Original observation lifetime unavailable for alignment')
|
||||
notify({'kind': 'alignment-pending', 'deadline_monotonic': deadline})
|
||||
successful = False
|
||||
try:
|
||||
content, data = observer.approach_data()
|
||||
route = report.get('approach_proof')
|
||||
evidence = validate_approach_evidence(content, observer.nonce, report['look_proof'], route)
|
||||
if not evidence['verified']:
|
||||
raise Refused('Native approach prerequisite refused: ' + '; '.join(evidence['errors']))
|
||||
if clock() >= deadline:
|
||||
raise Refused('Original alignment deadline exceeded during prerequisite validation')
|
||||
poses = {r['sample']: r for r in observer.look_records if r['kind'] == 'sample'}
|
||||
look = report['look_proof']
|
||||
calibration = {'nonce': observer.nonce, 'completed': True, 'pixels': 24,
|
||||
'baseline_direction': poses[look['baseline_samples'][-1]]['direction'],
|
||||
'response_direction': poses[look['response_sample']]['direction'],
|
||||
'ready_real_ms': observer.ready_real, 'ready_monotonic': observer.ready_at,
|
||||
'observer_start_real_ms': observer.queued_real, 'observer_deadline_monotonic': original_end,
|
||||
'alignment_deadline_monotonic': deadline}
|
||||
observer.alignment_previous = evidence['last_sample'] - 1
|
||||
def alignment_notify(event):
|
||||
if event.get('kind') != 'alignment-finished':
|
||||
notify(event)
|
||||
# Native ownership checks remain live, but no keyboard-sending capability
|
||||
# is handed to this stage. It may only assert that all keys are idle.
|
||||
perform_alignment(guard, keyboard.require_idle, AlignmentMouse(mouse, report, clock, deadline),
|
||||
observer.alignment_snapshots, route['geometry'], calibration, evidence, report,
|
||||
clock=clock, sleep=sleep, notify=alignment_notify)
|
||||
if not report.get('alignment_proof', {}).get('completed'):
|
||||
raise Refused('Alignment module did not complete')
|
||||
guard.check(); keyboard.require_idle()
|
||||
observer.approach_data()
|
||||
if clock() >= deadline:
|
||||
raise Refused('Original alignment deadline exceeded at final observation')
|
||||
successful = True
|
||||
finally:
|
||||
notify({'kind': 'alignment-finished', 'successful': successful})
|
||||
|
||||
|
||||
def arguments(argv=None):
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--enable-private-keyboard-proof', action='store_true', required=True)
|
||||
p.add_argument('--enable-private-look-proof', action='store_true')
|
||||
p.add_argument('--enable-private-alignment-proof', action='store_true', help='After successful approach, attempt stationary native lock selection using horizontal relative mouse only')
|
||||
p.add_argument('--enable-private-approach-proof', action='store_true', help='After look calibration, replace W/Tab cycle with bounded interior approach only')
|
||||
for name in ('prefix', 'game-executable', 'profile', 'report'):
|
||||
p.add_argument('--' + name, type=Path, required=True)
|
||||
@@ -703,6 +790,8 @@ def arguments(argv=None):
|
||||
p.add_argument('--nonce', required=True)
|
||||
p.add_argument('command', nargs=argparse.REMAINDER)
|
||||
args = p.parse_args(argv)
|
||||
if args.enable_private_alignment_proof and not (args.enable_private_approach_proof and args.enable_private_look_proof):
|
||||
p.error("Alignment proof requires explicit approach and look proof")
|
||||
if args.enable_private_approach_proof and not args.enable_private_look_proof:
|
||||
p.error("Approach proof requires explicit private look proof")
|
||||
return args
|
||||
@@ -761,9 +850,16 @@ APPROACH_MODULES = ('prison_approach_proof.py', 'ordinary_gate_evidence.py',
|
||||
'ordinary_look_evidence.py', 'prison_approach_evidence.py')
|
||||
|
||||
|
||||
ALIGNMENT_MODULES = ('prison_alignment_proof.py', 'prison_alignment_evidence.py')
|
||||
|
||||
|
||||
def approach_module_hashes():
|
||||
return module_hashes(APPROACH_MODULES)
|
||||
|
||||
|
||||
def module_hashes(names):
|
||||
folder = Path(__file__).resolve().parent
|
||||
return {name: hashlib.sha256((folder / name).read_bytes()).hexdigest() for name in APPROACH_MODULES}
|
||||
return {name: hashlib.sha256((folder / name).read_bytes()).hexdigest() for name in names}
|
||||
|
||||
|
||||
def report_for_write(report):
|
||||
@@ -771,14 +867,18 @@ def report_for_write(report):
|
||||
if report.get('approach_requested') is not True:
|
||||
return report
|
||||
result = dict(report)
|
||||
route = report.get('approach_proof')
|
||||
if isinstance(route, dict):
|
||||
result['approach_proof'] = dict(route)
|
||||
samples = route.get('samples')
|
||||
if isinstance(samples, list):
|
||||
result['approach_proof']['samples'] = [
|
||||
{key: sample.get(key) for key in ('sample', 'real_ms', 'received_monotonic')}
|
||||
if isinstance(sample, dict) else {'invalid_reference': True} for sample in samples]
|
||||
names = ['approach_proof']
|
||||
if report.get('alignment_requested') is True:
|
||||
names.append('alignment_proof')
|
||||
for name in names:
|
||||
route = report.get(name)
|
||||
if isinstance(route, dict):
|
||||
result[name] = dict(route)
|
||||
samples = route.get('samples')
|
||||
if isinstance(samples, list):
|
||||
result[name]['samples'] = [
|
||||
{key: sample.get(key) for key in ('sample', 'real_ms', 'received_monotonic')}
|
||||
if isinstance(sample, dict) else {'invalid_reference': True} for sample in samples]
|
||||
return result
|
||||
|
||||
|
||||
@@ -795,6 +895,7 @@ def main(argv=None):
|
||||
report = {'schema': 1, 'kind': 'headless-private-keyboard-experiment', 'input_sent': False,
|
||||
'look_requested': args.enable_private_look_proof,
|
||||
'approach_requested': getattr(args, 'enable_private_approach_proof', False),
|
||||
'alignment_requested': getattr(args, 'enable_private_alignment_proof', False),
|
||||
'sequence_completed': False, 'release_qualified': False, 'gameplay_qualified': False,
|
||||
'hard_supervision': NOTIFY is not None, 'nonce': args.nonce, 'binding': bound, 'command': command, 'report': str(args.report), 'input_attempted': False,
|
||||
'helper_sha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
|
||||
@@ -802,6 +903,8 @@ def main(argv=None):
|
||||
'prefix': str(args.prefix), 'profile': str(args.profile), 'game_executable': str(args.game_executable)}
|
||||
if report['approach_requested']:
|
||||
report['approach_module_sha256'] = approach_module_hashes()
|
||||
if report['alignment_requested']:
|
||||
report['alignment_module_sha256'] = module_hashes(ALIGNMENT_MODULES)
|
||||
fd = os.open(args.report, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
|
||||
info = os.fstat(fd)
|
||||
notify({'kind': 'report', 'path': str(args.report), 'device': info.st_dev, 'inode': info.st_ino,
|
||||
@@ -866,8 +969,8 @@ def main(argv=None):
|
||||
guard.check()
|
||||
keyboard = Keyboard(query)
|
||||
keyboard.require_idle()
|
||||
mouse = RelativeMouse(query, keyboard, owner, enable_approach=getattr(args, "enable_private_approach_proof", False)) if args.enable_private_look_proof else None
|
||||
sequence(guard, keyboard, observer, report, mouse=mouse, approach=getattr(args, "enable_private_approach_proof", False))
|
||||
mouse = RelativeMouse(query, keyboard, owner, enable_approach=getattr(args, "enable_private_approach_proof", False), enable_alignment=getattr(args, "enable_private_alignment_proof", False)) if args.enable_private_look_proof else None
|
||||
sequence(guard, keyboard, observer, report, mouse=mouse, approach=getattr(args, "enable_private_approach_proof", False), alignment=getattr(args, "enable_private_alignment_proof", False))
|
||||
except (OSError, ValueError, UnicodeError, subprocess.SubprocessError) as error:
|
||||
report['error'] = str(error)
|
||||
finally:
|
||||
@@ -972,6 +1075,7 @@ def supervise(argv=None):
|
||||
os.kill(child.pid, signum)
|
||||
for signum in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP):
|
||||
previous[signum] = signal.signal(signum, forward)
|
||||
alignment_deadline = None; alignment_finished = False
|
||||
approach_deadline = None; approach_finished = False
|
||||
look_at = None; held_at = None; held_key = None; owned_child = None; report_record = None; proof_finished = False; expired = False
|
||||
deadline = time.monotonic() + 190
|
||||
@@ -983,7 +1087,15 @@ def supervise(argv=None):
|
||||
event = parent.recv()
|
||||
except EOFError:
|
||||
event = None
|
||||
if event and event['kind'] == 'approach-pending':
|
||||
if event and event['kind'] == 'alignment-pending':
|
||||
proposed = event.get('deadline_monotonic')
|
||||
current = time.monotonic()
|
||||
if type(proposed) not in (int, float) or not math.isfinite(proposed) or proposed > current + 8:
|
||||
proposed = current
|
||||
alignment_deadline = proposed if alignment_deadline is None else min(alignment_deadline, proposed)
|
||||
elif event and event['kind'] == 'alignment-finished':
|
||||
alignment_finished = bool(event.get('successful'))
|
||||
elif event and event['kind'] == 'approach-pending':
|
||||
proposed = event.get('deadline_monotonic')
|
||||
current = time.monotonic()
|
||||
if type(proposed) not in (int, float) or not math.isfinite(proposed) or proposed > current + 35:
|
||||
@@ -1006,7 +1118,7 @@ def supervise(argv=None):
|
||||
elif event and event['kind'] == 'proof-finished':
|
||||
proof_finished = event['successful']
|
||||
now = time.monotonic()
|
||||
if received or now >= deadline or (approach_deadline is not None and not approach_finished and now >= approach_deadline) or (held_at is not None and now - held_at > 1.5) or (look_at is not None and now - look_at > 4):
|
||||
if received or now >= deadline or (alignment_deadline is not None and not alignment_finished and now >= alignment_deadline) or (approach_deadline is not None and not approach_finished and now >= approach_deadline) or (held_at is not None and now - held_at > 1.5) or (look_at is not None and now - look_at > 4):
|
||||
expired = True
|
||||
child.terminate() # asks worker finally to release if still provable
|
||||
child.join(1)
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Independent yaw alignment and exact native lock-selection verification."""
|
||||
import math
|
||||
import re
|
||||
from ordinary_look_evidence import validate_look_evidence
|
||||
from ordinary_gate_evidence import join_gate_snapshots, validate_gate_geometry
|
||||
from prison_approach_evidence import validate_approach_evidence, need, number, horizontal, norm, yaw, angle, neutral
|
||||
|
||||
|
||||
def validate_alignment_evidence(content, nonce, look_proof, approach_proof, proof):
|
||||
result={'verified':False,'errors':[],'scope':'stationary yaw-only alignment and native selection of the original prison lock after a verified interior approach; no F hold, unlock, exit or multiplayer qualification','held_action_verified':False,'escape_verified':False}
|
||||
try:
|
||||
approach=validate_approach_evidence(content,nonce,look_proof,approach_proof)
|
||||
need(approach['verified'],'prior native approach is unverified: '+str(approach['errors']))
|
||||
calibration=validate_look_evidence(content,nonce,look_proof)
|
||||
need(calibration['verified'],'same-run native calibration is unverified: '+str(calibration['errors']))
|
||||
gain=calibration['response_yaw_degrees']/24
|
||||
need(.01<=abs(gain)<=.5,'native calibration gain outside alignment range')
|
||||
need(type(proof) is dict and not proof.get('events') and proof.get('requested') is True and proof.get('completed') is True and proof.get('nonce')==nonce and not proof.get('error'),'alignment did not complete under this nonce')
|
||||
start=number(proof['started_monotonic']);finish=number(proof['finished_monotonic']);deadline=number(proof['deadline_monotonic'])
|
||||
need(approach_proof['finished_monotonic']<=start<finish<=deadline<=start+8,'alignment helper interval outside original stage bound')
|
||||
data=join_gate_snapshots(content,nonce);samples=data['snapshots'];first=proof['initial_sample'];last=proof['final_sample'];route_end=approach['last_sample']
|
||||
need(type(first) is int and type(last) is int and route_end<=first<last and first in samples and last in samples,'native alignment references invalid')
|
||||
geo=validate_gate_geometry(content,nonce,route_end,last)
|
||||
need(geo['verified'],'native alignment geometry invalid: '+str(geo['errors']))
|
||||
queued=re.findall(r'ERR INPUT OBSERVE: run='+re.escape(nonce)+r' sample=0 real=(\d+) world=[^ ]+ QUEUED(?:\r?$)',content,re.MULTILINE)
|
||||
need(len(queued)==1,'original native QUEUED clock missing or ambiguous')
|
||||
queued=int(queued[0]);ready=data['ready']['real_ms']
|
||||
original_host_end=number(approach_proof['deadline_monotonic'])-35+(45000-(ready-queued))/1000-.5
|
||||
need(deadline<=original_host_end,'alignment deadline renews original observation lifetime')
|
||||
reference=samples[route_end]['input']['origin'];last_real=None
|
||||
for index in range(route_end,last+1):
|
||||
need(index in samples,'missing native interstage/alignment observation')
|
||||
s=samples[index];g=s['geometry'];a=s['action']
|
||||
need(neutral(s) and math.dist(reference,s['input']['origin'])<=.05,'body moved during stationary alignment')
|
||||
need(queued<=s['real_ms']<queued+45000,'original native observation lifetime exceeded')
|
||||
need(g['door_state']==0 and g['door_control']==0 and a['pressed']==0 and not a['performing'] and a['progress']==0,'gate operated during alignment-only observation')
|
||||
if last_real is not None:need(0<s['real_ms']-last_real<=500,'native alignment sample gap')
|
||||
last_real=s['real_ms']
|
||||
refs=proof['samples'];need(type(refs) is list and 2<=len(refs)<=450,'bounded alignment references missing')
|
||||
ids=[r['sample'] for r in refs];need(all(type(i) is int for i in ids) and ids==list(range(first,last+1)),'alignment references not contiguous')
|
||||
receipts={};prior=0
|
||||
for r in refs:
|
||||
i=r['sample'];received=number(r['received_monotonic'])
|
||||
need(type(r['real_ms']) is int and r['real_ms']==samples[i]['real_ms'] and prior<=received<=finish,'alignment native/receipt clocks disagree')
|
||||
receipts[i]=received;prior=received
|
||||
need(0<=finish-receipts[last]<=.5,'final native alignment observation is stale')
|
||||
# Historical samples bridge the verified approach into the stage; they
|
||||
# cannot be made newly received or consume a fictitious renewed lease.
|
||||
current=[i for i in ids if receipts[i]>=start]
|
||||
native_begin=current[0] if current else first
|
||||
need(samples[last]['real_ms']-samples[native_begin]['real_ms']<=8000,'current native alignment interval exceeds eight seconds')
|
||||
def heading(s):
|
||||
eye=s['look']['camera'];handle=s['geometry']['context']
|
||||
need(.25<=horizontal(eye,handle)<=5,'native handle bearing is degenerate or out of scope')
|
||||
return math.degrees(math.atan2(handle[0]-eye[0],handle[2]-eye[2]))
|
||||
motions=proof['motions'];need(type(motions) is list and len(motions)<=6,'too many alignment motions')
|
||||
old_index=first;old_time=start;pixel_sum=0
|
||||
for motion in motions:
|
||||
dx=motion['dx'];at=number(motion['monotonic']);before=motion['after_sample'];after=motion['response_sample']
|
||||
need(type(dx) is int and 1<=abs(dx)<=128 and type(motion['dy']) is int and motion['dy']==0,'alignment motion is not bounded horizontal input')
|
||||
need(type(before) is int and type(after) is int and old_index<=before<after<=last,'alignment motion references reversed')
|
||||
need(old_time<=at<=finish and before in receipts and after in receipts and receipts[before]<=at<=receipts[after] and at-receipts[before]<=.5 and receipts[after]-at<=1.5,'alignment input timestamps contradict native samples')
|
||||
baseline=yaw(samples[before]['look']['direction']);wanted=angle(heading(samples[before]),baseline)
|
||||
change=angle(yaw(samples[after]['look']['direction']),baseline)
|
||||
need(abs(wanted)>3 and dx*gain*wanted>0 and .05<=abs(change)<=15 and change*dx*gain>0,'native turn did not follow same-run signed calibration toward handle')
|
||||
for i in range(before,after+1):
|
||||
d=angle(yaw(samples[i]['look']['direction']),baseline)
|
||||
need(abs(d)<=15 and (abs(d)<.05 or d*dx*gain>0),'intermediate native turn reversed or exceeded bound')
|
||||
need(after-1>=before and 100<=samples[after]['real_ms']-samples[after-1]['real_ms']<=500 and abs(angle(yaw(samples[after]['look']['direction']),yaw(samples[after-1]['look']['direction'])))<=.2,'native turn response did not settle over consecutive samples')
|
||||
need(samples[after]['real_ms']-samples[before]['real_ms']<=2500,'native turn response interval is unbounded')
|
||||
old_index=after;old_time=at;pixel_sum+=abs(dx)
|
||||
need(pixel_sum<=768,'alignment pixel budget exceeded')
|
||||
pair=proof['selection_samples'];need(type(pair) is list and len(pair)==2 and all(type(i) is int for i in pair) and first<=pair[0] and pair[1]==pair[0]+1 and pair[1]<=last,'selection lacks consecutive references')
|
||||
one,two=(samples[i] for i in pair)
|
||||
need(100<=two['real_ms']-one['real_ms']<=500 and abs(angle(yaw(two['look']['direction']),yaw(one['look']['direction'])))<=.2,'selection view did not settle')
|
||||
for i in range(pair[0],last+1):
|
||||
s=samples[i];a=s['action']
|
||||
need(a['selected'] and a['context_selected'] and a['cached_performable'] and a['progress_bound'] and a['duration']==4,'original native lock/context not selected and bound')
|
||||
need(abs(angle(heading(s),yaw(s['look']['direction'])))<=3,'view not aligned to actual handle bearing')
|
||||
result.update(verified=True,first_sample=first,last_sample=last,selection_samples=pair,turn_count=len(motions),native_duration_ms=samples[last]['real_ms']-samples[first]['real_ms'],final_yaw_error_degrees=abs(angle(heading(samples[last]),yaw(samples[last]['look']['direction']))))
|
||||
except (ValueError,KeyError,TypeError,OverflowError,IndexError) as error:
|
||||
result['errors'].append(str(error))
|
||||
return result
|
||||
@@ -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