Files
EscapistsReintroduced/tests/test_aircraft_translation.py
T

103 lines
7.7 KiB
Python

"""Actual bounded target/start/stage policy, with native boundaries substituted."""
from policy_harness import ROOT, body, run
SOURCE=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFlight.c'
def cpp(s):
return s.replace('protected ','').replace('ERR_AircraftFlightState.','State::').replace('ERR_AircraftFlightRules.','FlightRules::').replace('ERR_AircraftTranslationRules.','Rules::').replace('Math.','Math::').replace('vector.','vector::').replace('SignalsManagerComponent signals','SignalsManagerComponent* signals').replace('signals.FindSignal','signals->FindSignal')
rules='\n'.join(cpp(body(SOURCE,key)) for key in ('static bool Target(', 'static bool Reachable(', 'static vector Demand(', 'static bool Held('))
methods='\n'.join(cpp(body(SOURCE,key)) for key in ('bool StartTranslation(', 'protected bool BeginTranslation(', 'protected void CheckTranslation('))
run(r'''#include <cassert>
#include <cmath>
#include <algorithm>
#include <string>
#include <initializer_list>
#define PrintFormat(...) ((void)0)
using string=std::string;
using ERR_GameMode=int;using Vehicle=int;using SCR_AIGroup=int;using IEntity=int;using PilotCompartmentSlot=int;
namespace State {enum {IDLE,HOOKS,STARTING,POSITIVE,NEGATIVE,CLIMB,LANDING,SHUTDOWN,COMPLETE,CANCELLED,FAILED,RELINQUISHED,TRANSLATING};}
struct Math {static float Min(float a,float b){return std::min(a,b);}static float Max(float a,float b){return std::max(a,b);}static float AbsFloat(float a){return std::abs(a);}};
struct vector {float x=0,y=0,z=0;
float& operator[](int i){return i==0?x:(i==1?y:z);}float operator[](int i)const{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*(float n)const{return {x*n,y*n,z*n};}
float Length()const{return std::sqrt(x*x+y*y+z*z);}static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}
};
struct FlightRules {
'''+cpp(body(SOURCE,'static bool Hover('))+r'''
};
struct Rules {
'''+rules+r'''
};
struct ERR_AircraftFlightEnvelope {bool valid=true;vector home;explicit operator bool()const{return valid;}vector Home(){return home;}};
struct SignalsManagerComponent {bool present=true;int FindSignal(const char*){return present?0:-1;}};
struct Controller {bool hover=true;SignalsManagerComponent signals;SignalsManagerComponent* GetSignalsManager(){return &signals;}bool GetAutohoverEnabled(){return hover;}};
struct Simulation {vector velocity;bool contact=false;vector GetVelocity(){return velocity;}bool HasAnyGroundContact(){return contact;}};
struct Probe {
bool m_StartAttempted=false,m_TranslationRequested=false,m_TranslationStarted=false,m_TranslationProven=false;
vector m_TranslationTarget,m_TranslationOrigin,m_Home;
float m_TranslationDistance=0,m_Stable=-1,m_Deadline=240000,m_StageDeadline=0;
int m_State=State::CLIMB,m_Token=123,m_TranslationUpdates=10,cancelled=0,landings=0,starts=0;
Controller m_Controller;Simulation m_Simulation;string reason;
bool Start(ERR_GameMode,Vehicle,SCR_AIGroup,IEntity,PilotCompartmentSlot,ERR_AircraftFlightEnvelope,int,float){m_StartAttempted=true;starts++;return true;}
void Cancel(string detail){reason=detail;cancelled++;m_State=State::LANDING;}
bool Landing(float){landings++;m_State=State::LANDING;return true;}
'''+methods+r'''
};
int main(){
assert(Rules::Target({0,0,0},{3,999,4})); // API is explicitly horizontal
assert(!Rules::Target({0,0,0},{3.01f,0,4}));
assert(!Rules::Target({0,0,0},{NAN,0,0}));
assert(Rules::Reachable(4)&&Rules::Reachable(4.04f)&&!Rules::Reachable(4.06f));
assert(!Rules::Reachable(-.01f)&&!Rules::Reachable(NAN));
for(float error:{-100.f,-4.f,-.5f,0.f,.5f,4.f,100.f})for(float velocity:{-100.f,-1.f,0.f,1.f,100.f}){
vector demand=Rules::Demand({error,9,error},{0,1,0},{velocity,100,velocity});
assert(demand.Length()<=.030001f&&demand.y==0);
}
vector brake=Rules::Demand({0,0,0},{0,8,0},{1,0,0});assert(brake.x<0&&brake.z==0);
assert(Rules::Held(false,8,.2f,.1f,1,.1f,.5f,3.5f,4));
assert(!Rules::Held(true,8,.2f,.1f,1,.1f,.5f,3.5f,4));
assert(!Rules::Held(false,8,.2f,.1f,1,.3f,.5f,3.5f,4));
assert(!Rules::Held(false,8,.2f,.1f,1,.1f,.76f,3.5f,4));
assert(!Rules::Held(false,8,.2f,.1f,1,.1f,.5f,3.24f,4));
assert(!Rules::Held(false,10,.2f,.1f,1,.1f,.5f,3.5f,4));
// Invalid absolute targets cannot reach native Start; valid target copies once.
Probe invalid;ERR_AircraftFlightEnvelope q;
assert(!invalid.StartTranslation(1,1,1,1,1,q,123,10000,{6,0,0})&&invalid.starts==0&&!invalid.m_TranslationRequested);
Probe stale;q.valid=false;assert(!stale.StartTranslation(1,1,1,1,1,q,123,10000,{0,0,0})&&stale.starts==0);q.valid=true;
Probe immutable;vector target={3,100,0};assert(immutable.StartTranslation(1,1,1,1,1,q,123,10000,target));
target.x=0;assert(immutable.m_TranslationTarget.x==3&&immutable.m_TranslationTarget.y==8&&immutable.starts==1);
assert(!immutable.StartTranslation(1,1,1,1,1,q,123,10000,{0,0,0})&&immutable.starts==1);
// Natural hover drift may make an accepted preflight target unreachable. Land,
// never shift its target/home or extend the original deadline to force success.
Probe drift;drift.m_TranslationRequested=true;drift.m_TranslationTarget={0,8,0};
assert(!drift.BeginTranslation(1000,{4.1f,8,0})&&drift.cancelled==1&&!drift.m_TranslationStarted);
assert(drift.m_State==State::LANDING&&drift.m_TranslationTarget.x==0&&drift.m_Home.x==0&&drift.m_Deadline==240000);
Probe noSignals;noSignals.m_TranslationRequested=true;noSignals.m_Controller.signals.present=false;
assert(!noSignals.BeginTranslation(1000,{3,8,0})&&noSignals.cancelled==1&&!noSignals.m_TranslationStarted);
Probe move;move.m_TranslationRequested=true;move.m_TranslationTarget={0,8,0};move.m_Deadline=42000;
assert(move.BeginTranslation(1000,{3.3f,8,0}));assert(move.m_State==State::TRANSLATING&&move.m_StageDeadline==42000&&move.m_Deadline==42000);
assert(move.m_TranslationOrigin.x==3.3f&&move.m_TranslationTarget.x==0);
for(int i=0;i<5;i++)move.CheckTranslation(2000+i*1000,{.5f,8,0},8,0,1);
assert(!move.m_TranslationProven&&move.landings==0);
move.CheckTranslation(7000,{.5f,8,0},8,0,1);assert(move.m_TranslationProven&&move.landings==1);
Probe interrupted;interrupted.m_TranslationRequested=true;assert(interrupted.BeginTranslation(1000,{3.3f,8,0}));
interrupted.CheckTranslation(2000,{.5f,8,0},8,0,1);interrupted.CheckTranslation(5000,{2,8,0},8,0,1);
assert(interrupted.m_Stable==-1);interrupted.CheckTranslation(6000,{.5f,8,0},8,0,1);
interrupted.CheckTranslation(10000,{.5f,8,0},8,0,1);assert(!interrupted.m_TranslationProven);
Probe insufficient;insufficient.m_TranslationRequested=true;assert(insufficient.BeginTranslation(1000,{3.3f,8,0}));
insufficient.m_TranslationUpdates=2;insufficient.CheckTranslation(9000,{.5f,8,0},8,0,1);assert(insufficient.m_Stable==-1);
}
''')
source=SOURCE.read_text(); prepare=body(SOURCE,'void PrepareActions('); tick=body(SOURCE,'void Tick(')
assert 'Math.Clamp(vector.Dot(demand, transform[2]), -0.03, 0.03)' in prepare
assert 'Math.Clamp(vector.Dot(demand, transform[0]), -0.03, 0.03)' in prepare
assert prepare.index('!ControlLease()') < prepare.index('ERR_AircraftTranslationRules.Demand(')
assert 'm_State == ERR_AircraftFlightState.TRANSLATING' in prepare
assert tick.index('ERR_AircraftFlightRules.Envelope(') < tick.index('CheckTranslation(')
assert 'if (m_TranslationRequested) BeginTranslation(now, transform[3]);' in tick
assert 'else Landing(now);' in tick
assert source.count('m_TranslationTarget = target')==1
for method in (body(SOURCE,'protected bool BeginTranslation('),body(SOURCE,'protected void CheckTranslation(')):
assert 'm_Home =' not in method and 'm_Deadline =' not in method
assert 'm_TranslationTarget =' not in method
assert 'SetVelocity' not in method and 'SetWorldTransform' not in method
print('PASS: actual immutable target, bounded autohover demand, drift refusal, five-second hold and unchanged qualification/deadline')