Files
EscapistsReintroduced/tests/test_aircraft_field_rules.py
T

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')