Files
EscapistsReintroduced/tests/test_native_supply_travel.py
T
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

193 lines
15 KiB
Python

"""Execute the complete read-only supply travel probe with native boundary doubles."""
import os,re
from pathlib import Path
from policy_harness import ROOT,run
p=Path(os.environ.get('ERR_NATIVE_SUPPLY_TRAVEL_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSupplyTravelProbe.c'))
s=p.read_text()
assert s.startswith('#ifdef WORKBENCH') and s.rstrip().endswith('#endif')
for forbidden in ('SetOrigin(', 'SetAngles(', 'Spawn(', 'TrySpawn', 'SetHealth(', 'SetAmmo', '.Dispatch(', '.Reserve(', '.Tick(', '.Advance(', '.AddWaypoint(', '.RemoveWaypoint(', '.Ingest('):
assert forbidden not in s,forbidden
code=s.replace('#ifdef WORKBENCH','').replace('#endif','').replace('protected ','').replace('class ERR_Native','struct ERR_Native')
code=code.replace('void ERR_NativeSupplyTravelProbe(', 'ERR_NativeSupplyTravelProbe(')
code=re.sub(r'array<ref ([^>]+)>',r'array<\1*>',code).replace('ref ','')
code=re.sub(r'array<([^>]+)>',r'Arr<\1>',code).replace('vector','Vec')
code=code.replace('ERR_SupplyConvoyService.Cast(hq.Convoys())','dynamic_cast<SupplyService*>(hq->Convoys())')
code=code.replace('ERR_SupplyConvoyTask.Cast(m_Service.At(index))','dynamic_cast<SupplyTask*>(m_Service->At(index))')
code=re.sub(r'\bERR_NativeSupplyTravelFormation (?=\w)', 'ERR_NativeSupplyTravelFormation* ',code)
code=code.replace('foreach (','for (').replace('null','nullptr').replace('slot.ToString()','std::to_string(slot)')
for typ in ('ERR_ForceRules','ERR_Actors','ERR_GameMode','ERR_ConvoyRules','CompartmentAccessComponent','Replication','Vec'):
code=code.replace(typ+'.',typ+'::')
code=code.replace('LogLevel.ERROR','LogLevel::ERROR').replace('ERR_ForceKind.CONVOY','ERR_ForceKind::CONVOY')
for var in ('m_Mode','m_HQ','m_Service','m_Task','m_Receipt','saved','task','service','hq','record','original','agent','escort','team'):
code=re.sub(r'\b'+var+r'\.',var+'->',code)
for field in ('Escort','Team','Root','Group','Record'):
code=code.replace(field+'.',field+'->')
code=code.replace('Tactics.','Tactics->').replace('Forces().','Forces()->').replace('].GetOccupant()',']->GetOccupant()')
code=re.sub(r'^(\s*)(?!return\b)([\w<>*]+) (\w+);$',r'\1\2 \3{};',code,flags=re.M)
code=code.replace('\n}\n','\n};\n')
prelude=r'''
#include <cassert>
#include <cmath>
#include <string>
#include <vector>
#include <algorithm>
using string=std::string;
template<class T>struct Arr:std::vector<T>{using std::vector<T>::vector;int Count()const{return this->size();}bool IsEmpty()const{return this->empty();}void Insert(T v){this->push_back(v);}void Clear(){this->clear();}bool Contains(T v)const{return std::find(this->begin(),this->end(),v)!=this->end();}void Copy(const Arr<T>&a){*this=a;}int Find(T v)const{auto i=std::find(this->begin(),this->end(),v);return i==this->end()?-1:i-this->begin();}};
struct Vec{float v[3]={0,0,0};Vec()=default;Vec(float x,float y,float z){v[0]=x;v[1]=y;v[2]=z;}float&operator[](int i){return v[i];}float operator[](int i)const{return v[i];}static float DistanceXZ(Vec a,Vec b){return std::hypot(a[0]-b[0],a[2]-b[2]);}};
struct Entity;struct SCR_AIGroup;
using IEntity=Entity*;using Vehicle=Entity*;
struct Entity{Vec position;bool alive=true,fight=true,player=false;SCR_AIGroup*group=nullptr;IEntity vehicle=nullptr;Vec GetOrigin(){return position;}};
struct AIAgent{IEntity actor=nullptr;IEntity GetControlledEntity(){return actor;}};
struct SCR_AIGroup{int faction=2,players=0;Arr<AIAgent*> agents;int GetFaction(){return faction;}int GetPlayerCount(bool){return players;}void GetAgents(Arr<AIAgent*>&out){out=agents;}};
struct BaseCompartmentSlot{IEntity occupant=nullptr;int type=0;IEntity GetOccupant(){return occupant;}int GetType(){return type;}};
struct ECompartmentType{static const int PILOT=0,CARGO=1;};
struct ERR_Actors{static bool Alive(IEntity a){return a&&a->alive;}static bool CanFight(IEntity a){return Alive(a)&&a->fight;}static bool Player(IEntity a){return a&&a->player;}static SCR_AIGroup*Group(IEntity a){return a?a->group:nullptr;}};
struct ERR_ForceRules{static bool Finite(float v){return std::isfinite(v);}static bool FiniteVector(Vec v){return Finite(v[0])&&Finite(v[1])&&Finite(v[2]);}};
struct CompartmentAccessComponent{static IEntity GetVehicleIn(IEntity a){return a?a->vehicle:nullptr;}};
struct LogLevel{static const int ERROR=1;};
int printed=0;void Print(const string&,int=0){printed++;}template<class...T>void PrintFormat(T...){printed++;}
'''
prelude+=r'''
struct ERR_ForceKind{static const int CONVOY=4;};
struct ForceRecord{int Id=0,Kind=ERR_ForceKind::CONVOY;bool Physical=true,Eliminated=false;SCR_AIGroup*Group=nullptr;string SourceId;};using ERR_ForceRecord=ForceRecord*;
struct Ledger{Arr<ForceRecord*> records;ForceRecord*FindGroup(SCR_AIGroup*g){for(auto*r:records)if(r&&r->Group==g)return r;return nullptr;}};
struct ConvoyVehicle{int Index=0;IEntity Entity=nullptr,Driver=nullptr;SCR_AIGroup*Group=nullptr;Arr<IEntity>Members;Arr<BaseCompartmentSlot*>Seats;bool Released=false,Registered=true;};using ERR_ConvoyVehicle=ConvoyVehicle*;
struct SupplyTeam{SCR_AIGroup*Group=nullptr;Arr<IEntity>Members;Arr<BaseCompartmentSlot*>Seats;bool Released=false,Registered=true;};using ERR_SupplyTeam=SupplyTeam*;
struct ConvoyReceipt{int Id=1,OutstandingBodies=0,OutstandingGroups=0;bool Consumed=true;string SourceId="supply-1";Arr<int>Records;};using ERR_ConvoyReceipt=ConvoyReceipt*;
struct ConvoyTask{virtual ~ConvoyTask()=default;};
struct Tactics{int State(){return 0;}};
struct SupplyTask:ConvoyTask{struct Tactics*Tactics=nullptr;bool Ready=true,Stopped=false;string Reason;ERR_ConvoyReceipt Receipt=nullptr;Arr<ERR_ConvoyVehicle>Vehicles;Arr<ERR_SupplyTeam>Teams;Arr<Vec>Starts;};using ERR_SupplyConvoyTask=SupplyTask*;
struct ConvoyService{virtual ~ConvoyService()=default;};
struct SupplyService:ConvoyService{Arr<ConvoyTask*>tasks;int overrideCount=-1,reads=0;int Count(){return overrideCount==-1?tasks.Count():overrideCount;}ConvoyTask*At(int i){reads++;return tasks[i];}};using ERR_SupplyConvoyService=SupplyService*;
struct ERR_HQ{ConvoyService*service=nullptr;bool supply=true;ConvoyService*Convoys(){return service;}bool SupplyConvoys(){return supply;}string ConvoyStatus(){return "status";}};
struct ERR_GameMode{static inline ERR_GameMode*current=nullptr;bool active=true;ERR_HQ*hq=nullptr;Ledger ledger;bool ledgerPresent=true;static ERR_GameMode*Get(){return current;}bool Active(){return active;}Ledger*Forces(){return ledgerPresent?&ledger:nullptr;}ERR_HQ*Command(){return hq;}int EnemyFaction(){return 2;}};
struct Replication{static inline bool server=true;static bool IsServer(){return server;}};
struct ERR_ConvoyRules{static bool PartOf(IEntity a,IEntity b){return a&&a==b;}static bool Captured(IEntity r,SCR_AIGroup*g){return (r&&r->player)||(g&&g->players>0);}};
'''
# These native managed types are pointer references in Enforce. Arrays and vectors
# remain values here; production Copy/identity checks are executed unchanged.
code=re.sub(r'\b(ERR_GameMode|ERR_HQ|SCR_AIGroup|BaseCompartmentSlot|AIAgent)(?=[ >]|\*)',lambda m:m.group(1)+'*',code)
run(prelude+code+r'''
struct Setup{
ERR_GameMode mode;ERR_HQ hq;SupplyService service;SupplyTask task;ConvoyReceipt receipt;Entity roots[6],actors[25];SCR_AIGroup groups[8];ForceRecord records[8];AIAgent agents[25];BaseCompartmentSlot seats[25];ConvoyVehicle vehicles[6];SupplyTeam teams[2];
Setup(){ERR_GameMode::current=&mode;Replication::server=true;mode.hq=&hq;hq.service=&service;service.tasks.Insert(&task);task.Receipt=&receipt;int actor=0;
for(int slot=0;slot<8;slot++){
auto&g=groups[slot];auto&r=records[slot];r.Id=100+slot;r.Group=&g;r.SourceId=receipt.SourceId+"-vehicle-"+std::to_string(slot);receipt.Records.Insert(r.Id);mode.ledger.records.Insert(&r);
int count=slot>=6?7:slot==1||slot==2?1:slot==4?3:2;
if(slot<6){auto&v=vehicles[slot];v.Index=slot;v.Entity=&roots[slot];v.Group=&g;v.Driver=&actors[actor];task.Vehicles.Insert(&v);roots[slot].position=Vec(100+slot*40,10,200);task.Starts.Insert(Vec(-1000,0,-1000));}
else{teams[slot-6].Group=&g;task.Teams.Insert(&teams[slot-6]);}
for(int j=0;j<count;j++,actor++){
actors[actor].group=&g;actors[actor].vehicle=&roots[slot<6?slot:2];agents[actor].actor=&actors[actor];g.agents.Insert(&agents[actor]);seats[actor].occupant=&actors[actor];seats[actor].type=j==0?0:1;
if(slot<6){vehicles[slot].Members.Insert(&actors[actor]);vehicles[slot].Seats.Insert(&seats[actor]);}else{teams[slot-6].Members.Insert(&actors[actor]);teams[slot-6].Seats.Insert(&seats[actor]);}
}
}assert(actor==25);
}
void move(float amount=20){for(auto&r:roots)r.position[0]+=amount;}
};
int main(){
// Admission uses observation-time origins, not pre-admission/spawn travel.
{Setup s;ERR_NativeSupplyTravelProbe p(&s.mode,1000);s.task.Ready=false;s.move(100);assert(p.Tick(&s.hq,1001)==0);s.task.Ready=true;
assert(p.Tick(&s.hq,1100)==0&&p.m_Formations.Count()==8&&p.m_TravelDeadline==181100);
assert(p.Tick(&s.hq,1101)==0);s.move(19.99f);assert(p.Tick(&s.hq,1102)==0);s.move(.02f);assert(p.Tick(&s.hq,1103)==1);assert(p.Tick(&s.hq,1104)==-1);
}
// Movement is independently observed for every original vehicle; a returning
// original may retain its witnessed movement, but identity must still validate.
{Setup s;ERR_NativeSupplyTravelProbe p(&s.mode,0);assert(p.Tick(&s.hq,1)==0);
for(int i=0;i<6;i++){s.roots[i].position[0]+=20;assert(p.Tick(&s.hq,2+i)==(i==5?1:0));s.roots[i].position[0]-=20;}
}
{Setup s;ERR_NativeSupplyTravelProbe p(&s.mode,0);assert(p.Tick(&s.hq,1)==0);for(auto&r:s.roots)r.position[1]+=100;assert(p.Tick(&s.hq,2)==0);}
// Admission and travel deadlines never slide or accept boundary-time motion.
{Setup s;s.service.tasks.Clear();ERR_NativeSupplyTravelProbe p(&s.mode,1000);assert(p.Tick(&s.hq,120999)==0);assert(p.m_AdmissionDeadline==121000);assert(p.Tick(&s.hq,121000)==-1);s.service.tasks.Insert(&s.task);assert(p.Tick(&s.hq,121001)==-1);}
{Setup s;ERR_NativeSupplyTravelProbe p(&s.mode,0);assert(p.Tick(&s.hq,119999)==0);assert(p.m_TravelDeadline==299999);s.move();assert(p.Tick(&s.hq,299999)==-1);}
{Setup s;ERR_NativeSupplyTravelProbe p(&s.mode,0);assert(p.Tick(&s.hq,1)==0);assert(p.Tick(&s.hq,180000)==0);assert(p.m_TravelDeadline==180001);s.move();assert(p.Tick(&s.hq,180001)==-1);}
// Observing a ready partial/broken task cannot silently select a later task.
{Setup s;SupplyTask later=s.task;s.task.Teams.pop_back();s.service.tasks.Insert(&later);ERR_NativeSupplyTravelProbe p(&s.mode,0);assert(p.Tick(&s.hq,1)==-1&&p.m_Task==&s.task);}
{Setup s;SupplyTask pending;pending.Ready=false;s.service.tasks.insert(s.service.tasks.begin(),&pending);ERR_NativeSupplyTravelProbe p(&s.mode,0);assert(p.Tick(&s.hq,1)==0&&p.m_Task==&s.task&&p.m_TaskIndex==1);}
{Setup s;ConvoyTask legacy;s.service.tasks[0]=&legacy;ERR_NativeSupplyTravelProbe p(&s.mode,0);assert(p.Tick(&s.hq,1)==-1);}
// Infantry may physically leave the truck without being replaced. This proves
// travel observation only, not an enforced perpetual seating/tactical policy.
{Setup s;ERR_NativeSupplyTravelProbe p(&s.mode,0);assert(p.Tick(&s.hq,1)==0);for(auto&t:s.teams)for(auto*a:t.Members)a->vehicle=nullptr;s.move();assert(p.Tick(&s.hq,2)==1);}
// Permanent failure/Stop cannot re-adopt a now-valid task or pass late motion.
{Setup s;ERR_NativeSupplyTravelProbe p(&s.mode,0);assert(p.Tick(&s.hq,1)==0);p.Stop();p.Stop();s.move();assert(p.Tick(&s.hq,2)==-1&&p.m_Formations.IsEmpty()&&!p.m_Task&&!p.m_Mode);}
// Original task, receipt, exact record, body, group, seat and service identities
// remain necessary even after every vehicle has physically moved.
for(int fault=0;fault<36;fault++){
Setup s;ERR_NativeSupplyTravelProbe p(&s.mode,0);assert(p.Tick(&s.hq,1)==0);s.move();
Entity replacement;ConvoyReceipt receipt=s.receipt;ForceRecord record=s.records[0];ConvoyVehicle vehicle=s.vehicles[0];SupplyTeam team=s.teams[0];BaseCompartmentSlot seat;SCR_AIGroup group;SupplyService service=s.service;ERR_HQ hq=s.hq;
if(fault==0)s.task.Ready=false;
if(fault==1)s.task.Stopped=true;
if(fault==2)s.task.Receipt=&receipt;
if(fault==3)s.receipt.Consumed=false;
if(fault==4)s.receipt.Id++;
if(fault==5)s.receipt.SourceId="replacement";
if(fault==6)s.receipt.Records[0]++;
if(fault==7)s.receipt.OutstandingBodies=1;
if(fault==8)s.receipt.OutstandingGroups=1;
if(fault==9)s.task.Vehicles[0]=&vehicle;
if(fault==10)s.vehicles[0].Entity=&replacement;
if(fault==11)s.vehicles[0].Group=&group;
if(fault==12)s.vehicles[0].Driver=&replacement;
if(fault==13)s.vehicles[0].Released=true;
if(fault==14)s.vehicles[0].Registered=false;
if(fault==15)s.roots[0].alive=false;
if(fault==16)s.roots[0].player=true;
if(fault==17)s.roots[0].position[0]=NAN;
if(fault==18)s.vehicles[0].Members[1]=&replacement;
if(fault==19)s.vehicles[0].Seats[0]=&seat;
if(fault==20)s.vehicles[0].Driver->alive=false;
if(fault==21)s.vehicles[0].Driver->fight=false;
if(fault==22)s.vehicles[0].Driver->vehicle=nullptr;
if(fault==23)s.vehicles[0].Seats[0]->occupant=nullptr;
if(fault==24)s.task.Teams[0]=&team;
if(fault==25)s.teams[0].Released=true;
if(fault==26)s.teams[0].Members[0]->alive=false;
if(fault==27)s.mode.ledger.records[0]=&record;
if(fault==28)s.records[0].Physical=false;
if(fault==29)s.records[0].Eliminated=true;
if(fault==30)s.records[0].SourceId="alien";
if(fault==31)s.groups[0].agents[1]=s.groups[0].agents[0];
if(fault==32)s.hq.service=&service;
if(fault==33)s.mode.hq=&hq;
if(fault==34)s.service.tasks.Clear();
if(fault==35)s.groups[0].players=1;
assert(p.Tick(&s.hq,2)==-1);
}
// Malformed admission cannot turn a smaller or duplicate formation into proof.
for(int fault=0;fault<12;fault++){
Setup s;ERR_NativeSupplyTravelProbe p(&s.mode,0);
if(fault==0)s.task.Vehicles.pop_back();
if(fault==1)s.task.Teams.pop_back();
if(fault==2)s.receipt.Records.pop_back();
if(fault==3)s.vehicles[1].Entity=s.vehicles[0].Entity;
if(fault==4)s.teams[1].Group=s.teams[0].Group;
if(fault==5)s.teams[0].Members[1]=s.teams[0].Members[0];
if(fault==6)s.teams[0].Seats[1]=s.teams[0].Seats[0];
if(fault==7)s.vehicles[0].Members.pop_back();
if(fault==8)s.teams[0].Members.pop_back();
if(fault==9)s.vehicles[0].Seats.pop_back();
if(fault==10)s.groups[0].agents.pop_back();
if(fault==11)s.mode.ledger.records[0]=nullptr;
assert(p.Tick(&s.hq,1)==-1);
}
for(int fault=0;fault<10;fault++){
Setup s;ERR_NativeSupplyTravelProbe p(&s.mode,0);ERR_GameMode other;ConvoyService legacy;
if(fault==0)Replication::server=false;
if(fault==1)ERR_GameMode::current=&other;
if(fault==2)s.mode.active=false;
if(fault==3)s.mode.ledgerPresent=false;
if(fault==4)s.hq.supply=false;
if(fault==5)s.hq.service=&legacy;
if(fault==6)s.service.overrideCount=21;
if(fault==7){assert(p.Tick(&s.hq,10)==0);assert(p.Tick(&s.hq,9)==-1);continue;}
if(fault==8){assert(p.Tick(&s.hq,NAN)==-1);continue;}
if(fault==9){assert(p.Tick(&s.hq,INFINITY)==-1);continue;}
assert(p.Tick(&s.hq,1)==-1);
}
{Setup s;ERR_NativeSupplyTravelProbe p(&s.mode,-1);assert(p.Tick(&s.hq,1)==-1);}
{Setup s;ERR_NativeSupplyTravelProbe p(&s.mode,NAN);assert(p.Tick(&s.hq,1)==-1);}
}
''')
print('PASS: complete actual supply-travel probe, immutable 6+2 formations and 25 actors, admission/travel deadlines, six observed native-boundary moves, stop/ownership/partial-substitution refusals')