"""Exercise production hunter deployment when a terrain cannot fit the opening.""" import re from policy_harness import ROOT, body, run p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c' s=body(p,'protected bool StartHunter(').replace('protected ','').replace('foreach (','for (') s=s.replace('array players = {};','Array players;') s=s.replace('vector ', 'Vec ').replace('bool occupied;', 'bool occupied=false;').replace('bool openingOccupied;', 'bool openingOccupied=false;').replace('bool nearPlayer;', 'bool nearPlayer=false;') for typ in ('ERR_HQ','ERR_ConvoyPlacement','ERR_SiteRuntime','ERR_HunterVehicle','IEntity'): s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s) for name in ('m_Mode','m_Opening','site','placement','deployed','player','existing'): s=s.replace(name+'.',name+'->') s=s.replace('Forces().','Forces()->').replace('Sites().','Sites()->').replace('Definition.','Definition->').replace('Group.','Group->').replace('Entity.','Entity->') for typ in ('ERR_Actors','ERR_HunterOpeningPlan','ERR_HunterOpeningRules','ERR_LocationKind','ERR_SupplyConvoySystem','Math','ERR_HQ','vector'): s=s.replace(typ+'.',('Geometry' if typ=='vector' else typ)+'::') s=s.replace('ERR_ConvoyPlacement* placement = new ERR_ConvoyPlacement();','ERR_ConvoyPlacement localPlacement; ERR_ConvoyPlacement* placement=&localPlacement;') deployed=body(p,'int Deployed(').replace('foreach (ERR_HunterVehicle unit : m_Hunters)', 'for (ERR_HunterVehicle* unit : m_Hunters)').replace('unit.', 'unit->').replace('int count;', 'int count=0;') run(r''' #include #include #include #include using string=std::string; struct Vec { float x=0,y=0,z=0; float& operator[](int i){return i==0?x:i==1?y:z;} Vec operator+(Vec b){return {x+b.x,y+b.y,z+b.z};} }; Vec Vector(float x,float y,float z){return {x,y,z};} namespace Geometry{float DistanceXZ(Vec a,Vec b){return std::hypot(a.x-b.x,a.z-b.z);}} templatestruct Array:std::vector{using std::vector::vector;int Count(){return this->size();}}; struct IEntity{Vec position;Vec GetOrigin(){return position;}}; struct Group{int faction=1;int GetFaction(){return faction;}}; struct Definition{int Kind=1;Vec Position={1000,0,0};string Id="outpost";}; struct ERR_SiteRuntime{struct Definition* Definition=nullptr;struct Group* Group=nullptr;}; struct Sites{Array sites;int Count(){return sites.Count();}ERR_SiteRuntime* At(int i){return sites[i];}}; struct Forces{int count=0;int Count(){return count;}}; struct Mode{struct Forces forces;struct Sites sites;struct Forces* Forces(){return &forces;}struct Sites* Sites(){return &sites;}int EnemyFaction(){return 1;}}; struct ERR_HunterVehicle{bool Motorbike=false;bool Released=false;IEntity* Entity=nullptr;}; struct ERR_HQ{static void Event(string,string){}}; namespace Math{float RandomFloat(float,float){return 0;}} namespace ERR_LocationKind{enum{RADIO=1,DEPOT=2,OUTPOST=3};} namespace ERR_Actors{Array current;void Players(Array& out){out=current;}} struct ERR_ConvoyPlacement{static bool clear;bool Clear(Vec,float){return clear;}};bool ERR_ConvoyPlacement::clear=true; namespace ERR_HunterOpeningRules{bool InBand(Vec a,Vec b,float radius){auto d=Geometry::DistanceXZ(a,b);return d>=radius-250&&d<=radius+250;}}; struct ERR_HunterOpeningPlan{bool done=false,ready=false;Vec Center;Array Positions={{1200,0,0},{0,0,1200},{-1200,0,0}},Angles={{},{},{}};bool Done(){return done;}bool Ready(){return ready;}static bool AwayFromPlayers(Vec p,Array players){for(auto* a:players)if(Geometry::DistanceXZ(p,a->GetOrigin())<400)return false;return true;}}; namespace ERR_SupplyConvoySystem{bool road=true;int calls=0;bool ApproachDirected(Vec seed,Vec& out,Vec& angles){calls++;out=seed;angles={};return road;}} struct Service{Mode mode;Mode* m_Mode=&mode;Array m_Hunters;int m_Target=3,m_ReserveSpent=0;ERR_HunterVehicle* m_PendingReserve=nullptr;float m_OpeningDistance=1000;ERR_HunterOpeningPlan* m_Opening=nullptr;float m_NextSpawn=0;int spawned=0;Vec chosen;string source;bool SpawnHunter(ERR_HQ*,float,Vec p,Vec,string s){spawned++;chosen=p;source=s;return true;} '''+deployed+s+r''' }; int main(){ Service s;ERR_HQ hq;ERR_HunterOpeningPlan opening;Definition definition;ERR_SiteRuntime site;site.Definition=&definition;s.mode.sites.sites={&site}; assert(!s.StartHunter(&hq,0)&&s.spawned==0);s.m_Opening=&opening; assert(!s.StartHunter(&hq,0)&&ERR_SupplyConvoySystem::calls==0); // unfinished scan cannot be bypassed opening.done=true;assert(s.StartHunter(&hq,100)&&s.source=="outpost"&&s.chosen.x==1000); // exhausted opening falls back opening.ready=true;assert(s.StartHunter(&hq,100)&&s.source=="prison-opening"); IEntity player;player.position={1200,0,0};ERR_Actors::current={&player};definition.Position={2000,0,0}; assert(!s.StartHunter(&hq,100)); // occupied opening cannot escape the 1 km band via a distant objective definition.Position={-1000,0,0};assert(s.StartHunter(&hq,100)&&s.source=="outpost"); player.position={-1000,0,0};opening.ready=false;int kept=s.spawned; assert(!s.StartHunter(&hq,100)&&s.spawned==kept&&s.m_NextSpawn==10100); // safe player distance and retry delay ERR_Actors::current.clear();definition.Position={599,0,0};assert(!s.StartHunter(&hq,200)); definition.Position={750,0,0};assert(s.StartHunter(&hq,200)); // exact opening distance boundary IEntity parked;parked.position=definition.Position;ERR_HunterVehicle other;other.Entity=&parked;s.m_Hunters={&other}; assert(!s.StartHunter(&hq,200));other.Released=true;assert(s.StartHunter(&hq,200)); other.Released=false;parked.position={650,0,0};assert(s.StartHunter(&hq,200)); // 100 m separation accepted ERR_ConvoyPlacement::clear=false;assert(!s.StartHunter(&hq,200));ERR_ConvoyPlacement::clear=true; ERR_SupplyConvoySystem::road=false;assert(!s.StartHunter(&hq,200));ERR_SupplyConvoySystem::road=true; Group foreign;foreign.faction=2;site.Group=&foreign;assert(!s.StartHunter(&hq,200));site.Group=nullptr; definition.Kind=99;assert(!s.StartHunter(&hq,200));definition.Kind=1; s.mode.forces.count=250;assert(!s.StartHunter(&hq,200));s.mode.forces.count=0; s.m_Hunters.resize(3,&other);assert(!s.StartHunter(&hq,200)); s.m_Target=20;s.m_Hunters.resize(15,&other);assert(!s.StartHunter(&hq,200)); s.m_Hunters.resize(3,&other);s.m_Target=4;definition.Position={4000,0,0};assert(s.StartHunter(&hq,300)); // later reinforcements retain objective origins s.m_Target=3;s.m_PendingReserve=&other;assert(s.StartHunter(&hq,300)); // reserve uses the same safe base roads s.mode.forces.count=250;assert(!s.StartHunter(&hq,300));s.mode.forces.count=0; s.m_Hunters.resize(15,&other);assert(!s.StartHunter(&hq,300)); // reserve cannot bypass total cap s.m_Hunters.resize(3,&other);s.m_PendingReserve=nullptr;s.m_ReserveSpent=1;assert(s.StartHunter(&hq,300)); s.m_Hunters.clear();assert(!s.StartHunter(&hq,300)); // same 4 km road is forbidden for the opening s.mode.sites.sites.clear();assert(!s.StartHunter(&hq,300)&&s.m_NextSpawn==10300); } ''') print('PASS: exhausted/blocked opening keeps configured radius; distant objective fallback rejected; bounds, player clearance, separation, force cap and retry delay retained')