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

102 lines
8.9 KiB
Python

"""Execute delivered convoy cover rules and the canonical physical executor."""
import os,re
from pathlib import Path
from policy_harness import ROOT,body,run
rules=Path(os.environ.get('ERR_CONVOY_COMBAT',str(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyCombat.c')))
service=Path(os.environ.get('ERR_CONVOY_SERVICE',str(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c')))
accept=body(rules,'static bool Accept(')
for cls in ['ERR_ConvoyCombatState','ERR_ConvoyCombatOrder']:accept=accept.replace(cls+' ',cls+'* ')
accept=accept.replace('state.','state->').replace('order.','order->').replace('ERR_ForceRules.','ERR_ForceRules::')
preamble=r'''
#include <cassert>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
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 ERR_ConvoyCombatState{bool Held=false;int LastCommandId=0,ReportId=0;float ObservedAt=-1;};
struct ERR_ConvoyCombatOrder{int CommandId=0,ReportId=0,VehicleIndex=0;string SourceId;Group* Group=nullptr;bool Hold=false;float ObservedAt=-1,IssuedAt=-1,ExpiresAt=-1;};
namespace ERR_ForceRules{bool Finite(float n){return std::isfinite(n);}}
'''.replace('Group* Group','struct Group* Group')
run(preamble+'struct Rules{'+accept+r'''};
int main(){
ERR_ConvoyCombatState state;ERR_ConvoyCombatOrder hold;hold.CommandId=1;hold.ReportId=10;hold.Hold=true;hold.ObservedAt=100;hold.IssuedAt=1000;hold.ExpiresAt=31000;
assert(Rules::Accept(&state,&hold,1000));assert(!Rules::Accept(&state,&hold,31000));assert(!Rules::Accept(&state,&hold,999));
auto invalid=hold;invalid.ExpiresAt=31001;assert(!Rules::Accept(&state,&invalid,1000));invalid=hold;invalid.ObservedAt=NAN;assert(!Rules::Accept(&state,&invalid,1000));assert(!Rules::Accept(&state,&hold,NAN));
invalid=hold;invalid.IssuedAt=60101;invalid.ExpiresAt=70101;assert(!Rules::Accept(&state,&invalid,60101));
state.Held=true;state.LastCommandId=1;state.ReportId=10;state.ObservedAt=100;assert(!Rules::Accept(&state,&hold,1000));
auto refresh=hold;refresh.CommandId=2;refresh.ReportId=11;refresh.ObservedAt=200;assert(Rules::Accept(&state,&refresh,1000));refresh.ObservedAt=99;assert(!Rules::Accept(&state,&refresh,1000));
refresh=hold;refresh.CommandId=2;refresh.ObservedAt=101;assert(!Rules::Accept(&state,&refresh,1000));refresh=hold;refresh.CommandId=2;refresh.ReportId=9;assert(!Rules::Accept(&state,&refresh,1000));
auto resume=hold;resume.CommandId=3;resume.Hold=false;resume.IssuedAt=60100;resume.ExpiresAt=90100;assert(Rules::Accept(&state,&resume,60100));
resume.IssuedAt=60099;resume.ExpiresAt=90099;assert(!Rules::Accept(&state,&resume,60100));resume.IssuedAt=60100;resume.ExpiresAt=90100;
resume.ReportId=11;assert(!Rules::Accept(&state,&resume,60100));resume.ReportId=10;resume.ObservedAt=101;assert(!Rules::Accept(&state,&resume,60100));resume.ObservedAt=100;state.Held=false;assert(!Rules::Accept(&state,&resume,60100));
assert(!Rules::Accept(nullptr,&hold,1000)&&!Rules::Accept(&state,nullptr,1000));
}
''')
code=body(service,'bool ApplyCombatOrder(')
for cls in ['ERR_ConvoyTask','ERR_ConvoyCombatOrder','ERR_ConvoyVehicle','ERR_ForceRecord','AIAgent']:
code=re.sub(r'\b'+cls+r' ([A-Za-z_]\w*)',cls+r'* \1',code)
code=code.replace('array<AIAgent>','Array<AIAgent*>').replace('foreach (','for (')
for name in ['task','order','unit','record','agent','m_Mode']:code=code.replace(name+'.',name+'->')
code=code.replace('Receipt.','Receipt->').replace('Combat.','Combat->').replace('Route.','Route->').replace('Group.','Group->').replace('Forces().','Forces()->')
for cls in ['ERR_ConvoyCombatRules','ERR_ConvoyRules','ERR_ForceKind','ERR_HQ']:code=code.replace(cls+'.',cls+'::')
code=code.replace('string.Format','Format')
run(preamble+r'''
struct IEntity{};struct AIAgent{IEntity* entity;IEntity* GetControlledEntity(){return entity;}};
struct Group{Array<AIAgent*>agents;void GetAgents(Array<AIAgent*>&out){out=agents;}};
struct ERR_ForceRoute{int Count(){return 10;}};
enum class ERR_ForceKind{CONVOY,GARRISON};
struct ERR_ForceRecord{bool Physical=true,Eliminated=false;ERR_ForceKind Kind=ERR_ForceKind::CONVOY;int Id=3;};
struct Ledger{ERR_ForceRecord record;ERR_ForceRecord* FindGroup(Group*){return &record;}};
struct Mode{Ledger ledger;Ledger* Forces(){return &ledger;}};
struct ERR_ConvoyReceipt{bool Consumed=true;string SourceId="convoy-1";Array<int>Records{3};};
struct ERR_ConvoyVehicle{IEntity vehicle;IEntity* Entity=&vehicle;bool SupplyHeld=false,Registered=true,Released=false,Arrived=false,valid=true,waypoint=true,failOrder=false;int Index=0,RouteIndex=2,clears=0,orders=0,releases=0;struct Group* Group;Array<IEntity*>Members;ERR_ConvoyCombatState state;ERR_ConvoyCombatState* Combat=&state;bool CrewValid(Mode*){return valid;}void ClearOrder(){clears++;waypoint=false;}bool Order(ERR_ForceRoute*,float){if(waypoint)return true;orders++;if(failOrder)return false;waypoint=true;return true;}void Release(Mode*){releases++;Released=true;Combat->Held=false;}};
struct ERR_ConvoyTask{bool Stopped=false,Ready=true;ERR_ConvoyReceipt* Receipt;ERR_ForceRoute* Route;Array<ERR_ConvoyVehicle*>Vehicles;};
namespace ERR_ConvoyRules{bool captured=false;bool Captured(IEntity*,Group*){return captured;}};
struct ERR_ConvoyCombatRules{'''+accept+r'''};
string Format(string,int,int,int,bool,int){return "event";}
namespace ERR_HQ{int events=0;void Event(string,string){events++;}}
struct Service{Mode* m_Mode;bool current=true;Array<ERR_ConvoyTask*>m_Tasks;bool Current(){return current;}
'''+code+r'''};
struct Case{Mode mode;Service service{&mode};ERR_ConvoyReceipt receipt;ERR_ForceRoute route;ERR_ConvoyTask task;ERR_ConvoyVehicle unit;Group group;IEntity member;AIAgent agent{&member};ERR_ConvoyCombatOrder command;
Case(){unit.Group=&group;unit.Members={&member};group.agents={&agent};task.Receipt=&receipt;task.Route=&route;task.Vehicles={&unit};service.m_Tasks={&task};command.CommandId=1;command.ReportId=10;command.SourceId=receipt.SourceId;command.Group=&group;command.Hold=true;command.ObservedAt=100;command.IssuedAt=1000;command.ExpiresAt=31000;}
};
int main(){
Case supply;assert(supply.service.ApplyCombatOrder(&supply.task,&supply.command,1000));
supply.unit.SupplyHeld=true;supply.command.CommandId=2;supply.command.Hold=false;supply.command.IssuedAt=60100;supply.command.ExpiresAt=90100;
assert(supply.service.ApplyCombatOrder(&supply.task,&supply.command,60100));assert(!supply.unit.Combat->Held&&!supply.unit.waypoint&&supply.unit.orders==0&&supply.unit.releases==0);
Case ok;assert(ok.service.ApplyCombatOrder(&ok.task,&ok.command,1000));assert(ok.unit.Combat->Held&&ok.unit.clears==1&&!ok.unit.waypoint&&ok.unit.RouteIndex==2);
assert(!ok.service.ApplyCombatOrder(&ok.task,&ok.command,1001)&&ok.unit.clears==1);ok.command.CommandId=2;ok.command.ReportId=11;ok.command.ObservedAt=200;
assert(ok.service.ApplyCombatOrder(&ok.task,&ok.command,1002)&&ok.unit.clears==1&&ok.unit.Combat->ReportId==11);
ok.command.Hold=false;ok.command.CommandId=3;ok.command.IssuedAt=60200;ok.command.ExpiresAt=90200;
assert(ok.service.ApplyCombatOrder(&ok.task,&ok.command,60200)&&!ok.unit.Combat->Held&&ok.unit.waypoint&&ok.unit.orders==1&&ok.unit.RouteIndex==2);
assert(!ok.service.ApplyCombatOrder(&ok.task,&ok.command,60201)&&ok.unit.orders==1);
for(int failure=0;failure<15;failure++){
Case c;Group stranger;IEntity foreign;AIAgent foreignAgent{&foreign};
if(failure==0)c.service.current=false;
if(failure==1)c.service.m_Tasks.clear();
if(failure==2)c.task.Stopped=true;
if(failure==3)c.receipt.Consumed=false;
if(failure==4)c.command.SourceId="other";
if(failure==5)c.command.Group=&stranger;
if(failure==6)c.unit.valid=false;
if(failure==7)c.unit.Registered=false;
if(failure==8)c.unit.Released=true;
if(failure==9)c.mode.ledger.record.Physical=false;
if(failure==10)c.mode.ledger.record.Id=4;
if(failure==11)c.group.agents={&foreignAgent};
if(failure==12)c.command.VehicleIndex=1;
if(failure==13)ERR_ConvoyRules::captured=true;
if(failure==14)c.receipt.Records.clear();
assert(!c.service.ApplyCombatOrder(&c.task,&c.command,1000)&&c.unit.clears==0&&c.unit.orders==0&&!c.unit.Combat->Held&&c.unit.Combat->LastCommandId==0);
ERR_ConvoyRules::captured=false;
}
Case failed;assert(failed.service.ApplyCombatOrder(&failed.task,&failed.command,1000));failed.unit.failOrder=true;failed.command.Hold=false;failed.command.CommandId=2;failed.command.IssuedAt=60100;failed.command.ExpiresAt=90100;
assert(!failed.service.ApplyCombatOrder(&failed.task,&failed.command,60100)&&failed.unit.Released&&failed.unit.releases==1&&failed.unit.Combat->LastCommandId==2);
assert(!failed.service.ApplyCombatOrder(&failed.task,&failed.command,60101)&&failed.unit.orders==1&&failed.unit.releases==1);
}
''')
print('PASS: actual delivered cover freshness/lineage/replay and canonical executor; owned waypoint-only hold, one resume at preserved route index, invalid ownership/crew/ledger refusal and fail-closed native recreation')