Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
"""Execute production rescue landing policy and routing; no native physics claim."""
|
||||
from policy_harness import ROOT, body, run
|
||||
|
||||
base = ROOT / 'addons/core/Scripts/Game/ERR/10_AI/ERR_ReconFlightComponent.c'
|
||||
rescue = ROOT / 'addons/core/Scripts/Game/ERR/10_AI/ERR_RescueFlightComponent.c'
|
||||
|
||||
|
||||
def native(method):
|
||||
return (method.replace('protected override ', '').replace('protected ', 'virtual ')
|
||||
.replace('Math.', 'Math::').replace('super.', 'Base::')
|
||||
.replace('ERR_RescueLandingRules.', 'ERR_RescueLandingRules::'))
|
||||
|
||||
|
||||
run(r'''
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#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 low,float high){return std::clamp(a,low,high);}
|
||||
};
|
||||
struct vector {
|
||||
float x=0,y=0,z=0;
|
||||
vector()=default;vector(float a,float b,float c):x(a),y(b),z(c){}
|
||||
float& operator[](int i){return i==0?x:i==1?y:z;}
|
||||
vector operator*(float f)const{return{x*f,y*f,z*f};}
|
||||
vector operator-(vector b)const{return{x-b.x,y-b.y,z-b.z};}
|
||||
float Length()const{return std::sqrt(x*x+y*y+z*z);}
|
||||
vector Normalized()const{float n=Length();return n>0?(*this)*(1/n):vector{};}
|
||||
};
|
||||
float Horizontal(vector a){return std::hypot(a.x,a.z);}
|
||||
bool Near(float a,float b){return std::abs(a-b)<0.0001f;}
|
||||
bool Same(vector a,vector b){return Near(a.x,b.x)&&Near(a.y,b.y)&&Near(a.z,b.z);}
|
||||
struct Base {
|
||||
''' + native(body(base, 'protected vector LandingVelocity(')) + r'''
|
||||
};
|
||||
struct ERR_RescueLandingRules {
|
||||
''' + native(body(rescue, 'static vector Velocity(')) + r'''
|
||||
};
|
||||
struct Rescue : Base {
|
||||
bool m_Escort=false,m_Departing=false;
|
||||
''' + native(body(rescue, 'protected override vector LandingVelocity(')) + r'''
|
||||
};
|
||||
// Integrate only the velocity policy with the controller's unchanged 2-second
|
||||
// settling condition. Native collision, suspension and frame scheduling are not
|
||||
// modeled here and require the separate in-engine landing fixture.
|
||||
float IdealLandingSeconds(Base& flight,int hz){
|
||||
vector offset{90,-150,0};float dt=1.0f/hz,settled=0;
|
||||
for(int frame=0;frame<hz*180;frame++){
|
||||
vector velocity=flight.LandingVelocity(offset);
|
||||
if(Horizontal(offset)<3&&std::abs(offset.y)<1&&velocity.Length()<1)settled+=dt;
|
||||
else settled=0;
|
||||
if(settled>=2)return frame*dt;
|
||||
vector next=offset-velocity*dt;
|
||||
assert(next.y<=0); // Continuous taper must not command an overshoot through the pad.
|
||||
assert(Horizontal(next)<=Horizontal(offset)+0.0001f);
|
||||
offset=next;
|
||||
}
|
||||
assert(false&&"landing policy never settled");return -1;
|
||||
}
|
||||
int main(){
|
||||
Base base;Rescue rescue;Base& dispatched=rescue;
|
||||
assert(Same(base.LandingVelocity({0,0,0}),{}));
|
||||
assert(Same(dispatched.LandingVelocity({0,0,0}),{}));
|
||||
assert(Same(base.LandingVelocity({0,-150,0}),{0,-3,0}));
|
||||
assert(Same(dispatched.LandingVelocity({0,-150,0}),{0,-9,0}));
|
||||
assert(Same(dispatched.LandingVelocity({0,-30,0}),{0,-9,0}));
|
||||
assert(Same(dispatched.LandingVelocity({0,-20,0}),{0,-6,0}));
|
||||
assert(Same(dispatched.LandingVelocity({0,-10,0}),{0,-3,0}));
|
||||
// The extra speed is downward only. Below-pad recovery remains the old
|
||||
// proportional climb with its original 3 m/s limit, even outside alignment.
|
||||
for(float below : {0.1f,1.0f,5.0f,10.0f,100.0f}){
|
||||
assert(Same(base.LandingVelocity({0,below,0}),dispatched.LandingVelocity({0,below,0})));
|
||||
assert(dispatched.LandingVelocity({100,below,0}).y>0);
|
||||
assert(dispatched.LandingVelocity({100,below,0}).y<=3);
|
||||
}
|
||||
// Exactly the original final approach vector within 16 m horizontally and
|
||||
// ten metres vertically, including the 15 m alignment boundary.
|
||||
for(float x : {-16.0f,-15.01f,-15.0f,-7.0f,0.0f,7.0f,15.0f,15.01f,16.0f})
|
||||
for(float y : {-10.0f,-9.99f,-5.0f,-1.0f,0.0f,1.0f,5.0f,10.0f})
|
||||
assert(Same(base.LandingVelocity({x,y,0}),dispatched.LandingVelocity({x,y,0})));
|
||||
assert(dispatched.LandingVelocity({15,-100,0}).y==-9);
|
||||
assert(dispatched.LandingVelocity({15.001f,-100,0}).y==0);
|
||||
assert(dispatched.LandingVelocity({12,-100,9}).y==-9); // Radial gate, not an axis-only test.
|
||||
assert(dispatched.LandingVelocity({12,-100,9.001f}).y==0);
|
||||
// All approach directions remain aligned with the pad; the altitude must not
|
||||
// inflate the horizontal speed, and the horizontal cap cannot affect climb.
|
||||
for(float x : {-100.0f,-10.0f,0.0f,10.0f,100.0f})
|
||||
for(float z : {-100.0f,-10.0f,0.0f,10.0f,100.0f}){
|
||||
vector offset{x,-150,z};vector velocity=dispatched.LandingVelocity(offset);
|
||||
assert(Horizontal(velocity)<=14.0001f);
|
||||
assert(Horizontal(base.LandingVelocity(offset))<=8.0001f);
|
||||
assert(Near(velocity.x*z,velocity.z*x));
|
||||
assert(velocity.x*x+velocity.z*z>=0);
|
||||
if(Horizontal(offset)>15)assert(velocity.y==0);
|
||||
if(Horizontal(offset)>28)assert(Near(Horizontal(velocity),14));
|
||||
assert(Same({velocity.x,0,velocity.z},dispatched.LandingVelocity({x,0,z})));
|
||||
}
|
||||
// No downward speed jump at 30 m or 10 m: braking is monotonic and bounded
|
||||
// by the original 0.3 gain all the way through touchdown.
|
||||
float previous=0;
|
||||
for(int centimetres=0;centimetres<=4000;centimetres++){
|
||||
float height=centimetres/100.0f,speed=-dispatched.LandingVelocity({0,-height,0}).y;
|
||||
assert(speed>=previous-0.00001f&&speed-previous<0.00301f&&speed<=9);
|
||||
previous=speed;
|
||||
}
|
||||
// Exercise virtual dispatch with each flight role. Escorts and outbound
|
||||
// transport landings continue to use the default recon/logistics profile.
|
||||
for(bool escort : {false,true})for(bool departing : {false,true}){
|
||||
rescue.m_Escort=escort;rescue.m_Departing=departing;
|
||||
for(vector offset : {vector{80,-150,0},vector{0,-100,0},vector{8,-5,4},vector{20,50,30}}){
|
||||
vector expected=(escort||departing)?base.LandingVelocity(offset):ERR_RescueLandingRules::Velocity(offset);
|
||||
assert(Same(dispatched.LandingVelocity(offset),expected));
|
||||
}
|
||||
}
|
||||
rescue.m_Escort=false;rescue.m_Departing=false;
|
||||
for(int hz : {15,30,60,120}){
|
||||
float original=IdealLandingSeconds(base,hz),pickup=IdealLandingSeconds(dispatched,hz);
|
||||
assert(original>60&&original<70&&pickup>30&&pickup<36);
|
||||
assert(pickup<original*0.6f);
|
||||
std::printf("Policy-only arrival from 90 m offset / 150 m height at %d Hz: original %.2fs, rescue %.2fs\n",hz,original,pickup);
|
||||
}
|
||||
}
|
||||
''')
|
||||
print('PASS: faster rescue descent, continuous near-ground braking, alignment gate, unchanged recovery/escort/default profile, and bounded settling at 15/30/60/120 Hz')
|
||||
Reference in New Issue
Block a user