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

106 lines
12 KiB
Python

"""Execute complete actual finite two-leg transfer with native inventory boundaries."""
import os,re
from pathlib import Path
from policy_harness import ROOT,run
from test_finite_magazine_reservation import translated_reservation
p=Path(os.environ.get('ERR_GROUND_DEPOT_TRANSFER_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotTransfer.c'))
s=p.read_text();code=s[s.index('class ERR_GroundDepotTransfer\n'):]
code=re.sub(r'//[^\n]*','',code).replace('protected ','').replace('ref ','').replace('class ','struct ').replace('void ERR_GroundDepotTransfer(','ERR_GroundDepotTransfer(').replace('array<','Array<').replace('null','nullptr')
for kind,field in {'InventoryStorageManagerComponent':'inventory','InventoryItemComponent':'item','BaseMagazineComponent':'magazine','DamageManagerComponent':'damage'}.items():
code=re.sub(kind+r'\.Cast\(([^;\n]*?)\.FindComponent\('+kind+r'\)\)',lambda m:m[1]+'.'+field,code)
code=code.replace('.FindComponent('+kind+')','.'+field)
for name in ('EStoragePurpose','ERR_ForceRules','ERR_InfantryAmmoTransfer','ERR_InfantryMineTransfer'):code=code.replace(name+'.',name+'::')
for name in ('m_Owner','m_From','m_To','m_Item','m_Source','m_Destination','m_FromSlot','m_FromStorage','m_ToStorage','m_Reservation','m_Callback','source','destination','item','inventoryItem','magazine','damage','inventory','storage','slot'):
code=re.sub(r'\b'+name+r'\.',name+'->',code)
code=code.replace(').',')->').replace('ERR_FiniteMagazineReservation ', 'ERR_FiniteMagazineReservation* ').replace('ERR_GroundDepotCallback ', 'ERR_GroundDepotCallback* ')
code=re.sub(r'\b(bool|int|IEntity|ERR_GroundDepotLoading|InventoryStorageManagerComponent|InventoryStorageSlot|BaseInventoryStorageComponent|ERR_FiniteMagazineReservation\*|ERR_GroundDepotCallback\*) (\w+);',r'\1 \2{};',code)
code=code.replace('\n}\n','\n};\n')
callbacks=[]
for n in ('OnComplete','OnAllComplete','OnFailed','OnAllFailed'):
b=re.search('protected override void '+n+r'\(\) (\{[^\n]+\})',s)[1]
callbacks.append('void ERR_GroundDepotCallback::'+n+'() '+b.replace('Transfer.','Transfer->'))
pre=r'''
#include <cassert>
#include <cmath>
#include <vector>
#include <algorithm>
#include <functional>
#include <limits>
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool Contains(T x){return std::find(this->begin(),this->end(),x)!=this->end();}void Insert(T x){this->push_back(x);}void RemoveItem(T x){this->erase(std::remove(this->begin(),this->end(),x),this->end());}};
struct Entity;struct Inventory;struct Slot;struct Storage;struct Magazine;struct Owner;
using IEntity=Entity*;using InventoryStorageManagerComponent=Inventory*;using InventoryStorageSlot=Slot*;using BaseInventoryStorageComponent=Storage*;using BaseMagazineComponent=Magazine*;using ERR_GroundDepotLoading=Owner*;
struct Item{Slot*slot=nullptr;Slot*GetParentSlot(){return slot;}};using InventoryItemComponent=Item*;
struct Damage{bool destroyed=false;bool IsDestroyed(){return destroyed;}};using DamageManagerComponent=Damage*;
struct Entity{Entity*parent=nullptr;Item*item=nullptr;Magazine*magazine=nullptr;Damage*damage=nullptr;Inventory*inventory=nullptr;bool mine=false,armed=false,defaultArmed=false,collision=true;};
struct Slot{Storage*storage=nullptr;IEntity attached=nullptr;Storage*GetStorage(){return storage;}IEntity GetAttachedEntity(){return attached;}};
struct Storage{IEntity owner=nullptr;IEntity GetOwner(){return owner;}};
struct Magazine{bool used=false,well=true;int rounds=30,capacity=30;bool IsUsed(){return used;}bool GetMagazineWell(){return well;}int GetAmmoCount(){return rounds;}int GetMaxAmmoCount(){return capacity;}};
struct ERR_ForceRules{static bool Finite(float x){return std::isfinite(x);}};
struct ERR_InfantryAmmoTransfer{static bool StoredUnder(IEntity e,IEntity root){for(int i=0;e&&i<16;i++,e=e->parent)if(e==root)return true;return false;}};
// ReadyMine is an explicit native-component boundary; its complete implementation
// is covered by infantry mine transfer tests. This models exact inert PMN4 state.
struct ERR_InfantryMineTransfer{static bool ReadyMine(IEntity e){return e&&e->mine&&e->damage&&!e->damage->destroyed&&!e->armed&&!e->defaultArmed&&e->collision;}};
struct EStoragePurpose{static const int PURPOSE_DEPOSIT=1;};
struct ScriptedInventoryOperationCallback{virtual void OnComplete(){}virtual void OnAllComplete(){}virtual void OnFailed(){}virtual void OnAllFailed(){}virtual ~ScriptedInventoryOperationCallback()=default;};
struct ERR_GroundDepotTransfer;
struct ERR_GroundDepotCallback:ScriptedInventoryOperationCallback{ERR_GroundDepotTransfer*Transfer=nullptr;void OnComplete()override;void OnAllComplete()override;void OnFailed()override;void OnAllFailed()override;};
struct Owner{bool valid=true,claimed=false;int claims=0;bool TransferValid(ERR_GroundDepotTransfer*){return valid;}bool ClaimDelivery(ERR_GroundDepotTransfer*){claims++;if(claimed)return false;claimed=true;return true;}};
struct Inventory{IEntity owner=nullptr;Array<IEntity>items;Array<Storage*>storages;Storage*selected=nullptr;bool allow=true,accept=true;int calls=0,ack=0;ScriptedInventoryOperationCallback*last=nullptr;std::function<void()>effect;
IEntity GetOwner(){return owner;}void GetItems(Array<IEntity>&a,int){a=items;}void GetStorages(Array<Storage*>&a,int){a=storages;}Storage*FindStorageForItem(IEntity,int){return selected;}
bool CanMoveItemToStorage(IEntity,Storage*,int){return allow;}
bool TryMoveItemToStorage(IEntity,Storage*,int,ScriptedInventoryOperationCallback*cb){calls++;last=cb;if(effect)effect();if(ack==1)cb->OnComplete();if(ack==-1)cb->OnFailed();return accept;}};
'''
main=r'''
struct Fixture{bool mine=false;Owner owner;Entity cache,porter,truck,mag,foreign;Inventory source,carry,cargo;Storage cs,ps,ts;Slot cslot,pslot,tslot;Item item;Magazine magazine;Damage damage;Array<IEntity>original,spent;ERR_FiniteMagazineReservation reservation;
Fixture():reservation(&original,&spent,&mag){cache.inventory=&source;porter.inventory=&carry;truck.inventory=&cargo;source.owner=&cache;carry.owner=&porter;cargo.owner=&truck;cs.owner=&cache;ps.owner=&porter;ts.owner=&truck;source.storages={&cs};carry.storages={&ps};cargo.storages={&ts};carry.selected=&ps;cargo.selected=&ts;cslot.storage=&cs;pslot.storage=&ps;tslot.storage=&ts;cslot.attached=&mag;item.slot=&cslot;mag.item=&item;mag.magazine=&magazine;mag.damage=&damage;source.items={&mag};original={&mag};assert(reservation.Reserve());}
ERR_GroundDepotTransfer Pickup(){return {&owner,&cache,&porter,&mag ,&reservation,false,mine};}
ERR_GroundDepotTransfer Delivery(){return {&owner,&porter,&truck,&mag ,&reservation,true,mine};}
void Pick(){source.items.clear();carry.items={&mag};cslot.attached=nullptr;pslot.attached=&mag;item.slot=&pslot;}
void Deliver(){carry.items.clear();cargo.items={&mag};pslot.attached=nullptr;tslot.attached=&mag;item.slot=&tslot;}
};
int main(){
// Ready inert PMN4 uses the same exact native two-leg custody protocol,
// shared reservation and isolated callbacks, without a magazine component.
{Fixture f;f.mine=f.mag.mine=true;f.mag.magazine=nullptr;auto a=f.Pickup();assert(a.MineCargo()&&a.Tick(0)==0&&a.Unresolved());f.Pick();f.source.last->OnComplete();assert(a.Tick(1)==1&&a.Confirmed());auto b=f.Delivery();assert(b.MineCargo()&&b.Tick(2)==0&&f.owner.claims==1);f.Deliver();f.carry.last->OnComplete();assert(b.Tick(3)==1&&b.Confirmed()&&!b.Unresolved()&&f.spent.Count()==1);}
// Every phase refuses changed/armed/default-armed/non-PMN4/destroyed identity.
for(int phase=0;phase<3;phase++)for(int kind=0;kind<5;kind++){Fixture f;f.mine=f.mag.mine=true;f.mag.magazine=nullptr;auto a=f.Pickup();
if(phase==1)assert(a.Tick(0)==0);
if(phase==2){assert(a.Tick(0)==0);f.Pick();f.source.last->OnComplete();assert(a.Tick(1)==1);}
if(kind==0)f.mag.armed=true;
if(kind==1)f.mag.defaultArmed=true;
if(kind==2)f.mag.mine=false;
if(kind==3)f.damage.destroyed=true;
if(kind==4)f.mag.collision=false;
if(phase==2){auto b=f.Delivery();assert(b.Tick(2)==-1&&!b.Submitted()&&f.carry.calls==0&&!a.Confirmed());}
else{assert(a.Tick(1)==-1);assert(f.source.calls==(phase==1?1:0));if(phase==1)assert(a.Unresolved());}
}
// Inline native mutation after initial ready check cannot promote success.
{Fixture f;f.mine=f.mag.mine=true;auto a=f.Pickup();f.source.ack=1;f.source.effect=[&]{f.Pick();f.mag.armed=true;};assert(a.Tick(0)==-1&&!a.Confirmed()&&!a.Unresolved());}
{Fixture f;f.mine=f.mag.mine=true;auto a=f.Pickup();assert(a.Tick(0)==0);a.Stop();f.Pick();f.source.last->OnComplete();assert(a.Tick(1)==-1&&!a.Confirmed()&&!a.Unresolved()&&f.source.calls==1&&f.spent.Count()==1);}
{Fixture f;f.mine=f.mag.mine=true;f.source.accept=false;auto a=f.Pickup();assert(a.Tick(0)==-1&&a.Unresolved());f.damage.destroyed=true;assert(a.Unresolved());a.m_Item=nullptr;assert(!a.Unresolved()&&f.spent.Count()==1);}
{Fixture f;f.mine=f.mag.mine=true;f.mag.magazine=nullptr;auto wrong=ERR_GroundDepotTransfer(&f.owner,&f.cache,&f.porter,&f.mag,&f.reservation,false,false);assert(wrong.Tick(0)==-1&&!wrong.Submitted()&&f.source.calls==0);}
{Fixture f;auto a=f.Pickup();a.Acknowledge(true);assert(a.m_Ack==0);assert(a.Tick(0)==0&&a.Submitted()&&a.Unresolved());assert(f.reservation.CommittedFor(&f.mag));f.source.last->OnComplete();assert(a.Tick(1)==0);f.Pick();assert(a.Tick(2)==1&&a.Confirmed()&&!a.Unresolved());auto b=f.Delivery();assert(b.Tick(3)==0&&f.owner.claims==1);f.source.last->OnAllComplete();assert(b.Tick(4)==0);f.Deliver();assert(b.Tick(5)==0);f.carry.last->OnAllComplete();assert(b.Tick(6)==1&&b.Confirmed()&&!b.Unresolved());assert(f.source.calls==1&&f.carry.calls==1&&f.spent.Count()==1);}
{Fixture f;f.source.ack=1;f.source.accept=false;f.source.effect=[&]{assert(f.reservation.CommittedFor(&f.mag));assert(!f.reservation.ReleaseUnsubmitted());f.Pick();};auto a=f.Pickup();assert(a.Tick(0)==1&&!a.Unresolved());}
{Fixture f;f.source.accept=false;auto a=f.Pickup();assert(a.Tick(0)==-1&&a.Unresolved());a.Stop();f.Pick();f.source.last->OnComplete();assert(!a.Unresolved()&&!a.Confirmed()&&a.Tick(1)==-1);}
{Fixture f;auto a=f.Pickup();a.Tick(0);a.Stop();f.Pick();f.source.last->OnComplete();assert(!a.Unresolved()&&!a.Confirmed()&&a.Tick(1)==-1);}
{Fixture f;auto a=f.Pickup();f.source.ack=1;f.source.effect=[&]{a.Stop();f.Pick();};assert(a.Tick(0)==-1&&!a.Confirmed()&&!a.Unresolved());}
{Fixture f;auto a=f.Pickup();f.source.ack=1;f.source.effect=[&]{f.owner.valid=false;f.Pick();};assert(a.Tick(0)==-1&&!a.Confirmed()&&!a.Unresolved());}
{Fixture f;auto a=f.Pickup();a.Tick(0);f.source.last->OnComplete();f.source.last->OnFailed();assert(a.Tick(1)==-1&&!a.Unresolved());f.source.last->OnAllComplete();assert(a.Tick(2)==-1);}
{Fixture f;auto a=f.Pickup();a.Tick(0);f.item.slot=nullptr;f.source.last->OnFailed();assert(a.Tick(1)==-1&&a.Unresolved());}
{Fixture f;auto a=f.Pickup();a.Tick(0);assert(a.Tick(8000)==-1&&a.Unresolved());assert(f.source.calls==1&&!f.reservation.ReleaseUnsubmitted());}
{Fixture f;auto a=f.Pickup();a.Tick(10);assert(a.Tick(9)==-1&&a.Unresolved());}
{Fixture f;auto a=f.Pickup();a.Stop();assert(a.Tick(0)==-1&&!a.Submitted()&&f.reservation.ReleaseUnsubmitted());}
{Fixture f;auto a=f.Pickup();a.Tick(0);a.m_Item=nullptr;assert(a.Tick(1)==-1&&!a.Unresolved()&&f.spent.Count()==1);}
{Fixture f;auto a=f.Pickup();a.Tick(0);f.damage.destroyed=true;assert(a.Tick(1)==-1&&a.Unresolved());}
for(int bad=0;bad<15;bad++){Fixture f;auto a=f.Pickup();switch(bad){case0:break;}
if(bad==0)f.magazine.used=true;if(bad==1)f.magazine.rounds=0;if(bad==2)f.magazine.capacity=100001;if(bad==3)f.damage.destroyed=true;if(bad==4)f.owner.valid=false;if(bad==5)f.source.allow=false;if(bad==6)f.carry.selected=nullptr;if(bad==7)f.cslot.attached=&f.foreign;if(bad==8)f.item.slot=&f.pslot;if(bad==9)f.source.items.clear();if(bad==10)f.carry.items={&f.mag};if(bad==11)f.carry.storages.clear();if(bad==12)f.carry.owner=&f.foreign;if(bad==13)f.source.items.resize(129,&f.mag);if(bad==14)f.source.storages.resize(65,&f.cs);
assert(a.Tick(0)==-1&&!a.Submitted()&&f.source.calls==0&&f.reservation.ReleaseUnsubmitted());}
{Fixture f;auto a=f.Pickup();a.Tick(0);f.magazine.rounds--;assert(a.Tick(1)==-1&&a.Unresolved());}
{Fixture f;f.Pick();assert(f.reservation.Commit());auto a=f.Delivery();a.Tick(0);auto b=f.Delivery();assert(b.Tick(1)==-1&&!b.Submitted()&&f.carry.calls==1);}
}
'''.replace('switch(bad){case0:break;}','')
main=main.replace(';if(bad', ';\n if(bad')
run(pre+translated_reservation()+code+'\n'.join(callbacks)+main)
print('PASS: actual full magazine/inert-PMN4 depot leaf, exact custody, isolated callbacks, finite reservation, false/unknown and Stop quarantine, once-only delivery and caps; native movement unqualified')