222 lines
26 KiB
Python
222 lines
26 KiB
Python
"""Execute prototype flight methods with real rules; native boundaries are fakes."""
|
|
from policy_harness import ROOT, body, run
|
|
import re
|
|
P=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFieldFlight.c'
|
|
B=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFlight.c'
|
|
R=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFieldRules.c'
|
|
source=P.read_text()
|
|
def cpp(s):
|
|
s=s.replace('protected ','').replace('override ','').replace('ref ','').replace('super.','Base::')
|
|
for old,new in [('ERR_AircraftFlightState.','State::'),('ERR_AircraftFlightRules.','FlightRules::'),('ERR_AircraftFieldRules.','Rules::'),('ERR_AircraftTranslationRules.','TranslationRules::'),('Math.','Math::'),('vector.','vector::')]:s=s.replace(old,new)
|
|
s=s.replace('ERR_AircraftFieldEnvelope m_Field;', 'ERR_AircraftFieldEnvelope* m_Field=nullptr;')
|
|
s=s.replace('FuelManagerComponent fuel = FuelManagerComponent.Cast(aircraft.FindComponent(FuelManagerComponent));','FuelManagerComponent* fuel = FixtureFuel(aircraft);')
|
|
s=s.replace('fuel.', 'fuel->').replace('m_Fuel.', 'm_Fuel->')
|
|
s=s.replace('ERR_AircraftFieldEnvelope field','ERR_AircraftFieldEnvelope* field').replace('ERR_AircraftFlightEnvelope qualification','int qualification')
|
|
s=s.replace('SignalsManagerComponent signals','SignalsManagerComponent* signals').replace('ActionManager actions','ActionManager* actions')
|
|
for name in ('field','m_Field','signals','actions'):s=s.replace(name+'.',name+'->')
|
|
s=s.replace('"0 0 0"','vector{}').replace('out vector nextIntegral','vector& nextIntegral')
|
|
return s
|
|
rules=cpp(R.read_text().replace('class ERR_AircraftFieldRules','struct Rules')).rstrip()+';'
|
|
fields=cpp(source[source.index(' protected ref'):source.index(' int FieldPhase()')])
|
|
# Explicit defaults emulate Enforce's zero-initialized script fields.
|
|
fields=re.sub(r'(bool m_\w+);',r'\1=false;',fields)
|
|
fields=re.sub(r'(int m_\w+);',r'\1=0;',fields)
|
|
methods='\n'.join(cpp(body(P,k)) for k in (
|
|
'protected bool FuelAdmission(', 'bool StartField(', 'override bool Start(', 'override bool StartTranslation(',
|
|
'protected bool FieldContains(', 'protected override bool Grounded(',
|
|
'protected bool BeginFieldBrake(', 'protected override bool Landing(',
|
|
'protected override bool BeginTranslation(', 'protected override bool ControlLease(',
|
|
'protected vector FieldDemand(', 'override void PrepareActions(',
|
|
'protected override void CheckTranslation(', 'override void Tick('))
|
|
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;using CharacterControllerComponent=int;
|
|
namespace State {enum {IDLE,HOOKS,STARTING,POSITIVE,NEGATIVE,CLIMB,LANDING,SHUTDOWN,COMPLETE,CANCELLED,FAILED,RELINQUISHED,TRANSLATING};}
|
|
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 Min(float a,float b){return std::min(a,b);}static float Max(float a,float b){return std::max(a,b);}static float Sqrt(float a){return std::sqrt(a);}};
|
|
'''+rules+r'''
|
|
struct FlightRules {
|
|
'''+''.join(cpp(body(B,k)) for k in ('static bool Hover(', 'static float Vertical(', 'static int CancelAction('))+r'''
|
|
};
|
|
struct TranslationRules {
|
|
'''+cpp(body(B,'static vector Demand('))+r'''
|
|
};
|
|
struct ERR_AircraftFieldEnvelope {vector home={0,0,0},destination={100,0,0};bool available=true;vector Home(){return home;}vector Destination(){return destination;}bool Contains(vector p,float u){return available&&Rules::Contains(home,destination,p,u);}};
|
|
struct SignalsManagerComponent {bool available=true;int FindSignal(const char*){return available?1:-1;}};
|
|
struct Controller {bool hover=true,engine=true,enableAllowed=true;int hoverWrites=0;SignalsManagerComponent signals;explicit operator bool()const{return true;}SignalsManagerComponent* GetSignalsManager(){return &signals;}bool GetAutohoverEnabled(){return hover;}void SetAutohoverEnabled(bool enabled){hoverWrites++;if(enableAllowed)hover=enabled;}bool IsEngineOn(){return engine;}};
|
|
struct Simulation {vector velocity,angular;bool contact=false;vector GetVelocity(){return velocity;}vector GetAngularVelocityLocal(){return angular;}bool HasAnyGroundContact(){return contact;}};
|
|
struct Aircraft {vector origin={0,8,0};float upright=1;vector GetOrigin(){return origin;}vector GetYawPitchRoll(){return {};}void GetWorldTransform(vector (&t)[4]){t[0]={1,0,0};t[1]={0,upright,0};t[2]={0,0,1};t[3]=origin;}};
|
|
struct ActionManager {bool active=true;int activated=0;bool IsContextActive(const char*){return active;}void ActivateContext(const char*,int){activated++;}};
|
|
struct FuelManagerComponent {float total=100;bool present=true;bool HasFuel(){return present&&total>0;}float GetTotalFuel(){return total;}};
|
|
FuelManagerComponent fuelBoundary;FuelManagerComponent* FixtureFuel(int aircraft){return aircraft==2?&fuelBoundary:nullptr;}
|
|
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=240000,m_LastControl=0,m_LastTick=-1,m_Collective=-.8,m_Heading=0,clock=0;
|
|
int m_State=State::IDLE,m_Token=0,m_TranslationUpdates=0,m_ActionUpdates=0,m_HelicopterUpdates=0,m_Character=11,m_Pilot=12;
|
|
FuelManagerComponent* m_Fuel=&fuelBoundary;Controller m_Controller;Simulation m_Simulation;Aircraft m_Aircraft;ActionManager manager;ActionManager* m_Actions=&manager;string m_Reason;
|
|
bool owner=true,healthy=true,leased=true,grounded=false,pairsOkay=true,startOkay=true;
|
|
int starts=0,claims=0,landings=0,failures=0,relinquished=0,releases=0,shutdowns=0,pairs=0,baseInputs=0,baseTicks=0;float collectiveWrite=0,forwardWrite=0,asideWrite=0;
|
|
bool Start(ERR_GameMode,Vehicle,SCR_AIGroup,IEntity,PilotCompartmentSlot,ERR_AircraftFieldEnvelope* q,int token,float deadline){m_StartAttempted=true;starts++;if(!startOkay)return false;claims++;m_Home=q->Home();m_Token=token;m_Deadline=deadline;m_State=State::HOOKS;return true;}
|
|
virtual bool Grounded(){return grounded;}virtual bool Landing(float now){landings++;m_State=State::LANDING;m_Stable=-1;m_StageDeadline=Math::Min(m_Deadline,now+65000);return true;}
|
|
virtual bool BeginTranslation(float,vector){assert(false);return false;}
|
|
bool Owns(){return owner;}bool Healthy(){return healthy;}bool Lease(){return leased&&!Finished();}float Now(){return clock;}bool Finished(){return m_State>=State::COMPLETE&&m_State<=State::RELINQUISHED;}
|
|
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 Release(bool){releases++;}void RecordState(){}void Shutdown(float now){shutdowns++;m_State=State::SHUTDOWN;m_StageDeadline=Math::Min(m_Deadline,now+15000);m_Stable=-1;m_Controller.engine=false;}
|
|
bool Pair(const char* p,const char*,float value){pairs++;string name=p;if(name=="HelicopterCollectiveIncrease")collectiveWrite=value;if(name=="HelicopterCyclicForward")forwardWrite=value;if(name=="HelicopterCyclicRight")asideWrite=value;return pairsOkay;}
|
|
void PrepareActions(int,int,ActionManager*,float){baseInputs++;}void Tick(){baseTicks++;}
|
|
'''+cpp(body(B,'void Cancel('))+r'''
|
|
};
|
|
struct Flight:Base {
|
|
'''+fields+methods+r'''
|
|
};
|
|
void started(Flight& f,ERR_AircraftFieldEnvelope& q){assert(f.StartField(1,2,3,4,5,&q,77,240000));f.m_State=State::CLIMB;f.clock=60000;f.m_LastControl=f.clock;assert(f.BeginTranslation(f.clock,{0,8,0}));}
|
|
bool near(float a,float b){return std::abs(a-b)<.0001f;}
|
|
int main(){
|
|
ERR_AircraftFieldEnvelope q;
|
|
Flight fuelCheck;assert(!fuelCheck.FuelAdmission(0)&&!fuelCheck.FuelAdmission(999));fuelBoundary.total=49.99f;assert(!fuelCheck.StartField(1,2,3,4,5,&q,77,240000)&&fuelCheck.starts==0&&fuelCheck.claims==0&&!fuelCheck.m_Field);
|
|
fuelBoundary.total=50;assert(fuelCheck.FuelAdmission(2));fuelBoundary.present=false;assert(!fuelCheck.FuelAdmission(2));fuelBoundary.present=true;fuelBoundary.total=100;
|
|
Flight refuse;assert(!refuse.StartField(1,2,3,4,5,nullptr,77,240000)&&refuse.starts==0);
|
|
q.destination={101,0,0};assert(!refuse.StartField(1,2,3,4,5,&q,77,240000)&&refuse.starts==0);q.destination={100,0,0};
|
|
assert(!refuse.StartField(1,2,3,4,5,&q,77,238999)&&refuse.starts==0);
|
|
assert(!refuse.Start(1,2,3,4,5,1,77,240000));assert(!refuse.StartTranslation(1,2,3,4,5,1,77,240000,{100,0,0}));
|
|
Flight f;started(f,q);assert(f.starts==1&&f.claims==1&&f.m_Field==&q&&f.m_TranslationTarget[0]==100&&f.m_TranslationTarget[1]==8&&f.m_Token==77&&f.m_Deadline==240000);
|
|
assert(!f.StartField(1,2,3,4,5,&q,78,240000)&&f.starts==1);
|
|
assert(f.m_State==State::TRANSLATING&&f.m_FieldPhase==1&&f.m_StageDeadline==220000);
|
|
f.CheckTranslation(61000,{-0.23f,8,0},8,0,1);assert(f.m_FieldPhase==1); // actual post-hover distance can exceed100m
|
|
vector demand=f.FieldDemand({0,8,0},{},.1f);assert(f.m_FieldReference[0]>0&&f.m_FieldReference.Length()<=.020001f&&demand.Length()<=.080001f);
|
|
f.m_FieldReference={2,0,0};f.m_FieldIntegral={1,0,0};f.m_Aircraft.origin={80,8,0};f.m_Simulation.velocity={2,0,0};
|
|
f.CheckTranslation(89000,f.m_Aircraft.origin,8,2,1);assert(f.m_FieldPhase==1);
|
|
f.m_Aircraft.origin={82,8,0};
|
|
f.CheckTranslation(90000,f.m_Aircraft.origin,8,2,1);assert(f.m_FieldPhase==2&&f.m_FieldStopFrozen&&f.m_FieldLandingPoint[0]==100&&f.m_State==State::TRANSLATING&&f.landings==0&&f.m_StageDeadline==135000);
|
|
float brakeDeadline=f.m_StageDeadline;assert(f.m_FieldIntegral.Length()==0&&f.m_FieldReference.Length()==2);
|
|
auto firstBrake=f.FieldDemand(f.m_Aircraft.origin,f.m_Simulation.velocity,.1f);assert(f.m_FieldReference.Length()<2&&f.m_FieldReference.Length()>=1.9799f&&firstBrake.Length()<=.080001f);
|
|
float previous=f.m_FieldReference.Length();for(int i=0;i<20;i++){f.FieldDemand({85,8,0},{1.9f,0,0},.1f);assert(f.m_FieldReference.Length()<=previous+.000001f);previous=f.m_FieldReference.Length();}
|
|
auto savedIntegral=f.m_FieldIntegral;auto savedReference=f.m_FieldReference;
|
|
f.clock=91000;f.Cancel("stop during brake");assert(f.m_Cancelled&&f.m_FieldLandingPoint[0]==100&&f.m_StageDeadline==brakeDeadline&&f.landings==0);
|
|
f.clock=92000;f.Cancel("duplicate");assert(f.m_Reason=="stop during brake"&&f.m_StageDeadline==brakeDeadline&&f.m_Token==77&&(f.m_FieldReference-savedReference).Length()==0&&(f.m_FieldIntegral-savedIntegral).Length()==0);
|
|
for(int i=0;i<100;i++)f.FieldDemand(f.m_Aircraft.origin,f.m_Simulation.velocity,.1f);
|
|
assert(f.m_FieldReference.Length()<=.00001f);
|
|
f.m_Simulation.velocity={.1f,0,0};f.CheckTranslation(92000,{99.6f,8,0},8,.1f,1);f.CheckTranslation(93999,{99.6f,8,0},8,.1f,1);assert(f.m_FieldPhase==2);
|
|
f.m_Simulation.velocity={.3f,0,0};f.CheckTranslation(94000,{99.6f,8,0},8,.3f,1);assert(f.m_FieldLowSince<0);
|
|
f.m_Simulation.velocity={.1f,0,0};f.CheckTranslation(95000,{99.6f,8,0},8,.1f,1);f.CheckTranslation(97000,{99.6f,8,0},8,.1f,1);
|
|
assert(f.m_FieldPhase==3&&f.m_FieldIntegral.Length()==0&&f.m_FieldReference.Length()==0&&f.m_StageDeadline==brakeDeadline&&f.landings==0);
|
|
f.CheckTranslation(101999,{99.6f,8,0},8,.1f,1);assert(f.landings==0);
|
|
f.CheckTranslation(102000,{99.6f,8,0},8,.1f,1);assert(f.landings==1&&f.m_State==State::LANDING&&!f.m_FieldArrived&&!f.m_TranslationProven&&f.m_StageDeadline==127000&&f.m_Deadline==240000);
|
|
// A slow/unresponsive body cannot settle while a large slewed reference remains.
|
|
Flight lag;started(lag,q);lag.m_Aircraft.origin={50,8,0};lag.m_Simulation.velocity={1,0,0};lag.m_FieldReference={2,0,0};lag.m_FieldIntegral={4,0,0};lag.clock=70000;lag.Cancel("lag stop");
|
|
assert(lag.m_FieldIntegral.Length()==0&&lag.m_FieldReference[0]==2);
|
|
auto lagPoint=lag.m_FieldLandingPoint;auto lagDeadline=lag.m_StageDeadline;
|
|
vector lagDemand=lag.FieldDemand(lag.m_Aircraft.origin,lag.m_Simulation.velocity,.1f);
|
|
assert(near(lag.m_FieldReference[0],1.98f)&&lagDemand[0]>0); // preserve, disclose native lag risk
|
|
lag.m_Simulation.velocity={.1f,0,0};lag.CheckTranslation(70000,lag.m_Aircraft.origin,8,.1f,1);lag.CheckTranslation(72000,lag.m_Aircraft.origin,8,.1f,1);assert(lag.m_FieldPhase==2);
|
|
for(int i=0;i<110;i++)lag.FieldDemand(lag.m_Aircraft.origin,lag.m_Simulation.velocity,.1f);
|
|
lag.CheckTranslation(73000,lag.m_Aircraft.origin,8,.1f,1);assert(lag.m_FieldPhase==3&&lag.m_FieldLandingPoint[0]==lagPoint[0]&&lag.m_StageDeadline==lagDeadline);
|
|
// Reversed native velocity does not reverse the braking reference toward the target.
|
|
Flight opposite;started(opposite,q);opposite.m_Aircraft.origin={50,8,0};opposite.m_Simulation.velocity={-1,0,0};opposite.m_FieldReference={-2,0,0};opposite.m_FieldIntegral={-3,0,0};opposite.clock=70000;opposite.Cancel("opposing stop");
|
|
assert(opposite.m_FieldIntegral.Length()==0&&opposite.m_FieldLandingPoint[0]<50);
|
|
for(int i=0;i<110;i++){
|
|
vector before=opposite.m_FieldReference;opposite.FieldDemand({60,8,0},opposite.m_Simulation.velocity,.1f);
|
|
assert(opposite.m_FieldReference[0]<=.000001f&&opposite.m_FieldReference.Length()<=before.Length()+.000001f&&(opposite.m_FieldReference-before).Length()<=.020001f);
|
|
}
|
|
assert(opposite.m_FieldReference.Length()<=.00001f);
|
|
// Below-threshold jitter resets the observed hold only when speed reaches .25.
|
|
Flight jitter;started(jitter,q);jitter.m_Aircraft.origin={99.8f,8,0};assert(jitter.BeginFieldBrake(80000));
|
|
jitter.m_Simulation.velocity={.249f,0,0};jitter.CheckTranslation(80000,jitter.m_Aircraft.origin,8,.249f,1);
|
|
jitter.m_Simulation.velocity={.25f,0,0};jitter.CheckTranslation(81999,jitter.m_Aircraft.origin,8,.25f,1);assert(jitter.m_FieldLowSince<0);
|
|
jitter.m_Simulation.velocity={0,0,0};jitter.CheckTranslation(82000,jitter.m_Aircraft.origin,8,0,1);jitter.CheckTranslation(83999,jitter.m_Aircraft.origin,8,0,1);assert(jitter.m_FieldPhase==2);jitter.CheckTranslation(84000,jitter.m_Aircraft.origin,8,0,1);assert(jitter.m_FieldPhase==3);
|
|
// A ground-assisted stop cannot qualify or resume the airborne braking profile.
|
|
Flight contactAdmission;started(contactAdmission,q);contactAdmission.m_Simulation.contact=true;
|
|
assert(!contactAdmission.BeginFieldBrake(70000)&&contactAdmission.m_State==State::FAILED&&!contactAdmission.m_FieldStopFrozen&&contactAdmission.m_Controller.hoverWrites==0);
|
|
Flight contactWrite;started(contactWrite,q);assert(contactWrite.BeginFieldBrake(70000));contactWrite.m_Simulation.contact=true;
|
|
contactWrite.PrepareActions(contactWrite.m_Character,contactWrite.m_Pilot,contactWrite.m_Actions,.1f);
|
|
assert(contactWrite.m_State==State::FAILED&&contactWrite.pairs==0&&!contactWrite.m_FieldArrived);
|
|
contactWrite.m_Simulation.contact=false;contactWrite.Tick();assert(contactWrite.m_State==State::FAILED&&contactWrite.landings==0);
|
|
Flight contactStop;started(contactStop,q);assert(contactStop.BeginFieldBrake(70000));contactStop.m_FieldLowSince=70000;contactStop.m_Simulation.contact=true;
|
|
contactStop.CheckTranslation(73000,{99.8f,0,0},0,0,1);
|
|
assert(contactStop.m_State==State::FAILED&&contactStop.m_FieldPhase==2&&!contactStop.m_FieldArrived&&contactStop.landings==0);
|
|
// Real contact remains valid only after the existing airborne hold admitted landing.
|
|
Flight permittedLanding;started(permittedLanding,q);permittedLanding.m_FieldPhase=3;permittedLanding.m_State=State::LANDING;permittedLanding.m_Simulation.contact=true;
|
|
assert(permittedLanding.ControlLease()&&permittedLanding.failures==0);
|
|
// Grounded completion requires exact frozen centre, stable native contact and shutdown.
|
|
f.grounded=true;f.m_Aircraft.origin={98,0,0};assert(!f.Grounded());f.m_Aircraft.origin={99.6f,0,0};assert(f.Grounded());
|
|
f.clock=103000;f.m_LastControl=f.clock;f.Tick();f.clock=104000;f.m_LastControl=f.clock;f.Tick();f.clock=104999;f.m_LastControl=f.clock;f.Tick();assert(f.shutdowns==0);
|
|
f.clock=105000;f.m_LastControl=f.clock;f.Tick();assert(f.shutdowns==1&&f.m_State==State::SHUTDOWN);
|
|
f.clock=105100;f.m_LastControl=f.clock;f.Tick();assert(f.m_State==State::CANCELLED&&f.releases==1&&f.m_Token==77&&f.m_Deadline==240000);
|
|
// Uncancelled normal hold can produce COMPLETE only after native ground observations.
|
|
Flight normal;started(normal,q);normal.m_Aircraft.origin={99.7f,8,0};assert(normal.BeginFieldBrake(80000));
|
|
normal.CheckTranslation(80000,normal.m_Aircraft.origin,8,0,1);normal.CheckTranslation(82000,normal.m_Aircraft.origin,8,0,1);
|
|
normal.CheckTranslation(85000,{98,8,0},8,0,1);assert(normal.m_Stable<0&&!normal.m_FieldArrived);
|
|
normal.CheckTranslation(86000,normal.m_Aircraft.origin,8,0,1);normal.CheckTranslation(90999,normal.m_Aircraft.origin,8,0,1);assert(!normal.m_FieldArrived);
|
|
normal.CheckTranslation(91000,normal.m_Aircraft.origin,8,0,1);assert(normal.m_FieldArrived&&normal.m_TranslationProven&&normal.landings==1);
|
|
normal.m_Aircraft.origin={99.7f,0,0};normal.grounded=true;
|
|
for(int t=92000;t<=94100;t+=700){normal.clock=t;normal.m_LastControl=t;normal.Tick();}
|
|
assert(normal.m_State==State::SHUTDOWN);normal.clock=94200;normal.m_LastControl=normal.clock;normal.Tick();assert(normal.m_State==State::COMPLETE&&normal.releases==1);
|
|
// Cancelling during climb retains altitude/braking instead of invoking descent.
|
|
Flight climb;assert(climb.StartField(1,2,3,4,5,&q,77,240000));climb.m_State=State::CLIMB;climb.clock=30000;climb.m_Aircraft.origin={1,3,0};climb.Cancel("climb stop");
|
|
assert(climb.m_State==State::TRANSLATING&&climb.m_FieldPhase==2&&climb.landings==0&&!climb.m_TranslationStarted);
|
|
// Actual inherited Cancel may run airborne before startup enabled autohover.
|
|
for(int stage:{State::POSITIVE,State::NEGATIVE}){
|
|
Flight early;assert(early.StartField(1,2,3,4,5,&q,77,240000));early.m_State=stage;early.m_Controller.hover=false;early.clock=30000;early.m_Aircraft.origin={1,.2f,0};early.m_Simulation.velocity={.1f,0,0};
|
|
early.Cancel("pre-climb airborne stop");assert(early.m_Cancelled&&early.m_FieldPhase==2&&early.m_State==State::TRANSLATING&&early.m_Controller.hover&&early.m_Controller.hoverWrites==1&&early.failures==0&&early.landings==0);
|
|
auto frozen=early.m_FieldLandingPoint;auto stageDeadline=early.m_StageDeadline;early.PrepareActions(early.m_Character,early.m_Pilot,early.m_Actions,.1f);assert(early.pairs==4&&early.failures==0);
|
|
early.Cancel("duplicate");assert(early.m_Controller.hoverWrites==1&&early.m_FieldLandingPoint[0]==frozen[0]&&early.m_StageDeadline==stageDeadline&&early.m_Deadline==240000);
|
|
}
|
|
Flight refusedHover;started(refusedHover,q);refusedHover.m_Controller.hover=false;refusedHover.m_Controller.enableAllowed=false;refusedHover.Cancel("native hover refuses");assert(refusedHover.failures==1&&!refusedHover.m_FieldStopFrozen&&refusedHover.m_Controller.hoverWrites==1&&refusedHover.m_State==State::FAILED);
|
|
Flight capturedBrake;started(capturedBrake,q);capturedBrake.owner=false;capturedBrake.Cancel("captured before brake");assert(capturedBrake.relinquished==1&&capturedBrake.m_Controller.hoverWrites==0&&!capturedBrake.m_FieldStopFrozen);
|
|
// First cancellation freezes a velocity-projected qualified point exactly once.
|
|
Flight cancel;started(cancel,q);cancel.m_Aircraft.origin={30,8,0};cancel.m_Simulation.velocity={2,0,0};cancel.m_FieldReference={2,0,0};cancel.clock=70000;cancel.Cancel("first");
|
|
assert(cancel.m_State==State::TRANSLATING&&cancel.m_FieldPhase==2&&cancel.landings==0&&near(cancel.m_FieldLandingPoint[0],30+Rules::BrakingReserve(2)));
|
|
auto centre=cancel.m_FieldLandingPoint;float deadline=cancel.m_StageDeadline;cancel.clock=71000;cancel.m_Aircraft.origin={40,8,0};cancel.Cancel("again");assert(cancel.m_FieldLandingPoint[0]==centre[0]&&cancel.m_StageDeadline==deadline);
|
|
Flight settling;started(settling,q);settling.m_Aircraft.origin={99.7f,8,0};assert(settling.BeginFieldBrake(80000));settling.m_FieldPhase=3;settling.m_Stable=81000;settling.clock=82000;auto old=settling.m_StageDeadline;settling.Cancel("mid hold");assert(settling.failures==0&&settling.landings==0&&settling.m_State==State::TRANSLATING&&settling.m_StageDeadline==old);
|
|
// Cancellation cannot invent a stopping point beyond qualified space.
|
|
Flight outside;started(outside,q);outside.m_Aircraft.origin={100,8,0};outside.m_Simulation.velocity={2,0,0};outside.Cancel("too late");assert(outside.failures==1&&!outside.m_FieldStopFrozen&&outside.landings==0);
|
|
// Early abort is owner-tick based and cannot transition inherited input mid-call.
|
|
Flight abort;started(abort,q);abort.clock=155000;abort.m_LastControl=abort.clock;abort.m_Aircraft.origin={20,8,0};assert(abort.ControlLease()&&!abort.m_Cancelled);abort.Tick();assert(abort.m_Cancelled&&abort.m_FieldPhase==2&&abort.m_Deadline==240000&&abort.m_StageDeadline==200000);
|
|
Flight edge;started(edge,q);edge.clock=153499;edge.m_LastControl=edge.clock;edge.m_Aircraft.origin={20,8,0};edge.Tick();assert(!edge.m_Cancelled);edge.clock=153500;edge.m_LastControl=edge.clock;edge.Tick();assert(edge.m_Cancelled&&edge.m_Deadline==240000);
|
|
Flight low;started(low,q);low.m_Aircraft.origin={20,8,0};low.clock=70000;low.m_LastControl=low.clock;fuelBoundary.total=20;low.Tick();assert(low.m_Cancelled&&low.m_FieldPhase==2&&low.m_Reason=="field candidate low-fuel reserve reached");fuelBoundary.total=100;
|
|
Flight capture;started(capture,q);capture.owner=false;assert(!capture.ControlLease()&&capture.relinquished==1&&capture.pairs==0);
|
|
Flight expiry;started(expiry,q);expiry.clock=240000;assert(!expiry.ControlLease()&&expiry.failures==1);
|
|
Flight fast;started(fast,q);fast.m_Simulation.velocity={2.501f,0,0};assert(!fast.ControlLease()&&fast.failures==1);
|
|
Flight corridor;started(corridor,q);corridor.m_Aircraft.origin={50,8,8};assert(!corridor.ControlLease()&&corridor.failures==1);
|
|
// Exact callbacks only; no native input on invalid timestep or failed lease.
|
|
Flight input;started(input,q);input.m_Aircraft.origin={10,8,0};ActionManager other;
|
|
input.PrepareActions(999,input.m_Pilot,input.m_Actions,.1f);input.PrepareActions(input.m_Character,999,input.m_Actions,.1f);input.PrepareActions(input.m_Character,input.m_Pilot,&other,.1f);assert(input.pairs==0);
|
|
for(float dt:{0.0f,-1.0f,.201f,NAN}) { input.PrepareActions(input.m_Character,input.m_Pilot,input.m_Actions,dt); }
|
|
assert(input.pairs==0);
|
|
input.PrepareActions(input.m_Character,input.m_Pilot,input.m_Actions,.1f);assert(input.pairs==4&&input.m_ActionUpdates==1&&input.m_TranslationUpdates==1&&input.m_FieldReference.Length()<=.020001f);
|
|
input.m_State=State::LANDING;input.m_FieldPhase=3;input.m_FieldLandingPoint={10,0,0};input.m_Aircraft.origin={10,0,0};input.grounded=true;input.PrepareActions(input.m_Character,input.m_Pilot,input.m_Actions,.1f);assert(input.collectiveWrite==-1&&input.forwardWrite==0&&input.asideWrite==0);
|
|
input.owner=false;int writes=input.pairs;input.PrepareActions(input.m_Character,input.m_Pilot,input.m_Actions,.1f);assert(input.pairs==writes&&input.relinquished==1);
|
|
Flight inactive;started(inactive,q);inactive.manager.active=false;inactive.PrepareActions(inactive.m_Character,inactive.m_Pilot,inactive.m_Actions,.1f);assert(inactive.pairs==0&&!inactive.m_Readback&&inactive.manager.activated==1);
|
|
Flight gap;started(gap,q);gap.m_Aircraft.origin={99.7f,8,0};gap.BeginFieldBrake(80000);gap.m_FieldLowSince=81000;gap.m_Stable=81000;gap.m_LastTick=81000;gap.clock=83000;gap.m_LastControl=gap.clock;gap.Tick();assert(gap.m_FieldPhase==2&&gap.m_FieldLowSince==83000&&gap.m_Stable<0);
|
|
// Slow positioning/landing never consumes or retains the cruise PI output.
|
|
Flight slow;started(slow,q);slow.m_FieldPhase=3;slow.m_FieldStopFrozen=true;slow.m_FieldLandingPoint={100,0,0};slow.m_FieldIntegral={4,0,0};
|
|
auto actual=slow.FieldDemand({90,8,0},{1,2,0},.1f);auto shared=TranslationRules::Demand({100,0,0},{90,8,0},{1,2,0});assert((actual-shared).Length()==0&&actual.Length()<=.030001f);
|
|
Flight startup;assert(startup.StartField(1,2,3,4,5,&q,77,240000));startup.PrepareActions(11,12,startup.m_Actions,.1f);startup.Tick();assert(startup.baseInputs==1&&startup.baseTicks==1);
|
|
}
|
|
''')
|
|
# Preserve shared ownership, startup and bridge; no new actuation domain.
|
|
assert 'class ERR_AircraftFieldFlight : ERR_AircraftFlight' in source
|
|
for forbidden in ('s_Owners', 'modded class', 'SetWorldTransform(', 'SetOrigin(', 'SetVelocity(', 'SetActive(', 'Spawn(', '.Claim(', '.SetActionValue(', '.StartEngine(', '.StopEngine('):assert forbidden not in source
|
|
for unchanged in ('protected bool Owns(', 'protected bool Healthy(', 'protected bool Lease(', 'protected void Release(', 'void Cancel('):assert unchanged not in source
|
|
assert 'return super.Start(' in body(P,'bool StartField(')
|
|
assert 'AbortDue' not in body(P,'protected override bool ControlLease(')
|
|
assert 'ERR_AircraftFieldRules.AbortDue(now + 1500, m_Deadline)' in body(P,'override void Tick(')
|
|
assert 'm_Deadline =' not in source and 'm_Token =' not in source
|
|
prepare=body(P,'override void PrepareActions(')
|
|
assert prepare.index('!ControlLease()')<prepare.index('FieldDemand(')<prepare.index('Pair(')
|
|
assert 'm_StageDeadline' not in body(P,'protected override void CheckTranslation(')
|
|
assert 'm_FieldIntegral = "0 0 0"' in body(P,'protected override void CheckTranslation(')
|
|
print('PASS: actual field startup delegation, immutable lease/target, braking/settling/cancel transitions, early abort, callback scope, physical terminal policy and shared landing demand; native boundaries faked')
|
|
|
|
brake=body(P,'protected bool BeginFieldBrake(')
|
|
assert brake.index('if (m_FieldStopFrozen) return true;')<brake.index('!Owns()')<brake.index('m_Field.Contains(point, 1)')<brake.index('m_Controller.SetAutohoverEnabled(true)')<brake.index('if (!m_Controller.GetAutohoverEnabled())')<brake.index('m_FieldStopFrozen = true;')
|
|
print('PASS: actual inherited airborne pre-CLIMB cancellation enables verified native autohover once, refuses failed enable and never writes after capture')
|