Add typed field-flight prototype and preserve native qualification failures
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_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_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()
|
||||
|
||||
+18
-3
@@ -641,6 +641,8 @@ def validate_input_exercise(a):
|
||||
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):
|
||||
refuse('Input exercise with gate choices requires explicit approach')
|
||||
if type(getattr(a, 'exercise_prison_coarse_approach', False)) is not bool or (getattr(a, 'exercise_prison_coarse_approach', False) and not getattr(a, 'exercise_prison_approach', False)):
|
||||
refuse('Coarse approach requires explicit prison approach')
|
||||
if getattr(a, 'exercise_prison_alignment', False) and not getattr(a, 'exercise_prison_approach', False):
|
||||
refuse('Prison lock alignment requires the explicit prison approach experiment')
|
||||
if getattr(a, 'approach_wide_display', False) and not getattr(a, 'exercise_prison_approach', False):
|
||||
@@ -880,7 +882,7 @@ 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):
|
||||
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):
|
||||
"""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':
|
||||
@@ -891,6 +893,8 @@ def input_exercise_command(command, root, compat, profile, executable, run_id, h
|
||||
if (not display_context or len(child) < 3 or child[:2] != [sys.executable, str(ownership)]
|
||||
or not re.fullmatch(r'[A-Za-z0-9]{1,32}', run_id)):
|
||||
refuse('Input exercise requires the existing scoped display wrapper and observer nonce')
|
||||
if type(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):
|
||||
refuse('Action selection requires explicit alignment')
|
||||
if type(alignment) is not bool or (alignment and not approach):
|
||||
@@ -935,6 +939,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_approach:
|
||||
context['coarse_approach_requested'] = True
|
||||
wrapper.insert(-1, '--enable-private-coarse-approach-proof')
|
||||
if alignment:
|
||||
context['alignment_requested'] = True
|
||||
context['alignment_module_sha256'] = alignment_module_hashes(root)
|
||||
@@ -976,6 +983,8 @@ def input_exercise_poll_failure(context):
|
||||
return None
|
||||
if report.get('approach_requested', False) != context.get('approach_requested', False):
|
||||
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 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 (
|
||||
@@ -1044,6 +1053,11 @@ def input_exercise_result(result, context, session, observation, display_result)
|
||||
refuse('Approach module sources changed or differ from launch hashes')
|
||||
if not approach_requested and report.get('approach_proof') is not None:
|
||||
refuse('Unexpected approach evidence in fixed input run')
|
||||
coarse_approach_requested = context.get('coarse_approach_requested', False)
|
||||
if type(coarse_approach_requested) is not bool or type(report.get('coarse_approach_requested', False)) is not bool or report.get('coarse_approach_requested', False) != coarse_approach_requested or (coarse_approach_requested and not approach_requested):
|
||||
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')
|
||||
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')
|
||||
@@ -1061,7 +1075,7 @@ def input_exercise_result(result, context, session, observation, display_result)
|
||||
events = report.get('events', [])
|
||||
expected = [('W', True), ('W', False), ('Tab', True), ('Tab', False), ('Tab', True), ('Tab', False)]
|
||||
if approach_requested:
|
||||
if not isinstance(events, list) or not 2 <= len(events) <= 24 or len(events)%2:
|
||||
if not isinstance(events, list) or not 2 <= len(events) <= (32 if coarse_approach_requested else 24) or len(events)%2:
|
||||
refuse('Prison approach requires bounded W pairs')
|
||||
expected = [('W', i%2 == 0) for i in range(len(events))]
|
||||
if (not isinstance(events, list) or len(events) != len(expected)
|
||||
@@ -1198,7 +1212,7 @@ def run_locked(a, steam, compat, profile, packed=None):
|
||||
command, display_context = display_probe_command(command, ROOT, compat, profile, executable, run_id, os.environ.get('DISPLAY', ''))
|
||||
exercise_context = None
|
||||
if getattr(a, 'exercise_player_input', False):
|
||||
command, exercise_context = input_exercise_command(command, ROOT, compat, profile, executable, run_id, os.environ.get('DISPLAY', ''), display_context, getattr(a, 'exercise_relative_look', False), getattr(a, 'exercise_prison_approach', False), getattr(a, 'exercise_prison_alignment', False), getattr(a, 'exercise_prison_selection', 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))
|
||||
silent_sink = 'erri_game_'+run_id
|
||||
application_id = 'org.escapistsrereintroduced.game.'+run_id
|
||||
env = os.environ.copy()
|
||||
@@ -1365,6 +1379,7 @@ def main():
|
||||
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-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-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')
|
||||
parser.add_argument('--observe-prison-gate', action='store_true', help='Read-only native registered prison lock/context/progress telemetry; requires --observe-player-input')
|
||||
|
||||
@@ -50,6 +50,8 @@ def gate_options(native, args):
|
||||
if any(value not in {'-errOrdinaryGateObserve', '-errOrdinaryGateChoices'} for value in tokens):
|
||||
raise Refused('Unknown native gate observer option')
|
||||
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')
|
||||
if getattr(args, 'enable_private_selection_proof', False) and '-errOrdinaryGateChoices' not in tokens:
|
||||
raise Refused('Selection requires matching native gate choices')
|
||||
if approach and '-errOrdinaryGateObserve' not in tokens:
|
||||
@@ -134,7 +136,10 @@ def yaw_delta(first, second):
|
||||
|
||||
|
||||
class RelativeMouse:
|
||||
def __init__(self, query, keyboard, owner, enable_approach=False, enable_alignment=False):
|
||||
def __init__(self, query, keyboard, owner, enable_approach=False, enable_alignment=False, enable_coarse_approach=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")
|
||||
self.enable_coarse_approach = enable_coarse_approach
|
||||
self.query, self.keyboard, self.owner = query, keyboard, owner
|
||||
self.enable_approach = enable_approach
|
||||
self.enable_alignment = enable_alignment
|
||||
@@ -173,7 +178,7 @@ class RelativeMouse:
|
||||
self._move(dx, dy, guard)
|
||||
|
||||
def approach_move(self, dx, dy, guard, *, deadline, clock=time.monotonic):
|
||||
if (self.enable_approach is not True or type(dx) is not int or not 1 <= abs(dx) <= 128
|
||||
if (self.enable_approach is not True or type(dx) is not int or not 1 <= abs(dx) <= (384 if self.enable_coarse_approach else 128)
|
||||
or type(dy) is not int or dy != 0):
|
||||
raise Refused('Unsupported or unenabled approach relative step')
|
||||
def within_lease():
|
||||
@@ -590,7 +595,9 @@ 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):
|
||||
def sequence(guard, keyboard, observer, report, clock=time.monotonic, sleep=time.sleep, mouse=None, approach=False, alignment=False, selection=False, coarse_approach=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")
|
||||
deadline = clock() + 70
|
||||
held = None
|
||||
approach_completed = False
|
||||
@@ -764,7 +771,7 @@ def run_approach(guard, keyboard, observer, mouse, report, clock, sleep):
|
||||
deadline = observer.ready_at + 35
|
||||
perform_approach(guard, ApproachKeyboard(keyboard, report, clock, deadline), ApproachMouse(mouse, report, clock, deadline),
|
||||
observer.approach_snapshots, geometry, calibration, report,
|
||||
clock=clock, sleep=sleep, notify=route_notify, key_state_busy=KeyStateBusy)
|
||||
clock=clock, sleep=sleep, notify=route_notify, key_state_busy=KeyStateBusy, profile="coarse" if report.get("coarse_approach_requested", False) else "standard")
|
||||
if not report.get('approach_proof', {}).get('completed'):
|
||||
raise Refused('Approach module did not complete')
|
||||
|
||||
@@ -986,6 +993,7 @@ def arguments(argv=None):
|
||||
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-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'):
|
||||
p.add_argument('--' + name, type=Path, required=True)
|
||||
@@ -994,6 +1002,8 @@ def arguments(argv=None):
|
||||
p.add_argument('--nonce', required=True)
|
||||
p.add_argument('command', nargs=argparse.REMAINDER)
|
||||
args = p.parse_args(argv)
|
||||
if args.enable_private_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):
|
||||
p.error('Selection proof requires explicit alignment, approach and look proof')
|
||||
if args.enable_private_alignment_proof and not (args.enable_private_approach_proof and args.enable_private_look_proof):
|
||||
@@ -1102,6 +1112,7 @@ def main(argv=None):
|
||||
bound = display.binding(args.host_display, args.prefix)
|
||||
report = {'schema': 1, 'kind': 'headless-private-keyboard-experiment', 'input_sent': False,
|
||||
'look_requested': args.enable_private_look_proof,
|
||||
'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),
|
||||
'selection_requested': getattr(args, 'enable_private_selection_proof', False),
|
||||
@@ -1187,8 +1198,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)) 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))
|
||||
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))
|
||||
except (OSError, ValueError, UnicodeError, subprocess.SubprocessError) as error:
|
||||
report['error'] = str(error)
|
||||
finally:
|
||||
|
||||
@@ -12,7 +12,8 @@ p = argparse.ArgumentParser(description=__doc__)
|
||||
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-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('--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-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')
|
||||
p.add_argument('--flight-autohover', action='store_true', help='Opt-in owned native helicopter autohover experiment; requires Core powered-flight and actor-local action inputs')
|
||||
@@ -25,12 +26,13 @@ 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_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):
|
||||
p.error('mortar-courier requires --project chat --rounds 1 --timeout at least 500')
|
||||
if a.smoke in {'mortar-refresh', 'mortar-salvo', 'mortar-expiry', 'mortar-transfer', 'mortar-fire'} and (a.project != 'chat' or a.rounds != 1 or a.timeout < 400):
|
||||
p.error('mortar-refresh/mortar-salvo/mortar-expiry/mortar-transfer/mortar-fire requires --project chat --rounds 1 --timeout at least 400')
|
||||
if a.smoke in {'aircraft-route', 'aircraft-control', 'aircraft-cancel', 'aircraft-translate'} and (a.project != 'chat' or a.rounds != 1 or a.timeout < 420): p.error('aircraft-route/aircraft-control/aircraft-cancel/aircraft-translate requires Chat, one round, and at least 420 seconds')
|
||||
if a.smoke in {'aircraft-field', 'aircraft-route', 'aircraft-control', 'aircraft-cancel', 'aircraft-translate'} and (a.project != 'chat' or a.rounds != 1 or a.timeout < 420): p.error('aircraft-field/aircraft-route/aircraft-control/aircraft-cancel/aircraft-translate requires Chat, one round, and at least 420 seconds')
|
||||
if a.smoke == 'map-ownership' and (a.project != 'chat' or a.rounds != 1 or a.timeout < 180): p.error('map-ownership requires Chat, one round, and at least 180 seconds')
|
||||
if a.smoke == 'powered-translate' and (a.project != 'core' or not a.flight_action_inputs or not a.flight_autohover): p.error('powered-translate requires Core with flight-action-inputs and flight-autohover')
|
||||
if a.flight_autohover and (a.project != 'core' or a.smoke not in {'powered-flight', 'powered-translate'} or not a.flight_action_inputs): p.error('flight-autohover requires Core powered-flight with flight-action-inputs')
|
||||
@@ -78,6 +80,7 @@ for proc in Path('/proc').iterdir():
|
||||
p.error('Another Workbench already uses this test profile')
|
||||
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.route_service_cancel: command += ['-errSmokeRouteServiceCancel', '1']
|
||||
if a.station_teardown_diagnostics: command += ['-errStationTeardownDiag', '1']
|
||||
if a.flight_input_diagnostics: command += ['-errFlightInputDiag', '1']
|
||||
|
||||
@@ -40,10 +40,14 @@ def validate_approach_evidence(content, nonce, look_proof, proof):
|
||||
try:
|
||||
need(type(proof) is dict and proof.get('requested') is True and proof.get('completed') is True and proof.get('nonce')==nonce, 'approach did not complete under this nonce')
|
||||
need(not proof.get('error') and proof.get('paired_release_errors')==[] and proof.get('unconfirmed_held_key') is None, 'approach input did not end cleanly')
|
||||
profile=proof.get('profile','standard')
|
||||
need(type(profile) is str and profile in ('standard','coarse'), 'unknown approach profile')
|
||||
max_pixels,max_angle,max_pulses=(384,35,16) if profile=='coarse' else (128,15,12)
|
||||
result['profile']=profile
|
||||
start=number(proof['started_monotonic']); finish=number(proof['finished_monotonic']); deadline=number(proof['deadline_monotonic'])
|
||||
need(0 <= start < finish <= deadline and deadline-start <= 35, 'approach helper deadline invalid')
|
||||
events=proof['events']; stops=proof['stops']; motions=proof['motions']; references=proof['samples']
|
||||
need(type(events) is list and 2 <= len(events) <= 24 and len(events)%2==0, 'expected bounded W key pairs')
|
||||
need(type(events) is list and 2 <= len(events) <= 2*max_pulses and len(events)%2==0, 'expected bounded W key pairs')
|
||||
need(type(stops) is list and len(stops)*2==len(events), 'each W pair needs a native full-stop interval')
|
||||
need(type(motions) is list and len(motions)<=16 and type(references) is list and 2 <= len(references) <= 450, 'route reference bounds invalid')
|
||||
ids=[r['sample'] for r in references]
|
||||
@@ -110,16 +114,16 @@ def validate_approach_evidence(content, nonce, look_proof, proof):
|
||||
need(.01<=abs(gain)<=.5, 'native calibration gain outside approach range')
|
||||
for motion in motions:
|
||||
before,after=motion['after_sample'],motion['response_sample']; dx=motion['dx'];at=number(motion['monotonic'])
|
||||
need(type(dx) is int and 1<=abs(dx)<=128 and type(motion['dy']) is int and motion['dy']==0,'relative step exceeds route bounds')
|
||||
need(type(dx) is int and 1<=abs(dx)<=max_pixels and type(motion['dy']) is int and motion['dy']==0,'relative step exceeds route bounds')
|
||||
need(type(before) is int and type(after) is int and previous_motion<=before<after<=last and start<=at<=finish,'relative motion references/order invalid')
|
||||
need(previous_motion_time<=at and before in receipts and after in receipts and receipts[before]<=at<=receipts[after] and at-receipts[before]<=.5, 'relative motion timestamps contradict native receipts')
|
||||
previous_motion_time=at
|
||||
a,b=samples[before],samples[after];change=angle(yaw(b['look']['direction']),yaw(a['look']['direction']))
|
||||
need(.05<=abs(change)<=15 and change*dx*gain>0,'native turn did not acknowledge proposal')
|
||||
need(.05<=abs(change)<=max_angle and change*dx*gain>0,'native turn did not acknowledge proposal')
|
||||
for i in range(before,after+1):
|
||||
need(neutral(samples[i]) and horizontal(samples[i]['input']['origin'],a['input']['origin'])<=.05,'body moved during heading correction')
|
||||
turn=angle(yaw(samples[i]['look']['direction']),yaw(a['look']['direction']))
|
||||
need(abs(turn)<=15 and (abs(turn)<.05 or turn*dx*gain>0), 'intermediate native turn exceeded bounds or reversed')
|
||||
need(abs(turn)<=max_angle and (abs(turn)<.05 or turn*dx*gain>0), 'intermediate native turn exceeded bounds or reversed')
|
||||
stable_end(after);pixels+=abs(dx);previous_motion=after
|
||||
need(all(not (events[i]['monotonic'] <= m['monotonic'] <= events[i+1]['monotonic']) for m in motions for i in range(0,len(events),2)), 'mouse motion overlaps W hold')
|
||||
need(pixels<=2048,'relative pixel sum exceeds route bound')
|
||||
|
||||
@@ -16,7 +16,8 @@ Adapter contract (all records must be parsed from the same pinned console/nonce)
|
||||
* guard.check() is the existing full private ownership/focus guard. keyboard has
|
||||
require_idle()/send('W', bool). mouse.move(dx,0,guard) must use the same guarded
|
||||
connection and current/projected clipped pointer checks, explicitly permitting
|
||||
this variant's <=128px cap. Existing two-motion helper remains unchanged.
|
||||
the explicitly selected standard128px or coarse384px cap. Existing two-motion
|
||||
helper remains unchanged; profile choice never changes the observation clocks.
|
||||
* notify receives the existing press-pending/release-confirmed events, plus
|
||||
approach-pending (absolute monotonic deadline)/approach-finished. The wiring
|
||||
MUST hard-supervise that deadline and the unchanged 1.5-second key lease.
|
||||
@@ -91,8 +92,10 @@ def inside(position, center):
|
||||
return abs(position[0]-center[0]) <= 5.25 and abs(position[2]-center[2]) <= 5.25
|
||||
|
||||
|
||||
def perform_approach(guard, keyboard, mouse, observe, geometry, calibration, report, *, clock, sleep, notify, key_state_busy):
|
||||
def perform_approach(guard, keyboard, mouse, observe, geometry, calibration, report, *, clock, sleep, notify, key_state_busy, profile="standard"):
|
||||
"""Run one bounded route through explicit adapters; retain partial failure evidence."""
|
||||
require(profile in ('standard', 'coarse') and type(profile) is str, 'Unknown approach profile')
|
||||
max_pixels, max_angle, desired_angle, max_pulses = (384, 35, 25, 16) if profile == 'coarse' else (128, 15, 10, 12)
|
||||
require('approach_proof' not in report, 'Approach attempt already exists')
|
||||
pinned = copy.deepcopy(geometry)
|
||||
target, center = target_for(pinned)
|
||||
@@ -105,7 +108,7 @@ def perform_approach(guard, keyboard, mouse, observe, geometry, calibration, rep
|
||||
require(0 <= ready_at <= clock(), 'Invalid original READY monotonic time')
|
||||
deadline = ready_at+35
|
||||
require(clock() < deadline, 'Insufficient original approach time')
|
||||
state = {'requested': True, 'completed': False, 'nonce': pinned['nonce'], 'geometry': pinned,
|
||||
state = {'profile': profile, 'requested': True, 'completed': False, 'nonce': pinned['nonce'], 'geometry': pinned,
|
||||
'target': list(target), 'deadline_monotonic': deadline, 'started_monotonic': clock(),
|
||||
'finished_monotonic': None, 'motions': [], 'events': [], 'stops': [],
|
||||
'samples': [], 'cumulative_horizontal_m': 0., 'unconfirmed_held_key': None,
|
||||
@@ -132,7 +135,7 @@ def perform_approach(guard, keyboard, mouse, observe, geometry, calibration, rep
|
||||
if active_turn is not None:
|
||||
turn_yaw, turn_origin, turn_sign = active_turn
|
||||
change = delta(yaw(s['direction']), turn_yaw)
|
||||
require(abs(change) <= 15, 'Relative turn exceeded native angle bound')
|
||||
require(abs(change) <= max_angle, 'Relative turn exceeded native angle bound')
|
||||
require(horizontal(s['origin'], turn_origin) <= .05, 'Body translated during stationary heading')
|
||||
if abs(change) >= .05:
|
||||
require(change*turn_sign > 0, 'Native heading turned opposite to proposal')
|
||||
@@ -173,7 +176,7 @@ def perform_approach(guard, keyboard, mouse, observe, geometry, calibration, rep
|
||||
moved = moved_from is None or horizontal(s['origin'], moved_from) >= .05
|
||||
if angle is not None:
|
||||
change = delta(yaw(s['direction']), angle)
|
||||
require(abs(change) <= 15, 'Relative turn exceeded native angle bound')
|
||||
require(abs(change) <= max_angle, 'Relative turn exceeded native angle bound')
|
||||
if abs(change) >= .05:
|
||||
require(change*turn_sign > 0, 'Native heading turned opposite to proposal')
|
||||
moved = moved and abs(change) >= .05
|
||||
@@ -183,7 +186,7 @@ def perform_approach(guard, keyboard, mouse, observe, geometry, calibration, rep
|
||||
elif s['real_ms']-first['real_ms'] >= 100:
|
||||
if abs(delta(yaw(s['direction']), yaw(first['direction']))) <= .2 and horizontal(s['origin'], first['origin']) <= .02:
|
||||
if angle is not None:
|
||||
require(abs(delta(yaw(s['direction']), angle)) <= 15, 'Relative turn exceeded native angle bound')
|
||||
require(abs(delta(yaw(s['direction']), angle)) <= max_angle, 'Relative turn exceeded native angle bound')
|
||||
require(clock() <= end, 'Settling exceeded original stage deadline')
|
||||
return s
|
||||
first = s
|
||||
@@ -202,8 +205,8 @@ def perform_approach(guard, keyboard, mouse, observe, geometry, calibration, rep
|
||||
if abs(error) <= 3:
|
||||
return s
|
||||
require(clock() < end and turn_count < 16, 'Heading request budget exceeded')
|
||||
desired = max(-10., min(10., .75*error))
|
||||
dx = int(round(desired/gain)); dx = max(-128, min(128, dx))
|
||||
desired = max(-desired_angle, min(desired_angle, .75*error))
|
||||
dx = int(round(desired/gain)); dx = max(-max_pixels, min(max_pixels, dx))
|
||||
if dx == 0: dx = 1 if desired/gain > 0 else -1
|
||||
require(pixels+abs(dx) <= 2048, 'Relative pixel budget exceeded')
|
||||
before = read(); require(neutral(before), 'Body moved before turn')
|
||||
@@ -278,7 +281,7 @@ def perform_approach(guard, keyboard, mouse, observe, geometry, calibration, rep
|
||||
require(.75 < initial_distance <= 6, 'Initial approach distance outside route scope')
|
||||
while horizontal(current['origin'], target) > .75:
|
||||
current = heading(current)
|
||||
require(pulse_count < 12, 'Forward pulse budget exceeded')
|
||||
require(pulse_count < max_pulses, 'Forward pulse budget exceeded')
|
||||
remaining = horizontal(current['origin'], target)
|
||||
reserve = (largest_stop if largest_stop is not None else 1.25)+.25
|
||||
require(remaining > reserve, 'Insufficient distance for measured stopping reserve')
|
||||
|
||||
Reference in New Issue
Block a user