Harden participant and map ownership and validate bounded physical flight

This commit is contained in:
2026-09-19 12:17:01 +02:00
parent dfd7cc49e2
commit 4125018d04
120 changed files with 20043 additions and 47 deletions
+122
View File
@@ -294,4 +294,126 @@ controls(true,f,r);assert(f==0&&r==0);}
self.assertIn('m_Seat.GetOccupant() == m_Pilot',stop)
self.assertIn('CompartmentAccessComponent.GetVehicleIn(m_Pilot) == m_Aircraft',stop)
self.assertIn('RestoreAutohover(ownSeat)',stop)
def test_actual_translation_geometry_requests_and_state_gates(self):
signatures=['static bool Hover(', 'static bool TranslationInputs(', 'static bool TranslationActive(', 'static bool TranslationTarget(', 'static vector TranslationDemand(', 'static bool TranslationHeld(']
methods='\n'.join(body(SOURCE,k) for k in signatures)
methods=methods.replace('Math.', 'Math::').replace('vector.', 'vector::').replace('out vector target','vector& target')
callbacks='\n'.join(body(SOURCE,k) for k in ['protected bool BeginTranslation(', 'protected bool CheckTranslation('])
callbacks=callbacks.replace('protected ','').replace('ERR_PoweredFlightRules.','Rules::').replace('vector.','vector::')
callbacks=callbacks.replace('m_Controller.GetSignalsManager()', 'm_Controller.GetSignalsManager()').replace('SignalsManagerComponent signals','SignalsManagerComponent* signals').replace('signals.FindSignal','signals->FindSignal')
run(r'''#include <cassert>
#include <cmath>
#include <algorithm>
#include <initializer_list>
#include <string>
struct Math {static float AbsFloat(float x){return std::abs(x);}};
struct vector {
float x=0,y=0,z=0;
float& operator[](int i){return i==0?x:(i==1?y:z);}
vector operator+(vector v)const{return{x+v.x,y+v.y,z+v.z};}
vector operator-(vector v)const{return{x-v.x,y-v.y,z-v.z};}
vector operator*(float k)const{return{x*k,y*k,z*k};}
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);}
static const vector Zero;
};
const vector vector::Zero={0,0,0};
template<class... T> void PrintFormat(T...){}
struct Rules {
'''+methods+r'''
};
struct SignalsManagerComponent {
bool present=true;
int FindSignal(const char*){return present?0:-1;}
};
struct Controller {SignalsManagerComponent signals; SignalsManagerComponent* GetSignalsManager(){return &signals;}};
struct Simulation {vector velocity; bool contact=false; vector GetVelocity(){return velocity;} bool HasAnyGroundContact(){return contact;}};
struct Probe {
bool m_TranslationMode=true,m_TranslationFrozen=false,m_TranslationProven=false,m_ActionInputs=true,m_AutohoverMode=true,hoverReady=true,stopped=false;
vector m_Home={100,25,200},m_TranslationOrigin,m_TranslationTarget;
int m_Stage=4,m_TranslationUpdates=0;
float m_StageDeadline=0,m_Stable=-1,m_NextLog=0,m_Deadline=240000;
Controller m_Controller; Simulation m_Simulation;
bool AutohoverReady(){return hoverReady;}
bool Check(bool ok,const char*){if(!ok)stopped=true;return ok;}
'''+callbacks+r'''
};
int main(){
const vector home={100,25,200},fallback={1,5,0};
for(float radius:{0.f,.49f,.5f,3.25f,7.99f})for(int angle=0;angle<360;angle+=5){
float radians=angle*3.14159265f/180;
vector start={home.x+radius*std::cos(radians),home.y+8,home.z+radius*std::sin(radians)},target;
assert(Rules::TranslationTarget(home,start,fallback,target));
assert(std::abs(vector::DistanceXZ(start,target)-4)<.0001f);
assert(target.y==start.y&&vector::DistanceXZ(home,target)<=5);
// Whole straight target segment lies inside the unchanged origin envelope.
for(int i=0;i<=20;i++)assert(vector::DistanceXZ(home,start+(target-start)*(i/20.f))<8);
}
vector target;
assert(!Rules::TranslationTarget(home,home,vector::Zero,target));
assert(!Rules::TranslationTarget(home,home+vector{8,0,0},fallback,target));
assert(!Rules::TranslationTarget(home,home+vector{NAN,0,0},fallback,target));
for(int mask=0;mask<16;mask++)for(int stage=0;stage<12;stage++){
bool selected=mask&1,frozen=mask&2,actions=mask&4,hover=mask&8;
assert(Rules::TranslationInputs(selected,actions,hover)==(!selected||(actions&&hover)));
assert(Rules::TranslationActive(selected,frozen,actions,hover,stage)==(selected&&frozen&&actions&&hover&&stage==5));
}
for(float x:{-100.f,-4.f,0.f,4.f,100.f})for(float z:{-10.f,0.f,10.f})for(float speed:{-30.f,-.5f,0.f,.5f,30.f}){
vector demand=Rules::TranslationDemand(home+vector{x,900,z},home,vector{speed,99,-speed});
assert(demand.Length()<=.030001f&&demand.y==0);
}
assert(Rules::TranslationDemand(home,home,vector::Zero).Length()==0);
assert(Rules::TranslationDemand(home+vector{4,0,0},home,vector::Zero).x>0);
assert(Rules::TranslationDemand(home+vector{4,0,0},home,vector{1,0,0}).x<0); // brake overspeed
assert(Rules::TranslationDemand(home,home,vector{0,0,-1}).z>0);
assert(Rules::TranslationHeld(false,8,.2f,0,1,4,.2f,.75f,3.25f));
assert(!Rules::TranslationHeld(true,8,.2f,0,1,4,.2f,0,4));
assert(!Rules::TranslationHeld(false,8,.2f,0,1,4,.25f,0,4));
assert(!Rules::TranslationHeld(false,8,.2f,0,1,4,.2f,.751f,4));
assert(!Rules::TranslationHeld(false,8,.2f,0,1,4,.2f,0,3.249f));
assert(!Rules::TranslationHeld(false,8,.2f,0,1,8,.2f,0,4));
// Execute real stage methods: frozen origin/target, distinct ticks, reset and bounded timeout.
Probe p;vector start=home+vector{3.25f,8,0};
assert(p.BeginTranslation(1000,start,fallback));
assert(p.m_Stage==5&&p.m_StageDeadline==46000&&p.m_Deadline==240000&&p.m_TranslationFrozen);
vector frozen=p.m_TranslationTarget;
assert(!p.BeginTranslation(2000,home,fallback)); // never retarget/rebase a running trip
assert(vector::DistanceXZ(frozen,p.m_TranslationTarget)==0&&p.m_StageDeadline==46000);
p.stopped=false;p.m_TranslationUpdates=3;
assert(p.CheckTranslation(2000,start,8,0,1,3.25));assert(p.m_Stable<0);
assert(p.CheckTranslation(3000,frozen,8,0,1,.75));assert(p.m_Stable==3000);
assert(p.CheckTranslation(7999,frozen,8,0,1,.75));assert(!p.m_TranslationProven);
p.m_Simulation.velocity={.3f,0,0};
assert(p.CheckTranslation(8000,frozen,8,.3f,1,.75));assert(p.m_Stable<0);
p.m_Simulation.velocity={0,0,0};
assert(p.CheckTranslation(9000,frozen,8,0,1,.75));
assert(p.CheckTranslation(13999,frozen,8,0,1,.75));assert(p.m_Stage==5);
assert(p.CheckTranslation(14000,frozen,8,0,1,.75));
assert(p.m_TranslationProven&&p.m_Stage==6&&p.m_StageDeadline==79000&&p.m_Deadline==240000);
Probe expired;assert(expired.BeginTranslation(1000,start,fallback));expired.m_TranslationUpdates=3;
assert(!expired.CheckTranslation(46000,expired.m_TranslationTarget,8,0,1,.75));assert(!expired.m_TranslationProven);
for(int invalid=0;invalid<7;invalid++){
Probe refused;
if(invalid==0)refused.m_TranslationMode=false;
if(invalid==1)refused.m_Stage=5;
if(invalid==2)refused.m_ActionInputs=false;
if(invalid==3)refused.m_AutohoverMode=false;
if(invalid==4)refused.hoverReady=false;
if(invalid==5)refused.m_Controller.signals.present=false;
if(invalid==6)refused.m_TranslationFrozen=true;
assert(!refused.BeginTranslation(1000,start,fallback)&&refused.stopped);
}
}
''')
tick=body(SOURCE,'int Tick(')
self.assertLess(tick.index('TranslationInputs('),tick.index('if (m_Stage == 0)'))
self.assertIn('height > 18 || height < -3 || drift >= 8 || transform[1][1] < 0.85',tick)
self.assertIn('now >= m_Deadline',tick)
self.assertIn('m_TranslationMode && !m_TranslationProven',tick)
demand=body(SOURCE,'protected bool ComputePilotDemand(')
self.assertLess(demand.index('if (m_AutohoverMode) { forward = 0; aside = 0; }'),demand.index('TranslationActive('))
self.assertEqual(SOURCE.read_text().count('m_TranslationTarget = target;'),1)
self.assertEqual(SOURCE.read_text().count('m_TranslationOrigin = position;'),1)
if __name__=='__main__': unittest.main()