Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
123 lines
8.0 KiB
Python
123 lines
8.0 KiB
Python
"""Execute actual bounded hypothesis/road geometry code; native road cache remains an engine boundary."""
|
|
from policy_harness import ROOT, body, run
|
|
import re
|
|
P=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQHypotheses.c'
|
|
|
|
def cpp(s):
|
|
s=s.replace('protected ', '').replace('Math.','Math::').replace('vector.','vector::')
|
|
for c in ('ERR_HQHypothesisRules','ERR_HQHypothesisKind','ERR_HQMemoryKind','ERR_HQCoverageRules','ERR_HQPursuitRules'):
|
|
s=s.replace(c+'.',c+'::')
|
|
s=s.replace('out array<ref ERR_HQHypothesis> hypotheses','Array<ERR_HQHypothesis*>& hypotheses')
|
|
s=s.replace('array<vector> points','Array<vector>* points').replace('out float ','float& ').replace('out vector ','vector& ')
|
|
for c in ('ERR_HQBelief','ERR_HQLocalCoverage','ERR_HQHypothesisRoadCache','ERR_HQHypothesisRoad','ERR_HQHypothesis'):
|
|
s=re.sub(r'\b'+c+r' (\w+)',c+r'* \1',s)
|
|
s=s.replace('nearest, alternative','nearest=nullptr, *alternative=nullptr')
|
|
for name in ('points','belief','result','road','cache','coverage','hypothesis'):
|
|
s=s.replace(name+'.',name+'->')
|
|
s=s.replace('points[','(*points)[')
|
|
s=s.replace('hypotheses[1].','hypotheses[1]->')
|
|
s=s.replace('foreach (ERR_HQHypothesisRoad* road : cache->Roads)','for (ERR_HQHypothesisRoad* road : cache->Roads)')
|
|
s=s.replace('foreach (ERR_HQHypothesis* hypothesis : hypotheses)','for (ERR_HQHypothesis* hypothesis : hypotheses)')
|
|
# Enforce locals are zero-initialized.
|
|
s=re.sub(r'\bfloat (traversed|length|total);',r'float \1=0;',s).replace('bool found;','bool found=false;')
|
|
return s
|
|
|
|
rules='\n'.join(cpp(body(P,s)) for s in ('static float MinSpeed(', 'static float MaxSpeed(',
|
|
'static float PlanningSpeed(', 'static float Lead(', 'static float Earliest(', 'static float Latest(',
|
|
'static float BaseWeight(', 'static bool Project(', 'static float Length(', 'static bool Sample('))
|
|
methods='\n'.join(cpp(body(P,s)) for s in ('protected ERR_HQHypothesis Candidate(', 'protected bool Refine(', 'bool Build('))
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <limits>
|
|
using string=std::string;
|
|
struct vector{
|
|
float x=0,y=0,z=0;float& operator[](int i){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};}
|
|
vector operator/(float v)const{return *this*(1/v);}
|
|
float Length()const{return std::sqrt(x*x+y*y+z*z);}
|
|
vector Normalized()const{return *this/Length();}
|
|
static vector Lerp(vector a,vector b,float n){return a+(b-a)*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 Math{static float Clamp(float v,float a,float b){return std::clamp(v,a,b);}static float AbsFloat(float v){return std::abs(v);}};
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T v){this->push_back(v);}void Clear(){this->clear();}};
|
|
struct ERR_HQPursuitRules{
|
|
static bool Finite(float v){return std::isfinite(v);}
|
|
static bool PositionValid(vector p){return Finite(p.x)&&Finite(p.y)&&Finite(p.z);}
|
|
static bool Heading(vector p,bool h){p.y=0;return h&&PositionValid(p)&&p.Length()>=.5&&p.Length()<=70;}
|
|
static vector Direction(vector p,bool h){p.y=0;return Heading(p,h)?p.Normalized():Vector(0,0,1);}
|
|
static bool Actionable(float c,float age,float r){return Finite(c)&&Finite(age)&&Finite(r)&&c>=.15&&c<=1&&age>=0&&r>=0&&r<=2000;}
|
|
};
|
|
struct ERR_HQCoverageRules{static bool PositionValid(vector p){return ERR_HQPursuitRules::PositionValid(p)&&std::abs(p.x)<1000000&&std::abs(p.z)<1000000;}};
|
|
struct ERR_HQHypothesisKind{enum{HIDE,FORWARD,REVERSE,LEFT,RIGHT,ALTERNATE_ROAD};};
|
|
struct ERR_HQMemoryKind{enum{SIGHTING,GUNFIRE};};
|
|
struct ERR_HQBelief{int BeliefId=1,BeliefRevision=2,MessageId=7,Kind=0;vector Position{1000,0,1000},Velocity{0,0,4};float ObservedAt=0,Confidence=1,Radius=80,Speed=4;bool HasVelocity=true;string TravelMode="on foot";};
|
|
struct ERR_HQHypothesis{int Id=0,Kind=0,BeliefId=0,BeliefRevision=0,ReportId=0;vector Position;float Weight=0,Radius=0,EarliestSeconds=0,LatestSeconds=0;bool OnRoad=false;};
|
|
struct ERR_HQHypothesisRoad{Array<vector> points;Array<vector>*Points=&points;float Length=0;};
|
|
struct ERR_HQHypothesisRoadCache{Array<ERR_HQHypothesisRoad*> Roads;};
|
|
struct ERR_HQLocalCoverage{int calls=0;float Multiplier(vector p,float,float){calls++;return p.z>1100?.25:1;}};
|
|
struct ERR_HQHypothesisRules{
|
|
'''+rules+r'''
|
|
};
|
|
struct Planner{
|
|
ERR_HQHypothesisRoadCache roads;int surveys=0;
|
|
ERR_HQHypothesisRoadCache*Cache(vector,float){surveys++;return &roads;}
|
|
'''+methods+r'''
|
|
};
|
|
int main(){
|
|
using R=ERR_HQHypothesisRules;
|
|
float nan=std::numeric_limits<float>::quiet_NaN();
|
|
assert(R::Lead(0,4)==180&&R::Lead(30,4)==300&&R::Lead(900,25)==1800);
|
|
assert(R::Lead(-1,4)==0&&R::Lead(nan,4)==0);
|
|
assert(R::PlanningSpeed("on foot",nan)==1.5&&R::PlanningSpeed("vehicle",50)==25);
|
|
Array<vector> bend={{0,0,0},{100,0,0},{100,0,200}};float along,distance;vector p,d;
|
|
assert(R::Project(&bend,{50,0,10},along,distance)&&along==50&&distance==10);
|
|
assert(R::Sample(&bend,150,p,d)&&p.x==100&&p.z==50); // follows bend, never chord
|
|
assert(!R::Project(&bend,{50,10,0},along,distance)); // overpass height mismatch
|
|
assert(!R::Project(&bend,{50,0,-76},along,distance));
|
|
assert(!R::Sample(&bend,301,p,d)&&!R::Sample(&bend,-1,p,d));
|
|
Array<vector> malformed={{0,0,0},{nan,0,1}};assert(!R::Project(&malformed,{0,0,0},along,distance));
|
|
Planner planner;ERR_HQBelief b;Array<ERR_HQHypothesis*> out;
|
|
assert(planner.Build(&b,nullptr,30000,out)&&out.Count()==5);
|
|
float total=0;for(auto*h:out){total+=h->Weight;assert(h->BeliefId==1&&h->BeliefRevision==2&&h->ReportId==7);assert(h->LatestSeconds>=h->EarliestSeconds);}
|
|
assert(std::abs(total-1)<.0001&&out[0]->Position.z==1000&&out[1]->Position.z==1300);
|
|
assert(out[1]->EarliestSeconds==50&&out[1]->LatestSeconds==460); // since observation
|
|
assert(out[1]->Weight>out[2]->Weight&&out[2]->Position.z<1000);
|
|
auto originalForward=out[1]->Weight;ERR_HQLocalCoverage coverage;
|
|
assert(planner.Build(&b,&coverage,30000,out)&&coverage.calls==5&&out[1]->Weight<originalForward);
|
|
b.HasVelocity=false;assert(planner.Build(&b,nullptr,30000,out));assert(out[1]->Weight==out[2]->Weight);
|
|
b.HasVelocity=true;
|
|
ERR_HQHypothesisRoad road;road.points={{1000,0,800},{1000,0,1100},{1200,0,1100},{1200,0,1700}};road.Length=1100;
|
|
planner.roads.Roads.Insert(&road);
|
|
assert(planner.Build(&b,nullptr,30000,out)&&out[1]->OnRoad&&out[2]->OnRoad);
|
|
assert(out[1]->Position.x==1200&&out[1]->Position.z==1100); // actual road bend
|
|
assert(out[1]->EarliestSeconds==50);
|
|
ERR_HQHypothesisRoad cross;cross.points={{960,0,900},{960,0,1700}};cross.Length=800;planner.roads.Roads.Insert(&cross);
|
|
assert(planner.Build(&b,nullptr,30000,out)&&out.Count()==6&&out[5]->OnRoad);
|
|
assert(out[5]->Position.x==960); // independent alternate, no splice at crossing
|
|
int surveys=planner.surveys;b.Kind=ERR_HQMemoryKind::GUNFIRE;
|
|
assert(planner.Build(&b,nullptr,30000,out)&&out.Count()==5&&planner.surveys==surveys);
|
|
for(auto*h:out)assert(!h->OnRoad);
|
|
b.Kind=0;b.ObservedAt=30001;assert(!planner.Build(&b,nullptr,30000,out)&&out.empty());
|
|
b.ObservedAt=0;b.TravelMode="aircraft";assert(!planner.Build(&b,nullptr,30000,out));
|
|
b.TravelMode="on foot";b.Position.x=nan;assert(!planner.Build(&b,nullptr,30000,out));
|
|
b.Position.x=1000;b.Confidence=.14;assert(!planner.Build(&b,nullptr,30000,out));
|
|
assert(!planner.Build(nullptr,nullptr,30000,out));
|
|
}
|
|
''')
|
|
source=P.read_text()
|
|
for forbidden in ('GetPlayers(', 'GetPlayerManager(', 'GetControlledEntity(', '.Target', 'GetOrigin(', 'Spawn('):
|
|
assert forbidden not in source
|
|
step=body(P,'void Step(')
|
|
assert 'Pending.Count() > 128' in step and '> 4096' in step and 'examined < 4' in step
|
|
assert 'm_Caches.Count() >= 8' in source and 'Points.Insert(point)' in source
|
|
print('PASS: actual five/six bounded hypotheses, observation-relative ETA, future lead, hiding/reverse, local negative weighting, immutable report lineage, road bends/heights and finite cache budgets')
|