Files
EscapistsReintroduced/tools/check.py
T

132 lines
19 KiB
Python

#!/usr/bin/env python3
"""Validate addon/resource isolation and execute policy regressions. Not an engine compiler."""
from pathlib import Path
import json,re,subprocess,sys
from resource_references import hard_resource_references
from external_addons import policy, ALLOWED, direct_for, resource_allowed
from prefab_identity import check_prefabs
ROOT=Path(__file__).resolve().parents[1]
BASE='58D0FB3206B6F859'
def check():
subprocess.run([sys.executable,'-m','unittest','discover','-s',str(ROOT/'tests'),'-p','test_workshop_changelog.py'],check=True)
subprocess.run([sys.executable,'-m','unittest','discover','-s',str(ROOT/'tests'),'-p','test_discord_changelog_ci.py'],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_terrain_controller_init.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_terrain_chat_retention.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_empty_server_restart.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_rescue_factions.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_rescue_landing.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_rescue_descent.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_rescue_recovery.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_casualty_fleet.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_plane_boarding.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_civilian_traffic.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_hunter_fallback.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_hunter_tactics.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_hunter_gun_team.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_hunter_coordination.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_attack_coordination.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_convoy_hunt.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_recon_search.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_body_recovery_selection.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_terrain_editor.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_terrain_editor_registration.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_item_station_lifecycle.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_profile_config.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_welcome_voice.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_bundled_speech.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_speech_preparation.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_welcome_lifecycle.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_welcome_identity.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_reconnect_recovery.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_observer_view_buffer.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_observer_view_relay.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_observer_scope.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_observer_head_visibility.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_observer_orbit.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_speech_service.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_speech_menu.py')],check=True)
print(f'PASS: {check_prefabs((ROOT/"addons").rglob("*.et"))} prefabs have unique saved root entity IDs')
subprocess.run([sys.executable,str(ROOT/'tests/test_atlas_markers.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_objective_map_presentation.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_objective_map_element.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_native_objective_map_probe.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_prefab_identity.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_spawn_scene.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_spawn_loading_protection.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_spawn_scene_inventory.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_spawn_scene_lease.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_prison_ai.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_native_player_death_fixture.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_hq_planner_budget.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_observer_ownership.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_chat_client_ownership.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_native_observer_selector.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_ground_depot_loading.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_ground_depot_lifecycle.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_ground_depot_mine_claim.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_ground_depot_transfer.py')],check=True)
subprocess.run([sys.executable,str(ROOT/'tests/test_ground_depot_provenance.py')],check=True)
ids={};graph={};resources={};paths={};owners={}
for p in sorted((ROOT/'addons').glob('*/addon.gproj')):
text=p.read_text();g=re.search(r'GUID "([A-F0-9]{16})"',text)[1]
assert g not in ids,f'Duplicate addon GUID: {p}'
ids[g]=p.parent.name;graph[g]=re.findall(r'"([A-F0-9]{16})"',re.search(r'\bDependencies\s*\{([^{}]*)\}',text)[1])
assert ids == {'2528466DE220D91C': 'core'}, 'Expected one combined addon project'
external=policy(ROOT)
for guid,name in ids.items():
assert (set(graph[guid]) & set(ALLOWED)) == direct_for(name), f'Unreviewed direct external dependency scope: {name}'
for guid,entry in external['addons'].items(): graph[guid]=entry['dependencies']
def walk(g,stack):
assert g not in stack,'Dependency cycle: '+str(stack+[g])
for dep in graph[g]:
assert dep==BASE or dep in ids or dep in ALLOWED,f'External addon dependency {dep}'
if dep!=BASE:walk(dep,stack+[g])
for g in ids:walk(g,[])
for p in (ROOT/'addons').rglob('*.meta'):
m=re.search(r'Name "\{([A-F0-9]{16})\}([^"\n]+)"',p.read_text())
assert m,f'Malformed metadata: {p}'
g,path=m.groups();assert g not in resources,f'Duplicate resource GUID {g}'
addon=p.relative_to(ROOT/'addons').parts[0]
assert p.with_suffix('')==ROOT/'addons'/addon/path, f'Resource path mismatch: {p}'
assert p.with_suffix('').is_file(),f'Missing resource for {p}'
resources[g]=path;paths[path]=g
owners[g]=addon
def visible(g):
result={ids.get(g,g)}
for dep in graph[g]:
if dep!=BASE:result.update(visible(dep))
return result
visibility={ids[g]:visible(g) for g in ids}
assert set(ALLOWED) <= visibility['core'], 'Combined addon requires the authorized external closure'
vanilla=json.loads((ROOT/'docs/vanilla-resources.json').read_text())
for p in (ROOT/'addons').rglob('*'):
if not p.is_file() or p.suffix not in ['.c','.et','.conf','.ent','.layer','.layout','.emat','.bt','.acp']:continue
s=p.read_text()
assert not re.search(r'\b(?:ESCT\w*|SCRTC_\w+|SCRT_\w+|EAI_\w+)\b',s),f'Original ecosystem dependency: {p}'
for g,path in hard_resource_references(p.relative_to(ROOT).as_posix(),s):
assert resources.get(g)==path or vanilla.get(path)==g or resource_allowed(external,visibility[p.relative_to(ROOT/'addons').parts[0]],g,path),f'Unresolved/mismatched resource in {p}: {g} {path}'
if g in owners:
addon=p.relative_to(ROOT/'addons').parts[0]
assert owners[g] in visibility[addon],f'Resource outside declared dependency graph in {p}: {g} {path}'
if p.suffix in ['.et','.conf','.ent','.layout','.emat','.bt','.acp']:
assert p.with_name(p.name+'.meta').exists(),f'Missing metadata: {p}'
inventory=json.loads((ROOT/'docs/original-resource-inventory.json').read_text())
coverage=json.loads((ROOT/'docs/original-feature-coverage.json').read_text())
mapped=[];feature_ids=set()
for feature in coverage['features']:
assert feature['id'] not in feature_ids,'Duplicate feature responsibility'
feature_ids.add(feature['id'])
assert feature['status'] in {'pending','partial'},'Completion requires a separately reviewed acceptance update'
assert feature['remaining'],'Unfinished responsibilities must describe remaining work'
mapped.extend(feature['original_scripts'])
for path in feature['replacement_files']:
assert (ROOT/path).is_file(),f'Missing replacement reference: {path}'
assert len(mapped)==len(set(mapped)), 'Original scripts assigned more than once'
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/authorized external references')
for name in ['test_sarka_hunters.py', 'test_optional_motorbikes.py', 'test_server_memory_sampler.py', 'test_roadblocks.py', 'test_roadblock_background.py', 'test_roadblock_layout.py', 'test_roadblock_mining.py', 'test_crash_start.py', 'test_start_settings.py', 'test_start_backpack.py', 'test_intercept_vehicle_footprint.py', 'test_bmp_intercept_geometry.py', 'test_native_random_radius_trace.py', 'test_native_random_radius_runner.py', 'test_native_supply_travel.py', 'test_native_supply_travel_hook.py', 'test_native_supply_travel_runner.py', 'test_native_mounted_intercept.py', 'test_hq_mounted_dispatch.py', 'test_hq_mounted_intercept.py', 'test_hq_mounted_intercept_pair.py', 'test_convoy_intercept_pair.py', 'test_native_maintenance_refuel.py', 'test_mine_intel.py', 'test_mine_intel_discovery_integration.py', 'test_infantry_combat.py', 'test_enemy_medicine.py', 'test_hunter_combat.py', 'test_hunter_stuck.py', 'test_hq_local_awareness.py', 'test_unconscious_controls.py', 'test_self_epinephrine.py', 'test_town_garrisons.py', 'test_field_sites.py', 'test_gameplay_session_regressions.py', 'test_gameplay_review_fixes.py', 'test_server_log_regressions.py', 'test_mortar_load_ack.py', 'test_mortar_spotting.py', 'test_hunter_profiles.py', 'test_hunter_lifecycle.py', 'test_hunter_capture.py', 'test_hunter_opening.py', 'test_hunter_search_memory.py', 'test_hunter_reserve.py', 'test_hunter_sector_search.py', 'test_enemy_awareness.py', 'test_intel_body_inspection.py', 'test_body_recovery_lifecycle.py', 'test_radio_document_defence.py', 'test_air_support_policy.py', 'test_air_logistics_orders.py', 'test_air_recon.py', 'test_scouts.py', 'test_mortar_station.py', 'test_random_settings.py', 'test_debug_ai.py', 'test_generated_sites.py', 'test_terrain_sites.py', 'test_terrain_adapter.py', 'test_terrain_airfields.py','test_terrain_takeover.py', 'test_terrain_presentation.py', 'test_terrain_selection.py', 'test_faction_selection.py', 'test_faction_menu.py', 'test_content_policy.py', 'test_hq_road_intercept.py', 'test_hq_delivered_task.py', 'test_hq_local_handoff.py', 'test_hq_local_handoff_intake.py', 'test_native_local_handoff_probe.py', 'test_infantry_resupply_waypoints.py', 'test_infantry_placement_settle.py', 'test_native_infantry_fixture_lifecycle.py', 'test_native_infantry_cargo.py', 'test_native_infantry_setup.py', 'test_infantry_ammo_access.py', 'test_infantry_resupply_planning.py', 'test_infantry_supply_pool.py', 'test_hq_multiconvoy_supply.py', 'test_enemy_gunner_skill.py', 'test_native_memory_deadlines.py', 'test_convoy_auto_settings.py', 'test_convoy_startup.py', 'test_failure_result_refresh.py', 'test_infantry_resupply_coordinator.py', 'test_infantry_mine_coordinator.py', 'test_infantry_mine_transfer.py', 'test_infantry_ammo_transfer.py', 'test_finite_magazine_reservation.py', 'test_mounted_ammo_transfer.py', 'test_mounted_ammo_pickup.py', 'test_mounted_resupply.py', 'test_mounted_supply_integration.py', 'test_hq_ammo_reporting.py', 'test_enemy_radio.py', 'test_native_maintenance_stock.py', 'test_packed_external_game.py', 'test_hq_radio_node.py', 'test_vehicle_maintenance_stock.py', 'test_optional_transport.py', 'test_vehicle_entry_diagnostics.py', 'test_vehicle_security_native_probe.py', 'test_vehicle_security.py', 'test_vehicle_alarm.py', 'test_vehicle_alarm_perception.py', 'test_native_convoy_mine_stock.py', 'test_native_mine_stock_runner.py', 'test_native_mounted_ammo_probe.py', 'test_native_mounted_swap_probe.py', 'test_native_mounted_courier_probe.py', 'test_native_alarm_hearing.py', 'test_native_convoy_cover_probe.py', 'test_enemy_optics.py', 'test_convoy_combat.py', 'test_ai_stinger_admin.py', 'test_hq_ammo_dispatch.py', 'test_hq_convoy_combat.py', 'test_hq_ammo_sampler.py', 'test_hq_mine_sampler.py', 'test_hq_mounted_ammo_sampler.py', 'test_hq_mounted_binding.py', 'test_hq_vehicle_seats.py', 'test_native_hq_body_probe.py', 'test_hq_delivered_casualties.py', 'test_external_profile_review.py', 'test_hq_uplink_fairness.py', 'test_actor_group.py', 'test_native_package_external.py', 'test_native_convoy_probe.py', 'test_convoy_placement.py', 'test_convoy_road_plan.py', 'test_hq_convoy_dispatch.py', 'test_convoy_admission.py', 'test_convoy_dispatch.py', 'test_convoy_lifecycle.py', 'test_convoy_survivors.py', 'test_convoy_budget.py','test_supply_convoys.py', 'test_supply_budget_admission.py', 'test_mounted_initial_stock.py', 'test_convoy_mine_stock.py', 'test_convoy_column.py', 'test_hq_body_observer.py', 'test_hq_status_sampler.py', 'test_hq_watch.py', 'test_hq_assignment_lifecycle.py', 'test_force_ledger.py', 'test_enemy_equipment.py', 'test_enemy_loadout.py', 'test_automatic_equipment.py', 'test_engineer_mine.py', 'test_engineer_mine_site.py', 'test_engineer_mine_hq.py', 'test_engineer_mine_posts.py', 'test_hq_doctrine.py', 'test_hq_coverage.py', 'test_hq_hypotheses.py', 'test_hq_local_coverage.py', 'test_hq_operations.py', 'test_hq_squad_judgment.py', 'test_hq_tactical_ground.py', 'test_hq_operation_speech.py', 'test_hq_hunter_operations.py', 'test_hq_coordinated.py', 'test_hq_execution.py', 'test_ai_pursuit_replay.py', 'test_native_coordinated_runner.py', 'test_hq_task_reservations.py', 'test_hq_pursuit.py', 'test_hq_memory.py', 'test_external_addons.py', 'test_admin_tick_delivery.py', 'test_project_schema.py', 'test_admin_session.py', 'test_admin_providers.py', 'test_admin_menu.py', 'test_server_item_catalog.py', 'test_item_preferences.py', 'test_journal_readability.py', 'test_admin_player_tools.py', 'test_intel_admin.py', 'test_intel_reroll.py', 'test_intel_discovery_feedback.py', 'test_hq_report_selection.py', '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_spawn_preload_timeout.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_spectator_recovery.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_native_inventory_hitzone.py','test_inventory_hitzone_compat.py','test_player_medical.py','test_time_policy.py','test_environment_policy.py','test_view_distance.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()