"""Execute actual delivered-belief pursuit rules as C++; no engine/AI behavior claim.""" import re from policy_harness import ROOT, body, run P = ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c' source = P.read_text() methods = '\n'.join(body(P, signature) for signature in ( 'static bool Finite(', 'static bool PositionValid(', 'static int Limit(', 'static bool Actionable(', 'static bool Heading(', 'static ERR_HQPursuitRole Role(', 'static float LifetimeSeconds(', 'static float TaskLifetimeSeconds(', 'static bool CanCommit(', 'static int Bearing(', 'static bool BearingAllowed(', 'static vector Direction(', 'static vector Objective(', 'static float Score(', 'static bool Better(')) methods = methods.replace('Math.', 'Math::').replace('vector.', 'vector::') methods = methods.replace('ERR_HQPursuitRole.', 'ERR_HQPursuitRole::') methods = methods.replace('vector offset;', 'vector offset{};') run(r''' #include #include #include #include #include #include #include enum class ERR_HQPursuitRole { SEARCH, INTERCEPT, CONTAIN }; struct Math { static float Clamp(float n,float lo,float hi){return std::clamp(n,lo,hi);} static float AbsFloat(float n){return std::abs(n);} static float Min(float a,float b){return std::min(a,b);} }; struct vector { float x=0,y=0,z=0; vector()=default; vector(float a,float b,float c):x(a),y(b),z(c){} vector(const char* text){sscanf(text,"%f %f %f",&x,&y,&z);} float& operator[](int i){return i==0?x:i==1?y:z;} float operator[](int i)const{return i==0?x:i==1?y:z;} vector operator+(vector b)const{return {x+b.x,y+b.y,z+b.z};} vector operator-(vector b)const{return {x-b.x,y-b.y,z-b.z};} vector operator*(float v)const{return {x*v,y*v,z*v};} float Length()const{return std::sqrt(x*x+y*y+z*z);} vector Normalized()const{float n=Length();return {x/n,y/n,z/n};} static float Dot(vector a,vector b){return a.x*b.x+a.y*b.y+a.z*b.z;} static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);} }; vector Vector(float a,float b,float c){return {a,b,c};} struct Rules { '''+methods+r''' }; bool near(float a,float b){return std::abs(a-b)<0.001f;} int main(){ using Role=ERR_HQPursuitRole; assert(Rules::Limit(-1)==0&&Rules::Limit(0)==0&&Rules::Limit(1)==1); assert(Rules::Limit(3)==3&&Rules::Limit(8)==3); assert(Rules::Actionable(.9,10,50)&&Rules::Actionable(.15,600,2000)); assert(!Rules::Actionable(.149,10,50)&&Rules::Actionable(.9,601,50)); assert(Rules::Actionable(.2,1200,1900)&&!Rules::Actionable(.2,1200,2001)); assert(!Rules::Actionable(.9,-1,50)&&!Rules::Actionable(.9,0,-1)); float nan=std::numeric_limits::quiet_NaN(); float inf=std::numeric_limits::infinity(); assert(!Rules::Actionable(nan,0,50)&&!Rules::Actionable(.9,inf,50)); assert(!Rules::Actionable(.9,0,nan)&&!Rules::Actionable(1.1,0,50)); vector north{0,0,5}, center{1000,12,1000}; assert(Rules::Heading(north,true)&&!Rules::Heading(north,false)); assert(!Rules::Heading({0,0,0},true)&&!Rules::Heading({0,0,71},true)); assert(!Rules::Heading({nan,0,5},true)); assert(Rules::Role(.9,10,50,true,0)==Role::INTERCEPT); assert(Rules::Role(.9,10,50,true,1)==Role::SEARCH); assert(Rules::Role(.9,10,50,true,2)==Role::CONTAIN); for(int slot=0;slot<3;slot++){ assert(Rules::Role(.64,10,50,true,slot)==Role::SEARCH); assert(Rules::Role(.9,46,50,true,slot)==Role::SEARCH); assert(Rules::Role(.9,10,151,true,slot)==Role::SEARCH); assert(Rules::Role(.9,10,50,false,slot)==Role::SEARCH); } assert(Rules::Role(.79,10,50,true,2)==Role::SEARCH); assert(Rules::Role(.9,10,101,true,2)==Role::SEARCH); assert(Rules::Role(.9,10,50,true,-1)==Role::SEARCH); assert(Rules::LifetimeSeconds(Role::INTERCEPT)==60); assert(Rules::LifetimeSeconds(Role::CONTAIN)==90); assert(Rules::LifetimeSeconds(Role::SEARCH)==120); assert(Rules::TaskLifetimeSeconds(Role::SEARCH,0)==120); assert(near(Rules::TaskLifetimeSeconds(Role::SEARCH,2000),2000.f/3+45)); assert(Rules::TaskLifetimeSeconds(Role::SEARCH,5000)==900); assert(Rules::TaskLifetimeSeconds(Role::INTERCEPT,2000)==60); assert(Rules::TaskLifetimeSeconds(Role::CONTAIN,2000)==90); assert(Rules::TaskLifetimeSeconds(Role::SEARCH,-1)==0); assert(Rules::TaskLifetimeSeconds(Role::SEARCH,nan)==0); assert(Rules::TaskLifetimeSeconds(Role::SEARCH,inf)==0); // The caller must already own a physical, eligible and unassigned formation. assert(Rules::CanCommit(true,true,false,false,0,0,3)); assert(!Rules::CanCommit(false,true,false,false,0,0,3)); assert(!Rules::CanCommit(true,false,false,false,0,0,3)); assert(!Rules::CanCommit(true,true,true,false,0,0,3)); assert(!Rules::CanCommit(true,true,false,false,0,-1,3)); assert(!Rules::CanCommit(true,true,false,false,0,0,0)); assert(!Rules::CanCommit(true,true,false,false,0,3,99)); assert(!Rules::CanCommit(true,true,false,true,1,0,3)); int homeAvailable=3,assigned=0; for(int i=0;i<5;i++)if(Rules::CanCommit(true,true,false,true,homeAvailable,assigned,3)){ homeAvailable--;assigned++; } assert(homeAvailable==1&&assigned==2); // Pending orders spend the slot immediately, and each busy unit is ineligible. bool busy=false;int commitments=0; for(int i=0;i<5;i++)if(Rules::CanCommit(true,true,busy,false,1,commitments,3)){ busy=true;commitments++; } assert(commitments==1); vector intercept=Rules::Objective(center,north,true,.9,10,50,Role::INTERCEPT,0); assert(near(intercept.x,1000)&&near(intercept.z,1065)&&near(intercept.y,12)); vector contain=Rules::Objective(center,north,true,.9,10,50,Role::CONTAIN,2); assert(near(contain.x,1080)&&near(contain.z,1080)); vector stale=Rules::Objective(center,north,true,.4,180,400,Role::INTERCEPT,0); assert(near(stale.z,1250)); // degraded to a bounded search sector, no velocity lead for(int slot=-8;slot<9;slot++)for(float radius:{0.f,50.f,200.f,2000.f}){ vector objective=Rules::Objective(center,north,true,.2,300,radius,Role::SEARCH,slot); assert(vector::DistanceXZ(center,objective)<=300&&near(objective.y,center.y)); } vector maximum=Rules::Objective(center,{0,0,70},true,.9,0,50,Role::INTERCEPT,0); assert(near(maximum.z,1200)); // future lead capped even for a fast vehicle vector noDoubleAge=Rules::Objective(center,north,true,.9,40,50,Role::INTERCEPT,0); assert(near(noDoubleAge.z,1060)); // supplied position is already memory-predicted vector first=Rules::Objective(center,north,false,.4,180,100,Role::SEARCH,0); vector second=Rules::Objective(center,north,false,.4,180,100,Role::SEARCH,1); vector third=Rules::Objective(center,north,false,.4,180,100,Role::SEARCH,2); assert(vector::DistanceXZ(first,second)>100&&vector::DistanceXZ(second,third)>100); // Nearby units already ahead win interception; nearby correct-flank units contain. float ahead=Rules::Score(Role::INTERCEPT,center,north,{1000,0,1200},intercept); float behind=Rules::Score(Role::INTERCEPT,center,north,{1000,0,800},intercept); assert(ahead>=0&&behind<0); float right=Rules::Score(Role::CONTAIN,center,north,{1100,0,1050},contain); float left=Rules::Score(Role::CONTAIN,center,north,{900,0,1050},contain); assert(right=0); assert(Rules::Score(Role::INTERCEPT,center,north,{intercept.x+151,0,intercept.z},intercept)<0); assert(Rules::Score(Role::CONTAIN,center,north,{contain.x+240,0,contain.z},contain)>=0); assert(Rules::Score(Role::CONTAIN,center,north,{contain.x+241,0,contain.z},contain)<0); assert(Rules::Score(Role::SEARCH,center,north,{1000,0,1000},first) bearings; std::vector forces={{1000,0,1200},{1200,0,1000},{1000,0,800},{800,0,1000}}; for(vector force:forces){ int bearing=Rules::Bearing(force,center); if(!Rules::BearingAllowed(bearing,firstBearing,secondBearing))continue; bearings.insert(bearing); if(firstBearing<0)firstBearing=bearing; else if(bearing!=firstBearing&&secondBearing<0)secondBearing=bearing; } assert(bearings.size()==2); assert(Rules::BearingAllowed(firstBearing,firstBearing,secondBearing)); assert(!Rules::BearingAllowed(2,0,1)&&!Rules::BearingAllowed(3,0,1)); assert(!Rules::BearingAllowed(-1,-1,-1)&&!Rules::BearingAllowed(2,-1,1)); assert(Rules::Bearing({nan,0,0},center)==-1); } ''') for forbidden in ('GetGame(', 'GetOrigin(', 'GetPlayers(', 'GetControlledEntity(', 'Spawn(', 'CallLater(', 'SCR_AIGroup', 'IEntity'): assert forbidden not in source, f'pursuit must consume snapshots only: {forbidden}' assert all(len(line)<120 for line in source.splitlines()) print('PASS: actual bounded pursuit roles/objectives/scores, stale search degradation, physical/busy/home guards, finite lifetime and two-bearing escape gap; no hidden player reads or spawning')