Files
EscapistsReintroduced/tests/test_aircraft_translation.py
T
jakub cab998fb64 Combine Workshop addon and fix prison startup
Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
2026-09-20 16:42:52 +02:00

103 lines
7.7 KiB
Python

"""Actual bounded target/start/stage policy, with native boundaries substituted."""
from policy_harness import ROOT, body, run
SOURCE=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_AircraftFlight.c'
def cpp(s):
return s.replace('protected ','').replace('ERR_AircraftFlightState.','State::').replace('ERR_AircraftFlightRules.','FlightRules::').replace('ERR_AircraftTranslationRules.','Rules::').replace('Math.','Math::').replace('vector.','vector::').replace('SignalsManagerComponent signals','SignalsManagerComponent* signals').replace('signals.FindSignal','signals->FindSignal')
rules='\n'.join(cpp(body(SOURCE,key)) for key in ('static bool Target(', 'static bool Reachable(', 'static vector Demand(', 'static bool Held('))
methods='\n'.join(cpp(body(SOURCE,key)) for key in ('bool StartTranslation(', 'protected bool BeginTranslation(', 'protected void CheckTranslation('))
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 Min(float a,float b){return std::min(a,b);}static float Max(float a,float b){return std::max(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*(float n)const{return {x*n,y*n,z*n};}
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 FlightRules {
'''+cpp(body(SOURCE,'static bool Hover('))+r'''
};
struct Rules {
'''+rules+r'''
};
struct ERR_AircraftFlightEnvelope {bool valid=true;vector home;explicit operator bool()const{return valid;}vector Home(){return home;}};
struct SignalsManagerComponent {bool present=true;int FindSignal(const char*){return present?0:-1;}};
struct Controller {bool hover=true;SignalsManagerComponent signals;SignalsManagerComponent* GetSignalsManager(){return &signals;}bool GetAutohoverEnabled(){return hover;}};
struct Simulation {vector velocity;bool contact=false;vector GetVelocity(){return velocity;}bool HasAnyGroundContact(){return contact;}};
struct Probe {
bool m_StartAttempted=false,m_TranslationRequested=false,m_TranslationStarted=false,m_TranslationProven=false;
vector m_TranslationTarget,m_TranslationOrigin,m_Home;
float m_TranslationDistance=0,m_Stable=-1,m_Deadline=240000,m_StageDeadline=0;
int m_State=State::CLIMB,m_Token=123,m_TranslationUpdates=10,cancelled=0,landings=0,starts=0;
Controller m_Controller;Simulation m_Simulation;string reason;
bool Start(ERR_GameMode,Vehicle,SCR_AIGroup,IEntity,PilotCompartmentSlot,ERR_AircraftFlightEnvelope,int,float){m_StartAttempted=true;starts++;return true;}
void Cancel(string detail){reason=detail;cancelled++;m_State=State::LANDING;}
bool Landing(float){landings++;m_State=State::LANDING;return true;}
'''+methods+r'''
};
int main(){
assert(Rules::Target({0,0,0},{3,999,4})); // API is explicitly horizontal
assert(!Rules::Target({0,0,0},{3.01f,0,4}));
assert(!Rules::Target({0,0,0},{NAN,0,0}));
assert(Rules::Reachable(4)&&Rules::Reachable(4.04f)&&!Rules::Reachable(4.06f));
assert(!Rules::Reachable(-.01f)&&!Rules::Reachable(NAN));
for(float error:{-100.f,-4.f,-.5f,0.f,.5f,4.f,100.f})for(float velocity:{-100.f,-1.f,0.f,1.f,100.f}){
vector demand=Rules::Demand({error,9,error},{0,1,0},{velocity,100,velocity});
assert(demand.Length()<=.030001f&&demand.y==0);
}
vector brake=Rules::Demand({0,0,0},{0,8,0},{1,0,0});assert(brake.x<0&&brake.z==0);
assert(Rules::Held(false,8,.2f,.1f,1,.1f,.5f,3.5f,4));
assert(!Rules::Held(true,8,.2f,.1f,1,.1f,.5f,3.5f,4));
assert(!Rules::Held(false,8,.2f,.1f,1,.3f,.5f,3.5f,4));
assert(!Rules::Held(false,8,.2f,.1f,1,.1f,.76f,3.5f,4));
assert(!Rules::Held(false,8,.2f,.1f,1,.1f,.5f,3.24f,4));
assert(!Rules::Held(false,10,.2f,.1f,1,.1f,.5f,3.5f,4));
// Invalid absolute targets cannot reach native Start; valid target copies once.
Probe invalid;ERR_AircraftFlightEnvelope q;
assert(!invalid.StartTranslation(1,1,1,1,1,q,123,10000,{6,0,0})&&invalid.starts==0&&!invalid.m_TranslationRequested);
Probe stale;q.valid=false;assert(!stale.StartTranslation(1,1,1,1,1,q,123,10000,{0,0,0})&&stale.starts==0);q.valid=true;
Probe immutable;vector target={3,100,0};assert(immutable.StartTranslation(1,1,1,1,1,q,123,10000,target));
target.x=0;assert(immutable.m_TranslationTarget.x==3&&immutable.m_TranslationTarget.y==8&&immutable.starts==1);
assert(!immutable.StartTranslation(1,1,1,1,1,q,123,10000,{0,0,0})&&immutable.starts==1);
// Natural hover drift may make an accepted preflight target unreachable. Land,
// never shift its target/home or extend the original deadline to force success.
Probe drift;drift.m_TranslationRequested=true;drift.m_TranslationTarget={0,8,0};
assert(!drift.BeginTranslation(1000,{4.1f,8,0})&&drift.cancelled==1&&!drift.m_TranslationStarted);
assert(drift.m_State==State::LANDING&&drift.m_TranslationTarget.x==0&&drift.m_Home.x==0&&drift.m_Deadline==240000);
Probe noSignals;noSignals.m_TranslationRequested=true;noSignals.m_Controller.signals.present=false;
assert(!noSignals.BeginTranslation(1000,{3,8,0})&&noSignals.cancelled==1&&!noSignals.m_TranslationStarted);
Probe move;move.m_TranslationRequested=true;move.m_TranslationTarget={0,8,0};move.m_Deadline=42000;
assert(move.BeginTranslation(1000,{3.3f,8,0}));assert(move.m_State==State::TRANSLATING&&move.m_StageDeadline==42000&&move.m_Deadline==42000);
assert(move.m_TranslationOrigin.x==3.3f&&move.m_TranslationTarget.x==0);
for(int i=0;i<5;i++)move.CheckTranslation(2000+i*1000,{.5f,8,0},8,0,1);
assert(!move.m_TranslationProven&&move.landings==0);
move.CheckTranslation(7000,{.5f,8,0},8,0,1);assert(move.m_TranslationProven&&move.landings==1);
Probe interrupted;interrupted.m_TranslationRequested=true;assert(interrupted.BeginTranslation(1000,{3.3f,8,0}));
interrupted.CheckTranslation(2000,{.5f,8,0},8,0,1);interrupted.CheckTranslation(5000,{2,8,0},8,0,1);
assert(interrupted.m_Stable==-1);interrupted.CheckTranslation(6000,{.5f,8,0},8,0,1);
interrupted.CheckTranslation(10000,{.5f,8,0},8,0,1);assert(!interrupted.m_TranslationProven);
Probe insufficient;insufficient.m_TranslationRequested=true;assert(insufficient.BeginTranslation(1000,{3.3f,8,0}));
insufficient.m_TranslationUpdates=2;insufficient.CheckTranslation(9000,{.5f,8,0},8,0,1);assert(insufficient.m_Stable==-1);
}
''')
source=SOURCE.read_text(); prepare=body(SOURCE,'void PrepareActions('); tick=body(SOURCE,'void Tick(')
assert 'Math.Clamp(vector.Dot(demand, transform[2]), -0.03, 0.03)' in prepare
assert 'Math.Clamp(vector.Dot(demand, transform[0]), -0.03, 0.03)' in prepare
assert prepare.index('!ControlLease()') < prepare.index('ERR_AircraftTranslationRules.Demand(')
assert 'm_State == ERR_AircraftFlightState.TRANSLATING' in prepare
assert tick.index('ERR_AircraftFlightRules.Envelope(') < tick.index('CheckTranslation(')
assert 'if (m_TranslationRequested) BeginTranslation(now, transform[3]);' in tick
assert 'else Landing(now);' in tick
assert source.count('m_TranslationTarget = target')==1
for method in (body(SOURCE,'protected bool BeginTranslation('),body(SOURCE,'protected void CheckTranslation(')):
assert 'm_Home =' not in method and 'm_Deadline =' not in method
assert 'm_TranslationTarget =' not in method
assert 'SetVelocity' not in method and 'SetWorldTransform' not in method
print('PASS: actual immutable target, bounded autohover demand, drift refusal, five-second hold and unchanged qualification/deadline')