Add bounded aircraft control and verify current packaged runtime closure
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
"""Execute production controller rules/cancellation/stage bodies with native boundaries substituted.
|
||||
|
||||
No native flight, callback ordering, collision or multiplayer claim.
|
||||
"""
|
||||
import re
|
||||
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.','Rules::').replace('Math.','Math::').replace('CollectiveMode.','CollectiveMode::').replace('vector.','vector::').replace('SignalsManagerComponent signals','SignalsManagerComponent* signals').replace('signals.FindSignal','signals->FindSignal').replace('signals.GetSignalValue','signals->GetSignalValue')
|
||||
rules='\n'.join(cpp(body(SOURCE,s)) for s in ['static bool Terminal(', 'static bool Deadline(', 'static bool Envelope(', 'static bool Hover(', 'static bool Landed(', 'static float Vertical(', 'static int CancelAction('])
|
||||
methods='\n'.join(cpp(body(SOURCE,s)) for s in ['protected bool ControlLease(', 'protected bool Landing(', 'protected void Shutdown(', 'void Cancel(', 'void Tick('])
|
||||
run(r'''#include <cassert>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <initializer_list>
|
||||
using string=std::string;
|
||||
namespace State {enum {IDLE,HOOKS,STARTING,POSITIVE,NEGATIVE,CLIMB,LANDING,SHUTDOWN,COMPLETE,CANCELLED,FAILED,RELINQUISHED,TRANSLATING};}
|
||||
namespace CollectiveMode {enum {Direct=3};}
|
||||
struct Math {static float AbsFloat(float x){return std::abs(x);} static float Min(float a,float b){return std::min(a,b);} static float Clamp(float x,float a,float b){return std::clamp(x,a,b);}};
|
||||
struct vector {float x=0,y=0,z=0;float operator[](int i)const{return i==0?x:(i==1?y:z);}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 Rules {
|
||||
'''+rules+r'''
|
||||
};
|
||||
struct SignalsManagerComponent {float value=.05;int FindSignal(const char*){return 0;}float GetSignalValue(int){return value;}};
|
||||
struct Controller {
|
||||
explicit operator bool()const{return true;}
|
||||
bool engine=true,autohover=true,accept=true;int starts=0,stops=0,hoverWrites=0;
|
||||
SignalsManagerComponent signals;
|
||||
void SetAutohoverEnabled(bool value){hoverWrites++;if(accept)autohover=value;}
|
||||
bool GetAutohoverEnabled(){return autohover;}
|
||||
bool IsEngineOn(){return engine;}int GetCollectiveMode(){return CollectiveMode::Direct;}
|
||||
void StopEngine(bool force){assert(!force);stops++;engine=false;}
|
||||
void StartEngine(){starts++;engine=true;}
|
||||
SignalsManagerComponent* GetSignalsManager(){return &signals;}
|
||||
};
|
||||
struct Aircraft {vector position={0,8,0};void GetWorldTransform(vector* out){out[0]={1,0,0};out[1]={0,1,0};out[2]={0,0,1};out[3]=position;}};
|
||||
struct Simulation {vector velocity;bool contact=false;vector GetVelocity(){return velocity;}bool HasAnyGroundContact(){return contact;}float RotorGetRPMTarget(int){return 100;}float RotorGetRPM(int){return 100;}};
|
||||
struct Fuel{float GetTotalFuel(){return 99;}};
|
||||
struct Input {float collective=.05;float GetVehicleCollective(){return collective;}};
|
||||
struct Probe {
|
||||
bool owned=true,healthy=true,ground=false,lease=true,m_Cancelled=false,m_Readback=true,m_TranslationRequested=false;
|
||||
int m_State=State::CLIMB,m_ActionUpdates=10,m_HelicopterUpdates=10,m_GateUpdates=0;
|
||||
float now=1000,m_Deadline=200000,m_StageDeadline=66000,m_Stable=-1,m_LastTick=-1,m_LastControl=1000,m_InitialFuel=100;
|
||||
vector m_Home;string m_Reason;
|
||||
Controller m_Controller;Aircraft m_Aircraft;Simulation m_Simulation;Fuel m_Fuel;Input m_Input;
|
||||
int released=0,restored=0,logs=0;
|
||||
bool Lease(){return lease&&!Rules::Terminal(m_State);}bool Owns(){return owned;}bool Healthy(){return healthy;}bool Grounded(){return ground;}float Now(){return now;}
|
||||
void Release(bool restore){released++;if(restore){assert(owned&&ground);restored++;}}
|
||||
bool BeginTranslation(float,vector){return true;}
|
||||
void CheckTranslation(float,vector,float,float,float){}
|
||||
void RecordState(){logs++;}
|
||||
void Relinquish(string reason){m_Reason=reason;m_State=State::RELINQUISHED;Release(false);RecordState();}
|
||||
void Fail(string reason){m_Reason=reason;m_State=State::FAILED;Release(false);RecordState();}
|
||||
'''+methods+r'''
|
||||
};
|
||||
int main(){
|
||||
for(int flags=0;flags<8;flags++){
|
||||
bool owned=flags&1,ground=flags&2,engine=flags&4;
|
||||
assert(Rules::CancelAction(owned,ground,engine)==(!owned?0:(!ground?1:(engine?2:3))));
|
||||
}
|
||||
assert(Rules::Deadline(1,240001)&&!Rules::Deadline(1,240002));
|
||||
assert(!Rules::Terminal(State::TRANSLATING));
|
||||
assert(!Rules::Deadline(1,1)&&!Rules::Deadline(-1,100)&&!Rules::Deadline(NAN,10));
|
||||
assert(Rules::Envelope(-3,0,.85)&&Rules::Envelope(18,7.999f,1));
|
||||
assert(!Rules::Envelope(18.001f,0,1)&&!Rules::Envelope(8,8,1)&&!Rules::Envelope(8,0,.849f));
|
||||
assert(!Rules::Envelope(NAN,0,1));assert(!Rules::Envelope(0,NAN,1));
|
||||
assert(Rules::Landed(true,0,0,0,1)&&!Rules::Landed(false,0,0,0,1));
|
||||
assert(Rules::Hover(8,0,0,1)&&!Rules::Hover(8,-1,0,1));
|
||||
assert(Rules::Vertical(8,true)<0&&Rules::Vertical(0,false)>0);
|
||||
// Airborne cancellation retains control and original deadline, including repeats.
|
||||
Probe airborne;airborne.Cancel("report expired");assert(airborne.m_State==State::LANDING&&airborne.released==0);
|
||||
assert(airborne.m_StageDeadline==66000&&airborne.m_Deadline==200000);
|
||||
airborne.now=5000;airborne.Cancel("duplicate");assert(airborne.m_StageDeadline==66000&&airborne.m_Reason=="report expired");
|
||||
Probe translating;translating.m_State=State::TRANSLATING;translating.m_TranslationRequested=true;translating.Cancel("cancel translation");
|
||||
assert(translating.m_State==State::LANDING&&translating.released==0&&translating.m_Deadline==200000);
|
||||
translating.now=5000;translating.Cancel("duplicate translation cancellation");
|
||||
assert(translating.m_State==State::LANDING&&translating.released==0&&translating.m_Deadline==200000&&translating.m_StageDeadline==66000&&translating.m_Reason=="cancel translation");
|
||||
// Normal cancellation ends only after two distinct grounded seconds and engine off.
|
||||
airborne.ground=true;airborne.m_Simulation.contact=true;airborne.m_Aircraft.position={0,0,0};
|
||||
airborne.m_LastControl=5000;airborne.Tick();assert(airborne.m_State==State::LANDING);
|
||||
airborne.now=6000;airborne.m_LastControl=6000;airborne.Tick();
|
||||
airborne.now=7000;airborne.m_LastControl=7000;airborne.Tick();assert(airborne.m_State==State::SHUTDOWN&&airborne.m_Controller.stops==1);
|
||||
airborne.now=7500;airborne.m_LastControl=7500;airborne.Tick();assert(airborne.m_State==State::CANCELLED&&airborne.restored==1);
|
||||
// Noncancelled natural run preserves continuous five-second hover then landing.
|
||||
Probe normal;for(int i=0;i<=5;i++){normal.now=1000+i*1000;normal.m_LastControl=normal.now;normal.Tick();}
|
||||
assert(normal.m_State==State::LANDING&&!normal.m_Cancelled&&normal.released==0);
|
||||
// A gap cannot manufacture five continuous seconds of observed hover.
|
||||
Probe gap;gap.Tick();gap.now=7000;gap.m_LastControl=7000;gap.Tick();assert(gap.m_State==State::CLIMB&&gap.m_Stable==7000);
|
||||
// Deadline failure remains explicitly FAILED, never successful/available or engine killed.
|
||||
Probe expired;expired.m_Deadline=1500;expired.Cancel("cancel");assert(expired.m_StageDeadline==1500);
|
||||
expired.now=1500;expired.Tick();assert(expired.m_State==State::FAILED&&expired.restored==0&&expired.m_Controller.stops==0);
|
||||
assert(expired.logs>0&&!expired.m_Reason.empty());
|
||||
// Capture on either Cancel or Tick causes no autohover/engine writes.
|
||||
for(bool cancel:{false,true}){Probe capture;capture.owned=false;if(cancel)capture.Cancel("capture");else capture.Tick();
|
||||
assert(capture.m_State==State::RELINQUISHED&&capture.m_Controller.hoverWrites==0&&capture.m_Controller.stops==0&&capture.restored==0);}
|
||||
Probe dead;dead.healthy=false;dead.Cancel("pilot dead");assert(dead.m_State==State::FAILED&&dead.m_Controller.hoverWrites==0&&dead.m_Controller.stops==0);
|
||||
Probe grounded;grounded.m_State=State::HOOKS;grounded.ground=true;grounded.m_Controller.engine=false;grounded.Cancel("before start");
|
||||
assert(grounded.m_State==State::CANCELLED&&grounded.restored==1&&grounded.m_Controller.stops==0);
|
||||
Probe groundRunning;groundRunning.m_State=State::STARTING;groundRunning.ground=true;groundRunning.Cancel("on ground");assert(groundRunning.m_State==State::SHUTDOWN&&groundRunning.m_Controller.stops==1);
|
||||
Probe touchdown;touchdown.ground=true;touchdown.Cancel("new contact");assert(touchdown.m_State==State::LANDING&&touchdown.m_Controller.stops==0);
|
||||
Probe noHover;noHover.m_Controller.accept=false;noHover.m_Controller.autohover=false;noHover.Cancel("abort");assert(noHover.m_State==State::FAILED&&noHover.m_Controller.stops==0);
|
||||
Probe escape;escape.m_Aircraft.position={8,8,0};escape.Tick();assert(escape.m_State==State::FAILED&&escape.m_Controller.stops==0);
|
||||
// Input callbacks independently enforce expiry, even with no owner Tick.
|
||||
Probe staleCallback;staleCallback.now=staleCallback.m_StageDeadline;
|
||||
assert(!staleCallback.ControlLease()&&staleCallback.m_State==State::FAILED&&staleCallback.m_Controller.stops==0);
|
||||
Probe callbackCapture;callbackCapture.owned=false;
|
||||
assert(!callbackCapture.ControlLease()&&callbackCapture.m_State==State::RELINQUISHED&&callbackCapture.m_Controller.hoverWrites==0);
|
||||
Probe callbackEscape;callbackEscape.m_Aircraft.position={9,8,0};
|
||||
assert(!callbackEscape.ControlLease()&&callbackEscape.m_State==State::FAILED);
|
||||
Probe callbackOK;assert(callbackOK.ControlLease());
|
||||
Probe quiet;quiet.m_LastControl=-1;quiet.Tick();assert(quiet.m_State==State::FAILED);
|
||||
}
|
||||
''')
|
||||
# Integration contracts: no implicit activation or physical shortcuts introduced.
|
||||
source=SOURCE.read_text()
|
||||
assert 'modded class' not in source and 'CallLater(' not in source and 'SpawnEntity' not in source
|
||||
for forbidden in ('SetWorldTransform(', 'SetVelocity(', 'SetHealth(', 'SetFuel(', 'SetActive(', 'SetSignalValue('):assert forbidden not in source
|
||||
release=body(SOURCE,'protected void Release(')
|
||||
assert release.index('m_SettingsOwned && Owns()') < release.index('Pair(')
|
||||
assert release.index('restore && Grounded()') < release.index('SetAutohoverEnabled(m_OriginalAutohover)')
|
||||
assert 'StopEngine' not in release
|
||||
prepare=body(SOURCE,'void PrepareActions(')
|
||||
assert prepare.index('!ControlLease()') < prepare.index('actions.ActivateContext(')
|
||||
assert 'actions != m_Actions' in prepare
|
||||
assert source.count('m_Controller.StartEngine()')==1
|
||||
assert source.count('m_Controller.StopEngine(false)')==1
|
||||
print('PASS: actual aircraft cancellation/stage rules, ground-only completion, expiry/capture faults, input ownership and no physical shortcuts')
|
||||
Reference in New Issue
Block a user