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.
80 lines
6.8 KiB
Python
80 lines
6.8 KiB
Python
"""Execute production hill/road geometry and finite admission state machine."""
|
|
import re
|
|
from policy_harness import ROOT, body, run
|
|
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ScoutSystem.c'
|
|
hill=body(p,'static bool Hill(')
|
|
road=body(p,'static bool OnSegment(')
|
|
prepare=body(p,'ERR_PrepareResult Prepare(')
|
|
for name in ('Math','vector','ERR_PrepareResult','Replication'):
|
|
hill=hill.replace(name+'.',name+'::');road=road.replace(name+'.',name+'::');prepare=prepare.replace(name+'.',name+'::')
|
|
prepare=prepare.replace('ERR_ScenarioDefinition definition','ERR_ScenarioDefinition* definition').replace('foreach (ERR_ScoutPost post : m_Posts)','for (ERR_ScoutPost* post : m_Posts)').replace('post.','post->').replace('bool pending;','bool pending=false;').replace('GetGame().GetWorld().','clock.').replace('GetGame().GetWorldEntity().','world.').replace('System.GetTickCount()','0')
|
|
motion=body(p,'static void Estimate(').replace('ERR_HQReport report','ERR_HQReport* report').replace('ERR_HQReport previous','ERR_HQReport* previous')
|
|
for name in ('report','previous'): motion=motion.replace(name+'.',name+'->')
|
|
for name in ('vector','ERR_HQMemoryRules'): motion=motion.replace(name+'.',name+'::')
|
|
memory=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMemory.c'
|
|
credible=body(memory,'static float SpeedLimit(')+'\n'+body(memory,'static bool MotionCredible(')
|
|
credible=credible.replace('string ', 'std::string ')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <vector>
|
|
#include <string>
|
|
struct Math{static float Max(float a,float b){return std::max(a,b);}static float Clamp(float n,float a,float b){return std::clamp(n,a,b);}static float AbsFloat(float n){return std::abs(n);}};
|
|
struct vector{float v[3]={};vector()=default;vector(float x,float y,float z):v{x,y,z}{}float&operator[](int i){return v[i];}float operator[](int i)const{return v[i];}vector operator-(vector b)const{return{v[0]-b[0],v[1]-b[1],v[2]-b[2]};}vector operator/(float f)const{return{v[0]/f,v[1]/f,v[2]/f};}static const vector Zero;static float Dot(vector a,vector b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];}static vector Lerp(vector a,vector b,float f){return{a[0]+(b[0]-a[0])*f,a[1]+(b[1]-a[1])*f,a[2]+(b[2]-a[2])*f};}static float DistanceXZ(vector a,vector b){return std::hypot(a[0]-b[0],a[2]-b[2]);}};
|
|
const vector vector::Zero{};
|
|
'''+hill+'\n'+road+r'''
|
|
struct ERR_HQReport{void* Target=nullptr;void* Source=nullptr;bool Distress=false,HasVelocity=false;vector Position,Velocity;float ObservedAt=0;std::string TravelMode="on foot";};
|
|
struct ERR_HQMemoryRules{static bool Finite(float n){return std::isfinite(n);}
|
|
'''+credible+r'''};
|
|
'''+motion+r'''
|
|
enum class ERR_PrepareResult{PENDING,READY,FAILED};
|
|
struct Replication{static bool server;static bool IsServer(){return server;}};bool Replication::server=true;
|
|
struct ERR_ScenarioDefinition{};struct ERR_ScoutPost{bool Ready=false,Group=false;};
|
|
struct Clock{float now=0;float GetWorldTime(){return now;}};
|
|
struct World{void GetWorldBounds(vector&,vector&) {}};
|
|
struct Population{
|
|
bool m_Stopped=false,m_Ready=false,m_Begun=false,m_Surveyed=false;ERR_ScenarioDefinition*m_Definition=nullptr;int m_Started=0,spawns=0,stocks=0,surveys=0;float m_Deadline=10;vector m_Low,m_High;Clock clock;World world;
|
|
std::vector<ERR_ScoutPost*>m_Posts;
|
|
ERR_PrepareResult survey=ERR_PrepareResult::READY,stock=ERR_PrepareResult::READY;
|
|
ERR_PrepareResult Survey(){surveys++;m_Surveyed=survey==ERR_PrepareResult::READY;return survey;}
|
|
ERR_PrepareResult Fail(const char*){m_Stopped=true;return ERR_PrepareResult::FAILED;}
|
|
bool Spawn(ERR_ScoutPost*p){spawns++;p->Group=true;return true;}
|
|
ERR_PrepareResult Stock(ERR_ScoutPost*p){stocks++;p->Ready=stock==ERR_PrepareResult::READY;return stock;}
|
|
'''+prepare+r'''
|
|
};
|
|
int main(){
|
|
assert(Hill(90,200,6,4));assert(!Hill(34,100,30,8));assert(!Hill(35,200,30,8));assert(!Hill(100,200,5.9,8));assert(!Hill(100,200,30,3));
|
|
vector a{0,10,0},b{100,20,0};
|
|
assert(OnSegment({50,15,2},a,b,4));assert(!OnSegment({50,15,2.01},a,b,4));
|
|
assert(!OnSegment({50,25,0},a,b,4)); // aircraft/overpass != ground road
|
|
assert(!OnSegment({105,20,0},a,b,4)); // no infinite extension past end
|
|
assert(!OnSegment({50,15,0},a,a,4));assert(!OnSegment({50,15,0},a,b,0));
|
|
// A bend is tested as its real segments, not a diagonal shortcut.
|
|
assert(!OnSegment({50,10,50},{0,10,0},{100,10,0},4));assert(!OnSegment({50,10,50},{100,10,0},{100,10,100},4));
|
|
ERR_HQReport previous,current;previous.Target=&previous;current.Target=&previous;previous.Source=¤t;current.Source=¤t;
|
|
previous.ObservedAt=1000;previous.Position={0,0,0};current.ObservedAt=11000;current.Position={20,5,30};
|
|
Estimate(¤t,&previous);assert(current.HasVelocity&¤t.Velocity[0]==2&¤t.Velocity[1]==0&¤t.Velocity[2]==3);
|
|
current.Target=¤t;Estimate(¤t,&previous);assert(!current.HasVelocity);current.Target=&previous;
|
|
current.Source=nullptr;Estimate(¤t,&previous);assert(!current.HasVelocity);current.Source=¤t;
|
|
current.ObservedAt=1000;Estimate(¤t,&previous);assert(!current.HasVelocity);
|
|
current.ObservedAt=33000;Estimate(¤t,&previous);assert(!current.HasVelocity);
|
|
current.ObservedAt=11000;current.Position={500,0,0};Estimate(¤t,&previous);assert(!current.HasVelocity);
|
|
current.TravelMode="in vehicle";Estimate(¤t,&previous);assert(!current.HasVelocity);
|
|
previous.TravelMode="in vehicle";Estimate(¤t,&previous);assert(current.HasVelocity&¤t.Velocity[0]==50);
|
|
previous.Distress=true;Estimate(¤t,&previous);assert(!current.HasVelocity);
|
|
ERR_ScenarioDefinition def;Population pop;ERR_ScoutPost first,second;pop.m_Posts={&first,&second};
|
|
pop.survey=ERR_PrepareResult::PENDING;assert(pop.Prepare(&def)==ERR_PrepareResult::PENDING&&pop.spawns==0);
|
|
pop.survey=ERR_PrepareResult::READY;assert(pop.Prepare(&def)==ERR_PrepareResult::PENDING&&pop.spawns==1);
|
|
pop.stock=ERR_PrepareResult::PENDING;assert(pop.Prepare(&def)==ERR_PrepareResult::PENDING&&pop.spawns==2);
|
|
assert(pop.Prepare(&def)==ERR_PrepareResult::PENDING&&pop.spawns==2);
|
|
pop.stock=ERR_PrepareResult::READY;assert(pop.Prepare(&def)==ERR_PrepareResult::READY);
|
|
int stocks=pop.stocks;first.Group=false;first.Ready=false;
|
|
assert(pop.Prepare(&def)==ERR_PrepareResult::READY&&pop.spawns==2&&pop.stocks==stocks); // dead original never replaced/refilled
|
|
Population timeout;timeout.m_Begun=true;timeout.m_Surveyed=true;timeout.clock.now=11;assert(timeout.Prepare(&def)==ERR_PrepareResult::FAILED&&timeout.spawns==0);
|
|
Population stopped;stopped.m_Stopped=true;assert(stopped.Prepare(&def)==ERR_PrepareResult::FAILED&&stopped.surveys==0);
|
|
Replication::server=false;Population client;assert(client.Prepare(&def)==ERR_PrepareResult::FAILED&&client.surveys==0);
|
|
}
|
|
''')
|
|
print('PASS: hill prominence/elevation, actual road edges/heights/bends and server-only finite scout admission, pending stock, timeout and no replacements/refills')
|