Files
EscapistsReintroduced/tests/test_attack_coordination.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

83 lines
5.4 KiB
Python

"""Production shared attack-space leases and confirmed-contact manoeuvre gate."""
import re
from policy_harness import ROOT,body,run
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_AttackSpace.c'
s='\n'.join(body(p,x) for x in ['protected void Prune(', 'bool Free(', 'void Claim(', 'void Release(', 'void Clear('])
s=s.replace('protected ','').replace('foreach (','for (').replace('vector position','Vec position')
for typ in ['SCR_AIGroup','ERR_AttackSlot']:s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
s=s.replace('slot.','slot->').replace('added.','added->').replace('m_Slots[i].','m_Slots[i]->').replace('vector.DistanceXZ','Distance').replace('ERR_ForceRules.','ERR_ForceRules::')
r=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c'
gate='\n'.join(body(r,x) for x in ['static bool Finite(', 'static bool Actionable(', 'static bool Maneuver('])
gate=gate.replace('0.65', '0.65f').replace('0.15','0.15f')
run(r'''
#include <cassert>
#include <cmath>
#include <vector>
#include <limits>
struct Vec{float x=0,z=0;};float Distance(Vec a,Vec b){return std::hypot(a.x-b.x,a.z-b.z);}
struct ERR_ForceRules{static bool Finite(float x){return std::isfinite(x);}static bool FiniteVector(Vec p){return Finite(p.x)&&Finite(p.z);}};
struct SCR_AIGroup{};struct ERR_AttackSlot{SCR_AIGroup*Group=nullptr;Vec Position;float Until=0;};
template<class T>struct Array:std::vector<T>{int Count(){return this->size();}void Insert(T p){this->push_back(p);}void Remove(int i){delete this->at(i);this->erase(this->begin()+i);}void Clear(){while(!this->empty())Remove(0);}};
struct Space{Array<ERR_AttackSlot*>m_Slots;
'''+s+r'''
};struct Rules{
'''+gate+r'''
};int main(){
Space s;SCR_AIGroup cover,left,right;assert(s.Free(&cover,{},0));s.Claim(&cover,{},0);
assert(s.Free(&cover,{},1)&&!s.Free(&left,{59,0},1)&&s.Free(&left,{60,0},1));
s.Claim(&left,{120,0},1000);assert(!s.Free(&right,{100,0},1001));
s.Claim(&left,{240,0},2000);assert(s.Free(&right,{120,0},2001)&&!s.Free(&right,{240,0},2001));
s.Release(&cover);assert(s.Free(&right,{},2001)&&!s.Free(&right,{240,0},2001));
assert(!s.Free(nullptr,{240,0},21999)&&s.Free(nullptr,{240,0},22000));
s.Claim(nullptr,{},22001);assert(s.m_Slots.empty());
s.Claim(&cover,{std::numeric_limits<float>::quiet_NaN(),0},22001);assert(s.m_Slots.empty());
assert(!s.Free(&cover,{},-1));
s.Claim(&left,{120,0},22002);s.m_Slots[0]->Group=nullptr;assert(s.Free(&right,{120,0},22003));
SCR_AIGroup groups[129];for(int i=0;i<129;i++)s.Claim(&groups[i],{float(i*100),0},30000);assert(s.m_Slots.Count()==128);
s.Clear();assert(s.m_Slots.empty());
assert(Rules::Maneuver(true,.65,60,150,1)&&Rules::Maneuver(true,1,0,1,2));
assert(!Rules::Maneuver(false,1,0,1,1)&&!Rules::Maneuver(true,.64,0,1,1));
assert(!Rules::Maneuver(true,1,61,1,1)&&!Rules::Maneuver(true,1,0,151,1));
assert(!Rules::Maneuver(true,1,0,1,0)&&!Rules::Maneuver(true,1,0,1,3));
}
''')
print('PASS: shared own-force attack spacing, own-group refresh, exact release, expiry/deletion, finite capacity and fresh confirmed sighting-only flank permission')
# Execute the native vehicle-usage boundary: foot teams cannot choose their
# troop truck as transport until regroup, and a captured truck is never restored.
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyTactics.c'
s=body(p,'void VehicleUse(').replace('null','nullptr')
s=s.replace('SCR_AIVehicleUsageComponent.Cast(truck.FindComponent(SCR_AIVehicleUsageComponent))','truck->usage')
for typ in ['ERR_GameMode','Vehicle','SCR_AIGroupUtilityComponent','SCR_AIVehicleUsageComponent']:
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
for name in ['Group','utility','mode']:s=s.replace(name+'.',name+'->')
s=s.replace('ERR_ConvoyRules.','ERR_ConvoyRules::')
run(r'''
#include <cassert>
struct SCR_AIVehicleUsageComponent{};
struct Vehicle{SCR_AIVehicleUsageComponent*usage=nullptr;};
struct SCR_AIGroupUtilityComponent{int removed=0,added=0;void RemoveUsableVehicle(SCR_AIVehicleUsageComponent*){removed++;}void AddUsableVehicle(SCR_AIVehicleUsageComponent*){added++;}};
struct GroupType{int faction=1;SCR_AIGroupUtilityComponent*utility=nullptr;auto GetGroupUtilityComponent(){return utility;}int GetFaction(){return faction;}};
struct ERR_GameMode{bool active=true;bool Active(){return active;}int EnemyFaction(){return 1;}};
struct ERR_ConvoyRules{static inline bool captured=false;static bool Captured(Vehicle*,GroupType*){return captured;}};
struct Team{GroupType*Group=nullptr;Vehicle*m_WalkVehicle=nullptr;bool alive=true;bool Living(){return alive;}
'''+s+r'''
};int main(){
for(int invalid=0;invalid<6;invalid++){
ERR_GameMode mode;Vehicle truck,other;SCR_AIVehicleUsageComponent usage;SCR_AIGroupUtilityComponent utility;GroupType group;Team team;
truck.usage=other.usage=&usage;group.utility=&utility;team.Group=&group;ERR_ConvoyRules::captured=false;
team.VehicleUse(&mode,&truck,false);team.VehicleUse(&mode,&truck,false);
assert(utility.removed==1&&team.m_WalkVehicle==&truck);
team.VehicleUse(&mode,&other,true);assert(!utility.added&&team.m_WalkVehicle==&truck);
if(invalid==1)mode.active=false;
if(invalid==2)team.alive=false;
if(invalid==3)group.faction=2;
if(invalid==4)ERR_ConvoyRules::captured=true;
team.VehicleUse(invalid==5?nullptr:&mode,&truck,true);
assert(utility.added==(invalid==0)&&!team.m_WalkVehicle);
team.VehicleUse(&mode,&truck,true);assert(utility.added==(invalid==0));
}
}
''')
print('PASS: actual convoy foot transport detachment is idempotent; regroup restores only its live friendly uncaptured original truck')