Files
EscapistsReintroduced/tests/test_convoy_admission.py
T

144 lines
13 KiB
Python

"""Execute actual convoy readiness admission and finite cargo request policy."""
import os,re
from pathlib import Path
from policy_harness import ROOT,body,run
path=Path(os.environ.get('ERR_CONVOY_SERVICE',str(ROOT/'addons/ai/Scripts/Game/ERR/ERR_ConvoyService.c')))
s=body(path,'protected bool Admit(').replace('protected ','')
s=s.replace('SCR_AIVehicleUsageComponent.Cast(unit.Entity.FindComponent(SCR_AIVehicleUsageComponent))','&unit->Entity->usage').replace('ERR_HQ.Cast(m_Mode.Command())','m_Mode->Command()')
s=s.replace('array<AIAgent>','Array<AIAgent*>')
for cls in ['ERR_ConvoyTask','ERR_ConvoyVehicle','AIAgent','SCR_AIGroupUtilityComponent','SCR_AIVehicleUsageComponent','ERR_ForceRecord','ERR_HQ']:
s=re.sub(r'\b'+cls+r' ([A-Za-z_]\w*)',cls+r'* \1',s)
for obj in ['task','unit','agent','utility','m_Mode','m_Pool','record','hq','active']:
s=re.sub(r'\b'+obj+r'\.',obj+'->',s)
s=s.replace('unit->Index.ToString()','std::to_string(unit->Index)').replace('Group.','Group->').replace('Entity.','Entity->').replace('Receipt.','Receipt->').replace('m_VehicleMgr.','m_VehicleMgr->').replace('Forces().','Forces()->')
s=s.replace('null','nullptr')
s=s.replace('ERR_Actors.','ERR_Actors::').replace('ERR_ForceKind.','ERR_ForceKind::').replace('foreach (','for (')
run(r'''
#include <cassert>
#include <vector>
#include <string>
#include <algorithm>
using string=std::string;
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Insert(T v){this->push_back(v);}int Count(){return this->size();}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
struct IEntity{bool alive=true;};
struct AIAgent{IEntity* actor;int activations=0;IEntity* GetControlledEntity(){return actor;}void SetPermanentLOD(int){}void ActivateAI(){activations++;}};
struct SCR_AIVehicleUsageComponent{};
struct Manager{int calls=0;bool TryAddVehicle(SCR_AIVehicleUsageComponent*){calls++;return true;}};
struct SCR_AIGroupUtilityComponent{Manager* m_VehicleMgr;};
struct Group{Array<AIAgent*>agents;Manager manager;SCR_AIGroupUtilityComponent utility{&manager};void GetAgents(Array<AIAgent*>&out){out=agents;}SCR_AIGroupUtilityComponent* GetGroupUtilityComponent(){return &utility;}void SetPermanentLOD(int){}void ActivateAI(){}};
struct Vehicle{SCR_AIVehicleUsageComponent usage;int GetOrigin(){return 1;}};
struct ERR_ConvoyVehicle{int Index=0;bool Registered=false,valid=true;struct Group* Group;struct Vehicle* Entity;Array<IEntity*>Members;bool CrewValid(void*){return valid;}};
struct ERR_ConvoyReceipt{string SourceId="allocation";};
struct ERR_ConvoyTask{ERR_ConvoyReceipt* Receipt;Array<ERR_ConvoyVehicle*>Vehicles;bool stopped=false;void Stop(void*,string){stopped=true;}};
enum class ERR_ForceKind{CONVOY};
struct ERR_ForceRecord{bool Pinned=false;};
struct Ledger{int calls=0;bool fail=false;Array<Group*>registered;ERR_ForceRecord record;ERR_ForceRecord* Register(string,ERR_ForceKind,int,int,void*,Group* group,float){calls++;if(fail)return nullptr;registered.Insert(group);return &record;}};
struct ERR_HQ{int reservations=0;void ReserveGroup(Group*){reservations++;}};
struct Mode{Ledger ledger;ERR_HQ hq;int published=0;Ledger* Forces(){return &ledger;}ERR_HQ* Command(){return &hq;}void RegisterGroup(Group*group){assert(ledger.registered.Contains(group));published++;}};
struct Pool{int binds=0;bool fail=false;bool BindRecord(ERR_ConvoyReceipt*,int,ERR_ForceRecord*){binds++;return !fail;}};
namespace ERR_Actors{bool CanFight(IEntity*a){return a&&a->alive;}}
struct Service{Mode* m_Mode;Pool* m_Pool;int inspections=0;void InspectReadiness(ERR_ConvoyVehicle*,float){inspections++;}
''' + s + r'''
};
int main(){
Mode mode;Pool pool;Service service{&mode,&pool};ERR_ConvoyReceipt receipt;ERR_ConvoyTask task;task.Receipt=&receipt;
ERR_ConvoyVehicle units[6];Group groups[6];Vehicle vehicles[6];IEntity bodies[6];AIAgent agents[6];
for(int i=0;i<6;i++){units[i].Index=i;units[i].Group=&groups[i];units[i].Entity=&vehicles[i];units[i].Members.Insert(&bodies[i]);agents[i].actor=&bodies[i];groups[i].agents.Insert(&agents[i]);task.Vehicles.Insert(&units[i]);}
units[2].valid=false;assert(!service.Admit(&task,100));assert(mode.published==2&&pool.binds==2&&mode.ledger.calls==2&&service.inspections==6);
units[2].valid=true;assert(service.Admit(&task,200));assert(mode.published==6&&pool.binds==6&&mode.ledger.calls==6&&mode.hq.reservations==6);
assert(service.Admit(&task,300));assert(mode.published==6&&pool.binds==6&&mode.ledger.calls==6);for(auto&g:groups){assert(g.manager.calls==1);}
Mode duplicate;Pool other;Service check{&duplicate,&other};ERR_ConvoyTask invalid;invalid.Receipt=&receipt;ERR_ConvoyVehicle wrong=units[0];wrong.Registered=false;wrong.Group=&groups[0];IEntity stranger;AIAgent extra{&stranger};groups[0].agents[0]=&extra;invalid.Vehicles.Insert(&wrong);assert(!check.Admit(&invalid,400)&&duplicate.ledger.calls==0);
groups[0].agents[0]=&agents[0];other.fail=true;assert(!check.Admit(&invalid,400)&&invalid.stopped&&!wrong.Registered&&duplicate.published==0);
}
''')
cargo=body(path,'protected void IssueCargo(').replace('protected ','')
cargo=cargo.replace('InventoryStorageManagerComponent.Cast(task.Vehicles[1].Entity.FindComponent(InventoryStorageManagerComponent))','task->Vehicles[1]->Entity->inventory')
for cls in ['ERR_ConvoyTask','InventoryStorageManagerComponent','ERR_ConvoyCargoCallback']:
cargo=re.sub(r'\b'+cls+r' ([A-Za-z_]\w*)',cls+r'* \1',cargo)
for obj in ['task','inventory','operation']:
cargo=re.sub(r'\b'+obj+r'\.',obj+'->',cargo)
for cls in ['ERR_HQ','ERR_ConvoyResources','EStoragePurpose']:cargo=cargo.replace(cls+'.',cls+'::')
cargo=cargo.replace('null','nullptr')
run(r'''
#include <cassert>
#include <vector>
#include <string>
using ResourceName=int;
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Insert(T v){this->push_back(v);}int Count(){return this->size();}};
struct ERR_ConvoyCargoCallback{ResourceName Prefab=0;bool Finished=false;};
struct InventoryStorageManagerComponent{int requests=0,rifles=0,magazines=0;bool accept=true;bool TrySpawnPrefabToStorage(int prefab,void*,int,int,ERR_ConvoyCargoCallback*){requests++;if(prefab==1)rifles++;else magazines++;return accept;}};
struct Entity{InventoryStorageManagerComponent* inventory;};
struct Vehicle{struct Entity* Entity;};
struct ERR_ConvoyTask{bool CargoIssued=false,Stopped=false;Array<Vehicle*>Vehicles;Array<ERR_ConvoyCargoCallback*>Cargo;};
namespace ERR_HQ{void Event(std::string,std::string){}}
namespace ERR_ConvoyResources{int Rifle(){return 1;}int Magazine(){return 2;}}
namespace EStoragePurpose{int PURPOSE_DEPOSIT=1;}
struct Service{
''' + cargo + r'''
};
int main(){
InventoryStorageManagerComponent inventory;Entity entity{&inventory};Vehicle truck{&entity};ERR_ConvoyTask task;for(int i=0;i<6;i++){task.Vehicles.Insert(&truck);}Service service;
service.IssueCargo(&task);assert(task.CargoIssued&&inventory.requests==56&&inventory.rifles==8&&inventory.magazines==48&&task.Cargo.Count()==56);service.IssueCargo(&task);assert(inventory.requests==56);
inventory.accept=false;ERR_ConvoyTask reject;reject.Vehicles=task.Vehicles;service.IssueCargo(&reject);assert(inventory.requests==112);service.IssueCargo(&reject);assert(inventory.requests==112);for(auto*op:reject.Cargo){assert(op->Finished);}
ERR_ConvoyTask absent;absent.Vehicles=task.Vehicles;entity.inventory=nullptr;service.IssueCargo(&absent);assert(absent.CargoIssued&&inventory.requests==112);
ERR_ConvoyTask stopped;stopped.Vehicles=task.Vehicles;stopped.Stopped=true;service.IssueCargo(&stopped);assert(!stopped.CargoIssued&&stopped.Cargo.Count()==0);
}
''')
print('PASS: actual native readiness exact membership, one-time per-vehicle ledger admission/reservation, partial readiness retry without duplicate records and one-pass56-item cargo allocation without replenishment')
# Diagnostics themselves execute against API doubles: health/membership failures,
# native capacity visibility and all-six reporting stay read-only and throttled.
methods='\n'.join(body(path,s) for s in ('protected string ReadinessReason(', 'protected void InspectReadiness('))
methods=methods.replace('protected ','').replace('SCR_AIVehicleUsageComponent.Cast(unit.Entity.FindComponent(SCR_AIVehicleUsageComponent))','unit->Entity->usage')
methods=methods.replace('array<AIAgent>','Array<AIAgent*>').replace('foreach (','for (').replace('string.Format','Format')
for cls in ['ERR_ConvoyVehicle','IEntity','AIAgent','SCR_AIGroupUtilityComponent','SCR_AIVehicleUsageComponent']:
methods=re.sub(r'\b'+cls+r' ([A-Za-z_]\w*)',cls+r'* \1',methods)
for obj in ['unit','agent','utility','m_Mode']:
methods=re.sub(r'\b'+obj+r'\.',obj+'->',methods)
methods=methods.replace('Group.','Group->').replace('Entity.','Entity->').replace('unit->Seats[i].','unit->Seats[i]->')
for cls in ['ERR_Actors','ERR_ConvoyRules','CompartmentAccessComponent','ERR_HQ']:methods=methods.replace(cls+'.',cls+'::')
methods=methods.replace('int agents, activeAgents;', 'int agents=0, activeAgents=0;').replace('bool activeGroup;', 'bool activeGroup=false;')
run(r'''
#include <cassert>
#include <vector>
#include <string>
#include <algorithm>
using string=std::string;
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
struct Group;struct Vehicle;
struct IEntity{bool alive=true,player=false,awake=true;struct Group* group=nullptr;struct Vehicle* vehicle=nullptr;};
struct AIAgent{IEntity* entity=nullptr;bool active=false;IEntity* GetControlledEntity(){return entity;}bool IsAIActivated(){return active;}};
struct SCR_AIVehicleUsageComponent{};struct Manager{};
struct SCR_AIGroupUtilityComponent{Manager* m_VehicleMgr=nullptr;};
struct Group{int faction=1;bool active=false;Array<AIAgent*>agents;SCR_AIGroupUtilityComponent* utility=nullptr;int GetFaction(){return faction;}bool IsAIActivated(){return active;}void GetAgents(Array<AIAgent*>&a){a=agents;}SCR_AIGroupUtilityComponent* GetGroupUtilityComponent(){return utility;}};
struct Vehicle:IEntity{SCR_AIVehicleUsageComponent* usage=nullptr;};
struct Seat{IEntity* occupant=nullptr;IEntity* GetOccupant(){return occupant;}};
struct ERR_ConvoyVehicle{struct Group* Group=nullptr;struct Vehicle* Entity=nullptr;IEntity* Driver=nullptr;Array<IEntity*>Members;Array<Seat*>Seats;bool Registered=false;int Index=0;float NextReadinessNote=0;};
struct Mode{int EnemyFaction(){return 1;}};
namespace ERR_Actors{bool Alive(IEntity*e){return e&&e->alive;}bool CanFight(IEntity*e){return Alive(e)&&e->awake;}bool Player(IEntity*e){return e&&e->player;}struct Group* Group(IEntity*e){return e?e->group:nullptr;}}
namespace CompartmentAccessComponent{IEntity* GetVehicleIn(IEntity*e){return e?e->vehicle:nullptr;}}
namespace ERR_ConvoyRules{bool PartOf(IEntity*e,IEntity*root){return e==root;}}
int loggedMembers=0,loggedAgents=0,loggedActive=0;bool loggedGroup=false;
string Format(string,int,string reason,int members,int agents,int active,bool group,int,bool){loggedMembers=members;loggedAgents=agents;loggedActive=active;loggedGroup=group;return reason;}
namespace ERR_HQ{int calls=0;string last;void Event(string,string reason){calls++;last=reason;}}
struct Service{Mode* m_Mode;'''+methods+r'''};
int main(){
Mode mode;Service service{&mode};ERR_ConvoyVehicle unit;Group group;Vehicle vehicle;IEntity actor;AIAgent agent;Seat seat;Manager manager;SCR_AIGroupUtilityComponent utility{&manager};SCR_AIVehicleUsageComponent usage;
unit.Group=&group;unit.Entity=&vehicle;unit.Driver=&actor;unit.Members={&actor};unit.Seats={&seat};actor.group=&group;actor.vehicle=&vehicle;agent.entity=&actor;group.agents={&agent};group.utility=&utility;vehicle.usage=&usage;seat.occupant=&actor;
assert(service.ReadinessReason(&unit)=="readiness guards satisfied; awaiting admission");
actor.group=nullptr;assert(service.ReadinessReason(&unit)=="member native agent/parent group mismatch");actor.group=&group;
actor.awake=false;assert(service.ReadinessReason(&unit)=="driver absent/dead/unconscious or controller unavailable");actor.awake=true;
actor.vehicle=nullptr;assert(service.ReadinessReason(&unit)=="driver outside intended vehicle");actor.vehicle=&vehicle;
group.agents.clear();assert(service.ReadinessReason(&unit)=="native agent count differs from original crew");group.agents={nullptr};assert(service.ReadinessReason(&unit)=="null native agent");group.agents={&agent};
group.utility=nullptr;assert(service.ReadinessReason(&unit)=="group utility unavailable");group.utility=&utility;
utility.m_VehicleMgr=nullptr;assert(service.ReadinessReason(&unit)=="group vehicle manager unavailable");utility.m_VehicleMgr=&manager;
vehicle.usage=nullptr;assert(service.ReadinessReason(&unit)=="vehicle usage component unavailable");vehicle.usage=&usage;
seat.occupant=nullptr;assert(service.ReadinessReason(&unit)=="intended seat occupant mismatch");seat.occupant=&actor;
service.InspectReadiness(&unit,100);assert(ERR_HQ::calls==1&&unit.NextReadinessNote==5100&&loggedMembers==1&&loggedAgents==1&&loggedActive==0&&!loggedGroup);
service.InspectReadiness(&unit,1000);assert(ERR_HQ::calls==1);
agent.active=true;group.active=true;service.InspectReadiness(&unit,5100);assert(ERR_HQ::calls==2&&loggedActive==1&&loggedGroup);
unit.Registered=true;service.InspectReadiness(&unit,10100);assert(ERR_HQ::calls==3&&ERR_HQ::last=="already admitted");
assert(unit.Members.size()==1&&unit.Group==&group&&unit.Entity==&vehicle&&group.agents.size()==1);
}
''')
print('PASS: actual per-vehicle readiness reasons, native agent/activity counts, intended-seat diagnostics, five-second throttle and read-only observation')