Files
jakub 1b1a8b5173 Add mission customization, HQ support and aggression-driven AI
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.
2026-09-23 15:07:14 +02:00

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=&current;current.Source=&current;
previous.ObservedAt=1000;previous.Position={0,0,0};current.ObservedAt=11000;current.Position={20,5,30};
Estimate(&current,&previous);assert(current.HasVelocity&&current.Velocity[0]==2&&current.Velocity[1]==0&&current.Velocity[2]==3);
current.Target=&current;Estimate(&current,&previous);assert(!current.HasVelocity);current.Target=&previous;
current.Source=nullptr;Estimate(&current,&previous);assert(!current.HasVelocity);current.Source=&current;
current.ObservedAt=1000;Estimate(&current,&previous);assert(!current.HasVelocity);
current.ObservedAt=33000;Estimate(&current,&previous);assert(!current.HasVelocity);
current.ObservedAt=11000;current.Position={500,0,0};Estimate(&current,&previous);assert(!current.HasVelocity);
current.TravelMode="in vehicle";Estimate(&current,&previous);assert(!current.HasVelocity);
previous.TravelMode="in vehicle";Estimate(&current,&previous);assert(current.HasVelocity&&current.Velocity[0]==50);
previous.Distress=true;Estimate(&current,&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')