Wire production aircraft callbacks and diagnose native prison action selection

This commit is contained in:
2026-09-19 20:03:38 +02:00
parent 53eaf3894d
commit f870443b9b
58 changed files with 40247 additions and 27 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ def check():
assert set(mapped)==set(inventory['script_paths']), 'Original inventory has untracked or unknown entries'
print(f'PASS: {len(mapped)} original script entries tracked across {len(feature_ids)} unfinished responsibilities')
print(f'PASS: {len(ids)} isolated addons, acyclic dependencies, {len(resources)} unique resources, exact vanilla references')
for name in ['test_prison_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_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()
+22 -6
View File
@@ -624,6 +624,8 @@ def display_probe_result(context, session):
def validate_input_observer(a):
if getattr(a, 'observe_prison_choices', False) and not getattr(a, 'observe_prison_gate', False):
refuse('Prison action choices require the existing gate observer')
if getattr(a, 'observe_prison_gate', False) and not getattr(a, 'observe_player_input', False):
refuse('Prison gate observation requires the existing input observer')
if not getattr(a, 'observe_player_input', False):
@@ -635,6 +637,8 @@ def validate_input_observer(a):
def validate_input_exercise(a):
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 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):
@@ -654,13 +658,15 @@ def validate_input_exercise(a):
refuse('Input exercise requires --settle >=50')
def input_observer_command(command, project, packed, nonce, look=False, gate=False):
def input_observer_command(command, project, packed, nonce, look=False, gate=False, choices=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):
refuse('Input observer nonce must contain 1..32 ASCII letters/digits')
if type(look) is not bool:
refuse('Relative look selection must be boolean')
if type(choices) is not bool or (choices and not gate):
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):
@@ -670,6 +676,8 @@ def input_observer_command(command, project, packed, nonce, look=False, gate=Fal
options += ['-errOrdinaryLookObserve', nonce]
if gate:
options += ['-errOrdinaryGateObserve', nonce]
if choices:
options += ['-errOrdinaryGateChoices', nonce]
return command + options
@@ -875,10 +883,17 @@ 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')
if approach:
indices = [i for i,value in enumerate(child) if value.startswith('-errOrdinaryGate')]
if len(indices) != 1 or child[indices[0]] != '-errOrdinaryGateObserve' or child[indices[0]+1:indices[0]+2] != [run_id]:
refuse('Prison approach requires its exact native gate observer nonce')
gate_tokens = [value for value in child if value.startswith('-errOrdinaryGate')]
if any(value not in {'-errOrdinaryGateObserve', '-errOrdinaryGateChoices'} for value in gate_tokens):
refuse('Unknown native gate observer option')
if approach and '-errOrdinaryGateObserve' not in gate_tokens:
refuse('Prison approach requires its exact native gate observer nonce')
if '-errOrdinaryGateChoices' in gate_tokens and not approach:
refuse('Input exercise with gate choices requires explicit approach')
for name in set(gate_tokens):
index = child.index(name)
if child.count(name) != 1 or child[index+1:index+2] != [run_id]:
refuse('Native gate observer option is repeated or has a mismatched nonce')
if type(look) is not bool:
refuse('Relative look selection must be boolean')
look_options = [i for i,value in enumerate(child) if value.startswith('-errOrdinaryLook')]
@@ -1127,7 +1142,7 @@ 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))
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))
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', ''))
@@ -1300,6 +1315,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-alignment', action='store_true', help='Explicit stationary yaw-only alignment to native prison lock after verified approach; no F or gate operation')
parser.add_argument('--exercise-prison-approach', action='store_true', help='Explicit bounded relative-mouse/W approach inside prison; requires relative look and gate observer; never operates gate')
parser.add_argument('--observe-prison-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')
parser.add_argument('--exercise-relative-look', action='store_true', help='Explicit tiny relative right/return mouse experiment before W/Tab; requires --exercise-player-input and native view proof')
parser.add_argument('--exercise-player-input', action='store_true', help='Explicit bounded private W/Tab keyboard experiment; requires observer, display inspection, loose Core/Chat and settle >=50')
+18 -4
View File
@@ -43,6 +43,22 @@ def option(args, name):
return args[index + 1]
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.
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')
approach = getattr(args, 'enable_private_approach_proof', False)
if approach and '-errOrdinaryGateObserve' not in tokens:
raise Refused('Missing native gate observer nonce')
if '-errOrdinaryGateChoices' in tokens and not approach:
raise Refused('Native gate choices require explicit approach proof')
for name in set(tokens):
if option(native, name) != args.nonce:
raise Refused('Native gate observer nonce mismatch')
def game_arguments(owner, args):
native = display.process(owner['pid'])
if display.identity(native) != owner['process']:
@@ -51,8 +67,7 @@ def game_arguments(owner, args):
raise Refused('Native observer nonce mismatch')
if getattr(args, 'enable_private_look_proof', False) and option(native['args'], '-errOrdinaryLookObserve') != args.nonce:
raise Refused('Native look observer nonce mismatch')
if getattr(args, 'enable_private_approach_proof', False) and option(native['args'], '-errOrdinaryGateObserve') != args.nonce:
raise Refused('Native gate observer nonce mismatch')
gate_options(native['args'], args)
if option(native['args'], '-errOrdinaryScenario') not in ('core', 'chat'):
raise Refused('Not an ordinary successor scenario')
if option(native['args'], '-profile') != 'Z:' + str(args.profile):
@@ -824,8 +839,7 @@ def wrapper_command(args):
raise Refused('Wrapped native observer context differs')
if args.enable_private_look_proof and option(native, '-errOrdinaryLookObserve') != args.nonce:
raise Refused('Wrapped native look observer context differs')
if getattr(args, 'enable_private_approach_proof', False) and option(native, '-errOrdinaryGateObserve') != args.nonce:
raise Refused('Wrapped native gate observer context differs')
gate_options(native, args)
return command
+67
View File
@@ -0,0 +1,67 @@
"""Strict read-only joins for optional native gate action-choice diagnostics."""
import re
from ordinary_gate_evidence import join_gate_snapshots
PREFIX='ERR GATE CHOICE: '
IDENT=r'([A-Za-z_][A-Za-z_0-9]{0,127}):(-?\d+)'
BOOL=r'(0|1|true|false)'
LINE=re.compile(r'run=([A-Za-z0-9]{1,32}) sample=(\d+) real=(\d+) selected='+IDENT+r' context='+BOOL+r' actions=(\d+) permissions=(\d+) shown=(\d+) truncated='+BOOL+r' ordered=\[(.*)\]')
ENTRY=re.compile(r'(\d+)='+IDENT+r':(-?\d+)')
def need(value,reason):
if not value: raise ValueError(reason)
def parse_choice_record(line,nonce):
need(type(nonce) is str and re.fullmatch(r'[A-Za-z0-9]{1,32}',nonce),'invalid choice nonce')
if PREFIX.rstrip() not in line:return None
need(PREFIX in line and line.count(PREFIX)==1 and len(line)<=4096,'invalid choice line')
match=LINE.fullmatch(line.split(PREFIX,1)[1].strip());need(match is not None,'malformed choice record')
run,index,real,selected_class,selected_id,context,count,permissions,shown,truncated,ordered=match.groups()
index,real,selected_id,count,permissions,shown=map(int,(index,real,selected_id,count,permissions,shown))
need(run==nonce and 1<=index<=450 and 0<=real<=2147483647,'choice nonce or clock mismatch')
need(-1<=selected_id<=2147483647 and ((selected_class=='none')==(selected_id==-1)),'invalid selected action identity')
need(0<=count<=4096 and 0<=permissions<=4096 and shown==min(count,8) and (truncated in ('1','true'))==(count>shown),'choice list bounds disagree')
entries=[] if not ordered else ordered.split(',');need(len(entries)==shown,'choice entry count disagrees')
actions=[]
for i,text in enumerate(entries):
m=ENTRY.fullmatch(text);need(m is not None,'malformed ordered action')
n,cls,identity,permission=m.groups();n,identity,permission=map(int,(n,identity,permission))
need(n==i and -1<=identity<=2147483647 and ((cls=='none')==(identity==-1)),'ordered action identity/index invalid')
need(permission in (-1,0,1) and ((permission==-1)==(i>=permissions)),'cached permission index mismatch')
actions.append({'class':cls,'id':identity,'permitted':None if permission==-1 else bool(permission)})
return {'nonce':nonce,'sample':index,'real_ms':real,'selected_class':selected_class,'selected_id':selected_id,'context_selected':context in ('1','true'),'action_count':count,'permission_count':permissions,'truncated':truncated in ('1','true'),'actions':actions}
def parse_choice_records(content,nonce):
need(type(content) is str and len(content)<=32*1024*1024,'choice content bound')
records={};previous_real=-1
for line in content.splitlines():
r=parse_choice_record(line,nonce)
if r is None:continue
need(r['sample']==len(records)+1 and r['real_ms']>previous_real,'choice sequence missing, repeated or reversed')
records[r['sample']]=r;previous_real=r['real_ms']
need(records,'native choices absent')
return records
def join_choice_snapshots(content,nonce):
data=join_gate_snapshots(content,nonce);records=parse_choice_records(content,nonce)
for i,c in records.items():
need(i in data['snapshots'],'choice has no native gate peer')
s=data['snapshots'][i];a=s['action'];need(c['real_ms']==s['real_ms'] and c['context_selected']==a['context_selected'],'choice/gate clock or context mismatch')
if a['selected']:need(c['selected_class']=='ERR_UnlockPrisonAction','selected lock class disagrees')
if c['selected_class']=='ERR_UnlockPrisonAction':need(a['selected'],'native selected lock flag disagrees')
s['choices']=c
return data
def exact_prison_choices(snapshot):
"""Known non-aggregating closed Open/lock pair; not a general action sorter."""
c=snapshot.get('choices');need(type(c) is dict,'native choice snapshot absent')
a=c['actions'];need(c['context_selected'] and not c['truncated'] and c['action_count']==c['permission_count']==len(a)==2,'expected exact original two-action context')
need([e['class'] for e in a]==['ERR_PrisonDoorAction','ERR_UnlockPrisonAction'],'unsupported native action order')
need(all(type(e['id']) is int and e['id']>=0 for e in a) and a[0]['id']!=a[1]['id'],'invalid original action IDs')
need(a[0]['permitted'] is False and a[1]['permitted'] is True,'native closed-door/lock permissions changed')
need((c['selected_class'],c['selected_id']) in [(e['class'],e['id']) for e in a],'selected action outside original pair')
return c