Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
118 lines
12 KiB
Python
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')
|