--- /dev/null +++ b/tests/test_ground_depot_transfer.py @@ -0,0 +1,81 @@ +"""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'):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 +#include +#include +#include +#include +#include +templatestruct Array:std::vector{using std::vector::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;}; +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;}}; +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;Arrayitems;Arraystorages;Storage*selected=nullptr;bool allow=true,accept=true;int calls=0,ack=0;ScriptedInventoryOperationCallback*last=nullptr;std::functioneffect; + IEntity GetOwner(){return owner;}void GetItems(Array&a,int){a=items;}void GetStorages(Array&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{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;Arrayoriginal,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};} + ERR_GroundDepotTransfer Delivery(){return {&owner,&porter,&truck,&mag,&reservation,true};} + 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(){ + {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 depot leaf, exact custody, isolated callbacks, finite reservation, false/unknown and Stop quarantine, once-only delivery and caps; native movement unqualified') --- /dev/null +++ b/tests/test_ground_depot_provenance.py @@ -0,0 +1,83 @@ +"""Actual authored-stock capture/access and exact post-delivery stock admission.""" +import os,re +from pathlib import Path +from policy_harness import ROOT,body,run +from test_finite_magazine_reservation import translated_reservation +B=Path(os.environ.get('ERR_GROUND_DEPOT_ROOT',ROOT)) +S=Path(os.environ.get('ERR_SITE_SERVICE_SOURCE',B/'addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c')) +I=B/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c' +L=B/'addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotLoading.c' +source=S.read_text();start=source.index(' if (spec.Kind == ERR_LocationKind.OUTPOST');capture=source[start:source.index(' placement.Item = item;',start)] +access='\n'.join(body(S,k) for k in ('int DepotMagazineCount(', 'void DepotStock(', 'IEntity DepotMagazine(', 'bool DepotOriginal(')) +admit='\n'.join(body(I,k) for k in ('bool DepotCurrent(', 'bool DepotAvailable(', 'bool DepotNeeded(', 'bool AdmitDepot(')) +delivered=body(L,'bool DeliveredTo(') +def adapt(s): + s=s.replace('array','Array').replace('out Array items','Array& items').replace('ERR_SiteRuntime site','Site* site').replace('ERR_GroundDepotLoading job','Loading* job').replace('ERR_InfantryResupply service','Service* service') + for name in ('site','spec','stock','item','m_Stock','m_Spent','m_Mounted','m_Truck','job','m_Move','m_Reservation'): + if name not in ('m_Stock','m_Spent'): s=s.replace(name+'.',name+'->') + s=s.replace('site->Definition.', 'site->Definition->').replace('site->Responders.', 'site->Responders->') + s=s.replace('stock->TargetId.IsEmpty()','stock->TargetId.empty()').replace('item->FindComponent(BaseMagazineComponent)','item->magazine') + s=s.replace('foreach (IEntity item : m_Spent)', 'for (IEntity item : m_Spent)').replace('int committed;', 'int committed=0;') + s=s.replace('Replication.IsServer()','server').replace('ERR_PrepareResult.READY','1').replace('ERR_LocationKind.OUTPOST','3').replace('null','nullptr') + return s +run(r''' +#include +#include +#include +#include +using string=std::string;bool server=true; +templatestruct Array:std::vector{using std::vector::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{bool magazine=true;};using IEntity=Entity*; +struct Def{int Kind=3;bool RevealOnEscape=false;};struct Stock{string TargetId;}; +struct Responders{bool Group=true;};struct Site{Def* Definition=nullptr;IEntity Container=nullptr;struct Responders* Responders=nullptr;ArrayDepotMagazines;}; +struct Sites{bool m_Stopped=false;int m_State=0;Arraym_Sites;Arraym_Entities;bool Owns(IEntity x){return m_Entities.Contains(x);} +''' + adapt(access)+ '\nvoid Capture(Def* spec,Stock* stock,Site* site,IEntity item){'+adapt(capture)+r'''} +}; +''' + translated_reservation()+r''' +struct Service; +struct Proof{bool done=true,delivery=true;bool Confirmed(){return done;}bool Delivery(){return delivery;}}; +struct Loading{bool m_Imported=false,valid=true;int m_State=5;Service* m_Service=nullptr;IEntity m_Item=nullptr;Proof* m_Move=nullptr;ERR_FiniteMagazineReservation* m_Reservation=nullptr;bool Valid(){return valid;}bool DeliveredTo(Service*,IEntity);}; +struct Mounted{bool busy=false;bool Busy(){return busy;}}; +struct Truck{IEntity Entity=nullptr;}; +struct Service{bool current=true,truckValid=true,unresolved=false,m_Move=false,m_MineMove=false;int m_State=1,audits=0,m_DepotImports=0;Arraym_Stock,m_Spent,cargo;Mounted* m_Mounted=nullptr;Truck* m_Truck=nullptr;bool Current(){return current;}bool Unresolved(){return unresolved;}bool TruckValid(bool endpoint){assert(endpoint);return truckValid;}bool Contains(IEntity,IEntity item){return cargo.Contains(item);}void Audit(string){audits++;} +''' + adapt(admit)+r'''}; +''' + adapt(delivered).replace('bool DeliveredTo(', 'bool Loading::DeliveredTo(')+r''' +struct F{Entity cache,item,foreign,truck;Def def;Stock stock;Responders responders;Site site;Sites sites;Arrayoriginal,spent;ERR_FiniteMagazineReservation reservation;Service service;Loading job;Proof proof;Truck vehicle;Mounted mounted; + F():reservation(&original,&spent,&item){site.Definition=&def;site.Container=&cache;site.Responders=&responders;sites.m_Sites={&site};sites.m_Entities={&cache};sites.Capture(&def,&stock,&site,&item);original={&item};assert(reservation.Reserve()&&reservation.Commit());job.m_Reservation=&reservation;job.m_Move=&proof;job.m_Item=&item;job.m_Service=&service;vehicle.Entity=&truck;service.m_Truck=&vehicle;service.m_Mounted=&mounted;service.cargo={&item};} +}; +int main(){ + {F f;Arrayout;f.sites.DepotStock(&f.site,out);assert(out.empty());f.sites.m_State=1;f.sites.DepotStock(&f.site,out);assert(out.Count()==1&&out[0]==&f.item);out[0]=&f.foreign;out.clear();assert(f.sites.DepotOriginal(&f.site,&f.item)&&!f.sites.DepotOriginal(&f.site,&f.foreign));assert(f.sites.DepotMagazine(&f.site,-1)==nullptr&&f.sites.DepotMagazine(&f.site,1)==nullptr);f.sites.m_Stopped=true;assert(!f.sites.DepotOriginal(&f.site,&f.item));} + for(int kind=0;kind<6;kind++){F f;f.site.DepotMagazines.clear();if(kind==0)f.def.RevealOnEscape=true;if(kind==1)f.def.Kind=4;if(kind==2)f.stock.TargetId="radio";if(kind==3)f.item.magazine=false;if(kind==4)f.site.DepotMagazines.resize(96,&f.foreign);f.sites.Capture(&f.def,&f.stock,&f.site,&f.item);assert(f.site.DepotMagazines.Contains(&f.item)==(kind==5));} + for(int bad=0;bad<7;bad++){F f;f.sites.m_State=1;if(bad==0)server=false;if(bad==1)f.sites.m_Sites.clear();if(bad==2)f.site.Container=nullptr;if(bad==3)f.sites.m_Entities.clear();if(bad==4)f.site.Responders=nullptr;if(bad==5)f.responders.Group=false;if(bad==6)f.site.DepotMagazines.resize(97,&f.item);assert(f.sites.DepotMagazineCount(&f.site)==0);server=true;} + {F f;assert(f.service.AdmitDepot(&f.job,&f.item));assert(f.service.m_Stock.Count()==1&&f.service.m_Stock[0]==&f.item&&f.service.audits==1);assert(!f.service.AdmitDepot(&f.job,&f.item));ERR_FiniteMagazineReservation outgoing(&f.service.m_Stock,&f.service.m_Spent,&f.item);assert(outgoing.Reserve()&&outgoing.Commit());assert(f.spent.Count()==1&&f.service.m_Spent.Count()==1);assert(!f.service.AdmitDepot(&f.job,&f.item));} + // Deleted identities and mine-only spent entries cannot create magazine demand. + {F f;f.service.m_Stock={nullptr};f.service.m_Spent={nullptr,&f.foreign};assert(!f.service.DepotNeeded());f.service.m_Stock.Insert(&f.foreign);assert(f.service.DepotNeeded());f.service.m_DepotImports=1;assert(!f.service.DepotNeeded());Entity another;f.service.m_Stock.Insert(&another);f.service.m_Spent.Insert(&another);assert(f.service.DepotNeeded());} + // Final lifetime slot can complete delivery and departure; admission then closes. + {F f;f.service.m_Stock.resize(95,&f.foreign);f.service.m_Spent={&f.foreign};assert(f.service.DepotNeeded());assert(f.service.AdmitDepot(&f.job,&f.item));assert(f.service.m_Stock.Count()==96&&f.service.m_DepotImports==1);assert(f.service.DepotCurrent()&&!f.service.DepotAvailable()&&!f.service.DepotNeeded());} + for(int bad=0;bad<21;bad++){F f; + if(bad==0)f.service.current=false; + if(bad==1)f.service.m_State=7; + if(bad==2)f.service.m_Move=true; + if(bad==3)f.service.m_MineMove=true; + if(bad==4)f.service.unresolved=true; + if(bad==5)f.mounted.busy=true; + if(bad==6)f.service.truckValid=false; + if(bad==7)f.service.m_Stock.resize(96,&f.foreign); + if(bad==8)f.service.m_Spent.resize(96,&f.foreign); + if(bad==9)f.service.m_Spent={&f.item}; + if(bad==10)f.service.cargo.clear(); + if(bad==11)f.job.m_Imported=true; + if(bad==12)f.job.m_State=7; + if(bad==13)f.job.m_Service=nullptr; + if(bad==14)f.job.m_Item=&f.foreign; + if(bad==15)f.proof.done=false; + if(bad==16)f.proof.delivery=false; + if(bad==17)f.job.m_Move=nullptr; + if(bad==18)f.job.m_Reservation=nullptr; + if(bad==19)f.job.valid=false; + if(bad==20)f.spent.clear(); + int before=f.service.m_Stock.Count();assert(!f.service.AdmitDepot(&f.job,&f.item));assert(f.service.m_Stock.Count()==before&&f.service.audits==0);} + {F f;f.job.m_State=7;f.proof.done=true;assert(!f.service.AdmitDepot(&f.job,&f.item));f.job.m_State=5;f.job.valid=false;assert(!f.service.AdmitDepot(&f.job,&f.item));} +} +''') +print('PASS: actual authored identity capture, READY-only detached provenance, same shared stock admission, completed exact two-leg proof, lifetime caps and Stop no late adoption')