Integrate faction and random settings menus, generated objectives, scouts, convoys, air support, mortar stations and body-intel recovery. Add identified-body aggression, finite hunter UAZ deployment, dedicated objective defence and maximum AI skill with separate reduced aim accuracy. Include policy tests, native probes, evidence and documentation.
79 lines
11 KiB
Python
79 lines
11 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 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():
|
|
print(f'PASS: {check_prefabs((ROOT/"addons").rglob("*.et"))} prefabs have unique saved root entity IDs')
|
|
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_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)
|
|
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']: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 re.findall(r'\{([A-F0-9]{16})\}([^"\n]+)',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']:
|
|
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_hunter_lifecycle.py', 'test_enemy_awareness.py', 'test_intel_body_inspection.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_faction_selection.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_infantry_ammo_access.py', 'test_infantry_resupply_planning.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_ammo_transfer.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_vehicle_entry_diagnostics.py', 'test_vehicle_security_native_probe.py', 'test_vehicle_security.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_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_budget.py','test_supply_convoys.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_hq_doctrine.py', 'test_hq_coverage.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_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_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()
|