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

118 lines
12 KiB
Python

"""Execute optional finite PMN4 stock and exact spawn callback methods; no engine proof."""
from pathlib import Path
import os,re
from policy_harness import ROOT,body,run
BASE=Path(os.environ.get('ERR_MINE_STOCK_ROOT',ROOT));D=BASE/'addons/core/Scripts/Game/ERR/10_AI'
s=(D/'ERR_ConvoyMineStock.c').read_text()
assert s.count('TrySpawnPrefabToStorage(')==1
for forbidden in ('TryMove','SetOrigin','SetAmmoCount','DeleteEntity','ActivateTrigger','m_Spent','ERR_FiniteMagazineReservation'):
assert forbidden not in s,forbidden
assert 'm_Task.Cargo.Insert' not in s
code=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('override ','').replace('ref ','').replace('class ','struct ').replace(' : ScriptedInventoryOperationCallback',' : ScriptedInventoryOperationCallback')
code=code.replace('void ERR_ConvoyMineStock(', 'ERR_ConvoyMineStock(').replace('foreach (','for (').replace('array<','Array<').replace('null','nullptr')
for old,new in {
'RplComponent.Cast(Replication.FindItem(GetItem()))':'Replication::FindItem(GetItem())',
'InventoryStorageManagerComponent.Cast(m_Truck.FindComponent(InventoryStorageManagerComponent))':'m_Truck->inventory',
'm_Truck.FindComponent(InventoryStorageManagerComponent)':'m_Truck->inventory',
'InventoryItemComponent.Cast(item.FindComponent(InventoryItemComponent))':'item->carried',
'item.FindComponent(SCR_AntiPersonnelMineCollisionHandlerComponent)':'item->collision',
'SCR_PressureTriggerComponent.Cast(item.FindComponent(SCR_PressureTriggerComponent))':'item->trigger',
}.items():code=code.replace(old,new)
for typ in ('ERR_GameMode','ERR_SupplyConvoyTask','ERR_ConvoyVehicle','InventoryStorageManagerComponent','RplComponent','InventoryItemComponent','SCR_PressureTriggerComponent','ERR_ConvoyMineCargo','ERR_ConvoyCargoCallback'):
code=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',code)
code=code.replace('Array<ERR_ConvoyMineCargo>','Array<ERR_ConvoyMineCargo*>')
code=code.replace('Array<IEntity> items)', 'Array<IEntity>& items)')
for ns in ('ERR_GameMode','ERR_Actors','Replication','ERR_Inventory','ERR_InfantryAmmoTransfer','ERR_ForceRules','ERR_EnemyLoadoutCatalog','EStoragePurpose'):
code=code.replace(ns+'.',ns+'::')
for var in ('m_Mode','m_Task','m_Original','m_Truck','m_Inventory','task','replicated','item','carried','trigger','operation','load','pending','completed'):
code=re.sub(r'\b'+var+r'\.',var+'->',code)
for call in ('GetPrefabData()','GetParentSlot()'):code=code.replace(call+'.',call+'->')
code=re.sub(r'\b(bool|IEntity|ERR_GameMode\*|ERR_SupplyConvoyTask\*|ERR_ConvoyVehicle\*|InventoryStorageManagerComponent\*) (\w+);',r'\1 \2{};',code)
code=code.replace('\n}\n','\n};\n')
run(r'''
#include <cassert>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <functional>
#include <limits>
using string=std::string;
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T v){this->push_back(v);}void Clear(){this->clear();}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
struct Entity;using IEntity=Entity*;struct InventoryStorageManagerComponent;
struct RplComponent{IEntity entity=nullptr;IEntity GetEntity(){return entity;}};
struct Replication{inline static bool server=true;static bool IsServer(){return server;}static RplComponent*FindItem(RplComponent* id){return id;}};
struct ScriptedInventoryOperationCallback{RplComponent*item=nullptr;virtual ~ScriptedInventoryOperationCallback()=default;RplComponent*GetItem(){return item;}virtual void OnComplete(){}virtual void OnAllComplete(){}virtual void OnFailed(){}virtual void OnAllFailed(){}};
struct Prefab{string name="PMN4";string GetPrefabName(){return name;}};
struct SCR_PressureTriggerComponent{bool active=false,byDefault=false;bool IsActivated(){return active;}bool IsActivatedByDefault(){return byDefault;}};
struct Slot{IEntity attached=nullptr;IEntity GetAttachedEntity(){return attached;}};
struct InventoryItemComponent{Slot*slot=nullptr;Slot*GetParentSlot(){return slot;}};
struct Entity{InventoryStorageManagerComponent*inventory=nullptr;Prefab*prefab=nullptr;InventoryItemComponent*carried=nullptr;SCR_PressureTriggerComponent*trigger=nullptr;IEntity stored=nullptr;bool alive=true,collision=true;Prefab*GetPrefabData(){return prefab;}};
struct ERR_GameMode{inline static ERR_GameMode*current=nullptr;bool active=true;static ERR_GameMode*Get(){return current;}bool Active(){return active;}};
struct ERR_ConvoyCargoCallback{bool Finished=true;};
struct ERR_ConvoyVehicle{IEntity Entity_=nullptr;IEntity&Entity=Entity_;};
struct ERR_SupplyConvoyTask{bool Stopped=false,Ready=true,CargoIssued=true,MagazinesIssued=true;Array<ERR_ConvoyVehicle*>Vehicles;Array<ERR_ConvoyCargoCallback*>Cargo;};
struct ERR_Actors{static bool Alive(IEntity e){return e&&e->alive;}};
struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}};
struct ERR_EnemyLoadoutCatalog{inline static string MINE="PMN4";inline static bool available=true;static bool Available(string){return available;}};
struct ERR_InfantryAmmoTransfer{static bool StoredUnder(IEntity e,IEntity root){return e&&root&&e->stored==root;}};
struct EStoragePurpose{static const int PURPOSE_DEPOSIT=1;};
struct InventoryStorageManagerComponent{IEntity owner=nullptr;Array<IEntity>items;Array<ScriptedInventoryOperationCallback*>callbacks;int calls=0;bool accept=true;std::function<void(int,ScriptedInventoryOperationCallback*)>effect;
IEntity GetOwner(){return owner;}bool TrySpawnPrefabToStorage(string prefab,void*,int slot,int purpose,ScriptedInventoryOperationCallback*cb){assert(prefab=="PMN4"&&slot==-1&&purpose==1);int index=calls++;callbacks.Insert(cb);if(effect)effect(index,cb);return accept;}};
struct ERR_Inventory{static void Items(InventoryStorageManagerComponent*i,Array<IEntity>&a){a=i->items;}};
'''+code+r'''
struct Fixture{ERR_GameMode mode;ERR_SupplyConvoyTask task;ERR_ConvoyVehicle truck;Entity root,foreign;InventoryStorageManagerComponent inventory;ERR_ConvoyCargoCallback primary;
Entity mines[2];Prefab prefabs[2];InventoryItemComponent carried[2];Slot slots[2];SCR_PressureTriggerComponent triggers[2];RplComponent replicated[2];
Fixture(){ERR_GameMode::current=&mode;ERR_EnemyLoadoutCatalog::available=true;Replication::server=true;truck.Entity=&root;root.inventory=&inventory;inventory.owner=&root;
task.Vehicles.resize(6,nullptr);task.Vehicles[1]=&truck;task.Cargo={&primary};
for(int i=0;i<2;i++){mines[i].prefab=&prefabs[i];mines[i].carried=&carried[i];mines[i].trigger=&triggers[i];mines[i].stored=&root;carried[i].slot=&slots[i];slots[i].attached=&mines[i];replicated[i].entity=&mines[i];}}
ERR_ConvoyMineStock Job(){return ERR_ConvoyMineStock(&mode,&task);}
void Finish(int i){inventory.items.Insert(&mines[i]);inventory.callbacks[i]->item=&replicated[i];inventory.callbacks[i]->OnComplete();}
};
int main(){
{Fixture f;auto s=f.Job();s.Tick(0,true);assert(f.inventory.calls==2&&!s.Complete()&&s.Count()==0&&f.task.Cargo.Count()==1);
f.Finish(0);s.Tick(1000,true);assert(!s.Complete()&&s.Count()==0);f.Finish(1);s.Tick(2000,true);
assert(s.Complete()&&s.Count()==2&&s.At(0)==&f.mines[0]&&s.At(1)==&f.mines[1]&&!s.At(-1)&&!s.At(2));
f.inventory.items.clear();s.Tick(3000,true);assert(s.Count()==2&&f.inventory.calls==2);}
// Primary unknown/missing callbacks never become optional-magazine blockers.
for(int k=0;k<3;k++){Fixture f;if(k==0)f.primary.Finished=false;if(k==1)f.task.Cargo[0]=nullptr;if(k==2)f.task.MagazinesIssued=false;
auto s=f.Job();s.Tick(0,true);assert(f.inventory.calls==0&&!s.Complete());s.Tick(90000,true);assert(s.Complete());
f.primary.Finished=true;f.task.Cargo[0]=&f.primary;f.task.MagazinesIssued=true;s.Tick(90001,true);assert(f.inventory.calls==0);}
// Physical item and combined callback headroom independently limit the extras.
for(int k=0;k<4;k++){Fixture f;if(k<2)f.inventory.items.resize(127+k,&f.foreign);else f.task.Cargo.resize(125+k,&f.primary);
auto s=f.Job();s.Tick(0,true);assert(f.inventory.calls==(k%2==0?1:0));}
{Fixture f;f.inventory.items.resize(129,&f.foreign);auto s=f.Job();s.Tick(0,true);assert(s.Complete()&&f.inventory.calls==0);}
{Fixture f;f.task.Cargo.resize(129,&f.primary);auto s=f.Job();s.Tick(0,true);assert(s.Complete()&&f.inventory.calls==0);}
// Native refusal consumes an attempt; inline completion beats false return.
{Fixture f;f.inventory.accept=false;auto s=f.Job();s.Tick(0,true);assert(s.Complete()&&s.Count()==0&&f.inventory.calls==2);s.Tick(1,true);assert(f.inventory.calls==2);}
{Fixture f;f.inventory.accept=false;f.inventory.effect=[&](int i,ScriptedInventoryOperationCallback*cb){f.inventory.items.Insert(&f.mines[i]);cb->item=&f.replicated[i];cb->OnComplete();};
auto s=f.Job();s.Tick(0,true);assert(s.Complete()&&s.Count()==2&&f.inventory.calls==2);}
// Unknown, captured or lost optional callbacks cannot reopen a closed snapshot.
for(int k=0;k<6;k++){Fixture f;auto s=f.Job();s.Tick(0,true);if(k==0)s.Stop();if(k==1)s.Tick(1,false);if(k==2)s.Tick(90000,true);
if(k==3){f.truck.Entity=&f.foreign;s.Tick(1,true);}if(k==4){f.root.inventory=nullptr;s.Tick(1,true);}if(k==5){f.mode.active=false;s.Tick(1,true);}
assert(s.Complete());f.Finish(0);f.Finish(1);s.Tick(90001,true);assert(s.Count()==0&&f.inventory.calls==2&&f.task.Cargo.Count()==1);}
// Only exact callback-produced, current disarmed owned PMN4 items freeze.
for(int k=0;k<12;k++){Fixture f;auto s=f.Job();if(k==0){f.inventory.items.Insert(&f.mines[0]);}s.Tick(0,true);f.Finish(0);f.inventory.callbacks[1]->OnFailed();
switch(k){case 1:f.prefabs[0].name="other";break;case 2:f.mines[0].prefab=nullptr;break;case 3:f.mines[0].carried=nullptr;break;case 4:f.carried[0].slot=nullptr;break;
case 5:f.slots[0].attached=&f.foreign;break;case 6:f.mines[0].stored=&f.foreign;break;case 7:f.mines[0].alive=false;break;case 8:f.mines[0].collision=false;break;
case 9:f.mines[0].trigger=nullptr;break;case 10:f.triggers[0].active=true;break;case 11:f.triggers[0].byDefault=true;break;}
s.Tick(1,true);assert(s.Complete()&&s.Count()==0);}
{Fixture f;auto s=f.Job();s.Tick(0,true);f.Finish(0);f.inventory.callbacks[1]->item=&f.replicated[0];f.inventory.callbacks[1]->OnComplete();s.Tick(1,true);assert(s.Count()==1);}
{Fixture f;auto s=f.Job();s.Tick(0,true);f.inventory.callbacks[0]->OnComplete();f.inventory.callbacks[1]->OnComplete();s.Tick(1,true);assert(s.Count()==0&&s.Complete());}
// All current-server/task/truck/manager guards fail before optional spawning.
for(int k=0;k<10;k++){Fixture f;auto s=f.Job();ERR_GameMode other;ERR_ConvoyVehicle replaced;
switch(k){case 0:Replication::server=false;break;case 1:f.mode.active=false;break;case 2:ERR_GameMode::current=&other;break;case 3:f.task.Stopped=true;break;
case 4:f.task.Ready=false;break;case 5:f.task.Vehicles[1]=&replaced;break;case 6:f.truck.Entity=&f.foreign;break;case 7:f.inventory.owner=&f.foreign;break;case 8:f.root.inventory=nullptr;break;case 9:ERR_EnemyLoadoutCatalog::available=false;break;}
s.Tick(0,true);assert(s.Complete()&&f.inventory.calls==0);}
{Fixture f;auto s=f.Job();s.Tick(std::numeric_limits<float>::quiet_NaN(),true);assert(s.Complete()&&f.inventory.calls==0);}
{Fixture f;auto s=f.Job();s.Tick(10,true);s.Tick(9,true);assert(s.Complete());f.Finish(0);f.Finish(1);s.Tick(11,true);assert(s.Count()==0);}
{Fixture f;auto s=f.Job();s.Stop();s.Tick(0,true);assert(f.inventory.calls==0&&s.Count()==0);}
}
'''.replace(';if(', ';\n if('))
service=(D/'ERR_SupplyConvoyService.c').read_text();task=(D/'ERR_SupplyConvoyTactics.c').read_text()
issue=body(D/'ERR_SupplyConvoyService.c','protected override void IssueCargo(')
assert issue.index('supply.CargoIssued = true;')<issue.index('supply.MineStock = new ERR_ConvoyMineStock')<issue.index('TrySpawnPrefabToStorage')
assert 'if (task.MineStock) task.MineStock.Tick(now, MountedStockOwner(task, 1, ERR_HQ.Cast(m_Mode.Command())));' in service
assert 'ref ERR_ConvoyMineStock MineStock;' in task and 'if (MineStock) MineStock.Stop();' in task
print('PASS: exact optional PMN4 startup, primary callback independence, original owner,128 capacity/callback limits, terminal no-retry and callback-proven freeze')