100 lines
6.1 KiB
Python
100 lines
6.1 KiB
Python
"""Execute the actual field policy equations; no claim about native stopping/flight."""
|
|
from policy_harness import ROOT, run
|
|
import re
|
|
source = (ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFieldRules.c').read_text()
|
|
cpp = source.replace('class ERR_AircraftFieldRules', 'struct Rules').replace('out vector nextIntegral', 'vector& nextIntegral')
|
|
cpp = cpp.replace('Math.', 'Math::').replace('vector.', 'vector::').replace('"0 0 0"', 'vector{}')
|
|
cpp = re.sub(r'(?<![\w.])(\d+\.\d+)(?![\w.])', r'\1f', cpp)
|
|
cpp = cpp.rstrip() + ';'
|
|
run(r'''#include <cassert>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
struct vector {
|
|
float v[3]={0,0,0};
|
|
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-(vector b)const{return {v[0]-b[0],v[1]-b[1],v[2]-b[2]};}
|
|
vector operator*(float s)const{return {v[0]*s,v[1]*s,v[2]*s};}
|
|
float Length()const{return std::sqrt(Dot(*this,*this));}
|
|
static float Dot(vector a,vector b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];}
|
|
static float DistanceXZ(vector a,vector b){return std::hypot(a[0]-b[0],a[2]-b[2]);}
|
|
};
|
|
struct Math {static float AbsFloat(float x){return std::abs(x);} static float Clamp(float x,float lo,float hi){return std::clamp(x,lo,hi);} static float Max(float a,float b){return std::max(a,b);} static float Min(float a,float b){return std::min(a,b);} static float Sqrt(float a){return std::sqrt(a);}};
|
|
'''+cpp+r'''
|
|
bool close(float a,float b){return std::abs(a-b)<.00001f;}
|
|
int main(){
|
|
assert(Rules::Length(99.95f)&&Rules::Length(100.05f));
|
|
assert(!Rules::Length(99.949f)&&!Rules::Length(100.051f)&&!Rules::Length(NAN));
|
|
assert(Rules::SpeedAllowed({1.5f,9,2})&&!Rules::SpeedAllowed({1.501f,0,2}));
|
|
assert(!Rules::SpeedAllowed({0,NAN,0})&&!Rules::SpeedAllowed({INFINITY,0,0}));
|
|
assert(Rules::ValidStep(.5f)&&!Rules::ValidStep(0)&&!Rules::ValidStep(.501f)&&!Rules::ValidStep(NAN));
|
|
float speed=0;
|
|
for(int i=0;i<100;i++){float next=Rules::RampSpeed(speed,2,.1f);assert(next-speed<=.020001f);speed=next;}
|
|
assert(close(speed,2));
|
|
for(int i=0;i<100;i++){float next=Rules::RampSpeed(speed,0,.1f);assert(next<=speed&&speed-next<=.020001f);speed=next;}
|
|
assert(close(speed,0));
|
|
assert(close(Rules::RampSpeed(1,2,1),1)&&close(Rules::RampSpeed(1,NAN,.1f),1));
|
|
|
|
vector previous={2,0,0};
|
|
for(int i=0;i<400;i++){
|
|
vector ramped=Rules::RampReference(previous,{-2,8,0},.1f);
|
|
assert((ramped-previous).Length()<=.020001f&&ramped.Length()<=2.00001f&&ramped[1]==0);
|
|
previous=ramped;
|
|
}
|
|
assert(previous[0]<-1.99f);
|
|
vector sideways=Rules::RampReference({2,0,0},{0,0,2},.1f);
|
|
assert((sideways-vector{2,0,0}).Length()<=.020001f);
|
|
assert(close(Rules::RampReference({1,4,0},{-2,0,0},0)[0],1));
|
|
assert(close(Rules::ApproachSpeed(100,2),2)&&close(Rules::ApproachSpeed(.75f,2),0));
|
|
assert(close(Rules::ApproachSpeed(3,2),0));
|
|
assert(close(Rules::ApproachSpeed(Rules::BrakingReserve(1.5f),2),1.5f));
|
|
assert(Rules::ApproachSpeed(18,2)<2); // before the2m/s reserve, unlike old sqrt(distance-.75)
|
|
for(int i=3;i<=120;i++){
|
|
float admitted=Rules::ApproachSpeed(float(i),2);
|
|
assert(admitted<=2&&Rules::BrakingReserve(admitted)<=i+.00002f);
|
|
}
|
|
assert(Rules::ApproachSpeed(15,1.2f)<=1.2f);
|
|
assert(Rules::ApproachSpeed(10,.1f)<=.1f&&Rules::ApproachSpeed(NAN,1)==0);
|
|
assert(close(Rules::ApproachSpeed(108,2),2)&&Rules::ApproachSpeed(120.01f,2)==0);
|
|
assert(!Rules::ShouldBrake(100.23f,1)&&!Rules::ShouldBrake(108,2)&&!Rules::ShouldBrake(120,2));
|
|
assert(Rules::ShouldBrake(120.01f,1)&&!Rules::Length(108));
|
|
vector integral, next;
|
|
for(int i=0;i<2000;i++){
|
|
vector demand=Rules::VelocityDemand({2,100,0},{0,-2,0},integral,.1f,next);
|
|
assert(close(demand[0],.08f)&&demand[1]==0&&next.Length()==0);integral=next;
|
|
}
|
|
// Low error accumulates useful demand rather than permanently freezing I.
|
|
integral={};vector demand=Rules::VelocityDemand({.1f,5,0},{0,7,0},integral,.5f,next);
|
|
assert(close(next[0],.05f)&&close(demand[0],.011f)&&demand[1]==0);
|
|
// Opposing error unwinds a positive stored integral.
|
|
integral={4,0,0};demand=Rules::VelocityDemand({0,0,0},{.1f,0,0},integral,.5f,next);
|
|
assert(next[0]<4&&demand[0]<.08f);
|
|
// Diagonal demand is capped as a vector, not independently on both axes.
|
|
demand=Rules::VelocityDemand({2,3,2},{-1,4,-1},{4,5,4},.1f,next);
|
|
assert(demand.Length()<=.080001f&&demand[1]==0&&next.Length()<=4.00001f&&next[1]==0);
|
|
demand=Rules::VelocityDemand({0,0,0},{2,0,0},{},.1f,next);
|
|
assert(demand[0]<0&&close(demand.Length(),.08f));
|
|
demand=Rules::VelocityDemand({NAN,0,0},{},{1,0,0},.1f,next);assert(demand.Length()==0&&next.Length()==0);
|
|
demand=Rules::VelocityDemand({1,0,0},{},{1,0,0},0,next);assert(demand.Length()==0&&next.Length()==0);
|
|
assert(close(Rules::BrakingReserve(0),3)&&close(Rules::BrakingReserve(2.5f),26.333334f));
|
|
assert(Rules::ShouldBrake(25,0)&&!Rules::ShouldBrake(25.01f,0));
|
|
assert(Rules::ShouldBrake(26,2.5f)&&!Rules::ShouldBrake(27,2.5f));
|
|
assert(Rules::ShouldBrake(50,NAN)&&Rules::ShouldBrake(NAN,1));
|
|
assert(Rules::LeaseAdmitted(1000,241000)&&Rules::LeaseAdmitted(1000,240000));
|
|
assert(!Rules::LeaseAdmitted(1000,239999)&&!Rules::LeaseAdmitted(1000,241001)&&!Rules::LeaseAdmitted(-1,239999));
|
|
assert(!Rules::AbortDue(154999,240000)&&Rules::AbortDue(155000,240000));
|
|
assert(Rules::AbortDue(240000,240000)&&Rules::AbortDue(NAN,240000));
|
|
assert(Rules::LowSpeedHeld(.249f,1000,3000)&&!Rules::LowSpeedHeld(.25f,1000,3000));
|
|
assert(!Rules::LowSpeedHeld(0,1000,2999)&&!Rules::LowSpeedHeld(0,-1,3000)&&!Rules::LowSpeedHeld(NAN,0,3000));
|
|
vector start={0,10,0},end={100,10,0};
|
|
assert(Rules::Contains(start,end,{50,18,7.999f},1));
|
|
assert(!Rules::Contains(start,end,{50,18,8},1));
|
|
assert(Rules::Contains(start,end,{-7.99f,18,0},1)&&!Rules::Contains(start,end,{-8,18,0},1));
|
|
assert(!Rules::Contains(start,end,{108,18,0},1)&&!Rules::Contains(start,end,{50,28.01f,0},1));
|
|
assert(!Rules::Contains(start,end,{50,6.99f,0},1)&&!Rules::Contains(start,end,{50,18,0},.84f));
|
|
assert(!Rules::Contains(start,end,{NAN,18,0},1)&&!Rules::Contains(start,end,{50,18,0},NAN));
|
|
}
|
|
''')
|
|
print('PASS: actual field PI/ramp/vector caps, antiwindup, braking hypothesis, immutable admission limits and segment bounds; no native physics tested')
|