Validate independent packed addons and complete physical mortar salvos
This commit is contained in:
@@ -37,7 +37,7 @@ int main(){
|
||||
''')
|
||||
def test_native_input_only_and_local_hooks(self):
|
||||
source=SOURCE.read_text()
|
||||
for forbidden in ('SetVelocity(', 'SetAngularVelocity(', 'SetWorldTransform(', 'SetOrigin(', 'SetAngles(', '.Teleport(', 'ForceStartEngine(', '.EngineStart(', 'RotorSetForce', 'RotorSetTorque', 'SetHealth(', 'SetFuel(', 'SetGlobalFuelConsumptionScale', 'GetInputManager('):
|
||||
for forbidden in ('SetVelocity(', 'SetAngularVelocity(', 'SetWorldTransform(', 'SetOrigin(', 'SetAngles(', '.Teleport(', 'ForceStartEngine(', '.EngineStart(', 'RotorSetForce', 'RotorSetTorque', 'SetHealth(', 'SetFuel(', 'SetGlobalFuelConsumptionScale'):
|
||||
self.assertNotIn(forbidden,source)
|
||||
self.assertTrue(source.startswith('#ifdef WORKBENCH'))
|
||||
self.assertIn('outcome == "powered-flight"',source)
|
||||
@@ -110,11 +110,46 @@ int main(){
|
||||
for(int i=-100;i<=100;i++) {float command=NativeCollective(i/100.f); assert(command>=0 && command<=1 && command>=previous); previous=command;}
|
||||
}
|
||||
''')
|
||||
demand=body(SOURCE,'protected bool ComputePilotDemand(')
|
||||
self.assertIn('ERR_PoweredFlightRules.CollectiveCommand(collective, m_ActionInputs, m_Stage)',demand)
|
||||
pilot=body(SOURCE,'void ApplyPilot(')
|
||||
self.assertLess(pilot.index('ERR_PoweredFlightRules.NativeCollective(collective)'),pilot.index('m_Input.SetVehicleCollective(collective)'))
|
||||
self.assertLess(pilot.index('ComputePilotDemand('),pilot.index('m_Input.SetVehicleCollective(collective)'))
|
||||
self.assertIn('m_Input.GetVehicleCollective() == 0',SOURCE.read_text())
|
||||
def test_actual_signed_collective_encoding_and_negative_gate(self):
|
||||
methods='\n'.join(body(SOURCE,key).replace('Math.','Math::') for key in ['static float ClampInput(', 'static float NativeCollective(', 'static float CollectiveCommand(', 'static bool NegativeCollectiveObserved('])
|
||||
run('#include <cassert>\n#include <algorithm>\n#include <cmath>\nstruct Math {static float Clamp(float x,float a,float b){return std::clamp(x,a,b);} static float AbsFloat(float x){return std::abs(x);}};\n'+methods+"""
|
||||
int main(){
|
||||
for(int stage=0;stage<11;stage++)for(float demand: {-2.f,-1.f,-.05f,0.f,.5f,2.f}){
|
||||
assert(CollectiveCommand(demand,false,stage)==NativeCollective(demand));
|
||||
if(stage>=4&&stage<=6)assert(CollectiveCommand(demand,true,stage)==ClampInput(demand));
|
||||
else if(stage==9)assert(CollectiveCommand(demand,true,stage)==.05f);
|
||||
else if(stage==10)assert(CollectiveCommand(demand,true,stage)==-.05f);
|
||||
else if(stage==7)assert(CollectiveCommand(demand,true,stage)==-1);
|
||||
else assert(CollectiveCommand(demand,true,stage)==0);
|
||||
}
|
||||
assert(NegativeCollectiveObserved(-.05f,-.05f));
|
||||
for(float bad: {0.f,.05f,-1.f,NAN})assert(!NegativeCollectiveObserved(-.05f,bad));
|
||||
assert(!NegativeCollectiveObserved(0,0));assert(!NegativeCollectiveObserved(.05f,.05f));
|
||||
assert(!NegativeCollectiveObserved(NAN,-.05f));
|
||||
}
|
||||
""")
|
||||
source=SOURCE.read_text();tick=body(SOURCE,'int Tick(')
|
||||
gate=tick.split('if (m_Stage == 10)',1)[1].split('if (m_Stage == 4 ||',1)[0]
|
||||
for required in ('now >= m_StageDeadline', 'm_Controller.GetCollectiveMode() == CollectiveMode.Direct', '!m_Controller.GetAutohoverEnabled()', 'm_ControlUpdates >= m_NegativeGateUpdates + 3', 'now - m_NegativeSignalSince < 500'):
|
||||
self.assertIn(required,gate)
|
||||
self.assertLess(gate.index('negativeIndex >= 0'),gate.index('GetSignalValue(negativeIndex)'))
|
||||
self.assertLess(gate.index('now - m_NegativeSignalSince < 500'),gate.index('m_SignedCollectiveProven = true'))
|
||||
self.assertLess(gate.index('m_SignedCollectiveProven = true'),gate.index('Check(BeginAutohover()'))
|
||||
self.assertEqual(source.count('m_SignedCollectiveProven = true'),1)
|
||||
self.assertIn('m_ActionInputs && m_Stage >= 4 && m_Stage <= 6 && !m_SignedCollectiveProven',body(SOURCE,'protected bool ComputePilotDemand('))
|
||||
self.assertIn('m_NegativeSignalSince = -1;',tick.split('now - m_PreviousTick > 1500',1)[1].split('\n',1)[0])
|
||||
def test_heading_uses_explicit_yaw_not_xyz_pitch(self):
|
||||
source=SOURCE.read_text()
|
||||
self.assertNotIn('m_Aircraft.GetAngles()[0]',source)
|
||||
self.assertIn('m_Heading = m_Aircraft.GetYawPitchRoll()[0]',source)
|
||||
self.assertIn('float headingError = m_Heading - m_Aircraft.GetYawPitchRoll()[0]',source)
|
||||
def test_control_and_flight_geometry_use_actual_world_pose(self):
|
||||
for signature in ('void ApplyPilot(', 'protected void Diagnostic(', 'int Tick('):
|
||||
for signature in ('protected bool ComputePilotDemand(', 'protected void Diagnostic(', 'int Tick('):
|
||||
method=body(SOURCE,signature)
|
||||
self.assertIn('m_Aircraft.GetWorldTransform(transform)',method)
|
||||
self.assertNotIn('m_Simulation.GetWorldTransform(transform)',method)
|
||||
@@ -140,6 +175,33 @@ int main(){
|
||||
method=body(SOURCE,'static bool CollectiveObserved(').replace('Math.','Math::')
|
||||
run('#include <cassert>\n#include <cmath>\nstruct Math { static float AbsFloat(float x){return std::abs(x);} };\n'+method+'''
|
||||
int main(){assert(CollectiveObserved(.05f,.05f));assert(!CollectiveObserved(.05f,0));assert(!CollectiveObserved(0,0));assert(!CollectiveObserved(.05f,1));assert(!CollectiveObserved(.5f,.5f));assert(!CollectiveObserved(.05f,NAN));}
|
||||
''')
|
||||
def test_actor_action_mode_is_exclusive_owned_and_precedes_native_super(self):
|
||||
source=SOURCE.read_text()
|
||||
native=body(SOURCE,'override void OnPrepareControls(')
|
||||
self.assertLess(native.index('probe.PreparePilotActions('),native.index('super.OnPrepareControls('))
|
||||
apply=body(SOURCE,'void ApplyPilot(')
|
||||
self.assertLess(apply.index('if (m_ActionInputs) return;'),apply.index('ComputePilotDemand('))
|
||||
prepare=body(SOURCE,'void PreparePilotActions(')
|
||||
self.assertIn('!m_ActionInputs || !m_Bound',prepare)
|
||||
self.assertIn('controller != m_Character || owner != m_Pilot || !PoweredAuthority()',prepare)
|
||||
self.assertLess(prepare.index('!OwnActionManager(am)'),prepare.index('am.ActivateContext('))
|
||||
self.assertNotIn('.SetVehicle',prepare)
|
||||
ownership=body(SOURCE,'protected bool OwnActionManager(')
|
||||
self.assertIn('am == m_PilotAgent.GetActionManager()',ownership)
|
||||
self.assertIn('am != GetGame().GetInputManager()',ownership)
|
||||
self.assertEqual(source.count('GetInputManager()'),1)
|
||||
self.assertIn('System.GetCLIParam("errFlightActionInputs", actionInputs)',source)
|
||||
for axis in ('HelicopterCollectiveIncrease', 'HelicopterCollectiveDecrease', 'HelicopterCyclicForward', 'HelicopterCyclicBack', 'HelicopterCyclicRight', 'HelicopterCyclicLeft', 'HelicopterAntiTorqueRight', 'HelicopterAntiTorqueLeft'):
|
||||
self.assertIn('"'+axis+'"',prepare)
|
||||
self.assertIn('"'+axis+'"',body(SOURCE,'void Stop()'))
|
||||
stop=body(SOURCE,'void Stop()')
|
||||
self.assertIn('ours && m_ActionInputs && OwnActionManager(m_OwnedActions)',stop)
|
||||
self.assertIn('ours && !m_ActionInputs && m_Input',stop)
|
||||
def test_actual_directional_action_writer_zeros_opposite_and_bounds_values(self):
|
||||
method=body(SOURCE,'bool WriteActionPair(').replace('ActionManager am','ActionManager& am').replace('Math.','Math::')
|
||||
run('#include <cassert>\n#include <cmath>\n#include <algorithm>\n#include <map>\n#include <string>\nusing string=std::string;\nstruct Math {static float Clamp(float x,float a,float b){return std::clamp(x,a,b);} static float AbsFloat(float x){return std::abs(x);}};\nstruct ActionManager {std::map<string,float> values; void SetActionValue(string key,float value){values[key]=value;} float GetActionValue(string key){return values[key];}};\n'+method+'''
|
||||
int main(){ActionManager owned; owned.values["back"]=1;assert(WriteActionPair(owned,"forward","back",.3f));assert(owned.values["forward"]==.3f&&owned.values["back"]==0);assert(WriteActionPair(owned,"forward","back",-.4f));assert(owned.values["forward"]==0&&owned.values["back"]==.4f);assert(WriteActionPair(owned,"forward","back",0));assert(owned.values["forward"]==0&&owned.values["back"]==0);assert(WriteActionPair(owned,"forward","back",4));assert(owned.values["forward"]==1&&owned.values["back"]==0);}
|
||||
''')
|
||||
def test_stop_and_engine_shutdown_are_bounded(self):
|
||||
source=SOURCE.read_text();stop=body(SOURCE,'void Stop()')
|
||||
@@ -147,8 +209,89 @@ int main(){assert(CollectiveObserved(.05f,.05f));assert(!CollectiveObserved(.05f
|
||||
self.assertIn('s_Current = null',stop)
|
||||
self.assertNotIn('StopEngine',stop) # no engine kill on midair fail/capture
|
||||
self.assertNotIn('DeleteSpawned',stop)
|
||||
self.assertIn('((m_Stage >= 4 && m_Stage <= 6) || m_Stage == 9) && !m_Controller.IsEngineOn()',source)
|
||||
self.assertIn('((m_Stage >= 4 && m_Stage <= 6) || m_Stage == 9 || m_Stage == 10) && !m_Controller.IsEngineOn()',source)
|
||||
self.assertIn('m_Fuel.GetTotalFuel() < m_InitialFuel - 0.05',source)
|
||||
self.assertIn('m_Controller.StopEngine(false)',source)
|
||||
self.assertIn('ERR_PoweredFlightRules.Landed(m_Simulation.HasAnyGroundContact()',source)
|
||||
def test_actual_autohover_configuration_readback_and_owned_restore(self):
|
||||
methods='\n'.join(body(SOURCE,key) for key in ['bool SetAutohoverMode(', 'protected bool BeginAutohover(', 'protected bool AutohoverReady(', 'protected void RestoreAutohover('])
|
||||
methods=methods.replace('protected ', '').replace('m_Controller.', 'm_Controller->').replace(' = null;', ' = nullptr;').replace('LogLevel.', 'LogLevel::')
|
||||
run('''
|
||||
#include <cassert>
|
||||
#include <initializer_list>
|
||||
template<class... T> void PrintFormat(T...) {}
|
||||
struct LogLevel {static const int ERROR=1;};
|
||||
int nativeErrors=0;
|
||||
void Print(const char*,int){nativeErrors++;}
|
||||
struct Controller { void* owner=nullptr; bool enabled=false, accept=true; int writes=0;
|
||||
void* GetOwner(){return owner;} bool GetAutohoverEnabled(){return enabled;}
|
||||
void SetAutohoverEnabled(bool value){writes++; if(accept)enabled=value;}
|
||||
};
|
||||
struct Probe {
|
||||
int m_Stage=1;
|
||||
bool m_Started=false,m_Stopped=false,m_Complete=false,m_Bound=false;
|
||||
bool m_AutohoverMode=false,m_AutohoverSaved=false,m_OriginalAutohover=false,authority=true;
|
||||
void* m_Aircraft=nullptr; Controller* m_Controller=nullptr; Controller* m_AutohoverController=nullptr;
|
||||
bool PoweredAuthority(){return authority;}
|
||||
'''+methods+'''
|
||||
void Bind(Controller& c){m_Controller=&c;m_Aircraft=c.owner;m_Bound=true;m_Started=true;}
|
||||
};
|
||||
int main(){int airframe, foreign;
|
||||
Controller c;c.owner=&airframe;
|
||||
Probe manual;manual.Bind(c);assert(manual.BeginAutohover()&&manual.AutohoverReady());
|
||||
manual.RestoreAutohover(true);assert(c.writes==0); // default is observational
|
||||
Probe selected;assert(selected.SetAutohoverMode(true));selected.Bind(c);
|
||||
assert(!selected.SetAutohoverMode(false));assert(selected.BeginAutohover());
|
||||
assert(c.enabled&&c.writes==1&&selected.AutohoverReady());
|
||||
Probe waiting;waiting.SetAutohoverMode(true);waiting.Bind(c);
|
||||
for(int stage: {2,3,9,10}){waiting.m_Stage=stage;assert(waiting.AutohoverReady());}
|
||||
for(int stage: {1,4,5,6,7}){waiting.m_Stage=stage;assert(!waiting.AutohoverReady());}
|
||||
assert(!selected.BeginAutohover()&&c.writes==1); // no retry or timestamp-like renewal
|
||||
c.enabled=false;assert(!selected.AutohoverReady());c.enabled=true;
|
||||
selected.RestoreAutohover(true);assert(!c.enabled&&c.writes==2);
|
||||
selected.RestoreAutohover(true);assert(c.writes==2); // idempotent cleanup
|
||||
Probe alreadyEnabled;c.enabled=true;assert(alreadyEnabled.SetAutohoverMode(true));alreadyEnabled.Bind(c);
|
||||
assert(alreadyEnabled.BeginAutohover());alreadyEnabled.RestoreAutohover(true);assert(c.enabled);
|
||||
Probe captured;assert(captured.SetAutohoverMode(true));captured.Bind(c);assert(captured.BeginAutohover());
|
||||
int before=c.writes;captured.RestoreAutohover(false);assert(c.writes==before&&c.enabled);
|
||||
Probe refused;c.enabled=false;c.accept=false;assert(refused.SetAutohoverMode(true));refused.Bind(c);
|
||||
assert(!refused.BeginAutohover()&&!refused.AutohoverReady());assert(!refused.BeginAutohover());
|
||||
c.accept=true;refused.RestoreAutohover(true);assert(!c.enabled);
|
||||
Probe mismatch;assert(mismatch.SetAutohoverMode(true));mismatch.Bind(c);mismatch.m_Aircraft=&foreign;
|
||||
before=c.writes;assert(!mismatch.BeginAutohover()&&c.writes==before);
|
||||
Probe inactive;assert(inactive.SetAutohoverMode(true));inactive.Bind(c);inactive.authority=false;
|
||||
assert(!inactive.BeginAutohover()&&c.writes==before);
|
||||
Probe changed;assert(changed.SetAutohoverMode(true));changed.Bind(c);assert(changed.BeginAutohover());
|
||||
Controller other;other.owner=&airframe;changed.m_Controller=&other;
|
||||
assert(!changed.AutohoverReady());before=c.writes;changed.RestoreAutohover(true);
|
||||
assert(c.writes==before&&other.writes==0);
|
||||
Probe restoreFailed;c.enabled=false;assert(restoreFailed.SetAutohoverMode(true));restoreFailed.Bind(c);
|
||||
assert(restoreFailed.BeginAutohover());c.accept=false;restoreFailed.RestoreAutohover(true);
|
||||
assert(nativeErrors==1&&c.enabled); // rejected restore is retained as native failure
|
||||
for(int i=0;i<5;i++){Probe frozen;if(i==0){frozen.m_Started=true;}if(i==1){frozen.m_Stopped=true;}
|
||||
if(i==2){frozen.m_Complete=true;}if(i==3){frozen.m_Bound=true;}if(i==4){frozen.m_Aircraft=&airframe;}
|
||||
assert(!frozen.SetAutohoverMode(true));}
|
||||
}
|
||||
''')
|
||||
def test_autohover_neutralizes_only_custom_cyclic_and_keeps_flight_gates(self):
|
||||
neutral=body(SOURCE,'if (m_AutohoverMode)')
|
||||
run('#include <cassert>\nvoid controls(bool m_AutohoverMode,float& forward,float& aside){'+neutral+'''}
|
||||
int main(){float f=.1f,r=-.15f;controls(false,f,r);assert(f==.1f&&r==-.15f);
|
||||
controls(true,f,r);assert(f==0&&r==0);}
|
||||
''')
|
||||
demand=body(SOURCE,'protected bool ComputePilotDemand(')
|
||||
self.assertIn('!AutohoverReady()',demand)
|
||||
self.assertIn('- angular[0] * 1.2',demand)
|
||||
self.assertIn('+ angular[2] * 1.2',demand)
|
||||
self.assertIn('headingError * 0.01 - angular[1] * 0.3',demand)
|
||||
tick=body(SOURCE,'int Tick(')
|
||||
self.assertIn('!m_ReadbackOK || !AutohoverReady()',tick)
|
||||
self.assertLess(tick.index('m_Bound = true'),tick.index('Check(BeginAutohover()'))
|
||||
self.assertLess(tick.index('m_Controller.StartEngine()'),tick.index('Check(BeginAutohover()'))
|
||||
self.assertLess(tick.index('positive collective persists in existing native rotor signal'),tick.index('Check(BeginAutohover()'))
|
||||
self.assertIn('now - m_Stable >= 5000',tick)
|
||||
stop=body(SOURCE,'void Stop()')
|
||||
self.assertIn('m_Seat.GetOccupant() == m_Pilot',stop)
|
||||
self.assertIn('CompartmentAccessComponent.GetVehicleIn(m_Pilot) == m_Aircraft',stop)
|
||||
self.assertIn('RestoreAutohover(ownSeat)',stop)
|
||||
if __name__=='__main__': unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user