Files
EscapistsReintroduced/tests/test_supply_convoys.py
T

262 lines
22 KiB
Python

"""Execute production supply-circuit and combat timing policies with native boundaries doubled."""
from policy_harness import ROOT, body, run
import re
D=ROOT/'addons/core/Scripts/Game/ERR/10_AI'
def cpp(s):
s=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('override ','').replace('foreach (','for (').replace('null','nullptr')
s=s.replace('m_Task.', 'm_Task->').replace('m_Mode.', 'm_Mode->').replace('m_Service.', 'm_Service->')
for typ in ('ERR_SupplyConvoyTask','ERR_ConvoyVehicle','ERR_SupplyTeam','ERR_ForceRoute','ERR_ConvoyBoardingProgress','IEntity'):
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
for obj in ('task','vehicle','departure','team','lost','boarding','stranded','leg','infantryTeam','member','delayed'):
s=s.replace(obj+'.',obj+'->')
for cls in ('Math','ERR_SupplyConvoyRules','ERR_HQ','ERR_ForceRules','ERR_Actors','vector','string'):
s=s.replace(cls+'.',cls+'::')
s=s.replace('bool surviving, pending;','bool surviving=false, pending=false;').replace('bool pending;', 'bool pending=false;')
s=s.replace('float nearest = 400; bool found;', 'float nearest = 400; bool found=false;')
s=s.replace('m_Support[released].', 'm_Support[released]->').replace('Vehicles[released].', 'Vehicles[released]->').replace('infantryTeam->Group.', 'infantryTeam->Group->')
s=s.replace('->Column.', '->Column->').replace('->Combat.', '->Combat->')
s=s.replace('->Receipt.', '->Receipt->').replace('Vehicles[2].', 'Vehicles[2]->')
s=s.replace('.Entity.', '.Entity->').replace('->Entity.', '->Entity->')
return s
common=r'''
#include <cassert>
#include <cmath>
#include <limits>
#include <vector>
#include <string>
struct string:std::string{using std::string::string;string(std::string s):std::string(s){}template<class...T>static string Format(T...){return "event";}};
struct vector{float x=0;static float Distance(vector a,vector b){return std::abs(a.x-b.x);}static float DistanceXZ(vector a,vector b){return Distance(a,b);}};
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}};
namespace Math{int RandomInt(int low,int){return low;}float RandomFloat(float low,float){return low;}}
namespace ERR_HQ{void Event(string,string){}}
namespace ERR_ForceRules{bool Finite(float v){return std::isfinite(v);}bool FiniteVector(vector v){return Finite(v.x);}}
'''
next_base=cpp(body(D/'ERR_SupplyConvoySystem.c','static int NextBase('))
run(common+'struct ERR_SupplyConvoyRules{'+next_base+r'''};
struct Entity{vector position;vector GetOrigin(){return position;}};
struct ERR_ForceRoute{Array<vector> points;void Add(vector p){points.push_back(p);}void Seal(){}};
struct Receipt{string SourceId="test";};
struct Combat{bool Held=false;};
struct Column{bool live=true;int resets=0;bool Tick(float){return live;}void Reset(float,bool){resets++;}};
struct ERR_ConvoyVehicle{bool Released=false,Arrived=false,SupplyHeld=false;struct Combat combat;struct Combat* Combat=&combat;int cleared=0,RouteIndex=3;struct Entity entity;struct Entity* Entity=&entity;
void ClearOrder(){cleared++;}void Tick(void*,ERR_ForceRoute*,float){}};
struct ERR_SupplyConvoyTask{struct Column column;struct Column* Column=&column;Array<ERR_ConvoyVehicle*> Vehicles;float TravelDeadline=1000,Departure=0;bool Waiting=false,stopped=false;int LastBase=-1,Legs=0;
Array<vector> Bases={{0},{1000},{2000}};ERR_ForceRoute* Route=nullptr;struct Receipt receipt;struct Receipt* Receipt=&receipt;
void Stop(void*,string){stopped=true;}};
struct Service{void* m_Mode=nullptr;
'''+cpp(body(D/'ERR_SupplyConvoyService.c','protected void Circuit('))+r'''};
int main(){
for(int last=0;last<5;last++)for(int roll=0;roll<4;roll++){int next=ERR_SupplyConvoyRules::NextBase(5,last,roll);assert(next>=0&&next<5&&next!=last);}
assert(ERR_SupplyConvoyRules::NextBase(1,0,0)==-1);assert(ERR_SupplyConvoyRules::NextBase(3,3,0)==-1);
Service s;ERR_SupplyConvoyTask t;ERR_ConvoyVehicle a,b;t.Vehicles={&a,&b};
s.Circuit(&t,999);assert(!t.Waiting&&!a.Arrived);s.Circuit(&t,1000);assert(!a.Arrived&&!b.Arrived&&!t.Waiting); // timeout is not arrival
a.Arrived=b.Arrived=true;a.SupplyHeld=true;s.Circuit(&t,2000);assert(!t.Waiting);
a.SupplyHeld=false;b.combat.Held=true;s.Circuit(&t,3000);assert(!t.Waiting);
b.combat.Held=false;s.Circuit(&t,4000);assert(t.Waiting&&t.Departure==49000);
s.Circuit(&t,48999);assert(t.Legs==0);s.Circuit(&t,49000);assert(t.Legs==1&&!t.Waiting&&!a.Arrived&&!b.Arrived&&t.LastBase==0&&t.column.resets==1);
a.entity.position={250};b.entity.position={249};s.Circuit(&t,50000);assert(!a.Arrived&&!b.Arrived&&!t.Waiting); // no 250m shortcut
a.Arrived=true;b.Arrived=true;s.Circuit(&t,51000);assert(t.Waiting);s.Circuit(&t,96000);assert(t.LastBase==1&&t.Legs==2);
a.Released=true;b.Released=true;t.column.live=false;s.Circuit(&t,97000);assert(t.stopped); // no replacements
}
''')
run(common+r'''
struct ERR_ConvoyBoardingProgress{};
struct Group{int GetFaction(){return 1;}};struct IEntity{};struct Mode{int EnemyFaction(){return 1;}};
namespace ERR_Actors{bool Alive(IEntity*){return true;}struct Group* Group(IEntity*){return nullptr;}}
struct ERR_ConvoyVehicle{bool Released=false;void* Group=nullptr;void* Entity=nullptr;};
struct ERR_SupplyTeam{ERR_ConvoyBoardingProgress* BoardingProgress=nullptr;float BoardingRetryAt=0;bool Released=false,aboard=true,living=true;struct Group nativeGroup;struct Group* Group=&nativeGroup;Array<IEntity*> Members;int orders=0;void Clear(){}
bool Aboard(void*,bool required){return aboard==required;}bool Living(){return living;}void Release(void*){Released=true;}void Add(int){orders++;}};
struct Receipt{string SourceId="test";};
struct ERR_SupplyConvoyTask{Array<ERR_ConvoyVehicle*> Vehicles;Array<ERR_SupplyTeam*> Teams;struct Receipt receipt;struct Receipt* Receipt=&receipt;};
struct Tactics{ERR_SupplyConvoyTask* m_Task;Mode mode;Mode* m_Mode=&mode;Array<ERR_SupplyTeam*>m_Support;bool m_Stopped=false,m_TruckWithdrawn=false,seeing=false,truck=true;
int m_State=0,starts=0,reorders=0,withdraws=0,finishes=0,m_DismountAttempts=0,flanks=0;float m_NextDismount=0;void Flank(int,bool){flanks++;}float m_LastContact=-1,m_NextOrders=0,m_BoardDeadline=0;vector m_Contact;
bool Observe(void*,vector& p,float&){p={100};return seeing;}void Start(vector p,float){m_Contact=p;m_State=1;m_TruckWithdrawn=false;starts++;}
void Orders(bool,float){reorders++;}bool InfantryAboard(bool required){for(auto t:m_Task->Teams)if(!t->Released&&!t->Aboard(nullptr,required))return false;return true;}
void Support(int i,bool hold){if(i==2&&!hold)withdraws++;}bool Usable(int){return truck;}void Clear(){}int Board(void*,bool){return 1;}
float infantryDistance=100;float InfantryDistance(void*){return infantryDistance;}void ProtectInfantry(){}void RefreshBoarding(float){}void Finish(float){m_State=0;finishes++;}
'''+cpp(body(D/'ERR_SupplyConvoyTactics.c','void Report('))+cpp(body(D/'ERR_SupplyConvoyTactics.c','bool Tick('))+r'''};
int main(){
ERR_SupplyConvoyTask task;ERR_ConvoyVehicle driver;task.Vehicles={&driver,&driver,&driver};ERR_SupplyTeam left,right;task.Teams={&left,&right};Tactics t{&task};
t.Report({100},std::numeric_limits<float>::quiet_NaN(),0);assert(t.starts==0);
t.Report({100},-1,0);t.Report({100},10,0);t.Report({100},0,60001);assert(t.starts==0);
t.Report({100},1000,1000);assert(t.starts==1&&t.m_State==1);t.Report({0},999,1001);assert(t.m_LastContact==1000);
assert(t.Tick(60999)&&t.m_State==1&&t.withdraws==0);left.aboard=right.aboard=false;
assert(t.Tick(61000)&&t.m_State==2&&t.withdraws==1&&left.orders==1&&right.orders==1&&t.m_BoardDeadline==361000);
assert(t.Tick(360999));left.aboard=true;assert(!t.Tick(361000)&&t.finishes==1&&!left.Released&&right.Released);
right.Released=false;t.Report({100},200000,200000);t.Tick(260000);assert(t.m_State==2);
t.seeing=true;t.Tick(260001);assert(t.m_State==1&&t.starts==3); // fresh contact interrupts reboarding
t.seeing=false;t.truck=false;t.Tick(320001);assert(t.m_State==0&&left.Released&&right.Released); // truck lost: release surviving infantry
t.m_Stopped=true;t.Report({100},400000,400000);assert(t.starts==3&&!t.Tick(400000));
left.Released=right.Released=false;left.aboard=right.aboard=true;Tactics retry{&task};retry.Report({100},0,0);
for(int i=1;i<=9;i++){retry.Report({100},10000*i,10000*i);retry.Tick(10000*i);}
assert(retry.m_DismountAttempts==6&&retry.flanks==12); // six attempts per still-mounted team, no infinite retry
left.aboard=right.aboard=false;Tactics safety{&task};safety.Report({100},0,0);safety.infantryDistance=24;
safety.Tick(1000);assert(!safety.m_TruckWithdrawn&&safety.withdraws==0); // do not drive through troops at the doors
safety.infantryDistance=25;safety.Tick(2000);assert(safety.m_TruckWithdrawn&&safety.withdraws==1);
}
''')
startup=cpp(body(D/'ERR_SupplyConvoySystem.c','void Tick('))
run(common+r'''
struct Service{int count=0;int Count(){return count;}};
struct Startup{Service* m_Service;bool m_Stopped=false,m_Automatic=true,create=false;int m_Target=3,m_Attempts=0,calls=0;
float m_Began=-1,m_Next=0;string m_Reason;
bool StartOne(){calls++;if(create)m_Service->count++;return create;}
'''+startup+r'''};
int main(){
Service service;Startup s{&service};s.Tick(1000,true);s.Tick(30999,true);assert(!s.calls);s.Tick(31000,false);assert(!s.calls);s.Tick(36000,true);assert(s.calls==1);
s.create=true;s.Tick(41000,true);s.Tick(46000,true);s.Tick(51000,true);assert(service.count==3&&s.calls==4);s.Tick(56000,true);assert(s.calls==4);
Service noRoad;Startup fail{&noRoad};fail.Tick(0,true);for(int i=0;i<200;i++)fail.Tick(30000+5000*i,true);assert(fail.calls==120&&fail.m_Attempts==120);
Startup off{&noRoad};off.m_Target=0;off.Tick(0,true);off.Tick(30000,true);assert(!off.calls);
off.m_Target=3;off.m_Automatic=false;off.Tick(90000,true);assert(!off.calls);
}
''')
retain=body(D/'ERR_SupplyConvoyTactics.c','static bool Retain(')
retain=retain.replace('IEntity actor','IEntity* actor').replace('IEntity vehicle','IEntity* vehicle')
retain=retain.replace('SCR_VehicleDamageManagerComponent damage = SCR_VehicleDamageManagerComponent.Cast(vehicle.FindComponent(SCR_VehicleDamageManagerComponent));','SCR_VehicleDamageManagerComponent* damage = vehicle->damage;')
for name in ('ERR_Actors','CompartmentAccessComponent','EDamageState'):retain=retain.replace(name+'.',name+'::')
retain=retain.replace('damage.', 'damage->')
run(r"""
#include <cassert>
#include <map>
enum class EDamageState{UNDAMAGED,DAMAGED,DESTROYED};
struct SCR_VehicleDamageManagerComponent{float health=1;bool engine=true,gear=true;EDamageState state=EDamageState::UNDAMAGED;float GetHealthScaled(){return health;}bool GetEngineFunctional(){return engine;}bool GetGearboxFunctional(){return gear;}EDamageState GetState(){return state;}};
struct IEntity{bool player=false,fit=true;IEntity* vehicle=nullptr;SCR_VehicleDamageManagerComponent* damage=nullptr;};
namespace ERR_Actors{bool Player(IEntity* a){return a->player;}bool CanFight(IEntity* a){return a->fit;}}
namespace CompartmentAccessComponent{IEntity* GetVehicleIn(IEntity* a){return a->vehicle;}}
struct Map:std::map<IEntity*,IEntity*>{bool Contains(IEntity* a){return count(a);}IEntity* Get(IEntity* a){return at(a);}}s_Crews;
"""+retain+r"""
int main(){
IEntity driver,truck,other;SCR_VehicleDamageManagerComponent health;truck.damage=&health;driver.vehicle=&truck;
assert(!Retain(&driver,&truck));s_Crews[&driver]=&truck;assert(Retain(&driver,&truck));assert(!Retain(&driver,&other));
driver.player=true;assert(!Retain(&driver,&truck));driver.player=false;driver.fit=false;assert(!Retain(&driver,&truck));driver.fit=true;
health.state=EDamageState::DAMAGED;health.health=.9;assert(Retain(&driver,&truck));health.health=.25;assert(!Retain(&driver,&truck));health.health=1;
health.engine=false;assert(!Retain(&driver,&truck));health.engine=true;health.gear=false;assert(!Retain(&driver,&truck));health.gear=true;
health.state=EDamageState::DESTROYED;assert(!Retain(&driver,&truck));health.state=EDamageState::UNDAMAGED;driver.vehicle=nullptr;assert(!Retain(&driver,&truck));
}
""")
boarding=body(D/'ERR_SupplyConvoyTactics.c','static AIWaypoint Board(')
boarding=boarding.replace('AIWaypoint Board','AIWaypoint* Board').replace('Vehicle truck','Vehicle* truck').replace('truck.', 'truck->').replace('waypoint.', 'waypoint->').replace('specific.', 'specific->').replace('null','nullptr')
boarding=boarding.replace('SCR_BoardingWaypoint waypoint','SCR_BoardingWaypoint* waypoint').replace('SCR_BoardingEntityWaypoint specific','SCR_BoardingEntityWaypoint* specific')
boarding=boarding.replace('SCR_BoardingWaypoint.Cast(', 'dynamic_cast<SCR_BoardingWaypoint*>(').replace('SCR_BoardingEntityWaypoint.Cast(', 'dynamic_cast<SCR_BoardingEntityWaypoint*>(').replace('ERR_Actors.Spawn','ERR_Actors::Spawn').replace('EAIWaypointCompletionType.All','EAIWaypointCompletionType::All')
run(r"""
#include <cassert>
#include <string>
using ResourceName=std::string;
enum class EAIWaypointCompletionType{Leader,All};
struct Vehicle{int GetOrigin(){return 7;}};
struct AIWaypoint{virtual ~AIWaypoint(){}int radius=9;EAIWaypointCompletionType completion=EAIWaypointCompletionType::Leader;void SetCompletionRadius(int n){radius=n;}void SetCompletionType(EAIWaypointCompletionType type){completion=type;}};
struct SCR_BoardingWaypoint:AIWaypoint{bool driver=true,gunner=true,cargo=false;void SetAllowance(bool d,bool g,bool c){driver=d;gunner=g;cargo=c;}};
struct SCR_BoardingEntityWaypoint:SCR_BoardingWaypoint{Vehicle* vehicle=nullptr;void SetEntity(Vehicle* v){vehicle=v;}};
namespace ERR_Actors{bool fail=false;AIWaypoint* Spawn(ResourceName resource,int){if(fail)return nullptr;if(resource.find("GetIn.et")!=std::string::npos)return new SCR_BoardingEntityWaypoint();return new SCR_BoardingWaypoint();}}
"""+boarding+r"""
int main(){
Vehicle truck;assert(!Board(nullptr,false));
auto out=dynamic_cast<SCR_BoardingWaypoint*>(Board(&truck,false));assert(out&&out->completion==EAIWaypointCompletionType::All&&out->radius==100&&!out->driver&&!out->gunner&&out->cargo);
auto in=dynamic_cast<SCR_BoardingEntityWaypoint*>(Board(&truck,true));assert(in&&in->radius==100&&in->completion==EAIWaypointCompletionType::All&&in->vehicle==&truck&&!in->driver&&!in->gunner&&in->cargo);
delete out;delete in;ERR_Actors::fail=true;assert(!Board(&truck,true));
}
""")
delivery=body(D/'ERR_SupplyConvoyService.c','override bool ApplyCombatOrder(')
delivery=delivery.replace('override ','').replace('ERR_SupplyConvoyTask.Cast(task)','dynamic_cast<ERR_SupplyConvoyTask*>(task)')
for typ in ('ERR_ConvoyTask','ERR_SupplyConvoyTask','ERR_ConvoyCombatOrder'):delivery=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',delivery)
for name in ('supply','order'):delivery=delivery.replace(name+'.',name+'->')
delivery=delivery.replace('Tactics.','Tactics->').replace('super.ApplyCombatOrder','Base::ApplyCombatOrder')
run(r"""
#include <cassert>
struct ERR_ConvoyTask{virtual ~ERR_ConvoyTask(){}};
struct ERR_ConvoyCombatOrder{bool Hold=true;int Position=3;float ObservedAt=100;};
struct Tactics{int state=0,reports=0;int State(){return state;}void Report(int,float,float){reports++;}};
struct ERR_SupplyConvoyTask:ERR_ConvoyTask{struct Tactics tactics;struct Tactics* Tactics=&tactics;};
struct Base{bool accept=true;int applied=0;bool ApplyCombatOrder(ERR_ConvoyTask*,ERR_ConvoyCombatOrder*,float){applied++;return accept;}};
struct Service:Base{
"""+delivery+r"""
};
int main(){
Service service;ERR_SupplyConvoyTask task;ERR_ConvoyCombatOrder order;
for(int state=0;state<3;state++){task.tactics.state=state;assert(service.ApplyCombatOrder(&task,&order,100));}
assert(task.tactics.reports==3);order.Hold=false;assert(!service.ApplyCombatOrder(&task,&order,100));assert(service.applied==3);
task.tactics.state=0;assert(service.ApplyCombatOrder(&task,&order,100));assert(task.tactics.reports==3);
service.accept=false;order.Hold=true;assert(!service.ApplyCombatOrder(&task,&order,100)&&task.tactics.reports==3);
assert(!service.ApplyCombatOrder(nullptr,&order,100));assert(!service.ApplyCombatOrder(&task,nullptr,100));
}
""")
print('PASS: actual supply route cycling/arrival/stops, finite startup bounds, contact freshness, dismount hold, quiet interval, interrupted boarding, timeout and lost-truck release')
# Mounted groups retain their native spawn origin; sighting distance must follow vehicles.
distance=cpp(body(D/'ERR_SupplyConvoyTactics.c','protected float DistanceToConvoy(')).replace('Math.Min','Math::Min')
observe=cpp(body(D/'ERR_SupplyConvoyTactics.c','protected bool Observe('))
for typ in ('SCR_AIGroup','SCR_AIGroupPerception','PerceptionManager','SCR_AITargetInfo'):
observe=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',observe)
for obj in ('group','perception','manager','target'):observe=observe.replace(obj+'.',obj+'->')
observe=observe.replace('out vector position','vector& position').replace('inout float nearest','float& nearest').replace('bool found;','bool found=false;')
observe=observe.replace('GetGroupUtilityComponent().','GetGroupUtilityComponent()->').replace('GetFaction().','GetFaction()->').replace('GetGame().','GetGame()->').replace('EAITargetInfoCategory.','EAITargetInfoCategory::')
run(common+r"""
namespace Math{float Min(float a,float b){return a<b?a:b;}}
struct Entity{vector position;vector GetOrigin(){return position;}};
struct ERR_ConvoyVehicle{bool Released=false;struct Entity* Entity=nullptr;};
struct ERR_SupplyConvoyTask{Array<ERR_ConvoyVehicle*> Vehicles;};
struct Faction{bool hostile=true;bool IsFactionEnemy(Faction* other){return other->hostile;}};
enum class EAITargetInfoCategory{IDENTIFIED,SUSPECTED};
struct SCR_AITargetInfo{void* m_Entity=this;Faction* m_Faction=nullptr;float m_fTimestamp=10;EAITargetInfoCategory m_eCategory=EAITargetInfoCategory::IDENTIFIED;vector m_vWorldPos={5100};};
struct SCR_AIGroupPerception{Array<SCR_AITargetInfo*> m_aTargets;};
struct Utility{SCR_AIGroupPerception* m_Perception=nullptr;bool allowed=true;bool IsPositionAllowed(vector){return allowed;}};
struct SCR_AIGroup{Faction faction;Utility utility;Utility* GetGroupUtilityComponent(){return &utility;}Faction* GetFaction(){return &faction;}vector GetOrigin(){return {0};}};
struct PerceptionManager{float now=10;float GetTime(){return now;}};
struct Game{PerceptionManager manager;PerceptionManager* GetPerceptionManager(){return &manager;}}game;
Game* GetGame(){return &game;}
struct Observer{ERR_SupplyConvoyTask* m_Task;
"""+distance+observe+r"""
};
int main(){
Entity car{{5000}},lost{{0}};ERR_ConvoyVehicle live{false,&car},released{true,&lost};ERR_SupplyConvoyTask task{{&live,&released}};Observer observer{&task};
Faction enemy;SCR_AITargetInfo target;target.m_Faction=&enemy;SCR_AIGroupPerception perception{{&target}};SCR_AIGroup group;group.utility.m_Perception=&perception;
vector position;float nearest=400;assert(observer.Observe(&group,position,nearest)&&position.x==5100&&nearest==100);
target.m_vWorldPos={100};nearest=400;assert(!observer.Observe(&group,position,nearest)); // stale group/spawn origin is irrelevant
target.m_vWorldPos={5401};assert(!observer.Observe(&group,position,nearest));target.m_vWorldPos={5100};
target.m_eCategory=EAITargetInfoCategory::SUSPECTED;assert(!observer.Observe(&group,position,nearest));target.m_eCategory=EAITargetInfoCategory::IDENTIFIED;
target.m_fTimestamp=4;assert(!observer.Observe(&group,position,nearest));target.m_fTimestamp=11;assert(!observer.Observe(&group,position,nearest));target.m_fTimestamp=5;
assert(observer.Observe(&group,position,nearest));nearest=400;enemy.hostile=false;assert(!observer.Observe(&group,position,nearest));enemy.hostile=true;
group.utility.allowed=false;assert(!observer.Observe(&group,position,nearest));group.utility.allowed=true;
live.Released=true;assert(!observer.Observe(&group,position,nearest));assert(!observer.Observe(nullptr,position,nearest));
}
""")
retain_group=body(D/'ERR_SupplyConvoyTactics.c','static bool RetainGroup(')
retain_group=retain_group.replace('SCR_AIGroup group','SCR_AIGroup* group').replace('IEntity vehicle','IEntity* vehicle').replace('IEntity leader','IEntity* leader').replace('group.','group->').replace('CompartmentAccessComponent.','CompartmentAccessComponent::')
source=(D/'ERR_SupplyConvoyTactics.c').read_text()
overrides=[]
for cls in ('SCR_AIGetOutVehicle','SCR_AIGetOutActivity'):
section=source[source.index('modded class '+cls):];section=section[:section.index('\nclass ') if '\nclass ' in section else len(section)]
for method in ('CustomEvaluate','EvaluatePriorityLevel'):
# Each method's exact production body, selecting only this class's first occurrence.
a=section.index('override float '+method+'(');b=section.index('\n }',a)+3
method_source=section[a:b].replace('override ','').replace('m_Utility.','m_Utility->').replace('ERR_ConvoyMountedCrew.','ERR_ConvoyMountedCrew::').replace('super.','Base::')
overrides.append(method_source)
run(r"""
#include <cassert>
struct IEntity{bool held=false;IEntity* vehicle=nullptr;};
struct SCR_AIGroup{int players=0;IEntity* leader=nullptr;int GetPlayerCount(bool){return players;}IEntity* GetLeaderEntity(){return leader;}};
namespace CompartmentAccessComponent{IEntity* GetVehicleIn(IEntity* actor){return actor->vehicle;}}
struct ERR_ConvoyMountedCrew{static bool Retain(IEntity* actor,IEntity* vehicle){return actor&&actor->held&&vehicle&&actor->vehicle==vehicle;}
"""+retain_group+r"""
};
struct Utility{IEntity* m_OwnerEntity;SCR_AIGroup* m_Owner;};
struct Base{float CustomEvaluate(){return 1162;}float EvaluatePriorityLevel(){return 1000;}};
struct Individual:Base{Utility* m_Utility=nullptr;struct{IEntity* m_Value=nullptr;}m_Vehicle;
"""+''.join(overrides[:2])+r"""
};
struct Activity:Base{Utility* m_Utility=nullptr;struct{IEntity* m_Value=nullptr;}m_Vehicle;
"""+''.join(overrides[2:])+r"""
};
int main(){
IEntity truck,driver{true,&truck};SCR_AIGroup group{0,&driver};Utility utility{&driver,&group};Individual individual;Activity activity;
individual.m_Utility=&utility;individual.m_Vehicle.m_Value=&truck;activity.m_Utility=&utility;activity.m_Vehicle.m_Value=&truck;
assert(individual.CustomEvaluate()==0&&individual.EvaluatePriorityLevel()==0);assert(activity.CustomEvaluate()==0&&activity.EvaluatePriorityLevel()==0);
assert(ERR_ConvoyMountedCrew::RetainGroup(&group,nullptr));group.players=1;assert(activity.CustomEvaluate()==1162&&activity.EvaluatePriorityLevel()==1000);group.players=0;
driver.held=false;assert(individual.CustomEvaluate()==1162&&individual.EvaluatePriorityLevel()==1000);assert(activity.CustomEvaluate()==1162&&activity.EvaluatePriorityLevel()==1000);
group.leader=nullptr;assert(!ERR_ConvoyMountedCrew::RetainGroup(&group,&truck));assert(!ERR_ConvoyMountedCrew::RetainGroup(nullptr,&truck));
}
""")
print('PASS: moving-convoy perception boundaries and scoped individual/group abandonment priorities')