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

160 lines
16 KiB
Python

"""Execute actual optional transport admission/cleanup and Prepare ordering.
Engine clearance, spawning, inventory completion and seats are explicit doubles.
"""
import re
from policy_harness import ROOT, body, run
source = ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c'
def cpp(s):
s=s.replace('protected ', '').replace('null','nullptr').replace('foreach (','for (').replace('array<','Array<')
casts={
'Vehicle.Cast(entity)':'CastVehicle(entity)',
'ChimeraCharacter.Cast(entity)':'entity->character',
'SCR_AIGroup.Cast(entity)':'entity->group',
'CarControllerComponent.Cast(site.Transport.FindComponent(CarControllerComponent))':'site->Transport->controller',
'BaseCompartmentManagerComponent.Cast(entity.FindComponent(BaseCompartmentManagerComponent))':'entity->manager',
'BaseCompartmentManagerComponent.Cast(vehicle.FindComponent(BaseCompartmentManagerComponent))':'vehicle->manager',
'FuelManagerComponent.Cast(vehicle.FindComponent(FuelManagerComponent))':'vehicle->fuel',
}
for old,new in casts.items():s=s.replace(old,new)
for cls in ('ERR_Actors','ERR_PrepareResult','ERR_VehicleSecurityComponent','Replication','Math','SCR_CompartmentAccessComponent','ECompartmentType','LogLevel'):
s=s.replace(cls+'.',cls+'::')
for name in ('site','optionalSite','readySite','spec','entity','exact','controller','security','vehicle','manager','slot','seat','child','placement','m_ExtractionPlacement','m_Mode'):
s=s.replace(name+'.',name+'->')
s=s.replace('->Definition.','->Definition->').replace('->Transport.Optional','->Transport->Optional')
s=s.replace('->Transport.Offset','->Transport->Offset').replace('->Transport.ClearanceRadius','->Transport->ClearanceRadius')
s=s.replace('->Inventory.', '->Inventory->').replace('->Item.GetPrefabData().','->Item->GetPrefabData()->')
s=s.replace('->Location.', '->Location->').replace('fuel.','fuel->').replace('placement->Item.', 'placement->Item->')
s=s.replace('GetGame().GetWorld().GetWorldTime()', 'clockNow')
s=s.replace('string.Format(', 'Format(')
s=s.replace('int visited;', 'int visited=0;').replace('int pending, accepted, pendingGuards;', 'int pending=0, accepted=0, pendingGuards=0;')
s=s.replace('int pendingTransports;', 'int pendingTransports=0;').replace('bool driver, passenger;', 'bool driver=false, passenger=false;')
s=s.replace('if (!GetGame()) return;', '')
return s
methods='\n'.join(cpp(body(source,sig)) for sig in (
'protected bool TransportEmpty(', 'protected bool TransportFailure(',
'protected bool CreateTransport(', 'protected bool TransportReady(',
'protected bool Fail(', 'ERR_PrepareResult Prepare(', 'void Rollback(', 'void Stop('))
run(r'''
#include <cassert>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
struct string:std::string {using std::string::string;string(std::string s):std::string(s){}bool IsEmpty(){return empty();}};
template<class... A>string Format(string s,A...){return s;}
template<class... A>void PrintFormat(string,A...){}
namespace LogLevel{constexpr int ERROR=1;}
void Print(string,int){}
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;bool IsEmpty(){return this->empty();}int Count(){return this->size();}void Insert(T t){this->push_back(t);}void Clear(){this->clear();}bool Contains(T t){return std::find(this->begin(),this->end(),t)!=this->end();}void Remove(int n){this->erase(this->begin()+n);}void RemoveItem(T t){this->erase(std::remove(this->begin(),this->end(),t),this->end());}};
struct vector {float a[3]{};float&operator[](int n){return a[n];}vector operator+(vector v){return {{a[0]+v.a[0],a[1]+v.a[1],a[2]+v.a[2]}};}};
vector Vector(float x,float y,float z){return {{x,y,z}};}
namespace Math{float AbsFloat(float x){return std::abs(x);}}
float clockNow=0;
namespace Replication{bool server=true;bool IsServer(){return server;}}
enum ERR_PrepareResult {FAILED=-1,PENDING,READY};
struct Entity;using IEntity=Entity*;using Vehicle=Entity*;
struct Controller{bool parked=false;void SetPersistentHandBrake(bool b){parked=b;}};using CarControllerComponent=Controller*;
struct Security{bool good=true;int calls=0;bool Configure(){calls++;return good;}static Security* Find(IEntity e);};using ERR_VehicleSecurityComponent=Security;
struct Seat{IEntity occupant=nullptr;int type=0;IEntity GetOccupant(){return occupant;}};using BaseCompartmentSlot=Seat*;
struct Compartments{Array<BaseCompartmentSlot> seats;void GetCompartments(Array<BaseCompartmentSlot>&out){out=seats;}};using BaseCompartmentManagerComponent=Compartments*;
struct Fuel{bool fueled=true;bool HasFuel(){return fueled;}};using FuelManagerComponent=Fuel*;
struct Prefab{string GetPrefabName(){return "test-stock";}};
struct Entity{bool vehicle=true,alive=true,player=false,character=false,group=false;Controller* controller=nullptr;Security* security=nullptr;Compartments* manager=nullptr;Fuel* fuel=nullptr;IEntity child=nullptr,sibling=nullptr;int deletes=0;vector origin{},low{},high{};Prefab prefab;
IEntity GetChildren(){return child;}IEntity GetSibling(){return sibling;}vector GetOrigin(){return origin;}
void GetWorldBounds(vector& l,vector& h){l=low;h=high;}Prefab* GetPrefabData(){return &prefab;}};
Security* Security::Find(IEntity e){return e->security;}
IEntity CastVehicle(IEntity e){return e&&e->vehicle?e:nullptr;}
namespace ECompartmentType{constexpr int PILOT=0,CARGO=1;}
using ECompartmentType_=int;
struct SCR_CompartmentAccessComponent{static int GetCompartmentType(BaseCompartmentSlot s){return s->type;}};
namespace ERR_Actors{IEntity spawned=nullptr;int spawnCalls=0;bool Player(IEntity e){return e&&e->player;}bool Alive(IEntity e){return e&&e->alive;}vector Ground(vector p){return p;}IEntity Spawn(int,vector,vector){spawnCalls++;return spawned;}void DeleteSpawned(IEntity e){if(e)e->deletes++;}}
struct ERR_SiteTransport_ {bool Optional=false;int Prefab=123;vector Offset{};float Heading=0,ClearanceRadius=4,MaximumTerrainRange=1;};using ERR_SiteTransport=ERR_SiteTransport_*;
struct Definition {ERR_SiteTransport Transport=nullptr;string Id="clue-site";vector Position{};};
struct LocationType{bool prepared=false;void SetPrepared(bool value){prepared=value;}};
struct Site {LocationType*Location=nullptr;struct Definition* Definition=nullptr;IEntity Transport=nullptr,TransportEntity=nullptr;bool TransportAttempted=false,TransportOmitted=false;float TransportDeadline=-1;Site* Responders=nullptr;int guard=PENDING;};using ERR_SiteRuntime=Site*;
struct Inventory{Array<IEntity>items;bool accept=true;int calls=0;void GetItems(Array<IEntity>&out){out=items;}bool TryInsertItem(IEntity){calls++;return accept;}};
struct Placement{IEntity Item=nullptr;Inventory* Inventory_=nullptr;bool Accepted=false;};
struct Extraction{ERR_PrepareResult result=READY;int stops=0;ERR_PrepareResult Poll(float){return result;}string FailureReason(){return "aircraft failed";}void Stop(){stops++;}};
struct ExtractionStatus{bool pickup=true;bool PickupConfigured(){return pickup;}};
struct Mode{ExtractionStatus status;ExtractionStatus&Extraction(){return status;}Array<IEntity>owned;void Own(IEntity e){owned.Insert(e);}};
using ERR_StockPlacement=Placement*;
struct Service{Mode mode;Mode* m_Mode=&mode;Extraction extraction;Extraction* m_ExtractionPlacement=&extraction;Array<ERR_SiteRuntime>m_Sites;Array<IEntity>m_Entities;Array<ERR_StockPlacement>m_Placements;
bool m_Begun=true,m_Stopped=false,m_SingleSite=false,clear=true;int guardChecks=0,cancels=0,patrolStops=0;ERR_PrepareResult m_State=PENDING;float m_Deadline=15000;string m_FailureReason;
bool TransportClearance(vector,float,float){return clear;}
ERR_PrepareResult InitialGuards(ERR_SiteRuntime site){guardChecks++;if(site->guard==FAILED){Fail("guards failed");return FAILED;}return ERR_PrepareResult(site->guard);}
void CancelInitialGuardRequests(){cancels++;}void StopPatrols(){patrolStops++;}
''' + methods.replace('ERR_VehicleSecurityComponent security', 'Security* security').replace('ECompartmentType type','int type').replace('placement->Inventory','placement->Inventory_') + r'''
};
struct Fixture{Service svc;ERR_SiteTransport_ spec;Definition definition;Site site,responders;Entity car,stock,unrelated;Inventory inv;Placement placement;Controller controller;Security security;Compartments compartments;Seat driver,passenger;Fuel fuel;
Fixture(){clockNow=0;ERR_Actors::spawnCalls=0;ERR_Actors::spawned=&car;Replication::server=true;spec.Optional=true;definition.Transport=&spec;site.Definition=&definition;site.guard=READY;svc.m_Sites={&site};
car.controller=&controller;car.security=&security;car.manager=&compartments;car.fuel=&fuel;passenger.type=1;compartments.seats={&driver,&passenger};stock.vehicle=false;unrelated.vehicle=false;svc.m_Entities={&stock,&unrelated};
placement.Item=&stock;placement.Inventory_=&inv;svc.m_Placements={&placement};inv.items={&stock};}
};
int main(){
// Required extraction readiness gates full preparation, while isolated sites
// retain their independent admission contract and publish prepared state.
{Fixture f;f.svc.mode.status.pickup=false;assert(f.svc.Prepare()==FAILED&&ERR_Actors::spawnCalls==0);}
{Fixture f;LocationType location;f.site.Location=&location;f.svc.mode.status.pickup=false;f.svc.m_SingleSite=true;
assert(f.svc.Prepare()==READY&&location.prepared);}
// Required remains the implicit/default contract; optional creation is deferred.
{ERR_SiteTransport_ spec;assert(!spec.Optional);Fixture f;assert(f.svc.CreateTransport(&f.site,{}));assert(!f.site.TransportAttempted&&ERR_Actors::spawnCalls==0);}
// Required guards/responders, stock callbacks and aircraft must finish before survey/spawn.
{Fixture f;f.site.guard=PENDING;f.inv.items.clear();assert(f.svc.Prepare()==PENDING&&f.inv.calls==1&&ERR_Actors::spawnCalls==0);
f.site.guard=READY;f.responders.guard=PENDING;f.site.Responders=&f.responders;f.inv.items={&f.stock};assert(f.svc.Prepare()==PENDING&&ERR_Actors::spawnCalls==0);
f.responders.guard=READY;f.svc.extraction.result=PENDING;assert(f.svc.Prepare()==PENDING&&ERR_Actors::spawnCalls==0);
clockNow=30000;f.svc.extraction.result=READY;assert(f.svc.Prepare()==READY&&ERR_Actors::spawnCalls==1&&f.site.TransportDeadline==45000);
assert(f.svc.Prepare()==READY&&ERR_Actors::spawnCalls==1&&f.car.deletes==0);}
// Optional clearance, spawn, type, wheeled-component and security failures only omit one candidate.
for(int fault=0;fault<5;fault++){Fixture f;
if(fault==0)f.svc.clear=false;if(fault==1)ERR_Actors::spawned=nullptr;if(fault==2)f.car.vehicle=false;if(fault==3)f.car.controller=nullptr;if(fault==4)f.security.good=false;
assert(f.svc.Prepare()==READY&&f.site.TransportAttempted&&f.site.TransportOmitted&&!f.site.Transport&&!f.site.TransportEntity);
assert(f.stock.deletes==0&&f.unrelated.deletes==0&&f.svc.mode.owned.Contains(&f.stock)&&!f.svc.mode.owned.Contains(&f.car));
assert(f.car.deletes==(fault>=2?1:0));
int calls=ERR_Actors::spawnCalls,deletes=f.car.deletes;assert(f.svc.CreateTransport(&f.site,{},true)&&f.svc.Prepare()==READY&&ERR_Actors::spawnCalls==calls&&f.car.deletes==deletes);
f.svc.Stop();f.svc.Stop();assert(f.car.deletes==deletes&&f.svc.patrolStops==1);}
// Readiness gets exactly one independent15s interval, never resets or respawns.
{Fixture f;clockNow=20000;f.fuel.fueled=false;assert(f.svc.Prepare()==PENDING&&f.site.TransportDeadline==35000&&ERR_Actors::spawnCalls==1);
clockNow=35000;assert(f.svc.Prepare()==PENDING&&f.site.TransportDeadline==35000);
clockNow=35001;assert(f.svc.Prepare()==READY&&f.site.TransportOmitted&&f.car.deletes==1&&ERR_Actors::spawnCalls==1);
f.svc.Rollback();f.svc.Rollback();assert(f.car.deletes==1);}
// Deleted native entities null their weak references between polls. No replacement
// or early return may bypass the original optional readiness deadline.
{Fixture f;f.fuel.fueled=false;assert(f.svc.Prepare()==PENDING);f.site.Transport=nullptr;f.site.TransportEntity=nullptr;
clockNow=1000;assert(f.svc.Prepare()==PENDING&&ERR_Actors::spawnCalls==1);
clockNow=15001;assert(f.svc.Prepare()==READY&&f.site.TransportOmitted&&ERR_Actors::spawnCalls==1);}
// An unrelated required failure while the optional car is settling must not
// recursively delete an occupant that arrived before omission could run.
{Fixture f;f.fuel.fueled=false;assert(f.svc.Prepare()==PENDING);Entity occupant;f.passenger.occupant=&occupant;
f.site.guard=FAILED;assert(f.svc.Prepare()==FAILED&&f.car.deletes==0&&occupant.deletes==0&&!f.svc.m_Entities.Contains(&f.car));}
// A temporarily unsettled vehicle can become ready before its deadline.
{Fixture f;f.fuel.fueled=false;assert(f.svc.Prepare()==PENDING);f.fuel.fueled=true;clockNow=14999;assert(f.svc.Prepare()==READY&&!f.site.TransportOmitted&&f.svc.mode.owned.Contains(&f.car));}
// Required transport failure still rolls the complete preparation back.
for(int fault=0;fault<5;fault++){Fixture f;f.spec.Optional=false;
if(fault==0)f.svc.clear=false;if(fault==1)ERR_Actors::spawned=nullptr;if(fault==2)f.car.vehicle=false;if(fault==3)f.car.controller=nullptr;if(fault==4)f.security.good=false;
assert(!f.svc.CreateTransport(&f.site,{})&&f.svc.m_State==FAILED&&!f.site.TransportOmitted&&f.stock.deletes==1&&f.unrelated.deletes==1);}
{Fixture f;f.spec.Optional=false;assert(f.svc.CreateTransport(&f.site,{}));f.fuel.fueled=false;clockNow=15001;assert(f.svc.Prepare()==FAILED&&!f.site.TransportOmitted&&f.car.deletes==1);}
// Stock failure never creates an optional candidate, and omitted transport does
// not bypass a later required guard/stock failure or reclaim occupied actors.
{Fixture f;f.inv.items.clear();clockNow=15001;assert(f.svc.Prepare()==FAILED&&ERR_Actors::spawnCalls==0);}
{Fixture f;f.site.guard=FAILED;assert(f.svc.Prepare()==FAILED&&ERR_Actors::spawnCalls==0);}
// Occupants, characters, missing manager, oversized/cyclic hierarchy remain alive
// but are removed from this service's future cleanup/ownership publication.
for(int unsafe=0;unsafe<6;unsafe++){Fixture f;Entity acquired;acquired.vehicle=false;
if(unsafe==0)f.passenger.occupant=&acquired;if(unsafe==1){f.car.child=&acquired;acquired.character=true;}
if(unsafe==2)f.car.manager=nullptr;if(unsafe==3){f.car.child=&acquired;acquired.child=&acquired;}
if(unsafe==4){for(int n=0;n<65;n++)f.compartments.seats.Insert(&f.driver);}if(unsafe==5)f.car.player=true;
assert(f.svc.CreateTransport(&f.site,{},true));assert(f.svc.TransportFailure(&f.site,"test omission"));
assert(f.car.deletes==0&&!f.svc.m_Entities.Contains(&f.car)&&f.site.TransportOmitted);
f.svc.Fail("later required failure");assert(f.car.deletes==0&&acquired.deletes==0);}
// Forged public transport pointer or missing cleanup ownership cannot cause
// arbitrary deletion; only the separately captured owned spawn is eligible.
{Fixture f;assert(f.svc.CreateTransport(&f.site,{},true));f.site.Transport=&f.unrelated;
assert(f.svc.TransportFailure(&f.site,"failed"));assert(f.car.deletes==1&&f.unrelated.deletes==0);}
{Fixture f;assert(f.svc.CreateTransport(&f.site,{},true));f.svc.m_Entities.RemoveItem(&f.car);
assert(f.svc.TransportFailure(&f.site,"failed"));assert(f.car.deletes==0&&f.unrelated.deletes==0);}
// Stop while pending never creates a replacement or advances to readiness.
{Fixture f;f.fuel.fueled=false;assert(f.svc.Prepare()==PENDING);f.svc.Stop();f.svc.Stop();clockNow=99999;assert(f.svc.Prepare()==FAILED&&ERR_Actors::spawnCalls==1&&f.svc.patrolStops==1);}
}
'''.replace('if(fault==0)f.svc.clear=false;', 'if(fault==0){f.svc.clear=false;}').replace('if(fault==1)ERR_Actors::spawned=nullptr;', 'if(fault==1){ERR_Actors::spawned=nullptr;}').replace('if(fault==2)f.car.vehicle=false;', 'if(fault==2){f.car.vehicle=false;}').replace('if(fault==3)f.car.controller=nullptr;', 'if(fault==3){f.car.controller=nullptr;}').replace('if(fault==4)f.security.good=false;', 'if(fault==4){f.security.good=false;}').replace('if(unsafe==0)f.passenger.occupant=&acquired;', 'if(unsafe==0){f.passenger.occupant=&acquired;}').replace('if(unsafe==2)f.car.manager=nullptr;', 'if(unsafe==2){f.car.manager=nullptr;}').replace('if(unsafe==5)f.car.player=true;', 'if(unsafe==5){f.car.player=true;}'))
print('PASS: optional transport deferred behind required readiness, bounded once-only spawn/omission, isolated cleanup, occupied-candidate retention and required-default preservation')