Files
EscapistsReintroduced/tests/test_convoy_admission.py
T
jakub cab998fb64 Combine Workshop addon and fix prison startup
Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
2026-09-20 16:42:52 +02:00

144 lines
14 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/core/Scripts/Game/ERR/10_AI/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;int additions=0;void AddUsableVehicle(SCR_AIVehicleUsageComponent* usage){additions++;m_VehicleMgr->TryAddVehicle(usage);}};
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==2&&g.utility.additions==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')