Files
EscapistsReintroduced/tests/test_air_support_policy.py
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

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)&&center==-1&&!load.NeedsService(0));
load.m_Order=&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)&&center==730&&!load.NeedsService(299999));
center=-1;assert(!load.Cover(300000,center)&&center==-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=&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')