Files
EscapistsReintroduced/tests/test_aircraft_route_policy.py
T

104 lines
10 KiB
Python

"""Actual route entry/stage/cancellation bodies with physical/native boundaries stubbed."""
from policy_harness import ROOT, body, run
P=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftRouteFlight.c'
BASE=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFlight.c'
ENV=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftRouteEnvelope.c'
def cpp(s):
s=s.replace('protected ','').replace('override ','').replace('super.', 'Base::')
for a,b in [('ERR_AircraftFlightState.','State::'),('ERR_AircraftFlightRules.','FlightRules::'),('ERR_AircraftRouteRules.','RouteRules::'),('Math.','Math::'),('vector.','vector::')]:s=s.replace(a,b)
s=s.replace('ERR_AircraftRouteEnvelope route','ERR_AircraftRouteEnvelope* route').replace('SignalsManagerComponent signals','SignalsManagerComponent* signals')
for var in ('route','m_Route','signals'):s=s.replace(var+'.',var+'->')
return s
methods='\n'.join(cpp(body(P,key)) for key in ('bool StartRoute(', 'protected bool RouteContains(', 'protected override bool Grounded(', 'protected override bool Landing(', 'protected override bool BeginTranslation(', 'protected override void CheckTranslation(', 'protected override bool ControlLease('))
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 Clamp(float a,float lo,float hi){return std::clamp(a,lo,hi);}static float Min(float a,float b){return std::min(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+(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 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);}};
struct FlightRules {
'''+cpp(body(BASE,'static bool Hover('))+cpp(body(BASE,'static int CancelAction('))+r'''
};
struct RouteRules {
'''+''.join(cpp(body(ENV,k)) for k in ('static bool Length(', 'static vector Nearest(', 'static bool Contains(', 'static bool Held('))+r'''
};
struct ERR_AircraftRouteEnvelope {vector home={0,0,0},target={20,0,0};vector Home(){return home;}vector Destination(){return target;}bool Contains(vector p,float upright){return RouteRules::Contains(home,target,p,upright);}};
struct SignalsManagerComponent {bool present=true;int FindSignal(const char*){return present?0:-1;}};
struct Controller {bool hover=true,engine=true;SignalsManagerComponent signals;explicit operator bool()const{return true;}SignalsManagerComponent* GetSignalsManager(){return &signals;}bool GetAutohoverEnabled(){return hover;}bool IsEngineOn(){return engine;}};
struct Simulation {vector velocity;bool contact=false;vector GetVelocity(){return velocity;}bool HasAnyGroundContact(){return contact;}};
struct Aircraft {vector origin;float upright=1;vector GetOrigin(){return origin;}void GetWorldTransform(vector (&t)[4]){t[3]=origin;t[1]={0,upright,0};}};
struct Base {
bool m_StartAttempted=false,m_TranslationRequested=false,m_TranslationStarted=false,m_TranslationProven=false,m_Readback=true,m_Cancelled=false;
vector m_TranslationTarget,m_TranslationOrigin,m_Home;
float m_TranslationDistance=0,m_Stable=-1,m_Deadline=240000,m_StageDeadline=200000,clock=1000;
int m_State=State::CLIMB,m_Token=123,m_TranslationUpdates=10,landings=0,starts=0,failures=0,relinquished=0,restored=0;
bool owner=true,healthy=true,leased=true,grounded=false;
Controller m_Controller;Simulation m_Simulation;Aircraft m_Aircraft;string m_Reason;
bool Start(ERR_GameMode,Vehicle,SCR_AIGroup,IEntity,PilotCompartmentSlot,ERR_AircraftRouteEnvelope* q,int,float){m_StartAttempted=true;starts++;m_Home=q->Home();return true;}
bool Grounded(){return grounded;}bool Landing(float now){landings++;m_State=State::LANDING;m_Stable=-1;m_StageDeadline=Math::Min(m_Deadline,now+65000);return true;}
bool Owns(){return owner;}bool Healthy(){return healthy;}bool Lease(){return leased;}float Now(){return clock;}void Fail(string r){m_Reason=r;failures++;m_State=State::FAILED;}void Relinquish(string r){m_Reason=r;relinquished++;m_State=State::RELINQUISHED;}void RecordState(){}void Release(bool){restored++;}void Shutdown(float){m_State=State::SHUTDOWN;}
};
struct Probe:Base {
ERR_AircraftRouteEnvelope* m_Route=nullptr;vector m_LandingPoint;bool m_RouteArrived=false;
'''+methods+cpp(body(BASE,'void Cancel('))+r'''
};
int main(){
ERR_AircraftRouteEnvelope q;Probe invalid;assert(!invalid.StartRoute(1,1,1,1,1,nullptr,3,200000)&&invalid.starts==0);
q.target={21,0,0};assert(!invalid.StartRoute(1,1,1,1,1,&q,3,200000)&&invalid.starts==0);q.target={20,0,0};
Probe flight;assert(flight.StartRoute(1,1,1,1,1,&q,3,200000)&&flight.starts==1&&flight.m_TranslationRequested);
assert(flight.m_TranslationTarget.x==20&&flight.m_TranslationTarget.y==8&&flight.m_Home.x==0);
assert(!flight.StartRoute(1,1,1,1,1,&q,3,200000)&&flight.starts==1);
assert(!flight.RouteContains({9,8,0},1));assert(flight.BeginTranslation(1000,{2,8,0}));assert(flight.m_StageDeadline==161000&&flight.m_Deadline==240000);
assert(flight.RouteContains({19,8,0},1)&&!flight.RouteContains({10,8,8},1));
flight.m_Aircraft.origin={19.5f,8,0};
flight.CheckTranslation(2000,flight.m_Aircraft.origin,8,0,1);flight.CheckTranslation(5000,{18,8,0},8,0,1);assert(flight.m_Stable==-1);
flight.CheckTranslation(6000,flight.m_Aircraft.origin,8,0,1);flight.CheckTranslation(10000,flight.m_Aircraft.origin,8,0,1);assert(!flight.m_RouteArrived);
flight.CheckTranslation(11000,flight.m_Aircraft.origin,8,0,1);assert(flight.m_RouteArrived&&flight.m_TranslationProven&&flight.m_LandingPoint.x==20&&flight.landings==1);
flight.grounded=true;flight.m_Aircraft.origin={19,0,0};assert(!flight.Grounded());flight.m_Aircraft.origin={19.5f,0,0};assert(flight.Grounded());
Probe limited;limited.m_Deadline=60000;assert(limited.StartRoute(1,1,1,1,1,&q,3,60000));assert(limited.BeginTranslation(1000,{2,8,0}));assert(limited.m_StageDeadline==60000&&limited.m_Deadline==60000);
// Same parent cancellation body calls route Landing: freeze current centre,
// preserve absolute deadline and never move it on repeated cancellation.
Probe cancel;assert(cancel.StartRoute(1,1,1,1,1,&q,3,200000));assert(cancel.BeginTranslation(1000,{2,8,0}));cancel.m_Aircraft.origin={10,8,1};
cancel.Cancel("radio lost");assert(cancel.m_State==State::LANDING&&cancel.m_Cancelled&&cancel.m_LandingPoint.x==10&&cancel.m_LandingPoint.z==1&&cancel.m_Deadline==240000&&cancel.landings==1);
float stage=cancel.m_StageDeadline;cancel.clock=9000;cancel.m_Aircraft.origin={12,8,1};cancel.Cancel("again");assert(cancel.m_LandingPoint.x==10&&cancel.landings==1&&cancel.m_StageDeadline==stage);
// Shared native write gate refuses stale ownership, expiry and corridor escape.
Probe capture;capture.m_Route=&q;capture.owner=false;assert(!capture.ControlLease()&&capture.relinquished==1);
Probe expiry;expiry.m_Route=&q;expiry.clock=240000;assert(!expiry.ControlLease()&&expiry.failures==1);
Probe drift;drift.m_Route=&q;drift.m_TranslationStarted=true;drift.m_State=State::TRANSLATING;drift.m_Aircraft.origin={10,8,8};assert(!drift.ControlLease()&&drift.failures==1);
Probe healthy;healthy.m_Route=&q;healthy.m_TranslationStarted=true;healthy.m_State=State::TRANSLATING;healthy.m_Aircraft.origin={10,8,0};assert(healthy.ControlLease());
Probe ownershipCancel;ownershipCancel.m_Route=&q;ownershipCancel.owner=false;ownershipCancel.Cancel("captured");assert(ownershipCancel.relinquished==1&&ownershipCancel.landings==0&&ownershipCancel.restored==0);
}
''')
s=P.read_text()
assert 'class ERR_AircraftRouteFlight : ERR_AircraftFlight' in s
for unchanged in ('protected bool Owns(', 'protected bool Healthy(', 'protected bool Lease(', 'protected void Release(', 'void Cancel('):assert unchanged not in s
assert 'return super.Start(' in body(P,'bool StartRoute(')
assert 's_Owners' not in s, 'Use parent shared airframe registry, never another exclusivity domain'
prepare=body(P,'override void PrepareActions(')
assert prepare.index('!ControlLease()')<prepare.index('SetActionValue') if 'SetActionValue' in prepare else prepare.index('!ControlLease()')<prepare.index('Pair(')
assert 'target = m_LandingPoint' in prepare and 'ERR_AircraftTranslationRules.Demand(target,' in prepare
assert 'forward = 0; aside = 0;' in prepare
assert 'SetWorldTransform' not in s and 'SetVelocity' not in s and 'SetActive' not in s
# The large native startup/tick sequence is preserved verbatim except route geometry.
expected=body(BASE,'void Tick(').replace('void Tick(', 'override void Tick(',1).replace('ERR_AircraftFlightRules.Envelope(height, vector.DistanceXZ(transform[3], m_Home), transform[1][1])','RouteContains(transform[3], transform[1][1])')
assert body(P,'override void Tick(')==expected
print('PASS: actual route entry, fixed corridor/target, hover hold, cancellation landing freeze, shared lease/capture/deadline gate and unchanged native startup/tick sequence')
# Native compile exposed a getter return-path diagnostic. Keep the actual
# defensive getter executable, including its default pre-start observation.
run(r'''#include <cassert>
#include <cstdio>
struct vector {float x=0,y=0,z=0;vector(float a,float b,float c):x(a),y(b),z(c){}vector(const char* s){sscanf(s,"%f %f %f",&x,&y,&z);}};
struct Route {vector Destination(){return {20,3,4};}};
struct Observer {Route* m_Route=nullptr;
'''+cpp(body(P,'vector Destination('))+r'''
};
int main(){Observer p;auto absent=p.Destination();assert(absent.x==0&&absent.y==0&&absent.z==0);Route q;p.m_Route=&q;auto value=p.Destination();assert(value.x==20&&value.y==3&&value.z==4);}
''')
assert "m_StageDeadline = Math.Min(m_Deadline, now + 160000);" in body(P,"protected override bool BeginTranslation(")
assert "m_Deadline =" not in body(P,"protected override bool BeginTranslation(")