Prepare independent successor playtest with spawn recovery and casualty feedback
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_aircraft_field_probe.py', 'test_aircraft_field_runner.py', 'test_aircraft_field_rules.py', 'test_aircraft_field_envelope.py', 'test_aircraft_field_flight.py', 'test_command_maintenance.py', 'test_aircraft_service.py', 'test_aircraft_service_cancel_runner.py', 'test_prison_action_selection_proof.py', 'test_prison_action_selection_evidence.py', 'test_ordinary_gate_choices.py', 'test_aircraft_control_bridge.py', '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']:
|
||||
for name in ['test_casualty_feedback.py', 'test_initial_admission_exhaustion.py', 'test_prison_held_action_proof.py', 'test_prison_held_action_evidence.py', 'test_prison_held_input_adapter.py', 'test_prison_held_runner.py', 'test_prison_coarse_facing.py', 'test_aircraft_field_probe.py', 'test_aircraft_field_runner.py', 'test_aircraft_field_rules.py', 'test_aircraft_field_envelope.py', 'test_aircraft_field_flight.py', 'test_command_maintenance.py', 'test_aircraft_service.py', 'test_aircraft_service_cancel_runner.py', 'test_prison_action_selection_proof.py', 'test_prison_action_selection_evidence.py', 'test_ordinary_gate_choices.py', 'test_aircraft_control_bridge.py', '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()
|
||||
|
||||
+88
-6
@@ -637,6 +637,10 @@ def validate_input_observer(a):
|
||||
|
||||
|
||||
def validate_input_exercise(a):
|
||||
if type(getattr(a, 'exercise_prison_held_action', False)) is not bool or (getattr(a, 'exercise_prison_held_action', False) and not (getattr(a, 'exercise_prison_selection', False) and getattr(a, 'exercise_prison_coarse_facing', False))):
|
||||
refuse('Prison held action requires explicit selection and coarse facing')
|
||||
if type(getattr(a, 'exercise_prison_coarse_facing', False)) is not bool or (getattr(a, 'exercise_prison_coarse_facing', False) and not (getattr(a, 'exercise_prison_coarse_approach', False) and getattr(a, 'exercise_prison_selection', False))):
|
||||
refuse('Coarse facing requires explicit coarse approach and selection')
|
||||
if getattr(a, 'exercise_prison_selection', False) and not (getattr(a, 'exercise_prison_alignment', False) and getattr(a, 'observe_prison_choices', False)):
|
||||
refuse('Prison action selection requires explicit alignment and native choices')
|
||||
if getattr(a, 'observe_prison_choices', False) and getattr(a, 'exercise_player_input', False) and not getattr(a, 'exercise_prison_approach', False):
|
||||
@@ -662,7 +666,7 @@ def validate_input_exercise(a):
|
||||
refuse('Input exercise requires --settle >=50')
|
||||
|
||||
|
||||
def input_observer_command(command, project, packed, nonce, look=False, gate=False, choices=False):
|
||||
def input_observer_command(command, project, packed, nonce, look=False, gate=False, choices=False, held_action=False):
|
||||
if project not in {'core', 'chat'} or packed is not None:
|
||||
refuse('Input observation requires loose Core or Chat')
|
||||
if not re.fullmatch(r'[A-Za-z0-9]{1,32}', nonce):
|
||||
@@ -673,7 +677,9 @@ def input_observer_command(command, project, packed, nonce, look=False, gate=Fal
|
||||
refuse('Prison choices require explicit boolean gate observation')
|
||||
if type(gate) is not bool:
|
||||
refuse('Prison gate observation selection must be boolean')
|
||||
if any(value.startswith(('-errOrdinaryInput', '-errOrdinaryLook', '-errOrdinaryGate')) for value in command):
|
||||
if type(held_action) is not bool or (held_action and not (look and gate and choices)):
|
||||
refuse('Held action observation requires explicit look, gate and choices')
|
||||
if any(value.startswith(('-errOrdinaryInput', '-errOrdinaryLook', '-errOrdinaryGate', '-errOrdinaryHeld')) for value in command):
|
||||
refuse('Input observer command already has observer options')
|
||||
options = ['-errOrdinaryInputObserve', nonce, '-errOrdinaryInputScreenshots', '1']
|
||||
if look:
|
||||
@@ -682,6 +688,8 @@ def input_observer_command(command, project, packed, nonce, look=False, gate=Fal
|
||||
options += ['-errOrdinaryGateObserve', nonce]
|
||||
if choices:
|
||||
options += ['-errOrdinaryGateChoices', nonce]
|
||||
if held_action:
|
||||
options += ['-errOrdinaryHeldAction', nonce]
|
||||
return command + options
|
||||
|
||||
|
||||
@@ -882,7 +890,17 @@ def selection_module_hashes(root):
|
||||
return hashes
|
||||
|
||||
|
||||
def input_exercise_command(command, root, compat, profile, executable, run_id, host_display, display_context, look=False, approach=False, alignment=False, selection=False, coarse_approach=False):
|
||||
def held_module_hashes(root):
|
||||
hashes = {}
|
||||
for name in ('prison_held_action_proof.py', 'prison_held_action_evidence.py'):
|
||||
path = root/'tools'/name
|
||||
if path.is_symlink() or not path.is_file():
|
||||
refuse('Held action 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, selection=False, coarse_approach=False, coarse_facing=False, held_action=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':
|
||||
@@ -893,6 +911,10 @@ 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(held_action) is not bool or (held_action and not (selection and coarse_facing)):
|
||||
refuse('Held action requires explicit selection and coarse facing')
|
||||
if type(coarse_facing) is not bool or (coarse_facing and not (coarse_approach and selection)):
|
||||
refuse('Coarse facing requires explicit coarse approach and selection')
|
||||
if type(coarse_approach) is not bool or (coarse_approach and not approach):
|
||||
refuse('Coarse approach requires explicit approach')
|
||||
if type(selection) is not bool or (selection and not alignment):
|
||||
@@ -901,6 +923,12 @@ def input_exercise_command(command, root, compat, profile, executable, run_id, h
|
||||
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')
|
||||
held_tokens = [i for i,value in enumerate(child) if value.startswith('-errOrdinaryHeld')]
|
||||
if held_action:
|
||||
if len(held_tokens) != 1 or child[held_tokens[0]] != '-errOrdinaryHeldAction' or child[held_tokens[0]+1:held_tokens[0]+2] != [run_id]:
|
||||
refuse('Held action requires its exact native observer nonce')
|
||||
elif held_tokens:
|
||||
refuse('Unexpected native held-action option')
|
||||
gate_tokens = [value for value in child if value.startswith('-errOrdinaryGate')]
|
||||
if selection and '-errOrdinaryGateChoices' not in gate_tokens:
|
||||
refuse('Action selection requires exact native choices')
|
||||
@@ -939,6 +967,9 @@ 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 coarse_facing:
|
||||
context['coarse_facing_requested'] = True
|
||||
wrapper.insert(-1, '--enable-private-coarse-facing-proof')
|
||||
if coarse_approach:
|
||||
context['coarse_approach_requested'] = True
|
||||
wrapper.insert(-1, '--enable-private-coarse-approach-proof')
|
||||
@@ -950,6 +981,10 @@ def input_exercise_command(command, root, compat, profile, executable, run_id, h
|
||||
context['selection_requested'] = True
|
||||
context['selection_module_sha256'] = selection_module_hashes(root)
|
||||
wrapper.insert(-1, '--enable-private-selection-proof')
|
||||
if held_action:
|
||||
context['held_action_requested'] = True
|
||||
context['held_module_sha256'] = held_module_hashes(root)
|
||||
wrapper.insert(-1, '--enable-private-held-action-proof')
|
||||
return command[:separator+1] + wrapper + child, context
|
||||
|
||||
|
||||
@@ -971,6 +1006,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 type(report.get('held_action_requested', False)) is not bool or report.get('held_action_requested', False) != context.get('held_action_requested', False):
|
||||
return False
|
||||
if context.get('held_action_requested') and report.get('held_module_sha256') != context.get('held_module_sha256'):
|
||||
return False
|
||||
if context.get('selection_requested') and report.get('selection_module_sha256') != context.get('selection_module_sha256'):
|
||||
return False
|
||||
if report.get('selection_requested', False) != context.get('selection_requested', False):
|
||||
@@ -985,6 +1024,8 @@ def input_exercise_poll_failure(context):
|
||||
return None
|
||||
if type(report.get('coarse_approach_requested', False)) is not bool or report.get('coarse_approach_requested', False) != context.get('coarse_approach_requested', False):
|
||||
return False
|
||||
if type(report.get('coarse_facing_requested', False)) is not bool or report.get('coarse_facing_requested', False) != context.get('coarse_facing_requested', False):
|
||||
return False
|
||||
if report.get('look_requested', False) != context.get('look_requested', False):
|
||||
return False
|
||||
return bool(report.get('error') or report.get('supervision_failed') is True or (
|
||||
@@ -1028,6 +1069,15 @@ 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)
|
||||
held_action_requested = context.get('held_action_requested', False)
|
||||
if type(held_action_requested) is not bool or type(report.get('held_action_requested', False)) is not bool or report.get('held_action_requested', False) != held_action_requested:
|
||||
refuse('Held action request does not match launch context')
|
||||
if held_action_requested and (context.get('selection_requested') is not True or context.get('coarse_facing_requested') is not True or report.get('held_module_sha256') != context.get('held_module_sha256') or context.get('held_module_sha256') != held_module_hashes(ROOT)):
|
||||
refuse('Held action requires explicit coarse selection and unchanged module sources')
|
||||
if not held_action_requested and (report.get('held_action_proof') is not None or report.get('held_events')):
|
||||
refuse('Unexpected held action evidence or F input')
|
||||
if report.get('held_release_errors', []) != []:
|
||||
refuse('Held F release is unconfirmed')
|
||||
selection_requested = context.get('selection_requested', False)
|
||||
if type(report.get('selection_requested', False)) is not bool or report.get('selection_requested', False) != selection_requested:
|
||||
refuse('Action selection request does not match launch context')
|
||||
@@ -1058,6 +1108,12 @@ def input_exercise_result(result, context, session, observation, display_result)
|
||||
refuse('Coarse approach request does not match explicit launch context')
|
||||
if approach_requested and report.get('approach_proof', {}).get('profile', 'standard') != ('coarse' if coarse_approach_requested else 'standard'):
|
||||
refuse('Approach profile does not match explicit launch context')
|
||||
coarse_facing_requested = context.get('coarse_facing_requested', False)
|
||||
if type(coarse_facing_requested) is not bool or type(report.get('coarse_facing_requested', False)) is not bool or report.get('coarse_facing_requested', False) != coarse_facing_requested or (coarse_facing_requested and not (coarse_approach_requested and selection_requested)):
|
||||
refuse('Coarse facing request does not match explicit launch context')
|
||||
facing_profile = 'coarse' if coarse_facing_requested else 'standard'
|
||||
if report.get('facing_proof', {}).get('profile', 'standard') != facing_profile:
|
||||
refuse('Facing profile does not match explicit launch context')
|
||||
look_requested = context.get('look_requested', False)
|
||||
if type(report.get('look_requested', False)) is not bool or report.get('look_requested', False) != look_requested:
|
||||
refuse('Relative look request does not match launch context')
|
||||
@@ -1121,10 +1177,34 @@ def input_exercise_result(result, context, session, observation, display_result)
|
||||
if (not isinstance(event,dict) or type(event.get('button')) is not int or event['button'] != 5 or event.get('down') is not down or event.get('sent') is not True or event.get('finally')
|
||||
or type(event.get('monotonic')) not in (int,float) or not math.isfinite(event['monotonic']) or type(wheel.get(key)) not in (int,float) or not math.isfinite(wheel[key]) or abs(event['monotonic']-wheel[key]) > .01):
|
||||
refuse('Selection wheel adapter and policy pair disagree')
|
||||
proof['action_selection_proof'] = validate_selection_evidence((session/'console.log').read_text(errors='replace'),context['nonce'],report.get('look_proof'),route,report.get('facing_proof'),selected)
|
||||
proof['action_selection_proof'] = validate_selection_evidence((session/'console.log').read_text(errors='replace'),context['nonce'],report.get('look_proof'),route,report.get('facing_proof'),selected,facing_profile=facing_profile)
|
||||
proof['scope'] = 'ordinary private approach, stationary facing and one wheel pair with independent native original-lock selection; no F hold, unlock, exit or multiplayer qualification'
|
||||
if proof['action_selection_proof'].get('verified') is not True:
|
||||
refuse('Independent native action-selection evidence is incomplete')
|
||||
if held_action_requested:
|
||||
from prison_held_action_evidence import validate_held_action_evidence
|
||||
held = report.get('held_action_proof', {})
|
||||
held_events = report.get('held_events', [])
|
||||
policy_events = held.get('events', [])
|
||||
if (held.get('completed') is not True or held.get('status') != 'verified' or held.get('input_attempted') is not True
|
||||
or held.get('release_confirmed') is not True or held.get('unconfirmed_held_key') is not None
|
||||
or held.get('paired_release_errors') != [] or held.get('notification_errors') != [] or held.get('error')
|
||||
or not isinstance(held_events, list) or len(held_events) != 2 or not isinstance(policy_events, list) or len(policy_events) != 2):
|
||||
refuse('Held action lacks exactly one confirmed F pair')
|
||||
for event, policy_event, down, field in zip(held_events,policy_events,(True,False),('down_monotonic','up_monotonic')):
|
||||
stamp = event.get('monotonic') if isinstance(event,dict) else None
|
||||
if (not isinstance(event,dict) or set(event) != {'key','down','monotonic','sent'} or event.get('key') != 'F' or event.get('down') is not down or event.get('sent') is not True
|
||||
or type(stamp) not in (int,float) or not math.isfinite(stamp) or stamp < 0
|
||||
or not isinstance(policy_event,dict) or policy_event.get('key') != 'F' or policy_event.get('down') is not down or policy_event.get('confirmed') is not True or policy_event.get('cleanup')
|
||||
or type(policy_event.get('monotonic')) not in (int,float) or policy_event['monotonic'] != stamp
|
||||
or type(held.get(field)) not in (int,float) or held[field] != stamp):
|
||||
refuse('Held F adapter and policy pair disagree')
|
||||
if not held_events[0]['monotonic'] < held_events[1]['monotonic'] or held_events[1]['monotonic']-held_events[0]['monotonic'] > 5 or held_events[0]['monotonic'] <= buttons[-1]['monotonic']:
|
||||
refuse('Held F pair violates order or five-second bound')
|
||||
proof['held_action_proof'] = validate_held_action_evidence((session/'console.log').read_text(errors='replace'),context['nonce'],report.get('look_proof'),route,report.get('facing_proof'),selected,held,facing_profile=facing_profile)
|
||||
proof['scope'] = 'ordinary private approach, selection and one held F action with independent native unlock, original door motion and released-input evidence; no gate passage, full escape or multiplayer qualification'
|
||||
if any(proof['held_action_proof'].get(key) is not True for key in ('verified','held_action_verified','unlock_observed','gate_motion_observed')):
|
||||
refuse('Independent native held-action evidence is incomplete')
|
||||
elif 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'))
|
||||
@@ -1206,13 +1286,13 @@ def run_locked(a, steam, compat, profile, packed=None):
|
||||
run_id = uuid.uuid4().hex
|
||||
observation_nonce = run_id if getattr(a, 'observe_player_input', False) else None
|
||||
if observation_nonce:
|
||||
command = input_observer_command(command, a.project, packed, observation_nonce, getattr(a, 'exercise_relative_look', False), getattr(a, 'observe_prison_gate', False), getattr(a, 'observe_prison_choices', False))
|
||||
command = input_observer_command(command, a.project, packed, observation_nonce, getattr(a, 'exercise_relative_look', False), getattr(a, 'observe_prison_gate', False), getattr(a, 'observe_prison_choices', False), getattr(a, 'exercise_prison_held_action', False))
|
||||
display_context = None
|
||||
if getattr(a, 'inspect_headless_display', False):
|
||||
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), getattr(a, 'exercise_prison_alignment', False), getattr(a, 'exercise_prison_selection', False), getattr(a, 'exercise_prison_coarse_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), getattr(a, 'exercise_prison_selection', False), getattr(a, 'exercise_prison_coarse_approach', False), getattr(a, 'exercise_prison_coarse_facing', False), getattr(a, 'exercise_prison_held_action', False))
|
||||
silent_sink = 'erri_game_'+run_id
|
||||
application_id = 'org.escapistsrereintroduced.game.'+run_id
|
||||
env = os.environ.copy()
|
||||
@@ -1377,8 +1457,10 @@ 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-held-action', action='store_true', help='Explicit single held-F unlock experiment after coarse facing and selection; requires independent native progress, original door motion and release evidence')
|
||||
parser.add_argument('--exercise-prison-selection', action='store_true', help='Explicit facing and single wheel selection trial; requires alignment and native choice diagnostics; no F or gate operation')
|
||||
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-coarse-facing', action='store_true', help='Explicit coarse yaw facing; requires coarse approach and selection, unchanged total pixels and clocks')
|
||||
parser.add_argument('--exercise-prison-coarse-approach', action='store_true', help='Explicit coarse stationary-heading approach profile; requires approach and retains original clocks; no F')
|
||||
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-choices', action='store_true', help='Read-only native selected/ordered gate action diagnostics; requires --observe-prison-gate')
|
||||
|
||||
+283
-22
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Opt-in bounded private XTest keyboard and optional relative-look experiment. Not a launcher or gameplay proof."""
|
||||
import argparse
|
||||
import copy
|
||||
import ctypes as C
|
||||
import hashlib
|
||||
import json
|
||||
@@ -46,9 +47,19 @@ def option(args, name):
|
||||
def gate_options(native, args):
|
||||
# Two exact read-only options; a prefix match must never admit an alias,
|
||||
# duplicate, foreign nonce or extra diagnostic in keyboard-only mode.
|
||||
held = getattr(args, 'enable_private_held_action_proof', False)
|
||||
held_tokens = [value for value in native if value.startswith('-errOrdinaryHeld')]
|
||||
if type(held) is not bool or (held and not (getattr(args, 'enable_private_coarse_facing_proof', False) and getattr(args, 'enable_private_selection_proof', False))):
|
||||
raise Refused('Held action requires explicit coarse facing and selection')
|
||||
if held_tokens != (['-errOrdinaryHeldAction'] if held else []):
|
||||
raise Refused('Unexpected, missing or repeated native held observer option')
|
||||
if held and option(native, '-errOrdinaryHeldAction') != args.nonce:
|
||||
raise Refused('Native held observer nonce mismatch')
|
||||
tokens = [value for value in native if value.startswith('-errOrdinaryGate')]
|
||||
if any(value not in {'-errOrdinaryGateObserve', '-errOrdinaryGateChoices'} for value in tokens):
|
||||
raise Refused('Unknown native gate observer option')
|
||||
if getattr(args, 'enable_private_coarse_facing_proof', False) and not (getattr(args, 'enable_private_coarse_approach_proof', False) and getattr(args, 'enable_private_selection_proof', False)):
|
||||
raise Refused('Coarse facing requires coarse approach and selection')
|
||||
approach = getattr(args, 'enable_private_approach_proof', False)
|
||||
if getattr(args, 'enable_private_coarse_approach_proof', False) and not approach:
|
||||
raise Refused('Coarse approach requires explicit approach proof')
|
||||
@@ -136,9 +147,14 @@ def yaw_delta(first, second):
|
||||
|
||||
|
||||
class RelativeMouse:
|
||||
def __init__(self, query, keyboard, owner, enable_approach=False, enable_alignment=False, enable_coarse_approach=False):
|
||||
def __init__(self, query, keyboard, owner, enable_approach=False, enable_alignment=False, enable_coarse_approach=False, enable_coarse_facing=False, enable_selection=False):
|
||||
if type(enable_coarse_approach) is not bool or (enable_coarse_approach and not enable_approach):
|
||||
raise Refused("Coarse approach requires explicit approach capability")
|
||||
if type(enable_coarse_facing) is not bool or (enable_coarse_facing and not (enable_coarse_approach is True and enable_alignment is True and enable_selection is True)):
|
||||
raise Refused('Coarse facing requires explicit selection and coarse approach capability')
|
||||
self.enable_coarse_facing = enable_coarse_facing
|
||||
self.alignment_pixels = 0
|
||||
self.alignment_requests = 0
|
||||
self.enable_coarse_approach = enable_coarse_approach
|
||||
self.query, self.keyboard, self.owner = query, keyboard, owner
|
||||
self.enable_approach = enable_approach
|
||||
@@ -189,13 +205,19 @@ class RelativeMouse:
|
||||
|
||||
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):
|
||||
or not 1 <= abs(dx) <= (384 if self.enable_coarse_facing else 128) or type(dy) is not int or dy != 0):
|
||||
raise Refused('Unsupported or unenabled alignment relative step')
|
||||
def within_lease():
|
||||
if clock() >= deadline:
|
||||
if type(deadline) not in (int, float) or not math.isfinite(deadline) or not 0 <= clock() < deadline:
|
||||
raise Refused('Original alignment deadline exceeded before relative input')
|
||||
within_lease()
|
||||
self._move(dx, dy, guard, before_send=within_lease)
|
||||
if self.alignment_requests >= 6 or self.alignment_pixels+abs(dx) > 768:
|
||||
raise Refused('Alignment request or cumulative pixel budget exhausted')
|
||||
def commit():
|
||||
within_lease()
|
||||
self.alignment_requests += 1
|
||||
self.alignment_pixels += abs(dx)
|
||||
self._move(dx, dy, guard, before_send=commit)
|
||||
|
||||
def _move(self, dx, dy, guard, before_send=None):
|
||||
target = guard.check()
|
||||
@@ -304,7 +326,11 @@ class Observer:
|
||||
"""Only records appended after this exact owned console was opened count."""
|
||||
pattern = re.compile(r'ERR INPUT OBSERVE: run=([A-Za-z0-9]{1,32}) sample=(\d+) real=(\d+) world=(\d+(?:\.\d+)?) (.+)$')
|
||||
|
||||
def __init__(self, path, nonce, clock=time.monotonic, enable_look=False, enable_approach=False, enable_selection=False):
|
||||
def __init__(self, path, nonce, clock=time.monotonic, enable_look=False, enable_approach=False, enable_selection=False, enable_held=False):
|
||||
if type(enable_held) is not bool or (enable_held and not (enable_selection and enable_approach and enable_look)):
|
||||
raise Refused('Held observation requires all original observation capabilities')
|
||||
self.enable_held = enable_held
|
||||
self.held_previous = None; self.held_anchor = None
|
||||
self.path, self.nonce, self.clock = path, nonce, clock
|
||||
self.fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
|
||||
info = os.fstat(self.fd)
|
||||
@@ -476,8 +502,42 @@ class Observer:
|
||||
def selection_snapshots(self):
|
||||
return self._snapshot_batch('selection_previous')
|
||||
|
||||
def _snapshot_batch(self, cursor):
|
||||
def begin_held(self, final_sample):
|
||||
if self.enable_held is not True or self.held_anchor is not None or type(final_sample) is not int:
|
||||
raise Refused('Held observation cannot be started or re-adopted')
|
||||
_, data = self.approach_data()
|
||||
if final_sample not in data['snapshots'] or final_sample < data['ready']['sample']:
|
||||
raise Refused('Held observation lacks original selection anchor')
|
||||
self.held_anchor = final_sample; self.held_previous = final_sample-1
|
||||
|
||||
def held_data(self):
|
||||
# Native observer retains original world/player/action/door objects; the
|
||||
# same acknowledged stream may show their moving geometry after phase2.
|
||||
# Do not run the closed-yard geometry validator on that moving door.
|
||||
from ordinary_gate_choices import join_choice_snapshots
|
||||
self.fresh()
|
||||
if (self.enable_held is not True or self.held_anchor is None or self.events != ['READY'] or
|
||||
not self.gate_complete_end or any(r['kind'] == 'cancelled' for r in self.gate_records+self.look_records)):
|
||||
raise Refused('Original held observation context ended or was not adopted')
|
||||
content = '\n'.join(self.approach_lines[:self.gate_complete_end])
|
||||
data = join_choice_snapshots(content, self.nonce)
|
||||
latest = max(data['snapshots'])
|
||||
if (latest < self.held_anchor or self.clock()-self.gate_received[latest] > .5 or
|
||||
self.input_samples[-1]['real_ms']-data['snapshots'][latest]['real_ms'] > 500):
|
||||
raise Refused('Original held native snapshot is stale or reversed')
|
||||
phase = 1
|
||||
for index in range(self.held_anchor, latest+1):
|
||||
item = data['snapshots'][index]
|
||||
if item['action']['phase'] < phase or item['input']['inventory']:
|
||||
raise Refused('Original held phase or menu context changed')
|
||||
phase = item['action']['phase']
|
||||
return content, data
|
||||
|
||||
def held_snapshots(self):
|
||||
return self._snapshot_batch('held_previous')
|
||||
|
||||
def _snapshot_batch(self, cursor):
|
||||
_, data = self.held_data() if cursor == 'held_previous' else self.approach_data()
|
||||
snapshots = data['snapshots']; latest = max(snapshots)
|
||||
previous = getattr(self, cursor)
|
||||
first = latest if previous is None else min(previous + 1, latest)
|
||||
@@ -493,9 +553,11 @@ class Observer:
|
||||
'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')}}})
|
||||
if self.enable_held:
|
||||
result[-1].update(world_ms=native['world_ms'], turn=pose['turn'])
|
||||
if self.enable_selection:
|
||||
choice = item['choices']
|
||||
if cursor != 'approach_previous' and action['context_selected']:
|
||||
if cursor != 'approach_previous' and action['phase'] == 1 and action['context_selected']:
|
||||
from ordinary_gate_choices import exact_prison_choices
|
||||
choice = exact_prison_choices(item)
|
||||
result[-1]['choices'] = {key: choice[key] for key in ('nonce','sample','real_ms','selected_id','selected_class','actions')}
|
||||
@@ -545,6 +607,89 @@ class Keyboard:
|
||||
self.query.x.XFlush(self.query.d)
|
||||
|
||||
|
||||
class HeldFKey:
|
||||
"""One F-only attempt on the captured private connection; never creates one."""
|
||||
def __init__(self, guard, keyboard, mouse, report, clock, sleep, original_deadline, *, enabled=False):
|
||||
if enabled is not True or type(original_deadline) not in (int,float) or not math.isfinite(original_deadline):
|
||||
raise Refused('Held F requires explicit capability and original deadline')
|
||||
self.guard, self.keyboard, self.mouse = guard, keyboard, mouse
|
||||
self.query, self.xt = keyboard.query, keyboard.xt
|
||||
self.owner = copy.deepcopy(mouse.owner)
|
||||
self.report, self.clock, self.sleep = report, clock, sleep
|
||||
self.original_deadline = original_deadline
|
||||
self.attempted = False; self.pending = False; self.lease = None; self.up_attempts = 0
|
||||
self._context(guard)
|
||||
self.code = int(self.query.x.XKeysymToKeycode(self.query.d, 0x66))
|
||||
if (not 8 <= self.code <= 255 or self.query.x.XKeycodeToKeysym(self.query.d, self.code, 0) != 0x66 or
|
||||
self.code in keyboard.codes.values()):
|
||||
raise Refused('Default distinct unmodified F mapping unavailable')
|
||||
report.setdefault('held_events', []); report.setdefault('held_release_errors', [])
|
||||
|
||||
def _context(self, guard):
|
||||
if (guard is not self.guard or self.guard.query is not self.query or self.keyboard.query is not self.query or
|
||||
self.mouse.query is not self.query or self.mouse.keyboard is not self.keyboard or
|
||||
self.keyboard.xt is not self.xt or self.mouse.owner != self.owner or self.guard.owner != self.owner):
|
||||
raise Refused('Captured F owner or connection changed')
|
||||
|
||||
def _owned(self, guard, *, idle):
|
||||
self._context(guard)
|
||||
target = guard.check()
|
||||
if idle: self.keyboard.require_idle()
|
||||
self.mouse.validate_pointer(target,0,0)
|
||||
target = guard.check()
|
||||
if idle: self.keyboard.require_idle()
|
||||
self.mouse.validate_pointer(target,0,0)
|
||||
self._context(guard)
|
||||
|
||||
def down_f(self, guard, *, deadline, not_after):
|
||||
if (self.attempted or type(deadline) not in (int,float) or not math.isfinite(deadline) or
|
||||
type(not_after) not in (int,float) or not math.isfinite(not_after) or
|
||||
not 0 <= self.clock() <= not_after <= self.original_deadline-6 or
|
||||
not self.clock() < deadline <= min(self.original_deadline,self.clock()+5)):
|
||||
raise Refused('Repeated, expired or unqualified held F admission')
|
||||
# The numeric original keycode is retained for cleanup even if its
|
||||
# mapping later changes; never redirect key-up to a new F keycode.
|
||||
self.attempted = True; self.pending = True; self.lease = deadline
|
||||
self.report['unconfirmed_held_key'] = 'F'
|
||||
self._owned(guard,idle=True)
|
||||
if self.query.x.XKeycodeToKeysym(self.query.d,self.code,0) != 0x66:
|
||||
raise Refused('Original F mapping changed before down')
|
||||
now = self.clock()
|
||||
if not 0 <= now <= not_after or now >= self.lease:
|
||||
raise Refused('Six-second admission or native pose freshness expired before F')
|
||||
event = {'key':'F','down':True,'monotonic':now,'sent':False}
|
||||
self.report['held_events'].append(event); self.report['input_attempted'] = True
|
||||
if not self.xt.XTestFakeKeyEvent(self.query.d,self.code,1,0):
|
||||
raise Refused('Native held F down request uncertain')
|
||||
self.query.x.XFlush(self.query.d)
|
||||
event['sent'] = True; self.report['input_sent'] = True
|
||||
return {'key':'F','down_monotonic':now}
|
||||
|
||||
def up_f(self, guard, *, deadline, cleanup=False):
|
||||
try:
|
||||
if (type(cleanup) is not bool or not self.attempted or not self.pending or self.up_attempts >= 2 or
|
||||
type(deadline) not in (int,float) or not math.isfinite(deadline) or deadline != self.lease):
|
||||
raise Refused('Unowned, repeated or renewed held F release')
|
||||
self.up_attempts += 1
|
||||
self._owned(guard,idle=False)
|
||||
now = self.clock()
|
||||
if not math.isfinite(now) or now < 0 or (not cleanup and now >= min(self.lease,self.original_deadline)):
|
||||
raise Refused('Original F release deadline expired')
|
||||
event = {'key':'F','down':False,'monotonic':now,'sent':False}
|
||||
if cleanup: event['finally'] = True
|
||||
self.report['held_events'].append(event); self.report['input_attempted'] = True
|
||||
if not self.xt.XTestFakeKeyEvent(self.query.d,self.code,0,0):
|
||||
raise Refused('Native held F up request uncertain')
|
||||
self.query.x.XFlush(self.query.d)
|
||||
event['sent'] = True; self.report['input_sent'] = True
|
||||
confirm_release(guard,self.keyboard,self.report,'F',self.clock,self.sleep)
|
||||
self.pending = False; self.report['unconfirmed_held_key'] = None
|
||||
return {'key':'F','up_monotonic':now,'released':True}
|
||||
except Exception as error:
|
||||
self.report['held_release_errors'].append(str(error))
|
||||
raise
|
||||
|
||||
|
||||
class Guard:
|
||||
def __init__(self, args, bound, query, owner):
|
||||
self.args, self.bound, self.query, self.owner = args, bound, query, owner
|
||||
@@ -595,9 +740,13 @@ 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, alignment=False, selection=False, coarse_approach=False):
|
||||
def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time.sleep, mouse=None, approach=False, alignment=False, selection=False, coarse_approach=False, coarse_facing=False, held_action=False):
|
||||
if type(coarse_approach) is not bool or (coarse_approach and not approach) or report.get("coarse_approach_requested", False) is not coarse_approach:
|
||||
raise Refused("Coarse approach requires matching explicit capability")
|
||||
if type(coarse_facing) is not bool or (coarse_facing and not (selection and coarse_approach)) or report.get('coarse_facing_requested', False) is not coarse_facing:
|
||||
raise Refused('Coarse facing requires matching explicit selection capability')
|
||||
if type(held_action) is not bool or report.get('held_action_requested', False) is not held_action or (held_action and not (selection and coarse_facing and getattr(observer, 'enable_held', False))):
|
||||
raise Refused('Held action requires matching explicit selection and observer capabilities')
|
||||
deadline = clock() + 70
|
||||
held = None
|
||||
approach_completed = False
|
||||
@@ -665,6 +814,9 @@ def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time
|
||||
notify({'kind': 'approach-pending', 'deadline_monotonic': deadline})
|
||||
if observer.events != ['READY']:
|
||||
raise Refused('Inventory was already changed before input')
|
||||
if held_action:
|
||||
original_end = observer.ready_at + (45000-(observer.ready_real-observer.queued_real))/1000-.5
|
||||
notify({'kind':'held-observer-deadline','deadline_monotonic':original_end})
|
||||
if mouse is not None:
|
||||
perform_look(guard, keyboard, observer, mouse, report, clock, sleep)
|
||||
if not approach:
|
||||
@@ -679,6 +831,8 @@ def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time
|
||||
notify({'kind': 'approach-finished', 'successful': True})
|
||||
if selection:
|
||||
run_selection(guard, keyboard, observer, mouse, report, clock, sleep)
|
||||
if held_action:
|
||||
run_held_action(guard, keyboard, observer, mouse, report, clock, sleep)
|
||||
else:
|
||||
run_alignment(guard, keyboard, observer, mouse, report, clock, sleep)
|
||||
report["sequence_completed"] = True
|
||||
@@ -714,9 +868,9 @@ def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time
|
||||
if approach:
|
||||
route = report.get('approach_proof', {})
|
||||
report['paired_release_errors'].extend(route.get('paired_release_errors', []))
|
||||
report['unconfirmed_held_key'] = route.get('unconfirmed_held_key')
|
||||
report['unconfirmed_held_key'] = report.get('unconfirmed_held_key') or route.get('unconfirmed_held_key')
|
||||
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'] and not report.get('unconfirmed_held_button') and not report.get('wheel_release_errors')
|
||||
report['all_attempted_presses_released'] = not report.get('unconfirmed_held_key') and not report['paired_release_errors'] and not report.get('unconfirmed_held_button') and not report.get('wheel_release_errors') and not report.get('held_release_errors')
|
||||
|
||||
|
||||
class ApproachKeyboard:
|
||||
@@ -926,6 +1080,10 @@ def run_selection(guard, keyboard, observer, mouse, report, clock, sleep):
|
||||
from prison_alignment_proof import perform_facing
|
||||
from prison_alignment_evidence import validate_facing_evidence
|
||||
from prison_action_selection_proof import perform_selection
|
||||
coarse_facing = report.get('coarse_facing_requested', False)
|
||||
if type(coarse_facing) is not bool or coarse_facing is not getattr(mouse, 'enable_coarse_facing', False):
|
||||
raise Refused('Facing profile differs from owned mouse capability')
|
||||
facing_profile = 'coarse' if coarse_facing else 'standard'
|
||||
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')
|
||||
@@ -960,13 +1118,13 @@ def run_selection(guard, keyboard, observer, mouse, report, clock, sleep):
|
||||
# is handed to this stage. It may only assert that all keys are idle.
|
||||
perform_facing(guard, keyboard.require_idle, AlignmentMouse(mouse, report, clock, deadline),
|
||||
observer.alignment_snapshots, route['geometry'], calibration, evidence, report,
|
||||
clock=clock, sleep=sleep, notify=alignment_notify)
|
||||
clock=clock, sleep=sleep, notify=alignment_notify, profile=facing_profile)
|
||||
facing = report.get('facing_proof', {})
|
||||
if not facing.get('completed'):
|
||||
raise Refused('Facing module did not complete')
|
||||
deadline = min(deadline, facing['deadline_monotonic'])
|
||||
content, data = observer.approach_data()
|
||||
facing_evidence = validate_facing_evidence(content, observer.nonce, look, route, facing)
|
||||
facing_evidence = validate_facing_evidence(content, observer.nonce, look, route, facing, profile=facing_profile)
|
||||
if not facing_evidence.get('verified'):
|
||||
raise Refused('Independent native facing prerequisite refused')
|
||||
if clock() >= deadline:
|
||||
@@ -987,12 +1145,54 @@ def run_selection(guard, keyboard, observer, mouse, report, clock, sleep):
|
||||
notify({'kind': 'alignment-finished', 'successful': successful})
|
||||
|
||||
|
||||
def run_held_action(guard, keyboard, observer, mouse, report, clock, sleep):
|
||||
from prison_action_selection_evidence import validate_selection_evidence
|
||||
from prison_held_action_proof import perform_held_action
|
||||
from prison_held_action_evidence import validate_held_action_evidence
|
||||
if (report.get('held_action_requested') is not True or report.get('coarse_facing_requested') is not True or
|
||||
observer.enable_held is not True or mouse.enable_coarse_facing is not True):
|
||||
raise Refused('Held action lacks explicit native/input capabilities')
|
||||
content, _ = observer.approach_data()
|
||||
selected = report.get('action_selection_proof')
|
||||
accepted = validate_selection_evidence(content,observer.nonce,report.get('look_proof'),report.get('approach_proof'),
|
||||
report.get('facing_proof'),selected,facing_profile='coarse')
|
||||
if accepted.get('verified') is not True:
|
||||
raise Refused('Independent native one-wheel selection prerequisite refused')
|
||||
if observer.ready_at is None or observer.ready_real is None or observer.queued_real is None:
|
||||
raise Refused('Original held observation clock missing')
|
||||
original_end = observer.ready_at+(45000-(observer.ready_real-observer.queued_real))/1000-.5
|
||||
calibration = {'nonce':observer.nonce,'ready_real_ms':observer.ready_real,'ready_monotonic':observer.ready_at,
|
||||
'observer_start_real_ms':observer.queued_real,'observer_deadline_monotonic':original_end}
|
||||
observer.begin_held(accepted['last_sample'])
|
||||
key = HeldFKey(guard,keyboard,mouse,report,clock,sleep,original_end,enabled=True)
|
||||
try:
|
||||
perform_held_action(guard,keyboard.require_idle,key,observer.held_snapshots,selected['geometry'],calibration,accepted,report,
|
||||
clock=clock,sleep=sleep,notify=notify)
|
||||
finally:
|
||||
held = report.get('held_action_proof',{})
|
||||
report['paired_release_errors'].extend(held.get('paired_release_errors',[]))
|
||||
if held.get('unconfirmed_held_key') is not None:
|
||||
report['unconfirmed_held_key'] = held['unconfirmed_held_key']
|
||||
# Live success still belongs to the independent raw parser. No action/input
|
||||
# can follow this final read-only observation and full policy verification.
|
||||
content, _ = observer.held_data()
|
||||
verified = validate_held_action_evidence(content,observer.nonce,report.get('look_proof'),report.get('approach_proof'),
|
||||
report.get('facing_proof'),selected,report.get('held_action_proof'),facing_profile='coarse')
|
||||
if verified.get('verified') is not True:
|
||||
raise Refused('Independent native held-action evidence refused')
|
||||
guard.check(); keyboard.require_idle()
|
||||
if clock() >= original_end:
|
||||
raise Refused('Original held observation lifetime expired before completion')
|
||||
|
||||
|
||||
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-held-action-proof', action='store_true', help='One bounded F hold after independently accepted selection; requires explicit coarse facing')
|
||||
p.add_argument('--enable-private-selection-proof', action='store_true', help='After facing, try one button5 wheel selection; never perform the action')
|
||||
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-coarse-facing-proof', action='store_true', help='Explicit coarse facing only; requires coarse approach and selection, no renewed clocks')
|
||||
p.add_argument('--enable-private-coarse-approach-proof', action='store_true', help='Explicit larger stationary-yaw profile; requires approach, original clocks unchanged')
|
||||
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'):
|
||||
@@ -1002,6 +1202,10 @@ 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_held_action_proof and not (args.enable_private_selection_proof and args.enable_private_coarse_facing_proof):
|
||||
p.error('Held action requires selection and coarse facing')
|
||||
if args.enable_private_coarse_facing_proof and not (args.enable_private_coarse_approach_proof and args.enable_private_selection_proof):
|
||||
p.error('Coarse facing requires coarse approach and selection')
|
||||
if args.enable_private_coarse_approach_proof and not args.enable_private_approach_proof:
|
||||
p.error('Coarse approach proof requires explicit approach proof')
|
||||
if args.enable_private_selection_proof and not (args.enable_private_alignment_proof and args.enable_private_approach_proof and args.enable_private_look_proof):
|
||||
@@ -1067,6 +1271,7 @@ APPROACH_MODULES = ('prison_approach_proof.py', 'ordinary_gate_evidence.py',
|
||||
|
||||
ALIGNMENT_MODULES = ('prison_alignment_proof.py', 'prison_alignment_evidence.py')
|
||||
SELECTION_MODULES = ('ordinary_gate_choices.py', 'prison_action_selection_proof.py', 'prison_action_selection_evidence.py')
|
||||
HELD_MODULES = ('prison_held_action_proof.py', 'prison_held_action_evidence.py')
|
||||
|
||||
|
||||
def approach_module_hashes():
|
||||
@@ -1088,6 +1293,8 @@ def report_for_write(report):
|
||||
names.append('alignment_proof')
|
||||
if report.get('selection_requested') is True:
|
||||
names += ['facing_proof', 'action_selection_proof']
|
||||
if report.get('held_action_requested') is True:
|
||||
names.append('held_action_proof')
|
||||
for name in names:
|
||||
route = report.get(name)
|
||||
if isinstance(route, dict):
|
||||
@@ -1095,7 +1302,7 @@ def report_for_write(report):
|
||||
samples = route.get('samples')
|
||||
if isinstance(samples, list):
|
||||
result[name]['samples'] = [
|
||||
{key: sample.get(key) for key in ('sample', 'real_ms', 'received_monotonic')}
|
||||
{key: sample.get(key) for key in (('sample', 'real_ms', 'world_ms', 'received_monotonic') if name == 'held_action_proof' else ('sample', 'real_ms', 'received_monotonic'))}
|
||||
if isinstance(sample, dict) else {'invalid_reference': True} for sample in samples]
|
||||
return result
|
||||
|
||||
@@ -1111,7 +1318,9 @@ def main(argv=None):
|
||||
console_path(args)
|
||||
bound = display.binding(args.host_display, args.prefix)
|
||||
report = {'schema': 1, 'kind': 'headless-private-keyboard-experiment', 'input_sent': False,
|
||||
'held_action_requested': getattr(args, 'enable_private_held_action_proof', False),
|
||||
'look_requested': args.enable_private_look_proof,
|
||||
'coarse_facing_requested': getattr(args, 'enable_private_coarse_facing_proof', False),
|
||||
'coarse_approach_requested': getattr(args, 'enable_private_coarse_approach_proof', False),
|
||||
'approach_requested': getattr(args, 'enable_private_approach_proof', False),
|
||||
'alignment_requested': getattr(args, 'enable_private_alignment_proof', False),
|
||||
@@ -1128,6 +1337,9 @@ def main(argv=None):
|
||||
if report['selection_requested']:
|
||||
report['selection_module_sha256'] = module_hashes(SELECTION_MODULES)
|
||||
report.update(wheel_events=[], wheel_release_errors=[], unconfirmed_held_button=None)
|
||||
if report['held_action_requested']:
|
||||
report['held_module_sha256'] = module_hashes(HELD_MODULES)
|
||||
report.update(held_events=[], held_release_errors=[])
|
||||
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,
|
||||
@@ -1165,7 +1377,7 @@ def main(argv=None):
|
||||
raise Refused('New native console deadline exceeded')
|
||||
report['console'] = str(args.console)
|
||||
observer = Observer(args.console, args.nonce, enable_look=args.enable_private_look_proof,
|
||||
enable_approach=getattr(args, "enable_private_approach_proof", False), enable_selection=getattr(args, "enable_private_selection_proof", False))
|
||||
enable_approach=getattr(args, "enable_private_approach_proof", False), enable_selection=getattr(args, "enable_private_selection_proof", False), enable_held=getattr(args, "enable_private_held_action_proof", False))
|
||||
query = display.X11(bound['display'])
|
||||
while time.monotonic() < deadline:
|
||||
if child is not None and child.poll() is not None:
|
||||
@@ -1198,8 +1410,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), enable_alignment=getattr(args, "enable_private_alignment_proof", False), enable_coarse_approach=getattr(args, "enable_private_coarse_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), alignment=getattr(args, "enable_private_alignment_proof", False), selection=getattr(args, "enable_private_selection_proof", False), coarse_approach=getattr(args, "enable_private_coarse_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), enable_coarse_approach=getattr(args, "enable_private_coarse_approach_proof", False), enable_coarse_facing=getattr(args, 'enable_private_coarse_facing_proof', False), enable_selection=getattr(args, 'enable_private_selection_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), selection=getattr(args, "enable_private_selection_proof", False), coarse_approach=getattr(args, "enable_private_coarse_approach_proof", False), coarse_facing=getattr(args, 'enable_private_coarse_facing_proof', False), held_action=getattr(args, 'enable_private_held_action_proof', False))
|
||||
except (OSError, ValueError, UnicodeError, subprocess.SubprocessError) as error:
|
||||
report['error'] = str(error)
|
||||
finally:
|
||||
@@ -1296,6 +1508,50 @@ def supervision_failure(record, key, reason, button=None):
|
||||
os.close(fd)
|
||||
|
||||
|
||||
class HeldSupervisor:
|
||||
"""Independent absolute observer/F deadlines; other key leases stay1.5s."""
|
||||
def __init__(self, enabled):
|
||||
self.enabled = enabled; self.observer_deadline = None
|
||||
self.key_deadline = None; self.key = None; self.attempted = False; self.failed = False
|
||||
|
||||
def consume(self, event, now, *, other_pending=False):
|
||||
kind = event.get('kind')
|
||||
if not isinstance(kind,str) or not kind.startswith('held-'): return False
|
||||
if self.enabled is not True or type(now) not in (int,float) or not math.isfinite(now):
|
||||
self.failed = True; return True
|
||||
value = event.get('deadline_monotonic')
|
||||
numeric = type(value) in (int,float) and math.isfinite(value)
|
||||
if kind == 'held-observer-deadline':
|
||||
if self.observer_deadline is not None or not numeric or not now < value <= now+45:
|
||||
self.failed = True
|
||||
else:self.observer_deadline = value
|
||||
elif kind == 'held-action-pending':
|
||||
if self.observer_deadline is None or not numeric or not now < value <= self.observer_deadline:
|
||||
self.failed = True
|
||||
else:self.observer_deadline = value
|
||||
elif kind == 'held-key-pending':
|
||||
if (event.get('key') != 'F' or self.attempted or other_pending or self.observer_deadline is None or
|
||||
not numeric or not now < value <= min(now+5,self.observer_deadline)):
|
||||
self.failed = True
|
||||
else:
|
||||
self.attempted = True; self.key = 'F'; self.key_deadline = value
|
||||
elif kind == 'held-key-release-confirmed':
|
||||
if event.get('key') != 'F' or self.key != 'F':self.failed = True
|
||||
else:
|
||||
if now >= self.key_deadline:self.failed = True
|
||||
self.key = None; self.key_deadline = None
|
||||
elif kind != 'held-action-finished':self.failed = True
|
||||
return True
|
||||
|
||||
def expired(self, now, proof_finished=False):
|
||||
return (self.failed or (self.key_deadline is not None and now >= self.key_deadline) or
|
||||
(not proof_finished and self.observer_deadline is not None and now >= self.observer_deadline))
|
||||
|
||||
def cleanup_ack(self, event):
|
||||
if event.get('kind') == 'held-key-release-confirmed' and event.get('key') == self.key == 'F':
|
||||
self.key = None; self.key_deadline = None
|
||||
|
||||
|
||||
def supervise(argv=None):
|
||||
# Fork before loading Xlib or opening any display. The worker owns the one
|
||||
# connection. Parent never opens a second connection or retries an event.
|
||||
@@ -1313,6 +1569,8 @@ def supervise(argv=None):
|
||||
approach_deadline = None; approach_finished = False
|
||||
button_at = None; held_button = None
|
||||
look_at = None; held_at = None; held_key = None; owned_child = None; report_record = None; proof_finished = False; expired = False
|
||||
raw_args = sys.argv[1:] if argv is None else argv
|
||||
held_scope = HeldSupervisor(raw_args.count('--enable-private-held-action-proof') == 1)
|
||||
deadline = time.monotonic() + 190
|
||||
try:
|
||||
child.start(); child_pipe.close()
|
||||
@@ -1322,7 +1580,9 @@ def supervise(argv=None):
|
||||
event = parent.recv()
|
||||
except EOFError:
|
||||
event = None
|
||||
if event and event['kind'] == 'alignment-pending':
|
||||
if event and str(event.get('kind','')).startswith('held-') and held_scope.consume(event,time.monotonic(),other_pending=held_key is not None or held_button is not None or look_at is not None):
|
||||
pass
|
||||
elif 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:
|
||||
@@ -1359,7 +1619,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 (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 (button_at is not None and now - button_at > 1.5) 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 held_scope.expired(now,proof_finished) 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 (button_at is not None and now - button_at > 1.5) 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)
|
||||
@@ -1372,6 +1632,7 @@ def supervise(argv=None):
|
||||
last = parent.recv()
|
||||
except EOFError:
|
||||
break
|
||||
held_scope.cleanup_ack(last)
|
||||
if last['kind'] == 'release-confirmed':
|
||||
held_key = None
|
||||
elif last['kind'] == 'button-release-confirmed' and last['button'] == held_button:
|
||||
@@ -1380,14 +1641,14 @@ def supervise(argv=None):
|
||||
report_record = last
|
||||
elif last['kind'] == 'proof-finished':
|
||||
proof_finished = last['successful']
|
||||
if not (received and proof_finished and held_key is None and held_button is None):
|
||||
supervision_failure(report_record, held_key, 'Private input worker timed out or was interrupted', button=held_button)
|
||||
if not (received and proof_finished and held_key is None and held_scope.key is None and held_button is None):
|
||||
supervision_failure(report_record, held_scope.key or held_key, 'Private input worker timed out or was interrupted', button=held_button)
|
||||
break
|
||||
child.join(2)
|
||||
if child.exitcode != 0 and not expired:
|
||||
stop_exact_child(owned_child)
|
||||
if not (received and proof_finished and held_key is None and held_button is None):
|
||||
supervision_failure(report_record, held_key, 'Private input worker exited unsuccessfully', button=held_button)
|
||||
if not (received and proof_finished and held_key is None and held_scope.key is None and held_button is None):
|
||||
supervision_failure(report_record, held_scope.key or held_key, 'Private input worker exited unsuccessfully', button=held_button)
|
||||
return 1 if expired or child.exitcode != 0 else 0
|
||||
finally:
|
||||
if child.pid and child.is_alive():
|
||||
|
||||
@@ -13,6 +13,7 @@ p.add_argument('--steam', type=Path, default=Path.home()/'.local/share/Steam')
|
||||
p.add_argument('--compat', type=Path, required=True, help='Disposable cloned Proton compatdata directory outside Steam')
|
||||
p.add_argument('--profile', type=Path, required=True, help='Separate disposable successor Workbench profile')
|
||||
p.add_argument('--smoke', choices=['aircraft-field', 'aircraft-route', 'native-end-active', 'native-end-preparing', 'aircraft-control', 'aircraft-cancel', 'aircraft-translate', 'map-ownership', 'mortar-refresh', 'mortar-salvo', 'mortar-courier', 'station-teardown', 'prison-breach', 'powered-flight', 'powered-translate', 'local-admission', 'mortar-expiry', 'mortar-transfer', 'mortar-fire', 'patrol-hq', 'victory', 'lost-helicopter', 'player-death', 'radio-network', 'hq-response', 'mortar-inventory', 'startup-failure', 'site-survey', 'cache-loss', 'extraction-capacity', 'early-helicopter-loss', 'extraction-startup-failure', 'extraction-boundary-failure', 'transport-loss', 'transport-survey', 'lifecycle', 'objective-ui', 'recovery', 'prisoner-kit', 'medical', 'casualty-load', 'casualty-direct', 'casualty-bench', 'casualty-remove', 'environment', 'environment-restart', 'patrols', 'radio-document-loss', 'radio-document-loss-partial', 'guard-cancel', 'casualty-service'], default='victory')
|
||||
p.add_argument('--field-terrain-audit', action='store_true', help='After a fixed aircraft-field survey refusal, log one read-only 1681-node coarse terrain grid; never selects or qualifies a corridor')
|
||||
p.add_argument('--field-service-cancel', action='store_true', help='Explicit aircraft-field variant: Stop the original retained service during measured cruise; requires physical cancellation landing')
|
||||
p.add_argument('--route-service-cancel', action='store_true', help='Explicit aircraft-route variant: stop the retained service after actual airborne translation and require controlled cancellation landing')
|
||||
p.add_argument('--station-teardown-diagnostics', action='store_true', help='Opt-in bounded native deletion diagnostics; preserves all validation errors')
|
||||
@@ -26,6 +27,8 @@ a = p.parse_args()
|
||||
steam, compat, profile = a.steam.resolve(), a.compat.resolve(), a.profile.resolve()
|
||||
if not 1 <= a.rounds <= 10 or not 60 <= a.timeout <= 1800:
|
||||
p.error('Use 1..10 rounds and a 60..1800 second timeout')
|
||||
if a.field_terrain_audit and a.smoke != 'aircraft-field': p.error('--field-terrain-audit requires --smoke aircraft-field')
|
||||
if a.field_terrain_audit and a.field_service_cancel: p.error('--field-terrain-audit and --field-service-cancel are mutually exclusive')
|
||||
if a.field_service_cancel and a.smoke != 'aircraft-field': p.error('--field-service-cancel requires --smoke aircraft-field')
|
||||
if a.route_service_cancel and a.smoke != 'aircraft-route': p.error('--route-service-cancel requires --smoke aircraft-route')
|
||||
if a.smoke == 'mortar-courier' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 500):
|
||||
@@ -81,6 +84,7 @@ for proc in Path('/proc').iterdir():
|
||||
except (FileNotFoundError, PermissionError, ProcessLookupError): pass
|
||||
command = ['gamescope', '--backend', 'headless', '-W', '1280', '-H', '720', '-w', '1280', '-h', '720', '-r', '30', '--', str(proton), 'run', str(wb/'ArmaReforgerWorkbenchSteamDiag.exe'), '-gproj', 'Z:'+str(ROOT/'addons'/a.project/'addon.gproj'), '-addonsDir', 'Z:'+str(ROOT/'addons'), '-profile', profile_arg, '-logconsole', '-wbModule=WorldEditor', '-load', 'Worlds/ERR_Everon.ent', '-plugin=ERR_ValidateWorldPlugin', '-errPlay', '1', '-errSmoke', '1', '-errSmokeOutcome', a.smoke, '-noThrow', '-disableCrashReporter']
|
||||
if a.field_service_cancel: command += ['-errSmokeFieldServiceCancel', '1']
|
||||
if a.field_terrain_audit: command += ['-errSmokeFieldTerrainAudit', '1']
|
||||
if a.route_service_cancel: command += ['-errSmokeRouteServiceCancel', '1']
|
||||
if a.station_teardown_diagnostics: command += ['-errStationTeardownDiag', '1']
|
||||
if a.flight_input_diagnostics: command += ['-errFlightInputDiag', '1']
|
||||
|
||||
@@ -7,10 +7,10 @@ from prison_alignment_evidence import validate_facing_evidence
|
||||
from prison_approach_evidence import need,number,neutral,yaw,angle,horizontal
|
||||
|
||||
|
||||
def validate_selection_evidence(content,nonce,look_proof,approach_proof,facing_proof,proof):
|
||||
def validate_selection_evidence(content,nonce,look_proof,approach_proof,facing_proof,proof,*,facing_profile="standard"):
|
||||
result={'verified':False,'errors':[],'scope':'one guarded private wheel pair and native selection of the original prison lock after verified approach/facing; no F hold, unlock, exit or multiplayer qualification','held_action_verified':False,'escape_verified':False}
|
||||
try:
|
||||
facing=validate_facing_evidence(content,nonce,look_proof,approach_proof,facing_proof)
|
||||
facing=validate_facing_evidence(content,nonce,look_proof,approach_proof,facing_proof,profile=facing_profile)
|
||||
need(facing['verified'],'independent native facing prerequisite failed: '+str(facing['errors']))
|
||||
need(type(proof) is dict and proof.get('requested') is True and proof.get('completed') is True and proof.get('nonce')==nonce and not proof.get('error') and not proof.get('events') and not proof.get('motions'),'selection did not complete under exact nonce without extra input')
|
||||
need(set(proof)=={'requested','completed','nonce','geometry','started_monotonic','finished_monotonic','deadline_monotonic','initial_sample','final_sample','selection_samples','actions','wheel','samples'},'unexpected selection proof fields or extra input')
|
||||
|
||||
@@ -10,16 +10,20 @@ def validate_alignment_evidence(content, nonce, look_proof, approach_proof, proo
|
||||
return _validate(content,nonce,look_proof,approach_proof,proof)
|
||||
|
||||
|
||||
def validate_facing_evidence(content, nonce, look_proof, approach_proof, proof):
|
||||
return _validate(content,nonce,look_proof,approach_proof,proof,facing=True)
|
||||
def validate_facing_evidence(content, nonce, look_proof, approach_proof, proof, *, profile="standard"):
|
||||
return _validate(content,nonce,look_proof,approach_proof,proof,facing=True,profile=profile)
|
||||
|
||||
|
||||
def _validate(content, nonce, look_proof, approach_proof, proof, facing=False):
|
||||
def _validate(content, nonce, look_proof, approach_proof, proof, facing=False, profile="standard"):
|
||||
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}
|
||||
if facing:
|
||||
result['scope']='stationary yaw facing the original gate with exact disabled door and available lock diagnostics; lock selection, held action and escape remain unverified'
|
||||
result['lock_selection_verified']=False
|
||||
try:
|
||||
need(type(profile) is str and profile in ('standard','coarse') and (facing or profile == 'standard'), 'Unknown expected facing profile')
|
||||
need(type(proof) is dict and proof.get('profile','standard') == profile, 'Facing profile differs from explicit verifier capability')
|
||||
need(profile != 'coarse' or approach_proof.get('profile') == 'coarse', 'Coarse facing lacks coarse approach prerequisite')
|
||||
max_pixels, max_angle = (384,35) if profile == 'coarse' else (128,15)
|
||||
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)
|
||||
@@ -28,7 +32,7 @@ def _validate(content, nonce, look_proof, approach_proof, proof, facing=False):
|
||||
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')
|
||||
if facing:
|
||||
need(set(proof)=={'requested','completed','nonce','geometry','started_monotonic','finished_monotonic','deadline_monotonic','initial_sample','final_sample','facing_samples','actions','motions','samples'},'unexpected facing proof fields or lock-selection claim')
|
||||
need(set(proof)==({'requested','completed','nonce','geometry','started_monotonic','finished_monotonic','deadline_monotonic','initial_sample','final_sample','facing_samples','actions','motions','samples'} | ({'profile'} if 'profile' in proof else set())),'unexpected facing proof fields or lock-selection claim')
|
||||
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')
|
||||
if facing:
|
||||
@@ -80,15 +84,15 @@ def _validate(content, nonce, look_proof, approach_proof, proof, facing=False):
|
||||
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(dx) is int and 1<=abs(dx)<=max_pixels 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')
|
||||
need(abs(wanted)>3 and dx*gain*wanted>0 and .05<=abs(change)<=max_angle 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(abs(d)<=max_angle 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)
|
||||
|
||||
@@ -32,12 +32,16 @@ def perform_alignment(guard, require_idle, mouse, observe, geometry, calibration
|
||||
return _perform_alignment(guard, require_idle, mouse, observe, geometry, calibration, approach_evidence, report, clock=clock, sleep=sleep, notify=notify)
|
||||
|
||||
|
||||
def perform_facing(guard, require_idle, mouse, observe, geometry, calibration, approach_evidence, report, *, clock, sleep, notify):
|
||||
def perform_facing(guard, require_idle, mouse, observe, geometry, calibration, approach_evidence, report, *, clock, sleep, notify, profile="standard"):
|
||||
"""Separate facing-only outcome; requires exact disabled-door/lock diagnostics."""
|
||||
return _perform_alignment(guard, require_idle, mouse, observe, geometry, calibration, approach_evidence, report, clock=clock, sleep=sleep, notify=notify, facing=True)
|
||||
return _perform_alignment(guard, require_idle, mouse, observe, geometry, calibration, approach_evidence, report, clock=clock, sleep=sleep, notify=notify, facing=True, profile=profile)
|
||||
|
||||
|
||||
def _perform_alignment(guard, require_idle, mouse, observe, geometry, calibration, approach_evidence, report, *, clock, sleep, notify, facing=False):
|
||||
def _perform_alignment(guard, require_idle, mouse, observe, geometry, calibration, approach_evidence, report, *, clock, sleep, notify, facing=False, profile="standard"):
|
||||
need(type(profile) is str and profile in ('standard', 'coarse') and (facing or profile == 'standard'), 'Unknown or non-facing coarse profile')
|
||||
coarse = profile == 'coarse'
|
||||
need(not coarse or (report.get('coarse_facing_requested') is True and report.get('coarse_approach_requested') is True and report.get('selection_requested') is True), 'Coarse facing requires explicit matching capabilities')
|
||||
max_pixels, desired_angle, max_angle = (384, 25., 35.) if coarse else (128, 10., 15.)
|
||||
proof_key = 'facing_proof' if facing else 'alignment_proof'
|
||||
event_prefix = 'facing' if facing else 'alignment'
|
||||
need(proof_key not in report, 'Alignment attempt already exists')
|
||||
@@ -48,6 +52,7 @@ def _perform_alignment(guard, require_idle, mouse, observe, geometry, calibratio
|
||||
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')
|
||||
need(not coarse or approach.get('profile') == 'coarse', 'Coarse facing requires the accepted coarse 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'])
|
||||
@@ -70,9 +75,10 @@ def _perform_alignment(guard, require_idle, mouse, observe, geometry, calibratio
|
||||
'initial_sample': None, 'final_sample': None, 'selection_samples': [], 'motions': [], 'samples': []}
|
||||
report[proof_key] = state
|
||||
if facing:
|
||||
if coarse: state['profile'] = profile
|
||||
state['facing_samples'] = []
|
||||
del state['selection_samples']
|
||||
previous = None; active_turn = None; requests = 0
|
||||
previous = None; active_turn = None; requests = 0; pixels = 0
|
||||
|
||||
def neutral(s):
|
||||
return abs(s['forward']) <= .01 and length(s['input']) <= .01 and length(s['velocity']) <= .05
|
||||
@@ -109,7 +115,7 @@ def _perform_alignment(guard, require_idle, mouse, observe, geometry, calibratio
|
||||
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')
|
||||
need(abs(change) <= max_angle, 'Native yaw exceeded alignment turn bound')
|
||||
if abs(change) >= .05:
|
||||
need(change*sign > 0, 'Native yaw reversed alignment request')
|
||||
if previous:
|
||||
@@ -169,12 +175,20 @@ def _perform_alignment(guard, require_idle, mouse, observe, geometry, calibratio
|
||||
while clock() < deadline:
|
||||
pair = selection_pair()
|
||||
if pair:
|
||||
current = read()
|
||||
pair = selection_pair()
|
||||
if not pair:
|
||||
continue
|
||||
if not facing:
|
||||
current = read()
|
||||
pair = selection_pair()
|
||||
if not pair:
|
||||
continue
|
||||
# Facing already has a fully consumed, guarded stable pair.
|
||||
# Return its exact span; selection still consumes all later
|
||||
# history and rechecks ownership before any wheel request.
|
||||
require_idle()
|
||||
need(clock() < deadline, 'Frozen alignment deadline exceeded at completion')
|
||||
completed_at = clock()
|
||||
if facing:
|
||||
need(started <= completed_at and 0 <= completed_at-state['samples'][-1]['received_monotonic'] <= .5,
|
||||
'Final native alignment observation is stale or clock reversed')
|
||||
need(completed_at < deadline, 'Frozen alignment deadline exceeded at completion')
|
||||
state['facing_samples' if facing else 'selection_samples'] = pair; state['completed'] = True
|
||||
return state
|
||||
error = delta(bearing(current), yaw(current['direction']))
|
||||
@@ -191,9 +205,11 @@ def _perform_alignment(guard, require_idle, mouse, observe, geometry, calibratio
|
||||
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))))
|
||||
desired = max(-desired_angle, min(desired_angle, .75*error))
|
||||
dx = max(-max_pixels, min(max_pixels, int(round(desired/gain))))
|
||||
if dx == 0: dx = 1 if desired/gain > 0 else -1
|
||||
need(pixels+abs(dx) <= 768, 'Alignment cumulative pixel budget exhausted')
|
||||
pixels += abs(dx) # Retire the attempted input before any uncertain native write.
|
||||
active_turn = (yaw(before['direction']), dx*gain)
|
||||
mouse.move(dx, 0, guard, deadline=deadline)
|
||||
requests += 1
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
"""Dormant independent raw-console held-F verifier. Never sends input.
|
||||
|
||||
Primary installed contracts: ScriptedUserAction.GetActionProgressScript adds
|
||||
OnPostFrame timeSlice and returns seconds; SCR_InteractionHandlerComponent resets
|
||||
progress in the same frame as DoPerformObjectAction; BaseWorld.GetWorldTime is
|
||||
milliseconds. INPUT already records that world clock. Exact sampler/post-frame
|
||||
ordering is not documented: timing checks conservatively allow ONE observed
|
||||
adjacent world-sample interval plus2ms print rounding at each boundary, and
|
||||
record that inference. They never replace duration with a guessed near4 cutoff
|
||||
or wall-clock sleep. The source diagnostic skip-duration path would fail the
|
||||
normal-duration timing checks. Full native object pins come from the unchanged
|
||||
nonce-bound observer/source provenance; geometry alone cannot prove identity.
|
||||
|
||||
Earlier look/approach/facing/selection spans are revalidated without relaxing any
|
||||
old validator. Only this held span permits phase2 and moving original-door
|
||||
geometry. The runner still must verify exact process/display/source ownership,
|
||||
actual F-only adapter requests, hard supervision and native keymap release.
|
||||
"""
|
||||
import math
|
||||
import re
|
||||
from ordinary_gate_choices import join_choice_snapshots, exact_prison_choices
|
||||
from prison_action_selection_evidence import validate_selection_evidence
|
||||
from prison_approach_evidence import number, need, yaw, angle, norm
|
||||
|
||||
SCOPE='one original local prison lock hold, authoritative phase transition and physical door response; no passage, full escape, multiplayer or FPS qualification'
|
||||
|
||||
|
||||
def validate_held_action_evidence(content, nonce, look_proof, approach_proof, facing_proof, selection_proof, proof, *, facing_profile='standard'):
|
||||
result={'verified':False,'held_action_verified':False,'escape_verified':False,'errors':[],
|
||||
'scope':SCOPE,'unlock_observed':False,'gate_motion_observed':False,
|
||||
'timing_basis':'native world milliseconds; one adjacent sampled world interval plus2ms allowance for undocumented post-frame/sampler ordering',
|
||||
'adapter_release_confirmation_verified':False}
|
||||
try:
|
||||
selected=validate_selection_evidence(content,nonce,look_proof,approach_proof,facing_proof,selection_proof,facing_profile=facing_profile)
|
||||
need(selected['verified'],'prior independent selection failed: '+str(selected['errors']))
|
||||
required={'requested','completed','status','input_attempted','unlock_observed','gate_motion_observed','release_confirmed',
|
||||
'unconfirmed_held_key','paired_release_errors','notification_errors','events','samples','progress_samples',
|
||||
'transition_sample','control_sample','motion_sample','released_sample','initial_sample','final_sample',
|
||||
'started_monotonic','finished_monotonic','press_attempted_monotonic','down_monotonic','up_monotonic',
|
||||
'press_sample','original_deadline_monotonic','held_deadline_monotonic','nonce','geometry','actions',
|
||||
'selection_final_sample','release_barrier_sample'}
|
||||
need(type(proof) is dict and set(proof)==required,'unknown or missing held proof fields')
|
||||
need(proof['requested'] is True and proof['completed'] is True and proof['status']=='verified' and proof['nonce']==nonce and
|
||||
proof['input_attempted'] is True and proof['release_confirmed'] is True and proof['unlock_observed'] is True and
|
||||
proof['gate_motion_observed'] is True and proof['unconfirmed_held_key'] is None and
|
||||
proof['paired_release_errors']==[] and proof['notification_errors']==[], 'held proof did not complete cleanly')
|
||||
data=join_choice_snapshots(content,nonce);frames=data['snapshots']
|
||||
first=proof['initial_sample'];last=proof['final_sample'];anchor=selected['last_sample'];before=proof['press_sample']
|
||||
need(all(type(i) is int for i in (first,last,anchor,before)) and first==anchor==proof['selection_final_sample'] and first<before<last<=450,
|
||||
'held span or original selection handoff invalid')
|
||||
need(proof['geometry']==selection_proof['geometry'] and proof['actions']==selection_proof['actions'],'held geometry/actions replaced selection binding')
|
||||
refs=proof['samples'];need(type(refs) is list and 2<=len(refs)<=450,'held native references missing')
|
||||
need([x['sample'] for x in refs]==list(range(first,last+1)),'held native references omitted, duplicated or reordered')
|
||||
start=number(proof['started_monotonic']);finish=number(proof['finished_monotonic']);deadline=number(proof['original_deadline_monotonic'])
|
||||
attempt=number(proof['press_attempted_monotonic']);down=number(proof['down_monotonic']);up=number(proof['up_monotonic']);lease=number(proof['held_deadline_monotonic'])
|
||||
need(number(selection_proof['finished_monotonic'])<=start<=attempt<=down<up<finish<deadline and up<lease<=attempt+5 and lease<=deadline,
|
||||
'held request, release or completion exceeded immutable deadline')
|
||||
need(down+6<=deadline,'actual F request lacked six seconds of original observation budget')
|
||||
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 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
|
||||
anchor_ref=selection_proof['samples'][-1]
|
||||
anchor_end=number(anchor_ref['received_monotonic'])+(queued+45000-number(anchor_ref['real_ms']))/1000-.5
|
||||
need(deadline<=min(original_host_end,anchor_end),'held stage renewed original observation clock')
|
||||
events=proof['events'];need(type(events) is list and len(events)==2,'expected one F down and one F up only')
|
||||
for e,is_down,at in zip(events,(True,False),(down,up)):
|
||||
keys={'key','down','attempted_monotonic','confirmed','monotonic'} | (set() if is_down else {'cleanup'})
|
||||
need(type(e) is dict and set(e)==keys and e['key']=='F' and e['down'] is is_down and e['confirmed'] is True and
|
||||
number(e['monotonic'])==at and number(e['attempted_monotonic'])<=at and
|
||||
(is_down or e['cleanup'] is False),'unexpected key, retry, cleanup or unconfirmed F event')
|
||||
need(events[0]['attempted_monotonic']==attempt and down<=number(events[1]['attempted_monotonic'])<lease,'F attempted-event clocks disagree')
|
||||
receipts={};prior_receipt=-1
|
||||
for r in refs:
|
||||
i=r['sample'];need(i in frames and type(r['real_ms']) is int and r['real_ms']==frames[i]['real_ms'],'held reference differs from native clock')
|
||||
received=number(r['received_monotonic']);need(0<=received<=finish and received>=prior_receipt,'held receipts reversed or unbounded')
|
||||
if 'world_ms' in r:need(number(r['world_ms'])==frames[i]['input']['world_ms'],'held world reference differs from native clock')
|
||||
receipts[i]=received;prior_receipt=received
|
||||
need(refs[0]['real_ms']==anchor_ref['real_ms'] and refs[0]['received_monotonic']==anchor_ref['received_monotonic'],'original selection receipt changed at held handoff')
|
||||
need(0<=finish-receipts[last]<=.5 and receipts[before]<=attempt and down-receipts[before]<=.5,'held input/final native evidence is stale')
|
||||
for cancellation in (data['cancelled'],data['look_cancelled']):
|
||||
need(cancellation is None or cancellation['sample']>last,'original native observer cancelled within held span')
|
||||
body=frames[anchor]['input']['origin'];direction=yaw(frames[anchor]['look']['direction']);geometry=proof['geometry'];actions=proof['actions']
|
||||
need(actions[1]['id']==selected['selected_action_id'],'original selected lock ID differs')
|
||||
progress=[];prior_progress=0.;pressed_seen=False;transition=None;control=None;motion=None;previous=None;last_positive=None
|
||||
phase=1;allowance_max=0.;world_before=frames[before]['input']['world_ms']
|
||||
# Check the two fresh, selected pre-input native samples themselves.
|
||||
need(before-1>=first and 100<=frames[before]['real_ms']-frames[before-1]['real_ms']<=500,'F lacks a consecutive admission pair')
|
||||
for i in range(first,last+1):
|
||||
need(i in frames and 'choices' in frames[i],'held raw join/choice missing')
|
||||
s=frames[i];a=s['action'];g=s['geometry'];native=s['input'];view=s['look']
|
||||
need(queued<=s['real_ms']<queued+45000 and native['inventory'] is False,'held native lifetime/menu changed')
|
||||
need(a['duration']==4 and 0<=a['progress']<=4,'original native four-second action duration/progress changed')
|
||||
need(math.dist(body,native['origin'])<=.05 and abs(native['origin'][0]-geometry['prison'][0])<=5.25 and abs(native['origin'][2]-geometry['prison'][2])<=5.25 and
|
||||
abs(native['forward'])<=.01 and norm(native['input'])<=.01 and norm(native['velocity'])<=.05 and norm(view['turn'])<=.01 and
|
||||
abs(angle(yaw(view['direction']),direction))<=.2,'held body/view/movement changed')
|
||||
need(a['phase'] in (1,2) and a['phase']>=phase,'held native phase reversed or advanced beyond RADIO_SEARCH')
|
||||
need(math.dist(g['prison'],geometry['prison'])<=.01,'original prison geometry changed')
|
||||
if previous:
|
||||
need(0<s['real_ms']-previous['real_ms']<=500,'held native observation gap')
|
||||
world_delta=(native['world_ms']-previous['input']['world_ms'])/1000
|
||||
need(world_delta>=0,'native world clock reversed')
|
||||
adjacent=world_delta
|
||||
if i-2>=first:adjacent=max(adjacent,(previous['input']['world_ms']-frames[i-2]['input']['world_ms'])/1000)
|
||||
else:world_delta=0.;adjacent=0.
|
||||
tolerance=adjacent+.002;allowance_max=max(allowance_max,tolerance)
|
||||
if a['phase']==1:
|
||||
for name in ('gate','context'):need(math.dist(g[name],geometry[name])<=.01,'closed original door/context geometry changed')
|
||||
need(math.dist(g['gate_forward'],geometry['gate_forward'])<=.001 and abs(g['radius']-geometry['radius'])<=.001 and g['door_state']==0 and g['door_control']==0,
|
||||
'closed gate changed before accepted action transition')
|
||||
c=exact_prison_choices(s)
|
||||
need(c['actions']==actions and c['selected_id']==actions[1]['id'] and c['selected_class']==actions[1]['class'] and a['selected'] and a['context_selected'] and a['cached_performable'] and a['progress_bound'],
|
||||
'held original lock selection/permission/binding lost')
|
||||
if i<=before:
|
||||
need(a['pressed']==0 and a['progress']==0 and not a['performing'],'held action appeared before own F request')
|
||||
else:
|
||||
need(receipts[i]>=down,'native action observation predates actual F request')
|
||||
if a['pressed']>0:pressed_seen=True
|
||||
if a['progress']>0:
|
||||
need(a['performing'] and a['pressed']>0 and pressed_seen and a['progress']>=prior_progress,'native held action interrupted or progress decreased')
|
||||
need(a['progress']<=4 and a['progress']<=(native['world_ms']-world_before)/1000+tolerance,
|
||||
'native action progress exceeds available world time')
|
||||
need(a['progress']-prior_progress<=world_delta+tolerance,'native action progress jumped beyond adjacent world interval')
|
||||
if a['progress']>prior_progress:progress.append(i)
|
||||
prior_progress=a['progress'];last_positive=i
|
||||
elif prior_progress>0:raise ValueError('native action progress reset before phase transition')
|
||||
if prior_progress>0:need(a['performing'] and a['pressed']>0,'native action interrupted before phase transition')
|
||||
else:
|
||||
need(i>before and receipts[i]>=down and pressed_seen and len(progress)>=2 and last_positive is not None,'phase2 lacks advancing bound progress from this F attempt')
|
||||
if transition is None:
|
||||
remaining=(native['world_ms']-frames[last_positive]['input']['world_ms'])/1000
|
||||
need(prior_progress+remaining+tolerance>=4 and (native['world_ms']-world_before)/1000+tolerance>=4,
|
||||
'phase2 occurs before four seconds of reachable native action progress')
|
||||
transition=i;result['unlock_observed']=True
|
||||
# Raw parser and native original-object pins still apply. Only
|
||||
# closed geometry and selected/progress cache may now change.
|
||||
if abs(g['door_control'])>.001 and control is None:control=i
|
||||
if abs(g['door_state'])>.001 and motion is None:motion=i
|
||||
phase=a['phase'];previous=s
|
||||
need(transition is not None and control is not None and motion is not None,'original door transition/control/physical motion missing')
|
||||
result['gate_motion_observed']=True
|
||||
need(receipts[transition]<=number(events[1]['attempted_monotonic'])<=up,'normal F release preceded observed authoritative transition')
|
||||
need(proof['progress_samples']==progress and proof['transition_sample']==transition and proof['control_sample']==control and proof['motion_sample']==motion,
|
||||
'helper progress/transition/door references differ from raw observations')
|
||||
barrier=proof['release_barrier_sample'];released=proof['released_sample']
|
||||
need(type(barrier) is int and type(released) is int and transition<=barrier<released==last and barrier in receipts and released in receipts and
|
||||
receipts[barrier]>=up and receipts[released]>=up,'release lacks post-keyup drain and newer native acknowledgement')
|
||||
a=frames[released]['action'];need(a['phase']==2 and a['pressed']==0 and a['progress']==0 and not a['performing'],'native released action input not observed at final sample')
|
||||
acknowledged=False
|
||||
for i in range(barrier+1,last+1):
|
||||
action=frames[i]['action'];neutral=action['pressed']==0 and action['progress']==0 and not action['performing']
|
||||
if acknowledged:need(neutral,'native action input renewed after release acknowledgement')
|
||||
if receipts[i]>=up and neutral:acknowledged=True
|
||||
need(acknowledged,'newer native release acknowledgement missing')
|
||||
need(any(receipts[i]>=down and frames[i]['action']['pressed']>0 for i in range(before+1,transition)), 'native PerformAction acknowledgement missing')
|
||||
result.update(verified=True,held_action_verified=True,first_sample=first,last_sample=last,transition_sample=transition,
|
||||
control_sample=control,motion_sample=motion,released_sample=released,selected_action_id=actions[1]['id'],
|
||||
native_world_duration_ms=frames[transition]['input']['world_ms']-world_before,
|
||||
maximum_timing_allowance_seconds=allowance_max,
|
||||
release_confirmation_scope='actual native released input verified; native keymap/FFI release attestation remains separate runner responsibility')
|
||||
except (ValueError,KeyError,TypeError,OverflowError,IndexError) as error:
|
||||
result['errors'].append(str(error))
|
||||
return result
|
||||
@@ -0,0 +1,267 @@
|
||||
"""Dormant one-hold prison policy; no OS, input backend or native action calls.
|
||||
|
||||
perform_held_action accepts only explicit adapters:
|
||||
* guard.check(): original private window/focus/process/display ownership check.
|
||||
* require_idle(): native all-key/button idle check, used before pressing only.
|
||||
* key.down_f(guard, *, deadline, not_after) -> exactly
|
||||
{key:'F', down_monotonic:actual_request_time}. The adapter must freshly prove
|
||||
the SAME owner/connection and idle state immediately before the request, and
|
||||
refuse after not_after (which includes six-second admission and pose freshness).
|
||||
* key.up_f(guard, *, deadline, cleanup=False) -> exactly
|
||||
{key:'F', up_monotonic:actual_request_time, released:True}. The adapter freshly
|
||||
proves the same owner before key-up and confirms native keymap idle afterward.
|
||||
cleanup=True may release an uncertain F after expiry ONLY through the same
|
||||
freshly proven connection; it cannot renew the lease or qualify a late result.
|
||||
No adapter exposes another key, wheel, mouse, action invocation or pose setter.
|
||||
* notify('held-key-pending', key='F', deadline_monotonic=...) MUST establish an
|
||||
independent hard supervisor before any uncertain down. Its maximum lease is
|
||||
five seconds from that attempt, further shortened by the frozen observation
|
||||
deadline. 'held-key-release-confirmed' clears only that lease. The wrapper owns
|
||||
failure containment; this pure module cannot guarantee release after lost
|
||||
ownership and retains uncertainty rather than redirecting input.
|
||||
* observe(): all new consecutive native INPUT/LOOK/GATE/CHOICE joined snapshots,
|
||||
or an unchanged latest sample. Begin with the committed selection final sample.
|
||||
Keep original receipts; reject missing BASIS, nonce/world/body/life/account/
|
||||
camera/gate/action/context identity changes, menus or observer cancellation.
|
||||
Before phase2 snapshot fields match the selection adapter, plus turn=[x,y,z].
|
||||
After phase2 original observer object pins remain mandatory, but live door,
|
||||
context, gate and forward geometry may move; selected/permission/progress may
|
||||
disappear. Never substitute another native identity based on similar geometry.
|
||||
* calibration: nonce, observer_start_real_ms (QUEUED), ready_real_ms,
|
||||
ready_monotonic, optional observer_deadline_monotonic <= original bound.
|
||||
* selection_evidence is independently revalidated raw one-wheel evidence:
|
||||
verified, last_sample, selected_action_id, wheel_button=5. report must retain
|
||||
action_selection_proof and its full in-memory native samples/immutable refs.
|
||||
|
||||
Only six seconds of genuinely remaining ORIGINAL45s time admits a press. The
|
||||
previous facing/selection8s lease is closed, never renewed or borrowed. One F
|
||||
attempt only, <=5s hard lease, .75s reserved for guarded release, two advancing
|
||||
bound native progress samples, monotonic phase1->2, separate original-door
|
||||
control/physical motion, and native released-input observation are required.
|
||||
No exact4.0 progress sample is required: native completion can reset in one frame.
|
||||
Results distinguish refused-no-input, failed-after-input, unlock-observed-motion-
|
||||
unverified, and verified. Even verified means this local action/door observation
|
||||
only, not passage through the gate, a full escape, multiplayer or FPS evidence.
|
||||
Importing this file does nothing; no production/helper/runner activates it.
|
||||
"""
|
||||
import copy
|
||||
import math
|
||||
from prison_approach_proof import number, vector, length, yaw, delta, inside, target_for
|
||||
from prison_action_selection_proof import choices_for
|
||||
|
||||
|
||||
class Refused(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def need(value, message):
|
||||
if not value:
|
||||
raise Refused(message)
|
||||
|
||||
|
||||
def perform_held_action(guard, require_idle, key, observe, geometry, calibration, selection_evidence, report, *, clock, sleep, notify):
|
||||
need('held_action_proof' not in report, 'Held action attempt already exists')
|
||||
state = {'requested':True,'completed':False,'status':'refused-no-input',
|
||||
'input_attempted':False,'unlock_observed':False,'gate_motion_observed':False,
|
||||
'release_confirmed':False,'unconfirmed_held_key':None,'paired_release_errors':[],
|
||||
'notification_errors':[],'events':[],'samples':[],'progress_samples':[],'transition_sample':None,
|
||||
'control_sample':None,'motion_sample':None,'released_sample':None,
|
||||
'initial_sample':None,'final_sample':None,'started_monotonic':clock(),
|
||||
'finished_monotonic':None,'press_attempted_monotonic':None,
|
||||
'down_monotonic':None,'up_monotonic':None,'press_sample':None,
|
||||
'original_deadline_monotonic':None,'held_deadline_monotonic':None}
|
||||
report['held_action_proof'] = state
|
||||
held = False; down = None; previous = None; phase = 1; progress = 0.; pressed_ack = None
|
||||
deadline = None; lease = None; release_due = None; release_attempts = 0; release_barrier = None
|
||||
|
||||
def release(cleanup=False):
|
||||
nonlocal held, release_attempts
|
||||
if not held: return
|
||||
need(release_attempts < 2, 'Bounded F release attempts exhausted')
|
||||
release_attempts += 1
|
||||
event = {'key':'F','down':False,'attempted_monotonic':clock(),'cleanup':cleanup,'confirmed':False}
|
||||
state['events'].append(event)
|
||||
guard.check() # Expiry never excuses original ownership, even in cleanup.
|
||||
result = key.up_f(guard, deadline=lease, cleanup=cleanup)
|
||||
need(type(result) is dict and set(result)=={'key','up_monotonic','released'} and
|
||||
result['key']=='F' and result['released'] is True, 'Native F release is unconfirmed')
|
||||
up = number(result['up_monotonic']); returned = clock()
|
||||
need(event['attempted_monotonic'] <= up <= returned, 'Native F release timestamps invalid')
|
||||
event.update(monotonic=up, confirmed=True)
|
||||
state['up_monotonic']=up; state['release_confirmed']=True
|
||||
state['unconfirmed_held_key']=None; held=False
|
||||
notify({'kind':'held-key-release-confirmed','key':'F'})
|
||||
if not cleanup:
|
||||
need(returned < lease and returned < deadline, 'F release exceeded immutable lease')
|
||||
|
||||
try:
|
||||
pinned = copy.deepcopy(geometry); _, prison = target_for(pinned)
|
||||
state['nonce']=pinned['nonce']; state['geometry']=pinned
|
||||
selected = report.get('action_selection_proof', {})
|
||||
need(type(selection_evidence) is dict and selection_evidence.get('verified') is True and
|
||||
selection_evidence.get('wheel_button')==5 and selected.get('completed') is True and
|
||||
selected.get('nonce')==pinned['nonce'] and selected.get('geometry')==pinned and
|
||||
selection_evidence.get('last_sample')==selected.get('final_sample'),
|
||||
'Independently accepted same-run one-wheel selection required')
|
||||
prior = selected.get('samples', [])
|
||||
need(type(prior) is list and len(prior)>=2 and prior[-1]['sample']==selected['final_sample'],
|
||||
'Original full selection samples unavailable')
|
||||
anchor = copy.deepcopy(prior[-1]); body=vector(anchor['origin']); view=yaw(anchor['direction'])
|
||||
actions = choices_for(anchor,pinned,selected_lock=True)
|
||||
need(selected.get('actions')==actions and type(selection_evidence.get('selected_action_id')) is int and
|
||||
selection_evidence['selected_action_id']==actions[1]['id'], 'Original selected lock identity mismatch')
|
||||
need(selected.get('selection_samples')==[prior[-2]['sample'],anchor['sample']] and
|
||||
anchor['sample']==prior[-2]['sample']+1 and 100<=anchor['real_ms']-prior[-2]['real_ms']<=500,
|
||||
'Committed native selection pair missing')
|
||||
choices_for(prior[-2],pinned,selected_lock=True)
|
||||
queued=number(calibration['observer_start_real_ms']); ready=number(calibration['ready_real_ms'])
|
||||
ready_at=number(calibration['ready_monotonic']); started=state['started_monotonic']
|
||||
need(calibration.get('nonce')==pinned['nonce'] and 0<=queued and 2000<=ready-queued<45000 and
|
||||
0<=ready_at<=started, 'Original QUEUED/READY binding invalid')
|
||||
original=ready_at+(45000-(ready-queued))/1000-.5
|
||||
supplied=number(calibration.get('observer_deadline_monotonic',original))
|
||||
need(supplied<=original, 'Supplied observation deadline renews original lifetime')
|
||||
deadline=min(original,supplied,number(anchor['received_monotonic'])+(queued+45000-number(anchor['real_ms']))/1000-.5)
|
||||
state['original_deadline_monotonic']=deadline;state['actions']=actions
|
||||
state['selection_final_sample']=anchor['sample']
|
||||
need(started<deadline, 'Original observation lifetime expired')
|
||||
|
||||
def consume(raw):
|
||||
nonlocal previous, phase, progress, pressed_ack
|
||||
s=copy.deepcopy(raw); index=s['sample'];real=number(s['real_ms']);receipt=number(s['received_monotonic'])
|
||||
need(type(index) is int and anchor['sample']<=index<=450 and queued<=real<queued+45000 and
|
||||
0<=receipt<=clock(), 'Held native index/clock invalid')
|
||||
if previous:
|
||||
need(index>=previous['sample'] and real>=previous['real_ms'] and receipt>=previous['received_monotonic'],
|
||||
'Held native samples or receipts reversed')
|
||||
if index==previous['sample']:
|
||||
need(s==previous, 'Repeated held sample changed')
|
||||
return s
|
||||
need(index==previous['sample']+1 and 0<real-previous['real_ms']<=500, 'Held native history has a gap')
|
||||
else: need(index==anchor['sample'], 'Held handoff omitted selection final sample')
|
||||
for name in ('origin','camera','direction','body_forward','input','velocity','turn'):vector(s[name])
|
||||
need(inside(s['origin'],prison) and math.dist(body,s['origin'])<=.05 and
|
||||
abs(number(s['forward']))<=.01 and length(s['input'])<=.01 and length(s['velocity'])<=.05 and
|
||||
length(s['turn'])<=.01 and abs(delta(yaw(s['direction']),view))<=.2 and s['inventory'] is False,
|
||||
'Original stationary inside-prison body/view/input changed')
|
||||
need(type(s['phase']) is int and s['phase'] in (1,2) and s['phase']>=phase,
|
||||
'Held phase reversed or left PRISON/RADIO_SEARCH')
|
||||
for flag in ('selected','context_selected','cached_performable','progress_bound','performing'):
|
||||
need(type(s[flag]) is bool, 'Malformed held action flag')
|
||||
value=number(s['progress']);pressed=number(s['pressed']);door=number(s['door_state']);control=number(s['door_control'])
|
||||
need(0<=value<=4 and 0<=pressed<=1 and number(s['duration'])==4 and -1<=door<=1 and -1<=control<=1,
|
||||
'Invalid native action/door observation')
|
||||
geo=s['geometry'];need(type(geo) is dict and set(geo)==set(pinned) and geo['nonce']==pinned['nonce'] and
|
||||
geo['basis']==pinned['basis'] and math.dist(vector(geo['prison']),prison)<=.01,
|
||||
'Original native gate geometry binding changed')
|
||||
for name in ('gate','gate_forward','context'):vector(geo[name])
|
||||
need(.5<=length(geo['gate_forward'])<=2 and 0<number(geo['radius'])<=10,'Invalid live original door geometry')
|
||||
if s['phase']==1:
|
||||
need(geo==pinned and door==0 and control==0, 'Closed gate changed before accepted transition')
|
||||
observed=choices_for(s,pinned,selected_lock=True)
|
||||
need(observed==actions, 'Original selected action identities changed')
|
||||
if down is None or index<=state['press_sample']:
|
||||
need(pressed==0 and value==0 and not s['performing'], 'Action/input appeared before this F attempt')
|
||||
else:
|
||||
need(receipt>=down, 'Action sample predates actual F request')
|
||||
if pressed>0: pressed_ack=index
|
||||
if value>0:
|
||||
need(s['performing'] and pressed>0 and pressed_ack is not None, 'Progress lacks bound native input/performing proof')
|
||||
need(value>=progress, 'Native action progress decreased before transition')
|
||||
if value>progress: state['progress_samples'].append(index)
|
||||
progress=value
|
||||
elif progress>0:
|
||||
raise Refused('Native action progress reset before transition')
|
||||
if progress>0:need(s['performing'] and pressed>0,'Native action interrupted before transition')
|
||||
else:
|
||||
need(down is not None and index>state['press_sample'] and receipt>=down and
|
||||
pressed_ack is not None and len(state['progress_samples'])>=2,
|
||||
'Phase transition lacks advancing bound progress from this F hold')
|
||||
if phase==1:
|
||||
state['transition_sample']=index;state['unlock_observed']=True
|
||||
state['status']='unlock-observed-motion-unverified'
|
||||
if abs(control)>.001 and state['control_sample'] is None:state['control_sample']=index
|
||||
if abs(door)>.001 and state['motion_sample'] is None:state['motion_sample']=index
|
||||
state['gate_motion_observed']=state['control_sample'] is not None and state['motion_sample'] is not None
|
||||
# After the full post-keyup drain, every subsequent native sample
|
||||
# is evidence. A neutral observation cannot be reused after renewed
|
||||
# action input, even if the reversal shares one buffered batch.
|
||||
if state['released_sample'] is not None:
|
||||
need(pressed==0 and value==0 and not s['performing'], 'Native action input renewed after release acknowledgement')
|
||||
if (release_barrier is not None and index>release_barrier and receipt>=state['up_monotonic'] and
|
||||
pressed==0 and value==0 and not s['performing']):
|
||||
state['released_sample']=index
|
||||
phase=s['phase'];previous=s
|
||||
state['samples'].append(copy.deepcopy(s));state['final_sample']=index
|
||||
if state['initial_sample'] is None:state['initial_sample']=index
|
||||
return s
|
||||
|
||||
def read(idle=False):
|
||||
need(clock()<deadline, 'Original held observation deadline expired')
|
||||
guard.check()
|
||||
if idle:require_idle()
|
||||
batch=observe();need(type(batch) is list and 1<=len(batch)<=450,'Missing ordered held snapshots')
|
||||
for raw in batch:current=consume(raw)
|
||||
need(0<=clock()-current['received_monotonic']<=.5 and clock()<deadline,'Held native evidence stale or expired')
|
||||
return current
|
||||
|
||||
notify({'kind':'held-action-pending','deadline_monotonic':deadline})
|
||||
current=read(idle=True)
|
||||
while True:
|
||||
samples=state['samples']
|
||||
if len(samples)>=2 and samples[-1]['sample']==samples[-2]['sample']+1 and 100<=samples[-1]['real_ms']-samples[-2]['real_ms']<=500:
|
||||
break
|
||||
need(clock()+6<=deadline,'held-action-not-started: insufficient-original-budget')
|
||||
sleep(.025);current=read(idle=True)
|
||||
guard.check();require_idle()
|
||||
attempted=clock()
|
||||
not_after=min(deadline-6,current['received_monotonic']+.5)
|
||||
need(attempted<=not_after,'held-action-not-started: insufficient-original-budget-or-stale-pose')
|
||||
lease=min(deadline,attempted+5);release_due=lease-.75
|
||||
state.update(press_attempted_monotonic=attempted,held_deadline_monotonic=lease,press_sample=current['sample'],
|
||||
input_attempted=True,status='failed-after-input',unconfirmed_held_key='F')
|
||||
held=True
|
||||
event={'key':'F','down':True,'attempted_monotonic':attempted,'confirmed':False}
|
||||
state['events'].append(event)
|
||||
notify({'kind':'held-key-pending','key':'F','deadline_monotonic':lease})
|
||||
result=key.down_f(guard,deadline=lease,not_after=not_after)
|
||||
need(type(result) is dict and set(result)=={'key','down_monotonic'} and result['key']=='F','Unexpected held key adapter result')
|
||||
down=number(result['down_monotonic'])
|
||||
need(attempted<=down<=clock() and down<=not_after and clock()<release_due,'Actual F request exceeded admission/lease')
|
||||
event.update(monotonic=down,confirmed=True);state['down_monotonic']=down
|
||||
while True:
|
||||
if held and clock()>=release_due:
|
||||
raise Refused('Completion not observed before guarded F release reserve')
|
||||
current=read()
|
||||
if state['release_confirmed']:
|
||||
# Drain the entire first post-keyup batch before accepting a
|
||||
# newer native input acknowledgement. Never credit buffered
|
||||
# completion/reset samples merely received after our release.
|
||||
if release_barrier is None:
|
||||
release_barrier=current['sample'];state['release_barrier_sample']=release_barrier
|
||||
elif current['sample']>release_barrier and current['received_monotonic']>=state['up_monotonic'] and current['pressed']==0 and not current['performing'] and current['progress']==0:
|
||||
state['released_sample']=current['sample']
|
||||
if state['unlock_observed'] and held:release()
|
||||
if (state['unlock_observed'] and state['gate_motion_observed'] and state['release_confirmed'] and
|
||||
state['released_sample']==current['sample'] and current['pressed']==0 and current['progress']==0 and not current['performing']):
|
||||
state['completed']=True;state['status']='verified'
|
||||
return state
|
||||
sleep(min(.025,max(0,deadline-clock())))
|
||||
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))
|
||||
if state['unlock_observed'] and state['gate_motion_observed'] and not state['completed']:
|
||||
state['status']='failed-after-input'
|
||||
state['finished_monotonic']=clock()
|
||||
try:
|
||||
notify({'kind':'held-action-finished','successful':state['completed']})
|
||||
except Exception as error:
|
||||
state['notification_errors'].append(str(error))
|
||||
if 'error' not in state:
|
||||
state['completed']=False;state['status']='failed-after-input' if state['input_attempted'] else 'refused-no-input'
|
||||
state['error']=str(error)
|
||||
raise
|
||||
Reference in New Issue
Block a user