Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
109 lines
7.2 KiB
Python
109 lines
7.2 KiB
Python
"""Production recon timing, orbit and standoff geometry; no native flight claim."""
|
|
import re
|
|
from policy_harness import ROOT,body,run
|
|
path=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_AirReconSystem.c'
|
|
schedule=path.read_text().split('class ERR_AirReconSystem')[0]
|
|
schedule=schedule[schedule.index('class ERR_ReconSchedule'):].replace('class ERR_ReconSchedule','struct ERR_ReconSchedule').replace('\n}\n','\n};\n')
|
|
flight=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ReconFlightComponent.c'
|
|
rules=body(flight,'static vector Cruise(')+'\n'+body(flight,'static vector StandOff(')
|
|
rules=rules.replace('Math.','Math::').replace('vector.','vector::')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
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 Clamp(float a,float lo,float hi){return std::clamp(a,lo,hi);}};
|
|
struct vector{float x=0,y=0,z=0;vector()=default;vector(float a,float b,float c):x(a),y(b),z(c){}vector(const char*s){sscanf(s,"%f %f %f",&x,&y,&z);}float&operator[](int i){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-(vector b)const{return{x-b.x,y-b.y,z-b.z};}vector operator*(float f)const{return{x*f,y*f,z*f};}vector&operator-=(vector b){x-=b.x;y-=b.y;z-=b.z;return*this;}float Length()const{return std::sqrt(x*x+y*y+z*z);}vector Normalized()const{float f=Length();if(f==0)return{};return{x/f,y/f,z/f};}void Normalize(){*this=Normalized();}static float Dot(vector a,vector b){return a.x*b.x+a.y*b.y+a.z*b.z;}};
|
|
vector Vector(float x,float y,float z){return{x,y,z};}
|
|
'''+schedule+rules+r'''
|
|
int main(){
|
|
ERR_ReconSchedule s{};assert(!s.Due(1000000,false));s.Begin(1000);assert(s.Deadline==901000);
|
|
s.Begin(2000);assert(s.Deadline==901000);assert(!s.Due(900999,false));assert(s.Due(901000,false));assert(!s.Due(901000,true));
|
|
s.Launched();assert(s.Deadline<0&&!s.Due(2000000,false));s.Change(2,2000,true);assert(s.Deadline<0&&s.Minutes==2);
|
|
s.Lost(3000);assert(s.Deadline==123000&&!s.Due(122999,false));s.Change(15,4000,false);assert(s.Deadline==904000);
|
|
s.Stop();s.Begin(100);s.Lost(200);s.Change(1,300,false);assert(s.Deadline<0&&!s.Due(1e8,false));
|
|
vector orbit=Cruise({300,120,0},{0,0,0},false);assert(std::abs(orbit.x)<.001&&std::abs(orbit.z+28)<.001);
|
|
vector far=Cruise({1000,120,0},{0,0,0},false);assert(far.x==-40&&far.y==0);
|
|
vector near=Cruise({100,120,0},{0,0,0},false);assert(near.x>0); // push back out toward 300m orbit
|
|
vector home=Cruise({20,120,0},{0,0,0},true);assert(home.x==-8); // return approaches pad instead of orbiting
|
|
vector away=StandOff({100,120,0},{0,0,0},{-40,0,0});assert(away.x>0);
|
|
vector clear=StandOff({300,120,0},{0,0,0},{-40,0,0});assert(clear.x==-40);
|
|
vector coincident=StandOff({0,120,0},{0,0,0},{0,0,0});assert(std::isfinite(coincident.x)&&coincident.x>0);
|
|
// Clearing the target area specifies a minimum outward SPEED, not a
|
|
// per-frame impulse. Repeated calls must not accelerate an already-safe
|
|
// radial velocity, even while the aircraft remains inside the radius.
|
|
vector outward{25,0,28};
|
|
for(int i=0;i<600;i++)outward=StandOff({100,120,0},{0,0,0},outward);
|
|
assert(std::abs(outward.x-25)<.001&&outward.z==28);
|
|
vector correction=StandOff({170,120,0},{0,0,0},{-40,0,28});
|
|
assert(std::abs(correction.x-5)<.001&&correction.z==28);
|
|
// The same stationary close-contact observation at different frame rates
|
|
// must not inject unbounded speed into the persistent smoothed velocity.
|
|
for(int hz: {30,60,120}){
|
|
vector v{-40,0,28};
|
|
for(int frame=0;frame<hz*5;frame++){
|
|
vector desired{-10,0,28};v=v+(desired-v)*(0.6f/hz);
|
|
v=StandOff({100,120,0},{0,0,0},v);
|
|
assert(v.Length()<40);
|
|
}
|
|
}
|
|
|
|
}
|
|
''')
|
|
print('PASS: original 15-minute launch/loss cadence, active/captured timer disarm, saved delay reset, stop, 300m orbit and bounded close-contact standoff at 30/60/120 Hz')
|
|
|
|
# Explicit entity deletion must not leave an inactive launch deadline behind.
|
|
removed=body(path,'void Removed(')
|
|
for old,new in [('ERR_ReconFlightComponent flight','Flight* flight'),('Replication.','Replication::'),('ERR_HQ.','ERR_HQ::'),('m_Mode.','m_Mode->'),('null','nullptr'),('GetGame().GetWorld().GetWorldTime()','clockNow')]:
|
|
removed=removed.replace(old,new)
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
using string=std::string;
|
|
struct Flight{};struct Mode{bool active=true;bool Active(){return active;}};
|
|
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
|
|
struct ERR_HQ{inline static int events=0;static void Event(string,string){events++;}};
|
|
float clockNow=5000;
|
|
'''+schedule+r'''
|
|
struct System{
|
|
ERR_ReconSchedule m_Schedule;Flight* m_Current=nullptr;Mode* m_Mode=nullptr;int m_PadAttempts=3;string m_Reason;
|
|
string Status(){return m_Reason;}
|
|
'''+removed+r'''
|
|
};
|
|
int main(){
|
|
Mode mode;Flight owned,other;System s{};s.m_Mode=&mode;s.m_Current=&owned;s.m_Schedule.Begin(0);s.m_Schedule.Launched();
|
|
s.Removed(nullptr);s.Removed(&other);assert(s.m_Current==&owned&&s.m_Schedule.Deadline<0&&ERR_HQ::events==0);
|
|
Replication::server=false;s.Removed(&owned);assert(s.m_Current==&owned);Replication::server=true;
|
|
mode.active=false;s.Removed(&owned);assert(s.m_Current==&owned);mode.active=true;
|
|
s.Removed(&owned);assert(!s.m_Current&&s.m_PadAttempts==0&&s.m_Schedule.Deadline==905000&&ERR_HQ::events==1);
|
|
clockNow=9000;s.Removed(&owned);assert(s.m_Schedule.Deadline==905000&&ERR_HQ::events==1);
|
|
s.m_Current=&other;s.m_Schedule.Stop();s.Removed(&other);assert(s.m_Current==&other&&s.m_Schedule.Deadline<0);
|
|
}
|
|
''')
|
|
print('PASS: actual aircraft-removal notification guards owner/server/active/stop and schedules one full delay without replay extension')
|
|
|
|
# Persisting Random must feed a resolved positive duration to the live schedule.
|
|
set_delay=body(path,'bool SetDelay(')
|
|
for a,b in [('Replication.','Replication::'),('ERR_HQ.','ERR_HQ::'),('m_Mode.','m_Mode->'),('Settings().','Settings()->'),('GetGame().GetWorld().GetWorldTime()','clockNow'),('null','nullptr')]:set_delay=set_delay.replace(a,b)
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
using string=std::string;
|
|
namespace Replication{bool server=true;bool IsServer(){return server;}}
|
|
namespace ERR_HQ{void Event(string,string){}}
|
|
float clockNow=1000;
|
|
struct Settings{bool writable=true;int saved=15,resolved=37;bool Set(string,int v){if(!writable)return false;saved=v;return true;}int Runtime(string){return saved<0?resolved:saved;}};
|
|
struct Mode{struct Settings settings;struct Settings* Settings(){return &settings;}};
|
|
''' + schedule + r'''
|
|
struct System{Mode* m_Mode=nullptr;void* m_Current=nullptr;ERR_ReconSchedule m_Schedule{};int m_PadAttempts=2;string Status(){return "";}
|
|
''' + set_delay + r'''
|
|
};
|
|
int main(){Mode mode;System s;s.m_Mode=&mode;s.m_Schedule.Begin(0);
|
|
assert(s.SetDelay(-1)&&mode.settings.saved==-1&&s.m_Schedule.Minutes==37&&s.m_Schedule.Deadline==2221000);
|
|
mode.settings.writable=false;assert(!s.SetDelay(5)&&s.m_Schedule.Minutes==37&&mode.settings.saved==-1);
|
|
mode.settings.writable=true;int flight=1;s.m_Current=&flight;s.m_Schedule.Launched();assert(s.SetDelay(17)&&s.m_Schedule.Minutes==17&&s.m_Schedule.Deadline<0);
|
|
Replication::server=false;assert(!s.SetDelay(18)&&mode.settings.saved==17);
|
|
}
|
|
''')
|
|
print('PASS: actual helicopter delay applies resolved Random values, preserves countdown on save failure, and leaves active flights running')
|