Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
102 lines
6.6 KiB
Python
102 lines
6.6 KiB
Python
"""Execute production HQ reinforcement policy against confidence and force scenarios."""
|
|
from policy_harness import ROOT, run
|
|
import re
|
|
source=(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_AirSupportPolicy.c').read_text().split('// One immutable')[0]
|
|
source=source.replace('class ', 'struct ').replace('\n}', '\n};').replace('ERR_AirSupportSituation s','ERR_AirSupportSituation* s').replace('s.', 's->').replace('ERR_HQMemoryRules.','ERR_HQMemoryRules::').replace('Math.','Math::')
|
|
source=source.replace('ERR_HQMemoryRules->', 'ERR_HQMemoryRules::')
|
|
source=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',source)
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
#include <limits>
|
|
struct string:std::string{using std::string::string;bool IsEmpty(){return empty();}};
|
|
struct Math {static int Clamp(int x,int a,int b){return std::clamp(x,a,b);}static float Max(float a,float b){return std::max(a,b);}};
|
|
struct ERR_HQMemoryRules{static bool Finite(float v){return std::isfinite(v);}};
|
|
'''+source+r'''
|
|
int main(){
|
|
ERR_AirSupportSituation s{};s.AircraftReady=true;s.BeliefId=1;s.Samples=3;s.Spotted=3;s.Age=5;s.Confidence=.9;s.Radius=20;s.MotionKnown=true;s.Infantry=true;s.Speed=1;
|
|
ERR_AirSupportPolicy p;assert(!p.Consider(&s,0));assert(!p.Consider(&s,19999));assert(p.Consider(&s,20000));
|
|
p.Committed(20000);assert(!p.Consider(&s,259999));assert(!p.Consider(&s,260000));assert(p.Consider(&s,280000));
|
|
// Existing hunters delay reinforcement, but no longer suppress it forever.
|
|
ERR_AirSupportPolicy ground;s.CommittedFit=6;
|
|
assert(!ground.Consider(&s,0));assert(!ground.Consider(&s,44999));assert(ground.Consider(&s,45000));
|
|
s.CommittedFit=0;s.GroundFit=6;assert(ground.Consider(&s,45000));
|
|
ERR_AirSupportPolicy losses;s.ConfirmedLosses=2;
|
|
assert(!losses.Consider(&s,0));assert(!losses.Consider(&s,9999));assert(losses.Consider(&s,10000));
|
|
s.ArtilleryBusy=true;assert(!losses.Consider(&s,20000));s.ArtilleryBusy=false;assert(losses.Consider(&s,20001));
|
|
s.BeliefId=2;assert(!losses.Consider(&s,30000));assert(!losses.Consider(&s,39999));assert(losses.Consider(&s,40000));
|
|
auto good=s;
|
|
s.Age=91;assert(!p.Consider(&s,700000));s=good;s.Confidence=.69;assert(!p.Consider(&s,700000));
|
|
s=good;s.Radius=161;assert(!p.Consider(&s,700000));s=good;s.Samples=1;assert(!p.Consider(&s,700000));
|
|
s=good;s.Infantry=false;assert(!p.Consider(&s,700000));s=good;s.Speed=7;assert(!p.Consider(&s,700000));
|
|
s=good;s.MotionKnown=false;assert(!p.Consider(&s,700000));s=good;s.AircraftReady=false;assert(!p.Consider(&s,700000));
|
|
s=good;s.Age=std::numeric_limits<float>::quiet_NaN();assert(!p.Consider(&s,700000));
|
|
assert(!p.Consider(nullptr,700000));assert(!p.Consider(&good,std::numeric_limits<float>::infinity()));
|
|
// Busy mortar airspace still vetoes drops without discarding contact history.
|
|
ERR_AirSupportPolicy blocked;auto contact=good;contact.BeliefId=9;contact.GroundFit=0;contact.ConfirmedLosses=0;
|
|
assert(!blocked.Consider(&contact,0));contact.ArtilleryBusy=true;
|
|
assert(!blocked.Consider(&contact,10000));assert(!blocked.Consider(&contact,21000));
|
|
contact.ArtilleryBusy=false;assert(blocked.Consider(&contact,22000));
|
|
contact.Age=91;assert(!blocked.Consider(&contact,23000));
|
|
contact.Age=5;assert(!blocked.Consider(&contact,24000));
|
|
contact.BeliefId=10;assert(!blocked.Consider(&contact,45000));
|
|
assert(!blocked.Consider(&contact,64999));assert(blocked.Consider(&contact,65000));
|
|
// Cooldown cannot preconfirm the next drop, and failed orders back off.
|
|
blocked.Committed(65000);contact.ArtilleryBusy=true;
|
|
assert(!blocked.Consider(&contact,304999));
|
|
contact.ArtilleryBusy=false;assert(!blocked.Consider(&contact,305000));
|
|
assert(!blocked.Consider(&contact,324999));assert(blocked.Consider(&contact,325000));
|
|
blocked.Retry(325000);assert(!blocked.Consider(&contact,339999));
|
|
assert(!blocked.Consider(&contact,340000));assert(blocked.Consider(&contact,360000));
|
|
|
|
}
|
|
''')
|
|
print('PASS: production airborne memory confidence, persistence, ground coverage, confirmed losses, artillery veto, cooldown and invalid-input rules')
|
|
|
|
from policy_harness import body
|
|
load=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_AirAssaultLoad.c'
|
|
cover=body(load,'bool Cover(').replace('out vector center','int& center').replace('m_Order.Target','m_Order->Target')
|
|
service=body(load,'bool NeedsService(')
|
|
abort=body(load,'void Abort(').replace('null','nullptr')
|
|
budget=body(load,'static float ApproachBudget(').replace('Math.', 'Math::')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
struct Math {static float Clamp(float x,float a,float b){return std::clamp(x,a,b);}static float Max(float a,float b){return std::max(a,b);}};
|
|
struct Geometry {'''+budget+r'''};
|
|
struct Order {int Target=730;};
|
|
struct Load {
|
|
Order* m_Order=nullptr;bool m_Used=false,m_Approaching=false,m_RunCommitted=false,m_HasDropped=false;float m_CoverUntil=300000,m_AirspaceUntil=450000;
|
|
'''+cover+service+abort+r'''
|
|
};
|
|
int main(){
|
|
Load load;Order order;int center=-1;
|
|
assert(!load.Cover(0,center)&¢er==-1&&!load.NeedsService(0));
|
|
load.m_Order=ℴload.m_Used=true;load.m_Approaching=true;
|
|
assert(!load.Cover(1,center)&&!load.NeedsService(300001));
|
|
load.m_Approaching=false;assert(load.Cover(299999,center)&¢er==730&&!load.NeedsService(299999));
|
|
center=-1;assert(!load.Cover(300000,center)&¢er==-1&&load.NeedsService(300000));
|
|
// Previously an expired approach with nobody dropped falsely entered cover
|
|
// and made the original passenger load unavailable until an airport visit.
|
|
load.m_Approaching=true;load.m_RunCommitted=true;load.Abort();
|
|
assert(!load.m_Order&&!load.m_Used&&!load.m_Approaching&&!load.m_RunCommitted);
|
|
assert(!load.Cover(1,center)&&!load.NeedsService(1)&&load.m_AirspaceUntil==0);
|
|
load.Abort();assert(!load.m_Used); // idempotent, no new soldiers
|
|
// A partial/complete real drop must retain cover and descending airspace.
|
|
load.m_Order=ℴload.m_HasDropped=true;load.m_Used=true;load.m_Approaching=true;
|
|
load.m_CoverUntil=350000;load.m_AirspaceUntil=450000;load.Abort();
|
|
assert(load.Cover(349999,center)&&load.m_Used&&load.m_Order==&order&&load.m_AirspaceUntil==450000);
|
|
assert(load.NeedsService(350000));
|
|
// Separate radio freshness (unchanged) from physical travel time. Observed
|
|
// approaches of ~1.1 km and ~5 km cannot complete within the old 120 seconds.
|
|
assert(Geometry::ApproachBudget(0,0)==120000);
|
|
assert(Geometry::ApproachBudget(1100,0)==200000);
|
|
assert(Geometry::ApproachBudget(5000,0)==590000);
|
|
assert(Geometry::ApproachBudget(0,600)==390000);
|
|
assert(Geometry::ApproachBudget(20000,2000)==600000);
|
|
}
|
|
''')
|
|
print('PASS: production bounded approach deadline, empty-drop reuse, partial-drop protection; cover retains the carried HQ target until its deadline; airport reload waits until the drop pass and cover have finished')
|