193 lines
16 KiB
Python
193 lines
16 KiB
Python
"""Actual field evidence/observation bodies; physical engine boundaries remain untested."""
|
|
from policy_harness import ROOT, body, run
|
|
import re
|
|
P=ROOT/'addons/ai/Scripts/Game/ERR/ERR_NativeAircraftFieldProbe.c'
|
|
s=P.read_text()
|
|
def cpp(x):
|
|
x=x.replace('ERR_AircraftFlightState.','State::').replace('ERR_NativeAircraftFieldRules.','Rules::').replace('ERR_AircraftFieldRules.','FieldRules::').replace('vector.','vector::').replace('ERR_AircraftFieldFlight flight','Flight* flight').replace('flight.','flight->')
|
|
return re.sub(r'(?<![\w.])(\d+\.\d+)(?![\w.])',r'\1f',x)
|
|
rules=s[s.index('class ERR_NativeAircraftFieldRules'):s.index('class ERR_NativeAircraftFieldProbe')].replace('class ERR_NativeAircraftFieldRules','struct Rules').rstrip()+';'
|
|
observed='\n'.join(cpp(body(P,k)) for k in ('void ObserveBrakingContact(', 'void BrakeBegan(', 'void BrakeStopped('))
|
|
run(r'''#include <cassert>
|
|
#include <cmath>
|
|
#include <string>
|
|
using string=std::string;
|
|
struct vector {float x=0,y=0,z=0;static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}};
|
|
namespace State {enum {COMPLETE=8,CANCELLED,FAILED,RELINQUISHED};}
|
|
'''+cpp(rules)+r'''
|
|
struct FieldRules {static float HorizontalSpeed(vector v){return std::hypot(v.x,v.z);}static float BrakingReserve(float s){return s*s/.30f+s+3;}};
|
|
struct Flight {float ERR_NativeFieldStageDeadline(){return 70000;}vector LandingPoint(){return {100,0,0};}};
|
|
template<typename...T>void PrintFormat(T...args){}
|
|
struct Probe {
|
|
bool m_Stopped=false,m_BrakeObserved=false,m_BrakeStopped=false,m_BrakePassed=false,m_BrakeContactObserved=false;
|
|
string m_ObservationFailure;
|
|
Flight* m_Flight=nullptr;float m_BrakeAt=0,m_BrakeReserve=0,m_BrakeStageDeadline=0,m_ControlDeadline=240000,m_BrakeSampledTravel=0,m_BrakeLowSince=-1;
|
|
vector m_BrakeOrigin,m_BrakePrevious,m_BrakeLandingPoint;
|
|
'''+observed+r'''
|
|
};
|
|
int main(){
|
|
assert(Rules::Cruise(1,1,false,1)&&!Rules::Cruise(.99f,1,false,1)&&!Rules::Cruise(1,2.501f,false,1));
|
|
assert(!Rules::Cruise(1,1,true,1)&&!Rules::Cruise(1,1,false,2));
|
|
assert(Rules::Terminal(false,State::COMPLETE,0,true,true,true,true,100,.5f));
|
|
assert(!Rules::Terminal(false,State::COMPLETE,0,true,false,true,true,100,.5f));
|
|
assert(!Rules::Terminal(false,State::COMPLETE,0,true,true,false,true,100,.5f));
|
|
assert(!Rules::Terminal(false,State::COMPLETE,0,true,true,true,false,100,.5f));
|
|
assert(!Rules::Terminal(false,State::COMPLETE,0,true,true,true,true,98,.5f));
|
|
assert(!Rules::Terminal(false,State::COMPLETE,0,true,true,true,true,100,.76f));
|
|
assert(Rules::Terminal(true,State::CANCELLED,1,false,false,false,true,30,.5f));
|
|
assert(!Rules::Terminal(true,State::CANCELLED,1,true,false,false,true,30,.5f));
|
|
assert(!Rules::Terminal(true,State::COMPLETE,1,false,false,false,true,30,.5f));
|
|
assert(!Rules::Terminal(true,State::CANCELLED,0,false,false,false,true,30,.5f));
|
|
assert(!Rules::Terminal(true,State::CANCELLED,2,false,false,false,true,30,.5f));
|
|
assert(Rules::PhysicalFinish(true,true,true,1,1,1,1,101,101));
|
|
assert(!Rules::PhysicalFinish(true,false,true,1,1,1,1,101,101));
|
|
assert(!Rules::PhysicalFinish(true,true,false,1,1,1,1,101,101));
|
|
assert(!Rules::PhysicalFinish(true,true,true,0,1,1,1,101,101));
|
|
assert(!Rules::PhysicalFinish(true,true,true,1,2,1,1,101,101));
|
|
for(int i=0;i<5;i++)assert(!Rules::CanDispose(i!=0,i!=1,i!=2,i!=3,i!=4));
|
|
Flight flight,foreign;Probe p;p.m_Flight=&flight;
|
|
p.BrakeBegan(&foreign,1000,{0,0,0},{2,0,0},false);assert(!p.m_BrakeObserved);
|
|
p.BrakeBegan(&flight,1000,{0,0,0},{2,0,0},false);assert(p.m_BrakeObserved&&p.m_BrakeReserve>18&&p.m_BrakeReserve<19);
|
|
p.m_BrakeSampledTravel=15;p.m_BrakePrevious={15,0,0};p.m_BrakeLowSince=2000;
|
|
p.BrakeStopped(&flight,4000,{16,0,0},{.1f,0,0},false);assert(p.m_BrakePassed&&p.m_BrakeStopped&&p.m_ObservationFailure.empty());
|
|
p.BrakeBegan(&flight,5000,{50,0,0},{2,0,0},false);assert(!p.m_ObservationFailure.empty()&&p.m_BrakeAt==1000); // no renewal
|
|
for(int fault=0;fault<4;fault++){
|
|
Probe bad;bad.m_Flight=&flight;bad.BrakeBegan(&flight,1000,{0,0,0},{2,0,0},false);bad.m_BrakeLowSince=2000;
|
|
float stopped=4000;vector at={16,0,0},vel={.1f,0,0};
|
|
if(fault==0){at={19,0,0};} if(fault==1){stopped=3999;} if(fault==2){vel={.25f,0,0};} if(fault==3){bad.m_BrakeSampledTravel=20;}
|
|
bad.BrakeStopped(&flight,stopped,at,vel,false);assert(!bad.m_BrakePassed&&!bad.m_ObservationFailure.empty()&&!bad.m_Stopped); // failure deferred outside callback
|
|
}
|
|
// Ground friction at entry, an intermediate callback, or the stop sample
|
|
// permanently invalidates an otherwise plausible measured reserve/low-speed pass.
|
|
for(int contactAt=0;contactAt<3;contactAt++){
|
|
Probe grounded;grounded.m_Flight=&flight;
|
|
grounded.BrakeBegan(&flight,1000,{0,0,0},{2,0,0},contactAt==0);
|
|
grounded.m_BrakeLowSince=2000;
|
|
grounded.ObserveBrakingContact(contactAt==1);
|
|
grounded.ObserveBrakingContact(false); // later loss of contact cannot clear evidence
|
|
grounded.BrakeStopped(&flight,4000,{16,0,0},{.1f,0,0},contactAt==2);
|
|
assert(grounded.m_BrakeContactObserved&&!grounded.m_BrakePassed&&!grounded.m_ObservationFailure.empty()&&!grounded.m_Stopped);
|
|
}
|
|
Probe before;before.ObserveBrakingContact(true);assert(!before.m_BrakeContactObserved);
|
|
Probe after;after.m_BrakeObserved=true;after.m_BrakeStopped=true;after.ObserveBrakingContact(true);assert(!after.m_BrakeContactObserved); // actual landing is separate
|
|
assert(!Rules::Braking(NAN,1,1,0,3000,0,false)&&!Rules::Braking(10,NAN,1,0,3000,0,false));
|
|
}
|
|
''')
|
|
assert s.startswith('#ifdef WORKBENCH') and s.rstrip().endswith('#endif')
|
|
assert 'outcome == "aircraft-field"' in body(P,'static bool Enabled(')
|
|
find=body(P,'protected bool FindPad(')
|
|
assert '"4890 0 11730"' in find and '"0 0 100"' in find
|
|
assert find.count('.Survey(')==1 and 'for (' not in find and 'AirportOffset' not in s
|
|
assert s.count('ERR_Actors.Spawn(AIRCRAFT,')==1 and s.count('SpawnCharacterInCompartment(')==1
|
|
assert s.count('physics.SetActive(ActiveState.ACTIVE)')==1
|
|
for prohibited in ('SetWorldTransform(', 'SetOrigin(', 'SetVelocity(', '.StartEngine(', '.StopEngine(', 'm_Mode.Own(', 'm_Flight.Tick(', 'm_Flight.Cancel(', 'm_Flight.StartField(', 'FieldDemand('):assert prohibited not in s
|
|
for sig in ('void PrepareActions(', 'void PrepareHelicopter('):
|
|
assert '++' in body(P,sig) and '.Prepare' not in body(P,sig)
|
|
for sig in ('void ObserveBrakingContact(', 'void BrakeBegan(', 'void BrakeStopped('):assert 'Check(' not in body(P,sig) and 'Stop(' not in body(P,sig)
|
|
assert 'ObserveBrakingContact' in body(P,'void PrepareHelicopter(') and 'ObserveBrakingContact(contact)' in body(P,'int Tick(')
|
|
assert 'super.BeginFieldBrake(now)' in body(P,'protected override bool BeginFieldBrake(')
|
|
assert 'super.CheckTranslation(now, position, height, speed, upright)' in body(P,'protected override void CheckTranslation(')
|
|
start=body(P,'protected bool StartRetainedField(');assert '.StartField(m_Aircraft, m_Envelope, ORDER_TOKEN, m_ControlDeadline)' in start
|
|
assert 'ERR_AircraftFieldFlight.Cast(entry.Flight)' in body(P,' ERR_AircraftFieldFlight ERR_NativeOriginalFieldFlight(')
|
|
tick=body(P,'int Tick(')
|
|
assert tick.index('FindPad(m_Pad)')<tick.index('ERR_Actors.Spawn(AIRCRAFT,')
|
|
assert tick.index('QualifyField(')<tick.index('StartRetainedField()')
|
|
assert tick.index('m_Service.Tick();')<tick.index('m_ObservationFailure.IsEmpty()')<tick.index('if (!m_Flight.Finished())')
|
|
assert 'now - m_CruiseSince >= 10000' in tick and 'now - m_CruiseSince >= 1000' in tick
|
|
assert 'now - m_TranslationStable >= 5000' in tick and 'now - m_LandingStable >= 2000' in tick
|
|
stop=body(P,'void Stop(')
|
|
assert stop.index('s_Current = null')<stop.index('m_Service.Stop()')<stop.index('ERR_Actors.DeleteSpawned(')<stop.index('m_Service.Tick()')<stop.index('m_Flight = null')
|
|
assert 'CanDispose(' in stop
|
|
# Execute root-owned Chat dispatch branch: failure cannot become completion/reset.
|
|
chat=ROOT/'addons/chat/Scripts/Game/ERR/ERR_NativeSmoke.c';cs=chat.read_text()
|
|
a=cs.index('if (m_ERRSmokeStep == 240)');opening=cs.index('{',a);i=opening+1;depth=1
|
|
while depth:depth+=(cs[i]=='{')-(cs[i]=='}');i+=1
|
|
branch=cs[opening+1:i-1].replace('m_ERRAircraftField.Tick','m_ERRAircraftField.Tick').replace('ERR_Phase.DEFEAT','2')
|
|
run(r'''#include <cassert>
|
|
#include <initializer_list>
|
|
struct Fixture {int result=0;int Tick(float){return result;}};
|
|
struct Chat {Fixture m_ERRAircraftField;int m_ERRSmokeStep=240,failures=0,transitions=0;void ERR_Check(bool,const char*){failures++;}void Transition(int){transitions++;}void Tick(float now){
|
|
'''+branch+r'''} };
|
|
int main(){for(int result:{-1,0,1}){Chat c;c.m_ERRAircraftField.result=result;c.Tick(1000);assert(c.failures==(result<0));assert(c.transitions==(result==1));assert(c.m_ERRSmokeStep==(result==1?241:240));}}
|
|
''')
|
|
assert 'm_ERRTestAircraftField = outcome == "aircraft-field"' in cs
|
|
assert 'm_ERRTestAircraftControl || m_ERRTestAircraftRoute || m_ERRTestAircraftField' in cs and '+ 330000' in cs
|
|
assert 'm_ERRAircraftField.Stop()' in body(chat,'protected void ERR_StopHQPatrol(')
|
|
assert 'errSmokeFieldServiceCancel' in cs and 'current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 241' in cs
|
|
print('PASS: actual field result/braking observer and Chat dispatch rules; one finite fixed candidate, deferred callback failure and guarded cleanup; no native measurement')
|
|
|
|
# Execute the real optional grid/FindPad flow, faking only CLI/world/terrain/logging.
|
|
def audit_cpp(text):
|
|
text=text.replace('protected ','').replace('out vector result','vector& result')
|
|
for before,after in (('System.','System::'),('Replication.','Replication::'),('SCR_TerrainHelper.','Terrain::'),('ERR_AircraftFieldEnvelope.','Envelope::'),('ERR_Actors.','Actors::'),('Math.','Math::'),('GetGame().GetWorld()','GetGame()->GetWorld()')):text=text.replace(before,after)
|
|
for before,after in (('"0 0 0"','vector{}'),('"0 0 100"','vector{0,0,100}'),('"4890 0 11730"','vector{4890,0,11730}')):text=text.replace(before,after)
|
|
return text
|
|
run(r'''#include <cassert>
|
|
#include <cmath>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <sstream>
|
|
using string=std::string;
|
|
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]};}};
|
|
std::ostream& operator<<(std::ostream& out,vector v){return out<<v[0]<<','<<v[1]<<','<<v[2];}
|
|
std::vector<string> logs;
|
|
void Print(const char* value){logs.push_back(value);}
|
|
template<class...A>void PrintFormat(const char* format,A...args){std::ostringstream out;((out<<args<<' '),...);logs.push_back(string(format)+"|"+out.str());}
|
|
struct System {static std::map<string,string> args;static bool GetCLIParam(string name,string& value){auto i=args.find(name);if(i==args.end())return false;value=i->second;return true;}};std::map<string,string> System::args;
|
|
struct Replication {static bool server;static bool IsServer(){return server;}};bool Replication::server=true;
|
|
struct Game {bool world=true;bool GetWorld(){return world;}} game;bool gamePresent=true;Game* GetGame(){return gamePresent?&game:nullptr;}
|
|
struct Math {static float AbsFloat(float value){return std::abs(value);}};
|
|
struct Terrain {static std::vector<vector> points;static int invalidAt;static float invalidValue;static float GetTerrainY(vector point){points.push_back(point);if(int(points.size())==invalidAt)return invalidValue;return point[0]*.001f+point[2]*.002f;}};std::vector<vector> Terrain::points;int Terrain::invalidAt=0;float Terrain::invalidValue=NAN;
|
|
struct Actors {static int groundCalls;static vector Ground(vector p){groundCalls++;p[1]=28.5f;return p;}};int Actors::groundCalls=0;
|
|
struct Envelope {static bool permitted;static int calls;static bool Survey(vector from,vector to){calls++;assert(std::abs(from[0]-4890)<.01f&&std::abs(from[2]-11730)<.01f&&to[2]-from[2]==100);return permitted;}};bool Envelope::permitted=false;int Envelope::calls=0;
|
|
struct Probe {bool m_TerrainAudited=false,m_Stopped=false,m_AllocationRequested=false,m_Aircraft=false,m_Group=false,m_Pilot=false;vector m_Destination;
|
|
'''+ '\n'.join(audit_cpp(body(P,key)) for key in ('static bool Enabled(', 'static bool TerrainAuditEnabled(', 'protected void AuditTerrain(', 'protected bool FindPad('))+r'''
|
|
};
|
|
void reset(){System::args={{"errSmoke","1"},{"errSmokeOutcome","aircraft-field"},{"errSmokeFieldTerrainAudit","1"}};logs.clear();Terrain::points.clear();Terrain::invalidAt=0;Terrain::invalidValue=NAN;Actors::groundCalls=0;Envelope::calls=0;Envelope::permitted=false;Replication::server=true;gamePresent=true;game.world=true;}
|
|
int main(){
|
|
reset();Probe p;vector unchanged={99,88,77};assert(!p.FindPad(unchanged));assert(unchanged[0]==99&&unchanged[1]==88&&unchanged[2]==77&&p.m_Destination[0]==0);
|
|
assert(Envelope::calls==1&&Actors::groundCalls==2&&Terrain::points.size()==1681&&logs.size()==1683&&p.m_TerrainAudited&&!p.m_AllocationRequested&&!p.m_Aircraft&&!p.m_Group&&!p.m_Pilot);
|
|
assert(logs.front().find("minX=4750 maxX=5150 minZ=11600 maxZ=12000 spacing=10 columns=41 rows=41 nodes=1681")!=string::npos);
|
|
assert(logs.back().find("original survey refusal unchanged; no candidate qualified")!=string::npos);
|
|
for(int i=0;i<1681;i++){
|
|
auto q=Terrain::points[i];assert(q[0]==4750+(i/41)*10&&q[1]==0&&q[2]==11600+(i%41)*10);
|
|
std::ostringstream expected;expected<<'|'<<i<<' '<<q[0]<<' '<<q[2]<<' '<<(q[0]*.001f+q[2]*.002f)<<' ';
|
|
assert(logs[i+1].find("ERR AIRCRAFT FIELD TERRAIN AUDIT POINT:")==0&&logs[i+1].find(expected.str())!=string::npos);
|
|
}
|
|
p.AuditTerrain();assert(Terrain::points.size()==1681&&logs.size()==1683); // no retry, even if directly reentered
|
|
// Neither CLI omission nor malformed/conflicting scope permits collection.
|
|
for(int fault=0;fault<13;fault++){
|
|
reset();Probe refused;
|
|
if(fault==0)System::args.erase("errSmokeFieldTerrainAudit");
|
|
if(fault==1)System::args["errSmokeFieldTerrainAudit"]="0";
|
|
if(fault==2)System::args["errSmoke"]="0";
|
|
if(fault==3)System::args["errSmokeOutcome"]="aircraft-route";
|
|
if(fault==4)Replication::server=false;
|
|
if(fault==5)System::args["errSmokeFieldServiceCancel"]="1";
|
|
if(fault==6)refused.m_Stopped=true;
|
|
if(fault==7)refused.m_AllocationRequested=true;
|
|
if(fault==8)refused.m_Aircraft=true;
|
|
if(fault==9)refused.m_Group=true;
|
|
if(fault==10)refused.m_Pilot=true;
|
|
if(fault==11)gamePresent=false;
|
|
if(fault==12)game.world=false;
|
|
assert(!refused.FindPad(unchanged));assert(Terrain::points.empty()&&logs.empty()&&!refused.m_TerrainAudited&&Envelope::calls==1);
|
|
}
|
|
// Successful original survey never runs the optional audit or changes its result.
|
|
reset();Envelope::permitted=true;Probe success;assert(success.FindPad(unchanged));assert(Terrain::points.empty()&&!success.m_TerrainAudited&&Envelope::calls==1&&success.m_Destination[2]-unchanged[2]==100);
|
|
// Malformed heights stop without retry, completion, candidate selection or allocation.
|
|
for(float invalid:{float(NAN),float(INFINITY),float(-INFINITY),1000001.0f}){
|
|
reset();Terrain::invalidAt=17;Terrain::invalidValue=invalid;Probe malformed;assert(!malformed.FindPad(unchanged));
|
|
assert(Terrain::points.size()==17&&logs.size()==18&&logs.back().find("AUDIT ABORT:")!=string::npos&&malformed.m_TerrainAudited&&!malformed.m_AllocationRequested);
|
|
malformed.AuditTerrain();assert(Terrain::points.size()==17);
|
|
}
|
|
}
|
|
''')
|
|
audit=body(P,'protected void AuditTerrain(')
|
|
for forbidden in ('Spawn(', 'SetOrigin(', 'SetActive(', 'TraceMove(', '.Survey(', '.Qualify', 'new ', 'GetEntities', 'm_Destination ='):
|
|
assert forbidden not in audit
|
|
assert audit.count('GetTerrainY(')==1
|
|
assert find.index('.Survey(')<find.index('AuditTerrain();')<find.index('return false;')<find.index('result = candidate')
|
|
print('PASS: actual opt-in terrain audit records one fixed1681-point grid, preserves original survey refusal/success and exact no-allocation scope; malformed heights stop without retry; no native data acquired')
|